*/
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
+use TYPO3\CMS\Backend\Form\Utility\DisplayConditionEvaluator;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
continue;
}
- if (!$this->evaluateDisplayCondition($columnConfiguration['displayCond'], $result['databaseRow'])) {
+ $displayConditionValid = $this->getDisplayConditionEvaluator()->evaluateDisplayCondition(
+ $columnConfiguration['displayCond'],
+ $result['databaseRow']
+ );
+ if (!$displayConditionValid) {
unset($result['processedTca']['columns'][$columnName]);
}
}
if (!isset($sheetConfiguration['ROOT']['displayCond'])) {
continue;
}
- if (!$this->evaluateDisplayCondition($sheetConfiguration['ROOT']['displayCond'], $flexFormRowData, true)) {
+ $displayConditionValid = $this->getDisplayConditionEvaluator()->evaluateDisplayCondition(
+ $sheetConfiguration['ROOT']['displayCond'],
+ $flexFormRowData,
+ true
+ );
+ if (!$displayConditionValid) {
unset($result['processedTca']['columns'][$columnName]['config']['ds']['sheets'][$sheetName]);
}
}
if ($key === 'el' && is_array($value)) {
$newSubStructure = [];
foreach ($value as $subKey => $subValue) {
- if (!isset($subValue['displayCond']) || $this->evaluateDisplayCondition($subValue['displayCond'], $flexFormRowData, true)) {
+ if (!isset($subValue['displayCond']) || $this->getDisplayConditionEvaluator()->evaluateDisplayCondition($subValue['displayCond'], $flexFormRowData, true)) {
$newSubStructure[$subKey] = $subValue;
}
}
}
/**
- * Evaluates the provided condition and returns TRUE if the form
- * element should be displayed.
- *
- * The condition string is separated by colons and the first part
- * indicates what type of evaluation should be performed.
- *
- * @param string $displayCondition
- * @param array $record
- * @param bool $flexformContext
- * @param int $recursionLevel Internal level of recursion
- * @return bool TRUE if condition evaluates successfully
- */
- protected function evaluateDisplayCondition($displayCondition, array $record = [], $flexformContext = false, $recursionLevel = 0)
- {
- if ($recursionLevel > 99) {
- // This should not happen, treat as misconfiguration
- return true;
- }
- if (!is_array($displayCondition)) {
- // DisplayCondition is not an array - just get its value
- $result = $this->evaluateSingleDisplayCondition($displayCondition, $record, $flexformContext);
- } else {
- // Multiple conditions given as array ('AND|OR' => condition array)
- $conditionEvaluations = [
- 'AND' => [],
- 'OR' => [],
- ];
- foreach ($displayCondition as $logicalOperator => $groupedDisplayConditions) {
- $logicalOperator = strtoupper($logicalOperator);
- if (($logicalOperator !== 'AND' && $logicalOperator !== 'OR') || !is_array($groupedDisplayConditions)) {
- // Invalid line. Skip it.
- continue;
- } else {
- foreach ($groupedDisplayConditions as $key => $singleDisplayCondition) {
- $key = strtoupper($key);
- if (($key === 'AND' || $key === 'OR') && is_array($singleDisplayCondition)) {
- // Recursion statement: condition is 'AND' or 'OR' and is pointing to an array (should be conditions again)
- $conditionEvaluations[$logicalOperator][] = $this->evaluateDisplayCondition(
- [$key => $singleDisplayCondition],
- $record,
- $flexformContext,
- $recursionLevel + 1
- );
- } else {
- // Condition statement: collect evaluation of this single condition.
- $conditionEvaluations[$logicalOperator][] = $this->evaluateSingleDisplayCondition(
- $singleDisplayCondition,
- $record,
- $flexformContext
- );
- }
- }
- }
- }
- if (!empty($conditionEvaluations['OR']) && in_array(true, $conditionEvaluations['OR'], true)) {
- // There are OR conditions and at least one of them is TRUE
- $result = true;
- } elseif (!empty($conditionEvaluations['AND']) && !in_array(false, $conditionEvaluations['AND'], true)) {
- // There are AND conditions and none of them is FALSE
- $result = true;
- } elseif (!empty($conditionEvaluations['OR']) || !empty($conditionEvaluations['AND'])) {
- // There are some conditions. But no OR was TRUE and at least one AND was FALSE
- $result = false;
- } else {
- // There are no proper conditions - misconfiguration. Return TRUE.
- $result = true;
- }
- }
- return $result;
- }
-
- /**
- * Evaluates the provided condition and returns TRUE if the form
- * element should be displayed.
- *
- * The condition string is separated by colons and the first part
- * indicates what type of evaluation should be performed.
- *
- * @param string $displayCondition
- * @param array $record
- * @param bool $flexformContext
- * @return bool
- * @see evaluateDisplayCondition()
- */
- protected function evaluateSingleDisplayCondition($displayCondition, array $record = [], $flexformContext = false)
- {
- $result = false;
- list($matchType, $condition) = explode(':', $displayCondition, 2);
- switch ($matchType) {
- case 'FIELD':
- $result = $this->matchFieldCondition($condition, $record, $flexformContext);
- break;
- case 'HIDE_FOR_NON_ADMINS':
- $result = $this->matchHideForNonAdminsCondition();
- break;
- case 'REC':
- $result = $this->matchRecordCondition($condition, $record);
- break;
- case 'VERSION':
- $result = $this->matchVersionCondition($condition, $record);
- break;
- case 'USER':
- $result = $this->matchUserCondition($condition, $record);
- break;
- }
- return $result;
- }
-
- /**
- * Evaluates conditions concerning a field of the current record.
- * Requires a record set via ->setRecord()
- *
- * Example:
- * "FIELD:sys_language_uid:>:0" => TRUE, if the field 'sys_language_uid' is greater than 0
- *
- * @param string $condition
- * @param array $record
- * @param bool $flexformContext
- * @return bool
- */
- protected function matchFieldCondition($condition, $record, $flexformContext = false)
- {
- list($fieldName, $operator, $operand) = explode(':', $condition, 3);
- if ($flexformContext) {
- if (strpos($fieldName, 'parentRec.') !== false) {
- $fieldNameParts = explode('.', $fieldName, 2);
- $fieldValue = $record['parentRec'][$fieldNameParts[1]];
- } else {
- $fieldValue = $record[$fieldName]['vDEF'];
- }
- } else {
- $fieldValue = $record[$fieldName];
- }
- $result = false;
- switch ($operator) {
- case 'REQ':
- if (is_array($fieldValue) && count($fieldValue) <= 1) {
- $fieldValue = array_shift($fieldValue);
- }
- if (strtoupper($operand) === 'TRUE') {
- $result = (bool)$fieldValue;
- } else {
- $result = !$fieldValue;
- }
- break;
- case '>':
- if (is_array($fieldValue) && count($fieldValue) <= 1) {
- $fieldValue = array_shift($fieldValue);
- }
- $result = $fieldValue > $operand;
- break;
- case '<':
- if (is_array($fieldValue) && count($fieldValue) <= 1) {
- $fieldValue = array_shift($fieldValue);
- }
- $result = $fieldValue < $operand;
- break;
- case '>=':
- if (is_array($fieldValue) && count($fieldValue) <= 1) {
- $fieldValue = array_shift($fieldValue);
- }
- $result = $fieldValue >= $operand;
- break;
- case '<=':
- if (is_array($fieldValue) && count($fieldValue) <= 1) {
- $fieldValue = array_shift($fieldValue);
- }
- $result = $fieldValue <= $operand;
- break;
- case '-':
- case '!-':
- if (is_array($fieldValue) && count($fieldValue) <= 1) {
- $fieldValue = array_shift($fieldValue);
- }
- list($minimum, $maximum) = explode('-', $operand);
- $result = $fieldValue >= $minimum && $fieldValue <= $maximum;
- if ($operator[0] === '!') {
- $result = !$result;
- }
- break;
- case '=':
- case '!=':
- if (is_array($fieldValue) && count($fieldValue) <= 1) {
- $fieldValue = array_shift($fieldValue);
- }
- $result = $fieldValue == $operand;
- if ($operator[0] === '!') {
- $result = !$result;
- }
- break;
- case 'IN':
- case '!IN':
- if (is_array($fieldValue)) {
- $result = count(array_intersect($fieldValue, explode(',', $operand))) > 0;
- } else {
- $result = GeneralUtility::inList($operand, $fieldValue);
- }
- if ($operator[0] === '!') {
- $result = !$result;
- }
- break;
- case 'BIT':
- case '!BIT':
- $result = (bool)((int)$fieldValue & $operand);
- if ($operator[0] === '!') {
- $result = !$result;
- }
- break;
- }
- return $result;
- }
-
- /**
- * Evaluates TRUE if current backend user is an admin.
- *
- * @return bool
- */
- protected function matchHideForNonAdminsCondition()
- {
- return (bool)$this->getBackendUser()->isAdmin();
- }
-
- /**
- * Evaluates conditions concerning the status of the current record.
- * Requires a record set via ->setRecord()
- *
- * Example:
- * "REC:NEW:FALSE" => TRUE, if the record is already persisted (has a uid > 0)
- *
- * @param string $condition
- * @param array $record
- * @return bool
- */
- protected function matchRecordCondition($condition, $record)
- {
- $result = false;
- list($operator, $operand) = explode(':', $condition, 2);
- if ($operator === 'NEW') {
- if (strtoupper($operand) === 'TRUE') {
- $result = !((int)$record['uid'] > 0);
- } elseif (strtoupper($operand) === 'FALSE') {
- $result = ((int)$record['uid'] > 0);
- }
- }
- return $result;
- }
-
- /**
- * Evaluates whether the current record is versioned.
- * Requires a record set via ->setRecord()
- *
- * @param string $condition
- * @param array $record
- * @return bool
- */
- protected function matchVersionCondition($condition, $record)
- {
- $result = false;
- list($operator, $operand) = explode(':', $condition, 2);
- if ($operator === 'IS') {
- $isNewRecord = !((int)$record['uid'] > 0);
- // Detection of version can be done be detecting the workspace of the user
- $isUserInWorkspace = $this->getBackendUser()->workspace > 0;
- if ((int)$record['pid'] === -1 || (int)$record['_ORIG_pid'] === -1) {
- $isRecordDetectedAsVersion = true;
- } else {
- $isRecordDetectedAsVersion = false;
- }
- // New records in a workspace are not handled as a version record
- // if it's no new version, we detect versions like this:
- // -- if user is in workspace: always TRUE
- // -- if editor is in live ws: only TRUE if pid == -1
- $isVersion = ($isUserInWorkspace || $isRecordDetectedAsVersion) && !$isNewRecord;
- if (strtoupper($operand) === 'TRUE') {
- $result = $isVersion;
- } elseif (strtoupper($operand) === 'FALSE') {
- $result = !$isVersion;
- }
- }
- return $result;
- }
-
- /**
- * Evaluates via the referenced user-defined method
- *
- * @param string $condition
- * @param array $record
- * @return bool
- */
- protected function matchUserCondition($condition, $record)
- {
- $conditionParameters = explode(':', $condition);
- $userFunction = array_shift($conditionParameters);
-
- $parameter = [
- 'record' => $record,
- 'flexformValueKey' => 'vDEF',
- 'conditionParameters' => $conditionParameters
- ];
-
- return (bool)GeneralUtility::callUserFunction($userFunction, $parameter, $this);
- }
-
- /**
- * Get current backend user
+ * Returns the DisplayConditionEvaluator utility.
*
- * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+ * @return DisplayConditionEvaluator
*/
- protected function getBackendUser()
+ protected function getDisplayConditionEvaluator()
{
- return $GLOBALS['BE_USER'];
+ return GeneralUtility::makeInstance(DisplayConditionEvaluator::class);
}
}
--- /dev/null
+<?php
+namespace TYPO3\CMS\Backend\Form\Utility;
+
+/*
+ * 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\SingletonInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Utility class for evaluating TCA display conditions.
+ */
+class DisplayConditionEvaluator implements SingletonInterface
+{
+ /**
+ * Evaluates the provided condition and returns TRUE if the form
+ * element should be displayed.
+ *
+ * The condition string is separated by colons and the first part
+ * indicates what type of evaluation should be performed.
+ *
+ * @param string $displayCondition
+ * @param array $record
+ * @param bool $flexformContext
+ * @param int $recursionLevel Internal level of recursion
+ * @return bool TRUE if condition evaluates successfully
+ */
+ public function evaluateDisplayCondition($displayCondition, array $record = [], $flexformContext = false, $recursionLevel = 0)
+ {
+ if ($recursionLevel > 99) {
+ // This should not happen, treat as misconfiguration
+ return true;
+ }
+ if (!is_array($displayCondition)) {
+ // DisplayCondition is not an array - just get its value
+ $result = $this->evaluateSingleDisplayCondition($displayCondition, $record, $flexformContext);
+ } else {
+ // Multiple conditions given as array ('AND|OR' => condition array)
+ $conditionEvaluations = [
+ 'AND' => [],
+ 'OR' => [],
+ ];
+ foreach ($displayCondition as $logicalOperator => $groupedDisplayConditions) {
+ $logicalOperator = strtoupper($logicalOperator);
+ if (($logicalOperator !== 'AND' && $logicalOperator !== 'OR') || !is_array($groupedDisplayConditions)) {
+ // Invalid line. Skip it.
+ continue;
+ } else {
+ foreach ($groupedDisplayConditions as $key => $singleDisplayCondition) {
+ $key = strtoupper($key);
+ if (($key === 'AND' || $key === 'OR') && is_array($singleDisplayCondition)) {
+ // Recursion statement: condition is 'AND' or 'OR' and is pointing to an array (should be conditions again)
+ $conditionEvaluations[$logicalOperator][] = $this->evaluateDisplayCondition(
+ [$key => $singleDisplayCondition],
+ $record,
+ $flexformContext,
+ $recursionLevel + 1
+ );
+ } else {
+ // Condition statement: collect evaluation of this single condition.
+ $conditionEvaluations[$logicalOperator][] = $this->evaluateSingleDisplayCondition(
+ $singleDisplayCondition,
+ $record,
+ $flexformContext
+ );
+ }
+ }
+ }
+ }
+ if (!empty($conditionEvaluations['OR']) && in_array(true, $conditionEvaluations['OR'], true)) {
+ // There are OR conditions and at least one of them is TRUE
+ $result = true;
+ } elseif (!empty($conditionEvaluations['AND']) && !in_array(false, $conditionEvaluations['AND'], true)) {
+ // There are AND conditions and none of them is FALSE
+ $result = true;
+ } elseif (!empty($conditionEvaluations['OR']) || !empty($conditionEvaluations['AND'])) {
+ // There are some conditions. But no OR was TRUE and at least one AND was FALSE
+ $result = false;
+ } else {
+ // There are no proper conditions - misconfiguration. Return TRUE.
+ $result = true;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Evaluates the provided condition and returns TRUE if the form
+ * element should be displayed.
+ *
+ * The condition string is separated by colons and the first part
+ * indicates what type of evaluation should be performed.
+ *
+ * @param string $displayCondition
+ * @param array $record
+ * @param bool $flexformContext
+ * @return bool
+ * @see evaluateDisplayCondition()
+ */
+ protected function evaluateSingleDisplayCondition($displayCondition, array $record = [], $flexformContext = false)
+ {
+ $result = false;
+ list($matchType, $condition) = explode(':', $displayCondition, 2);
+ switch ($matchType) {
+ case 'FIELD':
+ $result = $this->matchFieldCondition($condition, $record, $flexformContext);
+ break;
+ case 'HIDE_FOR_NON_ADMINS':
+ $result = $this->matchHideForNonAdminsCondition();
+ break;
+ case 'REC':
+ $result = $this->matchRecordCondition($condition, $record);
+ break;
+ case 'VERSION':
+ $result = $this->matchVersionCondition($condition, $record);
+ break;
+ case 'USER':
+ $result = $this->matchUserCondition($condition, $record);
+ break;
+ }
+ return $result;
+ }
+
+ /**
+ * Evaluates conditions concerning a field of the current record.
+ * Requires a record set via ->setRecord()
+ *
+ * Example:
+ * "FIELD:sys_language_uid:>:0" => TRUE, if the field 'sys_language_uid' is greater than 0
+ *
+ * @param string $condition
+ * @param array $record
+ * @param bool $flexformContext
+ * @return bool
+ */
+ protected function matchFieldCondition($condition, $record, $flexformContext = false)
+ {
+ list($fieldName, $operator, $operand) = explode(':', $condition, 3);
+ if ($flexformContext) {
+ if (strpos($fieldName, 'parentRec.') !== false) {
+ $fieldNameParts = explode('.', $fieldName, 2);
+ $fieldValue = $record['parentRec'][$fieldNameParts[1]];
+ } else {
+ $fieldValue = $record[$fieldName]['vDEF'];
+ }
+ } else {
+ $fieldValue = $record[$fieldName];
+ }
+ $result = false;
+ switch ($operator) {
+ case 'REQ':
+ if (is_array($fieldValue) && count($fieldValue) <= 1) {
+ $fieldValue = array_shift($fieldValue);
+ }
+ if (strtoupper($operand) === 'TRUE') {
+ $result = (bool)$fieldValue;
+ } else {
+ $result = !$fieldValue;
+ }
+ break;
+ case '>':
+ if (is_array($fieldValue) && count($fieldValue) <= 1) {
+ $fieldValue = array_shift($fieldValue);
+ }
+ $result = $fieldValue > $operand;
+ break;
+ case '<':
+ if (is_array($fieldValue) && count($fieldValue) <= 1) {
+ $fieldValue = array_shift($fieldValue);
+ }
+ $result = $fieldValue < $operand;
+ break;
+ case '>=':
+ if (is_array($fieldValue) && count($fieldValue) <= 1) {
+ $fieldValue = array_shift($fieldValue);
+ }
+ $result = $fieldValue >= $operand;
+ break;
+ case '<=':
+ if (is_array($fieldValue) && count($fieldValue) <= 1) {
+ $fieldValue = array_shift($fieldValue);
+ }
+ $result = $fieldValue <= $operand;
+ break;
+ case '-':
+ case '!-':
+ if (is_array($fieldValue) && count($fieldValue) <= 1) {
+ $fieldValue = array_shift($fieldValue);
+ }
+ list($minimum, $maximum) = explode('-', $operand);
+ $result = $fieldValue >= $minimum && $fieldValue <= $maximum;
+ if ($operator[0] === '!') {
+ $result = !$result;
+ }
+ break;
+ case '=':
+ case '!=':
+ if (is_array($fieldValue) && count($fieldValue) <= 1) {
+ $fieldValue = array_shift($fieldValue);
+ }
+ $result = $fieldValue == $operand;
+ if ($operator[0] === '!') {
+ $result = !$result;
+ }
+ break;
+ case 'IN':
+ case '!IN':
+ if (is_array($fieldValue)) {
+ $result = count(array_intersect($fieldValue, explode(',', $operand))) > 0;
+ } else {
+ $result = GeneralUtility::inList($operand, $fieldValue);
+ }
+ if ($operator[0] === '!') {
+ $result = !$result;
+ }
+ break;
+ case 'BIT':
+ case '!BIT':
+ $result = (bool)((int)$fieldValue & $operand);
+ if ($operator[0] === '!') {
+ $result = !$result;
+ }
+ break;
+ }
+ return $result;
+ }
+
+ /**
+ * Evaluates TRUE if current backend user is an admin.
+ *
+ * @return bool
+ */
+ protected function matchHideForNonAdminsCondition()
+ {
+ return (bool)$this->getBackendUser()->isAdmin();
+ }
+
+ /**
+ * Evaluates conditions concerning the status of the current record.
+ * Requires a record set via ->setRecord()
+ *
+ * Example:
+ * "REC:NEW:FALSE" => TRUE, if the record is already persisted (has a uid > 0)
+ *
+ * @param string $condition
+ * @param array $record
+ * @return bool
+ */
+ protected function matchRecordCondition($condition, $record)
+ {
+ $result = false;
+ list($operator, $operand) = explode(':', $condition, 2);
+ if ($operator === 'NEW') {
+ if (strtoupper($operand) === 'TRUE') {
+ $result = !((int)$record['uid'] > 0);
+ } elseif (strtoupper($operand) === 'FALSE') {
+ $result = ((int)$record['uid'] > 0);
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Evaluates whether the current record is versioned.
+ * Requires a record set via ->setRecord()
+ *
+ * @param string $condition
+ * @param array $record
+ * @return bool
+ */
+ protected function matchVersionCondition($condition, $record)
+ {
+ $result = false;
+ list($operator, $operand) = explode(':', $condition, 2);
+ if ($operator === 'IS') {
+ $isNewRecord = !((int)$record['uid'] > 0);
+ // Detection of version can be done be detecting the workspace of the user
+ $isUserInWorkspace = $this->getBackendUser()->workspace > 0;
+ if ((int)$record['pid'] === -1 || (int)$record['_ORIG_pid'] === -1) {
+ $isRecordDetectedAsVersion = true;
+ } else {
+ $isRecordDetectedAsVersion = false;
+ }
+ // New records in a workspace are not handled as a version record
+ // if it's no new version, we detect versions like this:
+ // -- if user is in workspace: always TRUE
+ // -- if editor is in live ws: only TRUE if pid == -1
+ $isVersion = ($isUserInWorkspace || $isRecordDetectedAsVersion) && !$isNewRecord;
+ if (strtoupper($operand) === 'TRUE') {
+ $result = $isVersion;
+ } elseif (strtoupper($operand) === 'FALSE') {
+ $result = !$isVersion;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Evaluates via the referenced user-defined method
+ *
+ * @param string $condition
+ * @param array $record
+ * @return bool
+ */
+ protected function matchUserCondition($condition, $record)
+ {
+ $conditionParameters = explode(':', $condition);
+ $userFunction = array_shift($conditionParameters);
+
+ $parameter = [
+ 'record' => $record,
+ 'flexformValueKey' => 'vDEF',
+ 'conditionParameters' => $conditionParameters
+ ];
+
+ return (bool)GeneralUtility::callUserFunction($userFunction, $parameter, $this);
+ }
+
+ /**
+ * Get current backend user
+ *
+ * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+ */
+ protected function getBackendUser()
+ {
+ return $GLOBALS['BE_USER'];
+ }
+}
* The TYPO3 project - inspiring people to share!
*/
+use TYPO3\CMS\Backend\Form\Utility\DisplayConditionEvaluator;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
}
/**
- * Returns a list of category fields for a given table for populating selector "category_field"
- * in tt_content table (called as itemsProcFunc).
+ * Returns a list of category fields for the table configured in the categoryFieldsTable setting.
+ * For use in an itemsProcFunc of a TCA select field.
*
- * @param array $configuration Current field configuration
- * @throws \UnexpectedValueException
+ * @param array $configuration The TCA and row arrays passed to the itemsProcFunc.
* @return void
*/
- public function getCategoryFieldsForTable(array &$configuration)
+ public function getCategoryFieldItems(array &$configuration)
{
- $table = '';
- $menuType = isset($configuration['row']['menu_type'][0]) ? $configuration['row']['menu_type'][0] : '';
- // Define the table being looked up from the type of menu
- if ($menuType === 'categorized_pages') {
- $table = 'pages';
- } elseif ($menuType === 'categorized_content') {
- $table = 'tt_content';
- }
- // Return early if no table is defined
- if (empty($table)) {
- throw new \UnexpectedValueException('The given menu_type is not supported.', 1381823570);
- }
+ $table = $this->getActiveCategoryFieldsTable($configuration);
// Loop on all registries and find entries for the correct table
foreach ($this->registry as $tableName => $fields) {
if ($tableName === $table) {
}
/**
+ * Tries to determine of which table the category fields should be collected.
+ * It looks in the categoryFieldsTable TCA entry in the config section of the current field.
+ *
+ * It is possible to pass a plain string with a table name or an array of table names
+ * that can be activated with an active condition. There must exactly be one active
+ * table at once. A possible array configuration might look like this:
+ *
+ * 'categoryFieldsTable' => array(
+ * 'categorized_pages' => array(
+ * 'table' => 'pages',
+ * 'activeCondition' => 'FIELD:menu_type:=:categorized_pages'
+ * ),
+ * 'categorized_content' => array(
+ * 'table' => 'tt_content',
+ * 'activeCondition' => 'FIELD:menu_type:=:categorized_content'
+ * )
+ * ),
+ *
+ * @param array $configuration The TCA and row arrays passed to the itemsProcFunc.
+ * @throws \RuntimeException In case of an invalid configuration.
+ * @return string
+ */
+ protected function getActiveCategoryFieldsTable(array $configuration)
+ {
+ $fieldAndTableInfo = sprintf(' (field: %s, table: %s)', $configuration['field'], $configuration['table']);
+
+ if (empty($configuration['config']['categoryFieldsTable'])) {
+ throw new \RuntimeException(
+ 'The categoryFieldsTable setting is missing in the config section' . $fieldAndTableInfo,
+ 1447273908
+ );
+ }
+
+ if (is_string($configuration['config']['categoryFieldsTable'])) {
+ return $configuration['config']['categoryFieldsTable'];
+ }
+
+ if (!is_array($configuration['config']['categoryFieldsTable'])) {
+ throw new \RuntimeException(
+ sprintf(
+ 'The categoryFieldsTable table setting must be a string or an array, %s given' . $fieldAndTableInfo,
+ gettype($configuration['config']['categoryFieldsTable'])
+ ),
+ 1447274126
+ );
+ }
+
+ $activeTable = null;
+
+ foreach ($configuration['config']['categoryFieldsTable'] as $configKey => $tableConfig) {
+ if (empty($tableConfig['table'])) {
+ throw new \RuntimeException(
+ sprintf(
+ 'The table setting is missing for the categoryFieldsTable %s' . $fieldAndTableInfo,
+ $configKey
+ ),
+ 1447274131
+ );
+ }
+ if (empty($tableConfig['activeCondition'])) {
+ throw new \RuntimeException(
+ sprintf(
+ 'The activeCondition setting is missing for the categoryFieldsTable %s' . $fieldAndTableInfo,
+ $configKey
+ ),
+ 1480786868
+ );
+ }
+
+ if ($this->getDisplayConditionEvaluator()->evaluateDisplayCondition(
+ $tableConfig['activeCondition'],
+ $configuration['row']
+ )
+ ) {
+ if (!empty($activeTable)) {
+ throw new \RuntimeException(
+ sprintf(
+ 'There must only be one active categoryFieldsTable. Multiple active tables (%s, %s) '
+ . 'were found' . $fieldAndTableInfo,
+ $activeTable,
+ $tableConfig['table']
+ ),
+ 1480787321
+ );
+ }
+ $activeTable = $tableConfig['table'];
+ }
+ }
+
+ if (empty($activeTable)) {
+ throw new \RuntimeException('No active was found' . $fieldAndTableInfo, 1447274507);
+ }
+
+ return $activeTable;
+ }
+
+ /**
+ * Returns the display condition evaluator utility class.
+ *
+ * @return DisplayConditionEvaluator
+ */
+ protected function getDisplayConditionEvaluator()
+ {
+ return GeneralUtility::makeInstance(DisplayConditionEvaluator::class);
+ }
+
+ /**
* Tells whether a table has a category configuration in the registry.
*
* @param string $tableName Name of the table to be looked up
--- /dev/null
+.. include:: ../../Includes.txt
+
+===================================================================================
+Breaking: #53045 - getCategoryFieldsForTable() method removed from CategoryRegistry
+===================================================================================
+
+See :issue:`53045`
+
+Description
+===========
+
+The method :php:`getCategoryFieldsForTable()` is removed from the :php:`\TYPO3\CMS\Core\Category\CategoryRegistry`
+class.
+
+It could only handle the `tt_content` menus `categorized_pages` and `categorized_content`.
+
+
+Impact
+======
+
+The method :php:`getCategoryFieldsForTable()` is removed. Any third party code that uses it will break.
+
+
+Affected Installations
+======================
+
+All installations with third party code making using the removed method.
+
+
+Migration
+=========
+
+A new method :php:`getCategoryFieldItems()` is added that can be used by third party code for any
+categorized table.
+
+.. index:: Backend, PHP-API, TCA
--- /dev/null
+.. include:: ../../Includes.txt
+
+================================================================================
+Feature: #53045 - Categorized fields for table itemsProcFunc in CategoryRegistry
+================================================================================
+
+See :issue:`53045`
+
+Description
+===========
+
+A new method :php:`getCategoryFieldItems()` is added to the :php:`\TYPO3\CMS\Core\Category\CategoryRegistry` class.
+
+This method can be used as an `itemsProcFunc` in TCA and returns a list of all categorized fields of a table.
+
+The table for which the categorized fields should be returned can be specified in two ways.
+
+Static table
+------------
+
+You can provide a static table name in the config of your TCA field:
+
+.. code-block:: php
+
+ 'itemsProcFunc' => \TYPO3\CMS\Core\Category\CategoryRegistry::class . '->getCategoryFieldItems',
+ 'categoryFieldsTable' => 'my_table_name',
+
+
+Dynamic table selection
+-----------------------
+
+You can also provide a list of tables. The active table can be selected by using a display condition:
+
+.. code-block:: php
+
+ 'itemsProcFunc' => \TYPO3\CMS\Core\Category\CategoryRegistry::class . '->getCategoryFieldItems',
+ 'categoryFieldsTable' => [
+ 'categorized_pages' => [
+ 'table' => 'pages',
+ 'activeCondition' => 'FIELD:menu_type:=:categorized_pages'
+ ],
+ 'categorized_content' => [
+ 'table' => 'tt_content',
+ 'activeCondition' => 'FIELD:menu_type:=:categorized_content'
+ ]
+ ]
+
+
+Impact
+======
+
+The method :php:`getCategoryFieldsForTable()` is removed. It could only handle the `tt_content` menus
+`categorized_pages` and `categorized_content`.
+
+A new method :php:`getCategoryFieldItems()` is added that can be used by third party code for any
+categorized table.
+
+.. index:: Backend, PHP-API, TCA
*
* The TYPO3 project - inspiring people to share!
*/
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Lang\LanguageService;
/**
* Testcase for CategoryRegistry
$sqlData = $this->subject->addExtensionCategoryDatabaseSchemaToTablesDefinition([], 'text_extension_a');
$this->assertEmpty($sqlData['sqlString'][0]);
}
+
+ /**
+ * @test
+ */
+ public function getCategoryFieldItemsReturnsFieldsForStaticTable()
+ {
+ $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageService::class);
+ $this->subject->add('text_extension_a', $this->tables['first']);
+ $this->subject->add('text_extension_a', $this->tables['first'], 'categories2');
+ $configuration = [
+ 'config' => [
+ 'categoryFieldsTable' => $this->tables['first']
+ ]
+ ];
+ $this->subject->getCategoryFieldItems($configuration);
+ $this->assertEquals([['Categories', 'categories'], ['Categories', 'categories2']], $configuration['items']);
+ }
+
+ /**
+ * @test
+ */
+ public function getCategoryFieldItemsReturnsFieldsForDynamicTables()
+ {
+ $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageService::class);
+ $this->subject->add('text_extension_a', $this->tables['first']);
+ $this->subject->add('text_extension_a', $this->tables['first'], 'categories2');
+ $configuration = [
+ 'row' => [
+ 'menu_type' => 'categorized_pages',
+ ],
+ 'config' => [
+ 'categoryFieldsTable' => [
+ [
+ 'table' => 'othertable',
+ 'activeCondition' => 'FIELD:menu_type:=:categorized_content',
+ ],
+ [
+ 'table' => $this->tables['first'],
+ 'activeCondition' => 'FIELD:menu_type:=:categorized_pages',
+ ],
+ ],
+ ],
+ ];
+ $this->subject->getCategoryFieldItems($configuration);
+ $this->assertEquals([['Categories', 'categories'], ['Categories', 'categories2']], $configuration['items']);
+ }
+
+ /**
+ * @test
+ */
+ public function getCategoryFieldItemsThrowsExceptionIfConfigMissing()
+ {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionCode(1447273908);
+ $configuration = [];
+ $this->subject->getCategoryFieldItems($configuration);
+ }
+
+ /**
+ * @test
+ */
+ public function getCategoryFieldItemsThrowsExceptionIfTypeIsInvalid()
+ {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionCode(1447274126);
+ $configuration = [
+ 'config' => [
+ 'categoryFieldsTable' => new \stdClass()
+ ]
+ ];
+ $this->subject->getCategoryFieldItems($configuration);
+ }
+
+ /**
+ * @test
+ */
+ public function getCategoryFieldItemsThrowsExceptionIfTableIsMissing()
+ {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionCode(1447274131);
+ $configuration = [
+ 'config' => [
+ 'categoryFieldsTable' => [['activeCondition' => 'TRUE']]
+ ]
+ ];
+ $this->subject->getCategoryFieldItems($configuration);
+ }
+
+ /**
+ * @test
+ */
+ public function getCategoryFieldItemsThrowsExceptionIfActiveConditionIsMissing()
+ {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionCode(1480786868);
+ $configuration = [
+ 'config' => [
+ 'categoryFieldsTable' => [['table' => 'testtable']]
+ ]
+ ];
+ $this->subject->getCategoryFieldItems($configuration);
+ }
+
+ /**
+ * @test
+ */
+ public function getCategoryFieldItemsThrowsExceptionIfNoActiveTableIsFound()
+ {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionCode(1447274507);
+ $configuration = [
+ 'row' => [],
+ 'config' => [
+ 'categoryFieldsTable' => [
+ [
+ 'table' => 'testtable',
+ 'activeCondition' => 'FALSE',
+ ]
+ ],
+ ]
+ ];
+ $this->subject->getCategoryFieldItems($configuration);
+ }
+
+ /**
+ * @test
+ */
+ public function getCategoryFieldItemsThrowsExceptionIfMultipleTablesAreActive()
+ {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionCode(1480787321);
+ $configuration = [
+ 'row' => [
+ 'menu_type' => 'categorized_pages',
+ ],
+ 'config' => [
+ 'categoryFieldsTable' => [
+ [
+ 'table' => 'testtable',
+ 'activeCondition' => 'FIELD:menu_type:=:categorized_pages'
+ ],
+ [
+ 'table' => 'testtable2',
+ 'activeCondition' => 'FIELD:menu_type:=:categorized_pages'
+ ]
+ ],
+ ]
+ ];
+ $this->subject->getCategoryFieldItems($configuration);
+ }
}
'size' => 1,
'minitems' => 0,
'maxitems' => 1,
- 'itemsProcFunc' => \TYPO3\CMS\Core\Category\CategoryRegistry::class . '->getCategoryFieldsForTable',
+ 'itemsProcFunc' => \TYPO3\CMS\Core\Category\CategoryRegistry::class . '->getCategoryFieldItems',
+ 'categoryFieldsTable' => [
+ 'categorized_pages' => [
+ 'table' => 'pages',
+ 'activeCondition' => 'FIELD:menu_type:=:categorized_pages'
+ ],
+ 'categorized_content' => [
+ 'table' => 'tt_content',
+ 'activeCondition' => 'FIELD:menu_type:=:categorized_content'
+ ]
+ ]
]
],
'table_caption' => [