MediaWiki  1.27.2
Maintenance.php
Go to the documentation of this file.
1 <?php
23 // Bail on old versions of PHP, or if composer has not been run yet to install
24 // dependencies. Using dirname( __FILE__ ) here because __DIR__ is PHP5.3+.
25 // @codingStandardsIgnoreStart MediaWiki.Usage.DirUsage.FunctionFound
26 require_once dirname( __FILE__ ) . '/../includes/PHPVersionCheck.php';
27 // @codingStandardsIgnoreEnd
28 wfEntryPointCheck( 'cli' );
29 
35 // Define this so scripts can easily find doMaintenance.php
36 define( 'RUN_MAINTENANCE_IF_MAIN', __DIR__ . '/doMaintenance.php' );
37 define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless
38 
39 $maintClass = false;
40 
42 
53 abstract class Maintenance {
58  const DB_NONE = 0;
59  const DB_STD = 1;
60  const DB_ADMIN = 2;
61 
62  // Const for getStdin()
63  const STDIN_ALL = 'all';
64 
65  // This is the desired params
66  protected $mParams = [];
67 
68  // Array of mapping short parameters to long ones
69  protected $mShortParamsMap = [];
70 
71  // Array of desired args
72  protected $mArgList = [];
73 
74  // This is the list of options that were actually passed
75  protected $mOptions = [];
76 
77  // This is the list of arguments that were actually passed
78  protected $mArgs = [];
79 
80  // Name of the script currently running
81  protected $mSelf;
82 
83  // Special vars for params that are always used
84  protected $mQuiet = false;
85  protected $mDbUser, $mDbPass;
86 
87  // A description of the script, children should change this via addDescription()
88  protected $mDescription = '';
89 
90  // Have we already loaded our user input?
91  protected $mInputLoaded = false;
92 
99  protected $mBatchSize = null;
100 
101  // Generic options added by addDefaultParams()
102  private $mGenericParameters = [];
103  // Generic options which might or not be supported by the script
104  private $mDependantParameters = [];
105 
110  private $mDb = null;
111 
113  private $lastSlaveWait = 0.0;
114 
119  public $fileHandle;
120 
126  private $config;
127 
139  public $orderedOptions = [];
140 
145  public function __construct() {
146  // Setup $IP, using MW_INSTALL_PATH if it exists
147  global $IP;
148  $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== ''
149  ? getenv( 'MW_INSTALL_PATH' )
150  : realpath( __DIR__ . '/..' );
151 
152  $this->addDefaultParams();
153  register_shutdown_function( [ $this, 'outputChanneled' ], false );
154  }
155 
163  public static function shouldExecute() {
165 
166  if ( !function_exists( 'debug_backtrace' ) ) {
167  // If someone has a better idea...
168  return $wgCommandLineMode;
169  }
170 
171  $bt = debug_backtrace();
172  $count = count( $bt );
173  if ( $count < 2 ) {
174  return false; // sanity
175  }
176  if ( $bt[0]['class'] !== 'Maintenance' || $bt[0]['function'] !== 'shouldExecute' ) {
177  return false; // last call should be to this function
178  }
179  $includeFuncs = [ 'require_once', 'require', 'include', 'include_once' ];
180  for ( $i = 1; $i < $count; $i++ ) {
181  if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) {
182  return false; // previous calls should all be "requires"
183  }
184  }
185 
186  return true;
187  }
188 
192  abstract public function execute();
193 
205  protected function addOption( $name, $description, $required = false,
206  $withArg = false, $shortName = false, $multiOccurrence = false
207  ) {
208  $this->mParams[$name] = [
209  'desc' => $description,
210  'require' => $required,
211  'withArg' => $withArg,
212  'shortName' => $shortName,
213  'multiOccurrence' => $multiOccurrence
214  ];
215 
216  if ( $shortName !== false ) {
217  $this->mShortParamsMap[$shortName] = $name;
218  }
219  }
220 
226  protected function hasOption( $name ) {
227  return isset( $this->mOptions[$name] );
228  }
229 
240  protected function getOption( $name, $default = null ) {
241  if ( $this->hasOption( $name ) ) {
242  return $this->mOptions[$name];
243  } else {
244  // Set it so we don't have to provide the default again
245  $this->mOptions[$name] = $default;
246 
247  return $this->mOptions[$name];
248  }
249  }
250 
257  protected function addArg( $arg, $description, $required = true ) {
258  $this->mArgList[] = [
259  'name' => $arg,
260  'desc' => $description,
261  'require' => $required
262  ];
263  }
264 
269  protected function deleteOption( $name ) {
270  unset( $this->mParams[$name] );
271  }
272 
277  protected function addDescription( $text ) {
278  $this->mDescription = $text;
279  }
280 
286  protected function hasArg( $argId = 0 ) {
287  return isset( $this->mArgs[$argId] );
288  }
289 
296  protected function getArg( $argId = 0, $default = null ) {
297  return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default;
298  }
299 
304  protected function setBatchSize( $s = 0 ) {
305  $this->mBatchSize = $s;
306 
307  // If we support $mBatchSize, show the option.
308  // Used to be in addDefaultParams, but in order for that to
309  // work, subclasses would have to call this function in the constructor
310  // before they called parent::__construct which is just weird
311  // (and really wasn't done).
312  if ( $this->mBatchSize ) {
313  $this->addOption( 'batch-size', 'Run this many operations ' .
314  'per batch, default: ' . $this->mBatchSize, false, true );
315  if ( isset( $this->mParams['batch-size'] ) ) {
316  // This seems a little ugly...
317  $this->mDependantParameters['batch-size'] = $this->mParams['batch-size'];
318  }
319  }
320  }
321 
326  public function getName() {
327  return $this->mSelf;
328  }
329 
336  protected function getStdin( $len = null ) {
337  if ( $len == Maintenance::STDIN_ALL ) {
338  return file_get_contents( 'php://stdin' );
339  }
340  $f = fopen( 'php://stdin', 'rt' );
341  if ( !$len ) {
342  return $f;
343  }
344  $input = fgets( $f, $len );
345  fclose( $f );
346 
347  return rtrim( $input );
348  }
349 
353  public function isQuiet() {
354  return $this->mQuiet;
355  }
356 
363  protected function output( $out, $channel = null ) {
364  if ( $this->mQuiet ) {
365  return;
366  }
367  if ( $channel === null ) {
368  $this->cleanupChanneled();
369  print $out;
370  } else {
371  $out = preg_replace( '/\n\z/', '', $out );
372  $this->outputChanneled( $out, $channel );
373  }
374  }
375 
382  protected function error( $err, $die = 0 ) {
383  $this->outputChanneled( false );
384  if ( PHP_SAPI == 'cli' ) {
385  fwrite( STDERR, $err . "\n" );
386  } else {
387  print $err;
388  }
389  $die = intval( $die );
390  if ( $die > 0 ) {
391  die( $die );
392  }
393  }
394 
395  private $atLineStart = true;
396  private $lastChannel = null;
397 
401  public function cleanupChanneled() {
402  if ( !$this->atLineStart ) {
403  print "\n";
404  $this->atLineStart = true;
405  }
406  }
407 
416  public function outputChanneled( $msg, $channel = null ) {
417  if ( $msg === false ) {
418  $this->cleanupChanneled();
419 
420  return;
421  }
422 
423  // End the current line if necessary
424  if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
425  print "\n";
426  }
427 
428  print $msg;
429 
430  $this->atLineStart = false;
431  if ( $channel === null ) {
432  // For unchanneled messages, output trailing newline immediately
433  print "\n";
434  $this->atLineStart = true;
435  }
436  $this->lastChannel = $channel;
437  }
438 
449  public function getDbType() {
450  return Maintenance::DB_STD;
451  }
452 
456  protected function addDefaultParams() {
457 
458  # Generic (non script dependant) options:
459 
460  $this->addOption( 'help', 'Display this help message', false, false, 'h' );
461  $this->addOption( 'quiet', 'Whether to supress non-error output', false, false, 'q' );
462  $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true );
463  $this->addOption( 'wiki', 'For specifying the wiki ID', false, true );
464  $this->addOption( 'globals', 'Output globals at the end of processing for debugging' );
465  $this->addOption(
466  'memory-limit',
467  'Set a specific memory limit for the script, '
468  . '"max" for no limit or "default" to avoid changing it'
469  );
470  $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " .
471  "http://en.wikipedia.org. This is sometimes necessary because " .
472  "server name detection may fail in command line scripts.", false, true );
473  $this->addOption( 'profiler', 'Profiler output format (usually "text")', false, true );
474 
475  # Save generic options to display them separately in help
476  $this->mGenericParameters = $this->mParams;
477 
478  # Script dependant options:
479 
480  // If we support a DB, show the options
481  if ( $this->getDbType() > 0 ) {
482  $this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
483  $this->addOption( 'dbpass', 'The password to use for this script', false, true );
484  }
485 
486  # Save additional script dependant options to display
487  #  them separately in help
488  $this->mDependantParameters = array_diff_key( $this->mParams, $this->mGenericParameters );
489  }
490 
495  public function getConfig() {
496  if ( $this->config === null ) {
497  $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
498  }
499 
500  return $this->config;
501  }
502 
507  public function setConfig( Config $config ) {
508  $this->config = $config;
509  }
510 
518  public function runChild( $maintClass, $classFile = null ) {
519  // Make sure the class is loaded first
520  if ( !class_exists( $maintClass ) ) {
521  if ( $classFile ) {
522  require_once $classFile;
523  }
524  if ( !class_exists( $maintClass ) ) {
525  $this->error( "Cannot spawn child: $maintClass" );
526  }
527  }
528 
532  $child = new $maintClass();
533  $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs );
534  if ( !is_null( $this->mDb ) ) {
535  $child->setDB( $this->mDb );
536  }
537 
538  return $child;
539  }
540 
544  public function setup() {
546 
547  # Abort if called from a web server
548  if ( isset( $_SERVER ) && isset( $_SERVER['REQUEST_METHOD'] ) ) {
549  $this->error( 'This script must be run from the command line', true );
550  }
551 
552  if ( $IP === null ) {
553  $this->error( "\$IP not set, aborting!\n" .
554  '(Did you forget to call parent::__construct() in your maintenance script?)', 1 );
555  }
556 
557  # Make sure we can handle script parameters
558  if ( !defined( 'HPHP_VERSION' ) && !ini_get( 'register_argc_argv' ) ) {
559  $this->error( 'Cannot get command line arguments, register_argc_argv is set to false', true );
560  }
561 
562  // Send PHP warnings and errors to stderr instead of stdout.
563  // This aids in diagnosing problems, while keeping messages
564  // out of redirected output.
565  if ( ini_get( 'display_errors' ) ) {
566  ini_set( 'display_errors', 'stderr' );
567  }
568 
569  $this->loadParamsAndArgs();
570  $this->maybeHelp();
571 
572  # Set the memory limit
573  # Note we need to set it again later in cache LocalSettings changed it
574  $this->adjustMemoryLimit();
575 
576  # Set max execution time to 0 (no limit). PHP.net says that
577  # "When running PHP from the command line the default setting is 0."
578  # But sometimes this doesn't seem to be the case.
579  ini_set( 'max_execution_time', 0 );
580 
581  $wgRequestTime = microtime( true );
582 
583  # Define us as being in MediaWiki
584  define( 'MEDIAWIKI', true );
585 
586  $wgCommandLineMode = true;
587 
588  # Turn off output buffering if it's on
589  while ( ob_get_level() > 0 ) {
590  ob_end_flush();
591  }
592 
593  $this->validateParamsAndArgs();
594  }
595 
605  public function memoryLimit() {
606  $limit = $this->getOption( 'memory-limit', 'max' );
607  $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood
608  return $limit;
609  }
610 
614  protected function adjustMemoryLimit() {
615  $limit = $this->memoryLimit();
616  if ( $limit == 'max' ) {
617  $limit = -1; // no memory limit
618  }
619  if ( $limit != 'default' ) {
620  ini_set( 'memory_limit', $limit );
621  }
622  }
623 
627  protected function activateProfiler() {
628  global $wgProfiler, $wgProfileLimit, $wgTrxProfilerLimits;
629 
630  $output = $this->getOption( 'profiler' );
631  if ( !$output ) {
632  return;
633  }
634 
635  if ( is_array( $wgProfiler ) && isset( $wgProfiler['class'] ) ) {
636  $class = $wgProfiler['class'];
637  $profiler = new $class(
638  [ 'sampling' => 1, 'output' => [ $output ] ]
639  + $wgProfiler
640  + [ 'threshold' => $wgProfileLimit ]
641  );
642  $profiler->setTemplated( true );
643  Profiler::replaceStubInstance( $profiler );
644  }
645 
646  $trxProfiler = Profiler::instance()->getTransactionProfiler();
647  $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
648  $trxProfiler->setExpectations( $wgTrxProfilerLimits['Maintenance'], __METHOD__ );
649  }
650 
654  public function clearParamsAndArgs() {
655  $this->mOptions = [];
656  $this->mArgs = [];
657  $this->mInputLoaded = false;
658  }
659 
667  public function loadWithArgv( $argv ) {
668  $options = [];
669  $args = [];
670  $this->orderedOptions = [];
671 
672  # Parse arguments
673  for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
674  if ( $arg == '--' ) {
675  # End of options, remainder should be considered arguments
676  $arg = next( $argv );
677  while ( $arg !== false ) {
678  $args[] = $arg;
679  $arg = next( $argv );
680  }
681  break;
682  } elseif ( substr( $arg, 0, 2 ) == '--' ) {
683  # Long options
684  $option = substr( $arg, 2 );
685  if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) {
686  $param = next( $argv );
687  if ( $param === false ) {
688  $this->error( "\nERROR: $option parameter needs a value after it\n" );
689  $this->maybeHelp( true );
690  }
691 
692  $this->setParam( $options, $option, $param );
693  } else {
694  $bits = explode( '=', $option, 2 );
695  if ( count( $bits ) > 1 ) {
696  $option = $bits[0];
697  $param = $bits[1];
698  } else {
699  $param = 1;
700  }
701 
702  $this->setParam( $options, $option, $param );
703  }
704  } elseif ( $arg == '-' ) {
705  # Lonely "-", often used to indicate stdin or stdout.
706  $args[] = $arg;
707  } elseif ( substr( $arg, 0, 1 ) == '-' ) {
708  # Short options
709  $argLength = strlen( $arg );
710  for ( $p = 1; $p < $argLength; $p++ ) {
711  $option = $arg[$p];
712  if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) {
713  $option = $this->mShortParamsMap[$option];
714  }
715 
716  if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) {
717  $param = next( $argv );
718  if ( $param === false ) {
719  $this->error( "\nERROR: $option parameter needs a value after it\n" );
720  $this->maybeHelp( true );
721  }
722  $this->setParam( $options, $option, $param );
723  } else {
724  $this->setParam( $options, $option, 1 );
725  }
726  }
727  } else {
728  $args[] = $arg;
729  }
730  }
731 
732  $this->mOptions = $options;
733  $this->mArgs = $args;
734  $this->loadSpecialVars();
735  $this->mInputLoaded = true;
736  }
737 
750  private function setParam( &$options, $option, $value ) {
751  $this->orderedOptions[] = [ $option, $value ];
752 
753  if ( isset( $this->mParams[$option] ) ) {
754  $multi = $this->mParams[$option]['multiOccurrence'];
755  } else {
756  $multi = false;
757  }
758  $exists = array_key_exists( $option, $options );
759  if ( $multi && $exists ) {
760  $options[$option][] = $value;
761  } elseif ( $multi ) {
762  $options[$option] = [ $value ];
763  } elseif ( !$exists ) {
764  $options[$option] = $value;
765  } else {
766  $this->error( "\nERROR: $option parameter given twice\n" );
767  $this->maybeHelp( true );
768  }
769  }
770 
780  public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
781  # If we were given opts or args, set those and return early
782  if ( $self ) {
783  $this->mSelf = $self;
784  $this->mInputLoaded = true;
785  }
786  if ( $opts ) {
787  $this->mOptions = $opts;
788  $this->mInputLoaded = true;
789  }
790  if ( $args ) {
791  $this->mArgs = $args;
792  $this->mInputLoaded = true;
793  }
794 
795  # If we've already loaded input (either by user values or from $argv)
796  # skip on loading it again. The array_shift() will corrupt values if
797  # it's run again and again
798  if ( $this->mInputLoaded ) {
799  $this->loadSpecialVars();
800 
801  return;
802  }
803 
804  global $argv;
805  $this->mSelf = $argv[0];
806  $this->loadWithArgv( array_slice( $argv, 1 ) );
807  }
808 
812  protected function validateParamsAndArgs() {
813  $die = false;
814  # Check to make sure we've got all the required options
815  foreach ( $this->mParams as $opt => $info ) {
816  if ( $info['require'] && !$this->hasOption( $opt ) ) {
817  $this->error( "Param $opt required!" );
818  $die = true;
819  }
820  }
821  # Check arg list too
822  foreach ( $this->mArgList as $k => $info ) {
823  if ( $info['require'] && !$this->hasArg( $k ) ) {
824  $this->error( 'Argument <' . $info['name'] . '> required!' );
825  $die = true;
826  }
827  }
828 
829  if ( $die ) {
830  $this->maybeHelp( true );
831  }
832  }
833 
837  protected function loadSpecialVars() {
838  if ( $this->hasOption( 'dbuser' ) ) {
839  $this->mDbUser = $this->getOption( 'dbuser' );
840  }
841  if ( $this->hasOption( 'dbpass' ) ) {
842  $this->mDbPass = $this->getOption( 'dbpass' );
843  }
844  if ( $this->hasOption( 'quiet' ) ) {
845  $this->mQuiet = true;
846  }
847  if ( $this->hasOption( 'batch-size' ) ) {
848  $this->mBatchSize = intval( $this->getOption( 'batch-size' ) );
849  }
850  }
851 
856  protected function maybeHelp( $force = false ) {
857  if ( !$force && !$this->hasOption( 'help' ) ) {
858  return;
859  }
860 
861  $screenWidth = 80; // TODO: Calculate this!
862  $tab = " ";
863  $descWidth = $screenWidth - ( 2 * strlen( $tab ) );
864 
865  ksort( $this->mParams );
866  $this->mQuiet = false;
867 
868  // Description ...
869  if ( $this->mDescription ) {
870  $this->output( "\n" . $this->mDescription . "\n" );
871  }
872  $output = "\nUsage: php " . basename( $this->mSelf );
873 
874  // ... append parameters ...
875  if ( $this->mParams ) {
876  $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]";
877  }
878 
879  // ... and append arguments.
880  if ( $this->mArgList ) {
881  $output .= ' ';
882  foreach ( $this->mArgList as $k => $arg ) {
883  if ( $arg['require'] ) {
884  $output .= '<' . $arg['name'] . '>';
885  } else {
886  $output .= '[' . $arg['name'] . ']';
887  }
888  if ( $k < count( $this->mArgList ) - 1 ) {
889  $output .= ' ';
890  }
891  }
892  }
893  $this->output( "$output\n\n" );
894 
895  # TODO abstract some repetitive code below
896 
897  // Generic parameters
898  $this->output( "Generic maintenance parameters:\n" );
899  foreach ( $this->mGenericParameters as $par => $info ) {
900  if ( $info['shortName'] !== false ) {
901  $par .= " (-{$info['shortName']})";
902  }
903  $this->output(
904  wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
905  "\n$tab$tab" ) . "\n"
906  );
907  }
908  $this->output( "\n" );
909 
910  $scriptDependantParams = $this->mDependantParameters;
911  if ( count( $scriptDependantParams ) > 0 ) {
912  $this->output( "Script dependant parameters:\n" );
913  // Parameters description
914  foreach ( $scriptDependantParams as $par => $info ) {
915  if ( $info['shortName'] !== false ) {
916  $par .= " (-{$info['shortName']})";
917  }
918  $this->output(
919  wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
920  "\n$tab$tab" ) . "\n"
921  );
922  }
923  $this->output( "\n" );
924  }
925 
926  // Script specific parameters not defined on construction by
927  // Maintenance::addDefaultParams()
928  $scriptSpecificParams = array_diff_key(
929  # all script parameters:
930  $this->mParams,
931  # remove the Maintenance default parameters:
932  $this->mGenericParameters,
933  $this->mDependantParameters
934  );
935  if ( count( $scriptSpecificParams ) > 0 ) {
936  $this->output( "Script specific parameters:\n" );
937  // Parameters description
938  foreach ( $scriptSpecificParams as $par => $info ) {
939  if ( $info['shortName'] !== false ) {
940  $par .= " (-{$info['shortName']})";
941  }
942  $this->output(
943  wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
944  "\n$tab$tab" ) . "\n"
945  );
946  }
947  $this->output( "\n" );
948  }
949 
950  // Print arguments
951  if ( count( $this->mArgList ) > 0 ) {
952  $this->output( "Arguments:\n" );
953  // Arguments description
954  foreach ( $this->mArgList as $info ) {
955  $openChar = $info['require'] ? '<' : '[';
956  $closeChar = $info['require'] ? '>' : ']';
957  $this->output(
958  wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " .
959  $info['desc'], $descWidth, "\n$tab$tab" ) . "\n"
960  );
961  }
962  $this->output( "\n" );
963  }
964 
965  die( 1 );
966  }
967 
971  public function finalSetup() {
972  global $wgCommandLineMode, $wgShowSQLErrors, $wgServer;
975 
976  # Turn off output buffering again, it might have been turned on in the settings files
977  if ( ob_get_level() ) {
978  ob_end_flush();
979  }
980  # Same with these
981  $wgCommandLineMode = true;
982 
983  # Override $wgServer
984  if ( $this->hasOption( 'server' ) ) {
985  $wgServer = $this->getOption( 'server', $wgServer );
986  }
987 
988  # If these were passed, use them
989  if ( $this->mDbUser ) {
990  $wgDBadminuser = $this->mDbUser;
991  }
992  if ( $this->mDbPass ) {
993  $wgDBadminpassword = $this->mDbPass;
994  }
995 
996  if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) {
997  $wgDBuser = $wgDBadminuser;
998  $wgDBpassword = $wgDBadminpassword;
999 
1000  if ( $wgDBservers ) {
1004  foreach ( $wgDBservers as $i => $server ) {
1005  $wgDBservers[$i]['user'] = $wgDBuser;
1006  $wgDBservers[$i]['password'] = $wgDBpassword;
1007  }
1008  }
1009  if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
1010  $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
1011  $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
1012  }
1014  }
1015 
1016  // Per-script profiling; useful for debugging
1017  $this->activateProfiler();
1018 
1019  $this->afterFinalSetup();
1020 
1021  $wgShowSQLErrors = true;
1022 
1023  MediaWiki\suppressWarnings();
1024  set_time_limit( 0 );
1025  MediaWiki\restoreWarnings();
1026 
1027  $this->adjustMemoryLimit();
1028  }
1029 
1033  protected function afterFinalSetup() {
1034  if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
1035  call_user_func( MW_CMDLINE_CALLBACK );
1036  }
1037  }
1038 
1043  public function globals() {
1044  if ( $this->hasOption( 'globals' ) ) {
1045  print_r( $GLOBALS );
1046  }
1047  }
1048 
1053  public function loadSettings() {
1055 
1056  if ( isset( $this->mOptions['conf'] ) ) {
1057  $settingsFile = $this->mOptions['conf'];
1058  } elseif ( defined( "MW_CONFIG_FILE" ) ) {
1059  $settingsFile = MW_CONFIG_FILE;
1060  } else {
1061  $settingsFile = "$IP/LocalSettings.php";
1062  }
1063  if ( isset( $this->mOptions['wiki'] ) ) {
1064  $bits = explode( '-', $this->mOptions['wiki'] );
1065  if ( count( $bits ) == 1 ) {
1066  $bits[] = '';
1067  }
1068  define( 'MW_DB', $bits[0] );
1069  define( 'MW_PREFIX', $bits[1] );
1070  }
1071 
1072  if ( !is_readable( $settingsFile ) ) {
1073  $this->error( "A copy of your installation's LocalSettings.php\n" .
1074  "must exist and be readable in the source directory.\n" .
1075  "Use --conf to specify it.", true );
1076  }
1077  $wgCommandLineMode = true;
1078 
1079  return $settingsFile;
1080  }
1081 
1087  public function purgeRedundantText( $delete = true ) {
1088  # Data should come off the master, wrapped in a transaction
1089  $dbw = $this->getDB( DB_MASTER );
1090  $this->beginTransaction( $dbw, __METHOD__ );
1091 
1092  # Get "active" text records from the revisions table
1093  $this->output( 'Searching for active text records in revisions table...' );
1094  $res = $dbw->select( 'revision', 'rev_text_id', [], __METHOD__, [ 'DISTINCT' ] );
1095  foreach ( $res as $row ) {
1096  $cur[] = $row->rev_text_id;
1097  }
1098  $this->output( "done.\n" );
1099 
1100  # Get "active" text records from the archive table
1101  $this->output( 'Searching for active text records in archive table...' );
1102  $res = $dbw->select( 'archive', 'ar_text_id', [], __METHOD__, [ 'DISTINCT' ] );
1103  foreach ( $res as $row ) {
1104  # old pre-MW 1.5 records can have null ar_text_id's.
1105  if ( $row->ar_text_id !== null ) {
1106  $cur[] = $row->ar_text_id;
1107  }
1108  }
1109  $this->output( "done.\n" );
1110 
1111  # Get the IDs of all text records not in these sets
1112  $this->output( 'Searching for inactive text records...' );
1113  $cond = 'old_id NOT IN ( ' . $dbw->makeList( $cur ) . ' )';
1114  $res = $dbw->select( 'text', 'old_id', [ $cond ], __METHOD__, [ 'DISTINCT' ] );
1115  $old = [];
1116  foreach ( $res as $row ) {
1117  $old[] = $row->old_id;
1118  }
1119  $this->output( "done.\n" );
1120 
1121  # Inform the user of what we're going to do
1122  $count = count( $old );
1123  $this->output( "$count inactive items found.\n" );
1124 
1125  # Delete as appropriate
1126  if ( $delete && $count ) {
1127  $this->output( 'Deleting...' );
1128  $dbw->delete( 'text', [ 'old_id' => $old ], __METHOD__ );
1129  $this->output( "done.\n" );
1130  }
1131 
1132  # Done
1133  $this->commitTransaction( $dbw, __METHOD__ );
1134  }
1135 
1140  protected function getDir() {
1141  return __DIR__;
1142  }
1143 
1154  protected function getDB( $db, $groups = [], $wiki = false ) {
1155  if ( is_null( $this->mDb ) ) {
1156  return wfGetDB( $db, $groups, $wiki );
1157  } else {
1158  return $this->mDb;
1159  }
1160  }
1161 
1167  public function setDB( IDatabase $db ) {
1168  $this->mDb = $db;
1169  }
1170 
1181  protected function beginTransaction( IDatabase $dbw, $fname ) {
1182  $dbw->begin( $fname );
1183  }
1184 
1196  protected function commitTransaction( IDatabase $dbw, $fname ) {
1197  $dbw->commit( $fname );
1198 
1199  $ok = wfWaitForSlaves( $this->lastSlaveWait, false, '*', 30 );
1200  $this->lastSlaveWait = microtime( true );
1201 
1202  return $ok;
1203  }
1204 
1215  protected function rollbackTransaction( IDatabase $dbw, $fname ) {
1216  $dbw->rollback( $fname );
1217  }
1218 
1223  private function lockSearchindex( $db ) {
1224  $write = [ 'searchindex' ];
1225  $read = [
1226  'page',
1227  'revision',
1228  'text',
1229  'interwiki',
1230  'l10n_cache',
1231  'user',
1232  'page_restrictions'
1233  ];
1234  $db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ );
1235  }
1236 
1241  private function unlockSearchindex( $db ) {
1242  $db->unlockTables( __CLASS__ . '::' . __METHOD__ );
1243  }
1244 
1250  private function relockSearchindex( $db ) {
1251  $this->unlockSearchindex( $db );
1252  $this->lockSearchindex( $db );
1253  }
1254 
1262  public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) {
1263  $lockTime = time();
1264 
1265  # Lock searchindex
1266  if ( $maxLockTime ) {
1267  $this->output( " --- Waiting for lock ---" );
1268  $this->lockSearchindex( $dbw );
1269  $lockTime = time();
1270  $this->output( "\n" );
1271  }
1272 
1273  # Loop through the results and do a search update
1274  foreach ( $results as $row ) {
1275  # Allow reads to be processed
1276  if ( $maxLockTime && time() > $lockTime + $maxLockTime ) {
1277  $this->output( " --- Relocking ---" );
1278  $this->relockSearchindex( $dbw );
1279  $lockTime = time();
1280  $this->output( "\n" );
1281  }
1282  call_user_func( $callback, $dbw, $row );
1283  }
1284 
1285  # Unlock searchindex
1286  if ( $maxLockTime ) {
1287  $this->output( " --- Unlocking --" );
1288  $this->unlockSearchindex( $dbw );
1289  $this->output( "\n" );
1290  }
1291  }
1292 
1299  public function updateSearchIndexForPage( $dbw, $pageId ) {
1300  // Get current revision
1301  $rev = Revision::loadFromPageId( $dbw, $pageId );
1302  $title = null;
1303  if ( $rev ) {
1304  $titleObj = $rev->getTitle();
1305  $title = $titleObj->getPrefixedDBkey();
1306  $this->output( "$title..." );
1307  # Update searchindex
1308  $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getContent() );
1309  $u->doUpdate();
1310  $this->output( "\n" );
1311  }
1312 
1313  return $title;
1314  }
1315 
1324  public static function posix_isatty( $fd ) {
1325  if ( !function_exists( 'posix_isatty' ) ) {
1326  return !$fd;
1327  } else {
1328  return posix_isatty( $fd );
1329  }
1330  }
1331 
1337  public static function readconsole( $prompt = '> ' ) {
1338  static $isatty = null;
1339  if ( is_null( $isatty ) ) {
1340  $isatty = self::posix_isatty( 0 /*STDIN*/ );
1341  }
1342 
1343  if ( $isatty && function_exists( 'readline' ) ) {
1344  $resp = readline( $prompt );
1345  if ( $resp === null ) {
1346  // Workaround for https://github.com/facebook/hhvm/issues/4776
1347  return false;
1348  } else {
1349  return $resp;
1350  }
1351  } else {
1352  if ( $isatty ) {
1353  $st = self::readlineEmulation( $prompt );
1354  } else {
1355  if ( feof( STDIN ) ) {
1356  $st = false;
1357  } else {
1358  $st = fgets( STDIN, 1024 );
1359  }
1360  }
1361  if ( $st === false ) {
1362  return false;
1363  }
1364  $resp = trim( $st );
1365 
1366  return $resp;
1367  }
1368  }
1369 
1375  private static function readlineEmulation( $prompt ) {
1376  $bash = Installer::locateExecutableInDefaultPaths( [ 'bash' ] );
1377  if ( !wfIsWindows() && $bash ) {
1378  $retval = false;
1379  $encPrompt = wfEscapeShellArg( $prompt );
1380  $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
1381  $encCommand = wfEscapeShellArg( $command );
1382  $line = wfShellExec( "$bash -c $encCommand", $retval, [], [ 'walltime' => 0 ] );
1383 
1384  if ( $retval == 0 ) {
1385  return $line;
1386  } elseif ( $retval == 127 ) {
1387  // Couldn't execute bash even though we thought we saw it.
1388  // Shell probably spit out an error message, sorry :(
1389  // Fall through to fgets()...
1390  } else {
1391  // EOF/ctrl+D
1392  return false;
1393  }
1394  }
1395 
1396  // Fallback... we'll have no editing controls, EWWW
1397  if ( feof( STDIN ) ) {
1398  return false;
1399  }
1400  print $prompt;
1401 
1402  return fgets( STDIN, 1024 );
1403  }
1404 }
1405 
1410  protected $mSelf = "FakeMaintenanceScript";
1411 
1412  public function execute() {
1413  return;
1414  }
1415 }
1416 
1421 abstract class LoggedUpdateMaintenance extends Maintenance {
1422  public function __construct() {
1423  parent::__construct();
1424  $this->addOption( 'force', 'Run the update even if it was completed already' );
1425  $this->setBatchSize( 200 );
1426  }
1427 
1428  public function execute() {
1429  $db = $this->getDB( DB_MASTER );
1430  $key = $this->getUpdateKey();
1431 
1432  if ( !$this->hasOption( 'force' )
1433  && $db->selectRow( 'updatelog', '1', [ 'ul_key' => $key ], __METHOD__ )
1434  ) {
1435  $this->output( "..." . $this->updateSkippedMessage() . "\n" );
1436 
1437  return true;
1438  }
1439 
1440  if ( !$this->doDBUpdates() ) {
1441  return false;
1442  }
1443 
1444  if ( $db->insert( 'updatelog', [ 'ul_key' => $key ], __METHOD__, 'IGNORE' ) ) {
1445  return true;
1446  } else {
1447  $this->output( $this->updatelogFailedMessage() . "\n" );
1448 
1449  return false;
1450  }
1451  }
1452 
1457  protected function updateSkippedMessage() {
1458  $key = $this->getUpdateKey();
1459 
1460  return "Update '{$key}' already logged as completed.";
1461  }
1462 
1467  protected function updatelogFailedMessage() {
1468  $key = $this->getUpdateKey();
1469 
1470  return "Unable to log update '{$key}' as completed.";
1471  }
1472 
1478  abstract protected function doDBUpdates();
1479 
1484  abstract protected function getUpdateKey();
1485 }
#define the
table suitable for use with IDatabase::select()
unlockSearchindex($db)
Unlock the tables.
loadParamsAndArgs($self=null, $opts=null, $args=null)
Process command line arguments $mOptions becomes an array with keys set to the option names $mArgs be...
commitTransaction(IDatabase $dbw, $fname)
Commit the transcation on a DB handle and wait for slaves to catch up.
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
const STDIN_ALL
Definition: Maintenance.php:63
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:762
const DB_NONE
Constants for DB access type.
Definition: Maintenance.php:58
wfWaitForSlaves($ifWritesSince=null, $wiki=false, $cluster=false, $timeout=null)
Waits for the slaves to catch up to the master position.
const RUN_MAINTENANCE_IF_MAIN
Definition: Maintenance.php:36
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
float $lastSlaveWait
UNIX timestamp.
setParam(&$options, $option, $value)
Helper function used solely by loadParamsAndArgs to prevent code duplication.
$IP
Definition: WebStart.php:58
$wgDBadminpassword
Separate password for maintenance tasks.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
addArg($arg, $description, $required=true)
Add some args that are needed.
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:60
$wgDBpassword
Database user's password.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: maintenance.txt:39
begin($fname=__METHOD__)
Begin a transaction.
getName()
Get the script's name.
doUpdate()
Perform actual update for the entry.
$wgProfiler
Definition: WebStart.php:73
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.
lockSearchindex($db)
Lock the search index.
afterFinalSetup()
Execute a callback function at the end of initialisation.
$value
setConfig(Config $config)
getDB($db, $groups=[], $wiki=false)
Returns a database to be used by current maintenance script.
hasOption($name)
Checks to see if a particular param exists.
$wgDBuser
Database username.
static locateExecutableInDefaultPaths($names, $versionInfo=false)
Same as locateExecutable(), but checks in getPossibleBinPaths() by default.
Definition: Installer.php:1196
loadSpecialVars()
Handle the special variables that are global to all scripts.
wfShellExec($cmd, &$retval=null, $environ=[], $limits=[], $options=[])
Execute a shell command, with time and memory limits mirrored from the PHP configuration if supported...
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
wfIsWindows()
Check if the operating system is Windows.
static posix_isatty($fd)
Wrapper for posix_isatty() We default as considering stdin a tty (for nice readline methods) but trea...
getStdin($len=null)
Return input from stdin.
$maintClass
Definition: Maintenance.php:39
if($line===false) $args
Definition: cdb.php:64
cleanupChanneled()
Clean up channeled output.
static replaceStubInstance(Profiler $profiler)
Replace the current profiler with $profiler if no non-stub profiler is set.
Definition: Profiler.php:95
Database independant search index updater.
updatelogFailedMessage()
Message to show that the update log was unable to log the completion of this update.
Fake maintenance wrapper, mostly used for the web installer/updater.
activateProfiler()
Activate the profiler (assuming $wgProfiler is set)
addOption($name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
global $wgCommandLineMode
Definition: Setup.php:513
$GLOBALS['IP']
deleteOption($name)
Remove an option.
doDBUpdates()
Do the actual work.
updateSearchIndex($maxLockTime, $callback, $dbw, $results)
Perform a search index update with locking.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1004
relockSearchindex($db)
Unlock and lock again Since the lock is low-priority, queued reads will be able to complete...
$self
$res
Definition: database.txt:21
const DB_ADMIN
Definition: Maintenance.php:60
setup()
Do some sanity checking and basic setup.
setDB(IDatabase $db)
Sets database object to be returned by getDB().
clearParamsAndArgs()
Clear all params and arguments.
hasArg($argId=0)
Does a given argument exist?
static destroyInstance()
Shut down, close connections and destroy the cached instance.
Definition: LBFactory.php:124
$wgDBservers
Database load balancer This is a two-dimensional array, an array of server info structures Fields are...
globals()
Potentially debug globals.
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
adjustMemoryLimit()
Adjusts PHP's memory limit to better suit our needs, if needed.
getUpdateKey()
Get the update key name to go in the update log table.
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1584
resource $fileHandle
Used when creating separate schema files.
addDescription($text)
Set the description text.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getOption($name, $default=null)
Get an option, or return the default.
IDatabase $mDb
Used by getDB() / setDB()
static shouldExecute()
Should we execute the maintenance script, or just allow it to be included as a standalone class...
output($out, $channel=null)
Throw some output to the user.
purgeRedundantText($delete=true)
Support function for cleaning up redundant text records.
const DB_STD
Definition: Maintenance.php:59
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object & $output
Definition: hooks.txt:1004
static getDefaultInstance()
commit($fname=__METHOD__, $flush= '')
Commits a transaction previously started using begin().
loadWithArgv($argv)
Load params and arguments from a given array of command-line arguments.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
maybeHelp($force=false)
Maybe show the help.
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined...
Definition: Setup.php:35
Class for scripts that perform database maintenance and want to log the update in updatelog so we can...
$wgDBadminuser
Separate username for maintenance tasks.
static readconsole($prompt= '> ')
Prompt the console for input.
static readlineEmulation($prompt)
Emulate readline()
memoryLimit()
Normally we disable the memory_limit when running admin scripts.
int $mBatchSize
Batch size.
Definition: Maintenance.php:99
$line
Definition: cdb.php:59
error($err, $die=0)
Throw an error to the user.
static loadFromPageId($db, $pageid, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: Revision.php:245
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition: hooks.txt:1004
wfEntryPointCheck($entryPoint)
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.
updateSkippedMessage()
Message to show that the update was done already and was just skipped.
MediaWiki Logger LoggerFactory implements a PSR[0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method.MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances.The"Spi"in MediaWiki\Logger\Spi stands for"service provider interface".An SPI is an API intended to be implemented or extended by a third party.This software design pattern is intended to enable framework extension and replaceable components.It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki.The service provider interface allows the backend logging library to be implemented in multiple ways.The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime.This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance.Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
$count
wfEscapeShellArg()
Windows-compatible version of escapeshellarg() Windows doesn't recognise single-quotes in the shell...
$wgServer
URL of the server.
const DB_MASTER
Definition: Defines.php:47
getArg($argId=0, $default=null)
Get an argument.
$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.
rollback($fname=__METHOD__, $flush= '')
Rollback a transaction previously started using begin().
setBatchSize($s=0)
Set the batch size.
float $wgRequestTime
Request start time as fractional seconds since epoch.
Definition: WebStart.php:43
validateParamsAndArgs()
Run some validation checks on the params, etc.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account incomplete not yet checked for validity & $retval
Definition: hooks.txt:242
Config $config
Accessible via getConfig()
runChild($maintClass, $classFile=null)
Run a child maintenance script.
outputChanneled($msg, $channel=null)
Message outputter with channeled message support.
getDbType()
Does the script need different DB access? By default, we give Maintenance scripts normal rights to th...
public function execute()
Definition: maintenance.txt:45
Basic database interface for live and lazy-loaded DB handles.
Definition: IDatabase.php:35
__construct()
Default constructor.
beginTransaction(IDatabase $dbw, $fname)
Begin a transcation on a DB.
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310