TYPO3 API  SVNRelease
class.tx_lowlevel_cleaner_core.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
00006 *  All rights reserved
00007 *
00008 *  This script is part of the TYPO3 project. The TYPO3 project is
00009 *  free software; you can redistribute it and/or modify
00010 *  it under the terms of the GNU General Public License as published by
00011 *  the Free Software Foundation; either version 2 of the License, or
00012 *  (at your option) any later version.
00013 *
00014 *  The GNU General Public License can be found at
00015 *  http://www.gnu.org/copyleft/gpl.html.
00016 *  A copy is found in the textfile GPL.txt and important notices to the license
00017 *  from the author is found in LICENSE.txt distributed with these scripts.
00018 *
00019 *
00020 *  This script is distributed in the hope that it will be useful,
00021 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023 *  GNU General Public License for more details.
00024 *
00025 *  This copyright notice MUST APPEAR in all copies of the script!
00026 ***************************************************************/
00027 /**
00028  * Core functions for cleaning and analysing
00029  *
00030  * $Id: class.tx_lowlevel_cleaner_core.php 10478 2011-02-17 11:08:43Z ohader $
00031  *
00032  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00033  */
00034 /**
00035  * [CLASS/FUNCTION INDEX of SCRIPT]
00036  *
00037  *
00038  *
00039  *   71: class tx_lowlevel_cleaner_core extends t3lib_cli
00040  *   88:     function tx_lowlevel_cleaner_core()
00041  *
00042  *              SECTION: CLI functionality
00043  *  134:     function cli_main($argv)
00044  *  193:     function cli_referenceIndexCheck()
00045  *  228:     function cli_noExecutionCheck($matchString)
00046  *  251:     function cli_printInfo($header,$res)
00047  *
00048  *              SECTION: Page tree traversal
00049  *  331:     function genTree($rootID,$depth=1000,$echoLevel=0,$callBack='')
00050  *  369:     function genTree_traverse($rootID,$depth,$echoLevel=0,$callBack='',$versionSwapmode='',$rootIsVersion=0,$accumulatedPath='')
00051  *
00052  *              SECTION: Helper functions
00053  *  554:     function infoStr($rec)
00054  *
00055  * TOTAL FUNCTIONS: 8
00056  * (This index is automatically created/updated by the extension "extdeveval")
00057  *
00058  */
00059 
00060 
00061 /**
00062  * Core functions for cleaning and analysing
00063  *
00064  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00065  * @package TYPO3
00066  * @subpackage tx_lowlevel
00067  */
00068 class tx_lowlevel_cleaner_core extends t3lib_cli {
00069 
00070     var $genTree_traverseDeleted = TRUE;
00071     var $genTree_traverseVersions = TRUE;
00072 
00073 
00074 
00075     var $label_infoString = 'The list of records is organized as [table]:[uid]:[field]:[flexpointer]:[softref_key]';
00076     var $pagetreePlugins = array();
00077     var $cleanerModules = array();
00078 
00079     var $performanceStatistics = array();
00080 
00081     protected $workspaceIndex = array();
00082 
00083 
00084     /**
00085      * Constructor
00086      *
00087      * @return  void
00088      */
00089     function tx_lowlevel_cleaner_core() {
00090 
00091             // Running parent class constructor
00092         parent::t3lib_cli();
00093 
00094         $this->cleanerModules = (array)$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lowlevel']['cleanerModules'];
00095 
00096             // Adding options to help archive:
00097         $this->cli_options[] = array('-r', 'Execute this tool, otherwise help is shown');
00098         $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");
00099         $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")');
00100         $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.');
00101         $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');
00102         $this->cli_options[] = array('--YES', 'Implicit YES to all questions','Use this with EXTREME care. The option "-i" is not affected by this option.');
00103         $this->cli_options[] = array('-i', 'Interactive','Will ask you before running the AUTOFIX on each element.');
00104         $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!');
00105         $this->cli_options[] = array('--showhowto', 'Displays HOWTO file for cleaner script.');
00106 
00107             // Setting help texts:
00108         $this->cli_help['name'] = 'lowlevel_cleaner -- Analysis and clean-up tools for TYPO3 installations';
00109         $this->cli_help['synopsis'] = 'toolkey ###OPTIONS###';
00110         $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));
00111         $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.";
00112         $this->cli_help['author'] = "Kasper Skaarhoej, (c) 2006";
00113     }
00114 
00115 
00116 
00117 
00118 
00119 
00120 
00121 
00122 
00123     /**************************
00124      *
00125      * CLI functionality
00126      *
00127      *************************/
00128 
00129     /**
00130      * CLI engine
00131      *
00132      * @param   array       Command line arguments
00133      * @return  string
00134      */
00135     function cli_main($argv) {
00136 
00137             // Force user to admin state and set workspace to "Live":
00138         $GLOBALS['BE_USER']->user['admin'] = 1;
00139         $GLOBALS['BE_USER']->setWorkspace(0);
00140 
00141             // Print Howto:
00142         if ($this->cli_isArg('--showhowto'))    {
00143             $howto = t3lib_div::getUrl(t3lib_extMgm::extPath('lowlevel').'HOWTO_clean_up_TYPO3_installations.txt');
00144             echo wordwrap($howto,120).LF;
00145             exit;
00146         }
00147 
00148             // Print help
00149         $analysisType = (string)$this->cli_args['_DEFAULT'][1];
00150         if (!$analysisType) {
00151             $this->cli_validateArgs();
00152             $this->cli_help();
00153             exit;
00154         }
00155 
00156             // Analysis type:
00157         switch((string)$analysisType)    {
00158             default:
00159                 if (is_array($this->cleanerModules[$analysisType])) {
00160                     $cleanerMode = t3lib_div::getUserObj($this->cleanerModules[$analysisType][0]);
00161                     $cleanerMode->cli_validateArgs();
00162 
00163                     if ($this->cli_isArg('-r')) {   // Run it...
00164                         if (!$cleanerMode->checkRefIndex || $this->cli_referenceIndexCheck())   {
00165                             $res = $cleanerMode->main();
00166                             $this->cli_printInfo($analysisType, $res);
00167 
00168                                 // Autofix...
00169                             if ($this->cli_isArg('--AUTOFIX'))  {
00170                                 if ($this->cli_isArg('--YES') || $this->cli_keyboardInput_yes("\n\nNOW Running --AUTOFIX on result. OK?".($this->cli_isArg('--dryrun')?' (--dryrun simulation)':'')))   {
00171                                     $cleanerMode->main_autofix($res);
00172                                 } else {
00173                                     $this->cli_echo("ABORTING AutoFix...\n",1);
00174                                 }
00175                             }
00176                         }
00177                     } else {    // Help only...
00178                         $cleanerMode->cli_help();
00179                         exit;
00180                     }
00181                 } else {
00182                     $this->cli_echo("ERROR: Analysis Type '".$analysisType."' is unknown.\n",1);
00183                     exit;
00184                 }
00185             break;
00186         }
00187     }
00188 
00189     /**
00190      * Checks reference index
00191      *
00192      * @return  boolean     TRUE if reference index was OK (either OK, updated or ignored)
00193      */
00194     function cli_referenceIndexCheck()  {
00195 
00196             // Reference index option:
00197         $refIndexMode = isset($this->cli_args['--refindex']) ? $this->cli_args['--refindex'][0] : 'check';
00198         if (!t3lib_div::inList('update,ignore,check', $refIndexMode))   {
00199             $this->cli_echo("ERROR: Wrong value for --refindex argument.\n",1);
00200             exit;
00201         }
00202 
00203         switch($refIndexMode)   {
00204             case 'check':
00205             case 'update':
00206                 $refIndexObj = t3lib_div::makeInstance('t3lib_refindex');
00207                 list($headerContent,$bodyContent,$errorCount) = $refIndexObj->updateIndex($refIndexMode=='check',$this->cli_echo());
00208 
00209                 if ($errorCount && $refIndexMode=='check')  {
00210                     $ok = FALSE;
00211                     $this->cli_echo("ERROR: Reference Index Check failed! (run with '--refindex update' to fix)\n",1);
00212                 } else {
00213                     $ok = TRUE;
00214                 }
00215             break;
00216             case 'ignore':
00217                 $this->cli_echo("Reference Index Check: Bypassing reference index check...\n");
00218                 $ok = TRUE;
00219             break;
00220         }
00221 
00222         return $ok;
00223     }
00224 
00225     /**
00226      * @param   [type]      $matchString: ...
00227      * @return  string      If string, it's the reason for not executing. Returning FALSE means it should execute.
00228      */
00229     function cli_noExecutionCheck($matchString) {
00230 
00231             // Check for filter:
00232         if ($this->cli_isArg('--filterRegex') && $regex = $this->cli_argValue('--filterRegex',0))   {
00233             if (!preg_match($regex,$matchString))   return 'BYPASS: Filter Regex "'.$regex.'" did not match string "'.$matchString.'"';
00234         }
00235             // Check for interactive mode
00236         if ($this->cli_isArg('-i')) {
00237             if (!$this->cli_keyboardInput_yes(' EXECUTE?')) {
00238                 return 'BYPASS...';
00239             }
00240         }
00241             // Check for
00242         if ($this->cli_isArg('--dryrun'))   return 'BYPASS: --dryrun set';
00243     }
00244 
00245     /**
00246      * Formats a result array from a test so it fits output in the shell
00247      *
00248      * @param   string      name of the test (eg. function name)
00249      * @param   array       Result array from an analyze function
00250      * @return  void        Outputs with echo - capture content with output buffer if needed.
00251      */
00252     function cli_printInfo($header,$res)    {
00253 
00254         $detailLevel = t3lib_div::intInRange($this->cli_isArg('-v') ? $this->cli_argValue('-v') : 1,0,3);
00255         $silent = !$this->cli_echo();
00256 
00257         $severity = array(
00258             0 => 'MESSAGE',
00259             1 => 'INFO',
00260             2 => 'WARNING',
00261             3 => 'ERROR',
00262         );
00263 
00264             // Header output:
00265         if ($detailLevel <= 1)  {
00266             $this->cli_echo(
00267                 "*********************************************\n".
00268                 $header.LF.
00269                 "*********************************************\n");
00270             $this->cli_echo(wordwrap(trim($res['message'])).LF.LF);
00271         }
00272 
00273             // Traverse headers for output:
00274         if (is_array($res['headers'])) {
00275             foreach($res['headers'] as $key => $value)  {
00276 
00277                 if ($detailLevel <= intval($value[2]))  {
00278                     if (is_array($res[$key]) && (count($res[$key]) || !$silent)) {
00279 
00280                             // Header and explanaion:
00281                         $this->cli_echo('---------------------------------------------'.LF,1);
00282                         $this->cli_echo('['.$header.']'.LF,1);
00283                         $this->cli_echo($value[0].' ['.$severity[$value[2]].']'.LF,1);
00284                         $this->cli_echo('---------------------------------------------'.LF,1);
00285                         if (trim($value[1]))    {
00286                             $this->cli_echo('Explanation: '.wordwrap(trim($value[1])).LF.LF,1);
00287                         }
00288                     }
00289 
00290                         // Content:
00291                     if (is_array($res[$key]))   {
00292                         if (count($res[$key]))  {
00293                             if ($this->cli_echo('',1)) { print_r($res[$key]); }
00294                         } else {
00295                             $this->cli_echo('(None)'.LF.LF);
00296                         }
00297                     } else {
00298                         $this->cli_echo($res[$key].LF.LF);
00299                     }
00300                 }
00301             }
00302         }
00303     }
00304 
00305 
00306 
00307 
00308 
00309 
00310 
00311 
00312 
00313 
00314 
00315 
00316     /**************************
00317      *
00318      * Page tree traversal
00319      *
00320      *************************/
00321 
00322     /**
00323      * 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.
00324      * Output (in $this->recStats) can be useful for multiple purposes.
00325      *
00326      * @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)
00327      * @param   integer     Depth to traverse. zero is do not traverse at all. 1 = 1 sublevel, 1000= 1000 sublevels (all...)
00328      * @param   boolean     If >0, will echo information about the traversal process.
00329      * @param   string      Call back function (from this class or subclass)
00330      * @return  void
00331      */
00332     function genTree($rootID,$depth=1000,$echoLevel=0,$callBack='') {
00333 
00334         $pt = t3lib_div::milliseconds();$this->performanceStatistics['genTree()']='';
00335 
00336             // Initialize:
00337         if (t3lib_extMgm::isLoaded('workspaces')) {
00338             $this->workspaceIndex = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,title','sys_workspace','1=1'.t3lib_BEfunc::deleteClause('sys_workspace'),'','','','uid');
00339         }
00340         $this->workspaceIndex[-1] = TRUE;
00341         $this->workspaceIndex[0] = TRUE;
00342 
00343         $this->recStats = array(
00344             '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)
00345             'deleted' => array(),                               // Subset of "alL" that are deleted-flagged [Info]
00346             'versions' => array(),                              // Subset of "all" which are offline versions (pid=-1). [Info]
00347             'versions_published' => array(),                    // Subset of "versions" that is a count of 1 or more (has been published) [Info]
00348             'versions_liveWS' => array(),                       // Subset of "versions" that exists in live workspace [Info]
00349             'versions_lost_workspace' => array(),               // Subset of "versions" that doesn't belong to an existing workspace [Warning: Fix by move to live workspace]
00350             '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)]
00351             '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]
00352             '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]
00353             '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]
00354         );
00355 
00356             // Start traversal:
00357         $pt2 = t3lib_div::milliseconds();$this->performanceStatistics['genTree_traverse()']=''; $this->performanceStatistics['genTree_traverse():TraverseTables']='';
00358         $this->genTree_traverse($rootID,$depth,$echoLevel,$callBack);
00359         $this->performanceStatistics['genTree_traverse()'] = t3lib_div::milliseconds()-$pt2;
00360 
00361             // Sort recStats (for diff'able displays)
00362         foreach($this->recStats as $kk => $vv)  {
00363             foreach($this->recStats[$kk] as $tables => $recArrays)  {
00364                 ksort($this->recStats[$kk][$tables]);
00365             }
00366             ksort($this->recStats[$kk]);
00367         }
00368 
00369         if ($echoLevel>0)   echo LF.LF;
00370 
00371 
00372             // Processing performance statistics:
00373         $this->performanceStatistics['genTree()'] = t3lib_div::milliseconds()-$pt;
00374 
00375             // Count records:
00376         foreach($GLOBALS['TCA'] as $tableName => $cfg)  {
00377                 // Select all records belonging to page:
00378             $resSub = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
00379                 'count(*)',
00380                 $tableName,
00381                 ''
00382             );
00383             $countRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($resSub);
00384             $this->performanceStatistics['MySQL_count'][$tableName]=$countRow['count(*)'];
00385             $this->performanceStatistics['CSV'].=LF.$tableName.','.
00386                                 $this->performanceStatistics['genTree_traverse():TraverseTables:']['MySQL'][$tableName].','.
00387                                 $this->performanceStatistics['genTree_traverse():TraverseTables:']['Proc'][$tableName].','.
00388                                 $this->performanceStatistics['MySQL_count'][$tableName];
00389         }
00390 
00391         $this->performanceStatistics['recStats_size']['(ALL)']=strlen(serialize($this->recStats));
00392         foreach($this->recStats as $key => $arrcontent) {
00393             $this->performanceStatistics['recStats_size'][$key]=strlen(serialize($arrcontent));
00394         }
00395     }
00396 
00397     /**
00398      * Recursive traversal of page tree:
00399      *
00400      * @param   integer     Page root id (must be online, valid page record - or zero for page tree root)
00401      * @param   integer     Depth
00402      * @param   integer     Echo Level
00403      * @param   string      Call back function (from this class or subclass)
00404      * @param   string      DON'T set from outside, internal. (indicates we are inside a version of a page)
00405      * @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!)
00406      * @param   string      Internal string that accumulates the path
00407      * @return  void
00408      * @access private
00409      */
00410     function genTree_traverse($rootID,$depth,$echoLevel=0,$callBack='',$versionSwapmode='',$rootIsVersion=0,$accumulatedPath='')    {
00411 
00412             // Register page:
00413         $this->recStats['all']['pages'][$rootID] = $rootID;
00414         $pageRecord = t3lib_BEfunc::getRecordRaw('pages','uid='.intval($rootID),'deleted,title,t3ver_count,t3ver_wsid');
00415         $accumulatedPath.='/'.$pageRecord['title'];
00416 
00417             // Register if page is deleted:
00418         if ($pageRecord['deleted']) {
00419             $this->recStats['deleted']['pages'][$rootID] = $rootID;
00420         }
00421             // 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.
00422         if ($rootIsVersion) {
00423             $this->recStats['versions']['pages'][$rootID] = $rootID;
00424             if ($pageRecord['t3ver_count']>=1 && $pageRecord['t3ver_wsid']==0)  {   // If it has been published and is in archive now...
00425                 $this->recStats['versions_published']['pages'][$rootID] = $rootID;
00426             }
00427             if ($pageRecord['t3ver_wsid']==0)   {   // If it has been published and is in archive now...
00428                 $this->recStats['versions_liveWS']['pages'][$rootID] = $rootID;
00429             }
00430             if (!isset($this->workspaceIndex[$pageRecord['t3ver_wsid']]))   {   // If it doesn't belong to a workspace...
00431                 $this->recStats['versions_lost_workspace']['pages'][$rootID] = $rootID;
00432             }
00433             if ($rootIsVersion==2)  {   // In case the rootID is a version inside a versioned page
00434                 $this->recStats['versions_inside_versioned_page']['pages'][$rootID] = $rootID;
00435             }
00436         }
00437 
00438         if ($echoLevel>0)
00439             echo LF.$accumulatedPath.' ['.$rootID.']'.
00440                 ($pageRecord['deleted'] ? ' (DELETED)':'').
00441                 ($this->recStats['versions_published']['pages'][$rootID] ? ' (PUBLISHED)':'')
00442                 ;
00443         if ($echoLevel>1 && $this->recStats['versions_lost_workspace']['pages'][$rootID])
00444             echo LF.'   ERROR! This version belongs to non-existing workspace ('.$pageRecord['t3ver_wsid'].')!';
00445         if ($echoLevel>1 && $this->recStats['versions_inside_versioned_page']['pages'][$rootID])
00446             echo LF.'   WARNING! This version is inside an already versioned page or branch!';
00447 
00448             // Call back:
00449         if ($callBack)  {
00450             $this->$callBack('pages',$rootID,$echoLevel,$versionSwapmode,$rootIsVersion);
00451         }
00452 
00453         $pt3 = t3lib_div::milliseconds();
00454 
00455             // Traverse tables of records that belongs to page:
00456         foreach($GLOBALS['TCA'] as $tableName => $cfg)  {
00457             if ($tableName!='pages') {
00458 
00459                     // Select all records belonging to page:
00460                 $pt4=t3lib_div::milliseconds();
00461                 $resSub = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
00462                     'uid'.($GLOBALS['TCA'][$tableName]['ctrl']['delete']?','.$GLOBALS['TCA'][$tableName]['ctrl']['delete']:''),
00463                     $tableName,
00464                     'pid='.intval($rootID).
00465                         ($this->genTree_traverseDeleted ? '' : t3lib_BEfunc::deleteClause($tableName))
00466                 );
00467                 $this->performanceStatistics['genTree_traverse():TraverseTables:']['MySQL']['(ALL)']+= t3lib_div::milliseconds()-$pt4;
00468                 $this->performanceStatistics['genTree_traverse():TraverseTables:']['MySQL'][$tableName]+= t3lib_div::milliseconds()-$pt4;
00469 
00470                 $pt5=t3lib_div::milliseconds();
00471                 $count = $GLOBALS['TYPO3_DB']->sql_num_rows($resSub);
00472                 if ($count) {
00473                     if ($echoLevel==2)  echo LF.'   \-'.$tableName.' ('.$count.')';
00474                 }
00475 
00476                 while ($rowSub = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($resSub))    {
00477                     if ($echoLevel==3)  echo LF.'   \-'.$tableName.':'.$rowSub['uid'];
00478 
00479                         // 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:
00480                     if ($versionSwapmode=='SWAPMODE:-1' || ($versionSwapmode=='SWAPMODE:0' && !$GLOBALS['TCA'][$tableName]['ctrl']['versioning_followPages']))  {
00481                             // This is illegal records under a versioned page - therefore not registered in $this->recStats['all'] so they should be orphaned:
00482                         $this->recStats['illegal_record_under_versioned_page'][$tableName][$rowSub['uid']] = $rowSub['uid'];
00483                         if ($echoLevel>1)   echo LF.'       ERROR! Illegal record ('.$tableName.':'.$rowSub['uid'].') under versioned page!';
00484                     } else {
00485                         $this->recStats['all'][$tableName][$rowSub['uid']] = $rowSub['uid'];
00486 
00487                             // Register deleted:
00488                         if ($GLOBALS['TCA'][$tableName]['ctrl']['delete'] && $rowSub[$GLOBALS['TCA'][$tableName]['ctrl']['delete']])    {
00489                             $this->recStats['deleted'][$tableName][$rowSub['uid']] = $rowSub['uid'];
00490                             if ($echoLevel==3)  echo ' (DELETED)';
00491                         }
00492 
00493                             // Check location of records regarding tree root:
00494                         if (!$GLOBALS['TCA'][$tableName]['ctrl']['rootLevel'] && $rootID==0) {
00495                             $this->recStats['misplaced_at_rootlevel'][$tableName][$rowSub['uid']] = $rowSub['uid'];
00496                             if ($echoLevel>1)   echo LF.'       ERROR! Misplaced record ('.$tableName.':'.$rowSub['uid'].') on rootlevel!';
00497                         }
00498                         if ($GLOBALS['TCA'][$tableName]['ctrl']['rootLevel']==1 && $rootID>0) {
00499                             $this->recStats['misplaced_inside_tree'][$tableName][$rowSub['uid']] = $rowSub['uid'];
00500                             if ($echoLevel>1)   echo LF.'       ERROR! Misplaced record ('.$tableName.':'.$rowSub['uid'].') inside page tree!';
00501                         }
00502 
00503                             // Traverse plugins:
00504                         if ($callBack)  {
00505                             $this->$callBack($tableName,$rowSub['uid'],$echoLevel,$versionSwapmode,$rootIsVersion);
00506                         }
00507 
00508                             // Add any versions of those records:
00509                         if ($this->genTree_traverseVersions)    {
00510                             $versions = t3lib_BEfunc::selectVersionsOfRecord($tableName, $rowSub['uid'], 'uid,t3ver_wsid,t3ver_count'.($GLOBALS['TCA'][$tableName]['ctrl']['delete']?','.$GLOBALS['TCA'][$tableName]['ctrl']['delete']:''), 0, TRUE);
00511                             if (is_array($versions))    {
00512                                 foreach($versions as $verRec)   {
00513                                     if (!$verRec['_CURRENT_VERSION'])   {
00514                                         if ($echoLevel==3)  echo LF.'       \-[#OFFLINE VERSION: WS#'.$verRec['t3ver_wsid'].'/Cnt:'.$verRec['t3ver_count'].'] '.$tableName.':'.$verRec['uid'].')';
00515                                         $this->recStats['all'][$tableName][$verRec['uid']] = $verRec['uid'];
00516 
00517                                             // Register deleted:
00518                                         if ($GLOBALS['TCA'][$tableName]['ctrl']['delete'] && $verRec[$GLOBALS['TCA'][$tableName]['ctrl']['delete']])    {
00519                                             $this->recStats['deleted'][$tableName][$verRec['uid']] = $verRec['uid'];
00520                                             if ($echoLevel==3)  echo ' (DELETED)';
00521                                         }
00522 
00523                                             // Register version:
00524                                         $this->recStats['versions'][$tableName][$verRec['uid']] = $verRec['uid'];
00525                                         if ($verRec['t3ver_count']>=1 && $verRec['t3ver_wsid']==0)  {   // Only register published versions in LIVE workspace (published versions in draft workspaces are allowed)
00526                                             $this->recStats['versions_published'][$tableName][$verRec['uid']] = $verRec['uid'];
00527                                             if ($echoLevel==3)  echo ' (PUBLISHED)';
00528                                         }
00529                                         if ($verRec['t3ver_wsid']==0)   {
00530                                             $this->recStats['versions_liveWS'][$tableName][$verRec['uid']] = $verRec['uid'];
00531                                         }
00532                                         if (!isset($this->workspaceIndex[$verRec['t3ver_wsid']]))   {
00533                                             $this->recStats['versions_lost_workspace'][$tableName][$verRec['uid']] = $verRec['uid'];
00534                                             if ($echoLevel>1)   echo LF.'       ERROR! Version ('.$tableName.':'.$verRec['uid'].') belongs to non-existing workspace ('.$verRec['t3ver_wsid'].')!';
00535                                         }
00536                                         if ($versionSwapmode)   {   // In case we are inside a versioned branch, there should not exists versions inside that "branch".
00537                                             $this->recStats['versions_inside_versioned_page'][$tableName][$verRec['uid']] = $verRec['uid'];
00538                                             if ($echoLevel>1)   echo LF.'       ERROR! This version ('.$tableName.':'.$verRec['uid'].') is inside an already versioned page or branch!';
00539                                         }
00540 
00541                                             // Traverse plugins:
00542                                         if ($callBack)  {
00543                                             $this->$callBack($tableName,$verRec['uid'],$echoLevel,$versionSwapmode,$rootIsVersion);
00544                                         }
00545                                     }
00546                                 }
00547                             }
00548                             unset($versions);
00549                         }
00550                     }
00551                 }
00552 
00553                 $this->performanceStatistics['genTree_traverse():TraverseTables:']['Proc']['(ALL)']+= t3lib_div::milliseconds()-$pt5;
00554                 $this->performanceStatistics['genTree_traverse():TraverseTables:']['Proc'][$tableName]+= t3lib_div::milliseconds()-$pt5;
00555 
00556             }
00557         }
00558         unset($resSub);
00559         unset($rowSub);
00560         $this->performanceStatistics['genTree_traverse():TraverseTables']+= t3lib_div::milliseconds()-$pt3;
00561 
00562             // Find subpages to root ID and traverse (only when rootID is not a version or is a branch-version):
00563         if (!$versionSwapmode || $versionSwapmode=='SWAPMODE:1')    {
00564             if ($depth>0)   {
00565                 $depth--;
00566                 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
00567                     'uid',
00568                     'pages',
00569                     'pid='.intval($rootID).
00570                         ($this->genTree_traverseDeleted ? '' : t3lib_BEfunc::deleteClause('pages')),
00571                     '',
00572                     'sorting'
00573                 );
00574                 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))  {
00575                     $this->genTree_traverse($row['uid'],$depth,$echoLevel,$callBack,$versionSwapmode,0,$accumulatedPath);
00576                 }
00577             }
00578 
00579                 // Add any versions of pages
00580             if ($rootID>0 && $this->genTree_traverseVersions)   {
00581                 $versions = t3lib_BEfunc::selectVersionsOfRecord('pages', $rootID, 'uid,t3ver_oid,t3ver_wsid,t3ver_count,t3ver_swapmode', 0, TRUE);
00582                 if (is_array($versions))    {
00583                     foreach($versions as $verRec)   {
00584                         if (!$verRec['_CURRENT_VERSION'])   {
00585                             $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'].']');
00586                         }
00587                     }
00588                 }
00589             }
00590         }
00591     }
00592 
00593 
00594 
00595 
00596 
00597 
00598 
00599 
00600     /**************************
00601      *
00602      * Helper functions
00603      *
00604      *************************/
00605 
00606     /**
00607      * Compile info-string
00608      *
00609      * @param   array       Input record from sys_refindex
00610      * @return  string      String identifying the main record of the reference
00611      */
00612     function infoStr($rec)  {
00613         return $rec['tablename'].':'.$rec['recuid'].':'.$rec['field'].':'.$rec['flexpointer'].':'.$rec['softref_key'].($rec['deleted'] ? ' (DELETED)':'');
00614     }
00615 }
00616 
00617 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/lowlevel/class.tx_lowlevel_cleaner.php'])) {
00618     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/lowlevel/class.tx_lowlevel_cleaner.php']);
00619 }
00620 
00621 ?>