MediaWiki  master
Maintenance.php
Go to the documentation of this file.
1 <?php
31 
32 // NOTE: MaintenanceParameters is needed in the constructor, and we may not have
33 // autoloading enabled at this point?
34 require_once __DIR__ . '/MaintenanceParameters.php';
35 
66 abstract class Maintenance {
71  public const DB_NONE = 0;
72  public const DB_STD = 1;
73  public const DB_ADMIN = 2;
74 
75  // Const for getStdin()
76  public const STDIN_ALL = -1;
77 
78  // Help group names
79  public const SCRIPT_DEPENDENT_PARAMETERS = 'Script dependent parameters';
80  public const GENERIC_MAINTENANCE_PARAMETERS = 'Generic maintenance parameters';
81 
85  protected $parameters;
86 
93  protected $mParams = [];
94 
100  protected $mArgList = [];
101 
106  protected $mOptions = [];
107 
112  protected $mArgs = [];
113 
115  protected $mSelf;
116 
118  protected $mQuiet = false;
119  protected $mDbUser, $mDbPass;
120 
125  protected $mDescription = '';
126 
131  protected $mInputLoaded = false;
132 
139  protected $mBatchSize = null;
140 
145  private $mDb = null;
146 
148  private $lastReplicationWait = 0.0;
149 
154  public $fileHandle;
155 
157  private $hookContainer;
158 
160  private $hookRunner;
161 
167  private $config;
168 
173  private $requiredExtensions = [];
174 
187  public $orderedOptions = [];
188 
195  public function __construct() {
196  $this->parameters = new MaintenanceParameters();
197  $this->mOptions =& $this->parameters->getFieldReference( 'mOptions' );
198  $this->orderedOptions =& $this->parameters->getFieldReference( 'optionsSequence' );
199  $this->mArgs =& $this->parameters->getFieldReference( 'mArgs' );
200  $this->addDefaultParams();
201  }
202 
207  public function getParameters() {
208  return $this->parameters;
209  }
210 
215  public static function shouldExecute() {
216  return MaintenanceRunner::shouldExecute();
217  }
218 
227  abstract public function execute();
228 
235  protected function supportsOption( $name ) {
236  return $this->parameters->supportsOption( $name );
237  }
238 
250  protected function addOption( $name, $description, $required = false,
251  $withArg = false, $shortName = false, $multiOccurrence = false
252  ) {
253  $this->parameters->addOption(
254  $name,
255  $description,
256  $required,
257  $withArg,
258  $shortName,
259  $multiOccurrence
260  );
261  }
262 
269  protected function hasOption( $name ) {
270  return $this->parameters->hasOption( $name );
271  }
272 
284  protected function getOption( $name, $default = null ) {
285  return $this->parameters->getOption( $name, $default );
286  }
287 
294  protected function addArg( $arg, $description, $required = true ) {
295  $this->parameters->addArg( $arg, $description, $required );
296  }
297 
302  protected function deleteOption( $name ) {
303  $this->parameters->deleteOption( $name );
304  }
305 
311  protected function setAllowUnregisteredOptions( $allow ) {
312  $this->parameters->setAllowUnregisteredOptions( $allow );
313  }
314 
319  protected function addDescription( $text ) {
320  $this->parameters->setDescription( $text );
321  }
322 
328  protected function hasArg( $argId = 0 ) {
329  return $this->parameters->hasArg( $argId );
330  }
331 
339  protected function getArg( $argId = 0, $default = null ) {
340  return $this->parameters->getArg( $argId, $default );
341  }
342 
352  public function setOption( string $name, $value ): void {
353  $this->parameters->setOption( $name, $value );
354  }
355 
365  public function setArg( $argId, $value ): void {
366  $this->parameters->setArg( $argId, $value );
367  }
368 
376  protected function getBatchSize() {
377  return $this->mBatchSize;
378  }
379 
383  protected function setBatchSize( $s = 0 ) {
384  $this->mBatchSize = $s;
385 
386  // If we support $mBatchSize, show the option.
387  // Used to be in addDefaultParams, but in order for that to
388  // work, subclasses would have to call this function in the constructor
389  // before they called parent::__construct which is just weird
390  // (and really wasn't done).
391  if ( $this->mBatchSize ) {
392  $this->addOption( 'batch-size', 'Run this many operations ' .
393  'per batch, default: ' . $this->mBatchSize, false, true );
394  if ( $this->supportsOption( 'batch-size' ) ) {
395  // This seems a little ugly...
396  $this->parameters->assignGroup( self::SCRIPT_DEPENDENT_PARAMETERS, [ 'batch-size' ] );
397  }
398  }
399  }
400 
405  public function getName() {
406  return $this->mSelf;
407  }
408 
415  protected function getStdin( $len = null ) {
416  if ( $len == self::STDIN_ALL ) {
417  return file_get_contents( 'php://stdin' );
418  }
419  $f = fopen( 'php://stdin', 'rt' );
420  if ( !$len ) {
421  return $f;
422  }
423  $input = fgets( $f, $len );
424  fclose( $f );
425 
426  return rtrim( $input );
427  }
428 
432  public function isQuiet() {
433  return $this->mQuiet;
434  }
435 
443  protected function output( $out, $channel = null ) {
444  // This is sometimes called very early, before Setup.php is included.
445  if ( class_exists( MediaWikiServices::class ) ) {
446  // Flush stats periodically in long-running CLI scripts to avoid OOM (T181385)
447  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
448  if ( $stats->getDataCount() > 1000 ) {
449  MediaWiki::emitBufferedStatsdData( $stats, $this->getConfig() );
450  }
451  }
452 
453  if ( $this->mQuiet ) {
454  return;
455  }
456  if ( $channel === null ) {
457  $this->cleanupChanneled();
458  print $out;
459  } else {
460  $out = preg_replace( '/\n\z/', '', $out );
461  $this->outputChanneled( $out, $channel );
462  }
463  }
464 
472  protected function error( $err, $die = 0 ) {
473  if ( intval( $die ) !== 0 ) {
474  wfDeprecated( __METHOD__ . '( $err, $die )', '1.31' );
475  $this->fatalError( $err, intval( $die ) );
476  }
477  $this->outputChanneled( false );
478  if (
479  ( PHP_SAPI == 'cli' || PHP_SAPI == 'phpdbg' ) &&
480  !defined( 'MW_PHPUNIT_TEST' )
481  ) {
482  fwrite( STDERR, $err . "\n" );
483  } else {
484  print $err;
485  }
486  }
487 
497  protected function fatalError( $msg, $exitCode = 1 ) {
498  $this->error( $msg );
499  exit( $exitCode );
500  }
501 
502  private $atLineStart = true;
503  private $lastChannel = null;
504 
508  public function cleanupChanneled() {
509  if ( !$this->atLineStart ) {
510  print "\n";
511  $this->atLineStart = true;
512  }
513  }
514 
523  public function outputChanneled( $msg, $channel = null ) {
524  if ( $msg === false ) {
525  $this->cleanupChanneled();
526 
527  return;
528  }
529 
530  // End the current line if necessary
531  if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
532  print "\n";
533  }
534 
535  print $msg;
536 
537  $this->atLineStart = false;
538  if ( $channel === null ) {
539  // For unchanneled messages, output trailing newline immediately
540  print "\n";
541  $this->atLineStart = true;
542  }
543  $this->lastChannel = $channel;
544  }
545 
557  public function getDbType() {
558  return self::DB_STD;
559  }
560 
564  protected function addDefaultParams() {
565  # Generic (non-script-dependent) options:
566 
567  $this->addOption( 'help', 'Display this help message', false, false, 'h' );
568  $this->addOption( 'quiet', 'Whether to suppress non-error output', false, false, 'q' );
569 
570  # Save generic options to display them separately in help
571  $generic = [ 'help', 'quiet' ];
572  $this->parameters->assignGroup( self::GENERIC_MAINTENANCE_PARAMETERS, $generic );
573 
574  # Script-dependent options:
575 
576  // If we support a DB, show the options
577  if ( $this->getDbType() > 0 ) {
578  $this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
579  $this->addOption( 'dbpass', 'The password to use for this script', false, true );
580  $this->addOption( 'dbgroupdefault', 'The default DB group to use.', false, true );
581  }
582 
583  # Save additional script-dependent options to display
584  # them separately in help
585  $dependent = array_diff(
586  $this->parameters->getOptionNames(),
587  $generic
588  );
589  $this->parameters->assignGroup( self::SCRIPT_DEPENDENT_PARAMETERS, $dependent );
590  }
591 
597  public function getConfig() {
598  if ( $this->config === null ) {
599  $this->config = MediaWikiServices::getInstance()->getMainConfig();
600  }
601 
602  return $this->config;
603  }
604 
609  public function setConfig( Config $config ) {
610  $this->config = $config;
611  }
612 
622  protected function requireExtension( $name ) {
623  $this->requiredExtensions[] = $name;
624  }
625 
631  public function checkRequiredExtensions() {
632  $registry = ExtensionRegistry::getInstance();
633  $missing = [];
634  foreach ( $this->requiredExtensions as $name ) {
635  if ( !$registry->isLoaded( $name ) ) {
636  $missing[] = $name;
637  }
638  }
639 
640  if ( $missing ) {
641  if ( count( $missing ) === 1 ) {
642  $msg = 'The "' . $missing[ 0 ] . '" extension must be installed for this script to run. '
643  . 'Please enable it and then try again.';
644  } else {
645  $msg = 'The following extensions must be installed for this script to run: "'
646  . implode( '", "', $missing ) . '". Please enable them and then try again.';
647  }
648  $this->fatalError( $msg );
649  }
650  }
651 
661  public function setAgentAndTriggers() {
662  wfDeprecated( __METHOD__, '1.37' );
663  }
664 
672  public function runChild( $maintClass, $classFile = null ) {
673  // Make sure the class is loaded first
674  if ( !class_exists( $maintClass ) ) {
675  if ( $classFile ) {
676  require_once $classFile;
677  }
678  if ( !class_exists( $maintClass ) ) {
679  $this->fatalError( "Cannot spawn child: $maintClass" );
680  }
681  }
682 
686  $child = new $maintClass();
687  $child->loadParamsAndArgs(
688  $this->mSelf,
689  $this->parameters->getOptions(),
690  $this->parameters->getArgs()
691  );
692  if ( $this->mDb !== null ) {
693  $child->setDB( $this->mDb );
694  }
695 
696  return $child;
697  }
698 
702  public function setup() {
703  $this->loadParamsAndArgs();
704  }
705 
713  public function memoryLimit() {
714  return 'max';
715  }
716 
721  protected function adjustMemoryLimit() {
722  wfDeprecated( __METHOD__, '1.39' );
723 
724  if ( $this->parameters->hasOption( 'memory-limit' ) ) {
725  $limit = $this->parameters->getOption( 'memory-limit' );
726  $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood
727  } else {
728  $limit = $this->memoryLimit();
729  }
730 
731  if ( $limit == 'max' ) {
732  $limit = -1; // no memory limit
733  }
734  if ( $limit != 'default' ) {
735  ini_set( 'memory_limit', $limit );
736  }
737  }
738 
742  public function clearParamsAndArgs() {
743  $this->parameters->clear();
744  $this->mInputLoaded = false;
745  }
746 
754  public function loadWithArgv( $argv ) {
755  if ( $this->mDescription ) {
756  $this->parameters->setDescription( $this->mDescription );
757  }
758 
759  $this->parameters->loadWithArgv( $argv );
760 
761  if ( $this->parameters->hasErrors() ) {
762  $errors = "\nERROR: " . implode( "\nERROR: ", $this->parameters->getErrors() ) . "\n";
763  $this->error( $errors );
764  $this->maybeHelp( true );
765  }
766 
767  $this->loadSpecialVars();
768  $this->mInputLoaded = true;
769  }
770 
778  public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
779  # If we were given opts or args, set those and return early
780  if ( $self !== null || $opts !== null || $args !== null ) {
781  if ( $self !== null ) {
782  $this->mSelf = $self;
783  $this->parameters->setName( $self );
784  }
785  $this->parameters->setOptionsAndArgs( $opts ?? [], $args ?? [] );
786  $this->mInputLoaded = true;
787  }
788 
789  # If we've already loaded input (either by user values or from $argv)
790  # skip on loading it again. The array_shift() will corrupt values if
791  # it's run again and again
792  if ( $this->mInputLoaded ) {
793  $this->loadSpecialVars();
794 
795  return;
796  }
797 
798  global $argv;
799  $this->mSelf = $argv[0];
800  $this->parameters->setName( $this->mSelf );
801  $this->loadWithArgv( array_slice( $argv, 1 ) );
802  }
803 
808  public function validateParamsAndArgs() {
809  $valid = $this->parameters->validate();
810 
811  if ( $this->parameters->hasErrors() ) {
812  $errors = "\nERROR: " . implode( "\nERROR: ", $this->parameters->getErrors() ) . "\n";
813  $this->error( $errors );
814  }
815 
816  $this->maybeHelp( !$valid );
817  }
818 
823  protected function loadSpecialVars() {
824  if ( $this->hasOption( 'dbuser' ) ) {
825  $this->mDbUser = $this->getOption( 'dbuser' );
826  }
827  if ( $this->hasOption( 'dbpass' ) ) {
828  $this->mDbPass = $this->getOption( 'dbpass' );
829  }
830  if ( $this->hasOption( 'quiet' ) ) {
831  $this->mQuiet = true;
832  }
833  if ( $this->hasOption( 'batch-size' ) ) {
834  $this->mBatchSize = intval( $this->getOption( 'batch-size' ) );
835  }
836  }
837 
843  protected function maybeHelp( $force = false ) {
844  if ( !$force && !$this->hasOption( 'help' ) ) {
845  return;
846  }
847  $this->showHelp();
848  die( 1 );
849  }
850 
854  protected function showHelp() {
855  $this->mQuiet = false;
856  $help = $this->parameters->getHelp();
857  $this->output( $help );
858  }
859 
867  public function finalSetup( SettingsBuilder $settingsBuilder = null ) {
868  if ( !$settingsBuilder ) {
869  // HACK for backwards compatibility. All subclasses that override
870  // finalSetup() should be updated to pass $settingsBuilder along.
871  // XXX: We don't want the parameter to be nullable! How can we make it required
872  // without breaking backwards compatibility?
873  $settingsBuilder = $GLOBALS['wgSettings'];
874  }
875 
876  $config = $settingsBuilder->getConfig();
877  $overrides = [];
878  $overrides['DBadminuser'] = $config->get( MainConfigNames::DBadminuser );
879  $overrides['DBadminpassword'] = $config->get( MainConfigNames::DBadminpassword );
880 
881  # Turn off output buffering again, it might have been turned on in the settings files
882  if ( ob_get_level() ) {
883  ob_end_flush();
884  }
885  # Same with these
886  $overrides['CommandLineMode'] = true;
887 
888  # Override $wgServer
889  if ( $this->hasOption( 'server' ) ) {
890  $overrides['Server'] = $this->getOption( 'server', $config->get( MainConfigNames::Server ) );
891  }
892 
893  # If these were passed, use them
894  if ( $this->mDbUser ) {
895  $overrides['DBadminuser'] = $this->mDbUser;
896  }
897  if ( $this->mDbPass ) {
898  $overrides['DBadminpassword'] = $this->mDbPass;
899  }
900  if ( $this->hasOption( 'dbgroupdefault' ) ) {
901  $overrides['DBDefaultGroup'] = $this->getOption( 'dbgroupdefault', null );
902  // TODO: once MediaWikiServices::getInstance() starts throwing exceptions
903  // and not deprecation warnings for premature access to service container,
904  // we can remove this line. This method is called before Setup.php,
905  // so it would be guaranteed DBLoadBalancerFactory is not yet initialized.
906  if ( MediaWikiServices::hasInstance() ) {
907  $service = MediaWikiServices::getInstance()->peekService( 'DBLoadBalancerFactory' );
908  if ( $service ) {
909  $service->destroy();
910  }
911  }
912  }
913 
914  if ( $this->getDbType() == self::DB_ADMIN && isset( $overrides[ 'DBadminuser' ] ) ) {
915  $overrides['DBuser'] = $overrides[ 'DBadminuser' ];
916  $overrides['DBpassword'] = $overrides[ 'DBadminpassword' ];
917 
919  $dbServers = $config->get( MainConfigNames::DBservers );
920  if ( $dbServers ) {
921  foreach ( $dbServers as $i => $server ) {
922  $dbServers[$i]['user'] = $overrides['DBuser'];
923  $dbServers[$i]['password'] = $overrides['DBpassword'];
924  }
925  $overrides['DBservers'] = $dbServers;
926  }
927 
928  $lbFactoryConf = $config->get( MainConfigNames::LBFactoryConf );
929  if ( isset( $lbFactoryConf['serverTemplate'] ) ) {
930  $lbFactoryConf['serverTemplate']['user'] = $overrides['DBuser'];
931  $lbFactoryConf['serverTemplate']['password'] = $overrides['DBpassword'];
932  $overrides['LBFactoryConf'] = $lbFactoryConf;
933  }
934 
935  // TODO: once MediaWikiServices::getInstance() starts throwing exceptions
936  // and not deprecation warnings for premature access to service container,
937  // we can remove this line. This method is called before Setup.php,
938  // so it would be guaranteed DBLoadBalancerFactory is not yet initialized.
939  if ( MediaWikiServices::hasInstance() ) {
940  $service = MediaWikiServices::getInstance()->peekService( 'DBLoadBalancerFactory' );
941  if ( $service ) {
942  $service->destroy();
943  }
944  }
945  }
946 
947  $this->afterFinalSetup();
948 
949  $overrides['ShowExceptionDetails'] = true;
950  $overrides['ShowHostname'] = true;
951 
952  $ini = [
953  'max_execution_time' => 0,
954  ];
955 
956  $settingsBuilder->loadArray( [ 'config' => $overrides, 'php-ini' => $ini ] );
957  }
958 
963  protected function afterFinalSetup() {
964  }
965 
971  public function globals() {
972  wfDeprecated( __METHOD__, '1.39' );
973  if ( $this->hasOption( 'globals' ) ) {
974  print_r( $GLOBALS );
975  }
976  }
977 
987  public function shutdown() {
988  $lbFactory = null;
989  if (
990  $this->getDbType() !== self::DB_NONE &&
991  // Service might be disabled, e.g. when running install.php
992  !MediaWikiServices::getInstance()->isServiceDisabled( 'DBLoadBalancerFactory' )
993  ) {
994  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
995  if ( $lbFactory->isReadyForRoundOperations() ) {
996  $lbFactory->commitPrimaryChanges( get_class( $this ) );
997  }
998 
1000  }
1001 
1002  // Handle external profiler outputs
1003  // FIXME: Handle embedded outputs as well, such as ProfilerOutputText (T253547)
1004  $profiler = Profiler::instance();
1005  $profiler->logData();
1006 
1008  MediaWikiServices::getInstance()->getStatsdDataFactory(),
1009  $this->getConfig()
1010  );
1011 
1012  if ( $lbFactory ) {
1013  if ( $lbFactory->isReadyForRoundOperations() ) {
1014  $lbFactory->shutdown( $lbFactory::SHUTDOWN_NO_CHRONPROT );
1015  }
1016  }
1017  }
1018 
1023  public function loadSettings() {
1024  // noop
1025  return MW_CONFIG_FILE;
1026  }
1027 
1033  public function purgeRedundantText( $delete = true ) {
1034  # Data should come off the master, wrapped in a transaction
1035  $dbw = $this->getDB( DB_PRIMARY );
1036  $this->beginTransaction( $dbw, __METHOD__ );
1037 
1038  # Get "active" text records via the content table
1039  $cur = [];
1040  $this->output( 'Searching for active text records via contents table...' );
1041  $res = $dbw->select( 'content', 'content_address', [], __METHOD__, [ 'DISTINCT' ] );
1042  $blobStore = MediaWikiServices::getInstance()->getBlobStore();
1043  foreach ( $res as $row ) {
1044  // @phan-suppress-next-line PhanUndeclaredMethod
1045  $textId = $blobStore->getTextIdFromAddress( $row->content_address );
1046  if ( $textId ) {
1047  $cur[] = $textId;
1048  }
1049  }
1050  $this->output( "done.\n" );
1051 
1052  # Get the IDs of all text records not in these sets
1053  $this->output( 'Searching for inactive text records...' );
1054  $cond = 'old_id NOT IN ( ' . $dbw->makeList( $cur ) . ' )';
1055  $res = $dbw->select( 'text', 'old_id', [ $cond ], __METHOD__, [ 'DISTINCT' ] );
1056  $old = [];
1057  foreach ( $res as $row ) {
1058  $old[] = $row->old_id;
1059  }
1060  $this->output( "done.\n" );
1061 
1062  # Inform the user of what we're going to do
1063  $count = count( $old );
1064  $this->output( "$count inactive items found.\n" );
1065 
1066  # Delete as appropriate
1067  if ( $delete && $count ) {
1068  $this->output( 'Deleting...' );
1069  $dbw->delete( 'text', [ 'old_id' => $old ], __METHOD__ );
1070  $this->output( "done.\n" );
1071  }
1072 
1073  $this->commitTransaction( $dbw, __METHOD__ );
1074  }
1075 
1080  protected function getDir() {
1081  return __DIR__ . '/../';
1082  }
1083 
1098  protected function getDB( $db, $groups = [], $dbDomain = false ) {
1099  if ( $this->mDb === null ) {
1100  return MediaWikiServices::getInstance()
1101  ->getDBLoadBalancerFactory()
1102  ->getMainLB( $dbDomain )
1103  ->getMaintenanceConnectionRef( $db, $groups, $dbDomain );
1104  }
1105 
1106  return $this->mDb;
1107  }
1108 
1115  public function setDB( IMaintainableDatabase $db ) {
1116  $this->mDb = $db;
1117  }
1118 
1129  protected function beginTransaction( IDatabase $dbw, $fname ) {
1130  $dbw->begin( $fname );
1131  }
1132 
1144  protected function commitTransaction( IDatabase $dbw, $fname ) {
1145  $dbw->commit( $fname );
1146  return $this->waitForReplication();
1147  }
1148 
1157  protected function waitForReplication() {
1158  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
1159  $waitSucceeded = $lbFactory->waitForReplication(
1160  [ 'timeout' => 30, 'ifWritesSince' => $this->lastReplicationWait ]
1161  );
1162  $this->lastReplicationWait = microtime( true );
1163 
1164  // If possible, apply changes to the database configuration.
1165  // The primary use case for this is taking replicas out of rotation.
1166  // Long-running scripts may otherwise keep connections to
1167  // de-pooled database hosts, and may even re-connect to them.
1168  // If no config callback was configured, this has no effect.
1169  $lbFactory->autoReconfigure();
1170 
1171  return $waitSucceeded;
1172  }
1173 
1184  protected function rollbackTransaction( IDatabase $dbw, $fname ) {
1185  $dbw->rollback( $fname );
1186  }
1187 
1198  protected function countDown( $seconds ) {
1199  if ( $this->isQuiet() ) {
1200  return;
1201  }
1202  for ( $i = $seconds; $i >= 0; $i-- ) {
1203  if ( $i != $seconds ) {
1204  $this->output( str_repeat( "\x08", strlen( (string)( $i + 1 ) ) ) );
1205  }
1206  $this->output( (string)$i );
1207  if ( $i ) {
1208  sleep( 1 );
1209  }
1210  }
1211  $this->output( "\n" );
1212  }
1213 
1222  public static function posix_isatty( $fd ) {
1223  if ( !function_exists( 'posix_isatty' ) ) {
1224  return !$fd;
1225  }
1226 
1227  return posix_isatty( $fd );
1228  }
1229 
1235  public static function readconsole( $prompt = '> ' ) {
1236  static $isatty = null;
1237  if ( $isatty === null ) {
1238  $isatty = self::posix_isatty( 0 /*STDIN*/ );
1239  }
1240 
1241  if ( $isatty && function_exists( 'readline' ) ) {
1242  return readline( $prompt );
1243  }
1244 
1245  if ( $isatty ) {
1246  $st = self::readlineEmulation( $prompt );
1247  } elseif ( feof( STDIN ) ) {
1248  $st = false;
1249  } else {
1250  $st = fgets( STDIN, 1024 );
1251  }
1252  if ( $st === false ) {
1253  return false;
1254  }
1255 
1256  return trim( $st );
1257  }
1258 
1264  private static function readlineEmulation( $prompt ) {
1265  $bash = ExecutableFinder::findInDefaultPaths( 'bash' );
1266  if ( !wfIsWindows() && $bash ) {
1267  $encPrompt = Shell::escape( $prompt );
1268  $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
1269  $result = Shell::command( $bash, '-c', $command )
1270  ->passStdin()
1271  ->forwardStderr()
1272  ->execute();
1273 
1274  if ( $result->getExitCode() == 0 ) {
1275  return $result->getStdout();
1276  }
1277 
1278  if ( $result->getExitCode() == 127 ) {
1279  // Couldn't execute bash even though we thought we saw it.
1280  // Shell probably spit out an error message, sorry :(
1281  // Fall through to fgets()...
1282  } else {
1283  // EOF/ctrl+D
1284  return false;
1285  }
1286  }
1287 
1288  // Fallback... we'll have no editing controls, EWWW
1289  if ( feof( STDIN ) ) {
1290  return false;
1291  }
1292  print $prompt;
1293 
1294  return fgets( STDIN, 1024 );
1295  }
1296 
1304  public static function getTermSize() {
1305  static $termSize = null;
1306 
1307  if ( $termSize !== null ) {
1308  return $termSize;
1309  }
1310 
1311  $default = [ 80, 50 ];
1312 
1313  if ( wfIsWindows() || Shell::isDisabled() ) {
1314  $termSize = $default;
1315 
1316  return $termSize;
1317  }
1318 
1319  // It's possible to get the screen size with VT-100 terminal escapes,
1320  // but reading the responses is not possible without setting raw mode
1321  // (unless you want to require the user to press enter), and that
1322  // requires an ioctl(), which we can't do. So we have to shell out to
1323  // something that can do the relevant syscalls. There are a few
1324  // options. Linux and Mac OS X both have "stty size" which does the
1325  // job directly.
1326  $result = Shell::command( 'stty', 'size' )->passStdin()->execute();
1327  if ( $result->getExitCode() !== 0 ||
1328  !preg_match( '/^(\d+) (\d+)$/', $result->getStdout(), $m )
1329  ) {
1330  $termSize = $default;
1331 
1332  return $termSize;
1333  }
1334 
1335  $termSize = [ intval( $m[2] ), intval( $m[1] ) ];
1336 
1337  return $termSize;
1338  }
1339 
1344  public static function requireTestsAutoloader() {
1345  require_once __DIR__ . '/../../tests/common/TestsAutoLoader.php';
1346  }
1347 
1354  protected function getHookContainer() {
1355  if ( !$this->hookContainer ) {
1356  $this->hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1357  }
1358  return $this->hookContainer;
1359  }
1360 
1369  protected function getHookRunner() {
1370  if ( !$this->hookRunner ) {
1371  $this->hookRunner = new HookRunner( $this->getHookContainer() );
1372  }
1373  return $this->hookRunner;
1374  }
1375 
1386  protected function parseIntList( $text ) {
1387  $ids = preg_split( '/[\s,;:|]+/', $text );
1388  $ids = array_map(
1389  static function ( $id ) {
1390  return (int)$id;
1391  },
1392  $ids
1393  );
1394  return array_filter( $ids );
1395  }
1396 
1405  protected function validateUserOption( $errorMsg ) {
1406  if ( $this->hasOption( "user" ) ) {
1407  $user = User::newFromName( $this->getOption( 'user' ) );
1408  } elseif ( $this->hasOption( "userid" ) ) {
1409  $user = User::newFromId( $this->getOption( 'userid' ) );
1410  } else {
1411  $this->fatalError( $errorMsg );
1412  }
1413  if ( !$user || !$user->isRegistered() ) {
1414  if ( $this->hasOption( "user" ) ) {
1415  $this->fatalError( "No such user: " . $this->getOption( 'user' ) );
1416  } elseif ( $this->hasOption( "userid" ) ) {
1417  $this->fatalError( "No such user id: " . $this->getOption( 'userid' ) );
1418  }
1419  }
1420 
1421  return $user;
1422  }
1423 }
getDB()
wfIsWindows()
Check if the operating system is Windows.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
string false $maintClass
Definition: Maintenance.php:57
static doUpdates( $unused=null, $stage=self::ALL)
Consume and execute all pending updates.
static findInDefaultPaths( $names, $versionInfo=false)
Same as locateExecutable(), but checks in getPossibleBinPaths() by default.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: Maintenance.php:66
getDB( $db, $groups=[], $dbDomain=false)
Returns a database to be used by current maintenance script.
setup()
Do some checking and basic setup.
array[] $mParams
Empty.
Definition: Maintenance.php:93
__construct()
Default constructor.
error( $err, $die=0)
Throw an error to the user.
const STDIN_ALL
Definition: Maintenance.php:76
getName()
Get the script's name.
addArg( $arg, $description, $required=true)
Add some args that are needed.
setArg( $argId, $value)
Programmatically set the value of the given argument.
requireExtension( $name)
Indicate that the specified extension must be loaded before the script can run.
showHelp()
Definitely show the help.
MaintenanceParameters $parameters
Definition: Maintenance.php:85
int null $mBatchSize
Batch size.
setAgentAndTriggers()
This method used to be for internal use by doMaintenance.php to apply some optional global state to L...
setAllowUnregisteredOptions( $allow)
Sets whether to allow unregistered options, which are options passed to a script that do not match an...
beginTransaction(IDatabase $dbw, $fname)
Begin a transaction on a DB.
static getTermSize()
Get the terminal size as a two-element array where the first element is the width (number of columns)...
clearParamsAndArgs()
Clear all params and arguments.
const DB_NONE
Constants for DB access type.
Definition: Maintenance.php:71
commitTransaction(IDatabase $dbw, $fname)
Commit the transaction on a DB handle and wait for replica DBs to catch up.
output( $out, $channel=null)
Throw some output to the user.
supportsOption( $name)
Checks to see if a particular option in supported.
getStdin( $len=null)
Return input from stdin.
const DB_STD
Definition: Maintenance.php:72
cleanupChanneled()
Clean up channeled output.
memoryLimit()
Normally we disable the memory_limit when running admin scripts.
array $mArgList
Empty.
getHookRunner()
Get a HookRunner for running core hooks.
afterFinalSetup()
Override to perform any required operation at the end of initialisation.
finalSetup(SettingsBuilder $settingsBuilder=null)
Handle some last-minute setup here.
hasArg( $argId=0)
Does a given argument exist?
const GENERIC_MAINTENANCE_PARAMETERS
Definition: Maintenance.php:80
const DB_ADMIN
Definition: Maintenance.php:73
getDir()
Get the maintenance directory.
addDefaultParams()
Add the default parameters to the scripts.
bool $mInputLoaded
Have we already loaded our user input?
deleteOption( $name)
Remove an option.
static requireTestsAutoloader()
Call this to set up the autoloader to allow classes to be used from the tests directory.
loadParamsAndArgs( $self=null, $opts=null, $args=null)
Process command line arguments.
waitForReplication()
Wait for replica DBs to catch up.
outputChanneled( $msg, $channel=null)
Message outputter with channeled message support.
resource null $fileHandle
Used when creating separate schema files.
loadSpecialVars()
Handle the special variables that are global to all scripts.
setOption(string $name, $value)
Programmatically set the value of the given option.
setDB(IMaintainableDatabase $db)
Sets database object to be returned by getDB().
array $orderedOptions
Used to read the options in the order they were passed.
hasOption( $name)
Checks to see if a particular option was set.
purgeRedundantText( $delete=true)
Support function for cleaning up redundant text records.
countDown( $seconds)
Count down from $seconds to zero on the terminal, with a one-second pause between showing each number...
runChild( $maintClass, $classFile=null)
Run a child maintenance script.
array $mOptions
This is the list of options that were actually passed.
execute()
Do the actual work.
static readconsole( $prompt='> ')
Prompt the console for input.
static posix_isatty( $fd)
Wrapper for posix_isatty() We default as considering stdin a tty (for nice readline methods) but trea...
const SCRIPT_DEPENDENT_PARAMETERS
Definition: Maintenance.php:79
adjustMemoryLimit()
Adjusts PHP's memory limit to better suit our needs, if needed.
validateParamsAndArgs()
Run some validation checks on the params, etc.
getHookContainer()
Get a HookContainer, for running extension hooks or for hook metadata.
validateUserOption( $errorMsg)
getDbType()
Does the script need different DB access? By default, we give Maintenance scripts normal rights to th...
getBatchSize()
Returns batch size.
bool $mQuiet
Special vars for params that are always used.
parseIntList( $text)
Utility function to parse a string (perhaps from a command line option) into a list of integers (perh...
getArg( $argId=0, $default=null)
Get an argument.
addDescription( $text)
Set the description text.
shutdown()
Call before exiting CLI process for the last DB commit, and flush any remaining buffers and other def...
maybeHelp( $force=false)
Maybe show the help.
loadWithArgv( $argv)
Load params and arguments from a given array of command-line arguments.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
string null $mSelf
Name of the script currently running.
static shouldExecute()
getOption( $name, $default=null)
Get an option, or return the default.
checkRequiredExtensions()
Verify that the required extensions are installed.
rollbackTransaction(IDatabase $dbw, $fname)
Rollback the transaction on a DB handle.
string $mDescription
A description of the script, children should change this via addDescription()
globals()
Potentially debug globals.
setConfig(Config $config)
setBatchSize( $s=0)
array $mArgs
This is the list of arguments that were actually passed.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:560
A class containing constants representing the names of configuration variables.
Command line parameter handling for maintenance scripts.
A runner for maintenance scripts.
Service locator for MediaWiki core services.
Utility for loading settings files.
Executes shell commands.
Definition: Shell.php:46
static emitBufferedStatsdData(IBufferingStatsdDataFactory $stats, Config $config)
Send out any buffered statsd data according to sampling rules.
Definition: MediaWiki.php:1162
static instance()
Singleton.
Definition: Profiler.php:69
static newFromName( $name, $validate='valid')
Definition: User.php:598
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:639
$self
Interface for configuration instances.
Definition: Config.php:30
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:39
rollback( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Rollback a transaction previously started using begin()
commit( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Commits a transaction previously started using begin()
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
Advanced database interface for IDatabase handles that include maintenance methods.
$command
Definition: mcc.php:125
$help
Definition: mcc.php:32
if( $line===false) $args
Definition: mcc.php:124
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
const DB_PRIMARY
Definition: defines.php:28