Extbase:
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Storage / Typo3DbBackend.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
6 * All rights reserved
7 *
8 * This class is a backport of the corresponding class of FLOW3.
9 * All credits go to the v5 team.
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * A Storage backend
30 *
31 * @package Extbase
32 * @subpackage Persistence
33 * @version $Id: $
34 * @scope prototype
35 */
36 class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persistence_Storage_BackendInterface {
37
38 /**
39 * The TYPO3 database object
40 *
41 * @var t3lib_db
42 */
43 protected $databaseHandle;
44
45 /**
46 * The TYPO3 page select object. Used for language and workspace overlay
47 *
48 * @var t3lib_pageSelect
49 */
50 protected $pageSelectObject;
51
52 /**
53 * Constructs this Storage Backend instance
54 */
55 public function __construct() {
56 $this->databaseHandle = $GLOBALS['TYPO3_DB'];
57 }
58
59 /**
60 * Adds a row to the storage
61 *
62 * @param string $tableName The database table name
63 * @param array $row The row to be inserted
64 * @return int The uid of the inserted row
65 */
66 public function addRow($tableName, array $row) {
67 $fields = array();
68 $values = array();
69 $parameters = array();
70 unset($row['uid']); // TODO Check if the offset exists
71 foreach ($row as $columnName => $value) {
72 $fields[] = $columnName;
73 $values[] = '?';
74 $parameters[] = $value;
75 }
76
77 $sqlString = 'INSERT INTO ' . $tableName . ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')';
78 $this->replacePlaceholders($sqlString, $parameters);
79
80 $this->databaseHandle->sql_query($sqlString);
81 return $this->databaseHandle->sql_insert_id();
82 }
83
84 /**
85 * Updates a row in the storage
86 *
87 * @param string $tableName The database table name
88 * @param array $row The row to be updated
89 * @return void
90 */
91 public function updateRow($tableName, array $row) {
92 if (!isset($row['uid'])) throw new InvalidArgumentException('The given row must contain a value for "uid".');
93 $uid = (int)$row['uid'];
94 unset($row['uid']);
95 $fields = array();
96 $parameters = array();
97 foreach ($row as $columnName => $value) {
98 $fields[] = $columnName . '=?';
99 $parameters[] = $value;
100 }
101 $parameters[] = $uid;
102
103 $sqlString = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $fields) . ' WHERE uid=?';
104 $this->replacePlaceholders($sqlString, $parameters);
105
106 return $this->databaseHandle->sql_query($sqlString);
107 }
108
109 /**
110 * Deletes a row in the storage
111 *
112 * @param string $tableName The database table name
113 * @param array $uid The uid of the row to be deleted
114 * @return void
115 */
116 public function removeRow($tableName, $uid) {
117 $sqlString = 'DELETE FROM ' . $tableName . ' WHERE uid=?';
118 $this->replacePlaceholders($sqlString, array((int)$uid));
119 return $this->databaseHandle->sql_query($sqlString);
120 }
121
122 /**
123 * Returns an array with tuples matching the query.
124 *
125 * @param Tx_Extbase_Persistence_QOM_QueryObjectModelInterface $query
126 * @return array The matching tuples
127 */
128 public function getRows(Tx_Extbase_Persistence_QOM_QueryObjectModelInterface $query) {
129 $sql = array();
130 $parameters = array();
131 $tuples = array();
132
133 $this->parseSource($query, $sql, $parameters);
134 if ($query->getConstraint() !== NULL) {
135 $this->parseConstraint($query->getConstraint(), $sql, $parameters, $query->getBoundVariableValues());
136 }
137
138
139 $sqlString = 'SELECT ' . implode(',', $sql['fields']) . ' FROM ' . implode(' ', $sql['tables']);
140 if (!empty($sql['where'])) {
141 $sqlString .= ' WHERE ' . implode(' AND ', $sql['where']);
142 }
143 $this->replacePlaceholders($sqlString, $parameters);
144 $result = $this->databaseHandle->sql_query($sqlString);
145 if ($result) {
146 $tuples = $this->getRowsFromResult($query->getSelectorName(), $result);
147 }
148 return $tuples;
149 }
150
151 /**
152 * Checks if a Value Object equal to the given Object exists in the data base
153 *
154 * @param array $properties The properties of the Value Object
155 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The Data Map
156 * @return array The matching tuples
157 */
158 public function hasValueObject(array $properties, Tx_Extbase_Persistence_Mapper_DataMap $dataMap) {
159 $fields = array();
160 $parameters = array();
161 foreach ($properties as $propertyName => $propertyValue) {
162 if ($dataMap->isPersistableProperty($propertyName) && ($propertyName !== 'uid')) {
163 $fields[] = $dataMap->getColumnMap($propertyName)->getColumnName() . '=?';
164 $parameters[] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
165 }
166 }
167
168 $sqlString = 'SELECT * FROM ' . $dataMap->getTableName() . ' WHERE ' . implode(' AND ', $fields);
169 $this->replacePlaceholders($sqlString, $parameters);
170 $res = $this->databaseHandle->sql_query($sqlString);
171 $row = $this->databaseHandle->sql_fetch_assoc($res);
172 if ($row !== FALSE) {
173 return $row['uid'];
174 } else {
175 return FALSE;
176 }
177 }
178
179 /**
180 * Transforms a Query Source into SQL and parameter arrays
181 *
182 * @param Tx_Extbase_Persistence_QOM_QueryObjectModel $query
183 * @param array &$sql
184 * @param array &$parameters
185 * @return void
186 */
187 protected function parseSource(Tx_Extbase_Persistence_QOM_QueryObjectModel $query, array &$sql, array &$parameters) {
188 $source = $query->getSource();
189 $sql['where'] = array();
190 if ($source instanceof Tx_Extbase_Persistence_QOM_SelectorInterface) {
191 $selectorName = $source->getSelectorName();
192 $sql['fields'][] = $selectorName . '.*';
193 $sql['tables'][] = $selectorName;
194 if ($query->useEnableFields()) {
195 $sql['where'][] = $this->getEnableFields($selectorName);
196 }
197 } elseif ($source instanceof Tx_Extbase_Persistence_QOM_JoinInterface) {
198 $this->parseJoin($source, $sql, $parameters);
199 }
200 }
201
202 /**
203 * Transforms a Join into SQL and parameter arrays
204 *
205 * @param Tx_Extbase_Persistence_QOM_JoinInterface $join
206 * @param array &$sql
207 * @param array &$parameters
208 * @return void
209 */
210 protected function parseJoin(Tx_Extbase_Persistence_QOM_JoinInterface $join, array &$sql, array &$parameters) {
211 $leftSelectorName = $join->getLeft()->getSelectorName();
212 $rightSelectorName = $join->getRight()->getSelectorName();
213
214 $sql['fields'][] = $leftSelectorName . '.*';
215 $sql['fields'][] = $rightSelectorName . '.*';
216
217 // TODO Implement support for different join types and nested joins
218 $sql['tables'][] = $leftSelectorName . ' INNER JOIN ' . $rightSelectorName;
219
220 $joinCondition = $join->getJoinCondition();
221 // TODO Check the parsing of the join
222 if ($joinCondition instanceof Tx_Extbase_Persistence_QOM_EquiJoinCondition) {
223 $sql['tables'][] = 'ON ' . $joinCondition->getSelector1Name() . '.' . $joinCondition->getProperty1Name() . ' = ' . $joinCondition->getSelector2Name() . '.' . $joinCondition->getProperty2Name();
224 }
225 // TODO Implement childtableWhere
226 }
227
228 /**
229 * Transforms a constraint into SQL and parameter arrays
230 *
231 * @param Tx_Extbase_Persistence_QOM_ConstraintInterface $constraint
232 * @param array &$sql
233 * @param array &$parameters
234 * @param array $boundVariableValues
235 * @return void
236 */
237 protected function parseConstraint(Tx_Extbase_Persistence_QOM_ConstraintInterface $constraint = NULL, array &$sql, array &$parameters, array $boundVariableValues) {
238 if ($constraint instanceof Tx_Extbase_Persistence_QOM_AndInterface) {
239 $sql['where'][] = '(';
240 $this->parseConstraint($constraint->getConstraint1(), $sql, $parameters, $boundVariableValues);
241 $sql['where'][] = ' AND ';
242 $this->parseConstraint($constraint->getConstraint2(), $sql, $parameters, $boundVariableValues);
243 $sql['where'][] = ') ';
244 } elseif ($constraint instanceof Tx_Extbase_Persistence_QOM_OrInterface) {
245 $sql['where'][] = '(';
246 $this->parseConstraint($constraint->getConstraint1(), $sql, $parameters, $boundVariableValues);
247 $sql['where'][] = ' OR ';
248 $this->parseConstraint($constraint->getConstraint2(), $sql, $parameters, $boundVariableValues);
249 $sql['where'][] = ') ';
250 } elseif ($constraint instanceof Tx_Extbase_Persistence_QOM_NotInterface) {
251 $sql['where'][] = '(NOT ';
252 $this->parseConstraint($constraint->getConstraint(), $sql, $parameters, $boundVariableValues);
253 $sql['where'][] = ') ';
254 } elseif ($constraint instanceof Tx_Extbase_Persistence_QOM_ComparisonInterface) {
255 $this->parseComparison($constraint, $sql, $parameters, $boundVariableValues);
256 } elseif ($constraint instanceof Tx_Extbase_Persistence_QOM_RelatedInterface) {
257 $this->parseRelated($constraint, $sql, $parameters, $boundVariableValues);
258 }
259 }
260
261 /**
262 * Parse a Comparison into SQL and parameter arrays.
263 *
264 * @param Tx_Extbase_Persistence_QOM_ComparisonInterface $comparison The comparison to parse
265 * @param array &$sql SQL query parts to add to
266 * @param array &$parameters Parameters to bind to the SQL
267 * @param array $boundVariableValues The bound variables in the query and their values
268 * @return void
269 */
270 protected function parseComparison(Tx_Extbase_Persistence_QOM_ComparisonInterface $comparison, array &$sql, array &$parameters, array $boundVariableValues) {
271 $this->parseDynamicOperand($comparison->getOperand1(), $comparison->getOperator(), $sql, $parameters);
272
273 if ($comparison->getOperand2() instanceof Tx_Extbase_Persistence_QOM_BindVariableValueInterface) {
274 $parameters[] = $boundVariableValues[$comparison->getOperand2()->getBindVariableName()];
275 } elseif ($comparison->getOperand2() instanceof Tx_Extbase_Persistence_QOM_LiteralInterface) {
276 $parameters[] = $comparison->getOperand2()->getLiteralValue();
277 }
278 }
279
280 /**
281 * Parse a DynamicOperand into SQL and parameter arrays.
282 *
283 * @param Tx_Extbase_Persistence_QOM_DynamicOperandInterface $operand
284 * @param string $operator One of the JCR_OPERATOR_* constants
285 * @param array $boundVariableValues
286 * @param array &$parameters
287 * @param string $valueFunction an aoptional SQL function to apply to the operand value
288 * @return void
289 */
290 protected function parseDynamicOperand(Tx_Extbase_Persistence_QOM_DynamicOperandInterface $operand, $operator, array &$sql, array &$parameters, $valueFunction = NULL) {
291 if ($operand instanceof Tx_Extbase_Persistence_QOM_LowerCaseInterface) {
292 $this->parseDynamicOperand($operand->getOperand(), $operator, $sql, $parameters, 'LOWER');
293 } elseif ($operand instanceof Tx_Extbase_Persistence_QOM_UpperCaseInterface) {
294 $this->parseDynamicOperand($operand->getOperand(), $operator, $sql, $parameters, 'UPPER');
295 } elseif ($operand instanceof Tx_Extbase_Persistence_QOM_PropertyValueInterface) {
296 $selectorName = $operand->getSelectorName();
297 $operator = $this->resolveOperator($operator);
298
299 $constraintSQL = '(';
300 if ($valueFunction === NULL) {
301 $constraintSQL .= (!empty($selectorName) ? $selectorName . '.' : '') . $operand->getPropertyName() . ' ' . $operator . ' ?';
302 } else {
303 $constraintSQL .= $valueFunction . '(' . (!empty($selectorName) ? $selectorName . '.' : '') . $operand->getPropertyName() . ' ' . $operator . ' ?';
304 }
305 $constraintSQL .= ') ';
306
307 $sql['where'][] = $constraintSQL;
308 }
309 }
310
311 /**
312 * Returns the SQL operator for the given JCR operator type.
313 *
314 * @param string $operator One of the JCR_OPERATOR_* constants
315 * @return string an SQL operator
316 */
317 protected function resolveOperator($operator) {
318 switch ($operator) {
319 case Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO:
320 $operator = '=';
321 break;
322 case Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_OPERATOR_NOT_EQUAL_TO:
323 $operator = '!=';
324 break;
325 case Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_OPERATOR_LESS_THAN:
326 $operator = '<';
327 break;
328 case Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO:
329 $operator = '<=';
330 break;
331 case Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_OPERATOR_GREATER_THAN:
332 $operator = '>';
333 break;
334 case Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO:
335 $operator = '>=';
336 break;
337 case Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_OPERATOR_LIKE:
338 $operator = 'LIKE';
339 break;
340 default:
341 throw new Tx_Extbase_Persistence_Exception('Unsupported operator encountered.', 1242816073);
342 }
343
344 return $operator;
345 }
346
347 /**
348 * Replace query placeholders in a query part by the given
349 * parameters.
350 *
351 * @param string $queryPart The query part with placeholders
352 * @param array $parameters The parameters
353 * @return string The query part with replaced placeholders
354 */
355 protected function replacePlaceholders(&$sqlString, array $parameters) {
356 foreach ($parameters as $parameter) {
357 $markPosition = strpos($sqlString, '?');
358 if ($markPosition !== FALSE) {
359 $sqlString = substr($sqlString, 0, $markPosition) . '"' . $parameter . '"' . substr($sqlString, $markPosition + 1);
360 }
361 }
362 }
363
364 /**
365 * Returns the enable fields part of a WHERE query
366 *
367 * @return string WHERE query part
368 */
369 protected function getEnableFields($selectorName) {
370 return substr($GLOBALS['TSFE']->sys_page->enableFields($selectorName),4);
371 }
372
373 /**
374 * Transforms a Resource from a database query to an array of rows. Performs the language and
375 * workspace overlay before.
376 *
377 * @return array The result as an array of rows (tuples)
378 */
379 protected function getRowsFromResult($tableName, $res) {
380 $rows = array();
381 while ($row = $this->databaseHandle->sql_fetch_assoc($res)) {
382 $row = $this->doLanguageAndWorkspaceOverlay($tableName, $row);
383 if (is_array($row)) {
384 // TODO Check if this is necessary, maybe the last line is enough
385 $arrayKeys = range(0,count($row));
386 array_fill_keys($arrayKeys, $row);
387 $rows[] = $row;
388 }
389 }
390 return $rows;
391 }
392
393 /**
394 * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
395 * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
396 *
397 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap
398 * @param array $row The row array (as reference)
399 * @param string $languageUid The language id
400 * @param string $workspaceUidUid The workspace id
401 * @return void
402 */
403 protected function doLanguageAndWorkspaceOverlay($tableName, array $row, $languageUid = NULL, $workspaceUid = NULL) {
404 if (!($this->pageSelectObject instanceof t3lib_pageSelect)) {
405 if (TYPO3_MODE == 'FE') {
406 if (is_object($GLOBALS['TSFE'])) {
407 $this->pageSelectObject = $GLOBALS['TSFE']->sys_page;
408 if ($languageUid === NULL) {
409 $languageUid = $GLOBALS['TSFE']->sys_language_content;
410 }
411 } else {
412 require_once(PATH_t3lib . 'class.t3lib_page.php');
413 $this->pageSelectObject = t3lib_div::makeInstance('t3lib_pageSelect');
414 if ($languageUid === NULL) {
415 $languageUid = intval(t3lib_div::_GP('L'));
416 }
417 }
418 if ($workspaceUid !== NULL) {
419 $this->pageSelectObject->versioningWorkspaceId = $workspaceUid;
420 }
421 } else {
422 require_once(PATH_t3lib . 'class.t3lib_page.php');
423 $this->pageSelectObject = t3lib_div::makeInstance( 't3lib_pageSelect' );
424 //$this->pageSelectObject->versioningPreview = TRUE;
425 if ($workspaceUid === NULL) {
426 $workspaceUid = $GLOBALS['BE_USER']->workspace;
427 }
428 $this->pageSelectObject->versioningWorkspaceId = $workspaceUid;
429 }
430 }
431
432 $this->pageSelectObject->versionOL($tableName, $row, TRUE);
433 $row = $this->pageSelectObject->getRecordOverlay($tableName, $row, $languageUid, ''); //'hideNonTranslated'
434 // TODO Skip if empty languageoverlay (languagevisibility)
435 return $row;
436 }
437
438 }
439
440 ?>