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