[TASK] Cleanup TCA select field configuration
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Category / CategoryRegistry.php
1 <?php
2 namespace TYPO3\CMS\Core\Category;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\SingletonInterface;
18 use TYPO3\CMS\Core\Utility\ArrayUtility;
19 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Lang\LanguageService;
22
23 /**
24 * Class to register category configurations.
25 */
26 class CategoryRegistry implements SingletonInterface {
27
28 /**
29 * @var array
30 */
31 protected $registry = array();
32
33 /**
34 * @var array
35 */
36 protected $extensions = array();
37
38 /**
39 * @var array
40 */
41 protected $addedCategoryTabs = array();
42
43 /**
44 * @var string
45 */
46 protected $template = '';
47
48 /**
49 * Returns a class instance
50 *
51 * @return CategoryRegistry
52 */
53 static public function getInstance() {
54 return GeneralUtility::makeInstance(__CLASS__);
55 }
56
57 /**
58 * Creates this object.
59 */
60 public function __construct() {
61 $this->template = str_repeat(PHP_EOL, 3) . 'CREATE TABLE %s (' . PHP_EOL
62 . ' %s int(11) DEFAULT \'0\' NOT NULL' . PHP_EOL . ');' . str_repeat(PHP_EOL, 3);
63 }
64
65 /**
66 * Adds a new category configuration to this registry.
67 * TCA changes are directly applied
68 *
69 * @param string $extensionKey Extension key to be used
70 * @param string $tableName Name of the table to be registered
71 * @param string $fieldName Name of the field to be registered
72 * @param array $options Additional configuration options
73 * + fieldList: field configuration to be added to showitems
74 * + typesList: list of types that shall visualize the categories field
75 * + position: insert position of the categories field
76 * + label: backend label of the categories field
77 * + fieldConfiguration: TCA field config array to override defaults
78 * @param bool $override If TRUE, any category configuration for the same table / field is removed before the new configuration is added
79 * @return bool
80 * @throws \InvalidArgumentException
81 * @throws \RuntimeException
82 */
83 public function add($extensionKey, $tableName, $fieldName = 'categories', array $options = array(), $override = FALSE) {
84 $didRegister = FALSE;
85 if (empty($tableName) || !is_string($tableName)) {
86 throw new \InvalidArgumentException('No or invalid table name "' . $tableName . '" given.', 1369122038);
87 }
88 if (empty($extensionKey) || !is_string($extensionKey)) {
89 throw new \InvalidArgumentException('No or invalid extension key "' . $extensionKey . '" given.', 1397836158);
90 }
91
92 if ($override) {
93 $this->remove($tableName, $fieldName);
94 }
95
96 if (!$this->isRegistered($tableName, $fieldName)) {
97 $this->registry[$tableName][$fieldName] = $options;
98 $this->extensions[$extensionKey][$tableName][$fieldName] = $fieldName;
99
100 if (!isset($GLOBALS['TCA'][$tableName]['columns']) && isset($GLOBALS['TCA'][$tableName]['ctrl']['dynamicConfigFile'])) {
101 // Handle deprecated old style dynamic TCA column loading.
102 ExtensionManagementUtility::loadNewTcaColumnsConfigFiles();
103 }
104
105 if (isset($GLOBALS['TCA'][$tableName]['columns'])) {
106 $this->applyTcaForTableAndField($tableName, $fieldName);
107 $didRegister = TRUE;
108 }
109 }
110
111 return $didRegister;
112 }
113
114 /**
115 * Gets all extension keys that registered a category configuration.
116 *
117 * @return array
118 */
119 public function getExtensionKeys() {
120 return array_keys($this->extensions);
121 }
122
123 /**
124 * Gets all categorized tables
125 *
126 * @return array
127 */
128 public function getCategorizedTables() {
129 return array_keys($this->registry);
130 }
131
132 /**
133 * Returns a list of category fields for a given table for populating selector "category_field"
134 * in tt_content table (called as itemsProcFunc).
135 *
136 * @param array $configuration Current field configuration
137 * @throws \UnexpectedValueException
138 * @return void
139 */
140 public function getCategoryFieldsForTable(array &$configuration) {
141 $table = '';
142 $menuType = isset($configuration['row']['menu_type'][0]) ? $configuration['row']['menu_type'][0] : '';
143 // Define the table being looked up from the type of menu
144 if ($menuType === 'categorized_pages') {
145 $table = 'pages';
146 } elseif ($menuType === 'categorized_content') {
147 $table = 'tt_content';
148 }
149 // Return early if no table is defined
150 if (empty($table)) {
151 throw new \UnexpectedValueException('The given menu_type is not supported.', 1381823570);
152 }
153 // Loop on all registries and find entries for the correct table
154 foreach ($this->registry as $tableName => $fields) {
155 if ($tableName === $table) {
156 foreach ($fields as $fieldName => $options) {
157 $fieldLabel = $this->getLanguageService()->sL($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['label']);
158 $configuration['items'][] = array($fieldLabel, $fieldName);
159 }
160 }
161 }
162 }
163
164 /**
165 * Tells whether a table has a category configuration in the registry.
166 *
167 * @param string $tableName Name of the table to be looked up
168 * @param string $fieldName Name of the field to be looked up
169 * @return bool
170 */
171 public function isRegistered($tableName, $fieldName = 'categories') {
172 return isset($this->registry[$tableName][$fieldName]);
173 }
174
175 /**
176 * Generates tables definitions for all registered tables.
177 *
178 * @return string
179 */
180 public function getDatabaseTableDefinitions() {
181 $sql = '';
182 foreach ($this->getExtensionKeys() as $extensionKey) {
183 $sql .= $this->getDatabaseTableDefinition($extensionKey);
184 }
185 return $sql;
186 }
187
188 /**
189 * Generates table definitions for registered tables by an extension.
190 *
191 * @param string $extensionKey Extension key to have the database definitions created for
192 * @return string
193 */
194 public function getDatabaseTableDefinition($extensionKey) {
195 if (!isset($this->extensions[$extensionKey]) || !is_array($this->extensions[$extensionKey])) {
196 return '';
197 }
198 $sql = '';
199
200 foreach ($this->extensions[$extensionKey] as $tableName => $fields) {
201 foreach ($fields as $fieldName) {
202 $sql .= sprintf($this->template, $tableName, $fieldName);
203 }
204 }
205 return $sql;
206 }
207
208 /**
209 * Apply TCA to all registered tables
210 *
211 * @return void
212 * @internal
213 */
214 public function applyTcaForPreRegisteredTables() {
215 $this->registerDefaultCategorizedTables();
216 foreach ($this->registry as $tableName => $fields) {
217 foreach ($fields as $fieldName => $_) {
218 $this->applyTcaForTableAndField($tableName, $fieldName);
219 }
220 }
221 }
222
223 /**
224 * Applies the additions directly to the TCA
225 *
226 * @param string $tableName
227 * @param string $fieldName
228 */
229 protected function applyTcaForTableAndField($tableName, $fieldName) {
230 $this->addTcaColumn($tableName, $fieldName, $this->registry[$tableName][$fieldName]);
231 $this->addToAllTCAtypes($tableName, $fieldName, $this->registry[$tableName][$fieldName]);
232 }
233
234 /**
235 * Add default categorized tables to the registry
236 *
237 * @return void
238 */
239 protected function registerDefaultCategorizedTables() {
240 $defaultCategorizedTables = GeneralUtility::trimExplode(
241 ',',
242 $GLOBALS['TYPO3_CONF_VARS']['SYS']['defaultCategorizedTables'],
243 TRUE
244 );
245 foreach ($defaultCategorizedTables as $defaultCategorizedTable) {
246 if (!$this->isRegistered($defaultCategorizedTable)) {
247 $this->add('core', $defaultCategorizedTable, 'categories');
248 }
249 }
250 }
251
252 /**
253 * Add a new field into the TCA types -> showitem
254 *
255 * @param string $tableName Name of the table to be categorized
256 * @param string $fieldName Name of the field to be used to store categories
257 * @param array $options Additional configuration options
258 * + fieldList: field configuration to be added to showitems
259 * + typesList: list of types that shall visualize the categories field
260 * + position: insert position of the categories field
261 * @return void
262 */
263 protected function addToAllTCAtypes($tableName, $fieldName, array $options) {
264
265 // Makes sure to add more TCA to an existing structure
266 if (isset($GLOBALS['TCA'][$tableName]['columns'])) {
267
268 if (empty($options['fieldList'])) {
269 $fieldList = $this->addCategoryTab($tableName, $fieldName);
270 } else {
271 $fieldList = $options['fieldList'];
272 }
273
274 $typesList = '';
275 if (!empty($options['typesList'])) {
276 $typesList = $options['typesList'];
277 }
278
279 $position = '';
280 if (!empty($options['position'])) {
281 $position = $options['position'];
282 }
283
284 // Makes the new "categories" field to be visible in TSFE.
285 ExtensionManagementUtility::addToAllTCAtypes($tableName, $fieldList, $typesList, $position);
286 }
287 }
288
289 /**
290 * Creates the 'fieldList' string for $fieldName which includes a categories tab.
291 * But only one categories tab is added per table.
292 *
293 * @param string $tableName
294 * @param string $fieldName
295 * @return string
296 */
297 protected function addCategoryTab($tableName, $fieldName) {
298 $fieldList = '';
299 if (!isset($this->addedCategoryTabs[$tableName])) {
300 $fieldList .= '--div--;LLL:EXT:lang/locallang_tca.xlf:sys_category.tabs.category, ';
301 $this->addedCategoryTabs[$tableName] = $tableName;
302 }
303 $fieldList .= $fieldName;
304 return $fieldList;
305 }
306
307 /**
308 * Add a new TCA Column
309 *
310 * @param string $tableName Name of the table to be categorized
311 * @param string $fieldName Name of the field to be used to store categories
312 * @param array $options Additional configuration options
313 * + fieldConfiguration: TCA field config array to override defaults
314 * + label: backend label of the categories field
315 * + interface: boolean if the category should be included in the "interface" section of the TCA table
316 * + l10n_mode
317 * + l10n_display
318 * @return void
319 */
320 protected function addTcaColumn($tableName, $fieldName, array $options) {
321 // Makes sure to add more TCA to an existing structure
322 if (isset($GLOBALS['TCA'][$tableName]['columns'])) {
323 // Take specific label into account
324 $label = 'LLL:EXT:lang/locallang_tca.xlf:sys_category.categories';
325 if (!empty($options['label'])) {
326 $label = $options['label'];
327 }
328
329 // Take specific value of exclude flag into account
330 $exclude = TRUE;
331 if (isset($options['exclude'])) {
332 $exclude = (bool)$options['exclude'];
333 }
334
335 $fieldConfiguration = empty($options['fieldConfiguration']) ? array() : $options['fieldConfiguration'];
336
337 $columns = array(
338 $fieldName => array(
339 'exclude' => $exclude,
340 'label' => $label,
341 'config' => static::getTcaFieldConfiguration($tableName, $fieldName, $fieldConfiguration),
342 ),
343 );
344
345 if (isset($options['l10n_mode'])) {
346 $columns[$fieldName]['l10n_mode'] = $options['l10n_mode'];
347 }
348 if (isset($options['l10n_display'])) {
349 $columns[$fieldName]['l10n_display'] = $options['l10n_display'];
350 }
351
352 // Register opposite references for the foreign side of a relation
353 if (empty($GLOBALS['TCA']['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$tableName])) {
354 $GLOBALS['TCA']['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$tableName] = array();
355 }
356 if (!in_array($fieldName, $GLOBALS['TCA']['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$tableName])) {
357 $GLOBALS['TCA']['sys_category']['columns']['items']['config']['MM_oppositeUsage'][$tableName][] = $fieldName;
358 }
359
360 // Add field to interface list per default (unless the 'interface' property is FALSE)
361 if (
362 (!isset($options['interface']) || $options['interface'])
363 && !empty($GLOBALS['TCA'][$tableName]['interface']['showRecordFieldList'])
364 && !GeneralUtility::inList($GLOBALS['TCA'][$tableName]['interface']['showRecordFieldList'], $fieldName)
365 ) {
366 $GLOBALS['TCA'][$tableName]['interface']['showRecordFieldList'] .= ',' . $fieldName;
367 }
368
369 // Adding fields to an existing table definition
370 ExtensionManagementUtility::addTCAcolumns($tableName, $columns);
371 }
372 }
373
374 /**
375 * Get the config array for given table and field.
376 * This method does NOT take care of adding sql fields, adding the field to TCA types
377 * nor does it set the MM_oppositeUsage in the sys_category TCA. This has to be taken care of manually!
378 *
379 * @param string $tableName The table name
380 * @param string $fieldName The field name (default categories)
381 * @param array $fieldConfigurationOverride Changes to the default configuration
382 * @return array
383 * @api
384 */
385 static public function getTcaFieldConfiguration($tableName, $fieldName = 'categories', array $fieldConfigurationOverride = array()) {
386 // Forges a new field, default name is "categories"
387 $fieldConfiguration = array(
388 'type' => 'select',
389 'renderType' => 'selectTree',
390 'foreign_table' => 'sys_category',
391 'foreign_table_where' => ' AND sys_category.sys_language_uid IN (-1, 0) ORDER BY sys_category.sorting ASC',
392 'MM' => 'sys_category_record_mm',
393 'MM_opposite_field' => 'items',
394 'MM_match_fields' => array(
395 'tablenames' => $tableName,
396 'fieldname' => $fieldName,
397 ),
398 'size' => 10,
399 'autoSizeMax' => 50,
400 'maxitems' => 9999,
401 'treeConfig' => array(
402 'parentField' => 'parent',
403 'appearance' => array(
404 'expandAll' => TRUE,
405 'showHeader' => TRUE,
406 'maxLevels' => 99,
407 ),
408 ),
409 );
410
411 // Merge changes to TCA configuration
412 if (!empty($fieldConfigurationOverride)) {
413 ArrayUtility::mergeRecursiveWithOverrule(
414 $fieldConfiguration,
415 $fieldConfigurationOverride
416 );
417 }
418
419 return $fieldConfiguration;
420 }
421
422 /**
423 * A slot method to inject the required category database fields to the
424 * tables definition string
425 *
426 * @param array $sqlString
427 * @return array
428 */
429 public function addCategoryDatabaseSchemaToTablesDefinition(array $sqlString) {
430 $this->registerDefaultCategorizedTables();
431 $sqlString[] = $this->getDatabaseTableDefinitions();
432 return array('sqlString' => $sqlString);
433 }
434
435 /**
436 * A slot method to inject the required category database fields of an
437 * extension to the tables definition string
438 *
439 * @param array $sqlString
440 * @param string $extensionKey
441 * @return array
442 */
443 public function addExtensionCategoryDatabaseSchemaToTablesDefinition(array $sqlString, $extensionKey) {
444 $sqlString[] = $this->getDatabaseTableDefinition($extensionKey);
445 return array('sqlString' => $sqlString, 'extensionKey' => $extensionKey);
446 }
447
448 /**
449 * @return LanguageService
450 */
451 protected function getLanguageService() {
452 return $GLOBALS['LANG'];
453 }
454
455 /**
456 * Removes the given field in the given table from the registry if it is found.
457 *
458 * @param string $tableName The name of the table for which the registration should be removed.
459 * @param string $fieldName The name of the field for which the registration should be removed.
460 */
461 protected function remove($tableName, $fieldName) {
462 if (!$this->isRegistered($tableName, $fieldName)) {
463 return;
464 }
465
466 unset($this->registry[$tableName][$fieldName]);
467
468 foreach ($this->extensions as $extensionKey => $tableFieldConfig) {
469 foreach ($tableFieldConfig as $extTableName => $fieldNameArray) {
470 if ($extTableName === $tableName && isset($fieldNameArray[$fieldName])) {
471 unset($this->extensions[$extensionKey][$tableName][$fieldName]);
472 break;
473 }
474 }
475 }
476
477 // If no more fields are configured we unregister the categories tab.
478 if (empty($this->registry[$tableName]) && isset($this->addedCategoryTabs[$tableName])) {
479 unset($this->addedCategoryTabs[$tableName]);
480 }
481 }
482 }