Your IP : 216.73.216.220


Current Path : /var/www/surf/TYPO3/vendor/mask/mask/Classes/Helper/
Upload File :
Current File : /var/www/surf/TYPO3/vendor/mask/mask/Classes/Helper/InlineHelper.php

<?php

declare(strict_types=1);

/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace MASK\Mask\Helper;

use MASK\Mask\Definition\ElementTcaDefinition;
use MASK\Mask\Definition\TableDefinitionCollection;
use MASK\Mask\Domain\Repository\BackendLayoutRepository;
use MASK\Mask\Enumeration\FieldType;
use MASK\Mask\Utility\AffixUtility;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendGroupRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
use TYPO3\CMS\Core\Database\RelationHandler;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Http\ApplicationType;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

/**
 * Methods for working with inline fields (IRRE)
 */
class InlineHelper
{
    protected TableDefinitionCollection $tableDefinitionCollection;
    protected BackendLayoutRepository $backendLayoutRepository;

    public function __construct(TableDefinitionCollection $tableDefinitionCollection, BackendLayoutRepository $backendLayoutRepository)
    {
        $this->tableDefinitionCollection = $tableDefinitionCollection;
        $this->backendLayoutRepository = $backendLayoutRepository;
    }

    /**
     * Adds FAL-Files to the data-array if available
     * @todo Remove this and use core FilesProcessor instead.
     * @todo This needs to be converted then into the TypoScriptCodeGenerator.
     * @todo Check if this would be breaking, but hopefully not.
     */
    public function addFilesToData(array &$data, string $table = 'tt_content'): void
    {
        $uid = $data['uid'];
        if ($data['_LOCALIZED_UID'] ?? false) {
            $uid = $data['_LOCALIZED_UID'];
        } elseif ($data['_PAGES_OVERLAY_UID'] ?? false) {
            $uid = $data['_PAGES_OVERLAY_UID'];
        }

        // using is_numeric in favor to is_int
        // due to some rare cases where uids are provided as strings
        if (!is_numeric($uid)) {
            return;
        }

        // Cast to int for findByRelation call
        $uid = (int)$uid;

        $tcaKeys = [];
        if ($this->tableDefinitionCollection->hasTable($table)) {
            $tcaKeys = $this->tableDefinitionCollection->getTable($table)->tca->getKeys();
        }
        $imageFields = [];
        if ($table === 'tt_content') {
            $imageFields = ['media', 'image', 'assets'];
        }
        if ($table === 'pages') {
            $imageFields = ['media'];
        }
        $contentFields = array_merge($imageFields, $tcaKeys);
        $contentFields = array_unique($contentFields);

        $fileRepository = GeneralUtility::makeInstance(FileRepository::class);
        foreach ($contentFields as $fieldKey) {
            if ($this->tableDefinitionCollection->getFieldType($fieldKey, $table)->isFileReference()) {
                $data[$fieldKey] = $fileRepository->findByRelation($table, $fieldKey, $uid);
            }
        }
    }

    /**
     * Adds inline fields to the data array if available.
     * This method will be called recursively for every nested inline field by
     * $this->fillInlineField() and $this->getInlineElements().
     */
    public function addIrreToData(array &$data, string $table = 'tt_content', string $cType = '', string $originalTable = 'tt_content'): void
    {
        if ($cType === '') {
            $cType = (string)($data['CType'] ?? '');
        }
        if ($cType === '') {
            return;
        }
        if (!$this->tableDefinitionCollection->hasTable($table)) {
            return;
        }

        if ($table === 'tt_content') {
            $element = $this->tableDefinitionCollection->loadElement($table, AffixUtility::removeCTypePrefix($cType));
            $elementFields = $element->elementDefinition->columns ?? [];
        } elseif ($table === 'pages') {
            if (empty($data['uid'])) {
                return;
            }
            $backendLayoutIdentifier = $this->backendLayoutRepository->findIdentifierByPid((int)$data['uid']);
            if ($backendLayoutIdentifier === null || $backendLayoutIdentifier === '') {
                return;
            }
            $maskPageTemplateKey = str_replace('pagets__', '', $backendLayoutIdentifier);
            $element = $this->tableDefinitionCollection->loadElement($table, $maskPageTemplateKey);
            $elementFields = $element->elementDefinition->columns ?? [];
        } else {
            if ($originalTable === '') {
                return;
            }
            // If it's neither a tt_content record nor a page record, it has to be a mask inline record.
            $element = $this->tableDefinitionCollection->loadElement($originalTable, AffixUtility::removeCTypePrefix($cType));
            $elementFields = $this->tableDefinitionCollection->getTable($table)->tca->getKeys();
        }

        if (!$element instanceof ElementTcaDefinition) {
            return;
        }

        // Fill data for all fields recursively.
        foreach ($elementFields as $field) {
            $elementKey = $element->elementDefinition->key;
            $fieldType = $this->tableDefinitionCollection->getFieldType($field, $table);

            if ($fieldType->equals(FieldType::PALETTE)) {
                foreach ($this->tableDefinitionCollection->loadInlineFields($field, $elementKey, $element->elementDefinition) as $paletteField) {
                    $fieldType = $this->tableDefinitionCollection->getFieldType($paletteField->fullKey, $table);
                    $this->fillInlineField($data, $fieldType, $paletteField->fullKey, $cType, $table, $originalTable);
                }
            } else {
                $this->fillInlineField($data, $fieldType, $field, $cType, $table, $originalTable);
            }
        }
    }

    protected function fillInlineField(array &$data, FieldType $fieldType, string $field, string $cType, string $table, string $originalTable): void
    {
        if (!$fieldType->isRelationField()) {
            return;
        }
        $tcaFieldConfig = $GLOBALS['TCA'][$table]['columns'][$field] ?? [];
        // if it is of type inline and has to be filled (IRRE, FAL)
        if ($fieldType->equals(FieldType::INLINE) && $this->tableDefinitionCollection->hasTable($field)) {
            $elements = $this->getInlineElements($data, $field, $cType, 'parentid', $table, null, $originalTable);
            $data[$field] = $elements;
            // or if it is of type Content (Nested Content) and has to be filled
        } elseif ($fieldType->equals(FieldType::CONTENT)) {
            $content = $this->getRelations((string)($data[$field] ?? ''), $tcaFieldConfig['config']['foreign_table'], '', (int)$data['uid'], $table, $tcaFieldConfig['config'] ?? []);
            foreach ($content as $key => $element) {
                if ($element) {
                    $this->addIrreToData($element, 'tt_content', $element['CType'], $originalTable);
                    $this->addFilesToData($element);
                    $content[$key] = $element;
                }
            }
            $data[$field] = $content;
        } elseif ($fieldType->equals(FieldType::CATEGORY)) {
            if ($tcaFieldConfig['config']['relationship'] === 'manyToMany') {
                $data[$field . '_items'] = $this->getRelations('', ($tcaFieldConfig['config']['foreign_table'] ?? ''), $tcaFieldConfig['config']['MM'] ?? '', (int)$data['uid'], $table, $tcaFieldConfig['config'] ?? []);
            } else {
                $data[$field . '_items'] = $this->getRelations((string)($data[$field] ?? ''), ($tcaFieldConfig['config']['foreign_table'] ?? ''), $tcaFieldConfig['config']['MM'] ?? '', (int)$data['uid'], $table, $tcaFieldConfig['config'] ?? []);
            }
        } elseif (($tcaFieldConfig['config']['foreign_table'] ?? '') !== '' && $fieldType->equals(FieldType::SELECT)) {
            $data[$field . '_items'] = $this->getRelations((string)($data[$field] ?? ''), $tcaFieldConfig['config']['foreign_table'], $tcaFieldConfig['config']['MM'] ?? '', (int)$data['uid'], $table, $tcaFieldConfig['config'] ?? []);
        } elseif (($tcaFieldConfig['config']['internal_type'] ?? '') !== 'folder' && ($tcaFieldConfig['config']['type'] ?? '') !== 'folder' && $fieldType->equals(FieldType::GROUP)) {
            $data[$field . '_items'] = $this->getRelations((string)($data[$field] ?? ''), $tcaFieldConfig['config']['allowed'], $tcaFieldConfig['config']['MM'] ?? '', (int)$data['uid'], $table, $tcaFieldConfig['config'] ?? []);
        } elseif (in_array(($tcaFieldConfig['config']['renderType'] ?? ''), ['selectCheckBox', 'selectSingleBox', 'selectMultipleSideBySide'], true) && $fieldType->equals(FieldType::SELECT)) {
            $data[$field . '_items'] = ($data[$field] ?? '') !== '' ? explode(',', $data[$field]) : [];
        }
    }

    /**
     * @param array<string, mixed> $tcaFieldConf
     *
     * Returns the selected relations of select or group element
     */
    protected function getRelations(string $uidList, string $allowed, string $mmTable, int $uid, string $table, array $tcaFieldConf = []): array
    {
        $pageRepository = $this->getPageRepository();
        $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
        $relationHandler->start($uidList, $allowed, $mmTable, $uid, $table, $tcaFieldConf);
        foreach (array_keys($relationHandler->tableArray) as $table) {
            if (isset($GLOBALS['TCA'][$table])) {
                $autoHiddenSelection = -1;
                $ignoreWorkspaceFilter = ['pid' => true];
                $relationHandler->additionalWhere[$table] = $pageRepository->enableFields($table, $autoHiddenSelection, $ignoreWorkspaceFilter);
            }
        }
        $relationHandler->getFromDB();
        $relations = $relationHandler->getResolvedItemArray();
        $records = [];
        foreach ($relations as $relation) {
            $tableName = $relation['table'];
            $record = $relation['record'];
            $pageRepository->versionOL($tableName, $record);
            if (!is_array($record)) {
                continue;
            }
            $translatedRecord = $pageRepository->getLanguageOverlay($tableName, $record);
            if ($translatedRecord !== null) {
                $records[] = $translatedRecord;
            }
        }
        return $records;
    }

    /**
     * Returns Inline-Elements of Data-Object
     *
     * @param array $data the parent object
     * @param string $name The name of the irre attribut
     * @param string $cType The name of the irre attribut
     * @param string $parentFieldName The name of the irre parentid
     * @param string $parenttable The table where the parent element is stored
     * @param string|null $childTable name of childtable
     * @return array all irre elements of this attribut
     * @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
     */
    public function getInlineElements(
        array $data,
        string $name,
        string $cType,
        string $parentFieldName = 'parentid',
        string $parenttable = 'tt_content',
        ?string $childTable = null,
        string $originalTable = 'tt_content'
    ): array {
        // if the name of the child table is not explicitly given, take field key
        if (!$childTable) {
            $childTable = $name;
        }

        // by default, the uid of the parent is $data["uid"]
        $parentUid = $data['uid'];
        if (isset($data['_LOCALIZED_UID'])) {
            $parentUid = $data['_LOCALIZED_UID'];
        } elseif (isset($data['_PAGES_OVERLAY_UID'])) {
            $parentUid = $data['_PAGES_OVERLAY_UID'];
        }
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($childTable);

        $workspaceId = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'id');
        $inWorkspacePreviewMode = $workspaceId > 0;
        $isFrontendRequest = ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend();

        if ($isFrontendRequest && !empty($GLOBALS['TCA'][$childTable]['ctrl']['enablecolumns']['fe_group'])) {
            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(FrontendGroupRestriction::class));
        }

        // Remove default restrictions for workspace preview in order to fetch the original record uids.
        if ($inWorkspacePreviewMode) {
            $queryBuilder->getRestrictions()->removeAll();
        }

        if (BackendUtility::isTableWorkspaceEnabled($childTable)) {
            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $workspaceId));
        }

        $queryBuilder
            ->select('*')
            ->from($childTable)
            ->where($queryBuilder->expr()->eq($parentFieldName, $queryBuilder->createNamedParameter($parentUid, \PDO::PARAM_INT)))
            ->orderBy('sorting');

        if ($childTable !== 'tt_content') {
            $queryBuilder->andWhere($queryBuilder->expr()->eq('parenttable', $queryBuilder->createNamedParameter($parenttable)));
        }

        $statement = $queryBuilder->executeQuery();

        // and recursively add them to an array
        $elements = [];
        if ($isFrontendRequest) {
            $pageRepository = $this->getPageRepository();
            foreach ($statement->fetchAllAssociative() as $element) {
                if ($inWorkspacePreviewMode) {
                    $pageRepository->versionOL($childTable, $element);
                }
                if ($element !== false) {
                    $elements[$element['uid']] = $element;
                }
            }
        } else {
            foreach ($statement->fetchAllAssociative() as $element) {
                if ($inWorkspacePreviewMode) {
                    $element = BackendUtility::getRecordWSOL($childTable, $element['uid']);
                    // Ignore disabled elements in backend preview.
                    if ($element === null || ($element[$GLOBALS['TCA'][$childTable]['ctrl']['enablecolumns']['disabled']] ?? false)) {
                        continue;
                    }
                }
                $elements[$element['uid']] = $element;
            }
        }

        // Need to sort overlaid records again, because sorting might have changed.
        usort($elements, static function ($a, $b) {
            return $a['sorting'] <=> $b['sorting'];
        });

        foreach ($elements as $key => $element) {
            if ($element) {
                $childCType = $cType;
                if ($childTable === 'tt_content') {
                    $childCType = $element['CType'];
                }
                $this->addIrreToData($element, $childTable, $childCType, $originalTable);
                $this->addFilesToData($element, $childTable);
                $elements[$key] = $element;
            }
        }

        return $elements;
    }

    protected function getPageRepository(): PageRepository
    {
        $tsfe = $GLOBALS['TSFE'] ?? null;
        if ($tsfe instanceof TypoScriptFrontendController && $tsfe->sys_page !== '') {
            return $tsfe->sys_page;
        }
        return GeneralUtility::makeInstance(PageRepository::class);
    }
}