34require_once __DIR__ .
'/MaintenanceParameters.php';
148 private $lastReplicationWait = 0.0;
157 private $hookContainer;
173 private $requiredExtensions = [];
197 $this->mOptions =& $this->parameters->getFieldReference(
'mOptions' );
198 $this->orderedOptions =& $this->parameters->getFieldReference(
'optionsSequence' );
199 $this->mArgs =& $this->parameters->getFieldReference(
'mArgs' );
216 return MaintenanceRunner::shouldExecute();
236 return $this->parameters->supportsOption( $name );
250 protected function addOption( $name, $description, $required =
false,
251 $withArg =
false, $shortName =
false, $multiOccurrence =
false
253 $this->parameters->addOption(
270 return $this->parameters->hasOption( $name );
284 protected function getOption( $name, $default =
null ) {
285 return $this->parameters->getOption( $name, $default );
294 protected function addArg( $arg, $description, $required =
true ) {
295 $this->parameters->addArg( $arg, $description, $required );
303 $this->parameters->deleteOption( $name );
312 $this->parameters->setAllowUnregisteredOptions( $allow );
320 $this->parameters->setDescription( $text );
328 protected function hasArg( $argId = 0 ) {
329 return $this->parameters->hasArg( $argId );
339 protected function getArg( $argId = 0, $default =
null ) {
340 return $this->parameters->getArg( $argId, $default );
352 public function setOption(
string $name, $value ): void {
353 $this->parameters->
setOption( $name, $value );
365 public function setArg( $argId, $value ): void {
366 $this->parameters->setArg( $argId, $value );
377 return $this->mBatchSize;
384 $this->mBatchSize =
$s;
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' ) ) {
396 $this->parameters->assignGroup( self::SCRIPT_DEPENDENT_PARAMETERS, [
'batch-size' ] );
416 if ( $len == self::STDIN_ALL ) {
417 return file_get_contents(
'php://stdin' );
419 $f = fopen(
'php://stdin',
'rt' );
423 $input = fgets( $f, $len );
426 return rtrim( $input );
433 return $this->mQuiet;
443 protected function output( $out, $channel =
null ) {
445 if ( class_exists( MediaWikiServices::class ) ) {
447 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
448 if ( $stats->getDataCount() > 1000 ) {
449 MediaWiki::emitBufferedStatsdData( $stats, $this->getConfig() );
453 if ( $this->mQuiet ) {
456 if ( $channel ===
null ) {
457 $this->cleanupChanneled();
460 $out = preg_replace(
'/\n\z/',
'', $out );
461 $this->outputChanneled( $out, $channel );
472 protected function error( $err, $die = 0 ) {
473 if ( intval( $die ) !== 0 ) {
475 $this->fatalError( $err, intval( $die ) );
477 $this->outputChanneled(
false );
479 ( PHP_SAPI ==
'cli' || PHP_SAPI ==
'phpdbg' ) &&
480 !defined(
'MW_PHPUNIT_TEST' )
482 fwrite( STDERR, $err .
"\n" );
498 $this->error( $msg );
502 private $atLineStart =
true;
503 private $lastChannel =
null;
509 if ( !$this->atLineStart ) {
511 $this->atLineStart =
true;
524 if ( $msg ===
false ) {
525 $this->cleanupChanneled();
531 if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
537 $this->atLineStart =
false;
538 if ( $channel ===
null ) {
541 $this->atLineStart =
true;
543 $this->lastChannel = $channel;
565 # Generic (non-script-dependent) options:
567 $this->addOption(
'help',
'Display this help message',
false,
false,
'h' );
568 $this->addOption(
'quiet',
'Whether to suppress non-error output',
false,
false,
'q' );
570 # Save generic options to display them separately in help
571 $generic = [
'help',
'quiet' ];
572 $this->parameters->assignGroup( self::GENERIC_MAINTENANCE_PARAMETERS, $generic );
574 # Script-dependent 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 );
583 # Save additional script-dependent options to display
584 # them separately in help
585 $dependent = array_diff(
586 $this->parameters->getOptionNames(),
589 $this->parameters->assignGroup( self::SCRIPT_DEPENDENT_PARAMETERS, $dependent );
598 if ( $this->config ===
null ) {
599 $this->config = MediaWikiServices::getInstance()->getMainConfig();
602 return $this->config;
610 $this->config = $config;
623 $this->requiredExtensions[] = $name;
632 $registry = ExtensionRegistry::getInstance();
634 foreach ( $this->requiredExtensions as $name ) {
635 if ( !$registry->isLoaded( $name ) ) {
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.';
645 $msg =
'The following extensions must be installed for this script to run: "'
646 . implode(
'", "', $missing ) .
'". Please enable them and then try again.';
648 $this->fatalError( $msg );
676 require_once $classFile;
679 $this->fatalError(
"Cannot spawn child: $maintClass" );
687 $child->loadParamsAndArgs(
689 $this->parameters->getOptions(),
690 $this->parameters->getArgs()
692 if ( $this->mDb !==
null ) {
693 $child->setDB( $this->mDb );
703 $this->loadParamsAndArgs();
724 if ( $this->parameters->hasOption(
'memory-limit' ) ) {
725 $limit = $this->parameters->getOption(
'memory-limit' );
726 $limit = trim( $limit,
"\" '" );
728 $limit = $this->memoryLimit();
731 if ( $limit ==
'max' ) {
734 if ( $limit !=
'default' ) {
735 ini_set(
'memory_limit', $limit );
743 $this->parameters->clear();
744 $this->mInputLoaded =
false;
755 if ( $this->mDescription ) {
756 $this->parameters->setDescription( $this->mDescription );
759 $this->parameters->loadWithArgv( $argv );
761 if ( $this->parameters->hasErrors() ) {
762 $errors =
"\nERROR: " . implode(
"\nERROR: ", $this->parameters->getErrors() ) .
"\n";
763 $this->error( $errors );
764 $this->maybeHelp(
true );
767 $this->loadSpecialVars();
768 $this->mInputLoaded =
true;
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 );
785 $this->parameters->setOptionsAndArgs( $opts ?? [],
$args ?? [] );
786 $this->mInputLoaded =
true;
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();
799 $this->mSelf = $argv[0];
800 $this->parameters->setName( $this->mSelf );
801 $this->loadWithArgv( array_slice( $argv, 1 ) );
809 $valid = $this->parameters->validate();
811 if ( $this->parameters->hasErrors() ) {
812 $errors =
"\nERROR: " . implode(
"\nERROR: ", $this->parameters->getErrors() ) .
"\n";
813 $this->error( $errors );
816 $this->maybeHelp( !$valid );
824 if ( $this->hasOption(
'dbuser' ) ) {
825 $this->mDbUser = $this->getOption(
'dbuser' );
827 if ( $this->hasOption(
'dbpass' ) ) {
828 $this->mDbPass = $this->getOption(
'dbpass' );
830 if ( $this->hasOption(
'quiet' ) ) {
831 $this->mQuiet =
true;
833 if ( $this->hasOption(
'batch-size' ) ) {
834 $this->mBatchSize = intval( $this->getOption(
'batch-size' ) );
844 if ( !$force && !$this->hasOption(
'help' ) ) {
855 $this->mQuiet =
false;
856 $help = $this->parameters->getHelp();
857 $this->output(
$help );
868 if ( !$settingsBuilder ) {
873 $settingsBuilder = $GLOBALS[
'wgSettings'];
876 $config = $settingsBuilder->getConfig();
878 $overrides[
'DBadminuser'] = $config->get( MainConfigNames::DBadminuser );
879 $overrides[
'DBadminpassword'] = $config->get( MainConfigNames::DBadminpassword );
881 # Turn off output buffering again, it might have been turned on in the settings files
882 if ( ob_get_level() ) {
886 $overrides[
'CommandLineMode'] =
true;
889 if ( $this->hasOption(
'server' ) ) {
890 $overrides[
'Server'] = $this->getOption(
'server', $config->get( MainConfigNames::Server ) );
893 # If these were passed, use them
894 if ( $this->mDbUser ) {
895 $overrides[
'DBadminuser'] = $this->mDbUser;
897 if ( $this->mDbPass ) {
898 $overrides[
'DBadminpassword'] = $this->mDbPass;
900 if ( $this->hasOption(
'dbgroupdefault' ) ) {
901 $overrides[
'DBDefaultGroup'] = $this->getOption(
'dbgroupdefault',
null );
906 if ( MediaWikiServices::hasInstance() ) {
907 $service = MediaWikiServices::getInstance()->peekService(
'DBLoadBalancerFactory' );
914 if ( $this->getDbType() == self::DB_ADMIN && isset( $overrides[
'DBadminuser' ] ) ) {
915 $overrides[
'DBuser'] = $overrides[
'DBadminuser' ];
916 $overrides[
'DBpassword'] = $overrides[
'DBadminpassword' ];
919 $dbServers = $config->get( MainConfigNames::DBservers );
921 foreach ( $dbServers as $i => $server ) {
922 $dbServers[$i][
'user'] = $overrides[
'DBuser'];
923 $dbServers[$i][
'password'] = $overrides[
'DBpassword'];
925 $overrides[
'DBservers'] = $dbServers;
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;
939 if ( MediaWikiServices::hasInstance() ) {
940 $service = MediaWikiServices::getInstance()->peekService(
'DBLoadBalancerFactory' );
947 $this->afterFinalSetup();
949 $overrides[
'ShowExceptionDetails'] =
true;
950 $overrides[
'ShowHostname'] =
true;
953 'max_execution_time' => 0,
956 $settingsBuilder->loadArray( [
'config' => $overrides,
'php-ini' => $ini ] );
973 if ( $this->hasOption(
'globals' ) ) {
990 $this->getDbType() !== self::DB_NONE &&
992 !MediaWikiServices::getInstance()->isServiceDisabled(
'DBLoadBalancerFactory' )
994 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
995 if ( $lbFactory->isReadyForRoundOperations() ) {
996 $lbFactory->commitPrimaryChanges( get_class( $this ) );
999 DeferredUpdates::doUpdates();
1004 $profiler = Profiler::instance();
1005 $profiler->logData();
1007 MediaWiki::emitBufferedStatsdData(
1008 MediaWikiServices::getInstance()->getStatsdDataFactory(),
1013 if ( $lbFactory->isReadyForRoundOperations() ) {
1014 $lbFactory->shutdown( $lbFactory::SHUTDOWN_NO_CHRONPROT );
1025 return MW_CONFIG_FILE;
1034 # Data should come off the master, wrapped in a transaction
1036 $this->beginTransaction( $dbw, __METHOD__ );
1038 # Get "active" text records via the content table
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 ) {
1045 $textId = $blobStore->getTextIdFromAddress( $row->content_address );
1050 $this->output(
"done.\n" );
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' ] );
1057 foreach (
$res as $row ) {
1058 $old[] = $row->old_id;
1060 $this->output(
"done.\n" );
1062 # Inform the user of what we're going to do
1063 $count = count( $old );
1064 $this->output(
"$count inactive items found.\n" );
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" );
1073 $this->commitTransaction( $dbw, __METHOD__ );
1081 return __DIR__ .
'/../';
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 );
1130 $dbw->
begin( $fname );
1146 return $this->waitForReplication();
1158 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
1159 $waitSucceeded = $lbFactory->waitForReplication(
1160 [
'timeout' => 30,
'ifWritesSince' => $this->lastReplicationWait ]
1162 $this->lastReplicationWait = microtime(
true );
1169 $lbFactory->autoReconfigure();
1171 return $waitSucceeded;
1199 if ( $this->isQuiet() ) {
1202 for ( $i = $seconds; $i >= 0; $i-- ) {
1203 if ( $i != $seconds ) {
1204 $this->output( str_repeat(
"\x08", strlen( (
string)( $i + 1 ) ) ) );
1206 $this->output( (
string)$i );
1211 $this->output(
"\n" );
1223 if ( !function_exists(
'posix_isatty' ) ) {
1227 return posix_isatty( $fd );
1236 static $isatty =
null;
1237 if ( $isatty ===
null ) {
1238 $isatty = self::posix_isatty( 0 );
1241 if ( $isatty && function_exists(
'readline' ) ) {
1242 return readline( $prompt );
1246 $st = self::readlineEmulation( $prompt );
1247 } elseif ( feof( STDIN ) ) {
1250 $st = fgets( STDIN, 1024 );
1252 if ( $st ===
false ) {
1264 private static function readlineEmulation( $prompt ) {
1265 $bash = ExecutableFinder::findInDefaultPaths(
'bash' );
1267 $encPrompt = Shell::escape( $prompt );
1268 $command =
"read -er -p $encPrompt && echo \"\$REPLY\"";
1269 $result = Shell::command( $bash,
'-c',
$command )
1274 if ( $result->getExitCode() == 0 ) {
1275 return $result->getStdout();
1278 if ( $result->getExitCode() == 127 ) {
1289 if ( feof( STDIN ) ) {
1294 return fgets( STDIN, 1024 );
1305 static $termSize =
null;
1307 if ( $termSize !==
null ) {
1311 $default = [ 80, 50 ];
1314 $termSize = $default;
1326 $result = Shell::command(
'stty',
'size' )->passStdin()->execute();
1327 if ( $result->getExitCode() !== 0 ||
1328 !preg_match(
'/^(\d+) (\d+)$/', $result->getStdout(), $m )
1330 $termSize = $default;
1335 $termSize = [ intval( $m[2] ), intval( $m[1] ) ];
1345 require_once __DIR__ .
'/../../tests/common/TestsAutoLoader.php';
1355 if ( !$this->hookContainer ) {
1356 $this->hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1358 return $this->hookContainer;
1370 if ( !$this->hookRunner ) {
1371 $this->hookRunner =
new HookRunner( $this->getHookContainer() );
1373 return $this->hookRunner;
1387 $ids = preg_split(
'/[\s,;:|]+/', $text );
1389 static function ( $id ) {
1394 return array_filter( $ids );
1406 if ( $this->hasOption(
"user" ) ) {
1408 } elseif ( $this->hasOption(
"userid" ) ) {
1411 $this->fatalError( $errorMsg );
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' ) );
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.
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.
__construct()
Default constructor.
error( $err, $die=0)
Throw an error to the user.
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.
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.
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)
array $mArgs
This is the list of arguments that were actually passed.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
A class containing constants representing the names of configuration variables.
Command line parameter handling for maintenance scripts.
A runner for maintenance scripts.
static newFromName( $name, $validate='valid')
static newFromId( $id)
Static factory method for creation from a given user ID.
while(( $__line=Maintenance::readconsole()) !==false) print
Interface for configuration instances.
Advanced database interface for IDatabase handles that include maintenance methods.
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s