TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:27 +02:00
commit 44c503b2d1
233 changed files with 31556 additions and 0 deletions
@@ -0,0 +1,103 @@
<?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\DataProcessing;
use TYPO3\CMS\Core\Utility\CsvUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* This data processor will take field data formatted as a string, where each line, separated by line feed,
* represents a row. By default columns are separated by the delimiter character "comma ,",
* and can be enclosed by the character 'quotation mark "', like the default in a regular CSV file.
*
* An example of such a field is "bodytext" in the CType "table".
*
* The table data is transformed to a multi dimensional array, taking the delimiter and enclosure into account,
* before it is passed to the view.
*
* Example field data:
*
* This is row 1 column 1|This is row 1 column 2|This is row 1 column 3
* This is row 2 column 1|This is row 2 column 2|This is row 2 column 3
* This is row 3 column 1|This is row 3 column 2|This is row 3 column 3
*
* Example TypoScript configuration:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\CommaSeparatedValueProcessor
* 10 {
* if.isTrue.field = bodytext
* fieldName = bodytext
* fieldDelimiter = |
* fieldEnclosure = '
* maximumColumns = 2
* as = table
* }
*
* whereas "table" can be used as a variable {table} inside Fluid for iteration.
*
* Using maximumColumns limits the amount of columns in the multi dimensional array.
* In the example, field data of the last column will be stripped off.
*
* Multi line cells are taken into account.
*/
class CommaSeparatedValueProcessor implements DataProcessorInterface
{
/**
* Process CSV field data to split into a multi dimensional array
*
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* @return array the processed data as key/value store
*/
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
{
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
// The field name to process
$fieldName = $cObj->stdWrapValue('fieldName', $processorConfiguration);
if (empty($fieldName)) {
return $processedData;
}
$originalValue = (string)$cObj->data[$fieldName];
// Set the target variable
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, $fieldName);
// Set the maximum amount of columns
$maximumColumns = $cObj->stdWrapValue('maximumColumns', $processorConfiguration, 0);
// Set the field delimiter which is "," by default
$fieldDelimiter = (string)$cObj->stdWrapValue('fieldDelimiter', $processorConfiguration, ',');
// Set the field enclosure which is " by default
$fieldEnclosure = (string)$cObj->stdWrapValue('fieldEnclosure', $processorConfiguration, '"');
$processedData[$targetVariableName] = CsvUtility::csvToArray(
$originalValue,
$fieldDelimiter,
$fieldEnclosure,
(int)$maximumColumns
);
return $processedData;
}
}
@@ -0,0 +1,48 @@
<?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 TYPO3\CMS\Frontend\DataProcessing;
use Symfony\Component\DependencyInjection\ServiceLocator;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* Registry for data processors, tagged with "data.processor"
* @internal
*/
readonly class DataProcessorRegistry
{
public function __construct(private ServiceLocator $dataProcessorLocator) {}
public function getDataProcessor(string $identifer): ?DataProcessorInterface
{
if (!$this->dataProcessorLocator->has($identifer)) {
return null;
}
$dataProcessor = $this->dataProcessorLocator->get($identifer);
if (!($dataProcessor instanceof DataProcessorInterface)) {
throw new \UnexpectedValueException(
'Processor with alias / identifier "' . $identifer . '" '
. 'must implement interface "' . DataProcessorInterface::class . '"',
1666131903
);
}
return $dataProcessor;
}
}
@@ -0,0 +1,98 @@
<?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\DataProcessing;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* Fetch records from the database, using the default .select syntax from TypoScript.
*
* This way, e.g. a FLUIDTEMPLATE cObject can iterate over the array of records.
*
* Example TypoScript configuration:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor
* 10 {
* table = tt_address
* pidInList = 123
* where = company="Acme" AND first_name="Ralph"
* orderBy = sorting DESC
* as = addresses
* dataProcessing {
* 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
* 10 {
* references.fieldName = image
* }
* }
* }
*
* where "as" means the variable to be containing the result-set from the DB query.
*/
readonly class DatabaseQueryProcessor implements DataProcessorInterface
{
public function __construct(protected ContentDataProcessor $contentDataProcessor) {}
/**
* Fetches records from the database as an array
*
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
*
* @return array the processed data as key/value store
*/
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
{
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
// the table to query, if none given, exit
$tableName = $cObj->stdWrapValue('table', $processorConfiguration);
if (empty($tableName)) {
return $processedData;
}
if (isset($processorConfiguration['table.'])) {
unset($processorConfiguration['table.']);
}
if (isset($processorConfiguration['table'])) {
unset($processorConfiguration['table']);
}
// The variable to be used within the result
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'records');
// Execute a SQL statement to fetch the records
$records = $cObj->getRecords($tableName, $processorConfiguration);
$request = $cObj->getRequest();
$processedRecordVariables = [];
foreach ($records as $key => $record) {
$recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$recordContentObjectRenderer->setRequest($request);
$recordContentObjectRenderer->start($record, $tableName);
$processedRecordVariables[$key] = ['data' => $record];
$processedRecordVariables[$key] = $this->contentDataProcessor->process($recordContentObjectRenderer, $processorConfiguration, $processedRecordVariables[$key]);
}
$processedData[$targetVariableName] = $processedRecordVariables;
return $processedData;
}
}
+123
View File
@@ -0,0 +1,123 @@
<?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\DataProcessing;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
use TYPO3\CMS\Frontend\Resource\FileCollector;
/**
* This data processor can be used for processing data for record which contain
* relations to sys_file records (e.g. sys_file_reference records) or for fetching
* files directly from UIDs or from folders or collections.
*
*
* Example TypoScript configuration:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
* 10 {
* references.fieldName = image
* collections = 13,15
* as = myfiles
* }
*
* whereas "myfiles" can further be used as a variable {myfiles} inside a Fluid template for iteration.
*/
class FilesProcessor implements DataProcessorInterface
{
/**
* Process data of a record to resolve File objects to the view
*
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* @return array the processed data as key/value store
*/
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
{
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
// gather data
$fileCollector = GeneralUtility::makeInstance(FileCollector::class);
// references / relations
if (
(isset($processorConfiguration['references']) && $processorConfiguration['references'])
|| (isset($processorConfiguration['references.']) && $processorConfiguration['references.'])
) {
$referencesUidList = (string)$cObj->stdWrapValue('references', $processorConfiguration);
$referencesUids = GeneralUtility::intExplode(',', $referencesUidList, true);
$fileCollector->addFileReferences($referencesUids);
if (!empty($processorConfiguration['references.'])) {
$referenceConfiguration = $processorConfiguration['references.'];
$relationField = $cObj->stdWrapValue('fieldName', $referenceConfiguration);
// If no reference fieldName is set, there's nothing to do
if (!empty($relationField)) {
// Fetch the references of the default element
$relationTable = $cObj->stdWrapValue('table', $referenceConfiguration, $cObj->getCurrentTable());
if (!empty($relationTable)) {
$fileCollector->addFilesFromRelation($relationTable, $relationField, $cObj->data);
}
}
}
}
// files
$files = $cObj->stdWrapValue('files', $processorConfiguration);
if ($files) {
$files = GeneralUtility::intExplode(',', (string)$files, true);
$fileCollector->addFiles($files);
}
// collections
$collections = $cObj->stdWrapValue('collections', $processorConfiguration);
if (!empty($collections)) {
$collections = GeneralUtility::intExplode(',', (string)$collections, true);
$fileCollector->addFilesFromFileCollections($collections);
}
// folders
$folders = $cObj->stdWrapValue('folders', $processorConfiguration);
if (!empty($folders)) {
$folders = GeneralUtility::trimExplode(',', (string)$folders, true);
$fileCollector->addFilesFromFolders($folders, (bool)$cObj->stdWrapValue('recursive', $processorConfiguration['folders.'] ?? [], false));
}
// make sure to sort the files
$sortingProperty = $cObj->stdWrapValue('sorting', $processorConfiguration);
if ($sortingProperty) {
$sortingDirection = $cObj->stdWrapValue(
'direction',
$processorConfiguration['sorting.'] ?? [],
'ascending'
);
$fileCollector->sort($sortingProperty, $sortingDirection);
}
// set the files into a variable, default "files"
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'files');
$processedData[$targetVariableName] = $fileCollector->getFiles();
return $processedData;
}
}
@@ -0,0 +1,152 @@
<?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 TYPO3\CMS\Frontend\DataProcessing;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
use TYPO3\CMS\Frontend\Resource\FileCollector;
/**
* This data processor converts the XML structure of a given FlexForm field
* into a fluid readable array.
*
* Options:
* fieldName - The name of the field containing the FlexForm to be converted
* references - A key / value list for fields with file references to process
* dataProcessing - Additional sub DataProcessors to process
* as - The variable, the generated array should be assigned to
*
* Example of a minimal TypoScript configuration, which processes the field
* `pi_flexform` and assigns the array to the `flexFormData` variable:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\FlexFormProcessor
*
* Example of an advanced TypoScript configuration, which processes the field
* `my_flexform_field`, resolves its FAL references and assigns the array to the
* `myOutputVariable` variable:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\FlexFormProcessor
* 10 {
* fieldName = my_flexform_field
* references {
* my_flex_form_group.my_flex_form_field = my_field_reference
* }
* dataProcessing {
* 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
* 10 {
* references.fieldName = media
* }
* }
* as = myOutputVariable
* }
*/
#[Autoconfigure(public: true)]
readonly class FlexFormProcessor implements DataProcessorInterface
{
public function __construct(
private FlexFormTools $flexFormTools,
) {}
/**
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* @return array the processed data as key/value store
*/
public function process(
ContentObjectRenderer $cObj,
array $contentObjectConfiguration,
array $processorConfiguration,
array $processedData
): array {
// The field name to process
$fieldName = $cObj->stdWrapValue('fieldName', $processorConfiguration, 'pi_flexform');
if (!isset($processedData['data'][$fieldName])) {
return $processedData;
}
// Process FlexForm
$originalValue = $processedData['data'][$fieldName];
if (!is_string($originalValue)) {
return $processedData;
}
$flexFormData = $this->flexFormTools->convertFlexFormContentToArray($originalValue);
// Process FAL references
if (isset($processorConfiguration['references.']) && is_array($processorConfiguration['references.'])) {
$this->processFileReferences($cObj, $flexFormData, $processorConfiguration['references.']);
}
// Process additional DataProcessors
if (isset($processorConfiguration['dataProcessing.']) && is_array($processorConfiguration['dataProcessing.'])) {
// @todo: It looks as if data processors should retrieve the current request from the outside,
// this would avoid $cObj->getRequest() here.
$flexFormData = $this->processAdditionalDataProcessors($flexFormData, $processorConfiguration, $cObj->getRequest());
}
// Set the target variable
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'flexFormData');
$processedData[$targetVariableName] = $flexFormData;
return $processedData;
}
/**
* Recursively process FAL references and replace them by FAL objects.
*/
protected function processFileReferences(ContentObjectRenderer $cObj, array &$data, array $fields): void
{
foreach ($fields as $key => $field) {
$key = rtrim($key, '.');
if (!isset($data[$key])) {
continue;
}
if (is_array($field)) {
$this->processFileReferences($cObj, $data[$key], $field);
} else {
$fileCollector = GeneralUtility::makeInstance(FileCollector::class);
$fileCollector->addFilesFromRelation($cObj->getCurrentTable(), $field, $cObj->data);
$data[$key] = $fileCollector->getFiles();
}
}
}
/**
* Recursively process sub processors of a data processor
*/
protected function processAdditionalDataProcessors(array $data, array $processorConfiguration, ServerRequestInterface $request): array
{
$contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$contentObjectRenderer->setRequest($request);
$contentObjectRenderer->start([$data], '');
return GeneralUtility::makeInstance(ContentDataProcessor::class)->process(
$contentObjectRenderer,
$processorConfiguration,
$data
);
}
}
+510
View File
@@ -0,0 +1,510 @@
<?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\DataProcessing;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
/**
* This data processor will calculate rows, columns and dimensions for a gallery
* based on several settings and can be used for f.i. the CType "textmedia"
*
* The output will be an array which contains the rows and columns,
* including the file references and the calculated width and height for each media element,
* but also some more information of the gallery, like position, width and counters
*
* Example TypoScript configuration:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\GalleryProcessor
* 10 {
* filesProcessedDataKey = files
* mediaOrientation.field = imageorient
* numberOfColumns.field = imagecols
* equalMediaHeight.field = imageheight
* equalMediaWidth.field = imagewidth
* columnSpacing = 0
* borderEnabled.field = imageborder
* borderPadding = 0
* borderWidth = 0
* maxGalleryWidth = {$styles.content.mediatext.maxW}
* maxGalleryWidthInText = {$styles.content.mediatext.maxWInText}
* as = gallery
* }
*
* Output example:
*
* gallery {
* position {
* horizontal = center
* vertical = above
* noWrap = FALSE
* }
* width = 600
* count {
* files = 2
* columns = 1
* rows = 2
* }
* rows {
* 1 {
* columns {
* 1 {
* media = TYPO3\CMS\Core\Resource\FileReference
* dimensions {
* width = 600
* height = 400
* }
* }
* }
* }
* 2 {
* columns {
* 1 {
* media = TYPO3\CMS\Core\Resource\FileReference
* dimensions {
* width = 600
* height = 400
* }
* }
* }
* }
* }
* columnSpacing = 0
* border {
* enabled = FALSE
* width = 0
* padding = 0
* }
* }
*/
class GalleryProcessor implements DataProcessorInterface
{
/**
* The content object renderer
*
* @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
*/
protected $contentObjectRenderer;
/**
* The processor configuration
*
* @var array
*/
protected $processorConfiguration;
/**
* Matching the tt_content field towards the imageOrient option
*
* @var array
*/
protected $availableGalleryPositions = [
'horizontal' => [
'center' => [0, 8],
'right' => [1, 9, 17, 25],
'left' => [2, 10, 18, 26],
],
'vertical' => [
'above' => [0, 1, 2],
'intext' => [17, 18, 25, 26],
'below' => [8, 9, 10],
],
];
/**
* Storage for processed data
*
* @var array
*/
protected $galleryData = [
'position' => [
'horizontal' => '',
'vertical' => '',
'noWrap' => false,
],
'width' => 0,
'count' => [
'files' => 0,
'columns' => 0,
'rows' => 0,
],
'columnSpacing' => 0,
'border' => [
'enabled' => false,
'width' => 0,
'padding' => 0,
],
'rows' => [],
];
/**
* @var int
*/
protected $numberOfColumns;
/**
* @var int
*/
protected $mediaOrientation;
/**
* @var int
*/
protected $maxGalleryWidth;
/**
* @var int
*/
protected $maxGalleryWidthInText;
/**
* @var int
*/
protected $equalMediaHeight;
/**
* @var int
*/
protected $equalMediaWidth;
/**
* @var int
*/
protected $columnSpacing;
/**
* @var bool
*/
protected $borderEnabled;
/**
* @var int
*/
protected $borderWidth;
/**
* @var int
*/
protected $borderPadding;
/**
* @var string
*/
protected $cropVariant = 'default';
/**
* The (filtered) media files to be used in the gallery
*
* @var FileInterface[]
*/
protected $fileObjects = [];
/**
* The calculated dimensions for each media element
*
* @var array
*/
protected $mediaDimensions = [];
/**
* Process data for a gallery, for instance the CType "textmedia"
*
* @param ContentObjectRenderer $cObj The content object renderer, which contains data of the content element
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* @return array the processed data as key/value store
* @throws ContentRenderingException
*/
public function process(
ContentObjectRenderer $cObj,
array $contentObjectConfiguration,
array $processorConfiguration,
array $processedData
) {
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
$this->contentObjectRenderer = $cObj;
$this->processorConfiguration = $processorConfiguration;
$filesProcessedDataKey = (string)$cObj->stdWrapValue(
'filesProcessedDataKey',
$processorConfiguration,
'files'
);
if (isset($processedData[$filesProcessedDataKey]) && is_array($processedData[$filesProcessedDataKey])) {
$this->fileObjects = $processedData[$filesProcessedDataKey];
$this->galleryData['count']['files'] = count($this->fileObjects);
} else {
throw new ContentRenderingException('No files found for key ' . $filesProcessedDataKey . ' in $processedData.', 1436809789);
}
$this->numberOfColumns = (int)$this->getConfigurationValue('numberOfColumns', 'imagecols');
$this->mediaOrientation = (int)$this->getConfigurationValue('mediaOrientation', 'imageorient');
$this->maxGalleryWidth = (int)$this->getConfigurationValue('maxGalleryWidth') ?: 600;
$this->maxGalleryWidthInText = (int)$this->getConfigurationValue('maxGalleryWidthInText') ?: 300;
$this->equalMediaHeight = (int)$this->getConfigurationValue('equalMediaHeight', 'imageheight');
$this->equalMediaWidth = (int)$this->getConfigurationValue('equalMediaWidth', 'imagewidth');
$this->columnSpacing = (int)$this->getConfigurationValue('columnSpacing');
$this->borderEnabled = (bool)$this->getConfigurationValue('borderEnabled', 'imageborder');
$this->borderWidth = (int)$this->getConfigurationValue('borderWidth');
$this->borderPadding = (int)$this->getConfigurationValue('borderPadding');
$this->cropVariant = $this->getConfigurationValue('cropVariant') ?: 'default';
$this->determineGalleryPosition();
$this->determineMaximumGalleryWidth();
$this->calculateRowsAndColumns();
$this->calculateMediaWidthsAndHeights();
$this->prepareGalleryData();
$targetFieldName = (string)$cObj->stdWrapValue(
'as',
$processorConfiguration,
'gallery'
);
$processedData[$targetFieldName] = $this->galleryData;
return $processedData;
}
/**
* Get configuration value from processorConfiguration
* with when $dataArrayKey fallback to value from cObj->data array
*
* @param string $key
* @param string|null $dataArrayKey
* @return string
*/
protected function getConfigurationValue($key, $dataArrayKey = null)
{
$defaultValue = '';
if ($dataArrayKey && isset($this->contentObjectRenderer->data[$dataArrayKey])) {
$defaultValue = $this->contentObjectRenderer->data[$dataArrayKey];
}
return $this->contentObjectRenderer->stdWrapValue(
$key,
$this->processorConfiguration,
$defaultValue
);
}
/**
* Define the gallery position
*
* Gallery has a horizontal and a vertical position towards the text
* and a possible wrapping of the text around the gallery.
*/
protected function determineGalleryPosition()
{
foreach ($this->availableGalleryPositions as $positionDirectionKey => $positionDirectionValue) {
foreach ($positionDirectionValue as $positionKey => $positionArray) {
if (in_array($this->mediaOrientation, $positionArray, true)) {
$this->galleryData['position'][$positionDirectionKey] = $positionKey;
}
}
}
if ($this->mediaOrientation === 25 || $this->mediaOrientation === 26) {
$this->galleryData['position']['noWrap'] = true;
}
}
/**
* Get the gallery width based on vertical position
*/
protected function determineMaximumGalleryWidth()
{
if ($this->galleryData['position']['vertical'] === 'intext') {
$this->galleryData['width'] = $this->maxGalleryWidthInText;
} else {
$this->galleryData['width'] = $this->maxGalleryWidth;
}
}
/**
* Calculate the amount of rows and columns
*/
protected function calculateRowsAndColumns()
{
// If no columns defined, set it to 1
$columns = max((int)$this->numberOfColumns, 1);
// When more columns than media elements, set the columns to the amount of media elements
if ($columns > $this->galleryData['count']['files']) {
$columns = $this->galleryData['count']['files'];
}
if ($columns === 0) {
$columns = 1;
}
// Calculate the rows from the amount of files and the columns
$rows = ceil($this->galleryData['count']['files'] / $columns);
$this->galleryData['count']['columns'] = $columns;
$this->galleryData['count']['rows'] = (int)$rows;
}
/**
* Calculate the width/height of the media elements
*
* Based on the width of the gallery, defined equal width or height by a user, the spacing between columns and
* the use of a border, defined by user, where the border width and padding are taken into account
*
* File objects MUST already be filtered. They need a height and width to be shown in the gallery
*/
protected function calculateMediaWidthsAndHeights()
{
$columnSpacingTotal = ($this->galleryData['count']['columns'] - 1) * $this->columnSpacing;
$galleryWidthMinusBorderAndSpacing = max($this->galleryData['width'] - $columnSpacingTotal, 1);
if ($this->borderEnabled) {
$borderPaddingTotal = ($this->galleryData['count']['columns'] * 2) * $this->borderPadding;
$borderWidthTotal = ($this->galleryData['count']['columns'] * 2) * $this->borderWidth;
$galleryWidthMinusBorderAndSpacing = $galleryWidthMinusBorderAndSpacing - $borderPaddingTotal - $borderWidthTotal;
}
// User entered a predefined height
if ($this->equalMediaHeight) {
$mediaScalingCorrection = 1;
$maximumRowWidth = 0;
// Calculate the scaling correction when the total of media elements is wider than the gallery width
for ($row = 1; $row <= $this->galleryData['count']['rows']; $row++) {
$totalRowWidth = 0;
for ($column = 1; $column <= $this->galleryData['count']['columns']; $column++) {
$fileKey = (($row - 1) * $this->galleryData['count']['columns']) + $column - 1;
if ($fileKey > $this->galleryData['count']['files'] - 1) {
break 2;
}
$currentMediaScaling = $this->equalMediaHeight / max($this->getCroppedDimensionalProperty($this->fileObjects[$fileKey], 'height'), 1);
$totalRowWidth += $this->getCroppedDimensionalProperty($this->fileObjects[$fileKey], 'width') * $currentMediaScaling;
}
$maximumRowWidth = max($totalRowWidth, $maximumRowWidth);
$mediaInRowScaling = $totalRowWidth / $galleryWidthMinusBorderAndSpacing;
$mediaScalingCorrection = max($mediaInRowScaling, $mediaScalingCorrection);
}
// Set the corrected dimensions for each media element
foreach ($this->fileObjects as $key => $fileObject) {
$mediaHeight = floor($this->equalMediaHeight / $mediaScalingCorrection);
$mediaWidth = floor(
$this->getCroppedDimensionalProperty($fileObject, 'width') * ($mediaHeight / max($this->getCroppedDimensionalProperty($fileObject, 'height'), 1))
);
$this->mediaDimensions[$key] = [
'width' => $mediaWidth,
'height' => $mediaHeight,
];
}
// Recalculate gallery width
$this->galleryData['width'] = floor($maximumRowWidth / $mediaScalingCorrection);
// User entered a predefined width
} elseif ($this->equalMediaWidth) {
$mediaScalingCorrection = 1;
// Calculate the scaling correction when the total of media elements is wider than the gallery width
$totalRowWidth = $this->galleryData['count']['columns'] * $this->equalMediaWidth;
$mediaInRowScaling = $totalRowWidth / $galleryWidthMinusBorderAndSpacing;
$mediaScalingCorrection = max($mediaInRowScaling, $mediaScalingCorrection);
// Set the corrected dimensions for each media element
foreach ($this->fileObjects as $key => $fileObject) {
$mediaWidth = floor($this->equalMediaWidth / $mediaScalingCorrection);
$mediaHeight = floor(
$this->getCroppedDimensionalProperty($fileObject, 'height') * ($mediaWidth / max($this->getCroppedDimensionalProperty($fileObject, 'width'), 1))
);
$this->mediaDimensions[$key] = [
'width' => $mediaWidth,
'height' => $mediaHeight,
];
}
// Recalculate gallery width
$this->galleryData['width'] = floor($totalRowWidth / $mediaScalingCorrection);
// Automatic setting of width and height
} else {
$maxMediaWidth = (int)($galleryWidthMinusBorderAndSpacing / $this->galleryData['count']['columns']);
foreach ($this->fileObjects as $key => $fileObject) {
$croppedWidth = $this->getCroppedDimensionalProperty($fileObject, 'width');
$mediaWidth = $croppedWidth > 0 ? min($maxMediaWidth, $croppedWidth) : $maxMediaWidth;
$mediaHeight = floor(
$this->getCroppedDimensionalProperty($fileObject, 'height') * ($mediaWidth / max($this->getCroppedDimensionalProperty($fileObject, 'width'), 1))
);
$this->mediaDimensions[$key] = [
'width' => $mediaWidth,
'height' => $mediaHeight,
];
}
}
}
/**
* When retrieving the height or width for a media file
* a possible cropping needs to be taken into account.
*
* @param string $dimensionalProperty 'width' or 'height'
* @return int
*/
protected function getCroppedDimensionalProperty(FileInterface $fileObject, $dimensionalProperty)
{
if (!$fileObject->hasProperty('crop') || empty($fileObject->getProperty('crop'))) {
return $fileObject->getProperty($dimensionalProperty);
}
$croppingConfiguration = $fileObject->getProperty('crop');
$cropVariantCollection = CropVariantCollection::create((string)$croppingConfiguration);
return (int)$cropVariantCollection->getCropArea($this->cropVariant)->makeAbsoluteBasedOnFile($fileObject)->asArray()[$dimensionalProperty];
}
/**
* Prepare the gallery data
*
* Make an array for rows, columns and configuration
*/
protected function prepareGalleryData()
{
for ($row = 1; $row <= $this->galleryData['count']['rows']; $row++) {
for ($column = 1; $column <= $this->galleryData['count']['columns']; $column++) {
$fileKey = (($row - 1) * $this->galleryData['count']['columns']) + $column - 1;
$this->galleryData['rows'][$row]['columns'][$column] = [
'media' => $this->fileObjects[$fileKey] ?? null,
'dimensions' => [
'width' => $this->mediaDimensions[$fileKey]['width'] ?? null,
'height' => $this->mediaDimensions[$fileKey]['height'] ?? null,
],
];
}
}
$this->galleryData['columnSpacing'] = $this->columnSpacing;
$this->galleryData['border']['enabled'] = $this->borderEnabled;
$this->galleryData['border']['width'] = $this->borderWidth;
$this->galleryData['border']['padding'] = $this->borderPadding;
}
}
@@ -0,0 +1,239 @@
<?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 TYPO3\CMS\Frontend\DataProcessing;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
use TYPO3\CMS\Frontend\ContentObject\Menu\MenuContentObjectFactory;
use TYPO3\CMS\Frontend\Utility\CanonicalizationUtility;
/**
* This menu processor generates a language menu array that will be
* assigned to FLUIDTEMPLATE as variable.
*
* Options:
* if - TypoScript if condition
* languages - A list of languages id's (e.g. 0,1,2) to use for the menu
* creation or 'auto' to load from system or site languages
* as - The variable to be used within the result
*
* Example TypoScript configuration:
* 10 = TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor
* 10 {
* as = languagenavigation
* }
*/
class LanguageMenuProcessor implements DataProcessorInterface
{
protected ContentObjectRenderer $cObj;
protected array $processorConfiguration;
/**
* Allowed configuration keys for menu generation, other keys
* will throw an exception to prevent configuration errors.
*/
protected array $allowedConfigurationKeys = [
'if',
'if.',
'languages',
'languages.',
'as',
'addQueryString',
'addQueryString.',
];
/**
* Remove keys from configuration that should not be passed
* to the menu to prevent configuration errors
*/
protected array $removeConfigurationKeysForHmenu = [
'languages',
'languages.',
'as',
];
protected array $menuConfig = [
'special' => 'language',
'addQueryString' => 1,
];
protected array $menuDefaults = [
'as' => 'languagemenu',
];
public function __construct(
protected readonly MenuContentObjectFactory $menuContentObjectFactory,
protected readonly PageRepository $pageRepository,
) {}
/**
* Get configuration value from processorConfiguration
*/
protected function getConfigurationValue(string $key): string
{
return $this->cObj->stdWrapValue($key, $this->processorConfiguration, $this->menuDefaults[$key] ?? '');
}
protected function getRequest(): ServerRequestInterface
{
return $this->cObj->getRequest();
}
/**
* Returns the currently configured "site" if a site is configured (= resolved) in the current request.
*/
protected function getCurrentSite(): Site
{
return $this->getRequest()->getAttribute('site');
}
/**
* @throws \InvalidArgumentException
*/
protected function validateConfiguration(): void
{
$invalidArguments = [];
foreach ($this->processorConfiguration as $key => $value) {
if (!in_array($key, $this->allowedConfigurationKeys)) {
$invalidArguments[str_replace('.', '', $key)] = $key;
}
}
if (!empty($invalidArguments)) {
throw new \InvalidArgumentException('LanguageMenuProcessor configuration contains invalid arguments: ' . implode(', ', $invalidArguments), 1522959188);
}
}
/**
* Process languages and filter the configuration
*/
protected function prepareConfiguration(): void
{
$this->menuConfig = array_merge($this->menuConfig, $this->processorConfiguration);
// Process languages
$this->menuConfig['special.']['value'] = $this->cObj->stdWrapValue('languages', $this->menuConfig, 'auto');
// Filter configuration
foreach ($this->menuConfig as $key => $value) {
if (in_array($key, $this->removeConfigurationKeysForHmenu, true)) {
unset($this->menuConfig[$key]);
}
}
$paramsToExclude = CanonicalizationUtility::getParamsToExcludeForCanonicalizedUrl(
$this->getRequest()->getAttribute('frontend.page.information')->getId(),
(array)$GLOBALS['TYPO3_CONF_VARS']['FE']['additionalCanonicalizedUrlParameters'],
$this->cObj->getRequest()
);
$this->menuConfig['addQueryString.']['exclude'] = implode(
',',
array_merge(
GeneralUtility::trimExplode(',', $this->menuConfig['addQueryString.']['exclude'] ?? '', true),
$paramsToExclude
)
);
}
/**
* Build the menu configuration so it can be treated by TMENU
*/
protected function buildConfiguration(): void
{
$this->menuConfig['1'] = 'TMENU';
$this->menuConfig['1.']['NO'] = '1';
}
/**
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* @return array the processed data as key/value store
*/
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData): array
{
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
$this->cObj = $cObj;
$this->processorConfiguration = $processorConfiguration;
// Validate Configuration
$this->validateConfiguration();
// Build Configuration
$this->prepareConfiguration();
$this->buildConfiguration();
// Create menu object and get menu items directly
$request = $cObj->getRequest();
$site = $this->getCurrentSite();
$menu = $this->menuContentObjectFactory->getMenuObjectByType('TMENU');
$menu->parent_cObj = $cObj;
if (!$menu->start(null, $this->pageRepository, '', $this->menuConfig, 1, '', $request)) {
return $processedData;
}
$menu->makeMenu();
$menuItems = $menu->getMenuItems();
if ($menuItems === []) {
return $processedData;
}
// Enrich with language-specific fields
$processedMenu = [];
foreach ($menuItems as $key => $item) {
$languageId = (int)($item['data']['_REQUESTED_OVERLAY_LANGUAGE'] ?? 0);
try {
$languageObject = $site->getLanguageById($languageId);
} catch (\InvalidArgumentException) {
// Language not found in site config
continue;
}
$item['languageId'] = $languageId;
$item['locale'] = $languageObject->getLocale()->getName();
// Override title with language title (not page title)
$item['title'] = $languageObject->getTitle();
$item['navigationTitle'] = $languageObject->getNavigationTitle();
$item['twoLetterIsoCode'] = $languageObject->getLocale()->getLanguageCode();
$item['hreflang'] = $languageObject->getHreflang();
$item['direction'] = $languageObject->getLocale()->isRightToLeftLanguageDirection() ? 'rtl' : 'ltr';
$item['flag'] = $languageObject->getFlagIdentifier();
// Determine state from ITEM_STATE set by the menu system
$itemState = $item['data']['ITEM_STATE'] ?? '';
// active = 1 if state is ACT, ACTIFSUB, USERDEF2 (active states)
$item['active'] = in_array($itemState, ['ACT', 'ACTIFSUB', 'USERDEF2'], true) ? 1 : 0;
// current = 1 if state is CUR, CURIFSUB (current language)
$item['current'] = in_array($itemState, ['CUR', 'CURIFSUB'], true) ? 1 : 0;
// available = 1 unless USERDEF1/USERDEF2 state (language not available)
$item['available'] = !in_array($itemState, ['USERDEF1', 'USERDEF2'], true) ? 1 : 0;
$processedMenu[$key] = $item;
}
// Return processed data
$processedData[$this->getConfigurationValue('as')] = $processedMenu;
return $processedData;
}
}
+298
View File
@@ -0,0 +1,298 @@
<?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\DataProcessing;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
use TYPO3\CMS\Frontend\ContentObject\Menu\MenuContentObjectFactory;
/**
* This menu processor generates a menu array that will be assigned to
* FLUIDTEMPLATE as variable. Additional DataProcessing is supported and
* will be applied to each record.
*
* Options:
* as - The variable to be used within the result
* levels - Number of levels of the menu
* expandAll = If false, submenus will only render if the parent page is active
* includeSpacer = If true, pagetype spacer will be included in the menu
* titleField = Field that should be used for the title
*
* See HMENU docs for more options.
* https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Hmenu/Index.html
*
*
* Example TypoScript configuration:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor
* 10 {
* special = list
* special.value.field = pages
* levels = 7
* as = menu
* expandAll = 1
* includeSpacer = 1
* titleField = nav_title // title
* dataProcessing {
* 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
* 10 {
* references.fieldName = media
* }
* }
* }
*/
class MenuProcessor implements DataProcessorInterface
{
/**
* The content object renderer
*/
protected ?ContentObjectRenderer $cObj = null;
/**
* The processor configuration
*/
protected array $processorConfiguration;
/**
* Allowed configuration keys for menu generation, other keys
* will throw an exception to prevent configuration errors.
*/
public array $allowedConfigurationKeys = [
'cache',
'cache.',
'cache_period',
'entryLevel',
'entryLevel.',
'special',
'special.',
'minItems',
'minItems.',
'maxItems',
'maxItems.',
'begin',
'begin.',
'alternativeSortingField',
'alternativeSortingField.',
'showAccessRestrictedPages',
'showAccessRestrictedPages.',
'excludeUidList',
'excludeUidList.',
'excludeDoktypes',
'includeNotInMenu',
'includeNotInMenu.',
'alwaysActivePIDlist',
'alwaysActivePIDlist.',
'protectLvar',
'addQueryString',
'addQueryString.',
'if',
'if.',
'levels',
'levels.',
'expandAll',
'expandAll.',
'includeSpacer',
'includeSpacer.',
'as',
'titleField',
'titleField.',
'dataProcessing',
'dataProcessing.',
];
/**
* Remove keys from configuration that should not be passed
* to HMENU to prevent configuration errors
*/
public array $removeConfigurationKeysForHmenu = [
'levels',
'levels.',
'expandAll',
'expandAll.',
'includeSpacer',
'includeSpacer.',
'as',
'titleField',
'titleField.',
'dataProcessing',
'dataProcessing.',
];
protected array $menuConfig = [];
public array $menuDefaults = [
'levels' => 1,
'expandAll' => 1,
'includeSpacer' => 0,
'as' => 'menu',
'titleField' => 'nav_title // title',
];
protected int $menuLevels;
protected int $menuExpandAll;
protected int $menuIncludeSpacer;
protected string $menuTitleField;
protected string $menuAlternativeSortingField;
protected string $menuTargetVariableName;
public function __construct(
protected ContentDataProcessor $contentDataProcessor,
protected MenuContentObjectFactory $menuContentObjectFactory,
protected PageRepository $pageRepository,
) {}
/**
* Get configuration value from processorConfiguration
*/
protected function getConfigurationValue(string $key): string
{
return $this->cObj->stdWrapValue($key, $this->processorConfiguration, $this->menuDefaults[$key] ?? '');
}
/**
* @throws \InvalidArgumentException
*/
public function validateConfiguration(): void
{
$invalidArguments = [];
foreach ($this->processorConfiguration as $key => $value) {
if (!in_array($key, $this->allowedConfigurationKeys)) {
$invalidArguments[str_replace('.', '', $key)] = $key;
}
}
if (!empty($invalidArguments)) {
throw new \InvalidArgumentException('MenuProcessor Configuration contains invalid Arguments: ' . implode(', ', $invalidArguments), 1478806566);
}
}
public function prepareConfiguration(): void
{
$this->menuConfig = $this->processorConfiguration;
// Filter configuration
foreach ($this->menuConfig as $key => $value) {
if (in_array($key, $this->removeConfigurationKeysForHmenu)) {
unset($this->menuConfig[$key]);
}
}
// Process special value
if (isset($this->menuConfig['special.']['value.'])) {
$this->menuConfig['special.']['value'] = $this->cObj->stdWrapValue('value', $this->menuConfig['special.']);
unset($this->menuConfig['special.']['value.']);
}
}
/**
* Build the menu configuration so it can be treated by TMENU
*/
public function buildConfiguration(): void
{
for ($i = 1; $i <= $this->menuLevels; $i++) {
$this->menuConfig[$i] = 'TMENU';
if (array_key_exists('showAccessRestrictedPages', $this->menuConfig)) {
$this->menuConfig[$i . '.']['showAccessRestrictedPages'] = $this->menuConfig['showAccessRestrictedPages'];
if (array_key_exists('showAccessRestrictedPages.', $this->menuConfig)
&& is_array($this->menuConfig['showAccessRestrictedPages.'])) {
$this->menuConfig[$i . '.']['showAccessRestrictedPages.'] = $this->menuConfig['showAccessRestrictedPages.'];
}
}
$this->menuConfig[$i . '.']['expAll'] = $this->menuExpandAll;
$this->menuConfig[$i . '.']['alternativeSortingField'] = $this->menuAlternativeSortingField;
$this->menuConfig[$i . '.']['NO'] = '1';
if ($this->menuIncludeSpacer) {
$this->menuConfig[$i . '.']['SPC'] = '1';
}
}
}
/**
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* @return array the processed data as key/value store
*/
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
{
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
$this->cObj = $cObj;
$this->processorConfiguration = $processorConfiguration;
// Get Configuration
$this->menuLevels = (int)$this->getConfigurationValue('levels') ?: 1;
$this->menuExpandAll = (int)$this->getConfigurationValue('expandAll');
$this->menuIncludeSpacer = (int)$this->getConfigurationValue('includeSpacer');
$this->menuTargetVariableName = $this->getConfigurationValue('as');
$this->menuTitleField = $this->getConfigurationValue('titleField');
$this->menuAlternativeSortingField = $this->getConfigurationValue('alternativeSortingField');
// Validate Configuration
$this->validateConfiguration();
// Build Configuration
$this->prepareConfiguration();
$this->buildConfiguration();
// Create menu object and get menu items directly
$request = $cObj->getRequest();
$menu = $this->menuContentObjectFactory->getMenuObjectByType('TMENU');
$menu->parent_cObj = $cObj;
if (!$menu->start(null, $this->pageRepository, '', $this->menuConfig, 1, '', $request)) {
return $processedData;
}
$menu->makeMenu();
$menuItems = $menu->getMenuItems();
if ($menuItems === []) {
return $processedData;
}
// Process additional data processors
$processedMenu = [];
foreach ($menuItems as $key => $page) {
$processedMenu[$key] = $this->processAdditionalDataProcessors($page, $processorConfiguration);
}
// Return processed data
$processedData[$this->menuTargetVariableName] = $processedMenu;
return $processedData;
}
/**
* Process additional data processors
*/
protected function processAdditionalDataProcessors(array $page, array $processorConfiguration): array
{
if (is_array($page['children'] ?? false)) {
foreach ($page['children'] as $key => $item) {
$page['children'][$key] = $this->processAdditionalDataProcessors($item, $processorConfiguration);
}
}
$request = $this->cObj->getRequest();
$recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$recordContentObjectRenderer->setRequest($request);
$recordContentObjectRenderer->start($page['data'] ?? [], 'pages');
$page['title'] = (string)$recordContentObjectRenderer->stdWrap('', ['field' => $this->menuTitleField]);
return $this->contentDataProcessor->process($recordContentObjectRenderer, $processorConfiguration, $page);
}
}
@@ -0,0 +1,76 @@
<?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 TYPO3\CMS\Frontend\DataProcessing;
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Frontend\Content\RecordCollector;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
use TYPO3\CMS\Frontend\Event\AfterContentHasBeenFetchedEvent;
/**
* All-in-one data processor that loads all tt_content records from the current page
* layout into the template with a given identifier for each colPos, also respecting
* slideMode or collect options based on the page layouts content columns.
*
* Use "as" for the target variable where the fetched content elements will be provided.
* If empty, "content" is used.
*
* Example TypoScript configuration:
*
* page = PAGE
* page {
* 10 = PAGEVIEW
* 10 {
* paths.10 = EXT:my_site_package/Resources/Private/Templates/
* dataProcessing {
* 10 = page-content
* 10.as = myContent
* }
* }
* }
*
* which fetches all content elements for the current page and provides them as "myContent".
*/
readonly class PageContentFetchingProcessor implements DataProcessorInterface
{
public function __construct(
protected RecordCollector $recordCollector,
protected EventDispatcherInterface $eventDispatcher,
) {}
public function process(
ContentObjectRenderer $cObj,
array $contentObjectConfiguration,
array $processorConfiguration,
array $processedData
): array {
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
$request = $cObj->getRequest();
$pageInformation = $request->getAttribute('frontend.page.information');
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'content');
$contentAreas = $pageInformation->getPageLayout()?->getContentAreas();
$groupedContent = $this->eventDispatcher->dispatch(
new AfterContentHasBeenFetchedEvent($contentAreas->getGroupedRecords($request), $request)
)->groupedContent;
$processedData[$targetVariableName] = $contentAreas->withUpdatedRecords($groupedContent);
return $processedData;
}
}
@@ -0,0 +1,111 @@
<?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 TYPO3\CMS\Frontend\DataProcessing;
use TYPO3\CMS\Core\Domain\RecordFactory;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* Creates Record objects out of full data sets (= DB entries).
* This is typically useful in conjunction with the DatabaseQueryProcessor.
* Can also be used to transform the current data array of FLUIDTEMPLATE.
*
* The variable that contains the record(s) from a previous data processor,
* or from a FLUIDTEMPLATE view. Default is `data`.
*
* variableName = items
*
* The name of the database table of the records. Leave empty to auto-resolve
* the table from current ContentObjectRenderer.
*
* table = tt_content
*
* The target variable where the resolved record objects are contained.
* Can be set to `data` to override the input data array of FLUIDTEMPLATE.
* If empty, "record" or "records" (if multiple records are given) is used.
*
* as = myRecords
*
* Example TypoScript configuration:
*
* page = PAGE
* page {
* 10 = PAGEVIEW
* 10 {
* paths.10 = EXT:my_site_package/Resources/Private/Templates/
* dataProcessing {
* 10 = database-query
* 10 {
* as = mainContent
* table = tt_content
* select.where = colPos=0
* dataProcessing {
* 10 = record-transformation
* 10 {
* as = myContent
* }
* }
* }
* }
* }
* }
*
* which transforms all content elements fetched by the DatabaseQueryProcessor an provides them as "myContent".
*/
readonly class RecordTransformationProcessor implements DataProcessorInterface
{
public function __construct(
protected RecordFactory $recordFactory,
) {}
public function process(
ContentObjectRenderer $cObj,
array $contentObjectConfiguration,
array $processorConfiguration,
array $processedData
): array {
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
// `data` is the default variable name for the FLUIDTEMPLATE record
// and processed records of the DatabaseQueryProcessor.
$defaultVariableName = 'data';
$variableName = $cObj->stdWrapValue('variableName', $processorConfiguration, $defaultVariableName);
$input = $processedData[$variableName] ?? $processedData;
// We can only deal with arrays here.
if (!is_array($input)) {
return $processedData;
}
$table = $cObj->stdWrapValue('table', $processorConfiguration, $cObj->getCurrentTable());
$output = [];
if (array_is_list($input)) {
foreach ($input as $record) {
$output[] = $this->recordFactory->createResolvedRecordFromDatabaseRow($table, $record);
}
$defaultTargetVariableName = 'records';
} else {
$output = $this->recordFactory->createResolvedRecordFromDatabaseRow($table, $input);
$defaultTargetVariableName = 'record';
}
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, $defaultTargetVariableName);
// @todo Should we make sure that $output is actually a Record object?
$processedData[$targetVariableName] = $output;
return $processedData;
}
}
@@ -0,0 +1,50 @@
<?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 TYPO3\CMS\Frontend\DataProcessing;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* Fetch the SiteLanguage object containing all information about the current language
*
* Example TypoScript configuration:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\SiteLanguageProcessor
* 10 {
* as = siteLanguage
* }
*
* where "as" names the variable containing the SiteLanguage properties
*/
class SiteLanguageProcessor implements DataProcessorInterface
{
/**
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* @return array the processed data as key/value store
*/
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData): array
{
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'siteLanguage');
$processedData[$targetVariableName] = $cObj->getRequest()->getAttribute('language')?->toArray();
return $processedData;
}
}
+50
View File
@@ -0,0 +1,50 @@
<?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 TYPO3\CMS\Frontend\DataProcessing;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* Fetch the site object containing all information about the current site
*
* Example TypoScript configuration:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\SiteProcessor
* 10 {
* as = site
* }
*
* where "as" names the variable containing the site object
*/
class SiteProcessor implements DataProcessorInterface
{
/**
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* @return array the processed data as key/value store
*/
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData): array
{
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'site');
$processedData[$targetVariableName] = $cObj->getRequest()->getAttribute('site');
return $processedData;
}
}
+97
View File
@@ -0,0 +1,97 @@
<?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\DataProcessing;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* This data processor can be used for processing data for the content elements which have split contents in one field
* like e.g. "bullets". It will split the field data in an array ready to be iterated over in Fluid.
*
* Example field data:
*
* This is bullet 1, This is bullet 2, This is bullet 3
*
* Example TypoScript configuration:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\SplitProcessor
* 10 {
* if.isTrue.field = bodytext
* delimiter = ,
* fieldName = bodytext
* removeEmptyEntries = 1
* filterIntegers = 1
* filterUnique = 1
* as = bullets
* }
*
* whereas "bullets" can be used as a variable {bullets} inside Fluid for iteration.
*/
class SplitProcessor implements DataProcessorInterface
{
/**
* Process field data to split in an array
*
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* @return array the processed data as key/value store
*/
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
{
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
// The field name to process
$fieldName = $cObj->stdWrapValue('fieldName', $processorConfiguration);
if (empty($fieldName)) {
return $processedData;
}
$originalValue = (string)($cObj->data[$fieldName] ?? '');
// Set the target variable
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, $fieldName);
// Set the delimiter which is "LF" by default
$delimiter = (string)$cObj->stdWrapValue('delimiter', $processorConfiguration, LF);
// Filter integers
$filterIntegers = (bool)$cObj->stdWrapValue('filterIntegers', $processorConfiguration, false);
// Filter unique
$filterUnique = (bool)$cObj->stdWrapValue('filterUnique', $processorConfiguration, false);
// Remove empty entries
$removeEmptyEntries = (bool)$cObj->stdWrapValue('removeEmptyEntries', $processorConfiguration, false);
if ($filterIntegers === true) {
$processedData[$targetVariableName] = GeneralUtility::intExplode($delimiter, $originalValue, $removeEmptyEntries);
} else {
$processedData[$targetVariableName] = GeneralUtility::trimExplode($delimiter, $originalValue, $removeEmptyEntries);
}
if ($filterUnique === true) {
$processedData[$targetVariableName] = array_unique($processedData[$targetVariableName]);
}
return $processedData;
}
}