MediaWiki  master
Maintenance.php
Go to the documentation of this file.
1 <?php
23 define( 'MW_ENTRY_POINT', 'cli' );
24 
25 // Bail on old versions of PHP, or if composer has not been run yet to install
26 // dependencies.
27 require_once __DIR__ . '/../includes/PHPVersionCheck.php';
28 wfEntryPointCheck( 'text' );
29 
32 
38 // Define this so scripts can easily find doMaintenance.php
39 define( 'RUN_MAINTENANCE_IF_MAIN', __DIR__ . '/doMaintenance.php' );
40 
44 define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless
45 
50 $maintClass = false;
51 
52 // Some extensions rely on MW_INSTALL_PATH to find core files to include. Setting it here helps them
53 // if they're included by a core script (like DatabaseUpdater) after Maintenance.php has already
54 // been run.
55 if ( strval( getenv( 'MW_INSTALL_PATH' ) ) === '' ) {
56  putenv( 'MW_INSTALL_PATH=' . realpath( __DIR__ . '/..' ) );
57 }
58 
64 
86 abstract class Maintenance {
91  const DB_NONE = 0;
92  const DB_STD = 1;
93  const DB_ADMIN = 2;
94 
95  // Const for getStdin()
96  const STDIN_ALL = 'all';
97 
103  protected $mParams = [];
104 
105  // Array of mapping short parameters to long ones
106  protected $mShortParamsMap = [];
107 
108  // Array of desired/allowed args
109  protected $mArgList = [];
110 
111  // This is the list of options that were actually passed
112  protected $mOptions = [];
113 
114  // This is the list of arguments that were actually passed
115  protected $mArgs = [];
116 
117  // Allow arbitrary options to be passed, or only specified ones?
118  protected $mAllowUnregisteredOptions = false;
119 
120  // Name of the script currently running
121  protected $mSelf;
122 
123  // Special vars for params that are always used
124  protected $mQuiet = false;
125  protected $mDbUser, $mDbPass;
126 
127  // A description of the script, children should change this via addDescription()
128  protected $mDescription = '';
129 
130  // Have we already loaded our user input?
131  protected $mInputLoaded = false;
132 
139  protected $mBatchSize = null;
140 
146  private $mGenericParameters = [];
152  private $mDependantParameters = [];
153 
158  private $mDb = null;
159 
161  private $lastReplicationWait = 0.0;
162 
167  public $fileHandle;
168 
174  private $config;
175 
180  private $requiredExtensions = [];
181 
193  public $orderedOptions = [];
194 
199  public function __construct() {
200  global $IP;
201  $IP = getenv( 'MW_INSTALL_PATH' );
202 
203  $this->addDefaultParams();
204  register_shutdown_function( [ $this, 'outputChanneled' ], false );
205  }
206 
214  public static function shouldExecute() {
215  global $wgCommandLineMode;
216 
217  if ( !function_exists( 'debug_backtrace' ) ) {
218  // If someone has a better idea...
219  return $wgCommandLineMode;
220  }
221 
222  $bt = debug_backtrace();
223  $count = count( $bt );
224  if ( $count < 2 ) {
225  return false; // sanity
226  }
227  if ( $bt[0]['class'] !== self::class || $bt[0]['function'] !== 'shouldExecute' ) {
228  return false; // last call should be to this function
229  }
230  $includeFuncs = [ 'require_once', 'require', 'include', 'include_once' ];
231  for ( $i = 1; $i < $count; $i++ ) {
232  if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) {
233  return false; // previous calls should all be "requires"
234  }
235  }
236 
237  return true;
238  }
239 
248  abstract public function execute();
249 
256  protected function supportsOption( $name ) {
257  return isset( $this->mParams[$name] );
258  }
259 
271  protected function addOption( $name, $description, $required = false,
272  $withArg = false, $shortName = false, $multiOccurrence = false
273  ) {
274  $this->mParams[$name] = [
275  'desc' => $description,
276  'require' => $required,
277  'withArg' => $withArg,
278  'shortName' => $shortName,
279  'multiOccurrence' => $multiOccurrence
280  ];
281 
282  if ( $shortName !== false ) {
283  $this->mShortParamsMap[$shortName] = $name;
284  }
285  }
286 
292  protected function hasOption( $name ) {
293  return isset( $this->mOptions[$name] );
294  }
295 
306  protected function getOption( $name, $default = null ) {
307  if ( $this->hasOption( $name ) ) {
308  return $this->mOptions[$name];
309  } else {
310  // Set it so we don't have to provide the default again
311  $this->mOptions[$name] = $default;
312 
313  return $this->mOptions[$name];
314  }
315  }
316 
323  protected function addArg( $arg, $description, $required = true ) {
324  $this->mArgList[] = [
325  'name' => $arg,
326  'desc' => $description,
327  'require' => $required
328  ];
329  }
330 
335  protected function deleteOption( $name ) {
336  unset( $this->mParams[$name] );
337  }
338 
344  protected function setAllowUnregisteredOptions( $allow ) {
345  $this->mAllowUnregisteredOptions = $allow;
346  }
347 
352  protected function addDescription( $text ) {
353  $this->mDescription = $text;
354  }
355 
361  protected function hasArg( $argId = 0 ) {
362  if ( func_num_args() === 0 ) {
363  wfDeprecated( __METHOD__ . ' without an $argId', '1.33' );
364  }
365 
366  return isset( $this->mArgs[$argId] );
367  }
368 
375  protected function getArg( $argId = 0, $default = null ) {
376  if ( func_num_args() === 0 ) {
377  wfDeprecated( __METHOD__ . ' without an $argId', '1.33' );
378  }
379 
380  return $this->mArgs[$argId] ?? $default;
381  }
382 
390  protected function getBatchSize() {
391  return $this->mBatchSize;
392  }
393 
398  protected function setBatchSize( $s = 0 ) {
399  $this->mBatchSize = $s;
400 
401  // If we support $mBatchSize, show the option.
402  // Used to be in addDefaultParams, but in order for that to
403  // work, subclasses would have to call this function in the constructor
404  // before they called parent::__construct which is just weird
405  // (and really wasn't done).
406  if ( $this->mBatchSize ) {
407  $this->addOption( 'batch-size', 'Run this many operations ' .
408  'per batch, default: ' . $this->mBatchSize, false, true );
409  if ( isset( $this->mParams['batch-size'] ) ) {
410  // This seems a little ugly...
411  $this->mDependantParameters['batch-size'] = $this->mParams['batch-size'];
412  }
413  }
414  }
415 
420  public function getName() {
421  return $this->mSelf;
422  }
423 
430  protected function getStdin( $len = null ) {
431  if ( $len == self::STDIN_ALL ) {
432  return file_get_contents( 'php://stdin' );
433  }
434  $f = fopen( 'php://stdin', 'rt' );
435  if ( !$len ) {
436  return $f;
437  }
438  $input = fgets( $f, $len );
439  fclose( $f );
440 
441  return rtrim( $input );
442  }
443 
447  public function isQuiet() {
448  return $this->mQuiet;
449  }
450 
457  protected function output( $out, $channel = null ) {
458  // This is sometimes called very early, before Setup.php is included.
459  if ( class_exists( MediaWikiServices::class ) ) {
460  // Try to periodically flush buffered metrics to avoid OOMs
461  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
462  if ( $stats->getDataCount() > 1000 ) {
463  MediaWiki::emitBufferedStatsdData( $stats, $this->getConfig() );
464  }
465  }
466 
467  if ( $this->mQuiet ) {
468  return;
469  }
470  if ( $channel === null ) {
471  $this->cleanupChanneled();
472  print $out;
473  } else {
474  $out = preg_replace( '/\n\z/', '', $out );
475  $this->outputChanneled( $out, $channel );
476  }
477  }
478 
485  protected function error( $err, $die = 0 ) {
486  if ( intval( $die ) !== 0 ) {
487  wfDeprecated( __METHOD__ . '( $err, $die )', '1.31' );
488  $this->fatalError( $err, intval( $die ) );
489  }
490  $this->outputChanneled( false );
491  if (
492  ( PHP_SAPI == 'cli' || PHP_SAPI == 'phpdbg' ) &&
493  !defined( 'MW_PHPUNIT_TEST' )
494  ) {
495  fwrite( STDERR, $err . "\n" );
496  } else {
497  print $err;
498  }
499  }
500 
508  protected function fatalError( $msg, $exitCode = 1 ) {
509  $this->error( $msg );
510  exit( $exitCode );
511  }
512 
513  private $atLineStart = true;
514  private $lastChannel = null;
515 
519  public function cleanupChanneled() {
520  if ( !$this->atLineStart ) {
521  print "\n";
522  $this->atLineStart = true;
523  }
524  }
525 
534  public function outputChanneled( $msg, $channel = null ) {
535  if ( $msg === false ) {
536  $this->cleanupChanneled();
537 
538  return;
539  }
540 
541  // End the current line if necessary
542  if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
543  print "\n";
544  }
545 
546  print $msg;
547 
548  $this->atLineStart = false;
549  if ( $channel === null ) {
550  // For unchanneled messages, output trailing newline immediately
551  print "\n";
552  $this->atLineStart = true;
553  }
554  $this->lastChannel = $channel;
555  }
556 
567  public function getDbType() {
568  return self::DB_STD;
569  }
570 
574  protected function addDefaultParams() {
575  # Generic (non script dependant) options:
576 
577  $this->addOption( 'help', 'Display this help message', false, false, 'h' );
578  $this->addOption( 'quiet', 'Whether to suppress non-error output', false, false, 'q' );
579  $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true );
580  $this->addOption( 'wiki', 'For specifying the wiki ID', false, true );
581  $this->addOption( 'globals', 'Output globals at the end of processing for debugging' );
582  $this->addOption(
583  'memory-limit',
584  'Set a specific memory limit for the script, '
585  . '"max" for no limit or "default" to avoid changing it',
586  false,
587  true
588  );
589  $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " .
590  "http://en.wikipedia.org. This is sometimes necessary because " .
591  "server name detection may fail in command line scripts.", false, true );
592  $this->addOption( 'profiler', 'Profiler output format (usually "text")', false, true );
593  // This is named --mwdebug, because --debug would conflict in the phpunit.php CLI script.
594  $this->addOption( 'mwdebug', 'Enable built-in MediaWiki development settings', false, false );
595 
596  # Save generic options to display them separately in help
597  $this->mGenericParameters = $this->mParams;
598 
599  # Script dependant options:
600 
601  // If we support a DB, show the options
602  if ( $this->getDbType() > 0 ) {
603  $this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
604  $this->addOption( 'dbpass', 'The password to use for this script', false, true );
605  $this->addOption( 'dbgroupdefault', 'The default DB group to use.', false, true );
606  }
607 
608  # Save additional script dependant options to display
609  # ┬áthem separately in help
610  $this->mDependantParameters = array_diff_key( $this->mParams, $this->mGenericParameters );
611  }
612 
617  public function getConfig() {
618  if ( $this->config === null ) {
619  $this->config = MediaWikiServices::getInstance()->getMainConfig();
620  }
621 
622  return $this->config;
623  }
624 
629  public function setConfig( Config $config ) {
630  $this->config = $config;
631  }
632 
642  protected function requireExtension( $name ) {
643  $this->requiredExtensions[] = $name;
644  }
645 
651  public function checkRequiredExtensions() {
652  $registry = ExtensionRegistry::getInstance();
653  $missing = [];
654  foreach ( $this->requiredExtensions as $name ) {
655  if ( !$registry->isLoaded( $name ) ) {
656  $missing[] = $name;
657  }
658  }
659 
660  if ( $missing ) {
661  $joined = implode( ', ', $missing );
662  $msg = "The following extensions are required to be installed "
663  . "for this script to run: $joined. Please enable them and then try again.";
664  $this->fatalError( $msg );
665  }
666  }
667 
672  public function setAgentAndTriggers() {
673  if ( function_exists( 'posix_getpwuid' ) ) {
674  $agent = posix_getpwuid( posix_geteuid() )['name'];
675  } else {
676  $agent = 'sysadmin';
677  }
678  $agent .= '@' . wfHostname();
679 
680  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
681  // Add a comment for easy SHOW PROCESSLIST interpretation
682  $lbFactory->setAgentName(
683  mb_strlen( $agent ) > 15 ? mb_substr( $agent, 0, 15 ) . '...' : $agent
684  );
685  self::setLBFactoryTriggers( $lbFactory, $this->getConfig() );
686  }
687 
693  public static function setLBFactoryTriggers( LBFactory $LBFactory, Config $config ) {
694  $services = MediaWikiServices::getInstance();
695  $stats = $services->getStatsdDataFactory();
696  // Hook into period lag checks which often happen in long-running scripts
697  $lbFactory = $services->getDBLoadBalancerFactory();
698  $lbFactory->setWaitForReplicationListener(
699  __METHOD__,
700  function () use ( $stats, $config ) {
701  // Check config in case of JobRunner and unit tests
702  if ( $config->get( 'CommandLineMode' ) ) {
704  }
705  // Try to periodically flush buffered metrics to avoid OOMs
706  MediaWiki::emitBufferedStatsdData( $stats, $config );
707  }
708  );
709  // Check for other windows to run them. A script may read or do a few writes
710  // to the master but mostly be writing to something else, like a file store.
711  $lbFactory->getMainLB()->setTransactionListener(
712  __METHOD__,
713  function ( $trigger ) use ( $stats, $config ) {
714  // Check config in case of JobRunner and unit tests
715  if ( $config->get( 'CommandLineMode' ) && $trigger === IDatabase::TRIGGER_COMMIT ) {
717  }
718  // Try to periodically flush buffered metrics to avoid OOMs
719  MediaWiki::emitBufferedStatsdData( $stats, $config );
720  }
721  );
722  }
723 
731  public function runChild( $maintClass, $classFile = null ) {
732  // Make sure the class is loaded first
733  if ( !class_exists( $maintClass ) ) {
734  if ( $classFile ) {
735  require_once $classFile;
736  }
737  if ( !class_exists( $maintClass ) ) {
738  $this->error( "Cannot spawn child: $maintClass" );
739  }
740  }
741 
745  $child = new $maintClass();
746  $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs );
747  if ( !is_null( $this->mDb ) ) {
748  $child->setDB( $this->mDb );
749  }
750 
751  return $child;
752  }
753 
757  public function setup() {
758  global $IP, $wgCommandLineMode;
759 
760  # Abort if called from a web server
761  # wfIsCLI() is not available yet
762  if ( PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ) {
763  $this->fatalError( 'This script must be run from the command line' );
764  }
765 
766  if ( $IP === null ) {
767  $this->fatalError( "\$IP not set, aborting!\n" .
768  '(Did you forget to call parent::__construct() in your maintenance script?)' );
769  }
770 
771  # Make sure we can handle script parameters
772  if ( !defined( 'HPHP_VERSION' ) && !ini_get( 'register_argc_argv' ) ) {
773  $this->fatalError( 'Cannot get command line arguments, register_argc_argv is set to false' );
774  }
775 
776  // Send PHP warnings and errors to stderr instead of stdout.
777  // This aids in diagnosing problems, while keeping messages
778  // out of redirected output.
779  if ( ini_get( 'display_errors' ) ) {
780  ini_set( 'display_errors', 'stderr' );
781  }
782 
783  $this->loadParamsAndArgs();
784 
785  # Set the memory limit
786  # Note we need to set it again later in cache LocalSettings changed it
787  $this->adjustMemoryLimit();
788 
789  # Set max execution time to 0 (no limit). PHP.net says that
790  # "When running PHP from the command line the default setting is 0."
791  # But sometimes this doesn't seem to be the case.
792  ini_set( 'max_execution_time', 0 );
793 
794  # Define us as being in MediaWiki
795  define( 'MEDIAWIKI', true );
796 
797  $wgCommandLineMode = true;
798 
799  # Turn off output buffering if it's on
800  while ( ob_get_level() > 0 ) {
801  ob_end_flush();
802  }
803  }
804 
814  public function memoryLimit() {
815  $limit = $this->getOption( 'memory-limit', 'max' );
816  $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood
817  return $limit;
818  }
819 
823  protected function adjustMemoryLimit() {
824  $limit = $this->memoryLimit();
825  if ( $limit == 'max' ) {
826  $limit = -1; // no memory limit
827  }
828  if ( $limit != 'default' ) {
829  ini_set( 'memory_limit', $limit );
830  }
831  }
832 
836  protected function activateProfiler() {
838 
839  $output = $this->getOption( 'profiler' );
840  if ( !$output ) {
841  return;
842  }
843 
844  if ( is_array( $wgProfiler ) && isset( $wgProfiler['class'] ) ) {
845  $class = $wgProfiler['class'];
847  $profiler = new $class(
848  [ 'sampling' => 1, 'output' => [ $output ] ]
849  + $wgProfiler
850  + [ 'threshold' => $wgProfileLimit ]
851  );
852  $profiler->setAllowOutput();
853  Profiler::replaceStubInstance( $profiler );
854  }
855 
856  $trxProfiler = Profiler::instance()->getTransactionProfiler();
857  $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
858  $trxProfiler->setExpectations( $wgTrxProfilerLimits['Maintenance'], __METHOD__ );
859  }
860 
864  public function clearParamsAndArgs() {
865  $this->mOptions = [];
866  $this->mArgs = [];
867  $this->mInputLoaded = false;
868  }
869 
877  public function loadWithArgv( $argv ) {
878  $options = [];
879  $args = [];
880  $this->orderedOptions = [];
881 
882  # Parse arguments
883  for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
884  if ( $arg == '--' ) {
885  # End of options, remainder should be considered arguments
886  $arg = next( $argv );
887  while ( $arg !== false ) {
888  $args[] = $arg;
889  $arg = next( $argv );
890  }
891  break;
892  } elseif ( substr( $arg, 0, 2 ) == '--' ) {
893  # Long options
894  $option = substr( $arg, 2 );
895  if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) {
896  $param = next( $argv );
897  if ( $param === false ) {
898  $this->error( "\nERROR: $option parameter needs a value after it\n" );
899  $this->maybeHelp( true );
900  }
901 
902  $this->setParam( $options, $option, $param );
903  } else {
904  $bits = explode( '=', $option, 2 );
905  $this->setParam( $options, $bits[0], $bits[1] ?? 1 );
906  }
907  } elseif ( $arg == '-' ) {
908  # Lonely "-", often used to indicate stdin or stdout.
909  $args[] = $arg;
910  } elseif ( substr( $arg, 0, 1 ) == '-' ) {
911  # Short options
912  $argLength = strlen( $arg );
913  for ( $p = 1; $p < $argLength; $p++ ) {
914  $option = $arg[$p];
915  if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) {
916  $option = $this->mShortParamsMap[$option];
917  }
918 
919  if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) {
920  $param = next( $argv );
921  if ( $param === false ) {
922  $this->error( "\nERROR: $option parameter needs a value after it\n" );
923  $this->maybeHelp( true );
924  }
925  $this->setParam( $options, $option, $param );
926  } else {
927  $this->setParam( $options, $option, 1 );
928  }
929  }
930  } else {
931  $args[] = $arg;
932  }
933  }
934 
935  $this->mOptions = $options;
936  $this->mArgs = $args;
937  $this->loadSpecialVars();
938  $this->mInputLoaded = true;
939  }
940 
953  private function setParam( &$options, $option, $value ) {
954  $this->orderedOptions[] = [ $option, $value ];
955 
956  if ( isset( $this->mParams[$option] ) ) {
957  $multi = $this->mParams[$option]['multiOccurrence'];
958  } else {
959  $multi = false;
960  }
961  $exists = array_key_exists( $option, $options );
962  if ( $multi && $exists ) {
963  $options[$option][] = $value;
964  } elseif ( $multi ) {
965  $options[$option] = [ $value ];
966  } elseif ( !$exists ) {
967  $options[$option] = $value;
968  } else {
969  $this->error( "\nERROR: $option parameter given twice\n" );
970  $this->maybeHelp( true );
971  }
972  }
973 
983  public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
984  # If we were given opts or args, set those and return early
985  if ( $self ) {
986  $this->mSelf = $self;
987  $this->mInputLoaded = true;
988  }
989  if ( $opts ) {
990  $this->mOptions = $opts;
991  $this->mInputLoaded = true;
992  }
993  if ( $args ) {
994  $this->mArgs = $args;
995  $this->mInputLoaded = true;
996  }
997 
998  # If we've already loaded input (either by user values or from $argv)
999  # skip on loading it again. The array_shift() will corrupt values if
1000  # it's run again and again
1001  if ( $this->mInputLoaded ) {
1002  $this->loadSpecialVars();
1003 
1004  return;
1005  }
1006 
1007  global $argv;
1008  $this->mSelf = $argv[0];
1009  $this->loadWithArgv( array_slice( $argv, 1 ) );
1010  }
1011 
1015  public function validateParamsAndArgs() {
1016  $die = false;
1017  # Check to make sure we've got all the required options
1018  foreach ( $this->mParams as $opt => $info ) {
1019  if ( $info['require'] && !$this->hasOption( $opt ) ) {
1020  $this->error( "Param $opt required!" );
1021  $die = true;
1022  }
1023  }
1024  # Check arg list too
1025  foreach ( $this->mArgList as $k => $info ) {
1026  if ( $info['require'] && !$this->hasArg( $k ) ) {
1027  $this->error( 'Argument <' . $info['name'] . '> required!' );
1028  $die = true;
1029  }
1030  }
1031  if ( !$this->mAllowUnregisteredOptions ) {
1032  # Check for unexpected options
1033  foreach ( $this->mOptions as $opt => $val ) {
1034  if ( !$this->supportsOption( $opt ) ) {
1035  $this->error( "Unexpected option $opt!" );
1036  $die = true;
1037  }
1038  }
1039  }
1040 
1041  $this->maybeHelp( $die );
1042  }
1043 
1047  protected function loadSpecialVars() {
1048  if ( $this->hasOption( 'dbuser' ) ) {
1049  $this->mDbUser = $this->getOption( 'dbuser' );
1050  }
1051  if ( $this->hasOption( 'dbpass' ) ) {
1052  $this->mDbPass = $this->getOption( 'dbpass' );
1053  }
1054  if ( $this->hasOption( 'quiet' ) ) {
1055  $this->mQuiet = true;
1056  }
1057  if ( $this->hasOption( 'batch-size' ) ) {
1058  $this->mBatchSize = intval( $this->getOption( 'batch-size' ) );
1059  }
1060  }
1061 
1066  protected function maybeHelp( $force = false ) {
1067  if ( !$force && !$this->hasOption( 'help' ) ) {
1068  return;
1069  }
1070 
1071  $screenWidth = 80; // TODO: Calculate this!
1072  $tab = " ";
1073  $descWidth = $screenWidth - ( 2 * strlen( $tab ) );
1074 
1075  ksort( $this->mParams );
1076  $this->mQuiet = false;
1077 
1078  // Description ...
1079  if ( $this->mDescription ) {
1080  $this->output( "\n" . wordwrap( $this->mDescription, $screenWidth ) . "\n" );
1081  }
1082  $output = "\nUsage: php " . basename( $this->mSelf );
1083 
1084  // ... append parameters ...
1085  if ( $this->mParams ) {
1086  $output .= " [--" . implode( "|--", array_keys( $this->mParams ) ) . "]";
1087  }
1088 
1089  // ... and append arguments.
1090  if ( $this->mArgList ) {
1091  $output .= ' ';
1092  foreach ( $this->mArgList as $k => $arg ) {
1093  if ( $arg['require'] ) {
1094  $output .= '<' . $arg['name'] . '>';
1095  } else {
1096  $output .= '[' . $arg['name'] . ']';
1097  }
1098  if ( $k < count( $this->mArgList ) - 1 ) {
1099  $output .= ' ';
1100  }
1101  }
1102  }
1103  $this->output( "$output\n\n" );
1104 
1105  # TODO abstract some repetitive code below
1106 
1107  // Generic parameters
1108  $this->output( "Generic maintenance parameters:\n" );
1109  foreach ( $this->mGenericParameters as $par => $info ) {
1110  if ( $info['shortName'] !== false ) {
1111  $par .= " (-{$info['shortName']})";
1112  }
1113  $this->output(
1114  wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
1115  "\n$tab$tab" ) . "\n"
1116  );
1117  }
1118  $this->output( "\n" );
1119 
1120  $scriptDependantParams = $this->mDependantParameters;
1121  if ( count( $scriptDependantParams ) > 0 ) {
1122  $this->output( "Script dependant parameters:\n" );
1123  // Parameters description
1124  foreach ( $scriptDependantParams as $par => $info ) {
1125  if ( $info['shortName'] !== false ) {
1126  $par .= " (-{$info['shortName']})";
1127  }
1128  $this->output(
1129  wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
1130  "\n$tab$tab" ) . "\n"
1131  );
1132  }
1133  $this->output( "\n" );
1134  }
1135 
1136  // Script specific parameters not defined on construction by
1137  // Maintenance::addDefaultParams()
1138  $scriptSpecificParams = array_diff_key(
1139  # all script parameters:
1140  $this->mParams,
1141  # remove the Maintenance default parameters:
1142  $this->mGenericParameters,
1143  $this->mDependantParameters
1144  );
1145  '@phan-var array[] $scriptSpecificParams';
1146  if ( count( $scriptSpecificParams ) > 0 ) {
1147  $this->output( "Script specific parameters:\n" );
1148  // Parameters description
1149  foreach ( $scriptSpecificParams as $par => $info ) {
1150  if ( $info['shortName'] !== false ) {
1151  $par .= " (-{$info['shortName']})";
1152  }
1153  $this->output(
1154  wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
1155  "\n$tab$tab" ) . "\n"
1156  );
1157  }
1158  $this->output( "\n" );
1159  }
1160 
1161  // Print arguments
1162  if ( count( $this->mArgList ) > 0 ) {
1163  $this->output( "Arguments:\n" );
1164  // Arguments description
1165  foreach ( $this->mArgList as $info ) {
1166  $openChar = $info['require'] ? '<' : '[';
1167  $closeChar = $info['require'] ? '>' : ']';
1168  $this->output(
1169  wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " .
1170  $info['desc'], $descWidth, "\n$tab$tab" ) . "\n"
1171  );
1172  }
1173  $this->output( "\n" );
1174  }
1175 
1176  die( 1 );
1177  }
1178 
1182  public function finalSetup() {
1186 
1187  # Turn off output buffering again, it might have been turned on in the settings files
1188  if ( ob_get_level() ) {
1189  ob_end_flush();
1190  }
1191  # Same with these
1192  $wgCommandLineMode = true;
1193 
1194  # Override $wgServer
1195  if ( $this->hasOption( 'server' ) ) {
1196  $wgServer = $this->getOption( 'server', $wgServer );
1197  }
1198 
1199  # If these were passed, use them
1200  if ( $this->mDbUser ) {
1201  $wgDBadminuser = $this->mDbUser;
1202  }
1203  if ( $this->mDbPass ) {
1204  $wgDBadminpassword = $this->mDbPass;
1205  }
1206  if ( $this->hasOption( 'dbgroupdefault' ) ) {
1207  $wgDBDefaultGroup = $this->getOption( 'dbgroupdefault', null );
1208 
1209  MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->destroy();
1210  }
1211 
1212  if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) {
1213  $wgDBuser = $wgDBadminuser;
1214  $wgDBpassword = $wgDBadminpassword;
1215 
1216  if ( $wgDBservers ) {
1220  foreach ( $wgDBservers as $i => $server ) {
1221  $wgDBservers[$i]['user'] = $wgDBuser;
1222  $wgDBservers[$i]['password'] = $wgDBpassword;
1223  }
1224  }
1225  if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
1226  $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
1227  $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
1228  }
1229  MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->destroy();
1230  }
1231 
1232  # Apply debug settings
1233  if ( $this->hasOption( 'mwdebug' ) ) {
1234  require __DIR__ . '/../includes/DevelopmentSettings.php';
1235  }
1236 
1237  // Per-script profiling; useful for debugging
1238  $this->activateProfiler();
1239 
1240  $this->afterFinalSetup();
1241 
1242  $wgShowExceptionDetails = true;
1243  $wgShowHostnames = true;
1244 
1245  Wikimedia\suppressWarnings();
1246  set_time_limit( 0 );
1247  Wikimedia\restoreWarnings();
1248 
1249  $this->adjustMemoryLimit();
1250  }
1251 
1255  protected function afterFinalSetup() {
1256  if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
1257  // @phan-suppress-next-line PhanUndeclaredConstant
1258  call_user_func( MW_CMDLINE_CALLBACK );
1259  }
1260  }
1261 
1266  public function globals() {
1267  if ( $this->hasOption( 'globals' ) ) {
1268  print_r( $GLOBALS );
1269  }
1270  }
1271 
1276  public function loadSettings() {
1277  global $wgCommandLineMode, $IP;
1278 
1279  if ( isset( $this->mOptions['conf'] ) ) {
1280  $settingsFile = $this->mOptions['conf'];
1281  } elseif ( defined( "MW_CONFIG_FILE" ) ) {
1282  $settingsFile = MW_CONFIG_FILE;
1283  } else {
1284  $settingsFile = "$IP/LocalSettings.php";
1285  }
1286  if ( isset( $this->mOptions['wiki'] ) ) {
1287  $bits = explode( '-', $this->mOptions['wiki'], 2 );
1288  define( 'MW_DB', $bits[0] );
1289  define( 'MW_PREFIX', $bits[1] ?? '' );
1290  } elseif ( isset( $this->mOptions['server'] ) ) {
1291  // Provide the option for site admins to detect and configure
1292  // multiple wikis based on server names. This offers --server
1293  // as alternative to --wiki.
1294  // See https://www.mediawiki.org/wiki/Manual:Wiki_family
1295  $_SERVER['SERVER_NAME'] = $this->mOptions['server'];
1296  }
1297 
1298  if ( !is_readable( $settingsFile ) ) {
1299  $this->fatalError( "A copy of your installation's LocalSettings.php\n" .
1300  "must exist and be readable in the source directory.\n" .
1301  "Use --conf to specify it." );
1302  }
1303  $wgCommandLineMode = true;
1304 
1305  return $settingsFile;
1306  }
1307 
1313  public function purgeRedundantText( $delete = true ) {
1315 
1316  # Data should come off the master, wrapped in a transaction
1317  $dbw = $this->getDB( DB_MASTER );
1318  $this->beginTransaction( $dbw, __METHOD__ );
1319 
1320  if ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_READ_OLD ) {
1321  # Get "active" text records from the revisions table
1322  $cur = [];
1323  $this->output( 'Searching for active text records in revisions table...' );
1324  $res = $dbw->select( 'revision', 'rev_text_id', [], __METHOD__, [ 'DISTINCT' ] );
1325  foreach ( $res as $row ) {
1326  $cur[] = $row->rev_text_id;
1327  }
1328  $this->output( "done.\n" );
1329 
1330  # Get "active" text records from the archive table
1331  $this->output( 'Searching for active text records in archive table...' );
1332  $res = $dbw->select( 'archive', 'ar_text_id', [], __METHOD__, [ 'DISTINCT' ] );
1333  foreach ( $res as $row ) {
1334  # old pre-MW 1.5 records can have null ar_text_id's.
1335  if ( $row->ar_text_id !== null ) {
1336  $cur[] = $row->ar_text_id;
1337  }
1338  }
1339  $this->output( "done.\n" );
1340  } else {
1341  # Get "active" text records via the content table
1342  $cur = [];
1343  $this->output( 'Searching for active text records via contents table...' );
1344  $res = $dbw->select( 'content', 'content_address', [], __METHOD__, [ 'DISTINCT' ] );
1345  $blobStore = MediaWikiServices::getInstance()->getBlobStore();
1346  foreach ( $res as $row ) {
1347  // @phan-suppress-next-line PhanUndeclaredMethod
1348  $textId = $blobStore->getTextIdFromAddress( $row->content_address );
1349  if ( $textId ) {
1350  $cur[] = $textId;
1351  }
1352  }
1353  $this->output( "done.\n" );
1354  }
1355  $this->output( "done.\n" );
1356 
1357  # Get the IDs of all text records not in these sets
1358  $this->output( 'Searching for inactive text records...' );
1359  $cond = 'old_id NOT IN ( ' . $dbw->makeList( $cur ) . ' )';
1360  $res = $dbw->select( 'text', 'old_id', [ $cond ], __METHOD__, [ 'DISTINCT' ] );
1361  $old = [];
1362  foreach ( $res as $row ) {
1363  $old[] = $row->old_id;
1364  }
1365  $this->output( "done.\n" );
1366 
1367  # Inform the user of what we're going to do
1368  $count = count( $old );
1369  $this->output( "$count inactive items found.\n" );
1370 
1371  # Delete as appropriate
1372  if ( $delete && $count ) {
1373  $this->output( 'Deleting...' );
1374  $dbw->delete( 'text', [ 'old_id' => $old ], __METHOD__ );
1375  $this->output( "done.\n" );
1376  }
1377 
1378  $this->commitTransaction( $dbw, __METHOD__ );
1379  }
1380 
1385  protected function getDir() {
1386  return __DIR__;
1387  }
1388 
1401  protected function getDB( $db, $groups = [], $dbDomain = false ) {
1402  if ( $this->mDb === null ) {
1403  return MediaWikiServices::getInstance()
1404  ->getDBLoadBalancerFactory()
1405  ->getMainLB( $dbDomain )
1406  ->getMaintenanceConnectionRef( $db, $groups, $dbDomain );
1407  }
1408 
1409  return $this->mDb;
1410  }
1411 
1417  public function setDB( IMaintainableDatabase $db ) {
1418  $this->mDb = $db;
1419  }
1420 
1431  protected function beginTransaction( IDatabase $dbw, $fname ) {
1432  $dbw->begin( $fname );
1433  }
1434 
1446  protected function commitTransaction( IDatabase $dbw, $fname ) {
1447  $dbw->commit( $fname );
1448  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
1449  $waitSucceeded = $lbFactory->waitForReplication(
1450  [ 'timeout' => 30, 'ifWritesSince' => $this->lastReplicationWait ]
1451  );
1452  $this->lastReplicationWait = microtime( true );
1453  return $waitSucceeded;
1454  }
1455 
1466  protected function rollbackTransaction( IDatabase $dbw, $fname ) {
1467  $dbw->rollback( $fname );
1468  }
1469 
1474  private function lockSearchindex( $db ) {
1475  $write = [ 'searchindex' ];
1476  $read = [
1477  'page',
1478  'revision',
1479  'text',
1480  'interwiki',
1481  'l10n_cache',
1482  'user',
1483  'page_restrictions'
1484  ];
1485  $db->lockTables( $read, $write, __CLASS__ . '-searchIndexLock' );
1486  }
1487 
1492  private function unlockSearchindex( $db ) {
1493  $db->unlockTables( __CLASS__ . '-searchIndexLock' );
1494  }
1495 
1501  private function relockSearchindex( $db ) {
1502  $this->unlockSearchindex( $db );
1503  $this->lockSearchindex( $db );
1504  }
1505 
1513  public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) {
1514  $lockTime = time();
1515 
1516  # Lock searchindex
1517  if ( $maxLockTime ) {
1518  $this->output( " --- Waiting for lock ---" );
1519  $this->lockSearchindex( $dbw );
1520  $lockTime = time();
1521  $this->output( "\n" );
1522  }
1523 
1524  # Loop through the results and do a search update
1525  foreach ( $results as $row ) {
1526  # Allow reads to be processed
1527  if ( $maxLockTime && time() > $lockTime + $maxLockTime ) {
1528  $this->output( " --- Relocking ---" );
1529  $this->relockSearchindex( $dbw );
1530  $lockTime = time();
1531  $this->output( "\n" );
1532  }
1533  call_user_func( $callback, $dbw, $row );
1534  }
1535 
1536  # Unlock searchindex
1537  if ( $maxLockTime ) {
1538  $this->output( " --- Unlocking --" );
1539  $this->unlockSearchindex( $dbw );
1540  $this->output( "\n" );
1541  }
1542  }
1543 
1550  public function updateSearchIndexForPage( $dbw, $pageId ) {
1551  // Get current revision
1552  $rev = Revision::loadFromPageId( $dbw, $pageId );
1553  $title = null;
1554  if ( $rev ) {
1555  $titleObj = $rev->getTitle();
1556  $title = $titleObj->getPrefixedDBkey();
1557  $this->output( "$title..." );
1558  # Update searchindex
1559  $u = new SearchUpdate( $pageId, $titleObj, $rev->getContent() );
1560  $u->doUpdate();
1561  $this->output( "\n" );
1562  }
1563 
1564  return $title;
1565  }
1566 
1577  protected function countDown( $seconds ) {
1578  if ( $this->isQuiet() ) {
1579  return;
1580  }
1581  for ( $i = $seconds; $i >= 0; $i-- ) {
1582  if ( $i != $seconds ) {
1583  $this->output( str_repeat( "\x08", strlen( $i + 1 ) ) );
1584  }
1585  $this->output( $i );
1586  if ( $i ) {
1587  sleep( 1 );
1588  }
1589  }
1590  $this->output( "\n" );
1591  }
1592 
1601  public static function posix_isatty( $fd ) {
1602  if ( !function_exists( 'posix_isatty' ) ) {
1603  return !$fd;
1604  } else {
1605  return posix_isatty( $fd );
1606  }
1607  }
1608 
1614  public static function readconsole( $prompt = '> ' ) {
1615  static $isatty = null;
1616  if ( is_null( $isatty ) ) {
1617  $isatty = self::posix_isatty( 0 /*STDIN*/ );
1618  }
1619 
1620  if ( $isatty && function_exists( 'readline' ) ) {
1621  return readline( $prompt );
1622  } else {
1623  if ( $isatty ) {
1624  $st = self::readlineEmulation( $prompt );
1625  } else {
1626  if ( feof( STDIN ) ) {
1627  $st = false;
1628  } else {
1629  $st = fgets( STDIN, 1024 );
1630  }
1631  }
1632  if ( $st === false ) {
1633  return false;
1634  }
1635  $resp = trim( $st );
1636 
1637  return $resp;
1638  }
1639  }
1640 
1646  private static function readlineEmulation( $prompt ) {
1647  $bash = ExecutableFinder::findInDefaultPaths( 'bash' );
1648  if ( !wfIsWindows() && $bash ) {
1649  $retval = false;
1650  $encPrompt = Shell::escape( $prompt );
1651  $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
1652  $encCommand = Shell::escape( $command );
1653  $line = Shell::escape( "$bash -c $encCommand", $retval, [], [ 'walltime' => 0 ] );
1654 
1655  if ( $retval == 0 ) {
1656  return $line;
1657  } elseif ( $retval == 127 ) {
1658  // Couldn't execute bash even though we thought we saw it.
1659  // Shell probably spit out an error message, sorry :(
1660  // Fall through to fgets()...
1661  } else {
1662  // EOF/ctrl+D
1663  return false;
1664  }
1665  }
1666 
1667  // Fallback... we'll have no editing controls, EWWW
1668  if ( feof( STDIN ) ) {
1669  return false;
1670  }
1671  print $prompt;
1672 
1673  return fgets( STDIN, 1024 );
1674  }
1675 
1683  public static function getTermSize() {
1684  $default = [ 80, 50 ];
1685  if ( wfIsWindows() ) {
1686  return $default;
1687  }
1688  if ( Shell::isDisabled() ) {
1689  return $default;
1690  }
1691  // It's possible to get the screen size with VT-100 terminal escapes,
1692  // but reading the responses is not possible without setting raw mode
1693  // (unless you want to require the user to press enter), and that
1694  // requires an ioctl(), which we can't do. So we have to shell out to
1695  // something that can do the relevant syscalls. There are a few
1696  // options. Linux and Mac OS X both have "stty size" which does the
1697  // job directly.
1698  $result = Shell::command( 'stty', 'size' )
1699  ->execute();
1700  if ( $result->getExitCode() !== 0 ) {
1701  return $default;
1702  }
1703  if ( !preg_match( '/^(\d+) (\d+)$/', $result->getStdout(), $m ) ) {
1704  return $default;
1705  }
1706  return [ intval( $m[2] ), intval( $m[1] ) ];
1707  }
1708 
1713  public static function requireTestsAutoloader() {
1714  require_once __DIR__ . '/../tests/common/TestsAutoLoader.php';
1715  }
1716 }
1717 
1722  protected $mSelf = "FakeMaintenanceScript";
1723 
1724  public function execute() {
1725  }
1726 }
1727 
1732 abstract class LoggedUpdateMaintenance extends Maintenance {
1733  public function __construct() {
1734  parent::__construct();
1735  $this->addOption( 'force', 'Run the update even if it was completed already' );
1736  $this->setBatchSize( 200 );
1737  }
1738 
1739  public function execute() {
1740  $db = $this->getDB( DB_MASTER );
1741  $key = $this->getUpdateKey();
1742 
1743  if ( !$this->hasOption( 'force' )
1744  && $db->selectRow( 'updatelog', '1', [ 'ul_key' => $key ], __METHOD__ )
1745  ) {
1746  $this->output( "..." . $this->updateSkippedMessage() . "\n" );
1747 
1748  return true;
1749  }
1750 
1751  if ( !$this->doDBUpdates() ) {
1752  return false;
1753  }
1754 
1755  $db->insert( 'updatelog', [ 'ul_key' => $key ], __METHOD__, [ 'IGNORE' ] );
1756 
1757  return true;
1758  }
1759 
1764  protected function updateSkippedMessage() {
1765  $key = $this->getUpdateKey();
1766 
1767  return "Update '{$key}' already logged as completed. Use --force to run it again.";
1768  }
1769 
1775  abstract protected function doDBUpdates();
1776 
1781  abstract protected function getUpdateKey();
1782 }
commitTransaction(IDatabase $dbw, $fname)
Commit the transcation on a DB handle and wait for replica DBs to catch up.
array [] $mParams
Array of desired/allowed params.
const STDIN_ALL
Definition: Maintenance.php:96
getArg( $argId=0, $default=null)
Get an argument.
const DB_NONE
Constants for DB access type.
Definition: Maintenance.php:91
const RUN_MAINTENANCE_IF_MAIN
Definition: Maintenance.php:39
maybeHelp( $force=false)
Maybe show the help.
checkRequiredExtensions()
Verify that the required extensions are installed.
$wgDBDefaultGroup
Default group to use when getting database connections.
setParam(&$options, $option, $value)
Helper function used solely by loadParamsAndArgs to prevent code duplication.
error( $err, $die=0)
Throw an error to the user.
runChild( $maintClass, $classFile=null)
Run a child maintenance script.
static setLBFactoryTriggers(LBFactory $LBFactory, Config $config)
$IP
Definition: WebStart.php:41
An interface for generating database load balancers.
Definition: LBFactory.php:40
int $wgMultiContentRevisionSchemaMigrationStage
RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables)...
$wgDBadminpassword
Separate password for maintenance tasks.
lockSearchindex( $db)
Lock the search index.
getOption( $name, $default=null)
Get an option, or return the default.
getDir()
Get the maintenance directory.
array $orderedOptions
Used to read the options in the order they were passed.
$command
Definition: cdb.php:65
static instance()
Singleton.
Definition: Profiler.php:63
$wgDBpassword
Database user&#39;s password.
wfHostname()
Fetch server name for use in error reporting etc.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: Maintenance.php:86
getName()
Get the script&#39;s name.
addDefaultParams()
Add the default parameters to the scripts.
finalSetup()
Handle some last-minute setup here.
rollbackTransaction(IDatabase $dbw, $fname)
Rollback the transcation on a DB handle.
afterFinalSetup()
Execute a callback function at the end of initialisation.
commit( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Commits a transaction previously started using begin()
static readconsole( $prompt='> ')
Prompt the console for input.
requireExtension( $name)
Indicate that the specified extension must be loaded before the script can run.
setConfig(Config $config)
updateSearchIndex( $maxLockTime, $callback, $dbw, $results)
Perform a search index update with locking.
static loadFromPageId( $db, $pageid, $id=0)
Load either the current, or a specified, revision that&#39;s attached to a given page.
Definition: Revision.php:261
setBatchSize( $s=0)
Set the batch size.
$wgDBuser
Database username.
loadSpecialVars()
Handle the special variables that are global to all scripts.
$wgShowExceptionDetails
If set to true, uncaught exceptions will print the exception message and a complete stack trace to ou...
wfEntryPointCheck( $format='text', $scriptPath='/')
Check PHP version and that external dependencies are installed, and display an informative error if e...
updateSearchIndexForPage( $dbw, $pageId)
Update the searchindex table for a given pageid.
hasOption( $name)
Checks to see if a particular option exists.
array [] $mGenericParameters
Generic options added by addDefaultParams()
relockSearchindex( $db)
Unlock and lock again Since the lock is low-priority, queued reads will be able to complete...
loadWithArgv( $argv)
Load params and arguments from a given array of command-line arguments.
wfIsWindows()
Check if the operating system is Windows.
outputChanneled( $msg, $channel=null)
Message outputter with channeled message support.
const DB_MASTER
Definition: defines.php:26
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
array $requiredExtensions
if( $line===false) $args
Definition: cdb.php:64
cleanupChanneled()
Clean up channeled output.
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
static replaceStubInstance(Profiler $profiler)
Replace the current profiler with $profiler if no non-stub profiler is set.
Definition: Profiler.php:99
supportsOption( $name)
Checks to see if a particular option in supported.
Fake maintenance wrapper, mostly used for the web installer/updater.
activateProfiler()
Activate the profiler (assuming $wgProfiler is set)
rollback( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Rollback a transaction previously started using begin()
array [] $mDependantParameters
Generic options which might or not be supported by the script.
static requireTestsAutoloader()
Call this to set up the autoloader to allow classes to be used from the tests directory.
hasArg( $argId=0)
Does a given argument exist?
$wgTrxProfilerLimits
Performance expectations for DB usage.
Interface for configuration instances.
Definition: Config.php:28
$mAllowUnregisteredOptions
$self
float $lastReplicationWait
UNIX timestamp.
addDescription( $text)
Set the description text.
const DB_ADMIN
Definition: Maintenance.php:93
setup()
Do some sanity checking and basic setup.
clearParamsAndArgs()
Clear all params and arguments.
deleteOption( $name)
Remove an option.
$wgDBservers
Database load balancer This is a two-dimensional array, an array of server info structures Fields are...
addArg( $arg, $description, $required=true)
Add some args that are needed.
globals()
Potentially debug globals.
adjustMemoryLimit()
Adjusts PHP&#39;s memory limit to better suit our needs, if needed.
$GLOBALS['IP']
output( $out, $channel=null)
Throw some output to the user.
resource $fileHandle
Used when creating separate schema files.
unlockSearchindex( $db)
Unlock the tables.
static findInDefaultPaths( $names, $versionInfo=false)
Same as locateExecutable(), but checks in getPossibleBinPaths() by default.
static shouldExecute()
Should we execute the maintenance script, or just allow it to be included as a standalone class...
static tryOpportunisticExecute( $mode='run')
Run all deferred updates immediately if there are no DB writes active.
const DB_STD
Definition: Maintenance.php:92
static getTermSize()
Get the terminal size as a two-element array where the first element is the width (number of columns)...
static readlineEmulation( $prompt)
Emulate readline()
execute()
Do the actual work.
static posix_isatty( $fd)
Wrapper for posix_isatty() We default as considering stdin a tty (for nice readline methods) but trea...
Class for scripts that perform database maintenance and want to log the update in updatelog so we can...
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
$wgDBadminuser
Separate username for maintenance tasks.
memoryLimit()
Normally we disable the memory_limit when running admin scripts.
$wgProfileLimit
Only record profiling info for pages that took longer than this.
Advanced database interface for IDatabase handles that include maintenance methods.
static emitBufferedStatsdData(IBufferingStatsdDataFactory $stats, Config $config)
Send out any buffered statsd data according to sampling rules.
Definition: MediaWiki.php:1073
string false $maintClass
-var class-string|false
Definition: Maintenance.php:50
int $mBatchSize
Batch size.
getBatchSize()
Returns batch size.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$line
Definition: cdb.php:59
global $wgCommandLineMode
setAgentAndTriggers()
Set triggers like when to try to run deferred updates.
countDown( $seconds)
Count down from $seconds to zero on the terminal, with a one-second pause between showing each number...
updateSkippedMessage()
Message to show that the update was done already and was just skipped.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getStdin( $len=null)
Return input from stdin.
$wgServer
URL of the server.
$wgLBFactoryConf
Load balancer factory configuration To set up a multi-master wiki farm, set the class here to somethi...
loadSettings()
Generic setup for most installs.
const SCHEMA_COMPAT_READ_OLD
Definition: Defines.php:265
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
purgeRedundantText( $delete=true)
Support function for cleaning up redundant text records.
setDB(IMaintainableDatabase $db)
Sets database object to be returned by getDB().
$wgProfiler
Profiler configuration.
validateParamsAndArgs()
Run some validation checks on the params, etc.
Config $config
Accessible via getConfig()
IMaintainableDatabase $mDb
Used by getDB() / setDB()
getDB( $db, $groups=[], $dbDomain=false)
Returns a database to be used by current maintenance script.
loadParamsAndArgs( $self=null, $opts=null, $args=null)
Process command line arguments $mOptions becomes an array with keys set to the option names $mArgs be...
getDbType()
Does the script need different DB access? By default, we give Maintenance scripts normal rights to th...
__construct()
Default constructor.
beginTransaction(IDatabase $dbw, $fname)
Begin a transcation on a DB.
setAllowUnregisteredOptions( $allow)
Sets whether to allow unregistered options, which are options passed to a script that do not match an...
$wgShowHostnames
Expose backend server host names through the API and various HTML comments.