‪TYPO3CMS  9.5
DeletedRecords.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
31 
37 {
43  protected ‪$deletedRows = [];
44 
50  protected ‪$limit = '';
51 
57  protected ‪$table = [];
58 
64  protected ‪$recyclerHelper;
65 
71  public ‪$label;
72 
78  public ‪$title;
79 
80  /************************************************************
81  * GET DATA FUNCTIONS
82  *
83  *
84  ************************************************************/
96  public function ‪loadData($id, ‪$table, $depth, ‪$limit = '', $filter = '')
97  {
98  // set the limit
99  $this->limit = trim(‪$limit);
100  if (‪$table) {
101  if (in_array(‪$table, ‪RecyclerUtility::getModifyableTables(), true)) {
102  $this->table[] = ‪$table;
103  $this->‪setData($id, ‪$table, $depth, $filter);
104  }
105  } else {
106  foreach (‪RecyclerUtility::getModifyableTables() as $tableKey) {
107  // only go into this table if the limit allows it
108  if ($this->limit !== '') {
109  $parts = GeneralUtility::intExplode(',', $this->limit, true);
110  // abort loop if LIMIT 0,0
111  if ($parts[0] === 0 && $parts[1] === 0) {
112  break;
113  }
114  }
115  $this->table[] = $tableKey;
116  $this->‪setData($id, $tableKey, $depth, $filter);
117  }
118  }
119  return $this;
120  }
121 
131  public function ‪getTotalCount($id, ‪$table, $depth, $filter)
132  {
133  $deletedRecords = $this->‪loadData($id, ‪$table, $depth, '', $filter)->‪getDeletedRows();
134  $countTotal = 0;
135  foreach ($this->table as $tableName) {
136  $countTotal += count($deletedRecords[$tableName] ?? []);
137  }
138  return $countTotal;
139  }
140 
149  protected function ‪setData($id, ‪$table, $depth, $filter)
150  {
152  if (!$deletedField) {
153  return;
154  }
155 
156  $id = (int)$id;
157  $tcaCtrl = ‪$GLOBALS['TCA'][‪$table]['ctrl'];
158  $firstResult = 0;
159  $maxResults = 0;
160 
161  // get the limit
162  if (!empty($this->limit)) {
163  // count the number of deleted records for this pid
164  $queryBuilder = $this->‪getFilteredQueryBuilder($table, $id, $depth, $filter);
165  $queryBuilder->getRestrictions()->removeAll();
166 
167  $deletedCount = (int)$queryBuilder
168  ->count('*')
169  ->from(‪$table)
170  ->andWhere(
171  $queryBuilder->expr()->neq(
172  $deletedField,
173  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
174  )
175  )
176  ->execute()
177  ->fetchColumn(0);
178 
179  // split the limit
180  list($offset, $rowCount) = GeneralUtility::intExplode(',', $this->limit, true);
181  // subtract the number of deleted records from the limit's offset
182  $result = $offset - $deletedCount;
183  // if the result is >= 0
184  if ($result >= 0) {
185  // store the new offset in the limit and go into the next depth
186  $offset = $result;
187  $this->limit = implode(',', [$offset, $rowCount]);
188  // do NOT query this depth; limit also does not need to be set, we set it anyways
189  $allowQuery = false;
190  } else {
191  // the offset for the temporary limit has to remain like the original offset
192  // in case the original offset was just crossed by the amount of deleted records
193  $tempOffset = 0;
194  if ($offset !== 0) {
195  $tempOffset = $offset;
196  }
197  // set the offset in the limit to 0
198  $newOffset = 0;
199  // convert to negative result to the positive equivalent
200  $absResult = abs($result);
201  // if the result now is > limit's row count
202  if ($absResult > $rowCount) {
203  // use the limit's row count as the temporary limit
204  $firstResult = $tempOffset;
205  $maxResults = $rowCount;
206  // set the limit's row count to 0
207  $this->limit = implode(',', [$newOffset, 0]);
208  } else {
209  // if the result now is <= limit's row count
210  // use the result as the temporary limit
211  $firstResult = $tempOffset;
212  $maxResults = $absResult;
213  // subtract the result from the row count
214  $newCount = $rowCount - $absResult;
215  // store the new result in the limit's row count
216  $this->limit = implode(',', [$newOffset, $newCount]);
217  }
218  // allow query for this depth
219  $allowQuery = true;
220  }
221  } else {
222  $allowQuery = true;
223  }
224  // query for actual deleted records
225  if ($allowQuery) {
226  $queryBuilder = $this->‪getFilteredQueryBuilder($table, $id, $depth, $filter);
227  if ($firstResult) {
228  $queryBuilder->setFirstResult($firstResult);
229  }
230  if ($maxResults) {
231  $queryBuilder->setMaxResults($maxResults);
232  }
233  $recordsToCheck = $queryBuilder->select('*')
234  ->from(‪$table)
235  ->andWhere(
236  $queryBuilder->expr()->eq(
237  $deletedField,
238  $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)
239  )
240  )
241  ->orderBy('uid')
242  ->execute()
243  ->fetchAll();
244 
245  if ($recordsToCheck !== false) {
246  $this->‪checkRecordAccess($table, $recordsToCheck);
247  }
248  }
249  $this->label[‪$table] = $tcaCtrl['label'];
250  $this->title[‪$table] = $tcaCtrl['title'];
251  }
252 
262  protected function ‪getFilteredQueryBuilder(string ‪$table, int $pid, int $depth, string $filter): QueryBuilder
263  {
264  $pidList = $this->‪getTreeList($pid, $depth);
265  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(‪$table);
266  $queryBuilder->getRestrictions()
267  ->removeAll()
268  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
269 
270  // create the filter WHERE-clause
271  $filterConstraint = null;
272  if (trim($filter) !== '') {
273  $filterConstraint = $queryBuilder->expr()->comparison(
274  $queryBuilder->castFieldToTextType(‪$GLOBALS['TCA'][‪$table]['ctrl']['label']),
275  'LIKE',
276  $queryBuilder->createNamedParameter(
277  '%' . $queryBuilder->escapeLikeWildcards($filter) . '%',
278  \PDO::PARAM_STR
279  )
280  );
282  $filterConstraint = $queryBuilder->expr()->orX(
283  $queryBuilder->expr()->eq(
284  'uid',
285  $queryBuilder->createNamedParameter($filter, \PDO::PARAM_INT)
286  ),
287  $queryBuilder->expr()->eq(
288  'pid',
289  $queryBuilder->createNamedParameter($filter, \PDO::PARAM_INT)
290  ),
291  $filterConstraint
292  );
293  }
294  }
295 
296  $maxBindParameters = ‪PlatformInformation::getMaxBindParameters($queryBuilder->getConnection()->getDatabasePlatform());
297  $pidConstraints = [];
298  foreach (array_chunk($pidList, $maxBindParameters - 10) as $chunk) {
299  $pidConstraints[] = $queryBuilder->expr()->in(
300  'pid',
301  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
302  );
303  }
304  $queryBuilder->where(
305  $queryBuilder->expr()->andX(
306  $filterConstraint,
307  $queryBuilder->expr()->orX(...$pidConstraints)
308  )
309  );
310 
311  return $queryBuilder;
312  }
313 
320  protected function ‪checkRecordAccess(‪$table, array $rows)
321  {
322  $deleteField = '';
323  if (‪$table === 'pages') {
324  // The "checkAccess" method validates access to the passed table/rows. When access to
325  // a page record gets validated it is necessary to disable the "delete" field temporarily
326  // for the recycler.
327  // Else it wouldn't be possible to perform the check as many methods of BackendUtility
328  // like "BEgetRootLine", etc. will only work on non-deleted records.
329  $deleteField = ‪$GLOBALS['TCA'][‪$table]['ctrl']['delete'];
330  unset(‪$GLOBALS['TCA'][‪$table]['ctrl']['delete']);
331  }
332 
333  foreach ($rows as $row) {
335  $this->‪setDeletedRows($table, $row);
336  }
337  }
338 
339  if (‪$table === 'pages') {
340  ‪$GLOBALS['TCA'][‪$table]['ctrl']['delete'] = $deleteField;
341  }
342  }
343 
344  /************************************************************
345  * DELETE FUNCTIONS
346  ************************************************************/
353  public function deleteData($recordsArray)
354  {
355  if (is_array($recordsArray)) {
357  $tce = GeneralUtility::makeInstance(DataHandler::class);
358  $tce->start([], []);
359  $tce->disableDeleteClause();
360  foreach ($recordsArray as $record) {
361  list(‪$table, $uid) = explode(':', $record);
362  $tce->deleteEl(‪$table, (int)$uid, true, true);
363  }
364  return true;
365  }
366  return false;
367  }
368 
369  /************************************************************
370  * UNDELETE FUNCTIONS
371  ************************************************************/
380  public function ‪undeleteData($recordsArray, $recursive = false)
381  {
382  $result = false;
383  $affectedRecords = 0;
384  $depth = 999;
385  if (is_array($recordsArray)) {
386  $this->deletedRows = [];
387  $cmd = [];
388  foreach ($recordsArray as $record) {
389  list(‪$table, $uid) = explode(':', $record);
390  // get all parent pages and cover them
392  if ($pid > 0) {
393  $parentUidsToRecover = $this->‪getDeletedParentPages($pid);
394  $count = count($parentUidsToRecover);
395  for ($i = 0; $i < $count; ++$i) {
396  $parentUid = $parentUidsToRecover[$i];
397  $cmd['pages'][$parentUid]['undelete'] = 1;
398  $affectedRecords++;
399  }
400  if (isset($cmd['pages'])) {
401  // reverse the page list to recover it from top to bottom
402  $cmd['pages'] = array_reverse($cmd['pages'], true);
403  }
404  }
405  $cmd[‪$table][$uid]['undelete'] = 1;
406  $affectedRecords++;
407  if (‪$table === 'pages' && $recursive) {
408  $this->‪loadData($uid, '', $depth, '');
409  $childRecords = $this->‪getDeletedRows();
410  if (!empty($childRecords)) {
411  foreach ($childRecords as $childTable => $childRows) {
412  foreach ($childRows as $childRow) {
413  $cmd[$childTable][$childRow['uid']]['undelete'] = 1;
414  }
415  }
416  }
417  }
418  }
419  if ($cmd) {
420  $tce = GeneralUtility::makeInstance(DataHandler::class);
421  $tce->start([], $cmd);
422  $tce->process_cmdmap();
423  $result = $affectedRecords;
424  }
425  }
426  return $result;
427  }
428 
436  protected function ‪getDeletedParentPages($uid, &$pages = [])
437  {
438  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
439  $queryBuilder->getRestrictions()->removeAll();
440  $record = $queryBuilder
441  ->select('uid', 'pid')
442  ->from('pages')
443  ->where(
444  $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
445  $queryBuilder->expr()->eq(‪$GLOBALS['TCA']['pages']['ctrl']['delete'], 1)
446  )
447  ->execute()
448  ->fetch();
449  if ($record) {
450  $pages[] = $record['uid'];
451  if ((int)$record['pid'] !== 0) {
452  $this->‪getDeletedParentPages($record['pid'], $pages);
453  }
454  }
455 
456  return $pages;
457  }
458 
459  /************************************************************
460  * SETTER FUNCTIONS
461  ************************************************************/
468  public function ‪setDeletedRows(‪$table, array $row)
469  {
470  $this->deletedRows[‪$table][] = $row;
471  }
472 
473  /************************************************************
474  * GETTER FUNCTIONS
475  ************************************************************/
481  public function ‪getDeletedRows()
482  {
483  return ‪$this->deletedRows;
484  }
485 
491  public function ‪getTable()
492  {
493  return ‪$this->table;
494  }
495 
504  protected function ‪getTreeList(int $id, int $depth, int $begin = 0): array
505  {
506  $cache = $this->‪getCache();
507  $identifier = md5($id . '_' . $depth . '_' . $begin);
508  $pageTree = $cache->get($identifier);
509  if ($pageTree === false) {
510  $pageTree = $this->‪resolveTree($id, $depth, $begin, $this->‪getBackendUser()->getPagePermsClause(‪Permission::PAGE_SHOW));
511  $cache->set($identifier, $pageTree);
512  }
513 
514  return $pageTree;
515  }
516 
524  protected function ‪resolveTree(int $id, int $depth, int $begin = 0, string $permsClause = ''): array
525  {
526  $depth = (int)$depth;
527  $begin = (int)$begin;
528  $id = abs((int)$id);
529  $theList = [];
530  if ($begin === 0) {
531  $theList[] = $id;
532  }
533  if ($depth > 0) {
534  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
535  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
536  $statement = $queryBuilder->select('uid')
537  ->from('pages')
538  ->where(
539  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)),
541  )
542  ->execute();
543  while ($row = $statement->fetch()) {
544  if ($begin <= 0) {
545  $theList[] = $row['uid'];
546  }
547  if ($depth > 1) {
548  $theList = array_merge($theList, $this->‪resolveTree($row['uid'], $depth - 1, $begin - 1, $permsClause));
549  }
550  }
551  }
552  return $theList;
553  }
554 
560  protected function ‪getCache(): ‪FrontendInterface
561  {
562  return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
563  }
564 
570  protected function ‪getBackendUser(): ‪BackendUserAuthentication
571  {
572  return ‪$GLOBALS['BE_USER'];
573  }
574 }
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:81
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\loadData
‪DeletedRecords loadData($id, $table, $depth, $limit='', $filter='')
Definition: DeletedRecords.php:90
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\getDeletedParentPages
‪array getDeletedParentPages($uid, &$pages=[])
Definition: DeletedRecords.php:430
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:73
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\$label
‪array $label
Definition: DeletedRecords.php:66
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\getTotalCount
‪int getTotalCount($id, $table, $depth, $filter)
Definition: DeletedRecords.php:125
‪TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction
Definition: BackendWorkspaceRestriction.php:28
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\$table
‪array $table
Definition: DeletedRecords.php:54
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:23
‪TYPO3\CMS\Recycler\Utility\RecyclerUtility\getDeletedField
‪static string getDeletedField($tableName)
Definition: RecyclerUtility.php:127
‪TYPO3\CMS\Core\Database\Query\QueryBuilder
Definition: QueryBuilder.php:47
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\checkRecordAccess
‪checkRecordAccess($table, array $rows)
Definition: DeletedRecords.php:314
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\getDeletedRows
‪array getDeletedRows()
Definition: DeletedRecords.php:475
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\setDeletedRows
‪setDeletedRows($table, array $row)
Definition: DeletedRecords.php:462
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\$recyclerHelper
‪RecyclerUtility $recyclerHelper
Definition: DeletedRecords.php:60
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\$deletedRows
‪array $deletedRows
Definition: DeletedRecords.php:42
‪TYPO3\CMS\Recycler\Utility\RecyclerUtility\getModifyableTables
‪static getModifyableTables()
Definition: RecyclerUtility.php:220
‪TYPO3\CMS\Recycler\Utility\RecyclerUtility\getPidOfUid
‪static int getPidOfUid($uid, $table)
Definition: RecyclerUtility.php:167
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation\getMaxBindParameters
‪static int getMaxBindParameters(AbstractPlatform $platform)
Definition: PlatformInformation.php:71
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:30
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\getFilteredQueryBuilder
‪TYPO3 CMS Core Database Query QueryBuilder getFilteredQueryBuilder(string $table, int $pid, int $depth, string $filter)
Definition: DeletedRecords.php:256
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\setData
‪setData($id, $table, $depth, $filter)
Definition: DeletedRecords.php:143
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\getCache
‪FrontendInterface getCache()
Definition: DeletedRecords.php:554
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\$limit
‪string $limit
Definition: DeletedRecords.php:48
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:34
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:45
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:32
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\undeleteData
‪bool int undeleteData($recordsArray, $recursive=false)
Definition: DeletedRecords.php:374
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:31
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:21
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords
Definition: DeletedRecords.php:37
‪TYPO3\CMS\Recycler\Utility\RecyclerUtility\checkAccess
‪static bool checkAccess($table, $row)
Definition: RecyclerUtility.php:41
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:163
‪TYPO3\CMS\Recycler\Utility\RecyclerUtility
Definition: RecyclerUtility.php:27
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation
Definition: PlatformInformation.php:31
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\$title
‪array $title
Definition: DeletedRecords.php:72
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\getBackendUser
‪BackendUserAuthentication getBackendUser()
Definition: DeletedRecords.php:564
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:21
‪TYPO3\CMS\Recycler\Domain\Model
Definition: DeletedRecords.php:2
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:44
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\resolveTree
‪array resolveTree(int $id, int $depth, int $begin=0, string $permsClause='')
Definition: DeletedRecords.php:518
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\getTreeList
‪array getTreeList(int $id, int $depth, int $begin=0)
Definition: DeletedRecords.php:498
‪TYPO3\CMS\Recycler\Domain\Model\DeletedRecords\getTable
‪array getTable()
Definition: DeletedRecords.php:485