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