first commit

This commit is contained in:
salam committed 2026-10-19 08:18:39 +02:00
commit 1218dd1517
12716 files changed
+989233

No files matched your search

@@ -0,0 +1,102 @@
<?php
namespace TYPO3\CMS\Frontend\DataProcessing;
/*
* 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!
*/
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 = $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 = $cObj->stdWrapValue('fieldDelimiter', $processorConfiguration, ',');
// Set the field enclosure which is " by default
$fieldEnclosure = $cObj->stdWrapValue('fieldEnclosure', $processorConfiguration, '"');
$processedData[$targetVariableName] = CsvUtility::csvToArray(
$originalValue,
$fieldDelimiter,
$fieldEnclosure,
(int)$maximumColumns
);
return $processedData;
}
}
@@ -0,0 +1,107 @@
<?php
namespace TYPO3\CMS\Frontend\DataProcessing;
/*
* 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!
*/
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.
*/
class DatabaseQueryProcessor implements DataProcessorInterface
{
/**
* @var ContentDataProcessor
*/
protected $contentDataProcessor;
/**
* Constructor
*/
public function __construct()
{
$this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class);
}
/**
* 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);
$processedRecordVariables = [];
foreach ($records as $key => $record) {
/** @var ContentObjectRenderer $recordContentObjectRenderer */
$recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$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,114 @@
<?php
namespace TYPO3\CMS\Frontend\DataProcessing;
/*
* 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!
*/
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
/** @var FileCollector $fileCollector */
$fileCollector = GeneralUtility::makeInstance(FileCollector::class);
// references / relations
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(',', $files, true);
$fileCollector->addFiles($files);
}
// collections
$collections = $cObj->stdWrapValue('collections', $processorConfiguration);
if (!empty($collections)) {
$collections = GeneralUtility::trimExplode(',', $collections, true);
$fileCollector->addFilesFromFileCollections($collections);
}
// folders
$folders = $cObj->stdWrapValue('folders', $processorConfiguration);
if (!empty($folders)) {
$folders = GeneralUtility::trimExplode(',', $folders, true);
$fileCollector->addFilesFromFolders($folders, !empty($processorConfiguration['folders.']['recursive']));
}
// 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,512 @@
<?php
namespace TYPO3\CMS\Frontend\DataProcessing;
/*
* 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!
*/
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 FileInterface $fileObject
* @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,481 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Frontend\DataProcessing;
/*
* 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!
*/
use TYPO3\CMS\Core\Routing\SiteMatcher;
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
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\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Frontend\Utility\CanonicalizationUtility;
/**
* This menu processor generates a json encoded menu string that will be
* decoded again and 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 const LINK_PLACEHOLDER = '###LINKPLACEHOLDER###';
/**
* The content object renderer
*
* @var ContentObjectRenderer
*/
public $cObj;
/**
* The processor configuration
*
* @var array
*/
protected $processorConfiguration;
/**
* Allowed configuration keys for menu generation, other keys
* will throw an exception to prevent configuration errors.
*
* @var array
*/
protected $allowedConfigurationKeys = [
'if',
'if.',
'languages',
'languages.',
'as',
'addQueryString',
'addQueryString.'
];
/**
* Remove keys from configuration that should not be passed
* to HMENU to prevent configuration errors
*
* @var array
*/
protected $removeConfigurationKeysForHmenu = [
'languages',
'languages.',
'as'
];
/**
* @var array
*/
protected $menuConfig = [
'special' => 'language',
'addQueryString' => 1,
'addQueryString.' => [
'method' => 'GET'
],
'wrap' => '[|]'
];
/**
* @var array
*/
protected $menuLevelConfig = [
'doNotLinkIt' => '1',
'wrapItemAndSub' => '{|}, |*| {|}, |*| {|}',
'stdWrap.' => [
'cObject' => 'COA',
'cObject.' => [
'1' => 'LOAD_REGISTER',
'1.' => [
'languageId.' => [
'cObject' => 'TEXT',
'cObject.' => [
'value.' => [
'data' => 'register:languages_HMENU'
],
'listNum.' => [
'stdWrap.' => [
'data' => 'register:count_HMENU_MENUOBJ',
'wrap' => '|-1'
],
'splitChar' => ','
]
]
]
],
'10' => 'TEXT',
'10.' => [
'stdWrap.' => [
'data' => 'register:languageId'
],
'wrap' => '"languageId":|'
],
'11' => 'USER',
'11.' => [
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
'language.' => [
'data' => 'register:languageId'
],
'field' => 'locale',
'stdWrap.' => [
'wrap' => ',"locale":|'
]
],
'20' => 'USER',
'20.' => [
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
'language.' => [
'data' => 'register:languageId'
],
'field' => 'title',
'stdWrap.' => [
'wrap' => ',"title":|'
]
],
'21' => 'USER',
'21.' => [
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
'language.' => [
'data' => 'register:languageId'
],
'field' => 'navigationTitle',
'stdWrap.' => [
'wrap' => ',"navigationTitle":|'
]
],
'22' => 'USER',
'22.' => [
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
'language.' => [
'data' => 'register:languageId'
],
'field' => 'twoLetterIsoCode',
'stdWrap.' => [
'wrap' => ',"twoLetterIsoCode":|'
]
],
'23' => 'USER',
'23.' => [
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
'language.' => [
'data' => 'register:languageId'
],
'field' => 'hreflang',
'stdWrap.' => [
'wrap' => ',"hreflang":|'
]
],
'24' => 'USER',
'24.' => [
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor->getFieldAsJson',
'language.' => [
'data' => 'register:languageId'
],
'field' => 'direction',
'stdWrap.' => [
'wrap' => ',"direction":|'
]
],
'90' => 'TEXT',
'90.' => [
'value' => self::LINK_PLACEHOLDER,
'wrap' => ',"link":|',
],
'91' => 'TEXT',
'91.' => [
'value' => '0',
'wrap' => ',"active":|'
],
'92' => 'TEXT',
'92.' => [
'value' => '0',
'wrap' => ',"current":|'
],
'93' => 'TEXT',
'93.' => [
'value' => '1',
'wrap' => ',"available":|'
],
'99' => 'RESTORE_REGISTER'
]
]
];
/**
* @var array
*/
protected $menuDefaults = [
'as' => 'languagemenu'
];
/**
* @var string
*/
protected $menuTargetVariableName;
/**
* @var ContentDataProcessor
*/
protected $contentDataProcessor;
/**
* Constructor
*/
public function __construct()
{
$this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class);
}
/**
* Get configuration value from processorConfiguration
*
* @param string $key
* @return string
*/
protected function getConfigurationValue(string $key): string
{
return $this->cObj->stdWrapValue($key, $this->processorConfiguration, $this->menuDefaults[$key]);
}
/**
* @return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController(): TypoScriptFrontendController
{
return $GLOBALS['TSFE'];
}
/**
* Returns the currently configured "site" if a site is configured (= resolved) in the current request.
*
* @return SiteInterface
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
*/
protected function getCurrentSite(): SiteInterface
{
$matcher = GeneralUtility::makeInstance(SiteMatcher::class);
return $matcher->matchByPageId((int)$this->getTypoScriptFrontendController()->id);
}
/**
* JSON Encode
*
* @param mixed $value
* @return string
*/
protected function jsonEncode($value): string
{
return json_encode($value, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE);
}
/**
* @throws \InvalidArgumentException
*/
protected function validateConfiguration()
{
$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
if (empty($this->menuConfig['languages']) && empty($this->menuConfig['languages.'])) {
$this->menuConfig['special.']['value'] = 'auto';
} elseif (!empty($this->menuConfig['languages.'])) {
$this->menuConfig['special.']['value'] = $this->cObj->stdWrap($this->menuConfig['languages'], $this->menuConfig['languages.']);
} else {
$this->menuConfig['special.']['value'] = $this->menuConfig['languages'];
}
// Filter configuration
foreach ($this->menuConfig as $key => $value) {
if (in_array($key, $this->removeConfigurationKeysForHmenu, true)) {
unset($this->menuConfig[$key]);
}
}
$paramsToExclude = CanonicalizationUtility::getParamsToExcludeForCanonicalizedUrl(
(int)$this->getTypoScriptFrontendController()->id,
(array)$GLOBALS['TYPO3_CONF_VARS']['FE']['additionalCanonicalizedUrlParameters']
);
$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 HMENU cObject
*/
protected function buildConfiguration(): void
{
$this->menuConfig['1'] = 'TMENU';
$this->menuConfig['1.']['IProcFunc'] = LanguageMenuProcessor::class . '->replacePlaceholderInRenderedMenuItem';
$this->menuConfig['1.']['NO'] = '1';
$this->menuConfig['1.']['NO.'] = $this->menuLevelConfig;
$this->menuConfig['1.']['ACT'] = $this->menuConfig['1.']['NO'];
$this->menuConfig['1.']['ACT.'] = $this->menuConfig['1.']['NO.'];
$this->menuConfig['1.']['ACT.']['stdWrap.']['cObject.']['91.']['value'] = '1';
$this->menuConfig['1.']['CUR'] = $this->menuConfig['1.']['ACT'];
$this->menuConfig['1.']['CUR.'] = $this->menuConfig['1.']['ACT.'];
$this->menuConfig['1.']['CUR.']['stdWrap.']['cObject.']['92.']['value'] = '1';
$this->menuConfig['1.']['USERDEF1'] = $this->menuConfig['1.']['NO'];
$this->menuConfig['1.']['USERDEF1.'] = $this->menuConfig['1.']['NO.'];
$this->menuConfig['1.']['USERDEF1.']['stdWrap.']['cObject.']['93.']['value'] = '0';
$this->menuConfig['1.']['USERDEF2'] = $this->menuConfig['1.']['ACT'];
$this->menuConfig['1.']['USERDEF2.'] = $this->menuConfig['1.']['ACT.'];
$this->menuConfig['1.']['USERDEF2.']['stdWrap.']['cObject.']['93.']['value'] = '0';
}
/**
* Validate and Build the menu configuration so it can be treated by HMENU cObject
*/
protected function validateAndBuildConfiguration(): void
{
// Validate Configuration
$this->validateConfiguration();
// Build Configuration
$this->prepareConfiguration();
$this->buildConfiguration();
}
/**
* @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
{
$this->cObj = $cObj;
$this->processorConfiguration = $processorConfiguration;
// Get Configuration
$this->menuTargetVariableName = $this->getConfigurationValue('as');
// Validate and Build Configuration
$this->validateAndBuildConfiguration();
// Process Configuration
$menuContentObject = $cObj->getContentObject('HMENU');
$renderedMenu = $menuContentObject->render($this->menuConfig);
if ($renderedMenu) {
// Process menu
$menu = json_decode($renderedMenu, true);
$processedMenu = [];
foreach ($menu as $key => $language) {
$processedMenu[$key] = $language;
}
$processedData[$this->menuTargetVariableName] = $processedMenu;
}
return $processedData;
}
/**
* This UserFunc gets the link and the target
*
* @param array $menuItem
* @return array
*/
public function replacePlaceholderInRenderedMenuItem(array $menuItem): array
{
$link = $this->jsonEncode($menuItem['linkHREF']['HREF']);
$menuItem['parts']['title'] = str_replace(self::LINK_PLACEHOLDER, $link, $menuItem['parts']['title']);
return $menuItem;
}
/**
* Returns the data from the field and language submitted by $conf in JSON format
*
* @param string Empty string (no content to process)
* @param array TypoScript configuration
* @return string JSON encoded data
* @throws \InvalidArgumentException
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
*/
public function getFieldAsJson(string $content, array $conf): string
{
// Support of stdWrap for parameters
if (isset($conf['language.'])) {
$conf['language'] = $this->cObj->stdWrap($conf['language'], $conf['language.']);
unset($conf['language.']);
}
if (isset($conf['field.'])) {
$conf['field'] = $this->cObj->stdWrap($conf['field'], $conf['field.']);
unset($conf['field.']);
}
// Check required fields
if ($conf['language'] === '') {
throw new \InvalidArgumentException('Argument \'language\' must be supplied.', 1522959186);
}
if ($conf['field'] === '') {
throw new \InvalidArgumentException('Argument \'field\' must be supplied.', 1522959187);
}
// Get and check current site
$site = $this->getCurrentSite();
// Throws InvalidArgumentException in case language is not found which is fine
$language = $site->getLanguageById((int)$conf['language']);
if ($language->enabled()) {
$language = $language->toArray();
} else {
$language = null;
}
// Check field for return exists
if ($language !== null && !isset($language[$conf['field']])) {
throw new \InvalidArgumentException('Invalid value \'' . $conf['field'] . '\' for argument \'field\' supplied.', 1524063160);
}
return $this->jsonEncode($language[$conf['field']]);
}
}
@@ -0,0 +1,550 @@
<?php
namespace TYPO3\CMS\Frontend\DataProcessing;
/*
* 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!
*/
use TYPO3\CMS\Core\Routing\SiteMatcher;
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
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\Controller\TypoScriptFrontendController;
/**
* This menu processor utilizes HMENU to generate a json encoded menu
* string that will be decoded again and 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
{
const LINK_PLACEHOLDER = '###LINKPLACEHOLDER###';
const TARGET_PLACEHOLDER = '###TARGETPLACEHOLDER###';
/**
* The content object renderer
*
* @var ContentObjectRenderer
*/
public $cObj;
/**
* The processor configuration
*
* @var array
*/
protected $processorConfiguration;
/**
* Allowed configuration keys for menu generation, other keys
* will throw an exception to prevent configuration errors.
*
* @var array
*/
public $allowedConfigurationKeys = [
'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
*
* @var array
*/
public $removeConfigurationKeysForHmenu = [
'levels',
'levels.',
'expandAll',
'expandAll.',
'includeSpacer',
'includeSpacer.',
'as',
'titleField',
'titleField.',
'dataProcessing',
'dataProcessing.'
];
/**
* @var array
*/
protected $menuConfig = [
'wrap' => '[|]'
];
/**
* @var array
*/
protected $menuLevelConfig = [
'doNotLinkIt' => '1',
'wrapItemAndSub' => '{|}, |*| {|}, |*| {|}',
'stdWrap.' => [
'cObject' => 'COA',
'cObject.' => [
'10' => 'USER',
'10.' => [
'userFunc' => 'TYPO3\CMS\Frontend\DataProcessing\MenuProcessor->getDataAsJson',
'stdWrap.' => [
'wrap' => '"data":|'
]
],
'20' => 'TEXT',
'20.' => [
'field' => 'nav_title // title',
'trim' => '1',
'wrap' => ',"title":|',
'preUserFunc' => 'TYPO3\CMS\Frontend\DataProcessing\MenuProcessor->jsonEncodeUserFunc'
],
'21' => 'TEXT',
'21.' => [
'value' => self::LINK_PLACEHOLDER,
'wrap' => ',"link":|',
],
'22' => 'TEXT',
'22.' => [
'value' => self::TARGET_PLACEHOLDER,
'wrap' => ',"target":|',
],
'30' => 'TEXT',
'30.' => [
'value' => '0',
'wrap' => ',"active":|'
],
'40' => 'TEXT',
'40.' => [
'value' => '0',
'wrap' => ',"current":|'
],
'50' => 'TEXT',
'50.' => [
'value' => '0',
'wrap' => ',"spacer":|'
]
]
]
];
/**
* @var array
*/
public $menuDefaults = [
'levels' => 1,
'expandAll' => 1,
'includeSpacer' => 0,
'as' => 'menu',
'titleField' => 'nav_title // title'
];
/**
* @var int
*/
protected $menuLevels;
/**
* @var int
*/
protected $menuExpandAll;
/**
* @var int
*/
protected $menuIncludeSpacer;
/**
* @var string
*/
protected $menuTitleField;
/**
* @var string
*/
protected $menuAlternativeSortingField;
/**
* @var string
*/
protected $menuTargetVariableName;
/**
* @var ContentDataProcessor
*/
protected $contentDataProcessor;
/**
* Constructor
*/
public function __construct()
{
$this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class);
}
/**
* Get configuration value from processorConfiguration
*
* @param string $key
* @return string
*/
protected function getConfigurationValue($key)
{
return $this->cObj->stdWrapValue($key, $this->processorConfiguration, $this->menuDefaults[$key]);
}
/**
* Validate configuration
*
* @throws \InvalidArgumentException
*/
public function validateConfiguration()
{
$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);
}
}
/**
* Prepare Configuration
*/
public function prepareConfiguration()
{
$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->stdWrap($this->menuConfig['special.']['value'], $this->menuConfig['special.']['value.']);
unset($this->menuConfig['special.']['value.']);
}
}
/**
* Prepare configuration for a certain menu level in the hierarchy
*/
public function prepareLevelConfiguration()
{
$this->menuLevelConfig['stdWrap.']['cObject.'] = array_replace_recursive(
$this->menuLevelConfig['stdWrap.']['cObject.'],
[
'20.' => [
'field' => $this->menuTitleField,
]
]
);
}
/**
* Prepare the configuration when rendering a language menu
*/
public function prepareLevelLanguageConfiguration()
{
if ($this->menuConfig['special'] === 'language') {
$languageUids = $this->menuConfig['special.']['value'];
if ($this->menuConfig['special.']['value'] === 'auto') {
$site = $this->getCurrentSite();
$languageUids = implode(',', array_keys($site->getLanguages()));
}
$this->menuLevelConfig['stdWrap.']['cObject.'] = array_replace_recursive(
$this->menuLevelConfig['stdWrap.']['cObject.'],
[
'60' => 'TEXT',
'60.' => [
'value' => '1',
'wrap' => ',"available":|'
],
'70' => 'TEXT',
'70.' => [
'value' => $languageUids,
'listNum.' => [
'stdWrap.' => [
'data' => 'register:count_HMENU_MENUOBJ',
'wrap' => '|-1'
],
'splitChar' => ','
],
'wrap' => ',"languageUid":"|"'
]
]
);
}
}
/**
* Build the menu configuration so it can be treated by HMENU cObject
*/
public function buildConfiguration()
{
for ($i = 1; $i <= $this->menuLevels; $i++) {
$this->menuConfig[$i] = 'TMENU';
$this->menuConfig[$i . '.']['IProcFunc'] = 'TYPO3\CMS\Frontend\DataProcessing\MenuProcessor->replacePlaceholderInRenderedMenuItem';
if ($i > 1) {
$this->menuConfig[$i . '.']['stdWrap.']['wrap'] = ',"children": [|]';
}
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';
$this->menuConfig[$i . '.']['NO.'] = $this->menuLevelConfig;
if ($this->menuIncludeSpacer) {
$this->menuConfig[$i . '.']['SPC'] = '1';
$this->menuConfig[$i . '.']['SPC.'] = $this->menuConfig[$i . '.']['NO.'];
$this->menuConfig[$i . '.']['SPC.']['stdWrap.']['cObject.']['50.']['value'] = '1';
}
$this->menuConfig[$i . '.']['IFSUB'] = '1';
$this->menuConfig[$i . '.']['IFSUB.'] = $this->menuConfig[$i . '.']['NO.'];
$this->menuConfig[$i . '.']['ACT'] = '1';
$this->menuConfig[$i . '.']['ACT.'] = $this->menuConfig[$i . '.']['NO.'];
$this->menuConfig[$i . '.']['ACT.']['stdWrap.']['cObject.']['30.']['value'] = '1';
$this->menuConfig[$i . '.']['ACTIFSUB'] = '1';
$this->menuConfig[$i . '.']['ACTIFSUB.'] = $this->menuConfig[$i . '.']['ACT.'];
$this->menuConfig[$i . '.']['CUR'] = '1';
$this->menuConfig[$i . '.']['CUR.'] = $this->menuConfig[$i . '.']['ACT.'];
$this->menuConfig[$i . '.']['CUR.']['stdWrap.']['cObject.']['40.']['value'] = '1';
$this->menuConfig[$i . '.']['CURIFSUB'] = '1';
$this->menuConfig[$i . '.']['CURIFSUB.'] = $this->menuConfig[$i . '.']['CUR.'];
if ($this->menuConfig['special'] === 'language') {
$this->menuConfig[$i . '.']['USERDEF1'] = $this->menuConfig[$i . '.']['NO'];
$this->menuConfig[$i . '.']['USERDEF1.'] = $this->menuConfig[$i . '.']['NO.'];
$this->menuConfig[$i . '.']['USERDEF1.']['stdWrap.']['cObject.']['60.']['value'] = '0';
$this->menuConfig[$i . '.']['USERDEF2'] = $this->menuConfig[$i . '.']['ACT'];
$this->menuConfig[$i . '.']['USERDEF2.'] = $this->menuConfig[$i . '.']['ACT.'];
$this->menuConfig[$i . '.']['USERDEF2.']['stdWrap.']['cObject.']['60.']['value'] = '0';
}
}
}
/**
* @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)
{
$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->prepareLevelConfiguration();
$this->prepareLevelLanguageConfiguration();
$this->buildConfiguration();
// Process Configuration
$menuContentObject = $cObj->getContentObject('HMENU');
$renderedMenu = $menuContentObject->render($this->menuConfig);
if (!$renderedMenu) {
return $processedData;
}
// Process menu
$menu = json_decode($renderedMenu, true);
$processedMenu = [];
foreach ($menu as $key => $page) {
$processedMenu[$key] = $this->processAdditionalDataProcessors($page, $processorConfiguration);
}
// Return processed data
$processedData[$this->menuTargetVariableName] = $processedMenu;
return $processedData;
}
/**
* Process additional data processors
*
* @param array $page
* @param array $processorConfiguration
* @return array
*/
protected function processAdditionalDataProcessors($page, $processorConfiguration)
{
if (is_array($page['children'])) {
foreach ($page['children'] as $key => $item) {
$page['children'][$key] = $this->processAdditionalDataProcessors($item, $processorConfiguration);
}
}
/** @var ContentObjectRenderer $recordContentObjectRenderer */
$recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$recordContentObjectRenderer->start($page['data'], 'pages');
$processedPage = $this->contentDataProcessor->process($recordContentObjectRenderer, $processorConfiguration, $page);
return $processedPage;
}
/**
* Gets the data of the current record in JSON format
*
* @return string JSON encoded data
*/
public function getDataAsJson()
{
return $this->jsonEncode($this->cObj->data);
}
/**
* This UserFunc encodes the content as Json
*
* @param string $content
* @param array $conf
* @return string JSON encoded content
*/
public function jsonEncodeUserFunc($content, $conf)
{
$content = $this->jsonEncode($content);
return $content;
}
/**
* JSON Encode
*
* @param mixed $value
* @return string
*/
public function jsonEncode($value)
{
return json_encode($value, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE);
}
/**
* This UserFunc gets the link and the target
*
* @param array $menuItem
* @param array $conf
* @return array
*/
public function replacePlaceholderInRenderedMenuItem($menuItem, $conf)
{
$link = $this->jsonEncode($menuItem['linkHREF']['HREF']);
$target = $this->jsonEncode($menuItem['linkHREF']['TARGET']);
$menuItem['parts']['title'] = str_replace(self::LINK_PLACEHOLDER, $link, $menuItem['parts']['title']);
$menuItem['parts']['title'] = str_replace(self::TARGET_PLACEHOLDER, $target, $menuItem['parts']['title']);
return $menuItem;
}
/**
* Returns the currently configured "site" if a site is configured (= resolved) in the current request.
*
* @return SiteInterface
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
*/
protected function getCurrentSite(): SiteInterface
{
$matcher = GeneralUtility::makeInstance(SiteMatcher::class);
return $matcher->matchByPageId((int)$this->getTypoScriptFrontendController()->id);
}
/**
* @return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'];
}
}
@@ -0,0 +1,85 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Frontend\DataProcessing;
/*
* 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!
*/
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Routing\SiteMatcher;
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
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] = $this->getCurrentSite();
return $processedData;
}
/**
* Returns the currently configured "site" if a site is configured (= resolved) in the current request.
*
* @return SiteInterface|null
*/
protected function getCurrentSite(): ?SiteInterface
{
try {
return $this->getMatcher()->matchByPageId($this->getCurrentPageId());
} catch (SiteNotFoundException $e) {
// Do nothing
}
return null;
}
/**
* @return SiteMatcher
*/
protected function getMatcher(): SiteMatcher
{
return GeneralUtility::makeInstance(SiteMatcher::class);
}
/**
* @return int
*/
protected function getCurrentPageId(): int
{
return (int)$GLOBALS['TSFE']->id;
}
}
@@ -0,0 +1,96 @@
<?php
namespace TYPO3\CMS\Frontend\DataProcessing;
/*
* 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!
*/
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 = $cObj->data[$fieldName];
// Set the target variable
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, $fieldName);
// Set the delimiter which is "LF" by default
$delimiter = $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;
}
}