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