[TASK] Merge ext:integrity with ext:lowlevel
[Packages/TYPO3.CMS.git] / typo3 / sysext / lowlevel / Classes / CleanerCommand.php
1 <?php
2 namespace TYPO3\CMS\Lowlevel;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
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 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * Core functions for cleaning and analysing
31 *
32 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
33 */
34 /**
35 * Core functions for cleaning and analysing
36 *
37 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
38 * @package TYPO3
39 * @subpackage tx_lowlevel
40 */
41 class CleanerCommand extends \TYPO3\CMS\Core\Controller\CommandLineController {
42
43 /**
44 * @todo Define visibility
45 */
46 public $genTree_traverseDeleted = TRUE;
47
48 /**
49 * @todo Define visibility
50 */
51 public $genTree_traverseVersions = TRUE;
52
53 /**
54 * @todo Define visibility
55 */
56 public $label_infoString = 'The list of records is organized as [table]:[uid]:[field]:[flexpointer]:[softref_key]';
57
58 /**
59 * @todo Define visibility
60 */
61 public $pagetreePlugins = array();
62
63 /**
64 * @todo Define visibility
65 */
66 public $cleanerModules = array();
67
68 /**
69 * @todo Define visibility
70 */
71 public $performanceStatistics = array();
72
73 protected $workspaceIndex = array();
74
75 /**
76 * Constructor
77 *
78 * @todo Define visibility
79 */
80 public function __construct() {
81 // Running parent class constructor
82 parent::__construct();
83 $this->cleanerModules = (array) $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lowlevel']['cleanerModules'];
84 // Adding options to help archive:
85 $this->cli_options[] = array('-r', 'Execute this tool, otherwise help is shown');
86 $this->cli_options[] = array('-v level', 'Verbosity level 0-3', 'The value of level can be:
87 0 = all output
88 1 = info and greater (default)
89 2 = warnings and greater
90 3 = errors');
91 $this->cli_options[] = array('--refindex mode', 'Mode for reference index handling for operations that require a clean reference index ("update"/"ignore")', 'Options are "check" (default), "update" and "ignore". By default, the reference index is checked before running analysis that require a clean index. If the check fails, the analysis is not run. You can choose to bypass this completely (using value "ignore") or ask to have the index updated right away before the analysis (using value "update")');
92 $this->cli_options[] = array('--AUTOFIX [testName]', 'Repairs errors that can be automatically fixed.', 'Only add this option after having run the test without it so you know what will happen when you add this option! The optional parameter "[testName]" works for some tool keys to limit the fixing to a particular test.');
93 $this->cli_options[] = array('--dryrun', 'With --AUTOFIX it will only simulate a repair process', 'You may like to use this to see what the --AUTOFIX option will be doing. It will output the whole process like if a fix really occurred but nothing is in fact happening');
94 $this->cli_options[] = array('--YES', 'Implicit YES to all questions', 'Use this with EXTREME care. The option "-i" is not affected by this option.');
95 $this->cli_options[] = array('-i', 'Interactive', 'Will ask you before running the AUTOFIX on each element.');
96 $this->cli_options[] = array('--filterRegex expr', 'Define an expression for preg_match() that must match the element ID in order to auto repair it', 'The element ID is the string in quotation marks when the text \'Cleaning ... in "ELEMENT ID"\'. "expr" is the expression for preg_match(). To match for example "Nature3.JPG" and "Holiday3.JPG" you can use "/.*3.JPG/". To match for example "Image.jpg" and "Image.JPG" you can use "/.*.jpg/i". Try a --dryrun first to see what the matches are!');
97 $this->cli_options[] = array('--showhowto', 'Displays HOWTO file for cleaner script.');
98 // Setting help texts:
99 $this->cli_help['name'] = 'lowlevel_cleaner -- Analysis and clean-up tools for TYPO3 installations';
100 $this->cli_help['synopsis'] = 'toolkey ###OPTIONS###';
101 $this->cli_help['description'] = 'Dispatches to various analysis and clean-up tools which can plug into the API of this script. Typically you can run tests that will take longer than the usual max execution time of PHP. Such tasks could be checking for orphan records in the page tree or flushing all published versions in the system. For the complete list of options, please explore each of the \'toolkey\' keywords below:
102
103 ' . implode('
104 ', array_keys($this->cleanerModules));
105 $this->cli_help['examples'] = '/.../cli_dispatch.phpsh lowlevel_cleaner missing_files -s -r
106 This will show you missing files in the TYPO3 system and only report back if errors were found.';
107 $this->cli_help['author'] = 'Kasper Skaarhoej, (c) 2006';
108 }
109
110 /**************************
111 *
112 * CLI functionality
113 *
114 *************************/
115 /**
116 * CLI engine
117 *
118 * @param array $argv Command line arguments
119 * @return string
120 * @todo Define visibility
121 */
122 public function cli_main($argv) {
123 // Force user to admin state and set workspace to "Live":
124 $GLOBALS['BE_USER']->user['admin'] = 1;
125 $GLOBALS['BE_USER']->setWorkspace(0);
126 // Print Howto:
127 if ($this->cli_isArg('--showhowto')) {
128 $howto = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl(\TYPO3\CMS\Core\Extension\ExtensionManager::extPath('lowlevel') . 'HOWTO_clean_up_TYPO3_installations.txt');
129 echo wordwrap($howto, 120) . LF;
130 die;
131 }
132 // Print help
133 $analysisType = (string) $this->cli_args['_DEFAULT'][1];
134 if (!$analysisType) {
135 $this->cli_validateArgs();
136 $this->cli_help();
137 die;
138 }
139 // Analysis type:
140 switch ((string) $analysisType) {
141 default:
142 if (is_array($this->cleanerModules[$analysisType])) {
143 $cleanerMode = \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($this->cleanerModules[$analysisType][0]);
144 $cleanerMode->cli_validateArgs();
145 // Run it...
146 if ($this->cli_isArg('-r')) {
147 if (!$cleanerMode->checkRefIndex || $this->cli_referenceIndexCheck()) {
148 $res = $cleanerMode->main();
149 $this->cli_printInfo($analysisType, $res);
150 // Autofix...
151 if ($this->cli_isArg('--AUTOFIX')) {
152 if ($this->cli_isArg('--YES') || $this->cli_keyboardInput_yes('
153
154 NOW Running --AUTOFIX on result. OK?' . ($this->cli_isArg('--dryrun') ? ' (--dryrun simulation)' : ''))) {
155 $cleanerMode->main_autofix($res);
156 } else {
157 $this->cli_echo('ABORTING AutoFix...
158 ', 1);
159 }
160 }
161 }
162 } else {
163 // Help only...
164 $cleanerMode->cli_help();
165 die;
166 }
167 } else {
168 $this->cli_echo('ERROR: Analysis Type \'' . $analysisType . '\' is unknown.
169 ', 1);
170 die;
171 }
172 break;
173 }
174 }
175
176 /**
177 * Checks reference index
178 *
179 * @return boolean TRUE if reference index was OK (either OK, updated or ignored)
180 * @todo Define visibility
181 */
182 public function cli_referenceIndexCheck() {
183 // Reference index option:
184 $refIndexMode = isset($this->cli_args['--refindex']) ? $this->cli_args['--refindex'][0] : 'check';
185 if (!\TYPO3\CMS\Core\Utility\GeneralUtility::inList('update,ignore,check', $refIndexMode)) {
186 $this->cli_echo('ERROR: Wrong value for --refindex argument.
187 ', 1);
188 die;
189 }
190 switch ($refIndexMode) {
191 case 'check':
192
193 case 'update':
194 $refIndexObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ReferenceIndex');
195 list($headerContent, $bodyContent, $errorCount) = $refIndexObj->updateIndex($refIndexMode == 'check', $this->cli_echo());
196 if ($errorCount && $refIndexMode == 'check') {
197 $ok = FALSE;
198 $this->cli_echo('ERROR: Reference Index Check failed! (run with \'--refindex update\' to fix)
199 ', 1);
200 } else {
201 $ok = TRUE;
202 }
203 break;
204 case 'ignore':
205 $this->cli_echo('Reference Index Check: Bypassing reference index check...
206 ');
207 $ok = TRUE;
208 break;
209 }
210 return $ok;
211 }
212
213 /**
214 * @param string $matchString
215 * @return string If string, it's the reason for not executing. Returning FALSE means it should execute.
216 * @todo Define visibility
217 */
218 public function cli_noExecutionCheck($matchString) {
219 // Check for filter:
220 if ($this->cli_isArg('--filterRegex') && ($regex = $this->cli_argValue('--filterRegex', 0))) {
221 if (!preg_match($regex, $matchString)) {
222 return 'BYPASS: Filter Regex "' . $regex . '" did not match string "' . $matchString . '"';
223 }
224 }
225 // Check for interactive mode
226 if ($this->cli_isArg('-i')) {
227 if (!$this->cli_keyboardInput_yes(' EXECUTE?')) {
228 return 'BYPASS...';
229 }
230 }
231 // Check for
232 if ($this->cli_isArg('--dryrun')) {
233 return 'BYPASS: --dryrun set';
234 }
235 }
236
237 /**
238 * Formats a result array from a test so it fits output in the shell
239 *
240 * @param string $header Name of the test (eg. function name)
241 * @param array $res Result array from an analyze function
242 * @return void Outputs with echo - capture content with output buffer if needed.
243 * @todo Define visibility
244 */
245 public function cli_printInfo($header, $res) {
246 $detailLevel = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->cli_isArg('-v') ? $this->cli_argValue('-v') : 1, 0, 3);
247 $silent = !$this->cli_echo();
248 $severity = array(
249 0 => 'MESSAGE',
250 1 => 'INFO',
251 2 => 'WARNING',
252 3 => 'ERROR'
253 );
254 // Header output:
255 if ($detailLevel <= 1) {
256 $this->cli_echo('*********************************************
257 ' . $header . LF . '*********************************************
258 ');
259 $this->cli_echo(wordwrap(trim($res['message'])) . LF . LF);
260 }
261 // Traverse headers for output:
262 if (is_array($res['headers'])) {
263 foreach ($res['headers'] as $key => $value) {
264 if ($detailLevel <= intval($value[2])) {
265 if (is_array($res[$key]) && (count($res[$key]) || !$silent)) {
266 // Header and explanaion:
267 $this->cli_echo('---------------------------------------------' . LF, 1);
268 $this->cli_echo('[' . $header . ']' . LF, 1);
269 $this->cli_echo($value[0] . ' [' . $severity[$value[2]] . ']' . LF, 1);
270 $this->cli_echo('---------------------------------------------' . LF, 1);
271 if (trim($value[1])) {
272 $this->cli_echo('Explanation: ' . wordwrap(trim($value[1])) . LF . LF, 1);
273 }
274 }
275 // Content:
276 if (is_array($res[$key])) {
277 if (count($res[$key])) {
278 if ($this->cli_echo('', 1)) {
279 print_r($res[$key]);
280 }
281 } else {
282 $this->cli_echo('(None)' . LF . LF);
283 }
284 } else {
285 $this->cli_echo($res[$key] . LF . LF);
286 }
287 }
288 }
289 }
290 }
291
292 /**************************
293 *
294 * Page tree traversal
295 *
296 *************************/
297 /**
298 * Traverses the FULL/part of page tree, mainly to register ALL validly connected records (to find orphans) but also to register deleted records, versions etc.
299 * Output (in $this->recStats) can be useful for multiple purposes.
300 *
301 * @param integer $rootID Root page id from where to start traversal. Use "0" (zero) to have full page tree (necessary when spotting orphans, otherwise you can run it on parts only)
302 * @param integer $depth Depth to traverse. zero is do not traverse at all. 1 = 1 sublevel, 1000= 1000 sublevels (all...)
303 * @param boolean $echoLevel If >0, will echo information about the traversal process.
304 * @param string $callBack Call back function (from this class or subclass)
305 * @return void
306 * @todo Define visibility
307 */
308 public function genTree($rootID, $depth = 1000, $echoLevel = 0, $callBack = '') {
309 $pt = \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds();
310 $this->performanceStatistics['genTree()'] = '';
311 // Initialize:
312 if (\TYPO3\CMS\Core\Extension\ExtensionManager::isLoaded('workspaces')) {
313 $this->workspaceIndex = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,title', 'sys_workspace', '1=1' . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause('sys_workspace'), '', '', '', 'uid');
314 }
315 $this->workspaceIndex[-1] = TRUE;
316 $this->workspaceIndex[0] = TRUE;
317 $this->recStats = array(
318 'all' => array(),
319 // All records connected in tree including versions (the reverse are orphans). All Info and Warning categories below are included here (and therefore safe if you delete the reverse of the list)
320 'deleted' => array(),
321 // Subset of "alL" that are deleted-flagged [Info]
322 'versions' => array(),
323 // Subset of "all" which are offline versions (pid=-1). [Info]
324 'versions_published' => array(),
325 // Subset of "versions" that is a count of 1 or more (has been published) [Info]
326 'versions_liveWS' => array(),
327 // Subset of "versions" that exists in live workspace [Info]
328 'versions_lost_workspace' => array(),
329 // Subset of "versions" that doesn't belong to an existing workspace [Warning: Fix by move to live workspace]
330 'versions_inside_versioned_page' => array(),
331 // Subset of "versions" This is versions of elements found inside an already versioned branch / page. In real life this can work out, but is confusing and the backend should prevent this from happening to people. [Warning: Fix by deleting those versions (or publishing them)]
332 'illegal_record_under_versioned_page' => array(),
333 // If a page is "element" or "page" version and records are found attached to it, they might be illegally attached, so this will tell you. [Error: Fix by deleting orphans since they are not registered in "all" category]
334 'misplaced_at_rootlevel' => array(),
335 // Subset of "all": Those that should not be at root level but are. [Warning: Fix by moving record into page tree]
336 'misplaced_inside_tree' => array()
337 );
338 // Start traversal:
339 $pt2 = \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds();
340 $this->performanceStatistics['genTree_traverse()'] = '';
341 $this->performanceStatistics['genTree_traverse():TraverseTables'] = '';
342 $this->genTree_traverse($rootID, $depth, $echoLevel, $callBack);
343 $this->performanceStatistics['genTree_traverse()'] = \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds() - $pt2;
344 // Sort recStats (for diff'able displays)
345 foreach ($this->recStats as $kk => $vv) {
346 foreach ($this->recStats[$kk] as $tables => $recArrays) {
347 ksort($this->recStats[$kk][$tables]);
348 }
349 ksort($this->recStats[$kk]);
350 }
351 if ($echoLevel > 0) {
352 echo LF . LF;
353 }
354 // Processing performance statistics:
355 $this->performanceStatistics['genTree()'] = \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds() - $pt;
356 // Count records:
357 foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
358 // Select all records belonging to page:
359 $resSub = $GLOBALS['TYPO3_DB']->exec_SELECTquery('count(*)', $tableName, '');
360 $countRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($resSub);
361 $this->performanceStatistics['MySQL_count'][$tableName] = $countRow['count(*)'];
362 $this->performanceStatistics['CSV'] .= LF . $tableName . ',' . $this->performanceStatistics['genTree_traverse():TraverseTables:']['MySQL'][$tableName] . ',' . $this->performanceStatistics['genTree_traverse():TraverseTables:']['Proc'][$tableName] . ',' . $this->performanceStatistics['MySQL_count'][$tableName];
363 }
364 $this->performanceStatistics['recStats_size']['(ALL)'] = strlen(serialize($this->recStats));
365 foreach ($this->recStats as $key => $arrcontent) {
366 $this->performanceStatistics['recStats_size'][$key] = strlen(serialize($arrcontent));
367 }
368 }
369
370 /**
371 * Recursive traversal of page tree:
372 *
373 * @param integer $rootID Page root id (must be online, valid page record - or zero for page tree root)
374 * @param integer $depth Depth
375 * @param integer $echoLevel Echo Level
376 * @param string $callBack Call back function (from this class or subclass)
377 * @param string $versionSwapmode DON'T set from outside, internal. (indicates we are inside a version of a page) - will be "SWAPMODE:-1" or empty
378 * @param integer $rootIsVersion DON'T set from outside, internal. (1: Indicates that rootID is a version of a page, 2: ...that it is even a version of a version (which triggers a warning!)
379 * @param string $accumulatedPath Internal string that accumulates the path
380 * @return void
381 * @access private
382 * @todo $versionSwapmode needs to be cleaned up, since page and branch version (0, 1) does not exist anymore
383 * @todo Define visibility
384 */
385 public function genTree_traverse($rootID, $depth, $echoLevel = 0, $callBack = '', $versionSwapmode = '', $rootIsVersion = 0, $accumulatedPath = '') {
386 // Register page:
387 $this->recStats['all']['pages'][$rootID] = $rootID;
388 $pageRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordRaw('pages', 'uid=' . intval($rootID), 'deleted,title,t3ver_count,t3ver_wsid');
389 $accumulatedPath .= '/' . $pageRecord['title'];
390 // Register if page is deleted:
391 if ($pageRecord['deleted']) {
392 $this->recStats['deleted']['pages'][$rootID] = $rootID;
393 }
394 // 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.
395 if ($rootIsVersion) {
396 $this->recStats['versions']['pages'][$rootID] = $rootID;
397 // If it has been published and is in archive now...
398 if ($pageRecord['t3ver_count'] >= 1 && $pageRecord['t3ver_wsid'] == 0) {
399 $this->recStats['versions_published']['pages'][$rootID] = $rootID;
400 }
401 // If it has been published and is in archive now...
402 if ($pageRecord['t3ver_wsid'] == 0) {
403 $this->recStats['versions_liveWS']['pages'][$rootID] = $rootID;
404 }
405 // If it doesn't belong to a workspace...
406 if (!isset($this->workspaceIndex[$pageRecord['t3ver_wsid']])) {
407 $this->recStats['versions_lost_workspace']['pages'][$rootID] = $rootID;
408 }
409 // In case the rootID is a version inside a versioned page
410 if ($rootIsVersion == 2) {
411 $this->recStats['versions_inside_versioned_page']['pages'][$rootID] = $rootID;
412 }
413 }
414 if ($echoLevel > 0) {
415 echo LF . $accumulatedPath . ' [' . $rootID . ']' . ($pageRecord['deleted'] ? ' (DELETED)' : '') . ($this->recStats['versions_published']['pages'][$rootID] ? ' (PUBLISHED)' : '');
416 }
417 if ($echoLevel > 1 && $this->recStats['versions_lost_workspace']['pages'][$rootID]) {
418 echo LF . ' ERROR! This version belongs to non-existing workspace (' . $pageRecord['t3ver_wsid'] . ')!';
419 }
420 if ($echoLevel > 1 && $this->recStats['versions_inside_versioned_page']['pages'][$rootID]) {
421 echo LF . ' WARNING! This version is inside an already versioned page or branch!';
422 }
423 // Call back:
424 if ($callBack) {
425 $this->{$callBack}('pages', $rootID, $echoLevel, $versionSwapmode, $rootIsVersion);
426 }
427 $pt3 = \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds();
428 // Traverse tables of records that belongs to page:
429 foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
430 if ($tableName != 'pages') {
431 // Select all records belonging to page:
432 $pt4 = \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds();
433 $resSub = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid' . ($GLOBALS['TCA'][$tableName]['ctrl']['delete'] ? ',' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] : ''), $tableName, 'pid=' . intval($rootID) . ($this->genTree_traverseDeleted ? '' : \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($tableName)));
434 $this->performanceStatistics['genTree_traverse():TraverseTables:']['MySQL']['(ALL)'] += \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds() - $pt4;
435 $this->performanceStatistics['genTree_traverse():TraverseTables:']['MySQL'][$tableName] += \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds() - $pt4;
436 $pt5 = \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds();
437 $count = $GLOBALS['TYPO3_DB']->sql_num_rows($resSub);
438 if ($count) {
439 if ($echoLevel == 2) {
440 echo LF . ' \\-' . $tableName . ' (' . $count . ')';
441 }
442 }
443 while ($rowSub = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($resSub)) {
444 if ($echoLevel == 3) {
445 echo LF . ' \\-' . $tableName . ':' . $rowSub['uid'];
446 }
447 // If the rootID represents an "element" or "page" version type, we must check if the record from this table is allowed to belong to this:
448 if ($versionSwapmode == 'SWAPMODE:-1' || $versionSwapmode == 'SWAPMODE:0' && !$GLOBALS['TCA'][$tableName]['ctrl']['versioning_followPages']) {
449 // This is illegal records under a versioned page - therefore not registered in $this->recStats['all'] so they should be orphaned:
450 $this->recStats['illegal_record_under_versioned_page'][$tableName][$rowSub['uid']] = $rowSub['uid'];
451 if ($echoLevel > 1) {
452 echo LF . ' ERROR! Illegal record (' . $tableName . ':' . $rowSub['uid'] . ') under versioned page!';
453 }
454 } else {
455 $this->recStats['all'][$tableName][$rowSub['uid']] = $rowSub['uid'];
456 // Register deleted:
457 if ($GLOBALS['TCA'][$tableName]['ctrl']['delete'] && $rowSub[$GLOBALS['TCA'][$tableName]['ctrl']['delete']]) {
458 $this->recStats['deleted'][$tableName][$rowSub['uid']] = $rowSub['uid'];
459 if ($echoLevel == 3) {
460 echo ' (DELETED)';
461 }
462 }
463 // Check location of records regarding tree root:
464 if (!$GLOBALS['TCA'][$tableName]['ctrl']['rootLevel'] && $rootID == 0) {
465 $this->recStats['misplaced_at_rootlevel'][$tableName][$rowSub['uid']] = $rowSub['uid'];
466 if ($echoLevel > 1) {
467 echo LF . ' ERROR! Misplaced record (' . $tableName . ':' . $rowSub['uid'] . ') on rootlevel!';
468 }
469 }
470 if ($GLOBALS['TCA'][$tableName]['ctrl']['rootLevel'] == 1 && $rootID > 0) {
471 $this->recStats['misplaced_inside_tree'][$tableName][$rowSub['uid']] = $rowSub['uid'];
472 if ($echoLevel > 1) {
473 echo LF . ' ERROR! Misplaced record (' . $tableName . ':' . $rowSub['uid'] . ') inside page tree!';
474 }
475 }
476 // Traverse plugins:
477 if ($callBack) {
478 $this->{$callBack}($tableName, $rowSub['uid'], $echoLevel, $versionSwapmode, $rootIsVersion);
479 }
480 // Add any versions of those records:
481 if ($this->genTree_traverseVersions) {
482 $versions = \TYPO3\CMS\Backend\Utility\BackendUtility::selectVersionsOfRecord($tableName, $rowSub['uid'], 'uid,t3ver_wsid,t3ver_count' . ($GLOBALS['TCA'][$tableName]['ctrl']['delete'] ? ',' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] : ''), 0, TRUE);
483 if (is_array($versions)) {
484 foreach ($versions as $verRec) {
485 if (!$verRec['_CURRENT_VERSION']) {
486 if ($echoLevel == 3) {
487 echo LF . ' \\-[#OFFLINE VERSION: WS#' . $verRec['t3ver_wsid'] . '/Cnt:' . $verRec['t3ver_count'] . '] ' . $tableName . ':' . $verRec['uid'] . ')';
488 }
489 $this->recStats['all'][$tableName][$verRec['uid']] = $verRec['uid'];
490 // Register deleted:
491 if ($GLOBALS['TCA'][$tableName]['ctrl']['delete'] && $verRec[$GLOBALS['TCA'][$tableName]['ctrl']['delete']]) {
492 $this->recStats['deleted'][$tableName][$verRec['uid']] = $verRec['uid'];
493 if ($echoLevel == 3) {
494 echo ' (DELETED)';
495 }
496 }
497 // Register version:
498 $this->recStats['versions'][$tableName][$verRec['uid']] = $verRec['uid'];
499 if ($verRec['t3ver_count'] >= 1 && $verRec['t3ver_wsid'] == 0) {
500 // Only register published versions in LIVE workspace (published versions in draft workspaces are allowed)
501 $this->recStats['versions_published'][$tableName][$verRec['uid']] = $verRec['uid'];
502 if ($echoLevel == 3) {
503 echo ' (PUBLISHED)';
504 }
505 }
506 if ($verRec['t3ver_wsid'] == 0) {
507 $this->recStats['versions_liveWS'][$tableName][$verRec['uid']] = $verRec['uid'];
508 }
509 if (!isset($this->workspaceIndex[$verRec['t3ver_wsid']])) {
510 $this->recStats['versions_lost_workspace'][$tableName][$verRec['uid']] = $verRec['uid'];
511 if ($echoLevel > 1) {
512 echo LF . ' ERROR! Version (' . $tableName . ':' . $verRec['uid'] . ') belongs to non-existing workspace (' . $verRec['t3ver_wsid'] . ')!';
513 }
514 }
515 // In case we are inside a versioned branch, there should not exists versions inside that "branch".
516 if ($versionSwapmode) {
517 $this->recStats['versions_inside_versioned_page'][$tableName][$verRec['uid']] = $verRec['uid'];
518 if ($echoLevel > 1) {
519 echo LF . ' ERROR! This version (' . $tableName . ':' . $verRec['uid'] . ') is inside an already versioned page or branch!';
520 }
521 }
522 // Traverse plugins:
523 if ($callBack) {
524 $this->{$callBack}($tableName, $verRec['uid'], $echoLevel, $versionSwapmode, $rootIsVersion);
525 }
526 }
527 }
528 }
529 unset($versions);
530 }
531 }
532 }
533 $this->performanceStatistics['genTree_traverse():TraverseTables:']['Proc']['(ALL)'] += \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds() - $pt5;
534 $this->performanceStatistics['genTree_traverse():TraverseTables:']['Proc'][$tableName] += \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds() - $pt5;
535 }
536 }
537 unset($resSub);
538 unset($rowSub);
539 $this->performanceStatistics['genTree_traverse():TraverseTables'] += \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds() - $pt3;
540 // Find subpages to root ID and traverse (only when rootID is not a version or is a branch-version):
541 if (!$versionSwapmode || $versionSwapmode == 'SWAPMODE:1') {
542 if ($depth > 0) {
543 $depth--;
544 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', 'pages', 'pid=' . intval($rootID) . ($this->genTree_traverseDeleted ? '' : \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause('pages')), '', 'sorting');
545 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
546 $this->genTree_traverse($row['uid'], $depth, $echoLevel, $callBack, $versionSwapmode, 0, $accumulatedPath);
547 }
548 }
549 // Add any versions of pages
550 if ($rootID > 0 && $this->genTree_traverseVersions) {
551 $versions = \TYPO3\CMS\Backend\Utility\BackendUtility::selectVersionsOfRecord('pages', $rootID, 'uid,t3ver_oid,t3ver_wsid,t3ver_count', 0, TRUE);
552 if (is_array($versions)) {
553 foreach ($versions as $verRec) {
554 if (!$verRec['_CURRENT_VERSION']) {
555 $this->genTree_traverse($verRec['uid'], $depth, $echoLevel, $callBack, 'SWAPMODE:-1', $versionSwapmode ? 2 : 1, $accumulatedPath . ' [#OFFLINE VERSION: WS#' . $verRec['t3ver_wsid'] . '/Cnt:' . $verRec['t3ver_count'] . ']');
556 }
557 }
558 }
559 }
560 }
561 }
562
563 /**************************
564 *
565 * Helper functions
566 *
567 *************************/
568 /**
569 * Compile info-string
570 *
571 * @param array $rec Input record from sys_refindex
572 * @return string String identifying the main record of the reference
573 * @todo Define visibility
574 */
575 public function infoStr($rec) {
576 return $rec['tablename'] . ':' . $rec['recuid'] . ':' . $rec['field'] . ':' . $rec['flexpointer'] . ':' . $rec['softref_key'] . ($rec['deleted'] ? ' (DELETED)' : '');
577 }
578
579 }
580
581
582 ?>