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 
329  protected function hasArg( $argId = 0 ) {
330  return $this->parameters->hasArg( $argId );
331  }
332 
341  protected function getArg( $argId = 0, $default = null ) {
342  return $this->parameters->getArg( $argId, $default );
343  }
344 
354  public function setOption( string $name, $value ): void {
355  $this->parameters->setOption( $name, $value );
356  }
357 
367  public function setArg( $argId, $value ): void {
368  $this->parameters->setArg( $argId, $value );
369  }
370 
378  protected function getBatchSize() {
379  return $this->mBatchSize;
380  }
381 
385  protected function setBatchSize( $s = 0 ) {
386  $this->mBatchSize = $s;
387 
388  // If we support $mBatchSize, show the option.
389  // Used to be in addDefaultParams, but in order for that to
390  // work, subclasses would have to call this function in the constructor
391  // before they called parent::__construct which is just weird
392  // (and really wasn't done).
393  if ( $this->mBatchSize ) {
394  $this->addOption( 'batch-size', 'Run this many operations ' .
395  'per batch, default: ' . $this->mBatchSize, false, true );
396  if ( $this->supportsOption( 'batch-size' ) ) {
397  // This seems a little ugly...
398  $this->parameters->assignGroup( self::SCRIPT_DEPENDENT_PARAMETERS, [ 'batch-size' ] );
399  }
400  }
401  }
402 
407  public function getName() {
408  return $this->mSelf;
409  }
410 
417  protected function getStdin( $len = null ) {
418  if ( $len == self::STDIN_ALL ) {
419  return file_get_contents( 'php://stdin' );
420  }
421  $f = fopen( 'php://stdin', 'rt' );
422  if ( !$len ) {
423  return $f;
424  }
425  $input = fgets( $f, $len );
426  fclose( $f );
427 
428  return rtrim( $input );
429  }
430 
434  public function isQuiet() {
435  return $this->mQuiet;
436  }
437 
445  protected function output( $out, $channel = null ) {
446  // This is sometimes called very early, before Setup.php is included.
447  if ( class_exists( MediaWikiServices::class ) ) {
448  // Flush stats periodically in long-running CLI scripts to avoid OOM (T181385)
449  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
450  if ( $stats->getDataCount() > 1000 ) {
451  MediaWiki::emitBufferedStatsdData( $stats, $this->getConfig() );
452  }
453  }
454 
455  if ( $this->mQuiet ) {
456  return;
457  }
458  if ( $channel === null ) {
459  $this->cleanupChanneled();
460  print $out;
461  } else {
462  $out = preg_replace( '/\n\z/', '', $out );
463  $this->outputChanneled( $out, $channel );
464  }
465  }
466 
474  protected function error( $err, $die = 0 ) {
475  if ( intval( $die ) !== 0 ) {
476  wfDeprecated( __METHOD__ . '( $err, $die )', '1.31' );
477  $this->fatalError( $err, intval( $die ) );
478  }
479  $this->outputChanneled( false );
480  if (
481  ( PHP_SAPI == 'cli' || PHP_SAPI == 'phpdbg' ) &&
482  !defined( 'MW_PHPUNIT_TEST' )
483  ) {
484  fwrite( STDERR, $err . "\n" );
485  } else {
486  print $err;
487  }
488  }
489 
499  protected function fatalError( $msg, $exitCode = 1 ) {
500  $this->error( $msg );
501  exit( $exitCode );
502  }
503 
504  private $atLineStart = true;
505  private $lastChannel = null;
506 
510  public function cleanupChanneled() {
511  if ( !$this->atLineStart ) {
512  print "\n";
513  $this->atLineStart = true;
514  }
515  }
516 
525  public function outputChanneled( $msg, $channel = null ) {
526  if ( $msg === false ) {
527  $this->cleanupChanneled();
528 
529  return;
530  }
531 
532  // End the current line if necessary
533  if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
534  print "\n";
535  }
536 
537  print $msg;
538 
539  $this->atLineStart = false;
540  if ( $channel === null ) {
541  // For unchanneled messages, output trailing newline immediately
542  print "\n";
543  $this->atLineStart = true;
544  }
545  $this->lastChannel = $channel;
546  }
547 
559  public function getDbType() {
560  return self::DB_STD;
561  }
562 
566  protected function addDefaultParams() {
567  # Generic (non-script-dependent) options:
568 
569  $this->addOption( 'help', 'Display this help message', false, false, 'h' );
570  $this->addOption( 'quiet', 'Whether to suppress non-error output', false, false, 'q' );
571 
572  # Save generic options to display them separately in help
573  $generic = [ 'help', 'quiet' ];
574  $this->parameters->assignGroup( self::GENERIC_MAINTENANCE_PARAMETERS, $generic );
575 
576  # Script-dependent options:
577 
578  // If we support a DB, show the options
579  if ( $this->getDbType() > 0 ) {
580  $this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
581  $this->addOption( 'dbpass', 'The password to use for this script', false, true );
582  $this->addOption( 'dbgroupdefault', 'The default DB group to use.', false, true );
583  }
584 
585  # Save additional script-dependent options to display
586  # them separately in help
587  $dependent = array_diff(
588  $this->parameters->getOptionNames(),
589  $generic
590  );
591  $this->parameters->assignGroup( self::SCRIPT_DEPENDENT_PARAMETERS, $dependent );
592  }
593 
599  public function getConfig() {
600  if ( $this->config === null ) {
601  $this->config = MediaWikiServices::getInstance()->getMainConfig();
602  }
603 
604  return $this->config;
605  }
606 
611  public function setConfig( Config $config ) {
612  $this->config = $config;
613  }
614 
624  protected function requireExtension( $name ) {
625  $this->requiredExtensions[] = $name;
626  }
627 
633  public function checkRequiredExtensions() {
634  $registry = ExtensionRegistry::getInstance();
635  $missing = [];
636  foreach ( $this->requiredExtensions as $name ) {
637  if ( !$registry->isLoaded( $name ) ) {
638  $missing[] = $name;
639  }
640  }
641 
642  if ( $missing ) {
643  if ( count( $missing ) === 1 ) {
644  $msg = 'The "' . $missing[ 0 ] . '" extension must be installed for this script to run. '
645  . 'Please enable it and then try again.';
646  } else {
647  $msg = 'The following extensions must be installed for this script to run: "'
648  . implode( '", "', $missing ) . '". Please enable them and then try again.';
649  }
650  $this->fatalError( $msg );
651  }
652  }
653 
663  public function setAgentAndTriggers() {
664  wfDeprecated( __METHOD__, '1.37' );
665  }
666 
674  public function runChild( $maintClass, $classFile = null ) {
675  // Make sure the class is loaded first
676  if ( !class_exists( $maintClass ) ) {
677  if ( $classFile ) {
678  require_once $classFile;
679  }
680  if ( !class_exists( $maintClass ) ) {
681  $this->fatalError( "Cannot spawn child: $maintClass" );
682  }
683  }
684 
688  $child = new $maintClass();
689  $child->loadParamsAndArgs(
690  $this->mSelf,
691  $this->parameters->getOptions(),
692  $this->parameters->getArgs()
693  );
694  if ( $this->mDb !== null ) {
695  $child->setDB( $this->mDb );
696  }
697 
698  return $child;
699  }
700 
704  public function setup() {
705  $this->loadParamsAndArgs();
706  }
707 
715  public function memoryLimit() {
716  return 'max';
717  }
718 
723  protected function adjustMemoryLimit() {
724  wfDeprecated( __METHOD__, '1.39' );
725 
726  if ( $this->parameters->hasOption( 'memory-limit' ) ) {
727  $limit = $this->parameters->getOption( 'memory-limit' );
728  $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood
729  } else {
730  $limit = $this->memoryLimit();
731  }
732 
733  if ( $limit == 'max' ) {
734  $limit = -1; // no memory limit
735  }
736  if ( $limit != 'default' ) {
737  ini_set( 'memory_limit', $limit );
738  }
739  }
740 
744  public function clearParamsAndArgs() {
745  $this->parameters->clear();
746  $this->mInputLoaded = false;
747  }
748 
756  public function loadWithArgv( $argv ) {
757  if ( $this->mDescription ) {
758  $this->parameters->setDescription( $this->mDescription );
759  }
760 
761  $this->parameters->loadWithArgv( $argv );
762 
763  if ( $this->parameters->hasErrors() ) {
764  $errors = "\nERROR: " . implode( "\nERROR: ", $this->parameters->getErrors() ) . "\n";
765  $this->error( $errors );
766  $this->maybeHelp( true );
767  }
768 
769  $this->loadSpecialVars();
770  $this->mInputLoaded = true;
771  }
772 
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 !== null || $opts !== null || $args !== null ) {
783  if ( $self !== null ) {
784  $this->mSelf = $self;
785  $this->parameters->setName( $self );
786  }
787  $this->parameters->setOptionsAndArgs( $opts ?? [], $args ?? [] );
788  $this->mInputLoaded = true;
789  }
790 
791  # If we've already loaded input (either by user values or from $argv)
792  # skip on loading it again. The array_shift() will corrupt values if
793  # it's run again and again
794  if ( $this->mInputLoaded ) {
795  $this->loadSpecialVars();
796 
797  return;
798  }
799 
800  global $argv;
801  $this->mSelf = $argv[0];
802  $this->parameters->setName( $this->mSelf );
803  $this->loadWithArgv( array_slice( $argv, 1 ) );
804  }
805 
810  public function validateParamsAndArgs() {
811  $valid = $this->parameters->validate();
812 
813  if ( $this->parameters->hasErrors() ) {
814  $errors = "\nERROR: " . implode( "\nERROR: ", $this->parameters->getErrors() ) . "\n";
815  $this->error( $errors );
816  }
817 
818  $this->maybeHelp( !$valid );
819  }
820 
825  protected function loadSpecialVars() {
826  if ( $this->hasOption( 'dbuser' ) ) {
827  $this->mDbUser = $this->getOption( 'dbuser' );
828  }
829  if ( $this->hasOption( 'dbpass' ) ) {
830  $this->mDbPass = $this->getOption( 'dbpass' );
831  }
832  if ( $this->hasOption( 'quiet' ) ) {
833  $this->mQuiet = true;
834  }
835  if ( $this->hasOption( 'batch-size' ) ) {
836  $this->mBatchSize = intval( $this->getOption( 'batch-size' ) );
837  }
838  }
839 
845  protected function maybeHelp( $force = false ) {
846  if ( !$force && !$this->hasOption( 'help' ) ) {
847  return;
848  }
849  $this->showHelp();
850  die( 1 );
851  }
852 
856  protected function showHelp() {
857  $this->mQuiet = false;
858  $help = $this->parameters->getHelp();
859  $this->output( $help );
860  }
861 
869  public function finalSetup( SettingsBuilder $settingsBuilder = null ) {
870  if ( !$settingsBuilder ) {
871  // HACK for backwards compatibility. All subclasses that override
872  // finalSetup() should be updated to pass $settingsBuilder along.
873  // XXX: We don't want the parameter to be nullable! How can we make it required
874  // without breaking backwards compatibility?
875  $settingsBuilder = $GLOBALS['wgSettings'];
876  }
877 
878  $config = $settingsBuilder->getConfig();
879  $overrides = [];
880  $overrides['DBadminuser'] = $config->get( MainConfigNames::DBadminuser );
881  $overrides['DBadminpassword'] = $config->get( MainConfigNames::DBadminpassword );
882 
883  # Turn off output buffering again, it might have been turned on in the settings files
884  if ( ob_get_level() ) {
885  ob_end_flush();
886  }
887  # Same with these
888  $overrides['CommandLineMode'] = true;
889 
890  # Override $wgServer
891  if ( $this->hasOption( 'server' ) ) {
892  $overrides['Server'] = $this->getOption( 'server', $config->get( MainConfigNames::Server ) );
893  }
894 
895  # If these were passed, use them
896  if ( $this->mDbUser ) {
897  $overrides['DBadminuser'] = $this->mDbUser;
898  }
899  if ( $this->mDbPass ) {
900  $overrides['DBadminpassword'] = $this->mDbPass;
901  }
902  if ( $this->hasOption( 'dbgroupdefault' ) ) {
903  $overrides['DBDefaultGroup'] = $this->getOption( 'dbgroupdefault', null );
904  // TODO: once MediaWikiServices::getInstance() starts throwing exceptions
905  // and not deprecation warnings for premature access to service container,
906  // we can remove this line. This method is called before Setup.php,
907  // so it would be guaranteed DBLoadBalancerFactory is not yet initialized.
908  if ( MediaWikiServices::hasInstance() ) {
909  $service = MediaWikiServices::getInstance()->peekService( 'DBLoadBalancerFactory' );
910  if ( $service ) {
911  $service->destroy();
912  }
913  }
914  }
915 
916  if ( $this->getDbType() == self::DB_ADMIN && isset( $overrides[ 'DBadminuser' ] ) ) {
917  $overrides['DBuser'] = $overrides[ 'DBadminuser' ];
918  $overrides['DBpassword'] = $overrides[ 'DBadminpassword' ];
919 
921  $dbServers = $config->get( MainConfigNames::DBservers );
922  if ( $dbServers ) {
923  foreach ( $dbServers as $i => $server ) {
924  $dbServers[$i]['user'] = $overrides['DBuser'];
925  $dbServers[$i]['password'] = $overrides['DBpassword'];
926  }
927  $overrides['DBservers'] = $dbServers;
928  }
929 
930  $lbFactoryConf = $config->get( MainConfigNames::LBFactoryConf );
931  if ( isset( $lbFactoryConf['serverTemplate'] ) ) {
932  $lbFactoryConf['serverTemplate']['user'] = $overrides['DBuser'];
933  $lbFactoryConf['serverTemplate']['password'] = $overrides['DBpassword'];
934  $overrides['LBFactoryConf'] = $lbFactoryConf;
935  }
936 
937  // TODO: once MediaWikiServices::getInstance() starts throwing exceptions
938  // and not deprecation warnings for premature access to service container,
939  // we can remove this line. This method is called before Setup.php,
940  // so it would be guaranteed DBLoadBalancerFactory is not yet initialized.
941  if ( MediaWikiServices::hasInstance() ) {
942  $service = MediaWikiServices::getInstance()->peekService( 'DBLoadBalancerFactory' );
943  if ( $service ) {
944  $service->destroy();
945  }
946  }
947  }
948 
949  $this->afterFinalSetup();
950 
951  $overrides['ShowExceptionDetails'] = true;
952  $overrides['ShowHostname'] = true;
953 
954  $ini = [
955  'max_execution_time' => 0,
956  ];
957 
958  $settingsBuilder->loadArray( [ 'config' => $overrides, 'php-ini' => $ini ] );
959  }
960 
965  protected function afterFinalSetup() {
966  }
967 
973  public function globals() {
974  wfDeprecated( __METHOD__, '1.39' );
975  if ( $this->hasOption( 'globals' ) ) {
976  print_r( $GLOBALS );
977  }
978  }
979 
989  public function shutdown() {
990  $lbFactory = null;
991  if (
992  $this->getDbType() !== self::DB_NONE &&
993  // Service might be disabled, e.g. when running install.php
994  !MediaWikiServices::getInstance()->isServiceDisabled( 'DBLoadBalancerFactory' )
995  ) {
996  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
997  if ( $lbFactory->isReadyForRoundOperations() ) {
998  $lbFactory->commitPrimaryChanges( get_class( $this ) );
999  }
1000 
1002  }
1003 
1004  // Handle profiler outputs
1005  // NOTE: MaintenanceRunner ensures Profiler::setAllowOutput() during setup
1006  $profiler = Profiler::instance();
1007  $profiler->logData();
1008  $profiler->logDataPageOutputOnly();
1009 
1011  MediaWikiServices::getInstance()->getStatsdDataFactory(),
1012  $this->getConfig()
1013  );
1014 
1015  if ( $lbFactory ) {
1016  if ( $lbFactory->isReadyForRoundOperations() ) {
1017  $lbFactory->shutdown( $lbFactory::SHUTDOWN_NO_CHRONPROT );
1018  }
1019  }
1020  }
1021 
1026  public function loadSettings() {
1027  // noop
1028  return MW_CONFIG_FILE;
1029  }
1030 
1036  public function purgeRedundantText( $delete = true ) {
1037  # Data should come off the master, wrapped in a transaction
1038  $dbw = $this->getDB( DB_PRIMARY );
1039  $this->beginTransaction( $dbw, __METHOD__ );
1040 
1041  # Get "active" text records via the content table
1042  $cur = [];
1043  $this->output( 'Searching for active text records via contents table...' );
1044  $res = $dbw->select( 'content', 'content_address', [], __METHOD__, [ 'DISTINCT' ] );
1045  $blobStore = MediaWikiServices::getInstance()->getBlobStore();
1046  foreach ( $res as $row ) {
1047  // @phan-suppress-next-line PhanUndeclaredMethod
1048  $textId = $blobStore->getTextIdFromAddress( $row->content_address );
1049  if ( $textId ) {
1050  $cur[] = $textId;
1051  }
1052  }
1053  $this->output( "done.\n" );
1054 
1055  # Get the IDs of all text records not in these sets
1056  $this->output( 'Searching for inactive text records...' );
1057  $cond = 'old_id NOT IN ( ' . $dbw->makeList( $cur ) . ' )';
1058  $res = $dbw->select( 'text', 'old_id', [ $cond ], __METHOD__, [ 'DISTINCT' ] );
1059  $old = [];
1060  foreach ( $res as $row ) {
1061  $old[] = $row->old_id;
1062  }
1063  $this->output( "done.\n" );
1064 
1065  # Inform the user of what we're going to do
1066  $count = count( $old );
1067  $this->output( "$count inactive items found.\n" );
1068 
1069  # Delete as appropriate
1070  if ( $delete && $count ) {
1071  $this->output( 'Deleting...' );
1072  $dbw->delete( 'text', [ 'old_id' => $old ], __METHOD__ );
1073  $this->output( "done.\n" );
1074  }
1075 
1076  $this->commitTransaction( $dbw, __METHOD__ );
1077  }
1078 
1083  protected function getDir() {
1084  return __DIR__ . '/../';
1085  }
1086 
1101  protected function getDB( $db, $groups = [], $dbDomain = false ) {
1102  if ( $this->mDb === null ) {
1103  return MediaWikiServices::getInstance()
1104  ->getDBLoadBalancerFactory()
1105  ->getMainLB( $dbDomain )
1106  ->getMaintenanceConnectionRef( $db, $groups, $dbDomain );
1107  }
1108 
1109  return $this->mDb;
1110  }
1111 
1118  public function setDB( IMaintainableDatabase $db ) {
1119  $this->mDb = $db;
1120  }
1121 
1132  protected function beginTransaction( IDatabase $dbw, $fname ) {
1133  $dbw->begin( $fname );
1134  }
1135 
1147  protected function commitTransaction( IDatabase $dbw, $fname ) {
1148  $dbw->commit( $fname );
1149  return $this->waitForReplication();
1150  }
1151 
1160  protected function waitForReplication() {
1161  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
1162  $waitSucceeded = $lbFactory->waitForReplication(
1163  [ 'timeout' => 30, 'ifWritesSince' => $this->lastReplicationWait ]
1164  );
1165  $this->lastReplicationWait = microtime( true );
1166 
1167  // If possible, apply changes to the database configuration.
1168  // The primary use case for this is taking replicas out of rotation.
1169  // Long-running scripts may otherwise keep connections to
1170  // de-pooled database hosts, and may even re-connect to them.
1171  // If no config callback was configured, this has no effect.
1172  $lbFactory->autoReconfigure();
1173 
1174  return $waitSucceeded;
1175  }
1176 
1187  protected function rollbackTransaction( IDatabase $dbw, $fname ) {
1188  $dbw->rollback( $fname );
1189  }
1190 
1201  protected function countDown( $seconds ) {
1202  if ( $this->isQuiet() ) {
1203  return;
1204  }
1205  for ( $i = $seconds; $i >= 0; $i-- ) {
1206  if ( $i != $seconds ) {
1207  $this->output( str_repeat( "\x08", strlen( (string)( $i + 1 ) ) ) );
1208  }
1209  $this->output( (string)$i );
1210  if ( $i ) {
1211  sleep( 1 );
1212  }
1213  }
1214  $this->output( "\n" );
1215  }
1216 
1225  public static function posix_isatty( $fd ) {
1226  if ( !function_exists( 'posix_isatty' ) ) {
1227  return !$fd;
1228  }
1229 
1230  return posix_isatty( $fd );
1231  }
1232 
1238  public static function readconsole( $prompt = '> ' ) {
1239  static $isatty = null;
1240  if ( $isatty === null ) {
1241  $isatty = self::posix_isatty( 0 /*STDIN*/ );
1242  }
1243 
1244  if ( $isatty && function_exists( 'readline' ) ) {
1245  return readline( $prompt );
1246  }
1247 
1248  if ( $isatty ) {
1249  $st = self::readlineEmulation( $prompt );
1250  } elseif ( feof( STDIN ) ) {
1251  $st = false;
1252  } else {
1253  $st = fgets( STDIN, 1024 );
1254  }
1255  if ( $st === false ) {
1256  return false;
1257  }
1258 
1259  return trim( $st );
1260  }
1261 
1267  private static function readlineEmulation( $prompt ) {
1268  $bash = ExecutableFinder::findInDefaultPaths( 'bash' );
1269  if ( !wfIsWindows() && $bash ) {
1270  $encPrompt = Shell::escape( $prompt );
1271  $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
1272  $result = Shell::command( $bash, '-c', $command )
1273  ->passStdin()
1274  ->forwardStderr()
1275  ->execute();
1276 
1277  if ( $result->getExitCode() == 0 ) {
1278  return $result->getStdout();
1279  }
1280 
1281  if ( $result->getExitCode() == 127 ) {
1282  // Couldn't execute bash even though we thought we saw it.
1283  // Shell probably spit out an error message, sorry :(
1284  // Fall through to fgets()...
1285  } else {
1286  // EOF/ctrl+D
1287  return false;
1288  }
1289  }
1290 
1291  // Fallback... we'll have no editing controls, EWWW
1292  if ( feof( STDIN ) ) {
1293  return false;
1294  }
1295  print $prompt;
1296 
1297  return fgets( STDIN, 1024 );
1298  }
1299 
1307  public static function getTermSize() {
1308  static $termSize = null;
1309 
1310  if ( $termSize !== null ) {
1311  return $termSize;
1312  }
1313 
1314  $default = [ 80, 50 ];
1315 
1316  if ( wfIsWindows() || Shell::isDisabled() ) {
1317  $termSize = $default;
1318 
1319  return $termSize;
1320  }
1321 
1322  // It's possible to get the screen size with VT-100 terminal escapes,
1323  // but reading the responses is not possible without setting raw mode
1324  // (unless you want to require the user to press enter), and that
1325  // requires an ioctl(), which we can't do. So we have to shell out to
1326  // something that can do the relevant syscalls. There are a few
1327  // options. Linux and Mac OS X both have "stty size" which does the
1328  // job directly.
1329  $result = Shell::command( 'stty', 'size' )->passStdin()->execute();
1330  if ( $result->getExitCode() !== 0 ||
1331  !preg_match( '/^(\d+) (\d+)$/', $result->getStdout(), $m )
1332  ) {
1333  $termSize = $default;
1334 
1335  return $termSize;
1336  }
1337 
1338  $termSize = [ intval( $m[2] ), intval( $m[1] ) ];
1339 
1340  return $termSize;
1341  }
1342 
1347  public static function requireTestsAutoloader() {
1348  require_once __DIR__ . '/../../tests/common/TestsAutoLoader.php';
1349  }
1350 
1357  protected function getHookContainer() {
1358  if ( !$this->hookContainer ) {
1359  $this->hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1360  }
1361  return $this->hookContainer;
1362  }
1363 
1372  protected function getHookRunner() {
1373  if ( !$this->hookRunner ) {
1374  $this->hookRunner = new HookRunner( $this->getHookContainer() );
1375  }
1376  return $this->hookRunner;
1377  }
1378 
1389  protected function parseIntList( $text ) {
1390  $ids = preg_split( '/[\s,;:|]+/', $text );
1391  $ids = array_map(
1392  static function ( $id ) {
1393  return (int)$id;
1394  },
1395  $ids
1396  );
1397  return array_filter( $ids );
1398  }
1399 
1408  protected function validateUserOption( $errorMsg ) {
1409  if ( $this->hasOption( "user" ) ) {
1410  $user = User::newFromName( $this->getOption( 'user' ) );
1411  } elseif ( $this->hasOption( "userid" ) ) {
1412  $user = User::newFromId( $this->getOption( 'userid' ) );
1413  } else {
1414  $this->fatalError( $errorMsg );
1415  }
1416  if ( !$user || !$user->isRegistered() ) {
1417  if ( $this->hasOption( "user" ) ) {
1418  $this->fatalError( "No such user: " . $this->getOption( 'user' ) );
1419  } elseif ( $this->hasOption( "userid" ) ) {
1420  $this->fatalError( "No such user id: " . $this->getOption( 'userid' ) );
1421  }
1422  }
1423 
1424  return $user;
1425  }
1426 }
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:1150
static instance()
Singleton.
Definition: Profiler.php:94
static newFromName( $name, $validate='valid')
Definition: User.php:587
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:628
$self
Interface for configuration instances.
Definition: Config.php:30
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:40
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