17.08.2022
TYPO3 Formular Spam
Leider wird der standardmäßige Honeypot Spamschutz von TYPO3 Kontaktformularen des EXT:form Formular Frameworks häufig von Spam-Bots umgangen und TYPO3 Formulare können für Spam Versand misbraucht werden.
Mehr erfahren
In TYPO3 9 enthält das TYPO3 eigene Kontakt Formular System "Form Framework" (EXT:form) zwar bereits 22 Element Typen, die man beim Erstellen von Formularen benutzen kann. Ein Dropdown Element, bei dem der Nutzer im Formular sein Land aus einer Länderliste auswählen kann, ist aber leider nicht mit dabei, obwohl das sicher in sehr vielen Projekten immer wieder eine Anforderung ist. Daher haben wir uns an dem Artikel "How to create TYPO3 Form select element with options selected from database" von Daniel Siepmann orientiert und ein neues Formular Element entwickelt, dass diese Möglichkeit nun bietet.
Für das folgende Beispiel basiert auf TYPO3 9 und 10 und man benötigt eine Extension, die wir im Folgenden mit EXT:extension bezeichnen. Diese kann die Extension sein, die ihr ohnehin schon im System für euer Templating als Provider Extension habt. Zusätzlich wird mindestens die Extension EXT:static_info_tables benötigt als Datenquelle für die Länder der Welt. Zusätzlich machen weitere Extensions wie EXT:static_info_tables_de, EXT:static_info_tables_es, usw. Sinn, wenn man die Ländernamen auch in anderen Sprachen als englisch haben möchte.
Für ein neues Formular Element in EXT:form benötigt man eine php Klasse, die sich vor allem um das Füllen der Dropdown Optionen mit Daten kümmert, diese liegt in unserem Beispiel dann in EXT:extension/Classes/FormElements/CountryOptions.php:
<?php namespace Vendor\Extension\FormElements; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement; use TYPO3\CMS\Frontend\Category\Collection\CategoryCollection; class CountryOptions extends GenericFormElement { protected $valueField = ''; protected $labelField = ''; public function setProperty(string $key, $value) { // see form element config for the static country column to be used for the select option value if ($key === 'valueField') { $this->valueField = $value; } // see form element config for the static country column to be used for the select option label if ($key === 'labelField') { $this->labelField = $value; $this->setProperty('options', $this->getOptions($this->valueField, $this->labelField)); return; } parent::setProperty($key, $value); } protected function getOptions(string $valueField, string $labelField = 'cn_short_en') : array { $options = []; foreach ($this->getCountries($uid) as $country) { $label = $country[$labelField]; // dynamic label field generation based on current language isocode if marker {currentLanguage} is present if (strstr($labelField, '{currentLanguage}') !== false) { $siteLanguage = $GLOBALS['TYPO3_REQUEST']->getAttribute('language')->getTwoLetterIsoCode(); if (isset($country[str_replace('{currentLanguage}', $siteLanguage, $labelField)]) && $country[str_replace('{currentLanguage}', $siteLanguage, $labelField)]) { $label = $country[str_replace('{currentLanguage}', $siteLanguage, $labelField)]; } } $options[$country[$valueField]] = $label; } asort($options); return $options; } protected function getCountries() : array { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('static_countries'); $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); return $queryBuilder->select('*')->from('static_countries')->execute()->fetchAll(); } }
Dann muss das Element im Form Framework angemeldet werden über folgenden YAML Code. Dieser Code kann bspw. in EXT:extension/Configuration/Form/Frontend.yaml geschrieben werden:
TYPO3: CMS: Form: prototypes: standard: formElementsDefinition: SingleSelectWithCountries: __inheritances: 10: 'TYPO3.CMS.Form.prototypes.standard.formElementsDefinition.SingleSelect' implementationClassName: 'Vendor\Extension\FormElements\CountryOptions' renderingOptions: templateName: 'SingleSelect'
Mit dem folgenden Typoscript wird diese YAML Konfiguration dann beim Formular Framework angemeldet:
plugin.tx_form { settings { yamlConfigurations { 1490817689 = EXT:extension/Configuration/Form/Frontend.yaml } } }
Der folgende Teil ist grundsätzlich optional. Dieser Teil ist nur relevant, wenn man das Element im Formular Editor im TYPO3 CMS auch editieren können möchte, einen Eintrag im Anlegen-Assistent wünscht, ein Icon definieren möchte, usw. Dieser Code kann bspw. in EXT:extension/Configuration/Form/Backend.yaml geschrieben werden:
TYPO3: CMS: Form: prototypes: standard: formEditor: formEditorPartials: FormElement-SingleSelectWithCountries: 'Stage/SimpleTemplate' dynamicRequireJsModules: additionalViewModelModules: - 'TYPO3/CMS/Extension/Backend/FormEditor/SingleSelectWithCountriesViewModel' formElementsDefinition: SingleSelectWithCountries: formEditor: label: 'Countries' group: custom groupSorting: 1000 iconIdentifier: form-single-select editors: 100: identifier: header templateName: Inspector-FormElementHeaderEditor 200: identifier: label templateName: Inspector-TextEditor label: formEditor.elements.FormElement.editor.label.label propertyPath: label 230: identifier: elementDescription templateName: Inspector-TextEditor label: formEditor.elements.FormElement.editor.elementDescription.label propertyPath: properties.elementDescription 250: identifier: inactiveOption templateName: Inspector-TextEditor label: formEditor.elements.SelectionMixin.editor.inactiveOption.label propertyPath: properties.prependOptionLabel fieldExplanationText: formEditor.elements.SelectionMixin.editor.inactiveOption.fieldExplanationText doNotSetIfPropertyValueIsEmpty: true 700: identifier: gridColumnViewPortConfiguration templateName: Inspector-GridColumnViewPortConfigurationEditor label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.label configurationOptions: viewPorts: 10: viewPortIdentifier: xs label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xs.label 20: viewPortIdentifier: sm label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.sm.label 30: viewPortIdentifier: md label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.md.label 40: viewPortIdentifier: lg label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.lg.label numbersOfColumnsToUse: label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.label propertyPath: 'properties.gridColumnClassAutoConfiguration.viewPorts.{@viewPortIdentifier}.numbersOfColumnsToUse' fieldExplanationText: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.fieldExplanationText 800: identifier: requiredValidator templateName: Inspector-RequiredValidatorEditor label: formEditor.elements.FormElement.editor.requiredValidator.label validatorIdentifier: NotEmpty propertyPath: properties.fluidAdditionalAttributes.required propertyValue: required configurationOptions: validationErrorMessage: label: formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.label propertyPath: properties.validationErrorMessages fieldExplanationText: formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.fieldExplanationText errorCodes: 10: 1221560910 20: 1221560718 30: 1347992400 40: 1347992453 9999: identifier: removeButton templateName: Inspector-RemoveElementEditor properties: containerClassAttribute: input elementClassAttribute: '' elementErrorClassAttribute: error
Mit dem folgenden Typoscript wird diese YAML Konfiguration dann beim Formular Framework angemeldet:
module.tx_form { settings { yamlConfigurations { 1490817689 = EXT:extension/Configuration/Form/Backend.yaml } } }
Dann gibt es aktuell auch in TYPO3 Version 10 noch eine Besonderheit. Um das Element vollständig im Formular Editor bedienen zu können und eine korrekte Darstellung zu erhalten, muss man noch eine JavaScript Datei anlegen. Wir hoffen dass dieser uns doch ein wenig unnütz erscheinende Schritt in zukünftigen TYPO3 Versionen einmal entfallen wird. Die JavaScript Datei muss gleichlautend wie das Element benannt sein und in folgendem Ordner liegen: EXT:extension/Resources/Public/JavaScript/Backend/FormEditor/SingleSelectWithCountriesViewModel.js. Dies ist der Inhalt:
define([ 'jquery', 'TYPO3/CMS/Form/Backend/FormEditor/Helper' ], function ($, Helper) { 'use strict'; return (function ($, Helper) { /** * @private * * @var object */ var _formEditorApp = null; /** * @private * * @return object */ function getFormEditorApp() { return _formEditorApp; }; /** * @private * * @return object */ function getPublisherSubscriber() { return getFormEditorApp().getPublisherSubscriber(); }; /** * @private * * @return object */ function getUtility() { return getFormEditorApp().getUtility(); }; /** * @private * * @param object * @return object */ function getHelper() { return Helper; }; /** * @private * * @return object */ function getCurrentlySelectedFormElement() { return getFormEditorApp().getCurrentlySelectedFormElement(); }; /** * @private * * @param mixed test * @param string message * @param int messageCode * @return void */ function assert(test, message, messageCode) { return getFormEditorApp().assert(test, message, messageCode); }; /** * @private * * @return void * @throws 1491643380 */ function _helperSetup() { assert('function' === $.type(Helper.bootstrap), 'The view model helper does not implement the method "bootstrap"', 1491643380 ); Helper.bootstrap(getFormEditorApp()); }; /** * @private * * @return void */ function _subscribeEvents() { /** * @private * * @param string * @param array * args[0] = formElement * args[1] = template * @return void */ getPublisherSubscriber().subscribe('view/stage/abstract/render/template/perform', function (topic, args) { if (args[0].get('type') === 'SingleSelectWithCountries') { getFormEditorApp().getViewModel().getStage().renderSimpleTemplateWithValidators(args[0], args[1]); } }); }; /** * @public * * @param object formEditorApp * @return void */ function bootstrap(formEditorApp) { _formEditorApp = formEditorApp; _helperSetup(); _subscribeEvents(); }; /** * Publish the public methods. * Implements the "Revealing Module Pattern". */ return { bootstrap: bootstrap }; })($, Helper); });
Das Element selber kann man dann mit folgendem YAML Code in einem Formular einsetzen. Alternativ kann man den Neuanlegen Assistent des Formular Editors im Backend nutzen, wenn man den oben genannten Backend Code Teil auch eingesetzt hat.
Mit der Konfigurationsoption "valueField" kann man die Spalte der static_countries Tabelle definieren (diese Tabelle ist Teil der Extension EXT:static_info_tables), die für zu übertragenden Wert genutzt werden soll. Im folgenden Beispiel ist das der englische Landesname.
Mit der Konfigurationsoption "labelField" kann man die Spalte der static_countries Tabelle definieren, die für den anzuzeigenden Wert verwendet werden soll. Hier haben wir eine kleine Besonderheit entwickelt und zwar den Platzhalter "{currentLanguage}", welcher dann in obiger php Klasse automatisch anhand der aktuellen Sprache des Frontends mit "de", "es", usw. befüllt wird. Dafür werden dann aber für jede Sprache der Web-Site die weiteren Extensions wie EXT:static_info_tables_de, EXT:static_info_tables_es, usw. benötigt. Man kann aber auch statt dieses Markers einfach einen fixen Wert einsetzen.
... - properties: fluidAdditionalAttributes: required: required valueField: cn_short_en labelField: 'cn_short_{currentLanguage}' prependOptionLabel: 'please choose' defaultValue: '' type: SingleSelectWithCountries identifier: country label: Country validators: - identifier: NotEmpty ...