[BUGFIX] Recycler ignores depth setting
[Packages/TYPO3.CMS.git] / typo3 / sysext / recycler / Classes / Domain / Model / DeletedRecords.php
1 <?php
2 namespace TYPO3\CMS\Recycler\Domain\Model;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2009-2013 Julian Kleinhans <typo3@kj187.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 /**
28 * Model class for the 'recycler' extension.
29 *
30 * @author Julian Kleinhans <typo3@kj187.de>
31 */
32 class DeletedRecords {
33
34 /**
35 * Array with all deleted rows
36 *
37 * @var array
38 */
39 protected $deletedRows = array();
40
41 /**
42 * String with the global limit
43 *
44 * @var string
45 */
46 protected $limit = '';
47
48 /**
49 * Array with all avaiable FE tables
50 *
51 * @var array
52 */
53 protected $table = array();
54
55 /**
56 * Object from helper class
57 *
58 * @var \TYPO3\CMS\Recycler\Utility\RecyclerUtility
59 */
60 protected $recyclerHelper;
61
62 /**
63 * Array with all label fields drom different tables
64 *
65 * @var array
66 */
67 public $label;
68
69 /**
70 * Array with all title fields drom different tables
71 *
72 * @var array
73 */
74 public $title;
75
76 /************************************************************
77 * GET DATA FUNCTIONS
78 *
79 *
80 ************************************************************/
81 /**
82 * Load all deleted rows from $table
83 * If table is not set, it iterates the TCA tables
84 *
85 * @param integer $id: UID from selected page
86 * @param string $table: Tablename
87 * @param integer $depth: How many levels recursive
88 * @param integer $limit: MySQL LIMIT
89 * @param string $filter: Filter text
90 * @return recycler_model_delRecords
91 */
92 public function loadData($id, $table, $depth, $limit = '', $filter = '') {
93 // set the limit
94 $this->limit = trim($limit);
95 if ($table) {
96 if (array_key_exists($table, $GLOBALS['TCA'])) {
97 $this->table[] = $table;
98 $this->setData($id, $table, $depth, $GLOBALS['TCA'][$table]['ctrl'], $filter);
99 }
100 } else {
101 foreach ($GLOBALS['TCA'] as $tableKey => $tableValue) {
102 // only go into this table if the limit allows it
103 if ($this->limit != '') {
104 $parts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $this->limit);
105 // abort loop if LIMIT 0,0
106 if ($parts[0] == 0 && $parts[1] == 0) {
107 break;
108 }
109 }
110 $this->table[] = $tableKey;
111 $this->setData($id, $tableKey, $depth, $tableValue['ctrl'], $filter);
112 }
113 }
114 return $this;
115 }
116
117 /**
118 * Find the total count of deleted records
119 *
120 * @param integer $id: UID from record
121 * @param string $table: Tablename from record
122 * @param integer $depth: How many levels recursive
123 * @param string $filter: Filter text
124 * @return void
125 */
126 public function getTotalCount($id, $table, $depth, $filter) {
127 $deletedRecords = $this->loadData($id, $table, $depth, '', $filter)->getDeletedRows();
128 $countTotal = 0;
129 foreach ($this->table as $tableName) {
130 $countTotal += count($deletedRecords[$tableName]);
131 }
132 return $countTotal;
133 }
134
135 /**
136 * Set all deleted rows
137 *
138 * @param integer $id: UID from record
139 * @param string $table: Tablename from record
140 * @param integer $depth: How many levels recursive
141 * @param array $ctrl: TCA CTRL Array
142 * @param string $filter: Filter text
143 * @return void
144 */
145 protected function setData($id = 0, $table, $depth, $tcaCtrl, $filter) {
146 $id = intval($id);
147 if (array_key_exists('delete', $tcaCtrl)) {
148 // find the 'deleted' field for this table
149 $deletedField = \TYPO3\CMS\Recycler\Utility\RecyclerUtility::getDeletedField($table);
150 // create the filter WHERE-clause
151 if (trim($filter) != '') {
152 $filterWhere = ' AND (' . (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($filter) ? 'uid = ' . $filter . ' OR pid = ' . $filter . ' OR ' : '') . $tcaCtrl['label'] . ' LIKE "%' . $this->escapeValueForLike($filter, $table) . '%"' . ')';
153 }
154 // get the limit
155 if ($this->limit != '') {
156 // count the number of deleted records for this pid
157 $deletedCount = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $table, $deletedField . '<>0 AND pid = ' . $id . $filterWhere);
158 // split the limit
159 $parts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $this->limit);
160 $offset = $parts[0];
161 $rowCount = $parts[1];
162 // subtract the number of deleted records from the limit's offset
163 $result = $offset - $deletedCount;
164 // if the result is >= 0
165 if ($result >= 0) {
166 // store the new offset in the limit and go into the next depth
167 $offset = $result;
168 $this->limit = implode(',', array($offset, $rowCount));
169 // do NOT query this depth; limit also does not need to be set, we set it anyways
170 $allowQuery = FALSE;
171 $allowDepth = TRUE;
172 $limit = '';
173 } else {
174 // the offset for the temporary limit has to remain like the original offset
175 // in case the original offset was just crossed by the amount of deleted records
176 if ($offset != 0) {
177 $tempOffset = $offset;
178 } else {
179 $tempOffset = 0;
180 }
181 // set the offset in the limit to 0
182 $newOffset = 0;
183 // convert to negative result to the positive equivalent
184 $absResult = abs($result);
185 // if the result now is > limit's row count
186 if ($absResult > $rowCount) {
187 // use the limit's row count as the temporary limit
188 $limit = implode(',', array($tempOffset, $rowCount));
189 // set the limit's row count to 0
190 $this->limit = implode(',', array($newOffset, 0));
191 // do not go into new depth
192 $allowDepth = FALSE;
193 } else {
194 // if the result now is <= limit's row count
195 // use the result as the temporary limit
196 $limit = implode(',', array($tempOffset, $absResult));
197 // subtract the result from the row count
198 $newCount = $rowCount - $absResult;
199 // store the new result in the limit's row count
200 $this->limit = implode(',', array($newOffset, $newCount));
201 // if the new row count is > 0
202 if ($newCount > 0) {
203 // go into new depth
204 $allowDepth = TRUE;
205 } else {
206 // if the new row count is <= 0 (only =0 makes sense though)
207 // do not go into new depth
208 $allowDepth = FALSE;
209 }
210 }
211 // allow query for this depth
212 $allowQuery = TRUE;
213 }
214 } else {
215 $limit = '';
216 $allowDepth = TRUE;
217 $allowQuery = TRUE;
218 }
219 // query for actual deleted records
220 if ($allowQuery) {
221 $recordsToCheck = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordsByField($table, $deletedField, '1', ' AND pid = ' . $id . $filterWhere, '', '', $limit, FALSE);
222 if ($recordsToCheck) {
223 $this->checkRecordAccess($table, $recordsToCheck);
224 }
225 }
226 // go into depth
227 if ($allowDepth && $depth >= 1) {
228 // check recursively for elements beneath this page
229 $resPages = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', 'pages', 'pid=' . $id, '', 'sorting');
230 if ($resPages) {
231 while ($rowPages = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($resPages)) {
232 $this->setData($rowPages['uid'], $table, $depth - 1, $tcaCtrl, $filter);
233 // some records might have been added, check if we still have the limit for further queries
234 if ('' != $this->limit) {
235 $parts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $this->limit);
236 // abort loop if LIMIT 0,0
237 if ($parts[0] == 0 && $parts[1] == 0) {
238 break;
239 }
240 }
241 }
242 $GLOBALS['TYPO3_DB']->sql_free_result($resPages);
243 }
244 }
245 $this->label[$table] = $tcaCtrl['label'];
246 $this->title[$table] = $tcaCtrl['title'];
247 }
248 }
249
250 /**
251 * Checks whether the current backend user has access to the given records.
252 *
253 * @param string $table: Name of the table
254 * @param array $rows: Record row
255 * @return void
256 */
257 protected function checkRecordAccess($table, array $rows) {
258 foreach ($rows as $key => $row) {
259 if (\TYPO3\CMS\Recycler\Utility\RecyclerUtility::checkAccess($table, $row)) {
260 $this->setDeletedRows($table, $row);
261 }
262 }
263 }
264
265 /**
266 * Escapes a value to be used for like in a database query.
267 * There is a special handling for the characters '%' and '_'.
268 *
269 * @param string $value: The value to be escaped for like conditions
270 * @param string $tableName: The name of the table the query should be used for
271 * @return string The escaped value to be used for like conditions
272 */
273 protected function escapeValueForLike($value, $tableName) {
274 return $GLOBALS['TYPO3_DB']->escapeStrForLike($GLOBALS['TYPO3_DB']->quoteStr($value, $tableName), $tableName);
275 }
276
277 /************************************************************
278 * DELETE FUNCTIONS
279 ************************************************************/
280 /**
281 * Delete element from any table
282 *
283 * @param string $recordArray: Representation of the records
284 * @return void
285 */
286 public function deleteData($recordsArray) {
287 $recordsArray = json_decode($recordsArray);
288 if (is_array($recordsArray)) {
289 $tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
290 $tce->start('', '');
291 $tce->disableDeleteClause();
292 foreach ($recordsArray as $key => $record) {
293 $tce->deleteEl($record[0], $record[1], TRUE, TRUE);
294 }
295 return TRUE;
296 }
297 return FALSE;
298 }
299
300 /************************************************************
301 * UNDELETE FUNCTIONS
302 ************************************************************/
303 /**
304 * Undelete records
305 * If $recursive is TRUE all records below the page uid would be undelete too
306 *
307 * @param string $recordArray: Representation of the records
308 * @param boolean $recursive: TRUE/FALSE
309 * @return boolean
310 */
311 public function undeleteData($recordsArray, $recursive = FALSE) {
312 $result = FALSE;
313 $depth = 999;
314 $recordsArray = json_decode($recordsArray);
315 if (is_array($recordsArray)) {
316 $this->deletedRows = array();
317 $cmd = array();
318 foreach ($recordsArray as $key => $row) {
319 $cmd[$row[0]][$row[1]]['undelete'] = 1;
320 if ($row[0] == 'pages' && $recursive == TRUE) {
321 $this->loadData($row[1], '', $depth, '');
322 $childRecords = $this->getDeletedRows();
323 if (count($childRecords) > 0) {
324 foreach ($childRecords as $table => $childRows) {
325 foreach ($childRows as $childKey => $childRow) {
326 $cmd[$table][$childRow['uid']]['undelete'] = 1;
327 }
328 }
329 }
330 }
331 }
332 if ($cmd) {
333 $tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
334 $tce->start(array(), $cmd);
335 $tce->process_cmdmap();
336 $result = TRUE;
337 }
338 }
339 return $result;
340 }
341
342 /************************************************************
343 * SETTER FUNCTIONS
344 ************************************************************/
345 /**
346 * Set deleted rows
347 *
348 * @param string $table: Tablename
349 * @param array $row: Deleted record row
350 * @return void
351 */
352 public function setDeletedRows($table, array $row) {
353 $this->deletedRows[$table][] = $row;
354 }
355
356 /************************************************************
357 * GETTER FUNCTIONS
358 ************************************************************/
359 /**
360 * Get deleted Rows
361 *
362 * @return array $this->deletedRows: Array with all deleted rows from TCA
363 */
364 public function getDeletedRows() {
365 return $this->deletedRows;
366 }
367
368 /**
369 * Get table
370 *
371 * @return array $this->table: Array with table from TCA
372 */
373 public function getTable() {
374 return $this->table;
375 }
376
377 }
378
379
380 ?>