TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user