00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00059 require_once(PATH_t3lib.'class.t3lib_admin.php');
00060 require_once(PATH_t3lib.'class.t3lib_cli.php');
00061
00062
00063
00071 class tx_lowlevel_cleaner_core extends t3lib_cli {
00072
00073 var $genTree_traverseDeleted = TRUE;
00074 var $genTree_traverseVersions = TRUE;
00075
00076
00077
00078 var $label_infoString = 'The list of records is organized as [table]:[uid]:[field]:[flexpointer]:[softref_key]';
00079 var $pagetreePlugins = array();
00080 var $cleanerModules = array();
00081
00082
00088 function tx_lowlevel_cleaner_core() {
00089
00090
00091 parent::t3lib_cli();
00092
00093 $this->cleanerModules = (array)$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lowlevel']['cleanerModules'];
00094
00095
00096 $this->cli_options[] = array('-r', 'Execute this tool, otherwise help is shown');
00097 $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");
00098 $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")');
00099 $this->cli_options[] = array('--AUTOFIX', '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!');
00100 $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');
00101 $this->cli_options[] = array('--YES', 'Implicit YES to all questions','Use this with EXTREME care. The option "-i" is not affected by this option.');
00102 $this->cli_options[] = array('-i', 'Interactive','Will ask you before running the AUTOFIX on each element.');
00103 $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!');
00104 $this->cli_options[] = array('--showhowto', 'Displays HOWTO file for cleaner script.');
00105
00106
00107 $this->cli_help['name'] = 'lowlevel_cleaner -- Analysis and clean-up tools for TYPO3 installations';
00108 $this->cli_help['synopsis'] = 'toolkey ###OPTIONS###';
00109 $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));
00110 $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.";
00111 $this->cli_help['author'] = "Kasper Skaarhoej, (c) 2006";
00112 }
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00134 function cli_main($argv) {
00135
00136
00137 $GLOBALS['BE_USER']->user['admin'] = 1;
00138 $GLOBALS['BE_USER']->setWorkspace(0);
00139
00140
00141 if ($this->cli_isArg('--showhowto')) {
00142 $howto = t3lib_div::getUrl(t3lib_extMgm::extPath('lowlevel').'HOWTO_clean_up_TYPO3_installations.txt');
00143 echo wordwrap($howto,120).chr(10);
00144 exit;
00145 }
00146
00147
00148 $analysisType = (string)$this->cli_args['_DEFAULT'][1];
00149 if (!$analysisType) {
00150 $this->cli_validateArgs();
00151 $this->cli_help();
00152 exit;
00153 }
00154
00155
00156 switch((string)$analysisType) {
00157 default:
00158 if (is_array($this->cleanerModules[$analysisType])) {
00159 $cleanerMode = &t3lib_div::getUserObj($this->cleanerModules[$analysisType][0]);
00160 $cleanerMode->cli_validateArgs();
00161
00162 if ($this->cli_isArg('-r')) {
00163 if (!$cleanerMode->checkRefIndex || $this->cli_referenceIndexCheck()) {
00164 $res = $cleanerMode->main();
00165 $this->cli_printInfo($analysisType, $res);
00166
00167
00168 if ($this->cli_isArg('--AUTOFIX')) {
00169 if ($this->cli_isArg('--YES') || $this->cli_keyboardInput_yes("\n\nNOW Running --AUTOFIX on result. OK?".($this->cli_isArg('--dryrun')?' (--dryrun simulation)':''))) {
00170 $cleanerMode->main_autofix($res);
00171 } else {
00172 $this->cli_echo("ABORTING AutoFix...\n",1);
00173 }
00174 }
00175 }
00176 } else {
00177 $cleanerMode->cli_help();
00178 exit;
00179 }
00180 } else {
00181 $this->cli_echo("ERROR: Analysis Type '".$analysisType."' is unknown.\n",1);
00182 exit;
00183 }
00184 break;
00185 }
00186 }
00187
00193 function cli_referenceIndexCheck() {
00194
00195
00196 $refIndexMode = isset($this->cli_args['--refindex']) ? $this->cli_args['--refindex'][0] : 'check';
00197 if (!t3lib_div::inList('update,ignore,check', $refIndexMode)) {
00198 $this->cli_echo("ERROR: Wrong value for --refindex argument.\n",1);
00199 exit;
00200 }
00201
00202 switch($refIndexMode) {
00203 case 'check':
00204 case 'update':
00205 $refIndexObj = t3lib_div::makeInstance('t3lib_refindex');
00206 list($headerContent,$bodyContent,$errorCount) = $refIndexObj->updateIndex($refIndexMode=='check',$this->cli_echo());
00207
00208 if ($errorCount && $refIndexMode=='check') {
00209 $ok = FALSE;
00210 $this->cli_echo("ERROR: Reference Index Check failed! (run with '--refindex update' to fix)\n",1);
00211 } else {
00212 $ok = TRUE;
00213 }
00214 break;
00215 case 'ignore':
00216 $this->cli_echo("Reference Index Check: Bypassing reference index check...\n");
00217 $ok = TRUE;
00218 break;
00219 }
00220
00221 return $ok;
00222 }
00223
00228 function cli_noExecutionCheck($matchString) {
00229
00230
00231 if ($this->cli_isArg('--filterRegex') && $regex = $this->cli_argValue('--filterRegex',0)) {
00232 if (!preg_match($regex,$matchString)) return 'BYPASS: Filter Regex "'.$regex.'" did not match string "'.$matchString.'"';
00233 }
00234
00235 if ($this->cli_isArg('-i')) {
00236 if (!$this->cli_keyboardInput_yes(' EXECUTE?')) {
00237 return 'BYPASS...';
00238 }
00239 }
00240
00241 if ($this->cli_isArg('--dryrun')) return 'BYPASS: --dryrun set';
00242 }
00243
00251 function cli_printInfo($header,$res) {
00252
00253 $detailLevel = t3lib_div::intInRange($this->cli_isArg('-v') ? $this->cli_argValue('-v') : 1,0,3);
00254 $silent = !$this->cli_echo();
00255
00256 $severity = array(
00257 0 => 'MESSAGE',
00258 1 => 'INFO',
00259 2 => 'WARNING',
00260 3 => 'ERROR',
00261 );
00262
00263
00264 if ($detailLevel <= 1) {
00265 $this->cli_echo(
00266 "*********************************************\n".
00267 $header."\n".
00268 "*********************************************\n");
00269 $this->cli_echo(wordwrap(trim($res['message'])).chr(10).chr(10));
00270 }
00271
00272
00273 if (is_array($res['headers'])) {
00274 foreach($res['headers'] as $key => $value) {
00275
00276 if ($detailLevel <= intval($value[2])) {
00277 if (is_array($res[$key]) && (count($res[$key]) || !$silent)) {
00278
00279
00280 $this->cli_echo('---------------------------------------------'.chr(10),1);
00281 $this->cli_echo('['.$header.']'.chr(10),1);
00282 $this->cli_echo($value[0].' ['.$severity[$value[2]].']'.chr(10),1);
00283 $this->cli_echo('---------------------------------------------'.chr(10),1);
00284 if (trim($value[1])) {
00285 $this->cli_echo('Explanation: '.wordwrap(trim($value[1])).chr(10).chr(10),1);
00286 }
00287 }
00288
00289
00290 if (is_array($res[$key])) {
00291 if (count($res[$key])) {
00292 if ($this->cli_echo('',1)) { print_r($res[$key]); }
00293 } else {
00294 $this->cli_echo('(None)'.chr(10).chr(10));
00295 }
00296 } else {
00297 $this->cli_echo($res[$key].chr(10).chr(10));
00298 }
00299 }
00300 }
00301 }
00302 }
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00331 function genTree($rootID,$depth=1000,$echoLevel=0,$callBack='') {
00332
00333
00334 $this->workspaceIndex = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,title','sys_workspace','1=1'.t3lib_BEfunc::deleteClause('sys_workspace'),'','','','uid');
00335 $this->workspaceIndex[-1] = TRUE;
00336 $this->workspaceIndex[0] = TRUE;
00337
00338 $this->recStats = array(
00339 'all' => array(),
00340 'deleted' => array(),
00341 'versions' => array(),
00342 'versions_published' => array(),
00343 'versions_liveWS' => array(),
00344 'versions_lost_workspace' => array(),
00345 'versions_inside_versioned_page' => array(),
00346 'illegal_record_under_versioned_page' => array(),
00347 'misplaced_at_rootlevel' => array(),
00348 'misplaced_inside_tree' => array(),
00349 );
00350
00351
00352 $this->genTree_traverse($rootID,$depth,$echoLevel,$callBack);
00353
00354 if ($echoLevel>0) echo chr(10).chr(10);
00355 }
00356
00370 function genTree_traverse($rootID,$depth,$echoLevel=0,$callBack='',$versionSwapmode='',$rootIsVersion=0,$accumulatedPath='') {
00371
00372
00373 $this->recStats['all']['pages'][$rootID] = $rootID;
00374 $pageRecord = t3lib_BEfunc::getRecordRaw('pages','uid='.intval($rootID),'deleted,title,t3ver_count,t3ver_wsid');
00375 $accumulatedPath.='/'.$pageRecord['title'];
00376
00377
00378 if ($pageRecord['deleted']) {
00379 $this->recStats['deleted']['pages'][$rootID] = $rootID;
00380 }
00381
00382 if ($rootIsVersion) {
00383 $this->recStats['versions']['pages'][$rootID] = $rootID;
00384 if ($pageRecord['t3ver_count']>=1 && $pageRecord['t3ver_wsid']==0) {
00385 $this->recStats['versions_published']['pages'][$rootID] = $rootID;
00386 }
00387 if ($pageRecord['t3ver_wsid']==0) {
00388 $this->recStats['versions_liveWS']['pages'][$rootID] = $rootID;
00389 }
00390 if (!isset($this->workspaceIndex[$pageRecord['t3ver_wsid']])) {
00391 $this->recStats['versions_lost_workspace']['pages'][$rootID] = $rootID;
00392 }
00393 if ($rootIsVersion==2) {
00394 $this->recStats['versions_inside_versioned_page']['pages'][$rootID] = $rootID;
00395 }
00396 }
00397
00398 if ($echoLevel>0)
00399 echo chr(10).$accumulatedPath.' ['.$rootID.']'.
00400 ($pageRecord['deleted'] ? ' (DELETED)':'').
00401 ($this->recStats['versions_published']['pages'][$rootID] ? ' (PUBLISHED)':'')
00402 ;
00403 if ($echoLevel>1 && $this->recStats['versions_lost_workspace']['pages'][$rootID])
00404 echo chr(10).' ERROR! This version belongs to non-existing workspace ('.$pageRecord['t3ver_wsid'].')!';
00405 if ($echoLevel>1 && $this->recStats['versions_inside_versioned_page']['pages'][$rootID])
00406 echo chr(10).' WARNING! This version is inside an already versioned page or branch!';
00407
00408
00409 if ($callBack) {
00410 $this->$callBack('pages',$rootID,$echoLevel,$versionSwapmode,$rootIsVersion);
00411 }
00412
00413
00414 foreach($GLOBALS['TCA'] as $tableName => $cfg) {
00415 if ($tableName!='pages') {
00416
00417
00418 $resSub = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
00419 'uid'.($GLOBALS['TCA'][$tableName]['ctrl']['delete']?','.$GLOBALS['TCA'][$tableName]['ctrl']['delete']:''),
00420 $tableName,
00421 'pid='.intval($rootID).
00422 ($this->genTree_traverseDeleted ? '' : t3lib_BEfunc::deleteClause($tableName))
00423 );
00424
00425 $count = $GLOBALS['TYPO3_DB']->sql_num_rows($resSub);
00426 if ($count) {
00427 if ($echoLevel==2) echo chr(10).' \-'.$tableName.' ('.$count.')';
00428 }
00429
00430 while ($rowSub = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($resSub)) {
00431 if ($echoLevel==3) echo chr(10).' \-'.$tableName.':'.$rowSub['uid'];
00432
00433
00434 if ($versionSwapmode=='SWAPMODE:-1' || ($versionSwapmode=='SWAPMODE:0' && !$GLOBALS['TCA'][$tableName]['ctrl']['versioning_followPages'])) {
00435
00436 $this->recStats['illegal_record_under_versioned_page'][$tableName][$rowSub['uid']] = $rowSub['uid'];
00437 if ($echoLevel>1) echo chr(10).' ERROR! Illegal record ('.$tableName.':'.$rowSub['uid'].') under versioned page!';
00438 } else {
00439 $this->recStats['all'][$tableName][$rowSub['uid']] = $rowSub['uid'];
00440
00441
00442 if ($GLOBALS['TCA'][$tableName]['ctrl']['delete'] && $rowSub[$GLOBALS['TCA'][$tableName]['ctrl']['delete']]) {
00443 $this->recStats['deleted'][$tableName][$rowSub['uid']] = $rowSub['uid'];
00444 if ($echoLevel==3) echo ' (DELETED)';
00445 }
00446
00447
00448 if (!$GLOBALS['TCA'][$tableName]['ctrl']['rootLevel'] && $rootID==0) {
00449 $this->recStats['misplaced_at_rootlevel'][$tableName][$rowSub['uid']] = $rowSub['uid'];
00450 if ($echoLevel>1) echo chr(10).' ERROR! Misplaced record ('.$tableName.':'.$rowSub['uid'].') on rootlevel!';
00451 }
00452 if ($GLOBALS['TCA'][$tableName]['ctrl']['rootLevel']==1 && $rootID>0) {
00453 $this->recStats['misplaced_inside_tree'][$tableName][$rowSub['uid']] = $rowSub['uid'];
00454 if ($echoLevel>1) echo chr(10).' ERROR! Misplaced record ('.$tableName.':'.$rowSub['uid'].') inside page tree!';
00455 }
00456
00457
00458 if ($callBack) {
00459 $this->$callBack($tableName,$rowSub['uid'],$echoLevel,$versionSwapmode,$rootIsVersion);
00460 }
00461
00462
00463 if ($this->genTree_traverseVersions) {
00464 $versions = t3lib_BEfunc::selectVersionsOfRecord($tableName, $rowSub['uid'], 'uid,t3ver_wsid,t3ver_count'.($GLOBALS['TCA'][$tableName]['ctrl']['delete']?','.$GLOBALS['TCA'][$tableName]['ctrl']['delete']:''), 0, TRUE);
00465 if (is_array($versions)) {
00466 foreach($versions as $verRec) {
00467 if (!$verRec['_CURRENT_VERSION']) {
00468 if ($echoLevel==3) echo chr(10).' \-[#OFFLINE VERSION: WS#'.$verRec['t3ver_wsid'].'/Cnt:'.$verRec['t3ver_count'].'] '.$tableName.':'.$verRec['uid'].')';
00469 $this->recStats['all'][$tableName][$verRec['uid']] = $verRec['uid'];
00470
00471
00472 if ($GLOBALS['TCA'][$tableName]['ctrl']['delete'] && $verRec[$GLOBALS['TCA'][$tableName]['ctrl']['delete']]) {
00473 $this->recStats['deleted'][$tableName][$verRec['uid']] = $verRec['uid'];
00474 if ($echoLevel==3) echo ' (DELETED)';
00475 }
00476
00477
00478 $this->recStats['versions'][$tableName][$verRec['uid']] = $verRec['uid'];
00479 if ($verRec['t3ver_count']>=1 && $verRec['t3ver_wsid']==0) {
00480 $this->recStats['versions_published'][$tableName][$verRec['uid']] = $verRec['uid'];
00481 if ($echoLevel==3) echo ' (PUBLISHED)';
00482 }
00483 if ($verRec['t3ver_wsid']==0) {
00484 $this->recStats['versions_liveWS'][$tableName][$verRec['uid']] = $verRec['uid'];
00485 }
00486 if (!isset($this->workspaceIndex[$verRec['t3ver_wsid']])) {
00487 $this->recStats['versions_lost_workspace'][$tableName][$verRec['uid']] = $verRec['uid'];
00488 if ($echoLevel>1) echo chr(10).' ERROR! Version ('.$tableName.':'.$verRec['uid'].') belongs to non-existing workspace ('.$verRec['t3ver_wsid'].')!';
00489 }
00490 if ($versionSwapmode) {
00491 $this->recStats['versions_inside_versioned_page'][$tableName][$verRec['uid']] = $verRec['uid'];
00492 if ($echoLevel>1) echo chr(10).' ERROR! This version ('.$tableName.':'.$verRec['uid'].') is inside an already versioned page or branch!';
00493 }
00494
00495
00496 if ($callBack) {
00497 $this->$callBack($tableName,$verRec['uid'],$echoLevel,$versionSwapmode,$rootIsVersion);
00498 }
00499 }
00500 }
00501 }
00502 unset($versions);
00503 }
00504 }
00505 }
00506 }
00507 }
00508 unset($resSub);
00509 unset($rowSub);
00510
00511
00512 if (!$versionSwapmode || $versionSwapmode=='SWAPMODE:1') {
00513 if ($depth>0) {
00514 $depth--;
00515 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
00516 'uid',
00517 'pages',
00518 'pid='.intval($rootID).
00519 ($this->genTree_traverseDeleted ? '' : t3lib_BEfunc::deleteClause('pages')),
00520 '',
00521 'sorting'
00522 );
00523 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
00524 $this->genTree_traverse($row['uid'],$depth,$echoLevel,$callBack,$versionSwapmode,0,$accumulatedPath);
00525 }
00526 }
00527
00528
00529 if ($rootID>0 && $this->genTree_traverseVersions) {
00530 $versions = t3lib_BEfunc::selectVersionsOfRecord('pages', $rootID, 'uid,t3ver_oid,t3ver_wsid,t3ver_count,t3ver_swapmode', 0, TRUE);
00531 if (is_array($versions)) {
00532 foreach($versions as $verRec) {
00533 if (!$verRec['_CURRENT_VERSION']) {
00534 $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'].']');
00535 }
00536 }
00537 }
00538 }
00539 }
00540 }
00541
00542
00543
00544
00545
00546
00547
00548
00549
00550
00551
00552
00553
00554
00561 function infoStr($rec) {
00562 return $rec['tablename'].':'.$rec['recuid'].':'.$rec['field'].':'.$rec['flexpointer'].':'.$rec['softref_key'].($rec['deleted'] ? ' (DELETED)':'');
00563 }
00564 }
00565
00566 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/lowlevel/class.tx_lowlevel_cleaner.php']) {
00567 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/lowlevel/class.tx_lowlevel_cleaner.php']);
00568 }
00569 ?>