diff --git a/typo3/sysext/backend/Classes/Form/Container/OuterWrapContainer.php b/typo3/sysext/backend/Classes/Form/Container/OuterWrapContainer.php index b3aed3cce2c49dfa2a235e34817723cdcaaa7a40..9aeb130bd1a0869ca9aa48853d2c45d577a947c1 100644 --- a/typo3/sysext/backend/Classes/Form/Container/OuterWrapContainer.php +++ b/typo3/sysext/backend/Classes/Form/Container/OuterWrapContainer.php @@ -92,7 +92,8 @@ class OuterWrapContainer extends AbstractContainer { $newOrUid = ' <span class="typo3-TCEforms-recUid">[' . htmlspecialchars($row['uid']) . ']</span>'; - $recordLabel = BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($row), TRUE, FALSE); + // @todo: getRecordTitlePrep applies an htmlspecialchars here + $recordLabel = BackendUtility::getRecordTitlePrep($this->data['recordTitle']); if ($table === 'pages') { $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.editPage', TRUE); $pageTitle = sprintf($label, $tableTitle, $recordLabel); @@ -100,13 +101,13 @@ class OuterWrapContainer extends AbstractContainer { $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.editRecord', TRUE); $workspacedPageRecord = BackendUtility::getRecordWSOL('pages', $row['pid'], 'uid,title'); $pageTitle = BackendUtility::getRecordTitle('pages', $workspacedPageRecord, TRUE, FALSE); - if ($recordLabel === BackendUtility::getNoRecordTitle(TRUE)) { + if (empty($recordLabel)) { $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.editRecordNoTitle', TRUE); } if ($this->data['effectivePid'] === 0) { $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.editRecordRootLevel', TRUE); } - if ($recordLabel !== BackendUtility::getNoRecordTitle(TRUE)) { + if (!empty($recordLabel)) { // Use record title and prepend an edit label. $pageTitle = sprintf($label, $tableTitle, $recordLabel, $pageTitle); } else { diff --git a/typo3/sysext/backend/Classes/Form/FormDataCompiler.php b/typo3/sysext/backend/Classes/Form/FormDataCompiler.php index 8f4f70dd87cbbbe7147d85594b8e5e0fba38d670..2c6c92776ea820188585b879d4dcc31bfc2a2b62 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataCompiler.php +++ b/typo3/sysext/backend/Classes/Form/FormDataCompiler.php @@ -116,6 +116,8 @@ class FormDataCompiler { 'vanillaUid' => 0, // Url to return to 'returnUrl' => NULL, + // Title of the handled record. + 'recordTitle' => '', // Parent page record is either the full row of the parent page the record is located at or should // be added to, or it is NULL, if a record is added or edited below the root page node. 'parentPageRow' => NULL, diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaRecordTitle.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaRecordTitle.php new file mode 100644 index 0000000000000000000000000000000000000000..687232041e3087a00f857e45cc19922d7624eaa1 --- /dev/null +++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaRecordTitle.php @@ -0,0 +1,326 @@ +<?php +namespace TYPO3\CMS\Backend\Form\FormDataProvider; + +/* + * 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\Backend\Form\FormDataProviderInterface; +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\Database\DatabaseConnection; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Lang\LanguageService; + +/** + * Determine the title of a record and write it to $result['recordTitle']. + * + * TCA ctrl fields like label and label_alt are evaluated and their + * current values from databaseRow used to create the title. + */ +class TcaRecordTitle implements FormDataProviderInterface { + + /** + * Enrich the processed record information with the resolved title + * + * @param array $result Incoming result array + * @return array Modified array + */ + public function addData(array $result) { + if (!isset($result['processedTca']['ctrl']['label'])) { + throw new \UnexpectedValueException( + 'TCA of table ' . $result['tableName'] . ' misses required [\'ctrl\'][\'label\'] definition.', + 1443706103 + ); + } + + if (isset($result['processedTca']['ctrl']['label_userFunc'])) { + // userFunc takes precedence over everything + $parameters = [ + 'table' => $result['tableName'], + 'row' => $result['databaseRow'], + 'title' => '', + 'options' => isset($result['processedTca']['ctrl']['label_userFunc_options']) + ? $result['processedTca']['ctrl']['label_userFunc_options'] + : [], + ]; + $null = NULL; + GeneralUtility::callUserFunction($result['processedTca']['ctrl']['label_userFunc'], $parameters, $null); + $result['recordTitle'] = $parameters['title']; + } else { + $result = $this->getRecordTitleByLabelProperties($result); + } + + return $result; + } + + /** + * Build the record title from label, label_alt and label_alt_force properties + * + * @param array $result Incoming result array + * @return array Modified result array + */ + protected function getRecordTitleByLabelProperties(array $result) { + $titles = []; + $titleByLabel = $this->getRecordTitleForField($result['processedTca']['ctrl']['label'], $result); + if (!empty($titleByLabel)) { + $titles[] = $titleByLabel; + } + + $labelAltForce = isset($result['processedTca']['ctrl']['label_alt_force']) + ? (bool)$result['processedTca']['ctrl']['label_alt_force'] + : FALSE; + if (!empty($result['processedTca']['ctrl']['label_alt']) && ($labelAltForce || empty($titleByLabel))) { + // Dive into label_alt evaluation if label_alt_force is set or if label did not came up with a title yet + $labelAltFields = GeneralUtility::trimExplode(',', $result['processedTca']['ctrl']['label_alt'], TRUE); + foreach ($labelAltFields as $fieldName) { + $titleByLabelAlt = $this->getRecordTitleForField($fieldName, $result); + if (!empty($titleByLabelAlt)) { + $titles[] = $titleByLabelAlt; + } + if (!$labelAltForce && !empty($titleByLabelAlt)) { + // label_alt_force creates a comma separated list of multiple fields. + // If not set, one found field with content is enough + break; + } + } + } + + $result['recordTitle'] = implode(', ', $titles); + return $result; + } + + /** + * Record title of a single field + * + * @param string $fieldName Field to handle + * @param array $result Incoming result array + * @return string + */ + protected function getRecordTitleForField($fieldName, $result) { + if ($fieldName === 'uid') { + // uid return field content directly since it usually has not TCA definition + return $result['databaseRow']['uid']; + } + + if (!isset($result['processedTca']['columns'][$fieldName]['config']['type']) + || !is_string($result['processedTca']['columns'][$fieldName]['config']['type']) + ) { + return ''; + } + + $recordTitle = ''; + $rawValue = NULL; + if (array_key_exists($fieldName, $result['databaseRow'])) { + $rawValue = $result['databaseRow'][$fieldName]; + } + $fieldConfig = $result['processedTca']['columns'][$fieldName]['config']; + switch ($fieldConfig['type']) { + case 'radio': + $recordTitle = $this->getRecordTitleForRadioType($rawValue, $fieldConfig); + break; + case 'inline': + // intentional fall-through + case 'select': + $recordTitle = $this->getRecordTitleForSelectType($rawValue, $fieldConfig); + break; + case 'group': + $recordTitle = $this->getRecordTitleForGroupType($rawValue, $fieldConfig); + break; + case 'check': + $recordTitle = $this->getRecordTitleForCheckboxType($rawValue, $fieldConfig); + break; + case 'input': + $recordTitle = $this->getRecordTitleForInputType($rawValue, $fieldConfig); + break; + case 'text': + $recordTitle = $this->getRecordTitleForTextType($rawValue); + case 'flex': + // TODO: Check if and how a label could be generated from flex field data + default: + + } + + return $recordTitle; + } + + /** + * Return the record title for radio fields + * + * @param mixed $value Current database value of this field + * @param array $fieldConfig TCA field configuration + * @return string + */ + protected function getRecordTitleForRadioType($value, $fieldConfig) { + if (!isset($fieldConfig['items']) || !is_array($fieldConfig['items'])) { + return ''; + } + foreach ($fieldConfig['items'] as $item) { + list($itemLabel, $itemValue) = $item; + if ((string)$value === (string)$itemValue) { + return $itemLabel; + } + } + return ''; + } + + /** + * Return the record title for database records + * + * @param mixed $value Current database value of this field + * @param array $fieldConfig TCA field configuration + * @return string + */ + protected function getRecordTitleForSelectType($value, $fieldConfig) { + if (!is_array($value)) { + return ''; + } + $labelParts = []; + foreach ($value as $itemValue) { + $itemKey = array_search($itemValue, array_column($fieldConfig['items'], 1)); + if ($itemKey !== FALSE) { + $labelParts[] = $fieldConfig['items'][$itemKey][0]; + } + } + $title = implode(', ', $labelParts); + if (empty($title) && !empty($value)) { + $title = implode(', ', $value); + } + return $title; + } + + /** + * Return the record title for database records + * + * @param mixed $value Current database value of this field + * @param array $fieldConfig TCA field configuration + * @return string + */ + protected function getRecordTitleForGroupType($value, $fieldConfig) { + if ($fieldConfig['internal_type'] !== 'db') { + return implode(', ', GeneralUtility::trimExplode(',', $value, TRUE)); + } + $labelParts = array_map( + function($rawLabelItem) { + return array_pop(GeneralUtility::trimExplode('|', $rawLabelItem, TRUE, 2)); + }, + GeneralUtility::trimExplode(',', $value, TRUE) + ); + if (!empty($labelParts)) { + sort($labelParts); + return implode(', ', $labelParts); + } + return ''; + } + + /** + * Returns the record title for checkbox fields + * + * @param mixed $value Current database value of this field + * @param array $fieldConfig TCA field configuration + * @return string + */ + protected function getRecordTitleForCheckboxType($value, $fieldConfig) { + $languageService = $this->getLanguageService(); + if (empty($fieldConfig['items']) || !is_array($fieldConfig['items'])) { + $title = (bool)$value + ? $languageService->sL('LLL:EXT:lang/locallang_common.xlf:yes') + : $languageService->sL('LLL:EXT:lang/locallang_common.xlf:no'); + } else { + $labelParts = []; + foreach ($fieldConfig['items'] as $key => $val) { + if ($value & pow(2, $key)) { + $labelParts[] = $val[0]; + } + } + $title = implode(', ', $labelParts); + } + return $title; + } + + /** + * Returns the record title for input fields + * + * @param mixed $value Current database value of this field + * @param array $fieldConfig TCA field configuration + * @return string + */ + protected function getRecordTitleForInputType($value, $fieldConfig) { + if (!isset($value)) { + return ''; + } + $title = $value; + if (GeneralUtility::inList($fieldConfig['eval'], 'date')) { + if (isset($fieldConfig['dbType']) && $fieldConfig['dbType'] === 'date') { + $value = $value === '0000-00-00' ? 0 : (int)strtotime($value); + } else { + $value = (int)$value; + } + if (!empty($value)) { + $ageSuffix = ''; + // Generate age suffix as long as not explicitly suppressed + if (!isset($fieldConfig['disableAgeDisplay']) || (bool)$fieldConfig['disableAgeDisplay'] === FALSE) { + $ageDelta = $GLOBALS['EXEC_TIME'] - $value; + $calculatedAge = BackendUtility::calcAge( + abs($ageDelta), + $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears') + ); + $ageSuffix = ' (' . ($ageDelta > 0 ? '-' : '') . $calculatedAge . ')'; + } + $title = BackendUtility::date($value) . $ageSuffix; + } + } elseif (GeneralUtility::inList($fieldConfig['eval'], 'time')) { + if (!empty($value)) { + $title = BackendUtility::time((int)$value, FALSE); + } + } elseif (GeneralUtility::inList($fieldConfig['eval'], 'timesec')) { + if (!empty($value)) { + $title = BackendUtility::time((int)$value); + } + } elseif (GeneralUtility::inList($fieldConfig['eval'], 'datetime')) { + // Handle native date/time field + if (isset($fieldConfig['dbType']) && $fieldConfig['dbType'] === 'datetime') { + $value = $value === '0000-00-00 00:00:00' ? 0 : (int)strtotime($value); + } else { + $value = (int)$value; + } + if (!empty($value)) { + $title = BackendUtility::datetime($value); + } + } + return $title; + } + + /** + * Returns the record title for text fields + * + * @param mixed $value Current database value of this field + * @return string + */ + protected function getRecordTitleForTextType($value) { + return trim(strip_tags($value)); + } + + /** + * @return DatabaseConnection + */ + protected function getDatabaseConnection() { + return $GLOBALS['TYPO3_DB']; + } + + /** + * @return LanguageService + */ + protected function getLanguageService() { + return $GLOBALS['LANG']; + } + +} diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaTypesRemoveUnusedColumns.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaTypesRemoveUnusedColumns.php index 51b580bd2531bb0616e134cad3662831fdc73dc0..c0c6a730579cf9b94675686edfcd339223dc14a1 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaTypesRemoveUnusedColumns.php +++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaTypesRemoveUnusedColumns.php @@ -43,7 +43,16 @@ class TcaTypesRemoveUnusedColumns implements FormDataProviderInterface { $showItemFieldString = $result['processedTca']['types'][$recordTypeValue]['showitem']; $showItemFieldArray = GeneralUtility::trimExplode(',', $showItemFieldString, TRUE); - $shownColumnFields = []; + + // Do not remove fields that are used for record title calculation + $shownColumnFields = empty($result['processedTca']['ctrl']['label']) ? [] : [$result['processedTca']['ctrl']['label']]; + if (!empty($result['processedTca']['ctrl']['label_alt'])) { + $shownColumnFields = array_merge( + $shownColumnFields, + GeneralUtility::trimExplode(',', $result['processedTca']['ctrl']['label_alt'], TRUE) + ); + } + foreach ($showItemFieldArray as $fieldConfigurationString) { $fieldConfigurationArray = GeneralUtility::trimExplode(';', $fieldConfigurationString); $fieldName = $fieldConfigurationArray[0]; diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaRecordTitleTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaRecordTitleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..93b290bbbdc598338d41771dc0d231484a38107a --- /dev/null +++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaRecordTitleTest.php @@ -0,0 +1,796 @@ +<?php +namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataProvider; + +/* + * 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 Prophecy\Argument; +use Prophecy\Prophecy\ObjectProphecy; +use TYPO3\CMS\Backend\Form\FormDataProvider\TcaRecordTitle; +use TYPO3\CMS\Core\Tests\UnitTestCase; +use TYPO3\CMS\Lang\LanguageService; + +/** + * Test case + */ +class TcaRecordTitleTest extends UnitTestCase { + + /** + * @var TcaRecordTitle + */ + protected $subject; + + /** + * @var string + */ + protected $timeZone; + + public function setUp() { + $this->subject = new TcaRecordTitle(); + $this->timeZone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + } + + protected function tearDown() { + date_default_timezone_set($this->timeZone); + } + + /** + * @test + */ + public function addDataThrowsExceptionWithMissingLabel() { + $input = [ + 'tableName' => 'aTable', + 'databaseRew' => [], + 'processedTca' => [ + 'ctrl' => [], + ], + ]; + $this->setExpectedException(\UnexpectedValueException::class, $this->any(), 1443706103); + $this->subject->addData($input); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleForLabelUserFunction() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'uid', + 'label_userFunc' => function (&$parameters) { + $parameters['title'] = 'Test'; + } + ], + 'columns' => [], + ], + ]; + + $expected = $input; + $expected['recordTitle'] = 'Test'; + + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleForUid() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'uid' => 'NEW56017ee37d10e587251374', + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'uid' + ], + 'columns' => [], + ] + ]; + + /** @var LanguageService|ObjectProphecy $languageService */ + $languageService = $this->prophesize(LanguageService::class); + $GLOBALS['LANG'] = $languageService->reveal(); + $languageService->sL(Argument::cetera())->willReturnArgument(0); + + $expected = $input; + $expected['recordTitle'] = 'NEW56017ee37d10e587251374'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * Data provider for addDataReturnsRecordTitleForInputType + * Each data set is an array with the following elements: + * - TCA field ['config'] section + * - Database value for field + * - expected title to be generated + * + * @returns array + */ + public function addDataReturnsRecordTitleForInputTypeDataProvider() { + return [ + 'new record' => [ + [ + 'type' => 'input', + ], + '', + '', + ], + 'plain text input' => [ + [ + 'type' => 'input', + ], + 'aValue', + 'aValue', + ], + 'date input' => [ + [ + 'type' => 'input', + 'eval' => 'date' + ], + '978307261', + '01-01-01 (-7 days)', + ], + 'date input (dbType: date)' => [ + [ + 'type' => 'input', + 'eval' => 'date', + 'dbType' => 'date' + ], + '2001-01-01', + '01-01-01 (-7 days)', + ], + 'date input (disableAgeDisplay: TRUE)' => [ + [ + 'type' => 'input', + 'eval' => 'date', + 'disableAgeDisplay' => TRUE + ], + '978307261', + '01-01-01', + ], + 'time input' => [ + [ + 'type' => 'input', + 'eval' => 'time', + ], + '44100', + '12:15', + ], + 'timesec input' => [ + [ + 'type' => 'input', + 'eval' => 'timesec', + ], + '44130', + '12:15:30', + ], + 'datetime input' => [ + [ + 'type' => 'input', + 'eval' => 'datetime', + 'dbType' => 'date' + ], + '978307261', + '01-01-01 00:01', + ], + 'datetime input (dbType: datetime)' => [ + [ + 'type' => 'input', + 'eval' => 'datetime', + 'dbType' => 'datetime' + ], + '2014-12-31 23:59:59', + '31-12-14 23:59', + ], + ]; + } + + /** + * @test + * @dataProvider addDataReturnsRecordTitleForInputTypeDataProvider + * + * @param array $fieldConfig + * @param string $fieldValue + * @param string $expectedTitle + */ + public function addDataReturnsRecordTitleForInputType($fieldConfig, $fieldValue, $expectedTitle) { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'uid' => '1', + 'aField' => $fieldValue, + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField' + ], + 'columns' => [ + 'aField' => [ + 'config' => $fieldConfig, + ] + ], + ] + ]; + + /** @var LanguageService|ObjectProphecy $languageService */ + $languageService = $this->prophesize(LanguageService::class); + $GLOBALS['LANG'] = $languageService->reveal(); + $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears') + ->willReturn(' min| hrs| days| yrs| min| hour| day| year'); + $languageService->sL(Argument::cetera())->willReturnArgument(0); + $GLOBALS['EXEC_TIME'] = 978912061; + + $expected = $input; + $expected['recordTitle'] = $expectedTitle; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleWithAlternativeLabel() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'uid' => '1', + 'aField' => '', + 'anotherField' => 'anotherValue', + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField', + 'label_alt' => 'anotherField', + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + 'anotherField' => [ + 'config' => [ + 'type' => 'input' + ] + ] + ], + ] + ]; + + $expected = $input; + $expected['recordTitle'] = 'anotherValue'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleWithMultipleAlternativeLabels() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'uid' => '1', + 'aField' => '', + 'anotherField' => '', + 'additionalField' => 'additionalValue' + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField', + 'label_alt' => 'anotherField,additionalField', + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + 'anotherField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + 'additionalField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + ], + ] + ]; + + $expected = $input; + $expected['recordTitle'] = 'additionalValue'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleWithForcedAlternativeLabel() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'uid' => '1', + 'aField' => 'aField', + 'anotherField' => 'anotherField' + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField', + 'label_alt' => 'anotherField', + 'label_alt_force' => TRUE, + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + 'anotherField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + ], + ] + ]; + + $expected = $input; + $expected['recordTitle'] = 'aField, anotherField'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleWithMultipleForcedAlternativeLabels() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'uid' => '1', + 'aField' => 'aField', + 'anotherField' => 'anotherField', + 'additionalField' => 'additionalValue' + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField', + 'label_alt' => 'anotherField,additionalField', + 'label_alt_force' => TRUE, + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + 'anotherField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + 'additionalField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + ], + ] + ]; + + $expected = $input; + $expected['recordTitle'] = 'aField, anotherField, additionalValue'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleIgnoresEmptyAlternativeLabels() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'uid' => '1', + 'aField' => 'aField', + 'anotherField' => '', + 'additionalField' => 'additionalValue' + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField', + 'label_alt' => 'anotherField,additionalField', + 'label_alt_force' => TRUE, + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + 'anotherField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + 'additionalField' => [ + 'config' => [ + 'type' => 'input' + ] + ], + ], + ] + ]; + + $expected = $input; + $expected['recordTitle'] = 'aField, additionalValue'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleForRadioType() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'uid' => '1', + 'aField' => '2', + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField' + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'radio', + 'items' => [ + ['foo', 1], + ['bar', 2], + ['baz', 3], + ] + ] + ] + ], + ] + ]; + + $expected = $input; + $expected['recordTitle'] = 'bar'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * Data provider for addDataReturnsRecordTitleForGroupType + * Each data set is an array with the following elements: + * - TCA field configuration (merged with base config) + * - Database value for field + * - expected title to be generated + * + * @returns array + */ + public function addDataReturnsRecordTitleForGroupTypeDataProvider() { + return [ + 'new record' => [ + [ + 'internal_type' => 'db', + ], + '', + '' + ], + 'internal_type: file' => [ + [ + 'internal_type' => 'file', + ], + 'somePath/aFile.jpg,someOtherPath/anotherFile.png', + 'somePath/aFile.jpg, someOtherPath/anotherFile.png', + ], + 'internal_type: db, single table, single record' => [ + [ + 'internal_type' => 'db', + 'allowed' => 'aTable' + ], + '1|aValue', + 'aValue', + ], + 'internal_type: db, single table, multiple records' => [ + [ + 'internal_type' => 'db', + 'allowed' => 'aTable' + ], + '1|aValue,3|anotherValue', + 'aValue, anotherValue', + ], + 'internal_type: db, multiple tables, single record' => [ + [ + 'internal_type' => 'db', + 'allowed' => 'aTable,anotherTable' + ], + 'anotherTable_1|anotherValue', + 'anotherValue', + ], + 'internal_type: db, multiple tables, multiple records' => [ + [ + 'internal_type' => 'db', + 'allowed' => 'aTable,anotherTable' + ], + 'anotherTable_1|anotherValue,aTable_1|aValue', + 'aValue, anotherValue', + ], + ]; + } + + /** + * @test + * @dataProvider addDataReturnsRecordTitleForGroupTypeDataProvider + * + * @param array $fieldConfig + * @param string $fieldValue + * @param string $expectedTitle + */ + public function addDataReturnsRecordTitleForGroupType($fieldConfig, $fieldValue, $expectedTitle) { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'uid' => '1', + 'aField' => $fieldValue, + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField' + ], + 'columns' => [ + 'aField' => [ + 'config' => array_merge( + [ + 'type' => 'group', + ], + $fieldConfig + ), + ] + ], + ] + ]; + + /** @var LanguageService|ObjectProphecy $languageService */ + $languageService = $this->prophesize(LanguageService::class); + $GLOBALS['LANG'] = $languageService->reveal(); + $languageService->sL(Argument::cetera())->willReturnArgument(0); + + $expected = $input; + $expected['recordTitle'] = $expectedTitle; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleForGroupTypeWithInternalTypeDb() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'uid' => '1', + 'aField' => 'aTable_1|aValue,anotherTable_2|anotherValue', + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField' + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'group', + 'internal_type' => 'db', + 'allowed' => 'aTable,anotherTable', + ] + ] + ], + ] + ]; + + $expected = $input; + $expected['recordTitle'] = 'aValue, anotherValue'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleForSingleCheckboxType() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'aField' => 1, + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField' + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'check', + ] + ] + ], + ] + ]; + + /** @var LanguageService|ObjectProphecy $languageService */ + $languageService = $this->prophesize(LanguageService::class); + $GLOBALS['LANG'] = $languageService->reveal(); + $languageService->sL(Argument::cetera())->willReturnArgument(0)->shouldBeCalled(); + + $expected = $input; + $expected['recordTitle'] = 'LLL:EXT:lang/locallang_common.xlf:yes'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleForArrayCheckboxType() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'aField' => '5' + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField' + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'check', + 'items' => [ + ['foo', ''], + ['bar', ''], + ['baz', ''], + ] + ] + ] + ], + ] + ]; + + $expected = $input; + $expected['recordTitle'] = 'foo, baz'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsEmptyRecordTitleForFlexType() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'aField' => [ + 'data' => [ + 'sDEF' => [ + 'lDEF' => [ + 'aFlexField' => [ + 'vDEF' => 'aFlexValue', + ] + ] + ] + ] + ] + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField' + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'flex', + 'ds' => [ + 'sheets' => [ + 'sDEF' => [ + 'ROOT' => [ + 'type' => 'array', + 'el' => [ + 'aFlexField' => [ + 'label' => 'Some input field', + 'config' => [ + 'type' => 'input', + ], + ], + ], + ], + ], + ], + ] + + ] + ] + ], + ] + ]; + + $expected = $input; + $expected['recordTitle'] = ''; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsRecordTitleForSelectType() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'aField' => [ + '1', + '2' + ] + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField' + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'select', + 'items' => [ + ['foo', 1, NULL, NULL], + ['bar', 2, NULL, NULL], + ['baz', 4, NULL, NULL], + ] + ] + ] + ], + ] + ]; + + $expected = $input; + $expected['recordTitle'] = 'foo, bar'; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataReturnsStrippedAndTrimmedValueForTextType() { + $input = [ + 'tableName' => 'aTable', + 'databaseRow' => [ + 'aField' => '<p> text </p>', + ], + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'aField', + ], + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'text', + ], + ], + ], + ], + ]; + + $expected = $input; + $expected['recordTitle'] = 'text'; + $this->assertSame($expected, $this->subject->addData($input)); + } + +} diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaTypesRemoveUnusedColumnsTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaTypesRemoveUnusedColumnsTest.php index bee73bc026ad76b106624fa3c778cdde3b45cbc9..8cbea5d3b6e729b4e192a96e98fbc44d76b3cf53 100644 --- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaTypesRemoveUnusedColumnsTest.php +++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaTypesRemoveUnusedColumnsTest.php @@ -81,7 +81,6 @@ class TcaTypesRemoveUnusedColumnsTest extends UnitTestCase { 'palettes' => [ 'aPalette' => array( 'showitem' => 'keepMe', - 'canNotCollapse' => TRUE ), ], 'columns' => [ @@ -121,7 +120,6 @@ class TcaTypesRemoveUnusedColumnsTest extends UnitTestCase { 'palettes' => [ 'aPalette' => array( 'showitem' => 'aField', - 'canNotCollapse' => TRUE ), ], 'columns' => [ @@ -150,4 +148,99 @@ class TcaTypesRemoveUnusedColumnsTest extends UnitTestCase { $this->assertSame($expected, $this->subject->addData($input)); } + /** + * @test + */ + public function addDataKeepsColumnsFieldReferencedInLabel() { + $input = [ + 'databaseRow' => [], + 'recordTypeValue' => 'aType', + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'keepMe' + ], + 'types' => [ + 'aType' => [ + 'showitem' => 'keepMeToo' + ], + ], + 'palettes' => [ + 'aPalette' => array( + 'showitem' => 'keepMe', + ), + ], + 'columns' => [ + 'keepMe' => [ + 'config' => [ + 'type' => 'input', + ] + ], + 'keepMeToo' => [ + 'config' => [ + 'type' => 'input', + ] + ], + 'aField' => [ + 'config' => [ + 'type' => 'input', + ] + ] + ] + ] + ]; + + $expected = $input; + unset($expected['processedTca']['columns']['aField']); + + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataKeepsColumnsFieldReferencedInLabelAlt() { + $input = [ + 'databaseRow' => [], + 'recordTypeValue' => 'aType', + 'processedTca' => [ + 'ctrl' => [ + 'label' => 'keepMe', + 'label_alt' => 'keepMeToo' + ], + 'types' => [ + 'aType' => [ + 'showitem' => 'keepMe' + ], + ], + 'palettes' => [ + 'aPalette' => array( + 'showitem' => 'keepMe', + ), + ], + 'columns' => [ + 'keepMe' => [ + 'config' => [ + 'type' => 'input', + ] + ], + 'keepMeToo' => [ + 'config' => [ + 'type' => 'input', + ] + ], + 'aField' => [ + 'config' => [ + 'type' => 'input', + ] + ] + ] + ] + ]; + + $expected = $input; + unset($expected['processedTca']['columns']['aField']); + + $this->assertSame($expected, $this->subject->addData($input)); + } + } diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php index 96a4917e4eab697b7a00134f8e3abc56cb113c7f..8ed9668be21db75b9137cdb56e05fa56eaba5e59 100644 --- a/typo3/sysext/core/Configuration/DefaultConfiguration.php +++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php @@ -467,11 +467,16 @@ return array( \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems::class, ), ), - \TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions::class => array( + \TYPO3\CMS\Backend\Form\FormDataProvider\TcaRecordTitle::class => array( 'depends' => array( \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInline::class, ), ), + \TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions::class => array( + 'depends' => array( + \TYPO3\CMS\Backend\Form\FormDataProvider\TcaRecordTitle::class, + ), + ), ), 'flexFormSegment' => array( \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowDefaultValues::class => array(),