WEBcoast Logo

Custom upload folder

Many editors use the feature to upload a file/image directly in the record/ content element instead of uploading it via the file list first. By default those files are stored in "fileadmin/user_upload". This leads to a huge amount of files in the directory after a short time, where it is not easy to find anything. Fortunately, since TYPO3 CMS 7, there is way to handle this better.

As the target directory is determined from the backend user's settings, it is not surprising, that the hook, to influence the target directory, is found in there.
TYPO3\CMS\Core\Authentication\BackendUserAuthentication: Lines 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);
    }
}

You can simply register your own class and method by the following line:

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

It doesn't matter in which namespace you place the class and how you name the method. It is only necessary, that the method returns an object of type TYPO3\CMS\Core\Resource\Folder. If it does not, the button for the direct upload will not be displayed. You get the present target directory via the $_params['uploadFolder']. So you can simply return it, if there is no folder found, using your logic.

The following implementation the files uploaded in a content element are placed in a folder structure matching the page structure in the backend:

<?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) . '/';
    }
}

This example may be used freely. It was derived from a project and is therefore not testet.