Your IP : 216.73.217.13


Current Path : /var/www/surf/TYPO3/vendor/typo3/cms-frontend/Classes/ContentObject/
Upload File :
Current File : /var/www/surf/TYPO3/vendor/typo3/cms-frontend/Classes/ContentObject/FluidTemplateContentObject.php

<?php

/*
 * 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 TYPO3\CMS\Frontend\ContentObject;

use TYPO3\CMS\Core\TypoScript\TypoScriptService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
use TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;

/**
 * Contains FLUIDTEMPLATE class object
 */
class FluidTemplateContentObject extends AbstractContentObject
{
    /**
     * @var StandaloneView
     */
    protected $view;

    public function __construct(protected ContentDataProcessor $contentDataProcessor) {}

    /**
     * Rendering the cObject, FLUIDTEMPLATE
     *
     * Configuration properties:
     * - file string+stdWrap The FLUID template file
     * - layoutRootPaths array of filepath+stdWrap Root paths to layouts (fallback)
     * - partialRootPaths array of filepath+stdWrap Root paths to partials (fallback)
     * - variable array of cObjects, the keys are the variable names in fluid
     * - dataProcessing array of data processors which are classes to manipulate $data
     * - extbase.pluginName
     * - extbase.controllerExtensionName
     * - extbase.controllerName
     * - extbase.controllerActionName
     *
     * Example:
     * 10 = FLUIDTEMPLATE
     * 10.templateName = MyTemplate
     * 10.templateRootPaths.10 = EXT:site_configuration/Resources/Private/Templates/
     * 10.partialRootPaths.10 = EXT:site_configuration/Resources/Private/Partials/
     * 10.layoutRootPaths.10 = EXT:site_configuration/Resources/Private/Layouts/
     * 10.variables {
     *   mylabel = TEXT
     *   mylabel.value = Label from TypoScript coming
     * }
     *
     * @param array $conf Array of TypoScript properties
     * @return string The HTML output
     */
    public function render($conf = [])
    {
        $parentView = $this->view;
        $this->initializeStandaloneViewInstance();

        if (!is_array($conf)) {
            $conf = [];
        }

        $this->setFormat($conf);
        $this->setTemplate($conf);
        $this->setLayoutRootPath($conf);
        $this->setPartialRootPath($conf);
        $this->setExtbaseVariables($conf);
        $this->assignSettings($conf);
        $variables = $this->getContentObjectVariables($conf);
        $variables = $this->contentDataProcessor->process($this->cObj, $conf, $variables);

        $this->view->assignMultiple($variables);

        $this->renderFluidTemplateAssetsIntoPageRenderer($variables);
        $content = $this->renderFluidView();
        $content = $this->applyStandardWrapToRenderedContent($content, $conf);

        $this->view = $parentView;
        return $content;
    }

    /**
     * Attempts to render HeaderAssets and FooterAssets sections from the
     * Fluid template, then adds each (if not empty) to either header or
     * footer, as appropriate, using PageRenderer.
     */
    protected function renderFluidTemplateAssetsIntoPageRenderer(array $variables)
    {
        $pageRenderer = $this->getPageRenderer();
        $headerAssets = $this->view->renderSection('HeaderAssets', [...$variables, 'contentObject' => $this], true);
        $footerAssets = $this->view->renderSection('FooterAssets', [...$variables, 'contentObject' => $this], true);
        if (!empty(trim($headerAssets))) {
            $pageRenderer->addHeaderData($headerAssets);
        }
        if (!empty(trim($footerAssets))) {
            $pageRenderer->addFooterData($footerAssets);
        }
    }

    /**
     * Creating standalone view instance must not be done in construct() as
     * it can lead to a nasty cache issue since content object instances
     * are not always re-created by the content object rendered for every
     * usage, but can be re-used. Thus, we need a fresh instance of
     * StandaloneView every time render() is called.
     */
    protected function initializeStandaloneViewInstance()
    {
        $this->view = GeneralUtility::makeInstance(StandaloneView::class);
        $this->view->setRequest($this->request);
    }

    /**
     * Set template
     *
     * @param array $conf With possibly set file resource
     * @throws \InvalidArgumentException
     */
    protected function setTemplate(array $conf)
    {
        // Fetch the Fluid template by templateName
        if (
            (!empty($conf['templateName']) || !empty($conf['templateName.']))
            && !empty($conf['templateRootPaths.']) && is_array($conf['templateRootPaths.'])
        ) {
            $templateRootPaths = $this->applyStandardWrapToFluidPaths($conf['templateRootPaths.']);
            $this->view->setTemplateRootPaths($templateRootPaths);
            $templateName = $this->cObj->stdWrapValue('templateName', $conf ?? []);
            $this->view->setTemplate($templateName);
        } elseif (!empty($conf['template']) && !empty($conf['template.'])) {
            // Fetch the Fluid template by template cObject
            $templateSource = $this->cObj->cObjGetSingle($conf['template'], $conf['template.'], 'template');
            if ($templateSource === '') {
                throw new ContentRenderingException(
                    'Could not find template source for ' . $conf['template'],
                    1437420865
                );
            }
            $this->view->setTemplateSource($templateSource);
        } else {
            // Fetch the Fluid template by file stdWrap
            $file = (string)$this->cObj->stdWrapValue('file', $conf ?? []);
            // Get the absolute file name
            $templatePathAndFilename = GeneralUtility::getFileAbsFileName($file);
            $this->view->setTemplatePathAndFilename($templatePathAndFilename);
        }
    }

    /**
     * Set layout root path if given in configuration
     *
     * @param array $conf Configuration array
     */
    protected function setLayoutRootPath(array $conf)
    {
        // Override the default layout path via typoscript
        $layoutPaths = [];

        $layoutRootPath = (string)$this->cObj->stdWrapValue('layoutRootPath', $conf ?? []);
        if ($layoutRootPath !== '') {
            $layoutPaths[] = GeneralUtility::getFileAbsFileName($layoutRootPath);
        }
        if (isset($conf['layoutRootPaths.'])) {
            $layoutPaths = array_replace($layoutPaths, $this->applyStandardWrapToFluidPaths($conf['layoutRootPaths.']));
        }
        if (!empty($layoutPaths)) {
            $this->view->setLayoutRootPaths($layoutPaths);
        }
    }

    /**
     * Set partial root path if given in configuration
     *
     * @param array $conf Configuration array
     */
    protected function setPartialRootPath(array $conf)
    {
        $partialPaths = [];

        $partialRootPath = (string)$this->cObj->stdWrapValue('partialRootPath', $conf ?? []);
        if ($partialRootPath !== '') {
            $partialPaths[] = GeneralUtility::getFileAbsFileName($partialRootPath);
        }
        if (isset($conf['partialRootPaths.'])) {
            $partialPaths = array_replace($partialPaths, $this->applyStandardWrapToFluidPaths($conf['partialRootPaths.']));
        }
        if (!empty($partialPaths)) {
            $this->view->setPartialRootPaths($partialPaths);
        }
    }

    /**
     * Set different format if given in configuration
     *
     * @param array $conf Configuration array
     */
    protected function setFormat(array $conf)
    {
        $format = $this->cObj->stdWrapValue('format', $conf ?? []);
        if ($format) {
            $this->view->setFormat($format);
        }
    }

    /**
     * Set some extbase variables if given
     *
     * @param array $conf Configuration array
     */
    protected function setExtbaseVariables(array $conf)
    {
        // @todo: It is currently unclear if the if's below can happen at all: An extbase request has been
        //        prepared, but the setup of plugin name, controller extension name and friends
        //        did not happen? Maybe these four if's are useless and the main if that
        //        tests for all four properties is fine? Maybe the main if below is obsolete, too?
        //        This comment was added when StandaloneView still had a default constructor that actively
        //        creates a request by default. It might be more possible to resolve this when this is gone.
        $request = $this->request;
        $requestPluginName = (string)$this->cObj->stdWrapValue('pluginName', $conf['extbase.'] ?? []);
        if ($requestPluginName && $request instanceof RequestInterface) {
            $request = $request->withPluginName($requestPluginName);
            $this->view->setRequest($request);
        }
        $requestControllerExtensionName = (string)$this->cObj->stdWrapValue('controllerExtensionName', $conf['extbase.'] ?? []);
        if ($requestControllerExtensionName && $request instanceof RequestInterface) {
            $request = $request->withControllerExtensionName($requestControllerExtensionName);
            $this->view->setRequest($request);
        }
        $requestControllerName = (string)$this->cObj->stdWrapValue('controllerName', $conf['extbase.'] ?? []);
        if ($requestControllerName && $request instanceof RequestInterface) {
            $request = $request->withControllerName($requestControllerName);
            $this->view->setRequest($request);
        }
        $requestControllerActionName = (string)$this->cObj->stdWrapValue('controllerActionName', $conf['extbase.'] ?? []);
        if ($requestControllerActionName && $request instanceof RequestInterface) {
            $request = $request->withControllerActionName($requestControllerActionName);
            $this->view->setRequest($request);
        }

        if ($requestPluginName && $requestControllerExtensionName && $requestControllerName && $requestControllerActionName) {
            // @todo: Yep, ugly. Having all four properties indicates an extbase plugin and then starts
            //        extbase configuration manager. See https://forge.typo3.org/issues/78842 and investigate
            //        if we still need this?
            $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
            $configurationManager->setConfiguration([
                'extensionName' => $requestControllerExtensionName,
                'pluginName' => $requestPluginName,
            ]);
            if (!isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$requestControllerExtensionName]['plugins'][$requestPluginName]['controllers'])) {
                $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$requestControllerExtensionName]['plugins'][$requestPluginName]['controllers'] = [
                    $requestControllerName => [
                        'actions' => [
                            $requestControllerActionName,
                        ],
                    ],
                ];
            }
            $requestBuilder = GeneralUtility::makeInstance(RequestBuilder::class);
            $this->request = $requestBuilder->build($this->request);
            $this->view->setRequest($this->request);
        }
    }

    /**
     * Compile rendered content objects in variables array ready to assign to the view
     *
     * @param array $conf Configuration array
     * @return array the variables to be assigned
     * @throws \InvalidArgumentException
     */
    protected function getContentObjectVariables(array $conf)
    {
        $variables = [];
        $reservedVariables = ['data', 'current'];
        // Accumulate the variables to be process and loop them through cObjGetSingle
        $variablesToProcess = (array)($conf['variables.'] ?? []);
        foreach ($variablesToProcess as $variableName => $cObjType) {
            if (is_array($cObjType)) {
                continue;
            }
            if (!in_array($variableName, $reservedVariables)) {
                $cObjConf = $variablesToProcess[$variableName . '.'] ?? [];
                $variables[$variableName] = $this->cObj->cObjGetSingle($cObjType, $cObjConf, 'variables.' . $variableName);
            } else {
                throw new \InvalidArgumentException(
                    'Cannot use reserved name "' . $variableName . '" as variable name in FLUIDTEMPLATE.',
                    1288095720
                );
            }
        }
        $variables['data'] = $this->cObj->data;
        $variables['current'] = $this->cObj->data[$this->cObj->currentValKey ?? null] ?? null;
        return $variables;
    }

    /**
     * Set any TypoScript settings to the view. This is similar to a
     * default MVC action controller in extbase.
     *
     * @param array $conf Configuration
     */
    protected function assignSettings(array $conf)
    {
        if (isset($conf['settings.'])) {
            $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
            $settings = $typoScriptService->convertTypoScriptArrayToPlainArray($conf['settings.']);
            $this->view->assign('settings', $settings);
        }
    }

    /**
     * Render fluid standalone view
     *
     * @return string
     */
    protected function renderFluidView()
    {
        return $this->view->render();
    }

    /**
     * Apply standard wrap to content
     *
     * @param string $content Rendered HTML content
     * @param array $conf Configuration array
     * @return string Standard wrapped content
     */
    protected function applyStandardWrapToRenderedContent($content, array $conf)
    {
        if (isset($conf['stdWrap.'])) {
            $content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
        }
        return $content;
    }

    /**
     * Applies stdWrap on Fluid path definitions
     *
     *
     * @return array
     */
    protected function applyStandardWrapToFluidPaths(array $paths)
    {
        $finalPaths = [];
        foreach ($paths as $key => $path) {
            if (str_ends_with((string)$key, '.')) {
                if (isset($paths[substr($key, 0, -1)])) {
                    continue;
                }
                $path = $this->cObj->stdWrap('', $path);
            } elseif (isset($paths[$key . '.'])) {
                $path = $this->cObj->stdWrap($path, $paths[$key . '.']);
            }
            $finalPaths[$key] = GeneralUtility::getFileAbsFileName($path);
        }
        return $finalPaths;
    }
}