first commit
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user