Extbase:
[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 extbase
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 = '') {
61 $this->setClassName($className);
62 if (empty($tableName)) {
63 $this->setTableName(strtolower($className));
64 } else {
65 $this->setTableName($tableName);
66 }
67 $this->initialize();
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() {
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 // TODO convert underscore column names to lowercamelcase
120 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $this);
121 $this->setPropertyType($columnMap, $columnConfiguration);
122 // TODO support for IRRE
123 // TODO support for MM_insert_fields and MM_match_fields
124 // SK: are the above TODOs still valid?
125 $this->setRelations($columnMap, $columnConfiguration);
126 $this->addColumnMap($columnMap);
127 }
128 }
129 }
130
131 /**
132 * Adds available common columns (e.g. tstamp or crdate) to the data map. It takes the configured column names
133 * into account.
134 *
135 * @return void
136 */
137 protected function addCommonColumns() {
138 // TODO Decide whether we should add pid and uid columns by default
139 $this->addColumn('uid', Tx_Extbase_Persistence_PropertyType::LONG);
140 $this->addColumn('pid', Tx_Extbase_Persistence_PropertyType::LONG);
141 if ($this->hasTimestampColumn()) {
142 $this->addColumn($this->getTimestampColumnName(), Tx_Extbase_Persistence_PropertyType::DATE);
143 }
144 if ($this->hasCreationDateColumn()) {
145 $this->addColumn($this->getCreationDateColumnName(), Tx_Extbase_Persistence_PropertyType::DATE);
146 }
147 if ($this->hasCreatorUidColumn()) {
148 $this->addColumn($this->getCreatorUidColumnName(), Tx_Extbase_Persistence_PropertyType::LONG);
149 }
150 if ($this->hasDeletedColumn()) {
151 $this->addColumn($this->getDeletedColumnName(), Tx_Extbase_Persistence_PropertyType::BOOLEAN);
152 }
153 if ($this->hasHiddenColumn()) {
154 $this->addColumn($this->getHiddenColumnName(), Tx_Extbase_Persistence_PropertyType::BOOLEAN);
155 }
156 }
157
158 /**
159 * This method tries to determine the type of value the column hold by inspectiong the $TCA column configuration
160 * and sets it.
161 *
162 * @param string $columnMap The column map
163 * @param string $columnConfiguration The column configuration from $TCA
164 * @return void
165 */
166 protected function setPropertyType(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
167 $evalConfiguration = t3lib_div::trimExplode(',', $columnConfiguration['config']['eval']);
168 if (in_array('date', $evalConfiguration) || in_array('datetime', $evalConfiguration)) {
169 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::DATE);
170 } elseif ($columnConfiguration['config']['type'] === 'check' && empty($columnConfiguration['config']['items'])) {
171 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::BOOLEAN);
172 } elseif (in_array('int', $evalConfiguration)) {
173 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::LONG);
174 } elseif (in_array('double2', $evalConfiguration)) {
175 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::DOUBLE);
176 } else {
177 if (isset($columnConfiguration['config']['foreign_table'])) {
178 if ($columnConfiguration['config']['loadingStrategy'] === 'proxy') {
179 $columnMap->setLoadingStrategy(Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_PROXY);
180 } else {
181 $columnMap->setLoadingStrategy(Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_EAGER);
182 }
183 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::REFERENCE);
184 } else {
185 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::STRING);
186 }
187 }
188 }
189
190 /**
191 * This method tries to determine the type of type of relation to other tables and sets it based on
192 * the $TCA column configuration
193 *
194 * @param string $columnMap The column map
195 * @param string $columnConfiguration The column configuration from $TCA
196 * @return void
197 */
198 protected function setRelations(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
199 if (isset($columnConfiguration['config']['foreign_table']) && !isset($columnConfiguration['config']['MM'])) {
200 if ($columnConfiguration['config']['maxitems'] == 1) {
201 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE);
202 $columnMap->setChildClassName($columnConfiguration['config']['foreign_class']);
203 $columnMap->setChildTableName($columnConfiguration['config']['foreign_table']);
204 $columnMap->setChildTableWhere($columnConfiguration['config']['foreign_table_where']);
205 $columnMap->setChildSortbyFieldName($columnConfiguration['config']['foreign_sortby']);
206 $columnMap->setParentKeyFieldName($columnConfiguration['config']['foreign_field']);
207 $columnMap->setParentTableFieldName($columnConfiguration['config']['foreign_table_field']);
208 } else {
209 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY);
210 $columnMap->setChildClassName($columnConfiguration['config']['foreign_class']);
211 $columnMap->setChildTableName($columnConfiguration['config']['foreign_table']);
212 $columnMap->setChildTableWhere($columnConfiguration['config']['foreign_table_where']);
213 $columnMap->setChildSortbyFieldName($columnConfiguration['config']['foreign_sortby']);
214 $columnMap->setParentKeyFieldName($columnConfiguration['config']['foreign_field']);
215 $columnMap->setParentTableFieldName($columnConfiguration['config']['foreign_table_field']);
216 }
217 // TODO Support MM_match_fields
218 } elseif (array_key_exists('MM', $columnConfiguration['config'])) {
219 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY);
220 $columnMap->setChildClassName($columnConfiguration['config']['foreign_class']);
221 $columnMap->setChildTableName($columnConfiguration['config']['foreign_table']);
222 $columnMap->setRelationTableName($columnConfiguration['config']['MM']);
223 } else {
224 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE);
225 }
226 }
227
228 /**
229 * Sets the column maps.
230 *
231 * @param array $columnMaps The column maps stored in a flat array.
232 * @return void
233 */
234 public function setColumnMaps(array $columnMaps) {
235 $this->columnMaps = $columnMaps;
236 }
237
238 /**
239 * Adds a given column map to the data map.
240 *
241 * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
242 * @return void
243 */
244 public function addColumnMap(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap) {
245 $this->columnMaps[$columnMap->getPropertyName()] = $columnMap;
246 }
247
248 /**
249 * Builds a column map out of the given column name, type of value (optional), and type of
250 * relation (optional) and adds it to the data map.
251 *
252 * @param string $columnName The column name
253 * @param string $propertyType The type of value (default: string)
254 * @param string $typeOfRelation The type of relation (default: none)
255 * @return Tx_Extbase_Persistence_Mapper_DataMap Returns itself for a fluent interface
256 */
257 public function addColumn($columnName, $propertyType = Tx_Extbase_Persistence_PropertyType::STRING, $typeOfRelation = Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE) {
258 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName);
259 $columnMap->setPropertyType($propertyType);
260 $columnMap->setTypeOfRelation($typeOfRelation);
261 $this->addColumnMap($columnMap);
262 return $this;
263 }
264
265 /**
266 * Returns all column maps
267 *
268 * @return array The column maps
269 */
270 public function getColumnMaps() {
271 return $this->columnMaps;
272 }
273
274 /**
275 * Returns the column map corresponding to the given property name.
276 *
277 * @param string $propertyName
278 * @return Tx_Extbase_Persistence_Mapper_ColumnMap|NULL The column map or NULL if no corresponding column map was found.
279 */
280 public function getColumnMap($propertyName) {
281 return $this->columnMaps[$propertyName];
282 }
283
284 /**
285 * Returns TRUE if the property is persistable (configured in $TCA)
286 *
287 * @param string $propertyName The property name
288 * @return boolean TRUE if the property is persistable (configured in $TCA)
289 */
290 public function isPersistableProperty($propertyName) {
291 return isset($this->columnMaps[$propertyName]);
292 }
293
294 /**
295 * Check if versioning is enabled .
296 *
297 * @return boolean
298 */
299 public function isVersionable() {
300 return ($GLOBALS['TCA'] [$this->tableName] ['ctrl'] ['versioningWS'] === '1');
301 }
302
303 /**
304 * Returns TRUE if the table has a pid column holding the id of the page the record is virtually stored on.
305 * Currently we don't support tables without a pid column.
306 *
307 * @return boolean The result
308 */
309 public function hasPidColumn() {
310 // TODO Should we implement a check for having a pid column?
311 return TRUE;
312 }
313
314 /**
315 * Returns the name of a column holding the timestamp the record was modified
316 *
317 * @return string The field name
318 */
319 public function getTimestampColumnName() {
320 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp'];
321 }
322
323 /**
324 * Returns TRUE if the table has a column holding the timestamp the record was modified
325 *
326 * @return boolean The result
327 */
328 public function hasTimestampColumn() {
329 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp']);
330 }
331
332 /**
333 * Returns the name of a column holding the creation date timestamp
334 *
335 * @return string The field name
336 */
337 public function getCreationDateColumnName() {
338 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate'];
339 }
340
341 /**
342 * Returns TRUE if the table has olumn holding the creation date timestamp
343 *
344 * @return boolean The result
345 */
346 public function hasCreationDateColumn() {
347 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate']);
348 }
349
350 /**
351 * Returns the name of a column holding the uid of the back-end user who created this record
352 *
353 * @return string The field name
354 */
355 public function getCreatorUidColumnName() {
356 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id'];
357 }
358
359 /**
360 * Returns TRUE if the table has a column holding the uid of the back-end user who created this record
361 *
362 * @return boolean The result
363 */
364 public function hasCreatorUidColumn() {
365 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id']);
366 }
367
368 /**
369 * Returns the name of a column indicating the 'deleted' state of the row
370 *
371 * @return string The field name
372 */
373 public function getDeletedColumnName() {
374 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete'];
375 }
376
377 /**
378 * Returns TRUE if the table has a column indicating the 'deleted' state of the row
379 *
380 * @return boolean The result
381 */
382 public function hasDeletedColumn() {
383 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete']);
384 }
385
386 /**
387 * Returns the name of a column indicating the 'hidden' state of the row
388 *
389 * @return string The field name
390 */
391 public function getHiddenColumnName() {
392 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled'];
393 }
394
395 /**
396 * Returns TRUE if the table has a column indicating the 'hidden' state of the row
397 *
398 * @return boolean The result
399 */
400 public function hasHiddenColumn() {
401 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled']);
402 }
403
404 /**
405 * Returns the name of a column holding the timestamp the record should not displayed before
406 *
407 * @return string The field name
408 */
409 public function getStartTimeColumnName() {
410 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime'];
411 }
412
413 /**
414 * Returns TRUE if the table has a column holding the timestamp the record should not displayed before
415 *
416 * @return boolean The result
417 */
418 public function hasStartTimeColumn() {
419 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime']);
420 }
421
422 /**
423 * Returns the name of a column holding the timestamp the record should not displayed afterwards
424 *
425 * @return string The field name
426 */
427 public function getEndTimeColumnName() {
428 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime'];
429 }
430
431 /**
432 * Returns TRUE if the table has a column holding the timestamp the record should not displayed afterwards
433 *
434 * @return boolean The result
435 */
436 public function hasEndTimeColumn() {
437 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime']);
438 }
439
440 /**
441 * Returns the name of a column holding the uid of the front-end user group which is allowed to edit this record
442 *
443 * @return string The field name
444 */
445 public function getFrontEndUserGroupColumnName() {
446 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group'];
447 }
448
449 /**
450 * Returns TRUE if the table has a column holding the uid of the front-end user group which is allowed to edit this record
451 *
452 * @return boolean The result
453 */
454 public function hasFrontEndUserGroupColumn() {
455 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group']);
456 }
457
458 /**
459 * Converts a field name to the property name. It respects property name aliases defined in $TCA.
460 *
461 * @param string $fieldName The field name
462 * @return string $propertyName The property name
463 */
464 public function convertFieldNameToPropertyName($fieldName) {
465 $propertyName = $fieldName;
466 return $propertyName; // TODO Implement aliases for field names (see also convertPropertyNameToFieldName())
467 }
468
469 /**
470 * Converts a preoperty name to the field name. It respects property name aliases defined in $TCA.
471 *
472 * @param string $fieldName The field name
473 * @return string $propertyName The property name
474 */
475 public function convertPropertyNameToFieldName($propertyName) {
476 $fieldName = $propertyName;
477 return $fieldName;
478 }
479
480 /**
481 * Converts the given string into the given type
482 *
483 * @param integer $type one of the constants defined in Tx_Extbase_Persistence_PropertyType
484 * @param string $string a string representing a value of the given type
485 *
486 * @return string|int|float|DateTime|boolean
487 */
488 public function convertFieldValueToPropertyValue($type, $string) {
489 switch ($type) {
490 case Tx_Extbase_Persistence_PropertyType::LONG:
491 return (int) $string;
492 case Tx_Extbase_Persistence_PropertyType::DOUBLE:
493 case Tx_Extbase_Persistence_PropertyType::DECIMAL:
494 return (float) $string;
495 case Tx_Extbase_Persistence_PropertyType::DATE:
496 return new DateTime(strftime('%Y-%m-%d %H:%M:%S', $string)); // TODO Check for Time Zone issues
497 case Tx_Extbase_Persistence_PropertyType::BOOLEAN:
498 return (boolean) $string;
499 default:
500 return $string;
501 }
502 }
503
504 /**
505 * Converts a value from a property type to a database field type
506 *
507 * @param mixed $propertyValue The property value
508 * @return mixed The converted value
509 */
510 public function convertPropertyValueToFieldValue($propertyValue) {
511 if (is_bool($propertyValue)) {
512 $convertedValue = $propertyValue ? 1 : 0;
513 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
514 $convertedValue = $propertyValue->getUid();
515 } elseif ($propertyValue instanceof DateTime) {
516 $convertedValue = $propertyValue->format('U');
517 } elseif (is_int($propertyValue)) {
518 $convertedValue = $propertyValue;
519 } else {
520 // FIXME Full quote string does not work with the Typo3DbBackend parsing
521 $convertedValue = $propertyValue;
522 }
523 return $convertedValue;
524 }
525
526 }