[BUGFIX] Fix broken table name mapping for namespaced extension models
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Generic / Mapper / DataMapFactory.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
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 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26 /**
27 * A factory for a data map to map a single table configured in $TCA on a domain object.
28 *
29 * @package Extbase
30 * @subpackage Persistence\Mapper
31 * @version $ID:$
32 */
33 class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface {
34
35 /**
36 * @var \TYPO3\CMS\Extbase\Reflection\Service
37 */
38 protected $reflectionService;
39
40 /**
41 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
42 */
43 protected $configurationManager;
44
45 /**
46 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
47 */
48 protected $objectManager;
49
50 /**
51 * @var \TYPO3\CMS\Core\Cache\CacheManager
52 */
53 protected $cacheManager;
54
55 /**
56 * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
57 */
58 protected $dataMapCache;
59
60 /**
61 * Injects the reflection service
62 *
63 * @param \TYPO3\CMS\Extbase\Reflection\Service $reflectionService
64 * @return void
65 */
66 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\Service $reflectionService) {
67 $this->reflectionService = $reflectionService;
68 }
69
70 /**
71 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
72 * @return void
73 */
74 public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager) {
75 $this->configurationManager = $configurationManager;
76 }
77
78 /**
79 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
80 * @return void
81 */
82 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager) {
83 $this->objectManager = $objectManager;
84 }
85
86 /**
87 * @param \TYPO3\CMS\Core\Cache\CacheManager $cacheManager
88 */
89 public function injectCacheManager(\TYPO3\CMS\Core\Cache\CacheManager $cacheManager) {
90 $this->cacheManager = $cacheManager;
91 }
92
93 /**
94 * Lifecycle method
95 *
96 * @return void
97 */
98 public function initializeObject() {
99 $this->dataMapCache = $this->cacheManager->getCache('extbase_datamapfactory_datamap');
100 }
101
102 /**
103 * Builds a data map by adding column maps for all the configured columns in the $TCA.
104 * It also resolves the type of values the column is holding and the typo of relation the column
105 * represents.
106 *
107 * @param string $className The class name you want to fetch the Data Map for
108 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap The data map
109 */
110 public function buildDataMap($className) {
111 $dataMap = $this->dataMapCache->get(str_replace('\\', '%', $className));
112 if ($dataMap === FALSE) {
113 $dataMap = $this->buildDataMapInternal($className);
114 $this->dataMapCache->set(str_replace('\\', '%', $className), $dataMap);
115 }
116 return $dataMap;
117 }
118
119 /**
120 * Builds a data map by adding column maps for all the configured columns in the $TCA.
121 * It also resolves the type of values the column is holding and the typo of relation the column
122 * represents.
123 *
124 * @param string $className The class name you want to fetch the Data Map for
125 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidClassException
126 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap The data map
127 */
128 protected function buildDataMapInternal($className) {
129 if (!class_exists($className)) {
130 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidClassException('Could not find class definition for name "' . $className . '". This could be caused by a mis-spelling of the class name in the class definition.');
131 }
132 $recordType = NULL;
133 $subclasses = array();
134 $tableName = $this->resolveTableName($className);
135 $columnMapping = array();
136 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
137 $classSettings = $frameworkConfiguration['persistence']['classes'][$className];
138 if ($classSettings !== NULL) {
139 if (isset($classSettings['subclasses']) && is_array($classSettings['subclasses'])) {
140 $subclasses = $this->resolveSubclassesRecursive($frameworkConfiguration['persistence']['classes'], $classSettings['subclasses']);
141 }
142 if (isset($classSettings['mapping']['recordType']) && strlen($classSettings['mapping']['recordType']) > 0) {
143 $recordType = $classSettings['mapping']['recordType'];
144 }
145 if (isset($classSettings['mapping']['tableName']) && strlen($classSettings['mapping']['tableName']) > 0) {
146 $tableName = $classSettings['mapping']['tableName'];
147 }
148 $classHierarchy = array_merge(array($className), class_parents($className));
149 foreach ($classHierarchy as $currentClassName) {
150 if (in_array($currentClassName, array('TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity', 'TYPO3\\CMS\\Extbase\\DomainObject\\AbstractValueObject'))) {
151 break;
152 }
153 $currentClassSettings = $frameworkConfiguration['persistence']['classes'][$currentClassName];
154 if ($currentClassSettings !== NULL) {
155 if (isset($currentClassSettings['mapping']['columns']) && is_array($currentClassSettings['mapping']['columns'])) {
156 $columnMapping = \TYPO3\CMS\Core\Utility\GeneralUtility::array_merge_recursive_overrule($columnMapping, $currentClassSettings['mapping']['columns'], 0, FALSE);
157 }
158 }
159 }
160 }
161 /** @var $dataMap \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap */
162 $dataMap = $this->objectManager->create('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Mapper\\DataMap', $className, $tableName, $recordType, $subclasses);
163 $dataMap = $this->addMetaDataColumnNames($dataMap, $tableName);
164 // $classPropertyNames = $this->reflectionService->getClassPropertyNames($className);
165 $tcaColumnsDefinition = $this->getColumnsDefinition($tableName);
166 $tcaColumnsDefinition = \TYPO3\CMS\Core\Utility\GeneralUtility::array_merge_recursive_overrule($tcaColumnsDefinition, $columnMapping);
167 // TODO Is this is too powerful?
168 foreach ($tcaColumnsDefinition as $columnName => $columnDefinition) {
169 if (isset($columnDefinition['mapOnProperty'])) {
170 $propertyName = $columnDefinition['mapOnProperty'];
171 } else {
172 $propertyName = \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToLowerCamelCase($columnName);
173 }
174 // if (in_array($propertyName, $classPropertyNames)) { // TODO Enable check for property existance
175 $columnMap = new \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap($columnName, $propertyName);
176 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
177 $columnMap = $this->setRelations($columnMap, $columnDefinition['config'], $propertyMetaData);
178 $dataMap->addColumnMap($columnMap);
179 }
180 // debug($dataMap);
181 return $dataMap;
182 }
183
184 /**
185 * Resolve the table name for the given class name
186 *
187 * @param string $className
188 * @return string The table name
189 */
190 protected function resolveTableName($className) {
191 if (strpos($className, '\\') !== FALSE) {
192 $classNameParts = explode('\\', $className, 6);
193 // Skip vendor and product name for core classes
194 if (strpos($className, 'TYPO3\\CMS\\') === 0) {
195 $classPartsToSkip = 2;
196 } else {
197 $classPartsToSkip = 1;
198 }
199 $tableName = 'tx_' . strtolower(implode('_', array_slice($classNameParts, $classPartsToSkip)));
200 } else {
201 $tableName = strtolower($className);
202 }
203 return $tableName;
204 }
205
206 /**
207 * Resolves all subclasses for the given set of (sub-)classes.
208 * The whole classes configuration is used to determine all subclasses recursively.
209 *
210 * @param array $classesConfiguration The framework configuration part [persistence][classes].
211 * @param array $subclasses An array of subclasses defined via TypoScript
212 * @return array An numeric array that contains all available subclasses-strings as values.
213 */
214 protected function resolveSubclassesRecursive(array $classesConfiguration, array $subclasses) {
215 $allSubclasses = array();
216 foreach ($subclasses as $subclass) {
217 $allSubclasses[] = $subclass;
218 if (isset($classesConfiguration[$subclass]['subclasses']) && is_array($classesConfiguration[$subclass]['subclasses'])) {
219 $childSubclasses = $this->resolveSubclassesRecursive($classesConfiguration, $classesConfiguration[$subclass]['subclasses']);
220 $allSubclasses = array_merge($allSubclasses, $childSubclasses);
221 }
222 }
223 return $allSubclasses;
224 }
225
226 /**
227 * Returns the TCA ctrl section of the specified table; or NULL if not set
228 *
229 * @param string $tableName An optional table name to fetch the columns definition from
230 * @return array The TCA columns definition
231 */
232 protected function getControlSection($tableName) {
233 $this->includeTca($tableName);
234 return is_array($GLOBALS['TCA'][$tableName]['ctrl']) ? $GLOBALS['TCA'][$tableName]['ctrl'] : NULL;
235 }
236
237 /**
238 * Returns the TCA columns array of the specified table
239 *
240 * @param string $tableName An optional table name to fetch the columns definition from
241 * @return array The TCA columns definition
242 */
243 protected function getColumnsDefinition($tableName) {
244 $this->includeTca($tableName);
245 return is_array($GLOBALS['TCA'][$tableName]['columns']) ? $GLOBALS['TCA'][$tableName]['columns'] : array();
246 }
247
248 /**
249 * Includes the TCA for the given table
250 *
251 * @param string $tableName An optional table name to fetch the columns definition from
252 * @return void
253 */
254 protected function includeTca($tableName) {
255 if (TYPO3_MODE === 'FE') {
256 $GLOBALS['TSFE']->includeTCA();
257 }
258 \TYPO3\CMS\Core\Utility\GeneralUtility::loadTCA($tableName);
259 }
260
261 /**
262 * @param DataMap $dataMap
263 * @param $tableName
264 * @return DataMap
265 */
266 protected function addMetaDataColumnNames(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap $dataMap, $tableName) {
267 $controlSection = $GLOBALS['TCA'][$tableName]['ctrl'];
268 $dataMap->setPageIdColumnName('pid');
269 if (isset($controlSection['tstamp'])) {
270 $dataMap->setModificationDateColumnName($controlSection['tstamp']);
271 }
272 if (isset($controlSection['crdate'])) {
273 $dataMap->setCreationDateColumnName($controlSection['crdate']);
274 }
275 if (isset($controlSection['cruser_id'])) {
276 $dataMap->setCreatorColumnName($controlSection['cruser_id']);
277 }
278 if (isset($controlSection['delete'])) {
279 $dataMap->setDeletedFlagColumnName($controlSection['delete']);
280 }
281 if (isset($controlSection['languageField'])) {
282 $dataMap->setLanguageIdColumnName($controlSection['languageField']);
283 }
284 if (isset($controlSection['transOrigPointerField'])) {
285 $dataMap->setTranslationOriginColumnName($controlSection['transOrigPointerField']);
286 }
287 if (isset($controlSection['type'])) {
288 $dataMap->setRecordTypeColumnName($controlSection['type']);
289 }
290 if (isset($controlSection['enablecolumns']['disabled'])) {
291 $dataMap->setDisabledFlagColumnName($controlSection['enablecolumns']['disabled']);
292 }
293 if (isset($controlSection['enablecolumns']['starttime'])) {
294 $dataMap->setStartTimeColumnName($controlSection['enablecolumns']['starttime']);
295 }
296 if (isset($controlSection['enablecolumns']['endtime'])) {
297 $dataMap->setEndTimeColumnName($controlSection['enablecolumns']['endtime']);
298 }
299 if (isset($controlSection['enablecolumns']['fe_group'])) {
300 $dataMap->setFrontEndUserGroupColumnName($controlSection['enablecolumns']['fe_group']);
301 }
302 return $dataMap;
303 }
304
305 /**
306 * This method tries to determine the type of type of relation to other tables and sets it based on
307 * the $TCA column configuration
308 *
309 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap The column map
310 * @param string $columnConfiguration The column configuration from $TCA
311 * @param array $propertyMetaData The property metadata as delivered by the reflection service
312 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
313 */
314 protected function setRelations(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap, $columnConfiguration, $propertyMetaData) {
315 if (isset($columnConfiguration)) {
316 if (isset($columnConfiguration['MM']) || isset($columnConfiguration['foreign_selector'])) {
317 $columnMap = $this->setManyToManyRelation($columnMap, $columnConfiguration);
318 } elseif (isset($propertyMetaData['elementType'])) {
319 $columnMap = $this->setOneToManyRelation($columnMap, $columnConfiguration);
320 } elseif (isset($propertyMetaData['type']) && strpos($propertyMetaData['type'], '_') !== FALSE) {
321 $columnMap = $this->setOneToOneRelation($columnMap, $columnConfiguration);
322 } else {
323 $columnMap->setTypeOfRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_NONE);
324 }
325 } else {
326 $columnMap->setTypeOfRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_NONE);
327 }
328 return $columnMap;
329 }
330
331 /**
332 * This method sets the configuration for a 1:1 relation based on
333 * the $TCA column configuration
334 *
335 * @param string|\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap The column map
336 * @param string $columnConfiguration The column configuration from $TCA
337 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
338 */
339 protected function setOneToOneRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap, $columnConfiguration) {
340 $columnMap->setTypeOfRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_ONE);
341 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
342 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
343 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
344 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
345 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
346 return $columnMap;
347 }
348
349 /**
350 * This method sets the configuration for a 1:n relation based on
351 * the $TCA column configuration
352 *
353 * @param string|\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap The column map
354 * @param string $columnConfiguration The column configuration from $TCA
355 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
356 */
357 protected function setOneToManyRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap, $columnConfiguration) {
358 $columnMap->setTypeOfRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY);
359 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
360 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
361 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
362 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
363 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
364 return $columnMap;
365 }
366
367 /**
368 * This method sets the configuration for a m:n relation based on
369 * the $TCA column configuration
370 *
371 * @param string|\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap The column map
372 * @param string $columnConfiguration The column configuration from $TCA
373 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedRelationException
374 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
375 */
376 protected function setManyToManyRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap, $columnConfiguration) {
377 $columnMap->setTypeOfRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY);
378 if (isset($columnConfiguration['MM'])) {
379 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
380 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
381 $columnMap->setRelationTableName($columnConfiguration['MM']);
382 if (is_array($columnConfiguration['MM_match_fields'])) {
383 $columnMap->setRelationTableMatchFields($columnConfiguration['MM_match_fields']);
384 }
385 if (is_array($columnConfiguration['MM_insert_fields'])) {
386 $columnMap->setRelationTableInsertFields($columnConfiguration['MM_insert_fields']);
387 }
388 $columnMap->setRelationTableWhereStatement($columnConfiguration['MM_table_where']);
389 if (!empty($columnConfiguration['MM_opposite_field'])) {
390 $columnMap->setParentKeyFieldName('uid_foreign');
391 $columnMap->setChildKeyFieldName('uid_local');
392 $columnMap->setChildSortByFieldName('sorting_foreign');
393 } else {
394 $columnMap->setParentKeyFieldName('uid_local');
395 $columnMap->setChildKeyFieldName('uid_foreign');
396 $columnMap->setChildSortByFieldName('sorting');
397 }
398 } elseif (isset($columnConfiguration['foreign_selector'])) {
399 $columns = $this->getColumnsDefinition($columnConfiguration['foreign_table']);
400 $childKeyFieldName = $columnConfiguration['foreign_selector'];
401 $columnMap->setChildTableName($columns[$childKeyFieldName]['config']['foreign_table']);
402 $columnMap->setRelationTableName($columnConfiguration['foreign_table']);
403 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
404 $columnMap->setChildKeyFieldName($childKeyFieldName);
405 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
406 } else {
407 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedRelationException('The given information to build a many-to-many-relation was not sufficient. Check your TCA definitions. mm-relations with IRRE must have at least a defined "MM" or "foreign_selector".', 1268817963);
408 }
409 if ($this->getControlSection($columnMap->getRelationTableName()) !== NULL) {
410 $columnMap->setRelationTablePageIdColumnName('pid');
411 }
412 return $columnMap;
413 }
414
415 }
416
417
418 ?>