Google Tag Manager mit eigenem Cookie-Banner. Tracking passiert erst bei nächstem Seitenaufruf, nicht direkt nach Einwilligung in Cookie-Banner.

Ich zeige im Schnelldurchlauf und relativ schematisch die Erkenntnisse des heutigen Tages: Probleme und Stolpersteine beim Einbinden des Google Tag Managers in Verbindung mit Google Analytics und einer eigenen Cookie-Consent Lösung.

Probleme mit eigenem Cookie-Consent-Banner und Google Tag Manager / Google Analytics

Möchte man einen eigenen Cookie-Banner umsetzen, der dem Google Tag Manager erlaubt, Analytics auf der Seite einzubinden, ergeben sich ein paar Probleme:

  1. Trotz korrekter Einbindung nach Anleitung wird die erste Seite, auf der auch die Einwilligung im Cookie-Banner passiert, nicht in Google Analytics getracked. Erst der nächste Seitenaufruf wird erfasst. Dadurch geht die Seite mit dem Cookie-Banner – sprich die erste Seite, die der User besucht – im Tracking verloren.
  2. Obwohl im Tag Manager für den Analytics Tag unter „Integrierte Einwilligungsprüfungen“ steht, dass als Default ad_storage und analytics_storage auf granted stehen müssen, bevor das Tracking beginnt, wird dies nicht beachtet. Auch beim Verwenden des default-dataLayers, der diese beiden Variablen auf „denied“ setzt, wird der pageView getrackt.

Zu Punkt 1 waren die Erkenntnisse:

  • Ein reines push in den dataLayer über gtag('consent', 'update', ...) mit granted führt nicht automatisch auch zu einem Einbinden von Google Analytics – und damit zu einem direkten PageView-Tracking!
  • Alles, was man am dataLayer für consent.update setzt, muss VOR der Einbindung des Tag-Managers-Script-Tags passieren, also z.B. unmittelbar nachdem man die Defaults über gtag('consent', 'default', ...) angibt. Der dataLayer wird komplett „gefüllt“, dann der Tag-Manager eingebunden, der den Container dann abholt und verarbeitet.
  • Wird im Nachhinein über gtag('consent', 'update', ...) die Einwilligung geändert passiert gar nichts.
  • Events hingegen, die man über dataLayer.push({'event': 'deinEventName'}); triggern kann, kommen beim Tag Manager an und erlauben es, auch NACH Laden des Tag-Managers Scripte und Tags in die Seite zu injecten / einzufügen.

Zu Punkt 2 waren die Erkenntnisse:

  • Die Doku scheint mir irreführend – oder es ist ein Bug. Im Tag-Manager klingt „Integrierte Einwilligungsprüfungen“ wie eine Voreinstellung, die „ad_storage“ und „analytics_storage“ mit „granted“ voraussetzt, damit der Google Analytics Tag eingebunden wird. Er wird aber auch mit consent.default und „denied“ direkt eingebunden.
  • Interessant daran: Es werden keine Cookies gesetzt – ein pageView-Tracking findet faktisch aber trotzdem statt, was aus DSGVO-Sicht kritisch ist.
  • Lösung ist es, „ad_storage“ und „analytics_storage“ explizit noch mal unter „Zusätzliche Einwilligung zur Auslösung des Tags erforderlich“ anzugeben.

Im Video werden die oben beschriebenen Punkte alle gezeigt.

Links:

Die Anleitung und Codes in der Google Tag Manager Dokumentation:
https://developers.google.com/tag-manager/consent

Das Tag Assistant Legacy Plugin für Chrome:
https://chrome.google.com/webstore/detail/tag-assistant-legacy-by-g/kejbdjndbnbjgmefkgdddjlbokphdefk

Und hier der finale Quelltext aus dem Video als Orientierung.
Bitte nur als Skizze / Funktionsprinzip verstehen 🙂

Denkt daran, das XXXXXXXXXXXXXXX mit Eurer Google-Tag-Manager-ID zu ersetzen.


<html> 
  
  <head>       
    <script> 
        
      window.dataLayer = window.dataLayer || [];  
  
      function gtag(){  
        dataLayer.push(arguments);  
      }  
      // Default ad_storage to 'denied'.  
      gtag('consent', 'default', {  
        'ad_storage': 'denied',  
        'analytics_storage': 'denied'  
      });  
  
      if (document.cookie.indexOf('dsgvo=1') >-1) {  
        consentWasGranted();  
      }  
  
      function consentWasGranted() {  
        gtag('consent', 'update', {  
          'ad_storage': 'granted',  
          'analytics_storage': 'granted'  
        });  
      }  
        
      function consentWasClicked() {  
        consentWasGranted();  
        dataLayer.push({'event': 'consentChange'});  
        document.cookie = 'dsgvo=1';  
      }  
  
    </script>   
      
    <!-- Google Tag Manager --> 
    <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':  
      new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],  
      j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=  
      'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);  
      })(window,document,'script','dataLayer','XXXXXXXXXXXXXXX');</script> 
      <!-- End Google Tag Manager --> 
    
  </head> 
    
    <body> 
    <button onclick="consentWasClicked()">Send EVENT</button> 
  </body> 
  
</html>

Typo3: Link zu einer Seite im Frontend aus Scheduler-Task / dem Backend-Kontext oder CLI-Job erzeugen

Jeder kommt früher oder später an diesen Punkt: Er ist in Typo3 in einem Context wie einem Scheduler Task oder CLI Job und möchte einen Link zu einer Seite im Frontend generieren, z.B. um zyklisch eine E-Mail zu generieren, die einen Link zum Frontend enthält.

Jeder Typo3-Entwickler geht hier zunächst den gewohnten Weg: Der UriBuilder. Den Code kopiert man sich aus einem Controller oder der Typo3 Doku – und er sieht ungefähr so aus:

$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
$uriBuilder = $objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
$uri = $uriBuilder
  ->reset()
  ->setTargetPageUid(199)
  ->setArguments(['test'=>99])
  ->build();

Funktioniert wunderbar im Frontend-Context des Controllers – aber was passiert im Scheduler / Backend-Kontext?
Aus dem Link wird etwas in dieser Art:
/typo3/index.php?route=%2Fmodule%2Fsystem%2FtxschedulerM1&token=xxx&test=99
Für das Problem leider unbrauchbar. Und wieder eines der Themen, bei der man denkt: „Ist ganz einfach zu lösen“ – und sich dann wundert, wenn man nach einigen Stunden immer noch nicht die richtige Lösung finden konnte.

Leider ist das Web zu der Frage „Wie erzeugt man einen Link zum Frontend aus dem Backend-Kontext / aus dem Scheduler“ voll von Antworten, wie dieser hier bei StackOverflow: Allein beim Runterscrollen durch das Script denkt man eigentlich nur: Ist das Euer ernst? Ich will doch nur einen Link. Irgendwann baut man aus Ungeduld den Link dann vielleicht per Hand ($link = 'https://www.domain.de/?id=' . $pid) – aber richtig „gut“ fühlt sich das nicht an. Die URL ist hardgecoded, Parameter sind offen gelegt, der Slug / RealUrl fehlt. Nicht schön. Und erschwerend kommt hinzu: Der Code scheint sich von Typo3-Version zu Version zu ändern.

Wie es einfacher geht? Mit unserer Extension nnhelpers, die genau dort hilft, wo man denkt „Das muss doch irgendwie einfacher gehen“. nnhelpers ist eine Sammlung von Tools und Funktionen, die die Arbeit mit Typo3 und der Extension-Entwicklung erheblich vereinfacht. Alle Scripte sind als einzeilige „No-Brainer“ konzipiert. Und alle Methoden sind direkt im Backend in einem übersichtlichen „Spickzettel“ zusammengefasst.

So sieht die Lösung dann mit `nnhelpers` aus:

\nn\t3::Page()->getLink(199, ['test'=>99]);

Der gleiche Befehlt funktioniert in jedem Kontext – ob Frontend, Backend, CLI. Und er funktioniert versionsübergreifend für Typo3 6 aufwärts.
Er erzeugt einen Link zum Frontend aus dem Backend-Kontext oder Scheduler-Kontext oder CLI-Kontext mit absoluter URL zur Seite, bei der aber auch der Slug bzw. RealUrl-Pfad inkl. Parameter korrekt aufgelöst wird.

Richtig gesehen: `nnhelpers` braucht keine Instanz des `UriBuilder, der über den `ObjectManager` instanziiert werden muss, der über die `GeneralUtility` generiert werden muss. Der Einzeiler kann einfach – so wie er ist – an der Stelle eingefügt werden, wo er benötigt wird. Er hält damit den Code sauber und übersichtlich.

Hier geht es zur Extension im Repository von Typo3:
https://extensions.typo3.org/extension/nnhelpers

Die Dokumentation dazu gibt es hier:
Auf Deutsch: https://labor.99grad.de/typo3-docs/nnhelpers/de/
Und English: https://labor.99grad.de/typo3-docs/nnhelpers/en/

nnhelpers im Typo3 Repository (TER) veröffentlicht

Seit heute ist einer unser wichtigster „Mitarbeiter“ im TER und unterstützt Euch ab sofort bei Euren eigenen Projekten! Selbstverständlich vollkommen ehrenamtlich und ohne Berechnung 😉

Die Extension „nnhelpers“, eine Sammlung extrem hilfreicher Methoden und ViewHelper, die wir ausnahmslos jeden Tag bei der Extension-Entwicklung in Typo3 brauchen.

Hier geht es zur Extension im Repository von Typo3:
https://extensions.typo3.org/extension/nnhelpers

Die Dokumentation dazu gibt es hier:
https://labor.99grad.de/typo3-docs/nnhelpers/de/
https://labor.99grad.de/typo3-docs/nnhelpers/en/

typeNum in Link funktioniert nicht. Typo3 type removed from URL (Typo3 9, Typo3 10)

Problem nach dem Update auf Typo3 9 oder 10:

Beim Erstellen eines Links über den ViewHelper f:link.page, f:uri.page, f:link.typolink, f:uri.typolink etc. wird der typeNum-Parameter ignoriert.
Auch beim Versuch, type manuell über additionalParams zu übergeben, erscheint kein &type=123456 in der generierten URL:

<f:link.page pageType="123456" additionalParams="{s:1,p:2,type:123456}" pageUid="3"<

wird zu:
https://www.domain.de/?s=1&p=2

Der type=123456-Parameter wird einfach entfernt.

Lösung:
Die site-yaml Konfiguration prüfen und darauf achten, dass ein routeEnhancer für den Page-Type definiert wurde!


routeEnhancers:
  PageTypeSuffix:
    type: PageType
    default: '/'
    index: ''
    map:
      beispiel: 123456

Hier – als kleiner Spickzettel – noch mal die TypoScript-Setup zum simplen Aufruf einer Methode über typeNum.

Mit entsprechender Einstellung im TypoScript und der Site-Konfiguration (yaml) wird die angegebene Methode beim Aufruf der URL https://www.beispiel.de/?type=123456 ausgeführt.


beispiel = PAGE
beispiel {
  typeNum = 123456
  config {
    disableAllHeaderCode = 1
    xhtml_cleaning = 0
    admPanel = 0
    additionalHeaders = Content-type: text/plain
    no_cache = 1
    contentObjectExceptionHandler = 0
  }
  10 = USER
  10.userFunc = Pfad\Zu\Deinem\Script->methodenName
}

Für Suchmaschinen: Typo3 Typolink ignores type-Parameter, typeNum not working Typo3, typeNum funktioniert nicht, type wird aus URL entfernt beim ViewHelper. Typoscript mit page-type wird nicht ausgeführt. Problem typeNum in Link-URL.

Typo3 tx_form: Empfänger im Frontend auswählbar machen. Eigenes Formular-Element und eigener Finisher

Im Formular-Editor des FormFrameworks von Typo3 soll man verschiedene Empfänger angeben können. Im Frontend erscheint eine Dropdown-Liste der Empfänger. Der User kann eine Zieladresse aus dem Dropdown auswählen und so bestimmen, wer die Mail erhält.


Anleitung im Backend FormEditor

  • Neues Formular-Element in Formular einfügen
  • Im Formular-Element eine Liste der möglichen Empfänger angeben. Pro Zeile ein Empfänger in dieser Schreibweise: email@adresse.de;Empfängername
  • Neuen Finisher für das Formular auswählen

1. Wähle einen Namen für Deine Extension

Wenn Du die beiden Felder hier ausfüllst, wird im Quelltext alles automatisch für Dich ersetzt:



2. Eine Extension anlegen

(oder eine vorhandene nutzen)

Wir ignorieren mal alle Normen und halten es simpel. Folgende Dateien und Struktur brauchst Du in Deiner Extension.

Ersetze nng durch den Ordner-Namen Deiner Extension, in der Du arbeitest.
Ersetze das Wort RecipientSelector im Dateinamen durch Deinen Bezeichner.

nng
│
├── Classes
│   ├── Finishers
│   │   └── RecipientSelectorFinisher.php
│   ├── ViewHelpers
│   │   └── RecipientSelectorViewHelper.php
├── Configuration
│   ├── TypoScript
│   │   └── setup.txt
│   └── Yaml
│       ├── BaseSetup.yaml
│       ├── FormEditorSetup.yaml
│       └── FormEngineSetup.yaml
├── Resources
│   ├── Private
│   │   └── FormFramework
│   │       └── Frontent
│   │           └── Partials
│   │               └── RecipientSelector.html
│   └── Public
│       ├── Icons
│       │   └── RecipientSelector-icon.svg
│       └── JavaScript
│           └── Backend
│               └── FormEditor
│                   └── RecipientSelectorViewModel.js
├── ext_emconf.php
├── ext_localconf.php
└── ext_typoscript_setup.txt

3. Inhalte der Dateien

Folgender Code muss in die einzelnen Dateien:


Configuration/TypoScript/setup.txt


# Einstellungen für das Frontend
plugin.tx_form.settings.yamlConfigurations {
   153450370601 = EXT:nng/Configuration/Yaml/FormEditorSetup.yaml
}
# Einstellungen für das Backend
module.tx_form.settings.yamlConfigurations {
   153450370601 = EXT:nng/Configuration/Yaml/FormEditorSetup.yaml
   153450370602 = EXT:nng/Configuration/Yaml/FormEngineSetup.yaml
}

Configuration/Yaml/FormEditorSetup.yaml


# ---------------------------------------------------------------------------
# Diese YAML wird im Backend UND Frontend geladen.
# 

TYPO3:
  CMS:
    Form:
      prototypes:
        standard:
          formElementsDefinition:

            # ---------------------------------------------------------
            # Registrierung zusätzlicher Fluid-Pfade

            Form:
              renderingOptions:
                templateRootPaths:
                  125: 'EXT:nng/Resources/Private/FormFramework/Frontend/Templates/'
                partialRootPaths:
                  125: 'EXT:nng/Resources/Private/FormFramework/Frontend/Partials/'
                layoutRootPaths:
                  125: 'EXT:nng/Resources/Private/FormFramework/Frontend/Layouts/'

            # ---------------------------------------------------------
            # Registrierung des neuen Formular-Elementes

            RecipientSelector:
              __inheritances:
                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
              formEditor:
                label: 'Empfänger-Auswahl'
                group: custom
                groupSorting: 1000
                iconIdentifier: 'nng-selectrecipient'
                predefinedDefaults:
                  properties:
                    nng-recipients: ''
                editors:
                  300:
                    identifier: 'nng-recipients'
                    templateName: 'Inspector-TextareaEditor'
                    label: 'Empfänger-Liste'
                    propertyPath: 'properties.nng-recipients'

          # ---------------------------------------------------------
          # Registrierung des Finishers
          
          finishersDefinition:
            RecipientSelector:
              __inheritances:
                10: 'TYPO3.CMS.Form.mixins.finishersEmailMixin'
              implementationClassName: 'Nng\RecipientSelector\Finishers\RecipientSelectorFinisher'
              FormEngine:
                label: "C E-Mail an Empfänger aus Auswahl (nng)"
              formEditor:
                iconIdentifier: 'nng-selectrecipient'
                label: 'D E-Mail an Empfänger aus Auswahl (nng)'
                predefinedDefaults:
                  options:
                    pageUid: ''
                    additionalParameters: ''



Configuration/Yaml/FormEngineSetup.yaml



# ---------------------------------------------------------------------------
# Diese YAML wird NUR im Backend geladen.
# 

TYPO3:
  CMS:
    Form:
      prototypes:
        standard:

          # -------------------------------------------------------
          # JS für Formular-Element im Backend bereitstellen
          
          formEditor:

            dynamicRequireJsModules:
              additionalViewModelModules:
                125: 'TYPO3/CMS/Nnsite/Backend/FormEditor/RecipientSelectorViewModel'

            formEditorPartials:
              FormElement-RecipientSelector: 'Stage/SimpleTemplate'

          formElementsDefinition:
            Form:
              # -------------------------------------------------------
              # Backend: Definition des Finishers

              formEditor:
                editors:
                  # 900 ist fix. Hier werden die Finisher definiert.
                  900:
                    selectOptions:
                      125:
                        value: 'RecipientSelector'
                        label: 'E-Mail an Empfänger aus Auswahl (nnsite)'

                propertyCollections:
                  finishers:
                    125:
                      identifier: 'RecipientSelector'
                      editors:
                        __inheritances:
                          10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
                        100:
                          label: "E-Mail an Empfänger aus Auswahl (nnsite)"
                        120:
                          identifier: 'fieldSubject'
                          templateName: 'Inspector-TextEditor'
                          label: 'Betreff'
                          propertyPath: 'options.fieldSubject'
                          enableFormelementSelectionButton: true
                          propertyValidators:
                            10: 'NotEmpty'
                        500:
                          identifier: 'fieldFromEmail'
                          templateName: 'Inspector-TextEditor'
                          label: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderAddress.label'
                          propertyPath: 'options.fieldFromEmail'
                          enableFormelementSelectionButton: true
                          propertyValidatorsMode: 'OR'
                          propertyValidators:
                            10: 'NaiveEmail'
                            20: 'FormElementIdentifierWithinCurlyBracesExclusive'
                          fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderAddress.fieldExplanationText'
                        600:
                          identifier: 'fieldFromName'
                          templateName: 'Inspector-TextEditor'
                          label: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderName.label'
                          propertyPath: 'options.fieldFromName'
                          enableFormelementSelectionButton: true
                          propertyValidators:
                            10: 'FormElementIdentifierWithinCurlyBracesInclusive'
                          fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderName.fieldExplanationText'

Classes/ViewHelpers/RecipientSelectorViewHelper.html

Das eigentliche Fluid-Template für die Ausgabe im Frontend.


<?php

namespace Extname\RecipientSelector\ViewHelpers;

use \TYPO3\CMS\Extbase\Utility\DebuggerUtility;
use \TYPO3\CMS\Core\Utility\GeneralUtility;


class RecipientSelectorViewHelper extends \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper {


    /**
     * @var boolean
     */
    protected $escapeChildren = false;


    /**
     * @var boolean
     */
    protected $escapeOutput = false;
    
    
    /**
	 * Initialize arguments.
	 *
	 * @return void
	 */
	public function initializeArguments() {
		parent::initializeArguments();
    	$this->registerArgument('str', 'string', 'Text', false);
	}
	
	
	/**
	 * 
	 *
	 * @throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
	 * @return string Rendered tag
	 */
	public function render() {
		$str = $this->arguments['str'] ?: $this->renderChildren();
		$rows = explode("\n", $str);
		foreach ($rows as $k => $row) {
			$parts = explode(";", $row);
			$rows[$k] = ['key' => $k, 'email' => $parts[0], 'label'=>$parts[1]];
		}
		return $rows;
    }


}

Classes/Finishers/RecipientSelectorFinisher.php


<?php
namespace Extname\RecipientSelector'\Finishers;
 
use TYPO3\CMS\Core\Utility\GeneralUtility;	 
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\PathUtility;

class RecipientSelectorFinisher extends \TYPO3\CMS\Form\Domain\Finishers\EmailFinisher {
    
    /**
	 * Finisher
     * 
	 */
	protected function executeInternal()
	{
		$formRuntime = $this->finisherContext->getFormRuntime();
		$formValues = $this->finisherContext->getFormValues();

		$fromEmail = $this->parseOption('fieldFromEmail');
		$fromName = $this->parseOption('fieldFromName');
		$subjectEmail = $this->parseOption('fieldSubject');

		$recipientElement = false;

		// Finde das ERSTE Formular-Element, in dem die Empfänger definiert wurden
		foreach ($formValues as $k => $val) {
			$element = $formRuntime->getFormDefinition()->getElementByIdentifier($k);
			if ($element->getType() == 'RecipientSelector') {
				$recipientElement = [
					'key' 		=> $k,
					'element' 	=> $element,
					'value'		=> $val,
				];
			}
		}

		if (!$recipientElement) {
			die('Finisher ist im Formular angegeben, aber Formular-Element für Auswahl des Empfängers fehlt.');
		}

		$recipients = explode("\n", $recipientElement['element']->getProperties()['nnsite-recipients'] ?? '');

		foreach ($recipients as $n=>$recipient) {
			$parts = explode(";", $recipient);
			$recipients[$n] = [
				'key' => $n,
				'label' => $parts[1],
				'email' => $parts[0],
			];
		}

		if (!$recipients) {
			die('Keine Zieladressen im Formular angegeben.');
		}

		$recipientName = $recipients[$recipientElement['value']]['label'] ?? '';
		$recipientEmail = $recipients[$recipientElement['value']]['email'] ?? '';

		if (!$recipients) {
			die('Zieladresse nicht bekannt.');
		}

		$this->setOption('templateName', 'html');
		$this->setOption('subject', $subjectEmail);
		$this->setOption('recipientName', $recipientName);
		$this->setOption('recipientAddress', $recipientEmail);
		$this->setOption('senderName', $fromName);
		$this->setOption('senderAddress', $fromEmail);

		parent::executeInternal();
	}

}

Resources/Private/FormFramework/Frontend/Partials/RecipientSelector.html

Das eigentliche Fluid-Template für die Ausgabe im Frontend.

<div>
	<h2>{element.properties.elementDescription}</h2>
	<f:form.textfield
		property="{element.identifier}"
		id="{element.uniqueIdentifier}"
		class="{element.properties.elementClassAttribute} form-control form-field"
		errorClass="{element.properties.elementErrorClassAttribute}"
		additionalAttributes="{formvh:translateElementProperty(element:element, property: 'fluidAdditionalAttributes')}"
	/>	
</div>

Resources/Public/Icons/RecipientSelector-icon.svg

Ein simples, quadratisches SVG-Icon für die Darstellung im Backend.


Resources/Public/JavaScript/Backend/FormEditor/RecipientSelectorViewmodel.js


// Resources/Public/JavaScript/Backend/FormEditor/RecipientSelectorViewmodel.js

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') === 'RecipientSelector') {
                    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);
});

ext_emconf.php

Der übliche Code, um eine Extension zu registrieren – falls nicht schon vorhanden.

<?php
$EM_CONF[$_EXTKEY] = [
    'title' => 'Meine Extension',
    'description' => '',
    'category' => 'plugin',
    'author' => 'David Bascom',
    'author_email' => 'deine@email.de',
    'state' => 'stable',
    'internal' => '',
    'uploadfolder' => '1',
    'createDirs' => '',
    'clearCacheOnLoad' => 0,
    'version' => '0.1',
    'constraints' => [
        'depends' => [
            'typo3' => '8.7.0',
        ],
        'conflicts' => [],
        'suggests' => [],
    ],
];

ext_localconf.php

Registriert das Icon

<?php 
if (TYPO3_MODE === 'BE') {
    /** @var \TYPO3\CMS\Core\Imaging\IconRegistry $iconRegistry */
    $iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
    $iconRegistry->registerIcon(
        'RecipientSelector-icon',
        \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
        ['source' => 'EXT:nng/Resources/Public/Icons/RecipientSelector-icon.svg']
    );
}

ext_typoscript_setup.txt

Lädt das TypoScript, ohne das im Root-Template die „Enthält Erweiterungen“ gewählt werden müssen.

<INCLUDE_TYPOSCRIPT: source="FILE:EXT:nng/Configuration/Typoscript/setup.txt">

Eigenes Custom Form Element für tx_form-Extension – Typo3 v8

How-To für das Erstellen eines Custom Form Elements für die tx_form-Extension unter Typo3 v8.

Falls man ein Custom Form Element erstellen möchte, haltet euch an das How-To. Für detaillierte Informationen und die Dokumentation zur ext:form besucht: https://docs.typo3.org/typo3cms/extensions/form/Index.html

Außerdem haben wir eine „Anleitung für Dummies und Normalsterbliche, die einfach einen Job zu erledigen haben“ erstellt – eine Kurzfassung mit farblicher Kennzeichnung und einem simplen Online-Generator.

Weiterlesen

Datei-Upload in eigenem Typo3 Backend Module

Zum Upload einer Datei im Backend oder Frontend von Typo3 8+ hilft die ExtendedFileUtility mit ein paar schönen Methoden.

Für Suchmaschinen: Upload einer Datei in eigenem Backend-Module von Typo3. Upload file in typo3 backend module. File-Upload Typo3 backend, move_uploaded_file() in Mod-Controller. Typo3 ExtendedFileUtility upload example. TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::init(). Upload files in TYPO3 from Frontend. Datei vom Frontend hochladen und im Controller an Ort bewegen. Typo3 upload file data. f:form.upload nutzen zum Upload einer Datei aus dem Backend oder Frontend.

Weiterlesen

Extbase/Fluid ViewHelper für TCA Tabellen-Wizard (tablewizard) in eigener Extension nutzen

Problem: Man möchte in einer eigenen Extension den Tabellen-Wizard für ein TCA-Feld nutzen und diese dann per css_styled_content in einem Fluid-Template ausgeben lassen. Leider konnte ich keinen ViewHelper dafür finden und auch die ersten Verdächtigen z.B. über so etwas wie f:format.html parseFuncTSPath =“tt_content.table.20″ scheiterten.

Für Suchmaschinen: tca tablewizard viewhelper für tabellen. Eigener ViewHelper für tablewizard Daten Typo3. Parse tablewizard data in fluid template. tablewizard String in Tabelle konvertieren in eigener Extension. Tabellendaten in Typo3 Fluid ViewHelper umwandeln. Konvertieren von Daten aus dem Tabellen-Assistenten für Typo3. tablewizard in eigener Extension nutzen und in Fluid per css_styled_content parsen lassen.

Table-Wizard im TCA nutzen
Dazu nutzt man im TCA das Script wizard_table.php:

'tabellen_daten_feld' => array(
   'label' => 'Lebenslauf',
   'l10n_mode' => 'mergeIfNotBlank',
   'config' => array (
      'type' => 'text',
      'cols' => '30',   
      'rows' => '5',
      'default' => '',
      'wizards' => array(
         '_PADDING' => 2,
         'list' => Array(
            'notNewRecords' => 1, 
            'type' => 'script',
            'title' => 'Table wizard',
            'icon' => 'wizard_table.gif',
            'script' => 'wizard_table.php',
            // So kann die Option "kleine Felder" automatisch deaktiviert werden
            // 'script' => 'wizard_table.php?TABLE[textFields]=0',
            'params' => array(
               'xmlOutput' => 0
            )
         ),
      ),
   ),
),

ViewHelper für das Rendern der Tabelle
Der ViewHelper nutzt css_styled_content um die per Zeilenumbruch und mit dem Pipe-Symbol (|) getrennte Spalten und Zeilen in eine Tabelle umzuwandeln.

registerArgument('table', 'string', 'Die Tabellen-Daten, Delimiter ist das Pipe-Symbol', false, null);
      $this->registerArgument('layout', 'string', 'Die Zahl, die an der Klasse contenttable-XX erscheint', false, null);
   }

    /**
     * Rendert die Tabelle mit csc_styled_content
     */

    public function render() {

      if (!$this->arguments['table']) {
         $this->arguments['table'] = html_entity_decode($this->renderChildren());
      }
      
      $this->cscController->cObj = $this->configurationManager->getContentObject();
      $this->cscController->cObj->data = $this->arguments;

      return $this->cscController->render_table( null, array('field'=>'table') );
      
      
    }
}
?>

Der ViewHelper kann so eingesetzt werden:
Dabei kann unter „layout“ eine Zahl angegeben werden, die bei an der Tabelle als z.B. class=“contenttable-2″ erscheint.

{namespace VH=Pfad\ZuDeinen\ViewHelpers}

{tabellen_daten_feld}



{VH:table(layout:2,table:tabellen_daten_feld)}

Multiple YouTube Videos auf Seite in iFrame andere anhalten / stoppen

Problem: Man hat auf einer Seite mehrere YouTube Videos als iFrame eingebunden. Die Videos lassen sich aber parallel starten und dadurch laufen alle Videos gleichzeitig. Dieses Script stoppt alle anderen Videos auf der Seite automatisch, wenn eines der Videos gestartet wird:

Für Suchmaschinen: youtube alle videos stoppen wenn ein video startet, stop all iframes youtube videos when one video starts. Andere Videos anhalten, wenn ein Video läuft. Automatisch alle YouTube Filme anhalten wenn neues Video play gedrückt wird. Immer nur ein Video abspielen in YouTube iFrame auch wenn mehrere Videos auf der gleichen Seite eingebunden wurden.

(function ($) {
   $(function () {

      var tag = document.createElement('script');
      tag.src = "https://www.youtube.com/iframe_api";
      tag.type='text/javascript';
      $('body').append(tag);
   
      $('iframe[src*="youtube"]').each(function(){
         var $me = $(this);
         var src = $me.attr('src');
         if (src.indexOf('enablejsapi=1') == -1) {
            $me.attr({src: src + (src.indexOf('?') == -1 ? '?' : '&') + 'enablejsapi=1'});
         }
      });
   
      window.onYouTubeIframeAPIReady = function () {
   
         var playerCurrentlyPlaying = null;
         var player_cnt = 0;
         
         $('iframe[src*="youtube"]').each(function(){
            
            var $me = $(this);
            var player_id = $me.attr("id");
            
            if (!player_id) {
               player_id = "ytpl-"+(player_cnt++);
               $me.attr({id:player_id});
            }
            
            var player = new YT.Player( player_id, { 
               events: {   
                  'onReady': function () {},
                  'onStateChange': function (event) {
         
                     if (event.data == YT.PlayerState.PLAYING) { 
                        if (playerCurrentlyPlaying && playerCurrentlyPlaying != $me.data().player) {
                           playerCurrentlyPlaying.pauseVideo();
                        }
                        playerCurrentlyPlaying = $me.data().player;
                     }         
                  }
               }   
            });
            
            $me.data({player:player});
         });   
      }
   
   });
})(jQuery);

Deutscher Paket Dienst (DPD) SOAP Schnittstelle mit PHP nutzen um Versandetikett als PDF zu generieren

Hier ein schnelles Beispiel, wie die per PHP SoapClient() und DPD-WSDL-Schnittstelle ein Versand-Etikett als PDF generiert werden kann.

Für Suchmaschinen: PHP SOAP-Header für Authentifizierung, authenticate SOAP-Header, DPD-Api Versandetikett generieren, Generate DPD Label, DPD parse SOAP response, Fatal error: Uncaught SoapFault exception: [soap:Server] Fault occurred while processing. in getLabel.php:107 Stack trace: #0 getLabel.php(107): SoapClient->__call(’storeOrders‘, Array) #1 getLabel.php(107): SoapClient->storeOrders(Array).

getAuth(array(
      'delisId'          => 'your-Id',
      'password'         => 'your-Password',
      'messageLanguage'   => 'de_DE'
   ));
   
   // ...und das Token merken
   $auth = $res->return;
   
   
   // Jetzt das Label generieren:
   
   $c = new SoapClient('https://public-ws-stage.dpd.com/services/ShipmentService/V3_1?wsdl');
   
   $token = array(
      'delisId'         => $auth->delisId,
      'authToken'         => $auth->authToken,
      'messageLanguage'   => 'de_DE'
   );
   
   // Set the header with the authentication token
   $header = new SOAPHeader('http://dpd.com/common/service/types/Authentication/2.0', 'authentication', $token);
   $c->__setSoapHeaders($header);
   
   try {
      $res = $c->storeOrders( array
         (
            "printOptions" => array(
               "paperFormat" => "A4",
               "printerLanguage" => "PDF"
            ),
            "order" => array(
               "generalShipmentData" => array(
                  "sendingDepot" => $auth->depot,
                  "product" => "CL",
                  "mpsCompleteDelivery" => false,
                  "sender" => array(
                     "name1" => "Sender Name",
                     "street" => "Sender Street 2",
                     "country" => "DE",
                     "zipCode" => "65189",
                     "city" => "Wiesbaden",
                     "customerNumber" => "123456789"
                  ),
                  "recipient" => array(
                     "name1" => "John Malone",
                     "street" => "Johns Street 34",
                     "country" => "DE",
                     "zipCode" => "65201",
                     "city" => "Wiesbaden"
                  )
               ),
               "parcels" => array(
                  "parcelLabelNumber" => "09123829120"
               ),
               "productAndServiceData" => array(
                  "orderType" => "consignment"
               )
            )
         )
      );
   } catch (SoapFault $exception) {
      echo $exception->getMessage();
      die();
   }
   
   // Et voilà!
   
   header('Content-type: application/pdf');
   echo $res->orderResult->parcellabelsPDF;
?>