MediaWiki REL1_39
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?
34require_once __DIR__ . '/MaintenanceParameters.php';
35
66abstract 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
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
999 DeferredUpdates::doUpdates();
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
1007 MediaWiki::emitBufferedStatsdData(
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.
$maintClass
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
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.
__construct()
Default constructor.
error( $err, $die=0)
Throw an error to the user.
const STDIN_ALL
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
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.
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.
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
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
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...
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 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
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:69
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