2 namespace TYPO3\CMS\Lowlevel;
38 public $label_infoString =
'The list of records is organized as [table]:[uid]:[field]:[flexpointer]:[softref_key]';
66 parent::__construct();
67 $this->cleanerModules = (array)
$GLOBALS[
'TYPO3_CONF_VARS'][
'EXTCONF'][
'lowlevel'][
'cleanerModules'];
69 $this->cli_options[] = array(
'-r',
'Execute this tool, otherwise help is shown');
70 $this->cli_options[] = array(
'-v level',
'Verbosity level 0-3',
'The value of level can be:
72 1 = info and greater (default)
73 2 = warnings and greater
75 $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")');
76 $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.');
77 $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');
78 $this->cli_options[] = array(
'--YES',
'Implicit YES to all questions',
'Use this with EXTREME care. The option "-i" is not affected by this option.');
79 $this->cli_options[] = array(
'-i',
'Interactive',
'Will ask you before running the AUTOFIX on each element.');
80 $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!');
81 $this->cli_options[] = array(
'--showhowto',
'Displays HOWTO file for cleaner script.');
83 $this->
cli_help[
'name'] =
'lowlevel_cleaner -- Analysis and clean-up tools for TYPO3 installations';
84 $this->
cli_help[
'synopsis'] =
'toolkey ###OPTIONS###';
85 $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:
88 ', array_keys($this->cleanerModules));
89 $this->
cli_help[
'examples'] =
'/.../cli_dispatch.phpsh lowlevel_cleaner missing_files -s -r
90 This will show you missing files in the TYPO3 system and only report back if errors were found.';
91 $this->
cli_help[
'author'] =
'Kasper Skaarhoej, (c) 2006';
110 $GLOBALS[
'BE_USER']->user[
'admin'] = 1;
111 $GLOBALS[
'BE_USER']->setWorkspace(0);
114 $howto =
GeneralUtility::getUrl(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath(
'lowlevel') .
'README.rst');
115 echo wordwrap($howto, 120) . LF;
119 $analysisType = (string)$this->cli_args[
'_DEFAULT'][1];
120 if (!$analysisType) {
126 if (is_array($this->cleanerModules[$analysisType])) {
128 $cleanerMode->cli_validateArgs();
131 if (!$cleanerMode->checkRefIndex || $this->cli_referenceIndexCheck()) {
132 $res = $cleanerMode->main();
138 NOW Running --AUTOFIX on result. OK?' . ($this->
cli_isArg(
'--dryrun') ?
' (--dryrun simulation)' :
''))) {
139 $cleanerMode->main_autofix($res);
141 $this->
cli_echo(
'ABORTING AutoFix...
148 $cleanerMode->cli_help();
152 $this->
cli_echo(
'ERROR: Analysis Type \'' . $analysisType .
'\' is unknown.
163 public function cli_referenceIndexCheck()
165 // Reference index option:
166 $refIndexMode = isset($this->cli_args['--refindex
']) ? $this->cli_args['--refindex
'][0] : 'check
';
167 if (!GeneralUtility::inList('update,ignore,check
', $refIndexMode)) {
168 $this->cli_echo('ERROR: Wrong value
for --refindex argument.
172 switch ($refIndexMode) {
176 $refIndexObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ReferenceIndex::class);
177 list($headerContent, $bodyContent, $errorCount) = $refIndexObj->updateIndex($refIndexMode == 'check
', $this->cli_echo());
178 if ($errorCount && $refIndexMode == 'check
') {
180 $this->cli_echo('ERROR: Reference Index Check failed! (run with \
'--refindex update\' to fix)
187 $this->
cli_echo(
'Reference Index Check: Bypassing reference index check...
199 public function cli_noExecutionCheck($matchString)
202 if ($this->cli_isArg(
'--filterRegex') && ($regex = $this->cli_argValue(
'--filterRegex', 0))) {
203 if (!preg_match($regex, $matchString)) {
204 return 'BYPASS: Filter Regex "' . $regex .
'" did not match string "' . $matchString .
'"';
208 if ($this->cli_isArg(
'-i')) {
209 if (!$this->cli_keyboardInput_yes(
' EXECUTE?')) {
214 if ($this->cli_isArg(
'--dryrun')) {
215 return 'BYPASS: --dryrun set';
226 public function cli_printInfo($header, $res)
228 $detailLevel = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->cli_isArg(
'-v') ? $this->cli_argValue(
'-v') : 1, 0, 3);
229 $silent = !$this->cli_echo();
237 if ($detailLevel <= 1) {
238 $this->cli_echo(
'*********************************************
239 ' . $header . LF .
'*********************************************
241 $this->cli_echo(wordwrap(trim($res[
'message'])) . LF . LF);
244 if (is_array($res[
'headers'])) {
245 foreach ($res[
'headers'] as $key => $value) {
246 if ($detailLevel <= (
int)$value[2]) {
247 if (is_array($res[$key]) && (count($res[$key]) || !$silent)) {
249 $this->cli_echo(
'---------------------------------------------' . LF, 1);
250 $this->cli_echo(
'[' . $header .
']' . LF, 1);
251 $this->cli_echo($value[0] .
' [' . $severity[$value[2]] .
']' . LF, 1);
252 $this->cli_echo(
'---------------------------------------------' . LF, 1);
253 if (trim($value[1])) {
254 $this->cli_echo(
'Explanation: ' . wordwrap(trim($value[1])) . LF . LF, 1);
258 if (is_array($res[$key])) {
259 if (count($res[$key])) {
260 if ($this->cli_echo(
'', 1)) {
264 $this->cli_echo(
'(None)' . LF . LF);
267 $this->cli_echo($res[$key] . LF . LF);
289 public function genTree($rootID, $depth = 1000, $echoLevel = 0, $callBack =
'')
292 $this->performanceStatistics[
'genTree()'] =
'';
294 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded(
'workspaces')) {
297 $this->workspaceIndex[-1] =
true;
298 $this->workspaceIndex[0] =
true;
299 $this->recStats = array(
302 'deleted' => array(),
304 'versions' => array(),
306 'versions_published' => array(),
308 'versions_liveWS' => array(),
310 'versions_lost_workspace' => array(),
312 'versions_inside_versioned_page' => array(),
314 'illegal_record_under_versioned_page' => array(),
316 'misplaced_at_rootlevel' => array(),
318 'misplaced_inside_tree' => array()
322 $this->performanceStatistics[
'genTree_traverse()'] =
'';
323 $this->performanceStatistics[
'genTree_traverse():TraverseTables'] =
'';
324 $this->genTree_traverse($rootID, $depth, $echoLevel, $callBack);
327 foreach ($this->recStats as $kk => $vv) {
328 foreach ($this->recStats[$kk] as $tables => $recArrays) {
329 ksort($this->recStats[$kk][$tables]);
331 ksort($this->recStats[$kk]);
333 if ($echoLevel > 0) {
339 foreach (
$GLOBALS[
'TCA'] as $tableName => $cfg) {
341 $resSub =
$GLOBALS[
'TYPO3_DB']->exec_SELECTquery(
'count(*)', $tableName,
'');
342 $countRow =
$GLOBALS[
'TYPO3_DB']->sql_fetch_assoc($resSub);
343 $this->performanceStatistics[
'MySQL_count'][$tableName] = $countRow[
'count(*)'];
344 $this->performanceStatistics[
'CSV'] .= LF . $tableName .
',' . $this->performanceStatistics[
'genTree_traverse():TraverseTables:'][
'MySQL'][$tableName] .
',' . $this->performanceStatistics[
'genTree_traverse():TraverseTables:'][
'Proc'][$tableName] .
',' . $this->performanceStatistics[
'MySQL_count'][$tableName];
346 $this->performanceStatistics[
'recStats_size'][
'(ALL)'] = strlen(serialize($this->recStats));
347 foreach ($this->recStats as $key => $arrcontent) {
348 $this->performanceStatistics[
'recStats_size'][$key] = strlen(serialize($arrcontent));
366 public function genTree_traverse($rootID, $depth, $echoLevel = 0, $callBack =
'', $versionSwapmode =
'', $rootIsVersion = 0, $accumulatedPath =
'')
369 $this->recStats[
'all'][
'pages'][$rootID] = $rootID;
371 $accumulatedPath .=
'/' . $pageRecord[
'title'];
373 if ($pageRecord[
'deleted']) {
374 $this->recStats[
'deleted'][
'pages'][$rootID] = $rootID;
377 if ($rootIsVersion) {
378 $this->recStats[
'versions'][
'pages'][$rootID] = $rootID;
380 if ($pageRecord[
't3ver_count'] >= 1 && $pageRecord[
't3ver_wsid'] == 0) {
381 $this->recStats[
'versions_published'][
'pages'][$rootID] = $rootID;
384 if ($pageRecord[
't3ver_wsid'] == 0) {
385 $this->recStats[
'versions_liveWS'][
'pages'][$rootID] = $rootID;
388 if (!isset($this->workspaceIndex[$pageRecord[
't3ver_wsid']])) {
389 $this->recStats[
'versions_lost_workspace'][
'pages'][$rootID] = $rootID;
392 if ($rootIsVersion == 2) {
393 $this->recStats[
'versions_inside_versioned_page'][
'pages'][$rootID] = $rootID;
396 if ($echoLevel > 0) {
397 echo LF . $accumulatedPath .
' [' . $rootID .
']' . ($pageRecord[
'deleted'] ?
' (DELETED)' :
'') . ($this->recStats[
'versions_published'][
'pages'][$rootID] ?
' (PUBLISHED)' :
'');
399 if ($echoLevel > 1 && $this->recStats[
'versions_lost_workspace'][
'pages'][$rootID]) {
400 echo LF .
' ERROR! This version belongs to non-existing workspace (' . $pageRecord[
't3ver_wsid'] .
')!';
402 if ($echoLevel > 1 && $this->recStats[
'versions_inside_versioned_page'][
'pages'][$rootID]) {
403 echo LF .
' WARNING! This version is inside an already versioned page or branch!';
407 $this->{$callBack}(
'pages', $rootID, $echoLevel, $versionSwapmode, $rootIsVersion);
411 foreach (
$GLOBALS[
'TCA'] as $tableName => $cfg) {
412 if ($tableName !=
'pages') {
415 $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)));
419 $count =
$GLOBALS[
'TYPO3_DB']->sql_num_rows($resSub);
421 if ($echoLevel == 2) {
422 echo LF .
' \\-' . $tableName .
' (' . $count .
')';
425 while ($rowSub =
$GLOBALS[
'TYPO3_DB']->sql_fetch_assoc($resSub)) {
426 if ($echoLevel == 3) {
427 echo LF .
' \\-' . $tableName .
':' . $rowSub[
'uid'];
430 if ($versionSwapmode ==
'SWAPMODE:-1' || $versionSwapmode ==
'SWAPMODE:0' && !
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'versioning_followPages']) {
432 $this->recStats[
'illegal_record_under_versioned_page'][$tableName][$rowSub[
'uid']] = $rowSub[
'uid'];
433 if ($echoLevel > 1) {
434 echo LF .
' ERROR! Illegal record (' . $tableName .
':' . $rowSub[
'uid'] .
') under versioned page!';
437 $this->recStats[
'all'][$tableName][$rowSub[
'uid']] = $rowSub[
'uid'];
439 if (
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete'] && $rowSub[
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete']]) {
440 $this->recStats[
'deleted'][$tableName][$rowSub[
'uid']] = $rowSub[
'uid'];
441 if ($echoLevel == 3) {
446 if (!
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'rootLevel'] && $rootID == 0) {
447 $this->recStats[
'misplaced_at_rootlevel'][$tableName][$rowSub[
'uid']] = $rowSub[
'uid'];
448 if ($echoLevel > 1) {
449 echo LF .
' ERROR! Misplaced record (' . $tableName .
':' . $rowSub[
'uid'] .
') on rootlevel!';
452 if (
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'rootLevel'] == 1 && $rootID > 0) {
453 $this->recStats[
'misplaced_inside_tree'][$tableName][$rowSub[
'uid']] = $rowSub[
'uid'];
454 if ($echoLevel > 1) {
455 echo LF .
' ERROR! Misplaced record (' . $tableName .
':' . $rowSub[
'uid'] .
') inside page tree!';
460 $this->{$callBack}($tableName, $rowSub[
'uid'], $echoLevel, $versionSwapmode, $rootIsVersion);
463 if ($this->genTree_traverseVersions) {
465 if (is_array($versions)) {
466 foreach ($versions as $verRec) {
467 if (!$verRec[
'_CURRENT_VERSION']) {
468 if ($echoLevel == 3) {
469 echo LF .
' \\-[#OFFLINE VERSION: WS#' . $verRec[
't3ver_wsid'] .
'/Cnt:' . $verRec[
't3ver_count'] .
'] ' . $tableName .
':' . $verRec[
'uid'] .
')';
471 $this->recStats[
'all'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
473 if (
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete'] && $verRec[
$GLOBALS[
'TCA'][$tableName][
'ctrl'][
'delete']]) {
474 $this->recStats[
'deleted'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
475 if ($echoLevel == 3) {
480 $this->recStats[
'versions'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
481 if ($verRec[
't3ver_count'] >= 1 && $verRec[
't3ver_wsid'] == 0) {
483 $this->recStats[
'versions_published'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
484 if ($echoLevel == 3) {
488 if ($verRec[
't3ver_wsid'] == 0) {
489 $this->recStats[
'versions_liveWS'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
491 if (!isset($this->workspaceIndex[$verRec[
't3ver_wsid']])) {
492 $this->recStats[
'versions_lost_workspace'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
493 if ($echoLevel > 1) {
494 echo LF .
' ERROR! Version (' . $tableName .
':' . $verRec[
'uid'] .
') belongs to non-existing workspace (' . $verRec[
't3ver_wsid'] .
')!';
498 if ($versionSwapmode) {
499 $this->recStats[
'versions_inside_versioned_page'][$tableName][$verRec[
'uid']] = $verRec[
'uid'];
500 if ($echoLevel > 1) {
501 echo LF .
' ERROR! This version (' . $tableName .
':' . $verRec[
'uid'] .
') is inside an already versioned page or branch!';
506 $this->{$callBack}($tableName, $verRec[
'uid'], $echoLevel, $versionSwapmode, $rootIsVersion);
523 if (!$versionSwapmode || $versionSwapmode ==
'SWAPMODE:1') {
526 $res =
$GLOBALS[
'TYPO3_DB']->exec_SELECTquery(
'uid',
'pages',
'pid=' . (
int)$rootID . ($this->genTree_traverseDeleted ?
'' :
BackendUtility::deleteClause(
'pages')),
'',
'sorting');
527 while ($row =
$GLOBALS[
'TYPO3_DB']->sql_fetch_assoc($res)) {
528 $this->genTree_traverse($row[
'uid'], $depth, $echoLevel, $callBack, $versionSwapmode, 0, $accumulatedPath);
532 if ($rootID > 0 && $this->genTree_traverseVersions) {
534 if (is_array($versions)) {
535 foreach ($versions as $verRec) {
536 if (!$verRec[
'_CURRENT_VERSION']) {
537 $this->genTree_traverse($verRec[
'uid'], $depth, $echoLevel, $callBack,
'SWAPMODE:-1', $versionSwapmode ? 2 : 1, $accumulatedPath .
' [#OFFLINE VERSION: WS#' . $verRec[
't3ver_wsid'] .
'/Cnt:' . $verRec[
't3ver_count'] .
']');
556 public function infoStr($rec)
558 return $rec[
'tablename'] .
':' . $rec[
'recuid'] .
':' . $rec[
'field'] .
':' . $rec[
'flexpointer'] .
':' . $rec[
'softref_key'] . ($rec[
'deleted'] ?
' (DELETED)' :
'');