Typo3 und extbase: Frontendlink in einem Backend Hook generieren


November 17, 2015 at 11:41
Typo3

Beim Speichern eines Models im Backend brauchte ich einen Hook, um, falls hidden auf 0 gesetzt wird, eine Benachrichtigungsmail zu schicken. Dies war die größte Quälerei in Typo3 seit langem, und insbesondere das - wie man meinen sollte - simple Erstellen eines Frontend Links bereitete mir massive Probleme. Ich sage nicht, dass meine Lösung die Beste ist, aber ich habe es geschafft und darum halte ich die gesamte Prozedur hier fest.

Legen wir kurz die Basisdaten fest. Meine Extension heißt ophi_inserat und lässt User im Frontend schlichte Inserate aufgeben. Das Model für ein Inserat heißt "Ad" und wird in der Datenbank in tx_ophiinserat_domain_model_ad gespeichert. Im Backend kann man solche Inserate dann bearbeiten und beim erstmaligen setzen von hidden auf 0 soll eben eine Benachrichtigungsmail an den Ersteller des Inserats (die E-Mail Adresse wird in $email gespeichert) mit einem Link zum Bearbeiten des Inserats geschickt werden.

Schritt 1: erstellen eines Hooks um eine Aktion nach dem Speichern durchzuführen

In der ext_localconf.php meiner Extension füge ich folgende Zeile hinzu:


$GLOBALS ['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = 'EXT:'.$_EXTKEY.'/Classes/Hook/NotificationHook.php:Ophi\OphiInserat\Hook\NotificationHook';

Möglicherweise gibt es bessere Stellen, um den Hook einzufügen, aber ich fand beim Recherchieren leider nur "processDatamap". Nun habe ich meine Datei NotificationHook.php angelegt, und zwar unter ophi_inserat/Classes/Hook/NotificationHook.php und diese braucht die Funktion processDatamap_postProcessFieldArray. Da ich nur beim Bearbeiten meiner Domain eine Aktion durchführen will, frage ich gleich ganz am Anfang ab, ob es sich überhaupt um diesen table handelt. Zunächst sieht die Datei also so aus:


namespace Ophi\OphiInserat\Hook;
class NotificationHook {
    public function processDatamap_postProcessFieldArray($status, $table, $id, &$fieldArray, &$reference){
        if ($table == 'tx_ophiinserat_domain_model_ad') {
			//insert code here
		}
	}
}

Nun. Aus irgendeinem Grund sind in $fieldArray nur manche Felder zugänglich, aber da hidden praktischerweise dabei ist, habe ich mich nicht näher damit befasst, wie man auf die anderen zugreifen kann. Jetzt wird's allerdings knifflig. Wir befinden uns im Backend und damit steht quasi nichts zur Verfügung, was irgendwie nützlich oder praktisch wäre. Zunächst habe ich mir mal mein Repository geholt und anhand von $id (welche der ID des bearbeiteten Items entspricht) mein Inserat $ad gegriffen.


if ($table == 'tx_ophiinserat_domain_model_ad') {
	if($fieldArray['hidden'] == 0){
		//fetch some classes
		$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('\TYPO3\CMS\Extbase\Object\ObjectManager');
		$persistenceManager = $objectManager->get('\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager');
		$adRepository = $objectManager->get('\Ophi\OphiInserat\Domain\Repository\AdRepository');

		//fetch the ad in question
		$ad = $adRepository->findOneByUid($id);
	}
}

Damit habe ich nun mein $ad zur Verfügung und kann dank repository und persistenceManager auch Daten ändern. Damit nicht bei jedem Editieren wieder der Link geschickt wird, habe ich eine Spalte $emailSent definiert, die true oder false sein kann und die ich nach dem ersten Senden der E-Mail dann auf true setze. Da meine Seite zweisprachig ist, brauche ich zunächst die Texte für meine E-Mail in der richtigen Sprache. Hier fängt's an, so richtig fies zu werden.

Schritt 2: Texte in der richtigen Sprache

Ich habe extra bei stackoverflow nachgefragt und es gibt tatsächlich eine Möglichkeit, auf die gesamte geparste Übersetzungsdatei zuzugreifen. Hierfür braucht man die sogenannte LanduageFactory und über den LanguageKey ("en", "de", etc.) und den Pfad zur Übersetzungsdatei bekommt man ein Array, das in "default" alle Defaultwerte enthält und in "en" oder "de" oder welchen languageKey auch immer man angegeben hat die übersetzten Werte hat. Ich habe mir dann noch eine kleine Funktion geschrieben, um aus diesem Array den übersetzten bzw. falls dieser noch nicht existiert den default-Wert auslesen zu können:


private function getTranslation($data, $languageKey, $target){
	if(isset($data[$languageKey][$target])){
		return $data[$languageKey][$target][0]['target'];
	}  else {
		return $data['default'][$target][0]['target'];
	}
}

Diese Lösung gefällt mir recht gut, hier der Ausschnitt aus der Hook Datei, wo ich mir das Array hole und die Funktion aufrufe:


$languageFactory = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('\TYPO3\CMS\Core\Localization\LocalizationFactory');
$langId = $ad->getSysLanguageUid();
$languageKey = $langId == 1 ? 'en' : 'de';
$data = $languageFactory->getParsedData('EXT:ophi_inserat/Resources/Private/Language/locallang.xlf', $languageKey);
$text1 = $this->getTranslation($data, $languageKey, 'tx_ophiinserat_domain_model_ad.email.text1');

Schritt 3: den Link generieren

Dieser Schritt hat mich bei weitem am meisten Zeit und Nerven gekostet. Offensichtlich gibt es im Typo3 Backend Kontext keinerlei Möglichkeit, einen Frontend Link zu generieren. Aus lauter Verzweiflung habe ich mir also folgendes überlegt: ich erstelle ein Plugin, das einfach nur den Link zurückgibt. Ich behandle den Aufruf wie einen Ajax Aufruf, sodass hier keine Parameter oder dergleichen mitgegeben werden, und hole mir mit file_get_contents die so generierte URL. Das klingt wie ein furchtbarer Umweg, aber Typo3 hat mir keine Wahl gelassen und irgendwie, glücklicherweise, funktioniert es so wie ich mir gedacht habe. Zunächst habe ich also in meiner ext_localconf.php ein neues Plugin hinzugefügt:


\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
	'Ophi.' . $_EXTKEY,
	'AdFormLink',
	array(
		'Ad' => 'generateLink',
	),
	// non-cacheable actions
	array(
		'Ad' => 'generateLink',
	)
);

Dann habe ich die setup.txt konfiguriert, um das ganze als ajax Aufruf verfügbar zu machen:


ajax_frontend_url < page
ajax_frontend_url {
    typeNum = 1447752540
    config {
        disableAllHeaderCode = 1
        xhtml_cleaning = 0
        admPanel = 0
    }
    10 = USER
    10 {
        extensionName = OphiInserat
        pluginName = AdFormLink
        vendorName = Ophi
        userFunc = tx_extbase_core_bootstrap->run
    }
}

Und damit ist die URL /index.php?type=1447752540&id=123 verfügbar, wobei 123 in dem Fall eine beliebige Typo3 Seite wäre. Im Controller in der generateLinkAction mache ich dann einfach folgendes:


public function generateLinkAction(){
	$link = $this->uriBuilder
		->setCreateAbsoluteUri(true)
		->setTargetPageUid(123)
		->setArguments( array('tx_ophiinserat_adform' => array('code' => '12354567') ) )
		->build();
	die($link);
}

Mehr macht das Ding nicht. Den Code kann man dann noch als Parameter mitgeben, der sollte ja für jedes Inserat anders sein, aber das war dann kein großes Ding mehr, das spar ich mir einfach mal. So, und jetzt geht's um die Wurst, ich muss im Backend ja irgendwie diese URL abfragen und das stellte sich als erstaunlich kompliziert heraus. Ich zeige hier einfach mal die notwendigen Funktionen für diese eigentlich einfache Aufgabe:


/**
 * I have no idea why $pageId is necessary
 *
 * @param $pageId
 * @return string
 */
private function getSiteUrl($pageId){
	$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('\TYPO3\CMS\Extbase\Object\ObjectManager');
	$beFunc = $objectManager->get('\TYPO3\CMS\Backend\Utility\BackendUtility');
	$libDiv = $objectManager->get('\TYPO3\CMS\Core\Utility\GeneralUtility');
	$utilityHttp = $objectManager->get('\TYPO3\CMS\Core\Utility\HttpUtility');
	$domain = $beFunc::firstDomainRecord($beFunc::BEgetRootLine(60));
	$pageRecord = $beFunc::getRecord('pages', 60);
	$scheme = is_array($pageRecord) && isset($pageRecord['url_scheme']) && $pageRecord['url_scheme'] == $utilityHttp::SCHEME_HTTPS ? 'https' : 'http';
	$siteUrl = $domain ? $scheme . '://' . $domain . '/' : $libDiv::getIndpEnv('TYPO3_SITE_URL');
	return $siteUrl;
}

/**
 * generate the link to the adForm in a convoluted, weird way because there seems to be no other option
 * @param $code
 * @return string
 */
private function generateLink($code){
	$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('\TYPO3\CMS\Extbase\Object\ObjectManager');
	$libDiv = $objectManager->get('\TYPO3\CMS\Core\Utility\GeneralUtility');

	//fetch the base url first
	$siteUrl = $this->getSiteUrl($this->ajaxPageId);
	//now set the ajax url
	$url = $siteUrl.'index.php?type=1447752540&id='.$this->ajaxPageId;
	//set headers because otherwise it doesn't work
	$headers = array(
		'Cookie: fe_typo_user=' . $_COOKIE['fe_typo_user']
	);
	//as far as I understand it, this is the equivalent of file_get_contents
	$result = $libDiv::getURL($url, false, $headers);

	//with luck $result should contain a valid link now.
	return $result;
}

Und so liefert generateLink tatsächlich den im Plugin generierten Link zurück. Und damit sind wir fertig. Die komplette NotificationHook.php gibt es hier: NotificationHook


Tags: backend extbase hook


Hinterlasse einen Kommentar: