Generelles / Gut zu wissen (Siehe Clickstorm.de)

Zuerst lohnt sich ein Blick auf die Seiten von Clickstorm, in der ein Großteil der bei mir aufgetretenen Hindernisse bereits mit Lösungen beschrieben wurden

https://www.clickstorm.de/blog/typo3-12-entwickler

Danach funktionierte meine Webseite zwar immer noch nicht, aber den Rest habe ich mit den weiteren Tipps auf der Seite gelöst.

Umstellung auf CKeditor 5

Wenn man den Hinweisen auf der Clickstorm Seite gefolgt ist, dann hat man wieder einen halbwegs laufenden Backend Editor.
Um eigene “Link” Klassen für Interne, externe oder Links auf Files hinzuzufügen, muss man die Konfiguration noch erweitern. 

Das fehlt zwar in den Beispielen aus dem TYPO3 Core

public/typo3/sysext/rte_ckeditor/Configuration/RTE/Full.yaml


aber in der Klasse

public/typo3/sysext/rte_ckeditor/Classes/Controller/BrowseLinksController.php


habe ich die notwendigen Hinweise gefunden:

Wie in LTS 11, braucht man in der eigenen “myConfig.yaml” zusätzlich zu “Editor:” einen eigenen Abschnitt:

Buttons:         
   properties:      
      class:         
         allowedClasses: ‘internalLink,externalLink,fileLink pdf,btn bt-primary.btn bt-primary….'


und um bestimmte  Classen nur für Link Typen wie “File” oder "Page" zuzulassen, wie bisher

classesAnchor:       
   internalLink:                
      class: 'internalLink'                
      type: 'page'      
   filePdf:          
      class: 'fileLink pdf'          
      type: 'file'

Das Beispiel von meiner Webseite :  default.yaml 
 

Import und Namen der typoscript Dateien

Historisch gewachsen, hatte meine TYPO3 Instanz über die Jahre diverse  Benennungen und deren Zugriff erlebt. 
Diverse Datei Endungen wie:

'ts', 'txt', 'text' , 't3' , 't3s' , 'tscript' , 'tsconfig' 


and different kind of importing each other:
f.e.

<INCLUDE_TYPOSCRIPT src="/typo3conf/ext/jve_template/Configuration/TypoScript/Setup.ts"> 

<INCLUDE_TYPOSCRIPT src="FILE:EXT:jve_template/Configuration/TypoScript/Setup/Setup.t3s"> 
 


but should be:

@import 'EXT:jve_template/Configuration/TypoScript/Setup.typoscript'In PHP Storm in den Localen Dateien zu ändern ist simple gelöst gewesen.In der Datenbank in den diversen Tabellen, vergisst man aber schnell einen Eintrag anzupassen. Tabellen und Felder die bei mir genutzt wurden**sys_template** -> config and constants \**pages** -> TSconfig \**fe_users** -> TSconfig \**fe_groups** -> TSconfig \**be_users** -> TSconfig \**be_groups** -> TSconfig 


sicher nicht vollständig. Dafür habe ich mir einen Upgrade Wizard gebaut, den ich bisher erfolgreich in 2 Instanzen getestet habe:

composer req jvelletti/jve-upgradewizard

bitte unbedingt die readme.md beachten (erst lokal testen und nur mit backup der DB!!!)

  

Älteres eigenes TT Content Element mit Image Elementen

Historisch hatten die selbstgebauten TT Content Elemente , z.b. für einen Slider im Feld pi1_flexform nur eine Komma separierte Liste mit File IDs, aber es gab keine Sys_file_reference einträge, die die Verlinkung zwischen sys_file und dem tt_content element hergestellt haben. 

Somit wurden im Backend keine verknüpften Bilder gezeigt.

Beispiel:

data.elements.lDEF.settings.images.vDEF = 104,105,1018,1020
sollte aber sein:
data.elements.lDEF.settings.images.vDEF = 4
und dazu die 4 Sys_fle_referenz einträge mit der UID des TT_content elements


tablenames=tt_content
table_local=sys:file
uid_local=xxx (=UID des TT_content elements)
uid_foreign=104 ( bzw. 105,1018,1020 )
 


ich habe mir dafür einen Upgrade Wizard gebaut. der funktioniert zwar nur mit “meinen” Feldern. Der nachfolgende Code muss somit nicht für andere Flexfor Konfigurationen angepasst werden. der kümmert sich nur um die Korrektur des Feldes “pi1_flexform” 
Der ist bisher nur in einer Instanz unter PHP 8.1 getestet. Darum kein BACKUP, kein Mitleid !!

Anpassungen in Zeile 54: dem "$elements" Array mit den Flexform Feldern. 

EXT:jve_template/Classes/Upgrades/UpgradeImagesWizard.php
   

<?php

declare(strict_types=1);

namespace Jve\JveTemplate\Upgrades;

use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Attribute\UpgradeWizard;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;

#[UpgradeWizard('jveTemplate_upgradeImagesWizard')]
final class UpgradeImagesWizard implements UpgradeWizardInterface
{

    public int $verboseLevel = 0 ;
    /**
     * Return the speaking name of this wizard
     */
    public function getTitle(): string
    {
        return 'Fix sys_file_references of content elements';
    }

    /**
     * Return the description for this wizard
     */
    public function getDescription(): string
    {
        return 'Content elements like image slider had no complete sys_file_reference and direct UID (list) of such records.';
    }

    /**
     * Execute the update
     *
     * Called when a wizard reports that an update is necessary
     */
    public function executeUpdate(): bool
    {
        if ( isset( $_SERVER['argv'][3]) ) {
            if (  $_SERVER['argv'][3] =="-v" ) {
                $this->verboseLevel = 16 ;
            }
            if (  $_SERVER['argv'][3] =="-vv" ) {
                $this->verboseLevel = 64 ;
            }
            if (  $_SERVER['argv'][3] =="-vvv" ) {
                $this->verboseLevel = 128 ;
            }
        };
        $elements = [
            "jvetemplate_ce_imageSlider" => [ 'section' => 'elements' , 'field' => 'images' ] ,
            "jvetemplate_ce_imageLink" => [ 'section' => 'main' , 'field' => 'images' ] ,
            "jvetemplate_ce_countdown" => [ 'section' => 'main' , 'field' => 'image' ]
        ] ;
        $result = true;
        foreach ( $elements as $ctype => $elementField ) {
            $objCount = 0 ;
            $objects = $this->getContentElements( $ctype ) ;
            while ( $object = $objects->fetchAssociative() ) {
                try {
                    $this->checkContentElement( $object , $elementField ) ;
                    $objCount ++ ;
                } catch ( \Exception $e )  {
                    $this->debugOutput( 0 ,  $e->getFile() . " - Line: " .  $e->getLine() .   $e->getMessage() ) ;
                    $result = false ;
                }
            }
            $this->debugOutput( 15 ,  "Found " . $objCount . " of " . $ctype ) ;
        }
        $this->debugOutput( 0 ,  "" ) ;
        return $result ;
    }



    private function getContentElements($ctype) {
        /** @var ConnectionPool $connectionPool */
        $connectionPool = GeneralUtility::makeInstance( "TYPO3\\CMS\\Core\\Database\\ConnectionPool");
        $queryBuilder = $connectionPool->getConnectionForTable('tt_content')->createQueryBuilder();
        $queryBuilder->getRestrictions()->removeAll()->add( GeneralUtility::makeInstance(DeletedRestriction::class));
        $queryBuilder->select('uid','header','pi_flexform' , 'CType' ) ->from('tt_content') ;
        $expr = $queryBuilder->expr();
        $queryBuilder->where( $expr->eq('CType', $queryBuilder->createNamedParameter( $ctype )) ) ;
        return $queryBuilder->executeQuery() ;
    }

    private function checkContentElement($ttContent , $structure ) {
        $settingsField = $structure['field'] ;
        $flexFormService = GeneralUtility::makeInstance(FlexFormService::class ) ;

        $data = [ 'tablenames' => 'tt_content' , 'fieldname' => 'settings.' . $settingsField , 'uid_foreign' => $ttContent['uid']] ;
        $settings = $flexFormService->convertFlexFormContentToArray( $ttContent['pi_flexform'] );

        $this->debugOutput( 63 , $ttContent['uid']   . "-". $ttContent['CType']   . ":". $settingsField ) ;

        if( isset( $settings['settings'][$settingsField] ) && !isset($settings['settings']['imagesrepaired'])) {
            $references = GeneralUtility::trimExplode("," , $settings['settings'][$settingsField] );

            if( count( $references) > 0 ) {
                foreach ($references as $refUid ) {
                    $updateCount = $this->updateSysfile( $refUid , $data ) ;
                    $this->debugOutput( 123 ,  "Reference " . $refUid . " updated: "  . $updateCount ) ;
                }
                if( $flexFormString = $this->repairFlexForm($ttContent['pi_flexform'] , count( $references) , $structure )) {
                    $updateCount = $this->updateContentElement($ttContent['uid'] , $flexFormString  ) ;
                     $this->debugOutput( 123 ,  "Flexform of tt_content : " . $ttContent['uid'] . " updated: "  . $updateCount ) ;
                }
            }
        }
    }
    private function updateContentElement($uid , $data) {
        /** @var ConnectionPool $connectionPool */
        $connectionPool = GeneralUtility::makeInstance( "TYPO3\\CMS\\Core\\Database\\ConnectionPool");
        $queryBuilder = $connectionPool->getConnectionForTable('tt_content')->createQueryBuilder();
        $queryBuilder->getRestrictions()->removeAll()->add( GeneralUtility::makeInstance(DeletedRestriction::class));
        $queryBuilder->update('tt_content')
            ->set( "pi_flexform", $data ) ;

        $expr = $queryBuilder->expr();
        $queryBuilder->where( $expr->eq('uid', $uid ) ) ;
        return $queryBuilder->executeStatement() ;
    }


    private function updateSysfile($uid , $data ) {
        /** @var ConnectionPool $connectionPool */
        $connectionPool = GeneralUtility::makeInstance( "TYPO3\\CMS\\Core\\Database\\ConnectionPool");
        $queryBuilder = $connectionPool->getConnectionForTable('sys_file_reference')->createQueryBuilder();
        $queryBuilder->getRestrictions()->removeAll()->add( GeneralUtility::makeInstance(DeletedRestriction::class));
        $queryBuilder->update('sys_file_reference')
            ->set( "tablenames" ,  $data['tablenames'])
            ->set( "fieldname" ,  $data['fieldname'])
            ->set( "table_local" ,  'sys_file')
            ->set( "uid_foreign" , intval( $data['uid_foreign']))
        ;

        $expr = $queryBuilder->expr();
        $queryBuilder->where( $expr->eq('uid', $uid )) ;
        return $queryBuilder->executeStatement() ;
    }

    /**
     * @param string $flexString
     * @param int $count
     * @param array  $settingsField
     * @return false|string
     */
    private function repairFlexForm(string $flexString , int $count, array $structure): bool|string
    {
        $settingsField = $structure['field'] ;
        $section = $structure['section'] ;
        $flexFormArray = GeneralUtility::xml2array( $flexString );
        if ( isset( $flexFormArray['data'][$section] )) {
            $imageRepaired = $flexFormArray['data'][$section] ;
            $flexFormArray['data'][$section]['lDEF']['settings.upgradeImagesWizardBack']['vDEF'] = $flexFormArray['data'][$section]['lDEF']['settings.' . $settingsField]['vDEF'] ;
            $flexFormArray['data'][$section]['lDEF']['settings.' . $settingsField]['vDEF'] = $count ;
            $flexFormArray['data'][$section]['lDEF']['settings.imagesrepaired']['vDEF'] = 1 ;
            $flexFormTools = new FlexFormTools();

            return $flexFormTools->flexArray2Xml($flexFormArray, addPrologue: true);
        }
        return false ;

    }

    private function debugOutput( $minVerbosity , $text ) {
        if ( $this->verboseLevel > $minVerbosity  ) {
            echo "\n" . $text ;
        }
    }


    /**
     * Is an update necessary?
     *
     * Is used to determine whether a wizard needs to be run.
     * Check if data for migration exists.
     *
     * @return bool Whether an update is required (TRUE) or not (FALSE)
     */
    public function updateNecessary(): bool
    {
        return true;
    }

    /**
     * Returns an array of class names of prerequisite classes
     *
     * This way a wizard can define dependencies like "database up-to-date" or
     * "reference index updated"
     *
     * @return string[]
     */
    public function getPrerequisites(): array
    {
        return ['database up-to-date' , 'reference index updated'] ;
    }


}



 

FeLogin - Changes - Eigenes Template muss angepasst werden

Das Partial CookieWarning wird nicht mehr benötigt und besonders wichtig: 

das Login Template hat sich geändert: im FORM Tag wird unbedingt  der Parameter
requestToken="{requestToken}"
benötigt:

<f:form target="_top" fieldNamePrefix="" action="login" requestToken="{requestToken}" additionalAttributes="{spellcheck: 'false'}">


 

Content-Security-Policy benötigt zu "https:" Zugriff auf blob:

Mit V12 kann man mit den folgenden Settings das generieren der Content Security Policy TYPO3 überlassen. 

$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.frontend.enforceContentSecurityPolicy'] = true ;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.backend.enforceContentSecurityPolicy'] = true ;

Alternativ die Einstellungen auf FALSE setzten, dann aber in den eigenen Einstellungen in z. B. via .htaccess darauf achten, dass neben https: auch “blob:” enthält, sonst werden im Backend keine Inhalte im Bereich Admin geladen.  

default-src https: blob: 

local in meiner Entwicklungsumbebung habe ich das:

Header set Content-Security-Policy "default-src https: blob: 'unsafe-eval' 'unsafe-inline'; font-src https: data: filesystem: 'unsafe-inline'; img-src https: data: ;"