Best Practice

Use own HTML Templates | Using static_info_tables for country selection | Adding new fields to fe_users with your own extension | Adding own serverside and clientside validation to femanager forms | Add own Finisher classes | Using SignalSlots (Hook pendant) to extend femanager |

Use own HTML Templates

Basics

If you want to modify a HTML-File of femanager, you should not overwrite them directly in the extension folder. Think about upcoming versions with important bugfixes or security-patches.

There are two ways to use own HTML-Templates (and Partials / Layouts) instead of the original Templates.

Replace all HTML Templates from Femanager with own Templates

You can copy all Files from - EXT:femanager/Resources/Private/Templates/ - EXT:femanager/Resources/Private/Partials/ - EXT:femanager/Resources/Private/Layouts/

to a new folder in fileadmin - e.g. fileadmin/templates/femanager/ and modify them as you want. After that, you should say femanager to use the new Templates with some lines of TypoScript setup:

plugin.tx_femanager {
        view {
                templateRootPath = fileadmin/templates/femanager/Templates/
                partialRootPath = fileadmin/templates/femanager/Partials/
                layoutRootPath = fileadmin/templates/femanager/Layouts/
        }
}

Replace single HTML Template-Files

You can copy only the Files that you want to modify from - EXT:femanager/Resources/Private/Templates/ - EXT:femanager/Resources/Private/Partials/ - EXT:femanager/Resources/Private/Layouts/

to a new folder in fileadmin - e.g. fileadmin/templates/femanager/ and modify them as you want. After that, you should say femanager to use the old folders and merge them with the new folders

plugin.tx_femanager {
        view {
                templateRootPath >
                templateRootPaths {
                        10 = EXT:femanager/Resources/Private/Templates/
                        20 = fileadmin/templates/femanager/Templates/
                }
                partialRootPath >
                partialRootPaths {
                        10 = EXT:femanager/Resources/Private/Partials/
                        20 = fileadmin/templates/femanager/Partials/
                }
                layoutRootPath >
                layoutRootPaths {
                        10 = EXT:femanager/Resources/Private/Layouts/
                        20 = fileadmin/templates/femanager/Layouts/
                }
        }
}

Using static_info_tables for country selection

Basics

  • Install Extension static_info_tables
  • Install Extension static_info_tables(_de)(_fr)(_pl) etc... for localized countrynames
  • Import Records of the extensions via Extension Manager (see manual of static_info_tables)
  • Clear Cache
  • Copy all Partials from femanager to a fileadmin folder
  • Set the new Partial Path via Constants: plugin.tx_femanager.view.partialRootPath = fileadmin/femanager/Partials/
  • Open Partial Fields/Country.html and activate static_info_tables (see notes in HTML-File)

Details for Partial Country.html

The idea is very simple. You can change the “options Attribute” of the form.select ViewHelper:

<femanager:form.select
        id="femanager_field_country"
        property="country"
        options="{femanager:Form.GetCountriesFromStaticInfoTables()}"
        defaultOption="{f:translate(key:'pleaseChoose')}"
        class="input-block-level"
        additionalAttributes="{femanager:Validation.FormValidationData(settings:'{settings}',fieldName:'country')}" />

The GetCountriesFromStaticInfoTables-ViewHelper

Possible options for this ViewHelper are:

Name Description Default Value Example Value
key

Define the Record Column of static_countries table which should be used for storing to fe_users country

Note: Please use lowerCamelCase Writing for Fieldnames

isoCodeA3 isoCodeA2
value

Define the Record Column of static_countries table which should be visible in selection in femanager

Note: Please use lowerCamelCase Writing for Fieldnames

officialNameLocal shortNameFr
sortbyField

Define the Record Column of static_countries which should be used for a sorting

Note: Please use lowerCamelCase Writing for Fieldnames

isoCodeA3 shortNameDe
sorting Could be 'asc' or 'desc' for Ascending or Descending Sorting asc desc

Some Examples are:

{femanager:Form.GetCountriesFromStaticInfoTables(key:'isoCodeA2',value:'shortNameDe')}
{femanager:Form.GetCountriesFromStaticInfoTables(key:'isoCodeA2',value:'shortNameFr',sortbyField:'shortNameFr')}
{femanager:Form.GetCountriesFromStaticInfoTables(key:'isoCodeA3',value:'isoCodeA3',sortbyField:'isoCodeA3',sorting:'asc')}

Adding new fields to fe_users with your own extension

Picture

extendFields

Add new Fields to the Registraion-/Editform

Basics

  • Use TSConfig to add one or more fields to the field selection in femanager flexform
  • Copy the partial folder and add your fields
  • Create a new extension with ext_tables.sql and ext_tables.php for adding one or more new fields to fe_users
  • Create your own user model with getter/setter for your new fields that extends the user model from femanager
  • Use TypoScript to include your model
  • Override the createAction and updateAction to manipulte the object type

See https://github.com/einpraegsam/femanagerextended for an example extension how to extend femanager with new fields and validation methods

Step by Step

Add new fields to the flexform

newFields

Extend Fieldselection in Flexform

Add some Page-TSConfig to extend the selection:

tx_femanager {
        flexForm {
                        new {
                                        addFieldOptions {
                                                        twitterId = Twitter ID
                                                        skypeId = Skype ID
                                                        somethingElse = LLL:EXT:ext/Resources/Private/Language/locallang_be.xlf:custom
                                        }
                        }
                        edit < tx_femanager.flexForm.new
        }
}

Modify the partial folder

“twitterId” (see TSConfig) means that femanager searches for a partial TwitterId.html to render the field in the form. So you have to copy the folder EXT:femanager/Resources/Private/Partials (e.g.) to fileadmin/Partials and set the new partial path via TypoScript Constants (see exmple below). In addition you have to add the new Partials files.

plugin.tx_femanager.view.partialRootPath = fileadmin/Partials/

Example file fileadmin/Partials/Fields/TwitterId.html:

{namespace femanager=In2code\Femanager\ViewHelpers}
<div class="femanager_fieldset control-group">
                <label for="twitterId" class="control-label">
                                <f:translate key="tx_femanagerextended_domain_model_user.twitter_id" extensionName="femanagerextended">Twitter</f:translate>
                </label>
                <div class="controls">
                                <femanager:form.textfield
                                                                id="twitterId"
                                                                property="twitterId"
                                                                class="input-block-level"
                                                                additionalAttributes="{femanager:Validation.FormValidationData(settings:'{settings}',fieldName:'twitterId')}" />
                </div>
</div>

ext_tables.sql

Example SQL file in your extension which extends fe_users with your new fields:

CREATE TABLE fe_users (
        twitter_id varchar(255) DEFAULT '' NOT NULL,
        skype_id varchar(255) DEFAULT '' NOT NULL,
        tx_extbase_type varchar(255) DEFAULT '0' NOT NULL,
);

ext_tables.php

Example ext_tables.php file:

\TYPO3\CMS\Core\Utility\GeneralUtility::loadTCA('fe_users');
$TCA['tx_test_domain_model_address']['ctrl']['type'] = 'tx_extbase_type';
$tmp_fe_users_columns = array(
                'twitter_id' => array(
                                'exclude' => 1,
                                'label' => 'LLL:EXT:femanagerextended/Resources/Private/Language/locallang_db.xlf:tx_femanagerextended_domain_model_user.twitter_id',
                                'config' => array(
                                                'type' => 'input',
                                                'size' => 30,
                                                'eval' => 'trim'
                                ),
                ),
                'skype_id' => array(
                                'exclude' => 1,
                                'label' => 'LLL:EXT:femanagerextended/Resources/Private/Language/locallang_db.xlf:tx_femanagerextended_domain_model_user.skype_id',
                                'config' => array(
                                                'type' => 'input',
                                                'size' => 30,
                                                'eval' => 'trim'
                                ),
                ),
                'tx_extbase_type' => array(
                                'config' => array(
                                                'type' => 'input',
                                                'default' => '0'
                                )
                )
);


\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('fe_users', $tmp_fe_users_columns, 1);
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('fe_users', 'twitter_id, skype_id');

Own User Model

Example Model User.php which extends to the default femanager Model:

namespace In2code\Femanagerextended\Domain\Model;

class User extends \In2code\Femanager\Domain\Model\User {

        /**
         * twitterId
         *
         * @var string
         */
        protected $twitterId;

        /**
         * skypeId
         *
         * @var string
         */
        protected $skypeId;

        /**
         * Returns the twitterId
         *
         * @return string $twitterId
         */
        public function getTwitterId() {
                        return $this->twitterId;
        }

        /**
         * Sets the twitterId
         *
         * @param string $twitterId
         * @return void
         */
        public function setTwitterId($twitterId) {
                        $this->twitterId = $twitterId;
        }

        /**
         * Returns the skypeId
         *
         * @return string $skypeId
         */
        public function getSkypeId() {
                        return $this->skypeId;
        }

        /**
         * Sets the skypeId
         *
         * @param string $skypeId
         * @return void
         */
        public function setSkypeId($skypeId) {
                        $this->skypeId = $skypeId;
        }

        /**
         * @param string $username
         */
        public function setUsername($username) {
                        $this->username = $username;
        }

}

TypoScript to include Model and override default controller

config.tx_extbase{
        persistence{
                classes{
                        In2code\Femanager\Domain\Model\User {
                                subclasses {
                                        0 = In2code\Femanagerextended\Domain\Model\User
                                }
                        }
                        In2code\Femanagerextended\Domain\Model\User {
                                mapping {
                                        tableName = fe_users
                                        recordType = 0
                                }
                        }
                }
        }
        objects {
                In2code\Femanager\Controller\NewController.className = In2code\Femanagerextended\Controller\NewController
                In2code\Femanager\Controller\EditController.className = In2code\Femanagerextended\Controller\EditController
        }
}

Own Controller Files

EditController.php:

namespace In2code\Femanagerextended\Controller;

class EditController extends \In2code\Femanager\Controller\EditController {

        /**
         * action update
         *
         * @param In2code\Femanagerextended\Domain\Model\User $user
         * @validate $user In2code\Femanager\Domain\Validator\ServersideValidator
         * @validate $user In2code\Femanager\Domain\Validator\PasswordValidator
         * @return void
         */
        public function updateAction(\In2code\Femanagerextended\Domain\Model\User $user) {
                parent::updateAction($user);
        }
}

NewController.php:

namespace In2code\Femanagerextended\Controller;

class NewController extends \In2code\Femanager\Controller\NewController {

        /**
         * action create
         *
         * @param In2code\Femanagerextended\Domain\Model\User $user
         * @validate $user In2code\Femanager\Domain\Validator\ServersideValidator
         * @validate $user In2code\Femanager\Domain\Validator\PasswordValidator
         * @return void
         */
        public function createAction(\In2code\Femanagerextended\Domain\Model\User $user) {
                parent::createAction($user);
        }
}

Note: If there are PHP warnings like “…should be compatible with…“ for PHP7, see discusion and solution: https://stackoverflow.com/questions/45563671/how-to-extend-femanager-controller-under-php-7/45564378

Adding own serverside and clientside validation to femanager forms

Picture

newField1

Add own clientside (JcavaScript) Validation

newField2

Add own serverside (PHP) Validation

Basics

  • Use TypoScript to override ValidationClass of femanager with own classes – this enables your validation methods
  • Config the new validation methods via TypoScript
  • Add translation labels via TypoScript

See https://github.com/einpraegsam/femanagerextended for an example extension how to extend femanager with new fields and validation methods

Step by Step

Override Validation Classes with TypoScript

config.tx_extbase{
        objects {
                In2code\Femanager\Domain\Validator\ServersideValidator.className = In2code\Femanagerextended\Domain\Validator\CustomServersideValidator
                In2code\Femanager\Domain\Validator\ClientsideValidator.className = In2code\Femanagerextended\Domain\Validator\CustomClientsideValidator
        }
}

New validation classes

CustomClientsideValidator.php:

namespace In2code\Femanagerextended\Domain\Validator;

class CustomClientsideValidator extends \In2code\Femanager\Domain\Validator\ClientsideValidator
{

        /**
         * Custom Validator
         *              Activate via TypoScript - e.g. plugin.tx_femanager.settings.new.validation.username.custom = validationSetting
         *
         * @param string $value Given value from input field
         * @param string $validationSetting TypoScript Setting for this field
         * @return bool
         */
        protected function validateCustom($value, $validationSetting)
        {
                // check if string has string inside
                if (stristr($value, $validationSetting)) {
                        return TRUE;
                }
                return FALSE;
        }
}

CustomServersideValidator.php:

namespace In2code\Femanagerextended\Domain\Validator;

class CustomServersideValidator extends \In2code\Femanager\Domain\Validator\ServersideValidator
{

        /**
         * Custom Validator
         *              Activate via TypoScript - e.g. plugin.tx_femanager.settings.new.validation.username.custom = validationSetting
         *
         * @param string $value Given value from input field
         * @param string $validationSetting TypoScript Setting for this field
         * @return bool
         */
        protected function validateCustom($value, $validationSetting)
        {
                // check if string has string inside
                if (stristr($value, $validationSetting)) {
                        return TRUE;
                }
                return FALSE;
        }
}

TypoScript to enable new validation and set labels

plugin.tx_femanager {
        settings.new.validation {
                _enable.client = 1
                _enable.server = 1
                username {
                        # Custom Validator - check if value includes "abc"
                        custom = abc
                }
        }
        _LOCAL_LANG {
                default.validationErrorCustom = "abc" is missing
                de.validationErrorCustom = "abc" wird erwartet
        }
}

Add own Finisher classes

Introduction

Let's say you want to easily add some own php functions, that should be called after a user registered. Maybe you want to handle the user input with:

  • Send it to an API
  • Store it in a logfile
  • Save it into a table
  • Something else...

Small example

Just define which classes should be used. Every method like *Finisher() will be called - e.g. myFinisher():

plugin.tx_femanager.settings {
    finishers {
        1 {
            class = Vendor\Ext\Finisher\DoSomethingFinisher
        }
    }
}

Add a php-file and extend your class with the AbstractFinisher from femanager:

<?php
namespace Vendor\Ext\Finisher;

use In2code\Femanager\Finisher\AbstractFinisher;

/**
 * Class DoSomethingFinisher
 *
 * @package Vendor\Ext\Finisher
 */
class DoSomethingFinisher extends AbstractFinisher
{

    /**
     * MyFinisher
     *
     * @return void
     */
    public function myFinisher()
    {
        // ...
    }
}

Extended example

See the advanced example with some configuration in TypoScript and with the possibility to load the file (useful if file could not be loaded from autoloader because it's stored in fileadmin or elsewhere)

plugin.tx_femanager.settings {
    finishers {
        1 {
            # Classname that should be called with method *Finisher()
            class = Vendor\Ext\Finisher\DoSomethingFinisher

            # optional: Add configuration for your PHP
            config {
                foo = bar

                fooCObject = TEXT
                fooCObject.value = do something with this text
            }

            # optional: If file will not be loaded from autoloader, add path and it will be called with require_once
            require = fileadmin/femanager/finisher/DoSomethingFinisher.php
        }
    }
}

Add your php-file again and extend your class with the AbstractFinisher from femanager:

<?php
namespace Vendor\Ext\Finisher;

use In2code\Femanager\Domain\Model\User;
use In2code\Femanager\Finisher\AbstractFinisher;

/**
 * Class DoSomethingFinisher
 *
 * @package Vendor\Ext\Finisher
 */
class DoSomethingFinisher extends AbstractFinisher
{

    /**
     * @var User
     */
    protected $user;

    /**
     * @var array
     */
    protected $configuration;

    /**
     * @var array
     */
    protected $settings;

    /**
     * Will be called always at first
     *
     * @return void
     */
    public function initializeFinisher()
    {
    }

    /**
     * Will be called before myFinisher()
     *
     * @return void
     */
    public function initializeMyFinisher()
    {
    }

    /**
     * MyFinisher
     *
     * @return void
     */
    public function myFinisher()
    {
        // get value from configuration
        $foo = $this->configuration['foo'];

        // get subject
        $subject = $this->getMail()->getSubject();

        // ...
    }
}

Some notices

  • All methods which are ending with "finisher" will be called - e.g. saveFinisher()
  • The method initializeFinisher() will always be called at first
  • Every finisher method could have its own initialize method, which will be called before. Like initializeMyFinisher() before myFinisher()
  • Classes in extensions (if namespace and filename fits) will be automaticly included from TYPO3 autoloader. If you place a single file in fileadmin, use "require" in TypoScript
  • Per default 10 and 20 is already in use from femanager itself (SaveToAnyTableFinisher, SendParametersFinisher) since version 2.0

Using SignalSlots (Hook pendant) to extend femanager

Introduction

SignalSlots (former Hooks) are the possibility for other developer to extend the runtime of a femanager process with their own code.

As an example let's build an extension which sends username and email address of a new registered user to a defined email address.

Note: this is a little bit useless because there is already a setting in flexform to inform administrators and there is a setting in TypoScript to POST values to a third-party-software, but let's use this case for an example.

SignalSlots List

File Located in Signal Name Available Parameters Description
NewController.php createAction() createActionBeforePersist $user, $this Use this signal if you want to hook into the process before the new user was persisted
NewController.php confirmCreateRequestAction() confirmCreateRequestActionBeforePersist $user, $hash, $status, $this Use this signal if you want to hook into the confirmation process
AbstractController.php finalCreate() finalCreateAfterPersist $user, $action, $this Use this signal if you want to hook into the process after the new user was persisted
AbstractController.php updateAllConfirmed() updateAllConfirmedAfterPersist $user, $this Use this signal if you want to hook into the process after the new user was persisted
EditController.php updateAction() updateActionBeforePersist $user, $this Use this signal if you want to hook into the process before the user- profile was updated
EditController.php confirmUpdateRequestAction() updateActionBeforePersist $user, $this Use this signal if you want to hook into the process before the user- profile was updated
EditController.php deleteAction() deleteAction $user, $this Use this signal if you want to hook into the process before the user- profile will be deleted
InvitationController.php createAction() confirmUpdateRequestActionAfterPersist $user, $hash, $status, $this Use this signal if you want to hook into the process after a new user was persisted
InvitationController.php createAllConfirmed() createAllConfirmedAfterPersist $user, $this Use this signal if you want to hook into the process after a new user was persisted
InvitationController.php editAction() editActionAfterPersist $user, $hash, $this Use this signal if you want to hook into the process before a user adds a new password (step 1)
InvitationController.php updateAction() updateActionAfterPersist $user, $this Use this signal if you want to hook into the process after a user adds a new password (step 2)
UserController.php loginAsAction() loginAsAction $user, $this Use this signal if you want to hook into the process after you simulate a frontend user login
? ? ? ? Do you need a new Signal in femanager? Just request one on https://github.com/einpraegsam/femanager

Use a SignalSlot

Introduction

As described before, we want to send an email to a defined address every time when a new user is registered.

Creating an extension

femanagersignalslot/ext_emconf.php:

This file is important to install your new extension – write something like:

<?php

$EM_CONF[$_EXTKEY] = array(
                'title' => 'femanagersignalslot',
                'description' => 'signalslotexample for femanager',
                'state' => 'alpha',
                'version' => '0.0.1',
                'constraints' => array(
                                'depends' => array(
                                                'extbase' => '6.0.0-6.1.99',
                                                'fluid' => '6.0.0-6.1.99',
                                                'typo3' => '6.0.0-6.1.99',
                                                'femanager' => '1.0.0-1.0.99',
                                ),
                                'conflicts' => array(
                                ),
                                'suggests' => array(
                                ),
                ),
);

femanagersignalslot/ext_localconf.php:

This is an example how to use a signal from femanager – in this case we decided to use the signal “createActionBeforePersist” in class “In2codeFemanagerControllerNewController” and want to call a slot in class “In2codeFemanagersignalslotDomainServiceSendMailService” with methodname “send()”

<?php

$signalSlotDispatcher = t3lib_div::makeInstance('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
$signalSlotDispatcher->connect(
        'In2code\Femanager\Controller\NewController',
        'createActionBeforePersist',
        'In2code\Femanagersignalslot\Domain\Service\SendMailService',
        'send',
        FALSE
);

femanagersignalslot/Classes/Domain/Service/SendMailService.php:

This is our main class which is called every time a new registration process was initiated.

<?php
namespace In2code\Femanagersignalslot\Domain\Service;

class SendMailService
{

        /**
         * Send mail about user information
         *
         * @param \In2code\Femanager\Domain\Model\User $user
         * @param \In2code\Femanager\Controller\NewController $pObj
         * @return void
         */
        public function send($user, $pObj)
        {
                $message = '
                        New user registered
                        Username: ' . $user->getUsername() . '
                        Email: ' . $user->getEmail() . '
                ';
                mail('your@email.com', 'SignalSlot Test', $message);
        }
}