lsascha / registeraddress

newsletter registration extension to tt_address made in Extbase + Fluid for Typo3. with double-opt in, user editing and unsubscribing
GNU General Public License v2.0
7 stars 18 forks source link

Using directmail categories #6

Open hannesbochmann opened 6 years ago

hannesbochmann commented 6 years ago

Is it possible to give the user a selection of directmail categories in the subscription form? As far as I understand I would to need configure a subclass for the domain model in the persistence section of the TypoScript configuration to be able to save the field module_sys_dmail_category. Furthermore I would need a ViewHelper to provide the categories for the selection in the template. Or do you see a better way?

hannesbochmann commented 6 years ago

Here is a short wrap up what I did to support the categories:

Add custom model with the following TS:

config.tx_extbase{
    objects {
        AFM\Registeraddress\Domain\Model\Address.className = DMK\Myext\Domain\Model\Address
    }
    persistence{
        classes{
            DMK\Myext\Domain\Model\Address {
                mapping {
                    tableName = tt_address
                    recordType = Tx_Registeraddress_Address
                    columns {
                        module_sys_dmail_category.mapOnProperty = moduleSysDmailCategory
                    }
                }
            }
            DMK\Myext\Domain\Model\NewsletterCategory {
                mapping {
                    tableName = sys_dmail_category
                }
            }
        }
    }
}

The model looks like this:

class Address extends \AFM\Registeraddress\Domain\Model\Address
{

    /**
     * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\DMK\Myext\Domain\Model\NewsletterCategory>
     */
    protected $moduleSysDmailCategory;

    /**
     * __construct
     */
    public function __construct()
    {
        $this->initStorageObjects();
    }

    /**
     * @return void
     */
    protected function initStorageObjects()
    {
        $this->moduleSysDmailCategory = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
    }

    /**
     * @param \DMK\Myext\Domain\Model\NewsletterCategory $moduleSysDmailCategory
     * @return void
     */
    public function addModuleSysDmailCategory(\DMK\Myext\Domain\Model\NewsletterCategory $moduleSysDmailCategory)
    {
        $this->moduleSysDmailCategory->attach($moduleSysDmailCategory);
    }

    /**
     * @param \DMK\Myext\Domain\Model\NewsletterCategory $moduleSysDmailCategory The Category to be removed
     * @return void
     */
    public function removeModuleSysDmailCategory(\DMK\Myext\Domain\Model\NewsletterCategory $moduleSysDmailCategory)
    {
        $this->moduleSysDmailCategory->detach($moduleSysDmailCategory);
    }

    /**
     * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\DMK\Myext\Domain\Model\NewsletterCategory> $moduleSysDmailCategory
     */
    public function getModuleSysDmailCategory()
    {
        return $this->moduleSysDmailCategory;
    }

    /**
     * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\DMK\Myext\Domain\Model\NewsletterCategory> $moduleSysDmailCategory
     * @return void
     */
    public function setModuleSysDmailCategory(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $moduleSysDmailCategory)
    {
        $this->moduleSysDmailCategory = $moduleSysDmailCategory;
    }
}

The category model is just an empty wrapper:

class NewsletterCategory extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
}

So far so good, now I added a ViewHelper to return raw database records for the categories for the view. In this ViewHelper I just return all categories from a given pid. I think there needs to be a more generic solution.

Additionally I needed a ViewHelper to determine if a category is selected:

class IsCategorySelectedViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper
{

    /**
     * @param int $categoryUid
     *
     * @return array
     */
    public function render($categoryUid)
    {
        $request = $this->controllerContext->getRequest()->getOriginalRequest();

        // if form is not submitted the default is checked
        if (!$request) {
            $checked = true;
        } else {
            $checked = in_array($categoryUid, $request->getArgument('newAddress')['moduleSysDmailCategory']);
        }

        return $checked;
    }
}

Here's a template snippet for the field:

<f:alias map="{categories: '{mk:newsletter.getCategories()}'}">
    <f:if condition="{categories}">
        <f:if condition="{categories -> f:count()} == 1">
            <f:then>
                <f:comment>
                    We can't use the property attribute as we need the parameter to be
                    multiple (ending with []) in every case. This is not possible with the normal
                    hidden field. That's why we set the name by ourselves.
                </f:comment>
                <f:form.hidden name="newAddress[moduleSysDmailCategory][]" value="{categories.0.uid}" />
            </f:then>
            <f:else>
                <fieldset class="checkboxes">
                    <f:for each="{categories}" as="category">
                        <f:form.checkbox
                            checked="{mk:newsletter.isCategorySelected(categoryUid: '{category.uid}')}"
                            id="option-{category.uid}"
                            property="moduleSysDmailCategory"
                            multiple="1"
                            value="{category.uid}"
                        />
                        <label for="option-{category.uid}">{category.category}</label>
                    </f:for>
                </fieldset>
            </f:else>
        </f:if>
    </f:if>
</f:alias>

The last step was to validate that categories were selected. I added the corresponding validation into the createAction method of the controller with something like this: @validate $newAddress \DMK\Myext\Validation\Validator\AddressValidator. The validator looks like this:

class AddressValidator extends \TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator
{
    /**
     * Object Manager
     *
     * @var \TYPO3\CMS\Extbase\Object\ObjectManager
     * @inject
     */
    protected $objectManager;

    /**
     * Is valid
     *
     * @param \DMK\Mkwsw\Domain\Model\Address $address
     *
     * @return bool
     */
    public function isValid($address)
    {
        $isValid = true;
        if ($address->getModuleSysDmailCategory()->count() == 0) {
            $error = $this->objectManager->get(
                'TYPO3\CMS\Extbase\Validation\Error',
                'No newsletter category selected',
                'no_newsletter_category_selected'
            );
            $this->result->forProperty('moduleSysDmailCategory')->addError($error);
            $isValid = false;
        }

        return $isValid;
    }
}

The error message can be set with TS in the path plugin.tx_registeraddress._LOCAL_LANG.default.error.no_newsletter_category_selected.newAddress.moduleSysDmailCategory

Thought I share that in case anybody needs it.

mtness commented 6 years ago

Hi there! First of all, thank you for this huge submission!

I'm trying to get this feature working now, too - It seems that you ommitted the "getCategories"-Viewhelper? (it gets called right in the first line of the template)

hannesbochmann commented 6 years ago

I left it out because we use the rn_base extension to access the database which might not be suitable for most users. But anyway here it is so you can adapt the ViewHelper to use core methods or whatever you like. Getting the categories by pid might not be necessary, depends on your requirements:

class GetCategoriesViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper
{

    /**
     * @return array
     */
    public function render()
    {
        $settings = $this->renderingContext->getVariableProvider()->get('settings');
        return \Tx_Rnbase_Database_Connection::getInstance()->doSelect(
            '*',
            'sys_dmail_category',
            array('where' => 'pid = ' . intval($settings['categoriesStoragePid']))
        );
    }
}
mtness commented 6 years ago

Whoa! Thank you very much for your fast reply!

I'm almost done building the complete extension infrastructure for this feature - I will post it here when I'm done.

Meggie-K commented 5 years ago

Thank you for the Code, Hannes! Where exactly did you put the @validate $newAddress (...)\Validation\Validator\AddressValidator ?

(BTW: don't forget the namespaces! )

opaque01 commented 5 years ago

Extension for directmail categories: https://github.com/opaque01/registeraddress_categories

Meggie-K commented 5 years ago

Thanks. But there's no validation of the categories either.

hannesbochmann commented 5 years ago

The validation is done in the controller. You need to provide an own controller:

config.tx_extbase{
    objects {
        AFM\Registeraddress\Controller\AddressController.className = DMK\Myext\Controller\AddressController
    }
}

And the controller:

class AddressController extends \AFM\Registeraddress\Controller\AddressController
{

    /**
     * we check additionally that at least one category was choosen
     *
     * @param \AFM\Registeraddress\Domain\Model\Address $newAddress
     * @validate $newAddress \DMK\Myext\Validation\Validator\AddressValidator
     *
     * @return void
     */
    public function createAction(\AFM\Registeraddress\Domain\Model\Address $newAddress)
    {
        parent::createAction($newAddress);
    }
}
Meggie-K commented 5 years ago

Thanks! I finally it works! Though I had to change the ViewHelper to

// if form is not submitted the default is unchecked
        $checked = false;
        if ($request) {
          if (is_array($request->getArgument('newAddress')['moduleSysDmailCategory'])) {
            $checked = in_array($categoryUid, $request->getArgument('newAddress')['moduleSysDmailCategory']);
          } else {                
              array_push( $formCategories, $request->getArgument('newAddress')['moduleSysDmailCategory'] );
              $checked = in_array($categoryUid, $formCategories);
          }          
        }

(Hopefully) the last question: Do you know the error id? I tried

  <trans-unit id="error.1221560718.newAddress.moduleSysDmailCategory" xml:space="preserve">
    <source>Please choose at least one category.</source>
  </trans-unit>

but the number doesn't seem to be the right one. Sorry, but I don't know where to find it...

opaque01 commented 5 years ago

@Meggie-K Can you add it to this extension? https://github.com/opaque01/registeraddress_categories

Meggie-K commented 5 years ago

I'm not sure if it works with your extension (I wrote my own - which is more based on your code above and not exactly the same as registeraddress_categories). I had to change it because I worked in a VM without realurl - so the request was always set (id= 123).

hannesbochmann commented 5 years ago

The label for the error when no category is selected should be error.no_newsletter_category_selected.newAddress.moduleSysDmailCategory

Meggie-K commented 5 years ago

Thank you! Just noticed that you already mentioned that at the end of your first post in November... Sorry!

FYI: if you want to highlight the checkboxes when no category is selected, add <span class="checkmark"></span> after each checkbox label and insert following CSS:

.f3-form-error ~ .checkmark { position: absolute; left: 11px; height: 21px; width: 21px; border: 1px solid #FF0000; border-radius: 0.25em; }