0dec360962915a9cab4e0cac57c7d977675750f6
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Mapper / DataMap.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25 /**
26 * A data map to map a single table configured in $TCA on a domain object.
27 *
28 * @package Extbase
29 * @subpackage Persistence\Mapper
30 * @version $ID:$
31 */
32 class Tx_Extbase_Persistence_Mapper_DataMap {
33 /**
34 * The domain class name
35 *
36 * @var string
37 **/
38 protected $className;
39
40 /**
41 * The table name corresponding to the domain class configured in $TCA
42 *
43 * @var string
44 **/
45 protected $tableName;
46
47 /**
48 * An array of column maps configured in $TCA
49 *
50 * @var array
51 **/
52 protected $columnMaps;
53
54 /**
55 * Constructs this DataMap
56 *
57 * @param string $className The class name. This determines the table to fetch the configuration for
58 */
59 // TODO Refactor to factory pattern (DataMapFactory) and value object (DataMap)
60 public function __construct($className, $tableName = '', array $mapping = array()) {
61 $this->setClassName($className);
62 if (empty($tableName)) {
63 $this->setTableName(strtolower($className));
64 } else {
65 $this->setTableName($tableName);
66 }
67 $this->initialize($mapping);
68 }
69
70 /**
71 * Sets the name of the class the colum map represents
72 *
73 * @return void
74 */
75 public function setClassName($className) {
76 $this->className = $className;
77 }
78
79 /**
80 * Returns the name of the class the column map represents
81 *
82 * @return string The class name
83 */
84 public function getClassName() {
85 return $this->className;
86 }
87
88 /**
89 * Sets the name of the table the colum map represents
90 *
91 * @return void
92 */
93 public function setTableName($tableName) {
94 $this->tableName = $tableName;
95 }
96
97 /**
98 * Returns the name of the table the column map represents
99 *
100 * @return string The table name
101 */
102 public function getTableName() {
103 return $this->tableName;
104 }
105
106 /**
107 * Initializes the data map by adding column maps for all the configured columns in the $TCA.
108 * It also resolves the type of values the column is holding and the typo of relation the column
109 * represents.
110 *
111 * @return void
112 */
113 protected function initialize(array $mapping) {
114 t3lib_div::loadTCA($this->getTableName());
115 $columns = $GLOBALS['TCA'][$this->getTableName()]['columns'];
116 $this->addCommonColumns();
117 if (is_array($columns)) {
118 foreach ($columns as $columnName => $columnConfiguration) {
119 if (!empty($mapping[$columnName]['mapOnProperty'])) {
120 $propertyName = $mapping[$columnName]['mapOnProperty'];
121 } else {
122 $propertyName = Tx_Extbase_Utility_Plugin::convertUnderscoredToLowerCamelCase($columnName);
123 }
124 if (isset($mapping[$columnName]['foreignClass']) && !isset($columnConfiguration['config']['foreign_class'])) {
125 $columnConfiguration['config']['foreign_class'] = $mapping[$columnName]['foreignClass'];
126 }
127 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
128 $this->setPropertyType($columnMap, $columnConfiguration);
129 // TODO Check support for IRRE
130 $this->setRelations($columnMap, $columnConfiguration);
131 $this->addColumnMap($columnMap);
132 }
133 }
134 }
135
136 /**
137 * Adds available common columns (e.g. tstamp or crdate) to the data map. It takes the configured column names
138 * into account.
139 *
140 * @return void
141 */
142 protected function addCommonColumns() {
143 // TODO Decide whether we should add pid and uid columns by default
144 $this->addColumn('uid', NULL, Tx_Extbase_Persistence_PropertyType::LONG);
145 if ($this->hasPidColumn()) {
146 $this->addColumn('pid', NULL, Tx_Extbase_Persistence_PropertyType::LONG);
147 }
148 if ($this->hasTimestampColumn()) {
149 $this->addColumn($this->getTimestampColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::DATE);
150 }
151 if ($this->hasCreationDateColumn()) {
152 $this->addColumn($this->getCreationDateColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::DATE);
153 }
154 if ($this->hasCreatorUidColumn()) {
155 $this->addColumn($this->getCreatorUidColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::LONG);
156 }
157 if ($this->hasDeletedColumn()) {
158 $this->addColumn($this->getDeletedColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::BOOLEAN);
159 }
160 if ($this->hasHiddenColumn()) {
161 $this->addColumn($this->getHiddenColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::BOOLEAN);
162 }
163 }
164
165 /**
166 * This method tries to determine the type of value the column hold by inspectiong the $TCA column configuration
167 * and sets it.
168 *
169 * @param string $columnMap The column map
170 * @param string $columnConfiguration The column configuration from $TCA
171 * @return void
172 */
173 protected function setPropertyType(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
174 $evalConfiguration = t3lib_div::trimExplode(',', $columnConfiguration['config']['eval']);
175 if (in_array('date', $evalConfiguration) || in_array('datetime', $evalConfiguration)) {
176 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::DATE);
177 } elseif ($columnConfiguration['config']['type'] === 'check' && empty($columnConfiguration['config']['items'])) {
178 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::BOOLEAN);
179 } elseif (in_array('int', $evalConfiguration)) {
180 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::LONG);
181 } elseif (in_array('double2', $evalConfiguration)) {
182 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::DOUBLE);
183 } else {
184 if (isset($columnConfiguration['config']['foreign_table'])) {
185 if (isset($columnConfiguration['config']['loadingStrategy'])) {
186 $columnMap->setLoadingStrategy($columnConfiguration['config']['loadingStrategy']);
187 } else {
188 $columnMap->setLoadingStrategy(Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_EAGER);
189 }
190 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::REFERENCE);
191 } else {
192 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::STRING);
193 }
194 }
195 }
196
197 /**
198 * This method tries to determine the type of type of relation to other tables and sets it based on
199 * the $TCA column configuration
200 *
201 * @param string $columnMap The column map
202 * @param string $columnConfiguration The column configuration from $TCA
203 * @return void
204 */
205 protected function setRelations(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
206 if (isset($columnConfiguration['config']['foreign_table']) && !isset($columnConfiguration['config']['MM'])) {
207 if ($columnConfiguration['config']['maxitems'] == 1) {
208 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE);
209 } else {
210 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY);
211 }
212 $columnMap->setChildClassName($columnConfiguration['config']['foreign_class']);
213 $columnMap->setChildTableName($columnConfiguration['config']['foreign_table']);
214 $columnMap->setChildTableWhereStatement($columnConfiguration['config']['foreign_table_where']);
215 $columnMap->setChildSortbyFieldName($columnConfiguration['config']['foreign_sortby']);
216 $columnMap->setDeleteChildObjectsState($columnConfiguration['config']['deleteRelationsWithParent']);
217 $columnMap->setParentKeyFieldName($columnConfiguration['config']['foreign_field']);
218 $columnMap->setParentTableFieldName($columnConfiguration['config']['foreign_table_field']);
219 } elseif (array_key_exists('MM', $columnConfiguration['config'])) {
220 // TODO support for MM_insert_fields and MM_match_fields
221 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY);
222 $columnMap->setChildClassName($columnConfiguration['config']['foreign_class']);
223 $columnMap->setChildTableName($columnConfiguration['config']['foreign_table']);
224 $columnMap->setRelationTableName($columnConfiguration['config']['MM']);
225 if (is_array($columnConfiguration['config']['MM_match_fields'])) {
226 $columnMap->setRelationTableMatchFields($columnConfiguration['config']['MM_match_fields']);
227 }
228 $columnMap->setRelationTableWhereStatement($columnConfiguration['config']['MM_table_where']);
229 // TODO We currently do not support multi table relationships
230 if ($columnConfiguration['config']['MM_opposite_field']) {
231 $columnMap->setParentKeyFieldName('uid_foreign');
232 $columnMap->setChildKeyFieldName('uid_local');
233 $columnMap->setChildSortByFieldName('sorting_foreign');
234 } else {
235 $columnMap->setParentKeyFieldName('uid_local');
236 $columnMap->setChildKeyFieldName('uid_foreign');
237 $columnMap->setChildSortByFieldName('sorting');
238 }
239 } else {
240 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE);
241 }
242 }
243
244 /**
245 * Sets the column maps.
246 *
247 * @param array $columnMaps The column maps stored in a flat array.
248 * @return void
249 */
250 public function setColumnMaps(array $columnMaps) {
251 $this->columnMaps = $columnMaps;
252 }
253
254 /**
255 * Adds a given column map to the data map.
256 *
257 * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
258 * @return void
259 */
260 public function addColumnMap(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap) {
261 $this->columnMaps[$columnMap->getPropertyName()] = $columnMap;
262 }
263
264 /**
265 * Builds a column map out of the given column name, type of value (optional), and type of
266 * relation (optional) and adds it to the data map.
267 *
268 * @param string $columnName The column name
269 * @param string $propertyName The property name
270 * @param string $propertyType The type of value (default: string)
271 * @param string $typeOfRelation The type of relation (default: none)
272 * @return Tx_Extbase_Persistence_Mapper_DataMap Returns itself for a fluent interface
273 */
274 public function addColumn($columnName, $propertyName = '', $propertyType = Tx_Extbase_Persistence_PropertyType::STRING, $typeOfRelation = Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE) {
275 if (empty($propertyName)) {
276 $propertyName = Tx_Extbase_Utility_Plugin::convertUnderscoredToLowerCamelCase($columnName);
277 }
278
279 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
280 $columnMap->setPropertyType($propertyType);
281 $columnMap->setTypeOfRelation($typeOfRelation);
282 $this->addColumnMap($columnMap);
283 return $this;
284 }
285
286 /**
287 * Returns all column maps
288 *
289 * @return array The column maps
290 */
291 public function getColumnMaps() {
292 return $this->columnMaps;
293 }
294
295 /**
296 * Returns the column map corresponding to the given property name.
297 *
298 * @param string $propertyName
299 * @return Tx_Extbase_Persistence_Mapper_ColumnMap|NULL The column map or NULL if no corresponding column map was found.
300 */
301 public function getColumnMap($propertyName) {
302 return $this->columnMaps[$propertyName];
303 }
304
305 /**
306 * Returns TRUE if the property is persistable (configured in $TCA)
307 *
308 * @param string $propertyName The property name
309 * @return boolean TRUE if the property is persistable (configured in $TCA)
310 */
311 public function isPersistableProperty($propertyName) {
312 return isset($this->columnMaps[$propertyName]);
313 }
314
315 /**
316 * Check if versioning is enabled .
317 *
318 * @return boolean
319 */
320 public function isVersionable() {
321 return ($GLOBALS['TCA'] [$this->tableName] ['ctrl'] ['versioningWS'] === '1');
322 }
323
324 /**
325 * Returns TRUE if the table has a pid column holding the id of the page the record is virtually stored on.
326 * Currently we don't support tables without a pid column.
327 *
328 * @return boolean The result
329 */
330 public function hasPidColumn() {
331 // TODO Should we implement a check for having a pid column?
332 return TRUE;
333 }
334
335 /**
336 * Returns the name of a column holding the timestamp the record was modified
337 *
338 * @return string The field name
339 */
340 public function getTimestampColumnName() {
341 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp'];
342 }
343
344 /**
345 * Returns TRUE if the table has a column holding the timestamp the record was modified
346 *
347 * @return boolean The result
348 */
349 public function hasTimestampColumn() {
350 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp']);
351 }
352
353 /**
354 * Returns the name of a column holding the creation date timestamp
355 *
356 * @return string The field name
357 */
358 public function getCreationDateColumnName() {
359 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate'];
360 }
361
362 /**
363 * Returns TRUE if the table has olumn holding the creation date timestamp
364 *
365 * @return boolean The result
366 */
367 public function hasCreationDateColumn() {
368 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate']);
369 }
370
371 /**
372 * Returns the name of a column holding the uid of the back-end user who created this record
373 *
374 * @return string The field name
375 */
376 public function getCreatorUidColumnName() {
377 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id'];
378 }
379
380 /**
381 * Returns TRUE if the table has a column holding the uid of the back-end user who created this record
382 *
383 * @return boolean The result
384 */
385 public function hasCreatorUidColumn() {
386 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id']);
387 }
388
389 /**
390 * Returns the name of a column indicating the 'deleted' state of the row
391 *
392 * @return string The field name
393 */
394 public function getDeletedColumnName() {
395 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete'];
396 }
397
398 /**
399 * Returns TRUE if the table has a column indicating the 'deleted' state of the row
400 *
401 * @return boolean The result
402 */
403 public function hasDeletedColumn() {
404 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete']);
405 }
406
407 /**
408 * Returns the name of a column indicating the 'hidden' state of the row
409 *
410 * @return string The field name
411 */
412 public function getHiddenColumnName() {
413 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled'];
414 }
415
416 /**
417 * Returns TRUE if the table has a column indicating the 'hidden' state of the row
418 *
419 * @return boolean The result
420 */
421 public function hasHiddenColumn() {
422 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled']);
423 }
424
425 /**
426 * Returns the name of a column holding the timestamp the record should not displayed before
427 *
428 * @return string The field name
429 */
430 public function getStartTimeColumnName() {
431 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime'];
432 }
433
434 /**
435 * Returns TRUE if the table has a column holding the timestamp the record should not displayed before
436 *
437 * @return boolean The result
438 */
439 public function hasStartTimeColumn() {
440 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime']);
441 }
442
443 /**
444 * Returns the name of a column holding the timestamp the record should not displayed afterwards
445 *
446 * @return string The field name
447 */
448 public function getEndTimeColumnName() {
449 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime'];
450 }
451
452 /**
453 * Returns TRUE if the table has a column holding the timestamp the record should not displayed afterwards
454 *
455 * @return boolean The result
456 */
457 public function hasEndTimeColumn() {
458 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime']);
459 }
460
461 /**
462 * Returns the name of a column holding the uid of the front-end user group which is allowed to edit this record
463 *
464 * @return string The field name
465 */
466 public function getFrontEndUserGroupColumnName() {
467 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group'];
468 }
469
470 /**
471 * Returns TRUE if the table has a column holding the uid of the front-end user group which is allowed to edit this record
472 *
473 * @return boolean The result
474 */
475 public function hasFrontEndUserGroupColumn() {
476 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group']);
477 }
478
479 /**
480 * Converts a field name to the property name. It respects property name aliases defined in $TCA.
481 *
482 * @param string $fieldName The field name
483 * @return string $propertyName The property name
484 */
485 public function convertFieldNameToPropertyName($fieldName) {
486 $propertyName = $fieldName;
487 return $propertyName; // TODO Implement aliases for field names (see also convertPropertyNameToFieldName())
488 }
489
490 /**
491 * Converts a preoperty name to the field name. It respects property name aliases defined in $TCA.
492 *
493 * @param string $fieldName The field name
494 * @return string $propertyName The property name
495 */
496 public function convertPropertyNameToFieldName($propertyName) {
497 $fieldName = $propertyName;
498 return $fieldName;
499 }
500
501 /**
502 * Converts the given string into the given type
503 *
504 * @param integer $type one of the constants defined in Tx_Extbase_Persistence_PropertyType
505 * @param string $string a string representing a value of the given type
506 *
507 * @return string|int|float|DateTime|boolean
508 */
509 public function convertFieldValueToPropertyValue($type, $string) {
510 switch ($type) {
511 case Tx_Extbase_Persistence_PropertyType::LONG:
512 return (int) $string;
513 case Tx_Extbase_Persistence_PropertyType::DOUBLE:
514 case Tx_Extbase_Persistence_PropertyType::DECIMAL:
515 return (float) $string;
516 case Tx_Extbase_Persistence_PropertyType::DATE:
517 return new DateTime(strftime('%Y-%m-%d %H:%M:%S', $string)); // TODO Check for Time Zone issues
518 case Tx_Extbase_Persistence_PropertyType::BOOLEAN:
519 return (boolean) $string;
520 default:
521 return $string;
522 }
523 }
524
525 /**
526 * Converts a value from a property type to a database field type
527 *
528 * @param mixed $propertyValue The property value
529 * @return mixed The converted value
530 */
531 public function convertPropertyValueToFieldValue($propertyValue) {
532 if (is_bool($propertyValue)) {
533 $convertedValue = $propertyValue ? 1 : 0;
534 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
535 $convertedValue = $propertyValue->getUid();
536 } elseif (is_a($propertyValue, 'DateTime')) {
537 $convertedValue = $propertyValue->format('U');
538 } elseif (is_int($propertyValue)) {
539 $convertedValue = $propertyValue;
540 } else {
541 $convertedValue = $propertyValue;
542 }
543 return $convertedValue;
544 }
545
546 }