MediaWiki  REL1_19
Maintenance.php
Go to the documentation of this file.
00001 <?php
00028 // Define this so scripts can easily find doMaintenance.php
00029 define( 'RUN_MAINTENANCE_IF_MAIN', dirname( __FILE__ ) . '/doMaintenance.php' );
00030 define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless
00031 
00032 $maintClass = false;
00033 
00034 // Make sure we're on PHP5 or better
00035 if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.2.3' ) < 0 ) {
00036         require_once( dirname( __FILE__ ) . '/../includes/PHPVersionError.php' );
00037         wfPHPVersionError( 'cli' );
00038 }
00039 
00050 abstract class Maintenance {
00051 
00056         const DB_NONE  = 0;
00057         const DB_STD   = 1;
00058         const DB_ADMIN = 2;
00059 
00060         // Const for getStdin()
00061         const STDIN_ALL = 'all';
00062 
00063         // This is the desired params
00064         protected $mParams = array();
00065 
00066         // Array of mapping short parameters to long ones
00067         protected $mShortParamsMap = array();
00068 
00069         // Array of desired args
00070         protected $mArgList = array();
00071 
00072         // This is the list of options that were actually passed
00073         protected $mOptions = array();
00074 
00075         // This is the list of arguments that were actually passed
00076         protected $mArgs = array();
00077 
00078         // Name of the script currently running
00079         protected $mSelf;
00080 
00081         // Special vars for params that are always used
00082         protected $mQuiet = false;
00083         protected $mDbUser, $mDbPass;
00084 
00085         // A description of the script, children should change this
00086         protected $mDescription = '';
00087 
00088         // Have we already loaded our user input?
00089         protected $mInputLoaded = false;
00090 
00097         protected $mBatchSize = null;
00098 
00099         // Generic options added by addDefaultParams()
00100         private $mGenericParameters = array();
00101         // Generic options which might or not be supported by the script
00102         private $mDependantParameters = array();
00103 
00104         // Used by getDD() / setDB()
00105         private $mDb = null;
00106 
00112         protected static $mCoreScripts = null;
00113 
00118         public function __construct() {
00119                 // Setup $IP, using MW_INSTALL_PATH if it exists
00120                 global $IP;
00121                 $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== ''
00122                         ? getenv( 'MW_INSTALL_PATH' )
00123                         : realpath( dirname( __FILE__ ) . '/..' );
00124 
00125                 $this->addDefaultParams();
00126                 register_shutdown_function( array( $this, 'outputChanneled' ), false );
00127         }
00128 
00136         public static function shouldExecute() {
00137                 $bt = debug_backtrace();
00138                 $count = count( $bt );
00139                 if ( $count < 2 ) {
00140                         return false; // sanity
00141                 }
00142                 if ( $bt[0]['class'] !== 'Maintenance' || $bt[0]['function'] !== 'shouldExecute' ) {
00143                         return false; // last call should be to this function
00144                 }
00145                 $includeFuncs = array( 'require_once', 'require', 'include', 'include_once' );
00146                 for( $i=1; $i < $count; $i++ ) {
00147                         if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) {
00148                                 return false; // previous calls should all be "requires"
00149                         }
00150                 }
00151                 return true;
00152         }
00153 
00157         abstract public function execute();
00158 
00169         protected function addOption( $name, $description, $required = false, $withArg = false, $shortName = false ) {
00170                 $this->mParams[$name] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg, 'shortName' => $shortName );
00171                 if ( $shortName !== false ) {
00172                         $this->mShortParamsMap[$shortName] = $name;
00173                 }
00174         }
00175 
00181         protected function hasOption( $name ) {
00182                 return isset( $this->mOptions[$name] );
00183         }
00184 
00191         protected function getOption( $name, $default = null ) {
00192                 if ( $this->hasOption( $name ) ) {
00193                         return $this->mOptions[$name];
00194                 } else {
00195                         // Set it so we don't have to provide the default again
00196                         $this->mOptions[$name] = $default;
00197                         return $this->mOptions[$name];
00198                 }
00199         }
00200 
00207         protected function addArg( $arg, $description, $required = true ) {
00208                 $this->mArgList[] = array(
00209                         'name' => $arg,
00210                         'desc' => $description,
00211                         'require' => $required
00212                 );
00213         }
00214 
00219         protected function deleteOption( $name ) {
00220                 unset( $this->mParams[$name] );
00221         }
00222 
00227         protected function addDescription( $text ) {
00228                 $this->mDescription = $text;
00229         }
00230 
00236         protected function hasArg( $argId = 0 ) {
00237                 return isset( $this->mArgs[$argId] );
00238         }
00239 
00246         protected function getArg( $argId = 0, $default = null ) {
00247                 return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default;
00248         }
00249 
00254         protected function setBatchSize( $s = 0 ) {
00255                 $this->mBatchSize = $s;
00256 
00257                 // If we support $mBatchSize, show the option.
00258                 // Used to be in addDefaultParams, but in order for that to
00259                 // work, subclasses would have to call this function in the constructor
00260                 // before they called parent::__construct which is just weird
00261                 // (and really wasn't done).
00262                 if ( $this->mBatchSize ) {
00263                         $this->addOption( 'batch-size', 'Run this many operations ' .
00264                                 'per batch, default: ' . $this->mBatchSize, false, true );
00265                         if ( isset( $this->mParams['batch-size'] ) ) {
00266                                 // This seems a little ugly...
00267                                 $this->mDependantParameters['batch-size'] = $this->mParams['batch-size'];
00268                         }
00269                 }
00270         }
00271 
00276         public function getName() {
00277                 return $this->mSelf;
00278         }
00279 
00287         protected function getStdin( $len = null ) {
00288                 if ( $len == Maintenance::STDIN_ALL ) {
00289                         return file_get_contents( 'php://stdin' );
00290                 }
00291                 $f = fopen( 'php://stdin', 'rt' );
00292                 if ( !$len ) {
00293                         return $f;
00294                 }
00295                 $input = fgets( $f, $len );
00296                 fclose( $f );
00297                 return rtrim( $input );
00298         }
00299 
00300         public function isQuiet() {
00301                 return $this->mQuiet;
00302         }
00303 
00311         protected function output( $out, $channel = null ) {
00312                 if ( $this->mQuiet ) {
00313                         return;
00314                 }
00315                 if ( $channel === null ) {
00316                         $this->cleanupChanneled();
00317                         if( php_sapi_name() == 'cli' ) {
00318                                 fwrite( STDOUT, $out );
00319                         } else {
00320                                 print( $out );
00321                         }
00322                 } else {
00323                         $out = preg_replace( '/\n\z/', '', $out );
00324                         $this->outputChanneled( $out, $channel );
00325                 }
00326         }
00327 
00334         protected function error( $err, $die = 0 ) {
00335                 $this->outputChanneled( false );
00336                 if ( php_sapi_name() == 'cli' ) {
00337                         fwrite( STDERR, $err . "\n" );
00338                 } else {
00339                         print $err;
00340                 }
00341                 $die = intval( $die );
00342                 if ( $die > 0 ) {
00343                         die( $die );
00344                 }
00345         }
00346 
00347         private $atLineStart = true;
00348         private $lastChannel = null;
00349 
00353         public function cleanupChanneled() {
00354                 if ( !$this->atLineStart ) {
00355                         if( php_sapi_name() == 'cli' ) {
00356                                 fwrite( STDOUT, "\n" );
00357                         } else {
00358                                 print "\n";
00359                         }
00360                         $this->atLineStart = true;
00361                 }
00362         }
00363 
00372         public function outputChanneled( $msg, $channel = null ) {
00373                 if ( $msg === false ) {
00374                         $this->cleanupChanneled();
00375                         return;
00376                 }
00377 
00378                 $cli = php_sapi_name() == 'cli';
00379 
00380                 // End the current line if necessary
00381                 if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
00382                         if( $cli ) {
00383                                 fwrite( STDOUT, "\n" );
00384                         } else {
00385                                 print "\n";
00386                         }
00387                 }
00388 
00389                 if( $cli ) {
00390                         fwrite( STDOUT, $msg );
00391                 } else {
00392                         print $msg;
00393                 }
00394 
00395                 $this->atLineStart = false;
00396                 if ( $channel === null ) {
00397                         // For unchanneled messages, output trailing newline immediately
00398                         if( $cli ) {
00399                                 fwrite( STDOUT, "\n" );
00400                         } else {
00401                                 print "\n";
00402                         }
00403                         $this->atLineStart = true;
00404                 }
00405                 $this->lastChannel = $channel;
00406         }
00407 
00418         public function getDbType() {
00419                 return Maintenance::DB_STD;
00420         }
00421 
00425         protected function addDefaultParams() {
00426 
00427                 # Generic (non script dependant) options:
00428 
00429                 $this->addOption( 'help', 'Display this help message', false, false, 'h' );
00430                 $this->addOption( 'quiet', 'Whether to supress non-error output', false, false, 'q' );
00431                 $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true );
00432                 $this->addOption( 'wiki', 'For specifying the wiki ID', false, true );
00433                 $this->addOption( 'globals', 'Output globals at the end of processing for debugging' );
00434                 $this->addOption( 'memory-limit', 'Set a specific memory limit for the script, "max" for no limit or "default" to avoid changing it' );
00435                 $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " .
00436                                 "http://en.wikipedia.org. This is sometimes necessary because " .
00437                                 "server name detection may fail in command line scripts.", false, true );
00438 
00439                 # Save generic options to display them separately in help
00440                 $this->mGenericParameters = $this->mParams ;
00441 
00442                 # Script dependant options:
00443 
00444                 // If we support a DB, show the options
00445                 if ( $this->getDbType() > 0 ) {
00446                         $this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
00447                         $this->addOption( 'dbpass', 'The password to use for this script', false, true );
00448                 }
00449 
00450                 # Save additional script dependant options to display
00451                 # them separately in help
00452                 $this->mDependantParameters = array_diff_key( $this->mParams, $this->mGenericParameters );
00453         }
00454 
00462         public function runChild( $maintClass, $classFile = null ) {
00463                 // Make sure the class is loaded first
00464                 if ( !MWInit::classExists( $maintClass ) ) {
00465                         if ( $classFile ) {
00466                                 require_once( $classFile );
00467                         }
00468                         if ( !MWInit::classExists( $maintClass ) ) {
00469                                 $this->error( "Cannot spawn child: $maintClass" );
00470                         }
00471                 }
00472 
00476                 $child = new $maintClass();
00477                 $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs );
00478                 if ( !is_null( $this->mDb ) ) {
00479                         $child->setDB( $this->mDb );
00480                 }
00481                 return $child;
00482         }
00483 
00487         public function setup() {
00488                 global $wgCommandLineMode, $wgRequestTime;
00489 
00490                 # Abort if called from a web server
00491                 if ( isset( $_SERVER ) && isset( $_SERVER['REQUEST_METHOD'] ) ) {
00492                         $this->error( 'This script must be run from the command line', true );
00493                 }
00494 
00495                 # Make sure we can handle script parameters
00496                 if ( !function_exists( 'hphp_thread_set_warmup_enabled' ) && !ini_get( 'register_argc_argv' ) ) {
00497                         $this->error( 'Cannot get command line arguments, register_argc_argv is set to false', true );
00498                 }
00499 
00500                 if ( version_compare( phpversion(), '5.2.4' ) >= 0 ) {
00501                         // Send PHP warnings and errors to stderr instead of stdout.
00502                         // This aids in diagnosing problems, while keeping messages
00503                         // out of redirected output.
00504                         if ( ini_get( 'display_errors' ) ) {
00505                                 ini_set( 'display_errors', 'stderr' );
00506                         }
00507 
00508                         // Don't touch the setting on earlier versions of PHP,
00509                         // as setting it would disable output if you'd wanted it.
00510 
00511                         // Note that exceptions are also sent to stderr when
00512                         // command-line mode is on, regardless of PHP version.
00513                 }
00514 
00515                 $this->loadParamsAndArgs();
00516                 $this->maybeHelp();
00517 
00518                 # Set the memory limit
00519                 # Note we need to set it again later in cache LocalSettings changed it
00520                 $this->adjustMemoryLimit();
00521 
00522                 # Set max execution time to 0 (no limit). PHP.net says that
00523                 # "When running PHP from the command line the default setting is 0."
00524                 # But sometimes this doesn't seem to be the case.
00525                 ini_set( 'max_execution_time', 0 );
00526 
00527                 $wgRequestTime = microtime( true );
00528 
00529                 # Define us as being in MediaWiki
00530                 define( 'MEDIAWIKI', true );
00531 
00532                 $wgCommandLineMode = true;
00533                 # Turn off output buffering if it's on
00534                 @ob_end_flush();
00535 
00536                 $this->validateParamsAndArgs();
00537         }
00538 
00548         public function memoryLimit() {
00549                 $limit = $this->getOption( 'memory-limit', 'max' );
00550                 $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood
00551                 return $limit;
00552         }
00553 
00557         protected function adjustMemoryLimit() {
00558                 $limit = $this->memoryLimit();
00559                 if ( $limit == 'max' ) {
00560                         $limit = -1; // no memory limit
00561                 }
00562                 if ( $limit != 'default' ) {
00563                         ini_set( 'memory_limit', $limit );
00564                 }
00565         }
00566 
00570         public function clearParamsAndArgs() {
00571                 $this->mOptions = array();
00572                 $this->mArgs = array();
00573                 $this->mInputLoaded = false;
00574         }
00575 
00585         public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
00586                 # If we were given opts or args, set those and return early
00587                 if ( $self ) {
00588                         $this->mSelf = $self;
00589                         $this->mInputLoaded = true;
00590                 }
00591                 if ( $opts ) {
00592                         $this->mOptions = $opts;
00593                         $this->mInputLoaded = true;
00594                 }
00595                 if ( $args ) {
00596                         $this->mArgs = $args;
00597                         $this->mInputLoaded = true;
00598                 }
00599 
00600                 # If we've already loaded input (either by user values or from $argv)
00601                 # skip on loading it again. The array_shift() will corrupt values if
00602                 # it's run again and again
00603                 if ( $this->mInputLoaded ) {
00604                         $this->loadSpecialVars();
00605                         return;
00606                 }
00607 
00608                 global $argv;
00609                 $this->mSelf = array_shift( $argv );
00610 
00611                 $options = array();
00612                 $args = array();
00613 
00614                 # Parse arguments
00615                 for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
00616                         if ( $arg == '--' ) {
00617                                 # End of options, remainder should be considered arguments
00618                                 $arg = next( $argv );
00619                                 while ( $arg !== false ) {
00620                                         $args[] = $arg;
00621                                         $arg = next( $argv );
00622                                 }
00623                                 break;
00624                         } elseif ( substr( $arg, 0, 2 ) == '--' ) {
00625                                 # Long options
00626                                 $option = substr( $arg, 2 );
00627                                 if ( array_key_exists( $option, $options ) ) {
00628                                         $this->error( "\nERROR: $option parameter given twice\n" );
00629                                         $this->maybeHelp( true );
00630                                 }
00631                                 if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) {
00632                                         $param = next( $argv );
00633                                         if ( $param === false ) {
00634                                                 $this->error( "\nERROR: $option parameter needs a value after it\n" );
00635                                                 $this->maybeHelp( true );
00636                                         }
00637                                         $options[$option] = $param;
00638                                 } else {
00639                                         $bits = explode( '=', $option, 2 );
00640                                         if ( count( $bits ) > 1 ) {
00641                                                 $option = $bits[0];
00642                                                 $param = $bits[1];
00643                                         } else {
00644                                                 $param = 1;
00645                                         }
00646                                         $options[$option] = $param;
00647                                 }
00648                         } elseif ( substr( $arg, 0, 1 ) == '-' ) {
00649                                 # Short options
00650                                 for ( $p = 1; $p < strlen( $arg ); $p++ ) {
00651                                         $option = $arg { $p } ;
00652                                         if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) {
00653                                                 $option = $this->mShortParamsMap[$option];
00654                                         }
00655                                         if ( array_key_exists( $option, $options ) ) {
00656                                                 $this->error( "\nERROR: $option parameter given twice\n" );
00657                                                 $this->maybeHelp( true );
00658                                         }
00659                                         if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) {
00660                                                 $param = next( $argv );
00661                                                 if ( $param === false ) {
00662                                                         $this->error( "\nERROR: $option parameter needs a value after it\n" );
00663                                                         $this->maybeHelp( true );
00664                                                 }
00665                                                 $options[$option] = $param;
00666                                         } else {
00667                                                 $options[$option] = 1;
00668                                         }
00669                                 }
00670                         } else {
00671                                 $args[] = $arg;
00672                         }
00673                 }
00674 
00675                 $this->mOptions = $options;
00676                 $this->mArgs = $args;
00677                 $this->loadSpecialVars();
00678                 $this->mInputLoaded = true;
00679         }
00680 
00684         protected function validateParamsAndArgs() {
00685                 $die = false;
00686                 # Check to make sure we've got all the required options
00687                 foreach ( $this->mParams as $opt => $info ) {
00688                         if ( $info['require'] && !$this->hasOption( $opt ) ) {
00689                                 $this->error( "Param $opt required!" );
00690                                 $die = true;
00691                         }
00692                 }
00693                 # Check arg list too
00694                 foreach ( $this->mArgList as $k => $info ) {
00695                         if ( $info['require'] && !$this->hasArg( $k ) ) {
00696                                 $this->error( 'Argument <' . $info['name'] . '> required!' );
00697                                 $die = true;
00698                         }
00699                 }
00700 
00701                 if ( $die ) {
00702                         $this->maybeHelp( true );
00703                 }
00704         }
00705 
00709         protected function loadSpecialVars() {
00710                 if ( $this->hasOption( 'dbuser' ) ) {
00711                         $this->mDbUser = $this->getOption( 'dbuser' );
00712                 }
00713                 if ( $this->hasOption( 'dbpass' ) ) {
00714                         $this->mDbPass = $this->getOption( 'dbpass' );
00715                 }
00716                 if ( $this->hasOption( 'quiet' ) ) {
00717                         $this->mQuiet = true;
00718                 }
00719                 if ( $this->hasOption( 'batch-size' ) ) {
00720                         $this->mBatchSize = intval( $this->getOption( 'batch-size' ) );
00721                 }
00722         }
00723 
00728         protected function maybeHelp( $force = false ) {
00729                 if( !$force && !$this->hasOption( 'help' ) ) {
00730                         return;
00731                 }
00732 
00733                 $screenWidth = 80; // TODO: Caculate this!
00734                 $tab = "    ";
00735                 $descWidth = $screenWidth - ( 2 * strlen( $tab ) );
00736 
00737                 ksort( $this->mParams );
00738                 $this->mQuiet = false;
00739 
00740                 // Description ...
00741                 if ( $this->mDescription ) {
00742                         $this->output( "\n" . $this->mDescription . "\n" );
00743                 }
00744                 $output = "\nUsage: php " . basename( $this->mSelf );
00745 
00746                 // ... append parameters ...
00747                 if ( $this->mParams ) {
00748                         $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]";
00749                 }
00750 
00751                 // ... and append arguments.
00752                 if ( $this->mArgList ) {
00753                         $output .= ' ';
00754                         foreach ( $this->mArgList as $k => $arg ) {
00755                                 if ( $arg['require'] ) {
00756                                         $output .= '<' . $arg['name'] . '>';
00757                                 } else {
00758                                         $output .= '[' . $arg['name'] . ']';
00759                                 }
00760                                 if ( $k < count( $this->mArgList ) - 1 )
00761                                         $output .= ' ';
00762                         }
00763                 }
00764                 $this->output( "$output\n\n" );
00765 
00766                 # TODO abstract some repetitive code below
00767 
00768                 // Generic parameters
00769                 $this->output( "Generic maintenance parameters:\n" );
00770                 foreach ( $this->mGenericParameters as $par => $info ) {
00771                         if ( $info['shortName'] !== false ) {
00772                                 $par .= " (-{$info['shortName']})";
00773                         }
00774                         $this->output(
00775                                 wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
00776                                                 "\n$tab$tab" ) . "\n"
00777                         );
00778                 }
00779                 $this->output( "\n" );
00780 
00781                 $scriptDependantParams = $this->mDependantParameters;
00782                 if( count($scriptDependantParams) > 0 ) {
00783                         $this->output( "Script dependant parameters:\n" );
00784                         // Parameters description
00785                         foreach ( $scriptDependantParams as $par => $info ) {
00786                                 if ( $info['shortName'] !== false ) {
00787                                         $par .= " (-{$info['shortName']})";
00788                                 }
00789                                 $this->output(
00790                                         wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
00791                                                         "\n$tab$tab" ) . "\n"
00792                                 );
00793                         }
00794                         $this->output( "\n" );
00795                 }
00796 
00797 
00798                 // Script specific parameters not defined on construction by
00799                 // Maintenance::addDefaultParams()
00800                 $scriptSpecificParams = array_diff_key(
00801                         # all script parameters:
00802                         $this->mParams,
00803                         # remove the Maintenance default parameters:
00804                         $this->mGenericParameters,
00805                         $this->mDependantParameters
00806                 );
00807                 if( count($scriptSpecificParams) > 0 ) {
00808                         $this->output( "Script specific parameters:\n" );
00809                         // Parameters description
00810                         foreach ( $scriptSpecificParams as $par => $info ) {
00811                                 if ( $info['shortName'] !== false ) {
00812                                         $par .= " (-{$info['shortName']})";
00813                                 }
00814                                 $this->output(
00815                                         wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
00816                                                         "\n$tab$tab" ) . "\n"
00817                                 );
00818                         }
00819                         $this->output( "\n" );
00820                 }
00821 
00822                 // Print arguments
00823                 if( count( $this->mArgList ) > 0 ) {
00824                         $this->output( "Arguments:\n" );
00825                         // Arguments description
00826                         foreach ( $this->mArgList as $info ) {
00827                                 $openChar = $info['require'] ? '<' : '[';
00828                                 $closeChar = $info['require'] ? '>' : ']';
00829                                 $this->output(
00830                                         wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " .
00831                                                 $info['desc'], $descWidth, "\n$tab$tab" ) . "\n"
00832                                 );
00833                         }
00834                         $this->output( "\n" );
00835                 }
00836 
00837                 die( 1 );
00838         }
00839 
00843         public function finalSetup() {
00844                 global $wgCommandLineMode, $wgShowSQLErrors, $wgServer;
00845                 global $wgDBadminuser, $wgDBadminpassword;
00846                 global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf;
00847 
00848                 # Turn off output buffering again, it might have been turned on in the settings files
00849                 if ( ob_get_level() ) {
00850                         ob_end_flush();
00851                 }
00852                 # Same with these
00853                 $wgCommandLineMode = true;
00854 
00855                 # Override $wgServer
00856                 if( $this->hasOption( 'server') ) {
00857                         $wgServer = $this->getOption( 'server', $wgServer );
00858                 }
00859 
00860                 # If these were passed, use them
00861                 if ( $this->mDbUser ) {
00862                         $wgDBadminuser = $this->mDbUser;
00863                 }
00864                 if ( $this->mDbPass ) {
00865                         $wgDBadminpassword = $this->mDbPass;
00866                 }
00867 
00868                 if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) {
00869                         $wgDBuser = $wgDBadminuser;
00870                         $wgDBpassword = $wgDBadminpassword;
00871 
00872                         if ( $wgDBservers ) {
00876                                 foreach ( $wgDBservers as $i => $server ) {
00877                                         $wgDBservers[$i]['user'] = $wgDBuser;
00878                                         $wgDBservers[$i]['password'] = $wgDBpassword;
00879                                 }
00880                         }
00881                         if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
00882                                 $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
00883                                 $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
00884                         }
00885                         LBFactory::destroyInstance();
00886                 }
00887 
00888                 $this->afterFinalSetup();
00889 
00890                 $wgShowSQLErrors = true;
00891                 @set_time_limit( 0 );
00892                 $this->adjustMemoryLimit();
00893         }
00894 
00898         protected function afterFinalSetup() {
00899                 if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
00900                         call_user_func( MW_CMDLINE_CALLBACK );
00901                 }
00902         }
00903 
00908         public function globals() {
00909                 if ( $this->hasOption( 'globals' ) ) {
00910                         print_r( $GLOBALS );
00911                 }
00912         }
00913 
00918         public function loadSettings() {
00919                 global $wgCommandLineMode, $IP;
00920 
00921                 if ( isset( $this->mOptions['conf'] ) ) {
00922                         $settingsFile = $this->mOptions['conf'];
00923                 } elseif ( defined("MW_CONFIG_FILE") ) {
00924                         $settingsFile = MW_CONFIG_FILE;
00925                 } else {
00926                         $settingsFile = "$IP/LocalSettings.php";
00927                 }
00928                 if ( isset( $this->mOptions['wiki'] ) ) {
00929                         $bits = explode( '-', $this->mOptions['wiki'] );
00930                         if ( count( $bits ) == 1 ) {
00931                                 $bits[] = '';
00932                         }
00933                         define( 'MW_DB', $bits[0] );
00934                         define( 'MW_PREFIX', $bits[1] );
00935                 }
00936 
00937                 if ( !is_readable( $settingsFile ) ) {
00938                         $this->error( "A copy of your installation's LocalSettings.php\n" .
00939                                                 "must exist and be readable in the source directory.\n" .
00940                                                 "Use --conf to specify it." , true );
00941                 }
00942                 $wgCommandLineMode = true;
00943                 return $settingsFile;
00944         }
00945 
00951         public function purgeRedundantText( $delete = true ) {
00952                 # Data should come off the master, wrapped in a transaction
00953                 $dbw = $this->getDB( DB_MASTER );
00954                 $dbw->begin();
00955 
00956                 $tbl_arc = $dbw->tableName( 'archive' );
00957                 $tbl_rev = $dbw->tableName( 'revision' );
00958                 $tbl_txt = $dbw->tableName( 'text' );
00959 
00960                 # Get "active" text records from the revisions table
00961                 $this->output( 'Searching for active text records in revisions table...' );
00962                 $res = $dbw->query( "SELECT DISTINCT rev_text_id FROM $tbl_rev" );
00963                 foreach ( $res as $row ) {
00964                         $cur[] = $row->rev_text_id;
00965                 }
00966                 $this->output( "done.\n" );
00967 
00968                 # Get "active" text records from the archive table
00969                 $this->output( 'Searching for active text records in archive table...' );
00970                 $res = $dbw->query( "SELECT DISTINCT ar_text_id FROM $tbl_arc" );
00971                 foreach ( $res as $row ) {
00972                         $cur[] = $row->ar_text_id;
00973                 }
00974                 $this->output( "done.\n" );
00975 
00976                 # Get the IDs of all text records not in these sets
00977                 $this->output( 'Searching for inactive text records...' );
00978                 $set = implode( ', ', $cur );
00979                 $res = $dbw->query( "SELECT old_id FROM $tbl_txt WHERE old_id NOT IN ( $set )" );
00980                 $old = array();
00981                 foreach ( $res as $row ) {
00982                         $old[] = $row->old_id;
00983                 }
00984                 $this->output( "done.\n" );
00985 
00986                 # Inform the user of what we're going to do
00987                 $count = count( $old );
00988                 $this->output( "$count inactive items found.\n" );
00989 
00990                 # Delete as appropriate
00991                 if ( $delete && $count ) {
00992                         $this->output( 'Deleting...' );
00993                         $set = implode( ', ', $old );
00994                         $dbw->query( "DELETE FROM $tbl_txt WHERE old_id IN ( $set )" );
00995                         $this->output( "done.\n" );
00996                 }
00997 
00998                 # Done
00999                 $dbw->commit();
01000         }
01001 
01006         protected function getDir() {
01007                 return dirname( __FILE__ );
01008         }
01009 
01016         public static function getMaintenanceScripts() {
01017                 global $wgMaintenanceScripts;
01018                 return $wgMaintenanceScripts + self::getCoreScripts();
01019         }
01020 
01025         protected static function getCoreScripts() {
01026                 if ( !self::$mCoreScripts ) {
01027                         $paths = array(
01028                                 dirname( __FILE__ ),
01029                                 dirname( __FILE__ ) . '/gearman',
01030                                 dirname( __FILE__ ) . '/language',
01031                                 dirname( __FILE__ ) . '/storage',
01032                         );
01033                         self::$mCoreScripts = array();
01034                         foreach ( $paths as $p ) {
01035                                 $handle = opendir( $p );
01036                                 while ( ( $file = readdir( $handle ) ) !== false ) {
01037                                         if ( $file == 'Maintenance.php' ) {
01038                                                 continue;
01039                                         }
01040                                         $file = $p . '/' . $file;
01041                                         if ( is_dir( $file ) || !strpos( $file, '.php' ) ||
01042                                                 ( strpos( file_get_contents( $file ), '$maintClass' ) === false ) ) {
01043                                                 continue;
01044                                         }
01045                                         require( $file );
01046                                         $vars = get_defined_vars();
01047                                         if ( array_key_exists( 'maintClass', $vars ) ) {
01048                                                 self::$mCoreScripts[$vars['maintClass']] = $file;
01049                                         }
01050                                 }
01051                                 closedir( $handle );
01052                         }
01053                 }
01054                 return self::$mCoreScripts;
01055         }
01056 
01064         protected function &getDB( $db, $groups = array(), $wiki = false ) {
01065                 if ( is_null( $this->mDb ) ) {
01066                         return wfGetDB( $db, $groups, $wiki );
01067                 } else {
01068                         return $this->mDb;
01069                 }
01070         }
01071 
01077         public function setDB( &$db ) {
01078                 $this->mDb = $db;
01079         }
01080 
01085         private function lockSearchindex( &$db ) {
01086                 $write = array( 'searchindex' );
01087                 $read = array( 'page', 'revision', 'text', 'interwiki', 'l10n_cache' );
01088                 $db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ );
01089         }
01090 
01095         private function unlockSearchindex( &$db ) {
01096                 $db->unlockTables(  __CLASS__ . '::' . __METHOD__ );
01097         }
01098 
01104         private function relockSearchindex( &$db ) {
01105                 $this->unlockSearchindex( $db );
01106                 $this->lockSearchindex( $db );
01107         }
01108 
01116         public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) {
01117                 $lockTime = time();
01118 
01119                 # Lock searchindex
01120                 if ( $maxLockTime ) {
01121                         $this->output( "   --- Waiting for lock ---" );
01122                         $this->lockSearchindex( $dbw );
01123                         $lockTime = time();
01124                         $this->output( "\n" );
01125                 }
01126 
01127                 # Loop through the results and do a search update
01128                 foreach ( $results as $row ) {
01129                         # Allow reads to be processed
01130                         if ( $maxLockTime && time() > $lockTime + $maxLockTime ) {
01131                                 $this->output( "    --- Relocking ---" );
01132                                 $this->relockSearchindex( $dbw );
01133                                 $lockTime = time();
01134                                 $this->output( "\n" );
01135                         }
01136                         call_user_func( $callback, $dbw, $row );
01137                 }
01138 
01139                 # Unlock searchindex
01140                 if ( $maxLockTime ) {
01141                         $this->output( "    --- Unlocking --" );
01142                         $this->unlockSearchindex( $dbw );
01143                         $this->output( "\n" );
01144                 }
01145 
01146         }
01147 
01154         public function updateSearchIndexForPage( $dbw, $pageId ) {
01155                 // Get current revision
01156                 $rev = Revision::loadFromPageId( $dbw, $pageId );
01157                 $title = null;
01158                 if ( $rev ) {
01159                         $titleObj = $rev->getTitle();
01160                         $title = $titleObj->getPrefixedDBkey();
01161                         $this->output( "$title..." );
01162                         # Update searchindex
01163                         $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getText() );
01164                         $u->doUpdate();
01165                         $this->output( "\n" );
01166                 }
01167                 return $title;
01168         }
01169 
01178         public static function posix_isatty( $fd ) {
01179                 if ( !MWInit::functionExists( 'posix_isatty' ) ) {
01180                         return !$fd;
01181                 } else {
01182                         return posix_isatty( $fd );
01183                 }
01184         }
01185 
01191         public static function readconsole( $prompt = '> ' ) {
01192                 static $isatty = null;
01193                 if ( is_null( $isatty ) ) {
01194                         $isatty = self::posix_isatty( 0 /*STDIN*/ );
01195                 }
01196 
01197                 if ( $isatty && function_exists( 'readline' ) ) {
01198                         return readline( $prompt );
01199                 } else {
01200                         if ( $isatty ) {
01201                                 $st = self::readlineEmulation( $prompt );
01202                         } else {
01203                                 if ( feof( STDIN ) ) {
01204                                         $st = false;
01205                                 } else {
01206                                         $st = fgets( STDIN, 1024 );
01207                                 }
01208                         }
01209                         if ( $st === false ) return false;
01210                         $resp = trim( $st );
01211                         return $resp;
01212                 }
01213         }
01214 
01220         private static function readlineEmulation( $prompt ) {
01221                 $bash = Installer::locateExecutableInDefaultPaths( array( 'bash' ) );
01222                 if ( !wfIsWindows() && $bash ) {
01223                         $retval = false;
01224                         $encPrompt = wfEscapeShellArg( $prompt );
01225                         $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
01226                         $encCommand = wfEscapeShellArg( $command );
01227                         $line = wfShellExec( "$bash -c $encCommand", $retval );
01228 
01229                         if ( $retval == 0 ) {
01230                                 return $line;
01231                         } elseif ( $retval == 127 ) {
01232                                 // Couldn't execute bash even though we thought we saw it.
01233                                 // Shell probably spit out an error message, sorry :(
01234                                 // Fall through to fgets()...
01235                         } else {
01236                                 // EOF/ctrl+D
01237                                 return false;
01238                         }
01239                 }
01240 
01241                 // Fallback... we'll have no editing controls, EWWW
01242                 if ( feof( STDIN ) ) {
01243                         return false;
01244                 }
01245                 print $prompt;
01246                 return fgets( STDIN, 1024 );
01247         }
01248 }
01249 
01253 class FakeMaintenance extends Maintenance {
01254         protected $mSelf = "FakeMaintenanceScript";
01255         public function execute() {
01256                 return;
01257         }
01258 }
01259 
01264 abstract class LoggedUpdateMaintenance extends Maintenance {
01265         public function __construct() {
01266                 parent::__construct();
01267                 $this->addOption( 'force', 'Run the update even if it was completed already' );
01268                 $this->setBatchSize( 200 );
01269         }
01270 
01271         public function execute() {
01272                 $db = $this->getDB( DB_MASTER );
01273                 $key = $this->getUpdateKey();
01274 
01275                 if ( !$this->hasOption( 'force' ) &&
01276                         $db->selectRow( 'updatelog', '1', array( 'ul_key' => $key ), __METHOD__ ) )
01277                 {
01278                         $this->output( "..." . $this->updateSkippedMessage() . "\n" );
01279                         return true;
01280                 }
01281 
01282                 if ( !$this->doDBUpdates() ) {
01283                         return false;
01284                 }
01285 
01286                 if (
01287                         $db->insert( 'updatelog', array( 'ul_key' => $key ), __METHOD__, 'IGNORE' ) )
01288                 {
01289                         return true;
01290                 } else {
01291                         $this->output( $this->updatelogFailedMessage() . "\n" );
01292                         return false;
01293                 }
01294         }
01295 
01300         protected function updateSkippedMessage() {
01301                 $key = $this->getUpdateKey();
01302                 return "Update '{$key}' already logged as completed.";
01303         }
01304 
01309         protected function updatelogFailedMessage() {
01310                 $key = $this->getUpdateKey();
01311                 return "Unable to log update '{$key}' as completed.";
01312         }
01313 
01319         abstract protected function doDBUpdates();
01320 
01325         abstract protected function getUpdateKey();
01326 }