WEBcoast Logo

Custom upload folder

Viele Redakteure benutzen die Funktion, direkt im Datensatz bzw. Inhaltselement Dateien wie z.B. Bilder hochzuladen, ohne sie vorher über die Dateiliste hochzuladen. Das führt standardmäßig dazu, dass die Dateien alle im Ordner "fileadmin/user_upload" liegen. Das führt nach sehr kurzer Zeit einem sehr unübersichtlichen Haufen an Dateien. Seit TYPO3 CMS 7 gibt es aber eine Möglichkeit dem Herr zu werden.

Da das Zielverzeichnis aus dem Einstellungen des Backend-Benutzers ermittelt wird, ist es nicht weiter verwunderlich, dass sich dort auf der Hook befindet, mit dem man das Zielverzeichnis beeinflussen kann.
TYPO3\CMS\Core\Authentication\BackendUserAuthentication: Zeile 1948 - 1957:

// HOOK: getDefaultUploadFolder
if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getDefaultUploadFolder'])) {
    foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getDefaultUploadFolder'] as $_funcRef) {
        $_params = [
            'uploadFolder' => $uploadFolder,
            'pid' => $pid,
            'table' => $table,
            'field' => $field,
        ];
        $uploadFolder = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
    }
}

Man kann demnach seine eigene Funktion sehr einfach über folgende Zeile in der ext_tables.php einer Extension registrieren:

/* register hook for custom upload folder */
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getDefaultUploadFolder'][] = \Vendor\MyExtName\Hooks\BackendUserAuthentication::class . '->getDefaultUploadFolder';

In welchem Namespace sich die Klasse befindet und wie die Methode heißt ist vollkommen egal. Wichtig ist, dass die Funktion ein Objekt vom Typ TYPO3\CMS\Core\Resource\Folder zurück gibt. Ist das nicht der Fall, wird der Button zum direkten Hochladen nicht angezeigt. Man bekommt das bisher ermittelte Zielverzeichnis über die $_params['uploadFolder'] übergeben. Diesen kann man zurück geben, wenn man mit seiner Logik keinen entsprechenden Ordner gefunden hat.

Mit folgender Implementierung werden die in Inhaltselementen hochgeladenen Bilder entsprechend der Seitenstruktur in Ordner einsortiert:

<?php

namespace Vendor\MyExtName\Hooks;

use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Messaging\FlashMessageService;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class BackendUserAuthentication
{
    /**
     * @var string
     */
    protected $targetPath = 'fileadmin/Inhalte/';

    /**
     * @param array                                                    $incomingParameters
     * @param \TYPO3\CMS\Core\Authentication\BackendUserAuthentication $backendUserAuthentication
     *
     * @return Folder
     */
    public function getDefaultUploadFolder($incomingParameters, $backendUserAuthentication)
    {
        $table = $incomingParameters['table'];
        $uploadFolder = null;
        if ($table === 'tt_content') {
            $pid = $incomingParameters['pid'];
            $pagePath = $this->getPagePath($pid);
            $absoluteTargetPath = GeneralUtility::getFileAbsFileName($this->targetPath . $pagePath);
            if (!file_exists($absoluteTargetPath) && !@mkdir(
                    $absoluteTargetPath,
                    octdec($GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask']),
                    true
                )
            ) {
                $flashMessage = new FlashMessage(
                    sprintf(
                        $GLOBALS['LANG']->sL(
                            'LLL:EXT:my_ext_name/Resources/Private/Language/locallang_backend.xml:uploads.targetDirectionMissing.message'
                        ),
                        $this->targetPath . $pagePath
                    ), $GLOBALS['LANG']->sL(
                    'LLL:EXT:my_ext_name/Resources/Private/Language/locallang_backend.xml:uploads.targetDirectionMissing.header'
                ), FlashMessage::ERROR
                );
                /** @var $flashMessageService FlashMessageService */
                $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
                /** @var $flashMessageQueue FlashMessageQueue */
                $flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
                $flashMessageQueue->enqueue($flashMessage);
            } else {
                /** @var ResourceFactory $resourceFactory */
                $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
                $uploadFolder = $resourceFactory->retrieveFileOrFolderObject($absoluteTargetPath);
            }
        } else {
            $uploadFolder = $incomingParameters['uploadFolder'];
        }

        return $uploadFolder;
    }

    private function getPagePath($pid)
    {
        $rootLine = BackendUtility::BEgetRootLine($pid);
        $pageTitles = [];
        foreach ($rootLine as $page) {
            $pageTitles[] = str_replace(' ', '_', $page['title']);
        }

        if (empty($pageTitles)) {
            return '';
        }

        return implode('/', $pageTitles) . '/';
    }
}

Das Beispiel darf gerne kopiert und uneingeschränkt genutzt werden. Bei dem Code handelt es sich um eine abgewandelte Fassung aus einem Projekt. Daher ist dieser nicht getestet.