TYPO3 CMS  TYPO3_8-7
WorkspaceVersionRecordsCommand.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
32 
36 class WorkspaceVersionRecordsCommand extends Command
37 {
38 
43  protected $allWorkspaces = [0 => 'Live Workspace'];
44 
49  protected $foundRecords = [
50  // All versions of records found
51  // Subset of "all" which are offline versions (pid=-1) [Informational]
52  'all_versioned_records' => [],
53  // All records that has been published and can therefore be removed permanently
54  // Subset of "versions" that is a count of 1 or more (has been published) [Informational]
55  'published_versions' => [],
56  // All versions that are offline versions in the Live workspace. You may wish to flush these if you only use
57  // workspaces for versioning since then you might find lots of versions piling up in the live workspace which
58  // have simply been disconnected from the workspace before they were published.
59  'versions_in_live' => [],
60  // Versions that has lost their connection to a workspace in TYPO3.
61  // Subset of "versions" that doesn't belong to an existing workspace [Warning: Fix by move to live workspace]
62  'invalid_workspace' => []
63  ];
64 
68  public function configure()
69  {
70  $this
71  ->setDescription('Find all versioned records and possibly cleans up invalid records in the database.')
72  ->setHelp('Traverse page tree and find versioned records. Also list all versioned records, additionally with some inconsistencies in the database, which can cleaned up with the "action" option. If you want to get more detailed information, use the --verbose option.')
73  ->addOption(
74  'pid',
75  'p',
76  InputOption::VALUE_REQUIRED,
77  'Setting start page in page tree. Default is the page tree root, 0 (zero)'
78  )
79  ->addOption(
80  'depth',
81  'd',
82  InputOption::VALUE_REQUIRED,
83  'Setting traversal depth. 0 (zero) will only analyse start page (see --pid), 1 will traverse one level of subpages etc.'
84  )
85  ->addOption(
86  'dry-run',
87  null,
88  InputOption::VALUE_NONE,
89  'If this option is set, the records will not actually be deleted/modified, but just the output which records would be touched are shown'
90  )
91  ->addOption(
92  'action',
93  null,
94  InputOption::VALUE_OPTIONAL,
95  'Specify which action should be taken. Set it to "versions_in_live", "published_versions", "invalid_workspace" or "unused_placeholders"'
96  );
97  }
98 
105  protected function execute(InputInterface $input, OutputInterface $output)
106  {
107  // Make sure the _cli_ user is loaded
108  Bootstrap::getInstance()->initializeBackendAuthentication();
109 
110  $io = new SymfonyStyle($input, $output);
111  $io->title($this->getDescription());
112 
113  $startingPoint = 0;
114  if ($input->hasOption('pid') && MathUtility::canBeInterpretedAsInteger($input->getOption('pid'))) {
115  $startingPoint = MathUtility::forceIntegerInRange((int)$input->getOption('pid'), 0);
116  }
117 
118  $depth = 1000;
119  if ($input->hasOption('depth') && MathUtility::canBeInterpretedAsInteger($input->getOption('depth'))) {
120  $depth = MathUtility::forceIntegerInRange((int)$input->getOption('depth'), 0);
121  }
122 
123  $action = '';
124  if ($input->hasOption('action') && !empty($input->getOption('action'))) {
125  $action = $input->getOption('action');
126  }
127 
128  // type unsafe comparison and explicit boolean setting on purpose
129  $dryRun = $input->hasOption('dry-run') && $input->getOption('dry-run') != false ? true : false;
130 
131  if ($io->isVerbose()) {
132  $io->section('Searching the database now for versioned records.');
133  }
134 
135  $this->loadAllWorkspaceRecords();
136 
137  // Find all records that are versioned
138  $this->traversePageTreeForVersionedRecords($startingPoint, $depth);
139  // Sort recStats (for diff'able displays)
140  foreach ($this->foundRecords as $kk => $vv) {
141  foreach ($this->foundRecords[$kk] as $tables => $recArrays) {
142  ksort($this->foundRecords[$kk][$tables]);
143  }
144  ksort($this->foundRecords[$kk]);
145  }
146 
147  $unusedPlaceholders = $this->findUnusedPlaceholderRecords();
148 
149  // Finding all move placeholders with inconsistencies
150  // Move-to placeholder records which have bad integrity
151  $invalidMovePlaceholders = $this->findInvalidMovePlaceholderRecords();
152 
153  // Finding move_id_check inconsistencies
154  // Checking if t3ver_move_id is correct. t3ver_move_id must only be set with online records having t3ver_state=3.
155  $recordsWithInvalidMoveIds = $this->findInvalidMoveIdRecords();
156 
157  if (!$io->isQuiet()) {
158  $numberOfVersionedRecords = 0;
159  foreach ($this->foundRecords['all_versioned_records'] as $records) {
160  $numberOfVersionedRecords += count($records);
161  }
162 
163  $io->section('Found ' . $numberOfVersionedRecords . ' versioned records in the database.');
164  if ($io->isVeryVerbose()) {
165  foreach ($this->foundRecords['all_versioned_records'] as $table => $records) {
166  $io->writeln('Table "' . $table . '"');
167  $io->listing($records);
168  }
169  }
170 
171  $numberOfPublishedVersions = 0;
172  foreach ($this->foundRecords['published_versions'] as $records) {
173  $numberOfPublishedVersions += count($records);
174  }
175  $io->section('Found ' . $numberOfPublishedVersions . ' versioned records that have been published.');
176  if ($io->isVeryVerbose()) {
177  foreach ($this->foundRecords['published_versions'] as $table => $records) {
178  $io->writeln('Table "' . $table . '"');
179  $io->listing($records);
180  }
181  }
182 
183  $numberOfVersionsInLiveWorkspace = 0;
184  foreach ($this->foundRecords['versions_in_live'] as $records) {
185  $numberOfVersionsInLiveWorkspace += count($records);
186  }
187  $io->section('Found ' . $numberOfVersionsInLiveWorkspace . ' versioned records that are in the live workspace.');
188  if ($io->isVeryVerbose()) {
189  foreach ($this->foundRecords['versions_in_live'] as $table => $records) {
190  $io->writeln('Table "' . $table . '"');
191  $io->listing($records);
192  }
193  }
194 
195  $numberOfVersionsWithInvalidWorkspace = 0;
196  foreach ($this->foundRecords['invalid_workspace'] as $records) {
197  $numberOfVersionsWithInvalidWorkspace += count($records);
198  }
199  $io->section('Found ' . $numberOfVersionsWithInvalidWorkspace . ' versioned records with an invalid workspace.');
200  if ($io->isVeryVerbose()) {
201  foreach ($this->foundRecords['invalid_workspace'] as $table => $records) {
202  $io->writeln('Table "' . $table . '"');
203  $io->listing($records);
204  }
205  }
206 
207  $io->section('Found ' . count($unusedPlaceholders) . ' unused placeholder records.');
208  if ($io->isVeryVerbose()) {
209  $io->listing(array_keys($unusedPlaceholders));
210  }
211 
212  $io->section('Found ' . count($invalidMovePlaceholders) . ' invalid move placeholders.');
213  if ($io->isVeryVerbose()) {
214  $io->listing($invalidMovePlaceholders);
215  }
216 
217  $io->section('Found ' . count($recordsWithInvalidMoveIds) . ' versions with an invalid move ID.');
218  if ($io->isVeryVerbose()) {
219  $io->listing($recordsWithInvalidMoveIds);
220  }
221  }
222 
223  // Actually permanently delete / update records
224  switch ($action) {
225  // All versions that are offline versions in the Live workspace. You may wish to flush these if you only use
226  // workspaces for versioning since then you might find lots of versions piling up in the live workspace which
227  // have simply been disconnected from the workspace before they were published.
228  case 'versions_in_live':
229  $io->section('Deleting versioned records in live workspace now. ' . ($dryRun ? ' (Not deleting now, just a dry run)' : ''));
230  $this->deleteRecords($this->foundRecords['versions_in_live'], $dryRun, $io);
231  break;
232 
233  // All records that has been published and can therefore be removed permanently
234  // Subset of "versions" that is a count of 1 or more (has been published)
235  case 'published_versions':
236  $io->section('Deleting published records in live workspace now. ' . ($dryRun ? ' (Not deleting now, just a dry run)' : ''));
237  $this->deleteRecords($this->foundRecords['published_versions'], $dryRun, $io);
238  break;
239 
240  // Versions that has lost their connection to a workspace in TYPO3.
241  // Subset of "versions" that doesn't belong to an existing workspace [Warning: Fix by move to live workspace]
242  case 'invalid_workspace':
243  $io->section('Moving versions in invalid workspaces to live workspace now. ' . ($dryRun ? ' (Not deleting now, just a dry run)' : ''));
244  $this->resetRecordsWithoutValidWorkspace($this->foundRecords['invalid_workspace'], $dryRun, $io);
245  break;
246 
247  // Finding all placeholders with no records attached
248  // Placeholder records which are not used anymore by offline versions.
249  case 'unused_placeholders':
250  $io->section('Deleting unused placeholder records now. ' . ($dryRun ? ' (Not deleting now, just a dry run)' : ''));
251  $this->deleteUnusedPlaceholders($unusedPlaceholders, $dryRun, $io);
252  break;
253 
254  default:
255  $io->note('No action specified, just displaying statistics. See --action option for details.');
256  break;
257  }
258  $io->success('All done!');
259  }
260 
269  protected function traversePageTreeForVersionedRecords(int $rootID, int $depth, bool $isInsideVersionedPage = false, bool $rootIsVersion = false)
270  {
271  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
272  $queryBuilder->getRestrictions()->removeAll();
273 
274  $pageRecord = $queryBuilder
275  ->select(
276  'deleted',
277  'title',
278  't3ver_count',
279  't3ver_wsid'
280  )
281  ->from('pages')
282  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($rootID, \PDO::PARAM_INT)))
283  ->execute()
284  ->fetch();
285 
286  // If rootIsVersion is set it means that the input rootID is that of a version of a page. See below where the recursive call is made.
287  if ($rootIsVersion) {
288  $workspaceId = (int)$pageRecord['t3ver_wsid'];
289  $this->foundRecords['all_versioned_records']['pages'][$rootID] = $rootID;
290  // If it has been published and is in archive now...
291  if ($pageRecord['t3ver_count'] >= 1 && $workspaceId === 0) {
292  $this->foundRecords['published_versions']['pages'][$rootID] = $rootID;
293  }
294  // If it has been published and is in archive now...
295  if ($workspaceId === 0) {
296  $this->foundRecords['versions_in_live']['pages'][$rootID] = $rootID;
297  }
298  // If it doesn't belong to a workspace...
299  if (!isset($this->allWorkspaces[$workspaceId])) {
300  $this->foundRecords['invalid_workspace']['pages'][$rootID] = $rootID;
301  }
302  }
303  // Only check for records if not inside a version
304  if (!$isInsideVersionedPage) {
305  // Traverse tables of records that belongs to page
306  $tableNames = $this->getAllVersionableTables();
307  foreach ($tableNames as $tableName) {
308  if ($tableName !== 'pages') {
309  // Select all records belonging to page:
310  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
311  ->getQueryBuilderForTable($tableName);
312 
313  $queryBuilder->getRestrictions()->removeAll();
314 
315  $result = $queryBuilder
316  ->select('uid')
317  ->from($tableName)
318  ->where(
319  $queryBuilder->expr()->eq(
320  'pid',
321  $queryBuilder->createNamedParameter($rootID, \PDO::PARAM_INT)
322  )
323  )
324  ->execute();
325  while ($rowSub = $result->fetch()) {
326  // Add any versions of those records
327  $versions = BackendUtility::selectVersionsOfRecord($tableName, $rowSub['uid'], 'uid,t3ver_wsid,t3ver_count' . ($GLOBALS['TCA'][$tableName]['ctrl']['delete'] ? ',' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] : ''), null, true);
328  if (is_array($versions)) {
329  foreach ($versions as $verRec) {
330  if (!$verRec['_CURRENT_VERSION']) {
331  // Register version
332  $this->foundRecords['all_versioned_records'][$tableName][$verRec['uid']] = $verRec['uid'];
333  $workspaceId = (int)$verRec['t3ver_wsid'];
334  if ($verRec['t3ver_count'] >= 1 && $workspaceId === 0) {
335  // Only register published versions in LIVE workspace
336  // (published versions in draft workspaces are allowed)
337  $this->foundRecords['published_versions'][$tableName][$verRec['uid']] = $verRec['uid'];
338  }
339  if ($workspaceId === 0) {
340  $this->foundRecords['versions_in_live'][$tableName][$verRec['uid']] = $verRec['uid'];
341  }
342  if (!isset($this->allWorkspaces[$workspaceId])) {
343  $this->foundRecords['invalid_workspace'][$tableName][$verRec['uid']] = $verRec['uid'];
344  }
345  }
346  }
347  }
348  }
349  }
350  }
351  }
352  // Find subpages to root ID and traverse (only when rootID is not a version or is a branch-version):
353  if ($depth > 0) {
354  $depth--;
355  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
356  ->getQueryBuilderForTable('pages');
357 
358  $queryBuilder->getRestrictions()->removeAll();
359  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
360 
361  $queryBuilder
362  ->select('uid')
363  ->from('pages')
364  ->where(
365  $queryBuilder->expr()->eq(
366  'pid',
367  $queryBuilder->createNamedParameter($rootID, \PDO::PARAM_INT)
368  )
369  )
370  ->orderBy('sorting');
371 
372  $result = $queryBuilder->execute();
373  while ($row = $result->fetch()) {
374  $this->traversePageTreeForVersionedRecords((int)$row['uid'], $depth, $isInsideVersionedPage, false);
375  }
376  }
377  // Add any versions of pages
378  if ($rootID > 0) {
379  $versions = BackendUtility::selectVersionsOfRecord('pages', $rootID, 'uid,t3ver_oid,t3ver_wsid,t3ver_count', null, true);
380  if (is_array($versions)) {
381  foreach ($versions as $verRec) {
382  if (!$verRec['_CURRENT_VERSION']) {
383  $this->traversePageTreeForVersionedRecords((int)$verRec['uid'], $depth, true, true);
384  }
385  }
386  }
387  }
388  }
389 
395  protected function findUnusedPlaceholderRecords(): array
396  {
397  $unusedPlaceholders = [];
398  $tableNames = $this->getAllVersionableTables();
399  foreach ($tableNames as $table) {
400  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
401  ->getQueryBuilderForTable($table);
402 
403  $queryBuilder->getRestrictions()
404  ->removeAll()
405  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
406 
407  $result = $queryBuilder
408  ->select('uid', 'pid')
409  ->from($table)
410  ->where(
411  $queryBuilder->expr()->gte('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
412  $queryBuilder->expr()->eq(
413  't3ver_state',
414  $queryBuilder->createNamedParameter(
416  \PDO::PARAM_INT
417  )
418  )
419  )
420  ->execute();
421 
422  while ($placeholderRecord = $result->fetch()) {
423  $versions = BackendUtility::selectVersionsOfRecord($table, $placeholderRecord['uid'], 'uid', '*', null);
424  if (count($versions) <= 1) {
425  $unusedPlaceholders[$table . ':' . $placeholderRecord['uid']] = [
426  'table' => $table,
427  'uid' => $placeholderRecord['uid']
428  ];
429  }
430  }
431  }
432  ksort($unusedPlaceholders);
433  return $unusedPlaceholders;
434  }
435 
442  protected function findInvalidMovePlaceholderRecords(): array
443  {
444  $invalidMovePlaceholders = [];
445  $tableNames = $this->getAllVersionableTables();
446  foreach ($tableNames as $table) {
447  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
448  ->getQueryBuilderForTable($table);
449 
450  $queryBuilder->getRestrictions()
451  ->removeAll()
452  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
453 
454  $result = $queryBuilder
455  ->select('uid', 'pid', 't3ver_move_id', 't3ver_wsid', 't3ver_state')
456  ->from($table)
457  ->where(
458  $queryBuilder->expr()->gte('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
459  $queryBuilder->expr()->eq(
460  't3ver_state',
461  $queryBuilder->createNamedParameter(
463  \PDO::PARAM_INT
464  )
465  )
466  )
467  ->execute();
468  while ($placeholderRecord = $result->fetch()) {
469  $shortID = GeneralUtility::shortMD5($table . ':' . $placeholderRecord['uid']);
470  if ((int)$placeholderRecord['t3ver_wsid'] !== 0) {
471  $phrecCopy = $placeholderRecord;
472  if (BackendUtility::movePlhOL($table, $placeholderRecord)) {
473  if ($wsAlt = BackendUtility::getWorkspaceVersionOfRecord($phrecCopy['t3ver_wsid'], $table, $placeholderRecord['uid'], 'uid,pid,t3ver_state')) {
474  if (!VersionState::cast($wsAlt['t3ver_state'])->equals(VersionState::MOVE_POINTER)) {
475  $invalidMovePlaceholders[$shortID] = $table . ':' . $placeholderRecord['uid'] . ' - State for version was not "4" as it should be!';
476  }
477  } else {
478  $invalidMovePlaceholders[$shortID] = $table . ':' . $placeholderRecord['uid'] . ' - No version was found for online record to be moved. A version must exist.';
479  }
480  } else {
481  $invalidMovePlaceholders[$shortID] = $table . ':' . $placeholderRecord['uid'] . ' - Did not find online record for "t3ver_move_id" value ' . $placeholderRecord['t3ver_move_id'];
482  }
483  } else {
484  $invalidMovePlaceholders[$shortID] = $table . ':' . $placeholderRecord['uid'] . ' - Placeholder was not assigned a workspace value in t3ver_wsid.';
485  }
486  }
487  }
488  ksort($invalidMovePlaceholders);
489  return $invalidMovePlaceholders;
490  }
491 
498  protected function findInvalidMoveIdRecords(): array
499  {
500  $records = [];
501  $tableNames = $this->getAllVersionableTables();
502  foreach ($tableNames as $table) {
503  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
504  ->getQueryBuilderForTable($table);
505 
506  $queryBuilder->getRestrictions()
507  ->removeAll()
508  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
509 
510  $result = $queryBuilder
511  ->select('uid', 'pid', 't3ver_move_id', 't3ver_wsid', 't3ver_state')
512  ->from($table)
513  ->where(
514  $queryBuilder->expr()->neq(
515  't3ver_move_id',
516  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
517  )
518  )
519  ->execute();
520 
521  while ($placeholderRecord = $result->fetch()) {
522  if (VersionState::cast($placeholderRecord['t3ver_state'])->equals(VersionState::MOVE_PLACEHOLDER)) {
523  if ((int)$placeholderRecord['pid'] === -1) {
524  $records[] = $table . ':' . $placeholderRecord['uid'] . ' - Record was offline, must not be!';
525  }
526  } else {
527  $records[] = $table . ':' . $placeholderRecord['uid'] . ' - Record had t3ver_move_id set to "' . $placeholderRecord['t3ver_move_id'] . '" while having t3ver_state=' . $placeholderRecord['t3ver_state'];
528  }
529  }
530  }
531  return $records;
532  }
533 
534  /**************************
535  * actions / delete methods
536  **************************/
537 
545  protected function deleteRecords(array $records, bool $dryRun, SymfonyStyle $io)
546  {
547  // Putting "pages" table in the bottom
548  if (isset($records['pages'])) {
549  $_pages = $records['pages'];
550  unset($records['pages']);
551  // To delete sub pages first assuming they are accumulated from top of page tree.
552  $records['pages'] = array_reverse($_pages);
553  }
554 
555  // Set up the data handler instance
556  $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
557  $dataHandler->start([], []);
558 
559  // Traversing records
560  foreach ($records as $table => $uidsInTable) {
561  if ($io->isVerbose()) {
562  $io->writeln('Flushing published records from table "' . $table . '"');
563  }
564  foreach ($uidsInTable as $uid) {
565  if ($io->isVeryVerbose()) {
566  $io->writeln('Flushing record "' . $table . ':' . $uid . '"');
567  }
568  if (!$dryRun) {
569  $dataHandler->deleteEl($table, $uid, true, true);
570  if (!empty($dataHandler->errorLog)) {
571  $errorMessage = array_merge(['DataHandler reported an error'], $dataHandler->errorLog);
572  $io->error($errorMessage);
573  } elseif (!$io->isQuiet()) {
574  $io->writeln('Flushed published record "' . $table . ':' . $uid . '".');
575  }
576  }
577  }
578  }
579  }
580 
589  protected function resetRecordsWithoutValidWorkspace(array $records, bool $dryRun, SymfonyStyle $io)
590  {
591  foreach ($records as $table => $uidsInTable) {
592  if ($io->isVerbose()) {
593  $io->writeln('Resetting workspace to zero for records from table "' . $table . '"');
594  }
595  foreach ($uidsInTable as $uid) {
596  if ($io->isVeryVerbose()) {
597  $io->writeln('Flushing record "' . $table . ':' . $uid . '"');
598  }
599  if (!$dryRun) {
600  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
601  ->getQueryBuilderForTable($table);
602 
603  $queryBuilder
604  ->update($table)
605  ->where(
606  $queryBuilder->expr()->eq(
607  'uid',
608  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
609  )
610  )
611  ->set('t3ver_wsid', 0)
612  ->execute();
613  if (!$io->isQuiet()) {
614  $io->writeln('Flushed record "' . $table . ':' . $uid . '".');
615  }
616  }
617  }
618  }
619  }
620 
628  protected function deleteUnusedPlaceholders(array $records, bool $dryRun, SymfonyStyle $io)
629  {
630  $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
631  $dataHandler->start([], []);
632  foreach ($records as $record) {
633  $table = $record['table'];
634  $uid = $record['uid'];
635  if ($io->isVeryVerbose()) {
636  $io->writeln('Deleting unused placeholder (soft) "' . $table . ':' . $uid . '"');
637  }
638  if (!$dryRun) {
639  $dataHandler->deleteAction($table, $uid);
640  // Return errors if any
641  if (!empty($dataHandler->errorLog)) {
642  $errorMessage = array_merge(['DataHandler reported an error'], $dataHandler->errorLog);
643  $io->error($errorMessage);
644  } elseif (!$io->isQuiet()) {
645  $io->writeln('Permanently deleted unused placeholder "' . $table . ':' . $uid . '".');
646  }
647  }
648  }
649  }
650 
660  protected function loadAllWorkspaceRecords(): array
661  {
662  if (ExtensionManagementUtility::isLoaded('workspaces')) {
663  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
664  ->getQueryBuilderForTable('sys_workspace');
665 
666  $queryBuilder->getRestrictions()
667  ->removeAll()
668  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
669 
670  $result = $queryBuilder
671  ->select('uid', 'title')
672  ->from('sys_workspace')
673  ->execute();
674 
675  while ($workspaceRecord = $result->fetch()) {
676  $this->allWorkspaces[(int)$workspaceRecord['uid']] = $workspaceRecord['title'];
677  }
678  }
679  return $this->allWorkspaces;
680  }
681 
687  protected function getAllVersionableTables(): array
688  {
689  static $tables;
690  if (!is_array($tables)) {
691  $tables = [];
692  foreach ($GLOBALS['TCA'] as $tableName => $config) {
693  if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
694  $tables[] = $tableName;
695  }
696  }
697  }
698  return $tables;
699  }
700 }
static getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
traversePageTreeForVersionedRecords(int $rootID, int $depth, bool $isInsideVersionedPage=false, bool $rootIsVersion=false)
deleteUnusedPlaceholders(array $records, bool $dryRun, SymfonyStyle $io)
resetRecordsWithoutValidWorkspace(array $records, bool $dryRun, SymfonyStyle $io)
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static makeInstance($className,... $constructorArguments)
static selectVersionsOfRecord( $table, $uid, $fields=' *', $workspace=0, $includeDeletedRecords=false, $row=null)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
deleteRecords(array $records, bool $dryRun, SymfonyStyle $io)