45 public $label_infoString =
'The list of records is organized as [table]:[uid]:[field]:[flexpointer]:[softref_key]';
71 parent::__construct();
72 $this->cleanerModules = (array)
$GLOBALS[
'TYPO3_CONF_VARS'][
'EXTCONF'][
'lowlevel'][
'cleanerModules'];
74 $this->cli_options[] = array(
'-r',
'Execute this tool, otherwise help is shown');
75 $this->cli_options[] = array(
'-v level',
'Verbosity level 0-3',
'The value of level can be: 77 1 = info and greater (default) 78 2 = warnings and greater 80 $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")');
81 $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.');
82 $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');
83 $this->cli_options[] = array(
'--YES',
'Implicit YES to all questions',
'Use this with EXTREME care. The option "-i" is not affected by this option.');
84 $this->cli_options[] = array(
'-i',
'Interactive',
'Will ask you before running the AUTOFIX on each element.');
85 $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!');
86 $this->cli_options[] = array(
'--showhowto',
'Displays HOWTO file for cleaner script.');
88 $this->
cli_help[
'name'] =
'lowlevel_cleaner -- Analysis and clean-up tools for TYPO3 installations';
89 $this->
cli_help[
'synopsis'] =
'toolkey ###OPTIONS###';
90 $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: 93 ', array_keys($this->cleanerModules));
94 $this->
cli_help[
'examples'] =
'/.../cli_dispatch.phpsh lowlevel_cleaner missing_files -s -r 95 This will show you missing files in the TYPO3 system and only report back if errors were found.';
96 $this->
cli_help[
'author'] =
'Kasper Skaarhoej, (c) 2006';
116 $GLOBALS[
'BE_USER']->user[
'admin'] = 1;
117 $GLOBALS[
'BE_USER']->setWorkspace(0);
120 $howto =
GeneralUtility::getUrl(\
TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath(
'lowlevel') .
'HOWTO_clean_up_TYPO3_installations.txt');
121 echo wordwrap($howto, 120) . LF;
125 $analysisType = (string) $this->cli_args[
'_DEFAULT'][1];
126 if (!$analysisType) {
132 if (is_array($this->cleanerModules[$analysisType])) {
134 $cleanerMode->cli_validateArgs();
137 if (!$cleanerMode->checkRefIndex || $this->cli_referenceIndexCheck()) {
138 $res = $cleanerMode->main();
144 NOW Running --AUTOFIX on result. OK?' . ($this->
cli_isArg(
'--dryrun') ?
' (--dryrun simulation)' :
''))) {
145 $cleanerMode->main_autofix($res);
147 $this->
cli_echo(
'ABORTING AutoFix... 154 $cleanerMode->cli_help();
158 $this->
cli_echo(
'ERROR: Analysis Type \'' . $analysisType .
'\' is unknown.
170 public function cli_referenceIndexCheck() { 171 // Reference index option: 172 $refIndexMode = isset($this->cli_args['--refindex
']) ? $this->cli_args['--refindex
'][0] : 'check
'; 173 if (!GeneralUtility::inList('update,ignore,check
', $refIndexMode)) { 174 $this->cli_echo('ERROR: Wrong value
for --refindex argument.
178 switch ($refIndexMode) { 182 $refIndexObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ReferenceIndex
'); 183 list($headerContent, $bodyContent, $errorCount) = $refIndexObj->updateIndex($refIndexMode == 'check
', $this->cli_echo()); 184 if ($errorCount && $refIndexMode == 'check
') { 186 $this->cli_echo('ERROR: Reference Index Check failed! (run with \
'--refindex update\' to fix) 193 $this->
cli_echo(
'Reference Index Check: Bypassing reference index check... 209 if (!preg_match($regex, $matchString)) {
210 return 'BYPASS: Filter Regex "' . $regex .
'" did not match string "' . $matchString .
'"';
221 return 'BYPASS: --dryrun set';
243 if ($detailLevel <= 1) {
244 $this->
cli_echo(
'********************************************* 245 ' . $header . LF .
'********************************************* 247 $this->
cli_echo(wordwrap(trim($res[
'message'])) . LF . LF);
250 if (is_array($res[
'headers'])) {
251 foreach ($res[
'headers'] as $key => $value) {
252 if ($detailLevel <= (
int)$value[2]) {
253 if (is_array($res[$key]) && (count($res[$key]) || !$silent)) {
255 $this->
cli_echo(
'---------------------------------------------' . LF, 1);
256 $this->
cli_echo(
'[' . $header .
']' . LF, 1);
257 $this->
cli_echo($value[0] .
' [' . $severity[$value[2]] .
']' . LF, 1);
258 $this->
cli_echo(
'---------------------------------------------' . LF, 1);
259 if (trim($value[1])) {
260 $this->
cli_echo(
'Explanation: ' . wordwrap(trim($value[1])) . LF . LF, 1);
264 if (is_array($res[$key])) {
265 if (count($res[$key])) {
270 $this->
cli_echo(
'(None)' . LF . LF);
273 $this->
cli_echo($res[$key] . LF . LF);
296 public function genTree($rootID, $depth = 1000, $echoLevel = 0, $callBack =
'') {
298 $this->performanceStatistics[
'genTree()'] =
'';
300 if (\
TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded(
'workspaces')) {
303 $this->workspaceIndex[-1] = TRUE;
304 $this->workspaceIndex[0] = TRUE;
305 $this->recStats = array(
308 'deleted' => array(),
310 'versions' => array(),
312 'versions_published' => array(),
314 'versions_liveWS' => array(),
316 'versions_lost_workspace' => array(),
318 'versions_inside_versioned_page' => array(),
320 'illegal_record_under_versioned_page' => array(),
322 'misplaced_at_rootlevel' => array(),
324 'misplaced_inside_tree' => array()
328 $this->performanceStatistics[
'genTree_traverse()'] =
'';
329 $this->performanceStatistics[
'genTree_traverse():TraverseTables'] =
'';
333 foreach ($this->recStats as $kk => $vv) {
334 foreach ($this->recStats[$kk] as $tables => $recArrays) {
335 ksort($this->recStats[$kk][$tables]);
337 ksort($this->recStats[$kk]);
339 if ($echoLevel > 0) {
345 foreach (
$GLOBALS[
'TCA'] as $tableName => $cfg) {
347 $resSub =
$GLOBALS[
'TYPO3_DB']->exec_SELECTquery(
'count(*)', $tableName,
'');
348 $countRow =
$GLOBALS[
'TYPO3_DB']->sql_fetch_assoc($resSub);
349 $this->performanceStatistics[
'MySQL_count'][$tableName] = $countRow[
'count(*)'];
350 $this->performanceStatistics[
'CSV'] .= LF . $tableName .
',' . $this->performanceStatistics[
'genTree_traverse():TraverseTables:'][
'MySQL'][$tableName] .
',' . $this->performanceStatistics[
'genTree_traverse():TraverseTables:'][
'Proc'][$tableName] .
',' . $this->performanceStatistics[
'MySQL_count'][$tableName];
352 $this->performanceStatistics[
'recStats_size'][
'(ALL)'] = strlen(serialize($this->recStats));
353 foreach ($this->recStats as $key => $arrcontent) {
354 $this->performanceStatistics[
'recStats_size'][$key] = strlen(serialize($arrcontent));
373 public function genTree_traverse($rootID, $depth, $echoLevel = 0, $callBack =
'', $versionSwapmode =
'', $rootIsVersion = 0, $accumulatedPath =
'') {
375 $this->recStats[
'all'][
'pages'][$rootID] = $rootID;
377 $accumulatedPath .=
'/' . $pageRecord[
'title'];
379 if ($pageRecord[
'deleted']) {
380 $this->recStats[
'deleted'][
'pages'][$rootID] = $rootID;
383 if ($rootIsVersion) {
384 $this->recStats[
'versions'][
'pages'][$rootID] = $rootID;
386 if ($pageRecord[
't3ver_count'] >= 1 && $pageRecord[
't3ver_wsid'] == 0) {
387 $this->recStats[
'versions_published'][
'pages'][$rootID] = $rootID;
390 if ($pageRecord[
't3ver_wsid'] == 0) {
391 $this->recStats[
'versions_liveWS'][
'pages'][$rootID] = $rootID;
394 if (!isset($this->workspaceIndex[$pageRecord[
't3ver_wsid']])) {
395 $this->recStats[
'versions_lost_workspace'][
'pages'][$rootID] = $rootID;
398 if ($rootIsVersion == 2) {
399 $this->recStats[
'versions_inside_versioned_page'][
'pages'][$rootID] = $rootID;
402 if ($echoLevel > 0) {
403 echo LF . $accumulatedPath .
' [' . $rootID .
']' . ($pageRecord[
'deleted'] ?
' (DELETED)' :
'') . ($this->recStats[
'versions_published'][
'pages'][$rootID] ?
' (PUBLISHED)' :
'');
405 if ($echoLevel > 1 && $this->recStats[
'versions_lost_workspace'][
'pages'][$rootID]) {
406 echo LF .
' ERROR! This version belongs to non-existing workspace (' . $pageRecord[
't3ver_wsid'] .
')!';
408 if ($echoLevel > 1 && $this->recStats[
'versions_inside_versioned_page'][
'pages'][$rootID]) {
409 echo LF .
' WARNING! This version is inside an already versioned page or branch!';
413 $this->{$callBack}(
'pages', $rootID, $echoLevel, $versionSwapmode, $rootIsVersion);
417 foreach (
$GLOBALS[
'TCA'] as $tableName => $cfg) {
418 if ($tableName !=
'pages') {
421 $resSub =
$GLOBALS[
'TYPO3_DB']->exec_SELECTquery(
'uid' . (
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete'] ?
',' .
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete'] :
''), $tableName,
'pid=' . (
int)$rootID . ($this->genTree_traverseDeleted ?
'' :
BackendUtility::deleteClause($tableName)));
425 $count =
$GLOBALS[
'TYPO3_DB']->sql_num_rows($resSub);
427 if ($echoLevel == 2) {
428 echo LF .
' \\-' . $tableName .
' (' . $count .
')';
431 while ($rowSub =
$GLOBALS[
'TYPO3_DB']->sql_fetch_assoc($resSub)) {
432 if ($echoLevel == 3) {
433 echo LF .
' \\-' . $tableName .
':' . $rowSub[
'uid'];
436 if ($versionSwapmode ==
'SWAPMODE:-1' || $versionSwapmode ==
'SWAPMODE:0' && !
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'versioning_followPages']) {
438 $this->recStats[
'illegal_record_under_versioned_page'][$tableName][$rowSub[
'uid']] = $rowSub[
'uid'];
439 if ($echoLevel > 1) {
440 echo LF .
' ERROR! Illegal record (' . $tableName .
':' . $rowSub[
'uid'] .
') under versioned page!';
443 $this->recStats[
'all'][$tableName][$rowSub[
'uid']] = $rowSub[
'uid'];
445 if (
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete'] && $rowSub[
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete']]) {
446 $this->recStats[
'deleted'][$tableName][$rowSub[
'uid']] = $rowSub[
'uid'];
447 if ($echoLevel == 3) {
452 if (!
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'rootLevel'] && $rootID == 0) {
453 $this->recStats[
'misplaced_at_rootlevel'][$tableName][$rowSub[
'uid']] = $rowSub[
'uid'];
454 if ($echoLevel > 1) {
455 echo LF .
' ERROR! Misplaced record (' . $tableName .
':' . $rowSub[
'uid'] .
') on rootlevel!';
458 if (
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'rootLevel'] == 1 && $rootID > 0) {
459 $this->recStats[
'misplaced_inside_tree'][$tableName][$rowSub[
'uid']] = $rowSub[
'uid'];
460 if ($echoLevel > 1) {
461 echo LF .
' ERROR! Misplaced record (' . $tableName .
':' . $rowSub[
'uid'] .
') inside page tree!';
466 $this->{$callBack}($tableName, $rowSub[
'uid'], $echoLevel, $versionSwapmode, $rootIsVersion);
469 if ($this->genTree_traverseVersions) {
471 if (is_array($versions)) {
472 foreach ($versions as $verRec) {
473 if (!$verRec[
'_CURRENT_VERSION']) {
474 if ($echoLevel == 3) {
475 echo LF .
' \\-[#OFFLINE VERSION: WS#' . $verRec[
't3ver_wsid'] .
'/Cnt:' . $verRec[
't3ver_count'] .
'] ' . $tableName .
':' . $verRec[
'uid'] .
')';
477 $this->recStats[
'all'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
479 if (
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete'] && $verRec[
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete']]) {
480 $this->recStats[
'deleted'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
481 if ($echoLevel == 3) {
486 $this->recStats[
'versions'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
487 if ($verRec[
't3ver_count'] >= 1 && $verRec[
't3ver_wsid'] == 0) {
489 $this->recStats[
'versions_published'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
490 if ($echoLevel == 3) {
494 if ($verRec[
't3ver_wsid'] == 0) {
495 $this->recStats[
'versions_liveWS'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
497 if (!isset($this->workspaceIndex[$verRec[
't3ver_wsid']])) {
498 $this->recStats[
'versions_lost_workspace'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
499 if ($echoLevel > 1) {
500 echo LF .
' ERROR! Version (' . $tableName .
':' . $verRec[
'uid'] .
') belongs to non-existing workspace (' . $verRec[
't3ver_wsid'] .
')!';
504 if ($versionSwapmode) {
505 $this->recStats[
'versions_inside_versioned_page'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
506 if ($echoLevel > 1) {
507 echo LF .
' ERROR! This version (' . $tableName .
':' . $verRec[
'uid'] .
') is inside an already versioned page or branch!';
512 $this->{$callBack}($tableName, $verRec[
'uid'], $echoLevel, $versionSwapmode, $rootIsVersion);
529 if (!$versionSwapmode || $versionSwapmode ==
'SWAPMODE:1') {
532 $res =
$GLOBALS[
'TYPO3_DB']->exec_SELECTquery(
'uid',
'pages',
'pid=' . (
int)$rootID . ($this->genTree_traverseDeleted ?
'' :
BackendUtility::deleteClause(
'pages')),
'',
'sorting');
533 while ($row =
$GLOBALS[
'TYPO3_DB']->sql_fetch_assoc($res)) {
534 $this->
genTree_traverse($row[
'uid'], $depth, $echoLevel, $callBack, $versionSwapmode, 0, $accumulatedPath);
538 if ($rootID > 0 && $this->genTree_traverseVersions) {
540 if (is_array($versions)) {
541 foreach ($versions as $verRec) {
542 if (!$verRec[
'_CURRENT_VERSION']) {
543 $this->
genTree_traverse($verRec[
'uid'], $depth, $echoLevel, $callBack,
'SWAPMODE:-1', $versionSwapmode ? 2 : 1, $accumulatedPath .
' [#OFFLINE VERSION: WS#' . $verRec[
't3ver_wsid'] .
'/Cnt:' . $verRec[
't3ver_count'] .
']');
564 return $rec[
'tablename'] .
':' . $rec[
'recuid'] .
':' . $rec[
'field'] .
':' . $rec[
'flexpointer'] .
':' . $rec[
'softref_key'] . ($rec[
'deleted'] ?
' (DELETED)' :
'');
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
cli_setArguments(array $argv=array())
static getUserObj($classRef, $checkPrefix='', $silent=FALSE)
cli_keyboardInput_yes($msg='')
cli_echo($string='', $force=FALSE)
cli_argValue($option, $idx=0)
cli_printInfo($header, $res)
$genTree_traverseVersions
static getUrl($url, $includeHeader=0, $requestHeaders=FALSE, &$report=NULL)
cli_noExecutionCheck($matchString)
static getRecordRaw($table, $where='', $fields=' *')
static selectVersionsOfRecord($table, $uid, $fields=' *', $workspace=0, $includeDeletedRecords=FALSE, $row=NULL)
genTree($rootID, $depth=1000, $echoLevel=0, $callBack='')
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
static deleteClause($table, $tableAlias='')
genTree_traverse($rootID, $depth, $echoLevel=0, $callBack='', $versionSwapmode='', $rootIsVersion=0, $accumulatedPath='')