Im Folgenden zeige ich euch meine Vorgehensweise, um im Backend von TYPO3 7 LTS einen “Passwort generieren” Button für das Passwort der Backendbenutzer zu erstellen. Der Button soll rechts vom Passwortfeld erscheinen. Mit jedem Klick soll ein AJAX-Call gestartet, der einerseits das Passwort setzt und andererseits das Passwort im Klartext in einem Bootstrap-Panel anzeigt.
Solche Aufgaben für das Frontend gibt es im Netz wie Sand am Meer, aber im Backend läuft so einiges anders. Mir war wichtig, diese Aufgabe möglichst Core kompatibel, also ohne Tricksen, Biegen und Brechen umzusetzen.
Einen Wizard einfügen
Zunächst müsst ihr für das Passwort der be_users Tabelle einen weiteren Wizard hinzufügen. Erstellt in eurem SitePackage die Datei [sitePackage]/Configuration/TCA/Overrides/be_users.php mit diesem Inhalt:
Mit dem TCA Typ userFunc und der Angabe einer PHP-Klasse könnt ihr euch nun komplett selbst, um das Rendering eines Wizards kümmern. Erstellt nun die Datei [SitePackage]/Classes/Hooks/GeneratePassword.php:
<?phpnamespaceJWeiland\MyExt\Hooks;
useTYPO3\CMS\Backend\Form\Element\AbstractFormElement;
useTYPO3\CMS\Core\Imaging\IconFactory;
useTYPO3\CMS\Core\Utility\GeneralUtility;
useTYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
* @license www.gnu.org/licenses/gpl.html GNU General Public License, version 3 or later
*/classGeneratePassword{
/**
* Render a wizard to generate a password
*
* @param array $parentArray
* @param AbstractFormElement $formElement
*
* @return string The rendered Wizard
*/publicfunctionrender(array $parentArray, AbstractFormElement $formElement)
{
// render the structure of the panel to show the password after Ajax request
$parentArray['item'] .=sprintf(
'
%s
',
LocalizationUtility::translate('generatedPassword', 'myExt')
);
// render the button
/** @var IconFactory $iconFactory */ $iconFactory =GeneralUtility::makeInstance(IconFactory::class);
returnsprintf(
'%s',
$parentArray['uid'],
$parentArray['itemName'],
$iconFactory->getIcon('actions-document-synchronize')
);
}
}
Was hier nicht zu sehen ist: TYPO3 ruft die render() Methode mithilfe von GeneralUtility::callUserFunction() auf. Das Besondere daran: Alle Parameter werden als Referenzen übergeben. Das ergibt gleich 2 verschiedene Anwendungsbereiche.
In dem Array $parentArray gibt es unter dem Key item das vollständige HTML des Passwortfeldes. In meinem Beispiel füge ich ein leeres Bootstrap Panel hinzu. Einzig eine Überschrift habe ich diesem verpasst. In den Panel-Body wird später per JavaScript das Passwort dynamisch eingebunden.
Den eigentlichen Wizard müsst ihr über den Returnwert realisieren. Ich habe mich hier für ein einfaches Icon entschieden, das ich mithilfe der neuen IconFactory erstelle. Hinzu kommt eine CSS-Klasse und die beiden Werte uid und name als data Attribute auf die ihr später mit z.B. jQuery wieder zugreifen könnt. Gerade beim Editieren von mehreren Backendbenutzern (z.B. inline) ist es wichtig diese Daten zu haben, damit ihr nicht beim Klick auf den Button auf einmal die Passwörter aller geöffneten Benutzer ändert. Aber dazu später mehr.
Leert nach dem Speichern den Systemcache (roter Blitz) oder im Installtool Flush Caches. Der Wizard sollte zumindest schon zu sehen sein.
Vorbereitungen für das RequireJS Modul
Dieser Part hat richtig Zeit gekostet. Es gibt diverse Hooks mit denen ich das JavaScript mithilfe des PageRenderers hätte einbinden können, jedoch gibt es ein Problem, das man nicht sofort bemerkt.
Es ist ein gewaltiger Unterschied, ob ihr einen Datensatz über das List Module direkt bearbeitet, oder ihr den Datensatz als Teil eines anderen Datensatzes (TCA Typ: inline) bearbeitet. Das Problem bei Typ inline ist, dass das komplette HTML über AJAX nachgeladen wird. Wenn ich das JavaScript global einfüge, dann sind dem JavaScript diese neuen Felder nicht bekannt. Binde ich das JavaScript direkt in den be_users Datensatz ein, dann klappt das zwar für das direkte Bearbeiten, jedoch nicht, wenn der be_users Datensatz über inline nachgeladen wird. Das JavaScript ist zwar da, wird aber nicht ausgeführt.
Erst bei genauer Analyse des AJAX-Requests bin ich auf einen interessanten Wert im json aufmerksam geworden: scriptCall.
Im FormInlineAjaxController wird dieser Wert erstmalig erstellt und über mergeChildResultIntoJsonResult() je nach Eingabefeld immer weiter aufgefüllt. Jedes Eingabefeld in TYPO3 hat grundsätzlich die Möglichkeit über den Array-Key requireJsModules diesen scriptCall zu befüllen. Problem an der Sache: Die neue Formengine von TYPO3 bietet keinen Hook an, um dieses Array zu befüllen. In Rücksprache mit dem Core gibt es nur eine Lösung: Wir müssen das Rendering des Passwortfeldes komplett überschreiben und dies als eigenen renderType registrieren.
Fügt dazu in eurer ext_localconf.php folgende Zeilen ein:
// Add our own form elements, because we need the requireJSmodule for our password generation
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1454580921] =array(
'nodeName'=>'myExtPassword',
'priority'=>'70',
'class'=>\JWeiland\MyExt\Form\Element\InputTextElement::class,
);
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1454581671] =array(
'nodeName'=>'myExtRsaPassword',
'priority'=>'70',
'class'=>\JWeiland\MyExt\Form\Element\RsaInputElement::class,
);
Mit diesen Zeilen registriert ihr 2 neue renderTypen. 2 deshalb, weil wir je nachdem, ob rsaauth installiert ist oder nicht einen anderen renderTypen benötigen. Diese Unterscheidung realisieren wir wieder über unsere vorhin angelegte Datei [sitePackage]/Configuration/TCA/Overrides/be_users.php:
Hier seht ihr auch wieder die zuvor registrierten nodeNames, die nun als renderTypen eingesetzt werden. Durch diese Angabe brecht ihr aus dem Standardrendering von TYPO3 aus und leitet das Rendering in eure eigenen PHP-Klassen um.
Kopiert euch die beiden Dateien aus dem Core in ein Verzeichnis eurer Extension:
Ich habe bei mir den Pfad vom Original übernommen: [sitePackage]/Classes/Form/Element/
Passt nun die namespace Zeilen an eure Extension an und erweitert mit extends die Originalklassen. Hier ein Ausschnitt, wie die Datei aussehen könnte:
<?phpnamespaceJWeiland\MyExt\Form\Element;
useTYPO3\CMS\Backend\Utility\BackendUtility;
useTYPO3\CMS\Core\Imaging\Icon;
useTYPO3\CMS\Core\Imaging\IconFactory;
useTYPO3\CMS\Core\Utility\GeneralUtility;
useTYPO3\CMS\Core\Utility\MathUtility;
useTYPO3\CMS\Core\Utility\StringUtility;
/**
* Generation of TCEform elements of the type "input type=text"
*/classInputTextElementextends\TYPO3\CMS\Backend\Form\Element\InputTextElement{
/**
* This will render a single-line input form field, possibly with various control/validation features
*
* @return array As defined in initializeResultArray() of AbstractNode
*/publicfunctionrender()
{
/** @var IconFactory $iconFactory */ $iconFactory =GeneralUtility::makeInstance(IconFactory::class);
$languageService = $this->getLanguageService();
$table = $this->data['tableName'];
$fieldName = $this->data['fieldName'];
$row = $this->data['databaseRow'];
$parameterArray = $this->data['parameterArray'];
$resultArray = $this->initializeResultArray();
$resultArray['requireJsModules'] =array(
'TYPO3/CMS/MyExt/GeneratePassword' );
$isDateField =false;
...
Es klingt bescheuert, aber ihr müsst die komplette render() Methode aus der Originalklasse kopieren, nur um diese 3 Zeilen für das requireJsModules einzufügen.
Ähnlich sieht es in der Datei für das RsaInputElement aus. Hier wird bereits ein Modul geladen, das wir entsprechend erweitern müssen:
TYPO3 bringt von Haus aus RequireJS mit und sucht automatisch in dem Pfad [SitePackage]/Resources/Public/JavaScript nach RequireJS Modulen. Die Registrierung eines RequireJS Moduls haben wir bereits im vorherigen Abschnitt durchgeführt. Legt nun eine js-Datei an. Meine heißt GeneratePassword.js mit folgendem Inhalt:
Sehr wichtig: Anders als bei Extbase-Extensions müssen eure RequireJS Module mit dem TYPO3\CMS Vendornamen registriert werden. Der 2te Parameter ist ein Array von Modulen, das wir für unser Modul zwingend benötigen. In unserem Modul benötigen wir jquery, das wir dann im dritten Parameter über $ in unserem Modul zur Verfügung stellen.
Im Weiteren greift ihr nun auf die data Attribute unseres Wizards zu, um dann damit das exakte Passwortfeld ansprechen zu können und das Passwort zu verändern. Außerdem befüllt ihr noch den panel-body und zeigt es an.
Das Ajax-Script
Backend AJAX-Scripte sind zunächst in der ext_localconf.php zu registrieren:
Dank des eindeutigen Namens myExtGeneratePassword erzeugt TYPO3 für euch völlig automatisch eine vollständige AJAX-Url inkl. HashWerten, die ihr wie oben gesehen mit TYPO3.settings.ajaxUrls in eurem AJAX-Call verwenden könnt. Mit dem 2ten Parameter könnt ihr eine PHP-Klasse verknüpfen, die bei dem AJAX-Call aufgerufen werden soll. Meine GeneratePassword.php hat folgenden Inhalt:
<?phpnamespaceJWeiland\MyExt\Ajax;
useJWeiland\MyExt\Configuration\ExtConf;
useTYPO3\CMS\Core\Http\AjaxRequestHandler;
useTYPO3\CMS\Core\Utility\GeneralUtility;
/**
* @license www.gnu.org/licenses/gpl.html GNU General Public License, version 3 or later
*/classGeneratePassword {
/**
* Generate a password
*
* @param array $ajaxParameters
* @param AjaxRequestHandler $ajaxRequestHandler
* @return string
*/publicfunctiongenerate(array $ajaxParameters, AjaxRequestHandler $ajaxRequestHandler)
{
/** @var ExtConf $extConf */ $extConf =GeneralUtility::makeInstance(ExtConf::class);
$ajaxRequestHandler->setContent(array(
$this->generateStrongPassword(
$extConf->getPasswordLength(),
$extConf->getAllowedPasswordChars()
)
));
}
/**
* Generate a strong password
* This method was inspired by: gist.github.com/tylerhall/521810
*
* @param int $length
* @param string $availableSets
*
* @return string
*/publicfunctiongenerateStrongPassword($length =12, $availableSets ='luds')
{
$sets =array();
if (strpos($availableSets, 'l') !==false) {
$sets[] ='abcdefghjkmnpqrstuvwxyz';
}
if (strpos($availableSets, 'u') !==false) {
$sets[] ='ABCDEFGHJKMNPQRSTUVWXYZ';
}
if (strpos($availableSets, 'd') !==false) {
$sets[] ='23456789';
}
if (strpos($availableSets, 's') !==false) {
$sets[] ='!@#$%&*?';
}
$all ='';
$password ='';
foreach ($sets as $set)
{
$password .= $set[array_rand(str_split($set))];
$all .= $set;
}
$all =str_split($all);
for ($i =0; $i < $length -count($sets); $i++) {
$password .= $all[array_rand($all)];
}
$password =str_shuffle($password);
return $password;
}
}
Das Passwort konfigurierbar machen
Legt im Rootverzeichnis eurer Extension eine Datei mit dem Namen ext_conf_template.txt an. Mit dieser Datei könnt ihr eure Extension über den Extensionmanager konfigurierbar machen: