.. include:: ../Includes.txt .. include:: Images.txt .. _bestpractice: Best Practice ============= .. only:: html :ref:`changetemplates` | :ref:`countryselect` | :ref:`newfields` | :ref:`extendvalidators` | :ref:`finishers` | :ref:`signalslots` | .. _changetemplates: 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: .. code-block:: text 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 .. code-block:: text 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/ } } } .. _countryselect: 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: .. code-block:: text The GetCountriesFromStaticInfoTables-ViewHelper Possible options for this ViewHelper are: .. t3-field-list-table:: :header-rows: 1 - :Name: Name :Description: Description :Default: Default Value :Examplevalue: Example Value - :Name: key :Description: 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 :Default: isoCodeA3 :Examplevalue: isoCodeA2 - :Name: value :Description: Define the Record Column of static_countries table which should be visible in selection in femanager Note: Please use lowerCamelCase Writing for Fieldnames :Default: officialNameLocal :Examplevalue: shortNameFr - :Name: sortbyField :Description: Define the Record Column of static_countries which should be used for a sorting Note: Please use lowerCamelCase Writing for Fieldnames :Default: isoCodeA3 :Examplevalue: shortNameDe - :Name: sorting :Description: Could be 'asc' or 'desc' for Ascending or Descending Sorting :Default: asc :Examplevalue: desc Some Examples are: .. code-block:: text {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')} .. _newfields: 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: .. code-block:: text 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. .. code-block:: text plugin.tx_femanager.view.partialRootPath = fileadmin/Partials/ Example file fileadmin/Partials/Fields/TwitterId.html: .. code-block:: text {namespace femanager=In2code\Femanager\ViewHelpers}
ext_tables.sql """""""""""""" Example SQL file in your extension which extends fe_users with your new fields: .. code-block:: text 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: .. code-block:: text \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: .. code-block:: text 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 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" .. code-block:: text 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: .. code-block:: text 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: .. code-block:: text 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 .. _extendvalidators: 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 """"""""""""""""""""""""""""""""""""""""""" .. code-block:: text 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: .. code-block:: text 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: .. code-block:: text 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 """""""""""""""""""""""""""""""""""""""""""""""""" .. code-block:: text 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 } } .. _finishers: 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: :: 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 .. _signalslots: 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 ^^^^^^^^^^^^^^^^ .. t3-field-list-table:: :header-rows: 1 - :File: File :Located: Located in :Signal: Signal Name :Parameters: Available Parameters :Description: Description - :File: NewController.php :Located: createAction() :Signal: createActionBeforePersist :Parameters: $user, $this :Description: Use this signal if you want to hook into the process before the new user was persisted - :File: NewController.php :Located: confirmCreateRequestAction() :Signal: confirmCreateRequestActionBeforePersist :Parameters: $user, $hash, $status, $this :Description: Use this signal if you want to hook into the confirmation process - :File: AbstractController.php :Located: finalCreate() :Signal: finalCreateAfterPersist :Parameters: $user, $action, $this :Description: Use this signal if you want to hook into the process after the new user was persisted - :File: AbstractController.php :Located: updateAllConfirmed() :Signal: updateAllConfirmedAfterPersist :Parameters: $user, $this :Description: Use this signal if you want to hook into the process after the new user was persisted - :File: EditController.php :Located: updateAction() :Signal: updateActionBeforePersist :Parameters: $user, $this :Description: Use this signal if you want to hook into the process before the user- profile was updated - :File: EditController.php :Located: confirmUpdateRequestAction() :Signal: updateActionBeforePersist :Parameters: $user, $this :Description: Use this signal if you want to hook into the process before the user- profile was updated - :File: EditController.php :Located: deleteAction() :Signal: deleteAction :Parameters: $user, $this :Description: Use this signal if you want to hook into the process before the user- profile will be deleted - :File: InvitationController.php :Located: createAction() :Signal: confirmUpdateRequestActionAfterPersist :Parameters: $user, $hash, $status, $this :Description: Use this signal if you want to hook into the process after a new user was persisted - :File: InvitationController.php :Located: createAllConfirmed() :Signal: createAllConfirmedAfterPersist :Parameters: $user, $this :Description: Use this signal if you want to hook into the process after a new user was persisted - :File: InvitationController.php :Located: editAction() :Signal: editActionAfterPersist :Parameters: $user, $hash, $this :Description: Use this signal if you want to hook into the process before a user adds a new password (step 1) - :File: InvitationController.php :Located: updateAction() :Signal: updateActionAfterPersist :Parameters: $user, $this :Description: Use this signal if you want to hook into the process after a user adds a new password (step 2) - :File: UserController.php :Located: loginAsAction() :Signal: loginAsAction :Parameters: $user, $this :Description: Use this signal if you want to hook into the process after you simulate a frontend user login - :File: ? :Located: ? :Signal: ? :Parameters: ? :Description: 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: .. code-block:: text '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()” .. code-block:: text 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. .. code-block:: text getUsername() . ' Email: ' . $user->getEmail() . ' '; mail('your@email.com', 'SignalSlot Test', $message); } }