MediaWiki  REL1_23
Maintenance.php
Go to the documentation of this file.
00001 <?php
00023 // Make sure we're on PHP5.3.2 or better
00024 if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.2' ) < 0 ) {
00025     // We need to use dirname( __FILE__ ) here cause __DIR__ is PHP5.3+
00026     require_once dirname( __FILE__ ) . '/../includes/PHPVersionError.php';
00027     wfPHPVersionError( 'cli' );
00028 }
00029 
00035 // Define this so scripts can easily find doMaintenance.php
00036 define( 'RUN_MAINTENANCE_IF_MAIN', __DIR__ . '/doMaintenance.php' );
00037 define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless
00038 
00039 $maintClass = false;
00040 
00051 abstract class Maintenance {
00052 
00057     const DB_NONE = 0;
00058     const DB_STD = 1;
00059     const DB_ADMIN = 2;
00060 
00061     // Const for getStdin()
00062     const STDIN_ALL = 'all';
00063 
00064     // This is the desired params
00065     protected $mParams = array();
00066 
00067     // Array of mapping short parameters to long ones
00068     protected $mShortParamsMap = array();
00069 
00070     // Array of desired args
00071     protected $mArgList = array();
00072 
00073     // This is the list of options that were actually passed
00074     protected $mOptions = array();
00075 
00076     // This is the list of arguments that were actually passed
00077     protected $mArgs = array();
00078 
00079     // Name of the script currently running
00080     protected $mSelf;
00081 
00082     // Special vars for params that are always used
00083     protected $mQuiet = false;
00084     protected $mDbUser, $mDbPass;
00085 
00086     // A description of the script, children should change this
00087     protected $mDescription = '';
00088 
00089     // Have we already loaded our user input?
00090     protected $mInputLoaded = false;
00091 
00098     protected $mBatchSize = null;
00099 
00100     // Generic options added by addDefaultParams()
00101     private $mGenericParameters = array();
00102     // Generic options which might or not be supported by the script
00103     private $mDependantParameters = array();
00104 
00109     private $mDb = null;
00110 
00115     public $fileHandle;
00116 
00121     public function __construct() {
00122         // Setup $IP, using MW_INSTALL_PATH if it exists
00123         global $IP;
00124         $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== ''
00125             ? getenv( 'MW_INSTALL_PATH' )
00126             : realpath( __DIR__ . '/..' );
00127 
00128         $this->addDefaultParams();
00129         register_shutdown_function( array( $this, 'outputChanneled' ), false );
00130     }
00131 
00139     public static function shouldExecute() {
00140         $bt = debug_backtrace();
00141         $count = count( $bt );
00142         if ( $count < 2 ) {
00143             return false; // sanity
00144         }
00145         if ( $bt[0]['class'] !== 'Maintenance' || $bt[0]['function'] !== 'shouldExecute' ) {
00146             return false; // last call should be to this function
00147         }
00148         $includeFuncs = array( 'require_once', 'require', 'include', 'include_once' );
00149         for ( $i = 1; $i < $count; $i++ ) {
00150             if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) {
00151                 return false; // previous calls should all be "requires"
00152             }
00153         }
00154         return true;
00155     }
00156 
00160     abstract public function execute();
00161 
00172     protected function addOption( $name, $description, $required = false, $withArg = false, $shortName = false ) {
00173         $this->mParams[$name] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg, 'shortName' => $shortName );
00174         if ( $shortName !== false ) {
00175             $this->mShortParamsMap[$shortName] = $name;
00176         }
00177     }
00178 
00184     protected function hasOption( $name ) {
00185         return isset( $this->mOptions[$name] );
00186     }
00187 
00194     protected function getOption( $name, $default = null ) {
00195         if ( $this->hasOption( $name ) ) {
00196             return $this->mOptions[$name];
00197         } else {
00198             // Set it so we don't have to provide the default again
00199             $this->mOptions[$name] = $default;
00200             return $this->mOptions[$name];
00201         }
00202     }
00203 
00210     protected function addArg( $arg, $description, $required = true ) {
00211         $this->mArgList[] = array(
00212             'name' => $arg,
00213             'desc' => $description,
00214             'require' => $required
00215         );
00216     }
00217 
00222     protected function deleteOption( $name ) {
00223         unset( $this->mParams[$name] );
00224     }
00225 
00230     protected function addDescription( $text ) {
00231         $this->mDescription = $text;
00232     }
00233 
00239     protected function hasArg( $argId = 0 ) {
00240         return isset( $this->mArgs[$argId] );
00241     }
00242 
00249     protected function getArg( $argId = 0, $default = null ) {
00250         return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default;
00251     }
00252 
00257     protected function setBatchSize( $s = 0 ) {
00258         $this->mBatchSize = $s;
00259 
00260         // If we support $mBatchSize, show the option.
00261         // Used to be in addDefaultParams, but in order for that to
00262         // work, subclasses would have to call this function in the constructor
00263         // before they called parent::__construct which is just weird
00264         // (and really wasn't done).
00265         if ( $this->mBatchSize ) {
00266             $this->addOption( 'batch-size', 'Run this many operations ' .
00267                 'per batch, default: ' . $this->mBatchSize, false, true );
00268             if ( isset( $this->mParams['batch-size'] ) ) {
00269                 // This seems a little ugly...
00270                 $this->mDependantParameters['batch-size'] = $this->mParams['batch-size'];
00271             }
00272         }
00273     }
00274 
00279     public function getName() {
00280         return $this->mSelf;
00281     }
00282 
00290     protected function getStdin( $len = null ) {
00291         if ( $len == Maintenance::STDIN_ALL ) {
00292             return file_get_contents( 'php://stdin' );
00293         }
00294         $f = fopen( 'php://stdin', 'rt' );
00295         if ( !$len ) {
00296             return $f;
00297         }
00298         $input = fgets( $f, $len );
00299         fclose( $f );
00300         return rtrim( $input );
00301     }
00302 
00306     public function isQuiet() {
00307         return $this->mQuiet;
00308     }
00309 
00317     protected function output( $out, $channel = null ) {
00318         if ( $this->mQuiet ) {
00319             return;
00320         }
00321         if ( $channel === null ) {
00322             $this->cleanupChanneled();
00323             print $out;
00324         } else {
00325             $out = preg_replace( '/\n\z/', '', $out );
00326             $this->outputChanneled( $out, $channel );
00327         }
00328     }
00329 
00336     protected function error( $err, $die = 0 ) {
00337         $this->outputChanneled( false );
00338         if ( PHP_SAPI == 'cli' ) {
00339             fwrite( STDERR, $err . "\n" );
00340         } else {
00341             print $err;
00342         }
00343         $die = intval( $die );
00344         if ( $die > 0 ) {
00345             die( $die );
00346         }
00347     }
00348 
00349     private $atLineStart = true;
00350     private $lastChannel = null;
00351 
00355     public function cleanupChanneled() {
00356         if ( !$this->atLineStart ) {
00357             print "\n";
00358             $this->atLineStart = true;
00359         }
00360     }
00361 
00370     public function outputChanneled( $msg, $channel = null ) {
00371         if ( $msg === false ) {
00372             $this->cleanupChanneled();
00373             return;
00374         }
00375 
00376         // End the current line if necessary
00377         if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
00378             print "\n";
00379         }
00380 
00381         print $msg;
00382 
00383         $this->atLineStart = false;
00384         if ( $channel === null ) {
00385             // For unchanneled messages, output trailing newline immediately
00386             print "\n";
00387             $this->atLineStart = true;
00388         }
00389         $this->lastChannel = $channel;
00390     }
00391 
00402     public function getDbType() {
00403         return Maintenance::DB_STD;
00404     }
00405 
00409     protected function addDefaultParams() {
00410 
00411         # Generic (non script dependant) options:
00412 
00413         $this->addOption( 'help', 'Display this help message', false, false, 'h' );
00414         $this->addOption( 'quiet', 'Whether to supress non-error output', false, false, 'q' );
00415         $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true );
00416         $this->addOption( 'wiki', 'For specifying the wiki ID', false, true );
00417         $this->addOption( 'globals', 'Output globals at the end of processing for debugging' );
00418         $this->addOption( 'memory-limit', 'Set a specific memory limit for the script, "max" for no limit or "default" to avoid changing it' );
00419         $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " .
00420                 "http://en.wikipedia.org. This is sometimes necessary because " .
00421                 "server name detection may fail in command line scripts.", false, true );
00422         $this->addOption( 'profiler', 'Set to "text" or "trace" to show profiling output', false, true );
00423 
00424         # Save generic options to display them separately in help
00425         $this->mGenericParameters = $this->mParams;
00426 
00427         # Script dependant options:
00428 
00429         // If we support a DB, show the options
00430         if ( $this->getDbType() > 0 ) {
00431             $this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
00432             $this->addOption( 'dbpass', 'The password to use for this script', false, true );
00433         }
00434 
00435         # Save additional script dependant options to display
00436         # them separately in help
00437         $this->mDependantParameters = array_diff_key( $this->mParams, $this->mGenericParameters );
00438     }
00439 
00447     public function runChild( $maintClass, $classFile = null ) {
00448         // Make sure the class is loaded first
00449         if ( !class_exists( $maintClass ) ) {
00450             if ( $classFile ) {
00451                 require_once $classFile;
00452             }
00453             if ( !class_exists( $maintClass ) ) {
00454                 $this->error( "Cannot spawn child: $maintClass" );
00455             }
00456         }
00457 
00461         $child = new $maintClass();
00462         $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs );
00463         if ( !is_null( $this->mDb ) ) {
00464             $child->setDB( $this->mDb );
00465         }
00466         return $child;
00467     }
00468 
00472     public function setup() {
00473         global $IP, $wgCommandLineMode, $wgRequestTime;
00474 
00475         # Abort if called from a web server
00476         if ( isset( $_SERVER ) && isset( $_SERVER['REQUEST_METHOD'] ) ) {
00477             $this->error( 'This script must be run from the command line', true );
00478         }
00479 
00480         if ( $IP === null ) {
00481             $this->error( "\$IP not set, aborting!\n" .
00482                 '(Did you forget to call parent::__construct() in your maintenance script?)', 1 );
00483         }
00484 
00485         # Make sure we can handle script parameters
00486         if ( !defined( 'HPHP_VERSION' ) && !ini_get( 'register_argc_argv' ) ) {
00487             $this->error( 'Cannot get command line arguments, register_argc_argv is set to false', true );
00488         }
00489 
00490         // Send PHP warnings and errors to stderr instead of stdout.
00491         // This aids in diagnosing problems, while keeping messages
00492         // out of redirected output.
00493         if ( ini_get( 'display_errors' ) ) {
00494             ini_set( 'display_errors', 'stderr' );
00495         }
00496 
00497         $this->loadParamsAndArgs();
00498         $this->maybeHelp();
00499 
00500         # Set the memory limit
00501         # Note we need to set it again later in cache LocalSettings changed it
00502         $this->adjustMemoryLimit();
00503 
00504         # Set max execution time to 0 (no limit). PHP.net says that
00505         # "When running PHP from the command line the default setting is 0."
00506         # But sometimes this doesn't seem to be the case.
00507         ini_set( 'max_execution_time', 0 );
00508 
00509         $wgRequestTime = microtime( true );
00510 
00511         # Define us as being in MediaWiki
00512         define( 'MEDIAWIKI', true );
00513 
00514         $wgCommandLineMode = true;
00515 
00516         # Turn off output buffering if it's on
00517         while ( ob_get_level() > 0 ) {
00518             ob_end_flush();
00519         }
00520 
00521         $this->validateParamsAndArgs();
00522     }
00523 
00533     public function memoryLimit() {
00534         $limit = $this->getOption( 'memory-limit', 'max' );
00535         $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood
00536         return $limit;
00537     }
00538 
00542     protected function adjustMemoryLimit() {
00543         $limit = $this->memoryLimit();
00544         if ( $limit == 'max' ) {
00545             $limit = -1; // no memory limit
00546         }
00547         if ( $limit != 'default' ) {
00548             ini_set( 'memory_limit', $limit );
00549         }
00550     }
00551 
00555     public function clearParamsAndArgs() {
00556         $this->mOptions = array();
00557         $this->mArgs = array();
00558         $this->mInputLoaded = false;
00559     }
00560 
00570     public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
00571         # If we were given opts or args, set those and return early
00572         if ( $self ) {
00573             $this->mSelf = $self;
00574             $this->mInputLoaded = true;
00575         }
00576         if ( $opts ) {
00577             $this->mOptions = $opts;
00578             $this->mInputLoaded = true;
00579         }
00580         if ( $args ) {
00581             $this->mArgs = $args;
00582             $this->mInputLoaded = true;
00583         }
00584 
00585         # If we've already loaded input (either by user values or from $argv)
00586         # skip on loading it again. The array_shift() will corrupt values if
00587         # it's run again and again
00588         if ( $this->mInputLoaded ) {
00589             $this->loadSpecialVars();
00590             return;
00591         }
00592 
00593         global $argv;
00594         $this->mSelf = array_shift( $argv );
00595 
00596         $options = array();
00597         $args = array();
00598 
00599         # Parse arguments
00600         for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
00601             if ( $arg == '--' ) {
00602                 # End of options, remainder should be considered arguments
00603                 $arg = next( $argv );
00604                 while ( $arg !== false ) {
00605                     $args[] = $arg;
00606                     $arg = next( $argv );
00607                 }
00608                 break;
00609             } elseif ( substr( $arg, 0, 2 ) == '--' ) {
00610                 # Long options
00611                 $option = substr( $arg, 2 );
00612                 if ( array_key_exists( $option, $options ) ) {
00613                     $this->error( "\nERROR: $option parameter given twice\n" );
00614                     $this->maybeHelp( true );
00615                 }
00616                 if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) {
00617                     $param = next( $argv );
00618                     if ( $param === false ) {
00619                         $this->error( "\nERROR: $option parameter needs a value after it\n" );
00620                         $this->maybeHelp( true );
00621                     }
00622                     $options[$option] = $param;
00623                 } else {
00624                     $bits = explode( '=', $option, 2 );
00625                     if ( count( $bits ) > 1 ) {
00626                         $option = $bits[0];
00627                         $param = $bits[1];
00628                     } else {
00629                         $param = 1;
00630                     }
00631                     $options[$option] = $param;
00632                 }
00633             } elseif ( substr( $arg, 0, 1 ) == '-' ) {
00634                 # Short options
00635                 for ( $p = 1; $p < strlen( $arg ); $p++ ) {
00636                     $option = $arg { $p };
00637                     if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) {
00638                         $option = $this->mShortParamsMap[$option];
00639                     }
00640                     if ( array_key_exists( $option, $options ) ) {
00641                         $this->error( "\nERROR: $option parameter given twice\n" );
00642                         $this->maybeHelp( true );
00643                     }
00644                     if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) {
00645                         $param = next( $argv );
00646                         if ( $param === false ) {
00647                             $this->error( "\nERROR: $option parameter needs a value after it\n" );
00648                             $this->maybeHelp( true );
00649                         }
00650                         $options[$option] = $param;
00651                     } else {
00652                         $options[$option] = 1;
00653                     }
00654                 }
00655             } else {
00656                 $args[] = $arg;
00657             }
00658         }
00659 
00660         $this->mOptions = $options;
00661         $this->mArgs = $args;
00662         $this->loadSpecialVars();
00663         $this->mInputLoaded = true;
00664     }
00665 
00669     protected function validateParamsAndArgs() {
00670         $die = false;
00671         # Check to make sure we've got all the required options
00672         foreach ( $this->mParams as $opt => $info ) {
00673             if ( $info['require'] && !$this->hasOption( $opt ) ) {
00674                 $this->error( "Param $opt required!" );
00675                 $die = true;
00676             }
00677         }
00678         # Check arg list too
00679         foreach ( $this->mArgList as $k => $info ) {
00680             if ( $info['require'] && !$this->hasArg( $k ) ) {
00681                 $this->error( 'Argument <' . $info['name'] . '> required!' );
00682                 $die = true;
00683             }
00684         }
00685 
00686         if ( $die ) {
00687             $this->maybeHelp( true );
00688         }
00689     }
00690 
00694     protected function loadSpecialVars() {
00695         if ( $this->hasOption( 'dbuser' ) ) {
00696             $this->mDbUser = $this->getOption( 'dbuser' );
00697         }
00698         if ( $this->hasOption( 'dbpass' ) ) {
00699             $this->mDbPass = $this->getOption( 'dbpass' );
00700         }
00701         if ( $this->hasOption( 'quiet' ) ) {
00702             $this->mQuiet = true;
00703         }
00704         if ( $this->hasOption( 'batch-size' ) ) {
00705             $this->mBatchSize = intval( $this->getOption( 'batch-size' ) );
00706         }
00707     }
00708 
00713     protected function maybeHelp( $force = false ) {
00714         if ( !$force && !$this->hasOption( 'help' ) ) {
00715             return;
00716         }
00717 
00718         $screenWidth = 80; // TODO: Caculate this!
00719         $tab = "    ";
00720         $descWidth = $screenWidth - ( 2 * strlen( $tab ) );
00721 
00722         ksort( $this->mParams );
00723         $this->mQuiet = false;
00724 
00725         // Description ...
00726         if ( $this->mDescription ) {
00727             $this->output( "\n" . $this->mDescription . "\n" );
00728         }
00729         $output = "\nUsage: php " . basename( $this->mSelf );
00730 
00731         // ... append parameters ...
00732         if ( $this->mParams ) {
00733             $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]";
00734         }
00735 
00736         // ... and append arguments.
00737         if ( $this->mArgList ) {
00738             $output .= ' ';
00739             foreach ( $this->mArgList as $k => $arg ) {
00740                 if ( $arg['require'] ) {
00741                     $output .= '<' . $arg['name'] . '>';
00742                 } else {
00743                     $output .= '[' . $arg['name'] . ']';
00744                 }
00745                 if ( $k < count( $this->mArgList ) - 1 ) {
00746                     $output .= ' ';
00747                 }
00748             }
00749         }
00750         $this->output( "$output\n\n" );
00751 
00752         # TODO abstract some repetitive code below
00753 
00754         // Generic parameters
00755         $this->output( "Generic maintenance parameters:\n" );
00756         foreach ( $this->mGenericParameters as $par => $info ) {
00757             if ( $info['shortName'] !== false ) {
00758                 $par .= " (-{$info['shortName']})";
00759             }
00760             $this->output(
00761                 wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
00762                         "\n$tab$tab" ) . "\n"
00763             );
00764         }
00765         $this->output( "\n" );
00766 
00767         $scriptDependantParams = $this->mDependantParameters;
00768         if ( count( $scriptDependantParams ) > 0 ) {
00769             $this->output( "Script dependant parameters:\n" );
00770             // Parameters description
00771             foreach ( $scriptDependantParams as $par => $info ) {
00772                 if ( $info['shortName'] !== false ) {
00773                     $par .= " (-{$info['shortName']})";
00774                 }
00775                 $this->output(
00776                     wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
00777                             "\n$tab$tab" ) . "\n"
00778                 );
00779             }
00780             $this->output( "\n" );
00781         }
00782 
00783         // Script specific parameters not defined on construction by
00784         // Maintenance::addDefaultParams()
00785         $scriptSpecificParams = array_diff_key(
00786             # all script parameters:
00787             $this->mParams,
00788             # remove the Maintenance default parameters:
00789             $this->mGenericParameters,
00790             $this->mDependantParameters
00791         );
00792         if ( count( $scriptSpecificParams ) > 0 ) {
00793             $this->output( "Script specific parameters:\n" );
00794             // Parameters description
00795             foreach ( $scriptSpecificParams as $par => $info ) {
00796                 if ( $info['shortName'] !== false ) {
00797                     $par .= " (-{$info['shortName']})";
00798                 }
00799                 $this->output(
00800                     wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
00801                             "\n$tab$tab" ) . "\n"
00802                 );
00803             }
00804             $this->output( "\n" );
00805         }
00806 
00807         // Print arguments
00808         if ( count( $this->mArgList ) > 0 ) {
00809             $this->output( "Arguments:\n" );
00810             // Arguments description
00811             foreach ( $this->mArgList as $info ) {
00812                 $openChar = $info['require'] ? '<' : '[';
00813                 $closeChar = $info['require'] ? '>' : ']';
00814                 $this->output(
00815                     wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " .
00816                         $info['desc'], $descWidth, "\n$tab$tab" ) . "\n"
00817                 );
00818             }
00819             $this->output( "\n" );
00820         }
00821 
00822         die( 1 );
00823     }
00824 
00828     public function finalSetup() {
00829         global $wgCommandLineMode, $wgShowSQLErrors, $wgServer;
00830         global $wgDBadminuser, $wgDBadminpassword;
00831         global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf;
00832 
00833         # Turn off output buffering again, it might have been turned on in the settings files
00834         if ( ob_get_level() ) {
00835             ob_end_flush();
00836         }
00837         # Same with these
00838         $wgCommandLineMode = true;
00839 
00840         # Override $wgServer
00841         if ( $this->hasOption( 'server' ) ) {
00842             $wgServer = $this->getOption( 'server', $wgServer );
00843         }
00844 
00845         # If these were passed, use them
00846         if ( $this->mDbUser ) {
00847             $wgDBadminuser = $this->mDbUser;
00848         }
00849         if ( $this->mDbPass ) {
00850             $wgDBadminpassword = $this->mDbPass;
00851         }
00852 
00853         if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) {
00854             $wgDBuser = $wgDBadminuser;
00855             $wgDBpassword = $wgDBadminpassword;
00856 
00857             if ( $wgDBservers ) {
00861                 foreach ( $wgDBservers as $i => $server ) {
00862                     $wgDBservers[$i]['user'] = $wgDBuser;
00863                     $wgDBservers[$i]['password'] = $wgDBpassword;
00864                 }
00865             }
00866             if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
00867                 $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
00868                 $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
00869             }
00870             LBFactory::destroyInstance();
00871         }
00872 
00873         $this->afterFinalSetup();
00874 
00875         $wgShowSQLErrors = true;
00876         @set_time_limit( 0 );
00877         $this->adjustMemoryLimit();
00878 
00879         // Per-script profiling; useful for debugging
00880         $forcedProfiler = $this->getOption( 'profiler' );
00881         if ( $forcedProfiler === 'text' ) {
00882             Profiler::setInstance( new ProfilerSimpleText( array() ) );
00883             Profiler::instance()->setTemplated( true );
00884         } elseif ( $forcedProfiler === 'trace' ) {
00885             Profiler::setInstance( new ProfilerSimpleTrace( array() ) );
00886             Profiler::instance()->setTemplated( true );
00887         }
00888     }
00889 
00893     protected function afterFinalSetup() {
00894         if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
00895             call_user_func( MW_CMDLINE_CALLBACK );
00896         }
00897     }
00898 
00903     public function globals() {
00904         if ( $this->hasOption( 'globals' ) ) {
00905             print_r( $GLOBALS );
00906         }
00907     }
00908 
00913     public function loadSettings() {
00914         global $wgCommandLineMode, $IP;
00915 
00916         if ( isset( $this->mOptions['conf'] ) ) {
00917             $settingsFile = $this->mOptions['conf'];
00918         } elseif ( defined( "MW_CONFIG_FILE" ) ) {
00919             $settingsFile = MW_CONFIG_FILE;
00920         } else {
00921             $settingsFile = "$IP/LocalSettings.php";
00922         }
00923         if ( isset( $this->mOptions['wiki'] ) ) {
00924             $bits = explode( '-', $this->mOptions['wiki'] );
00925             if ( count( $bits ) == 1 ) {
00926                 $bits[] = '';
00927             }
00928             define( 'MW_DB', $bits[0] );
00929             define( 'MW_PREFIX', $bits[1] );
00930         }
00931 
00932         if ( !is_readable( $settingsFile ) ) {
00933             $this->error( "A copy of your installation's LocalSettings.php\n" .
00934                         "must exist and be readable in the source directory.\n" .
00935                         "Use --conf to specify it.", true );
00936         }
00937         $wgCommandLineMode = true;
00938         return $settingsFile;
00939     }
00940 
00946     public function purgeRedundantText( $delete = true ) {
00947         # Data should come off the master, wrapped in a transaction
00948         $dbw = $this->getDB( DB_MASTER );
00949         $dbw->begin( __METHOD__ );
00950 
00951         # Get "active" text records from the revisions table
00952         $this->output( 'Searching for active text records in revisions table...' );
00953         $res = $dbw->select( 'revision', 'rev_text_id', array(), __METHOD__, array( 'DISTINCT' ) );
00954         foreach ( $res as $row ) {
00955             $cur[] = $row->rev_text_id;
00956         }
00957         $this->output( "done.\n" );
00958 
00959         # Get "active" text records from the archive table
00960         $this->output( 'Searching for active text records in archive table...' );
00961         $res = $dbw->select( 'archive', 'ar_text_id', array(), __METHOD__, array( 'DISTINCT' ) );
00962         foreach ( $res as $row ) {
00963             # old pre-MW 1.5 records can have null ar_text_id's.
00964             if ( $row->ar_text_id !== null ) {
00965                 $cur[] = $row->ar_text_id;
00966             }
00967         }
00968         $this->output( "done.\n" );
00969 
00970         # Get the IDs of all text records not in these sets
00971         $this->output( 'Searching for inactive text records...' );
00972         $cond = 'old_id NOT IN ( ' . $dbw->makeList( $cur ) . ' )';
00973         $res = $dbw->select( 'text', 'old_id', array( $cond ), __METHOD__, array( 'DISTINCT' ) );
00974         $old = array();
00975         foreach ( $res as $row ) {
00976             $old[] = $row->old_id;
00977         }
00978         $this->output( "done.\n" );
00979 
00980         # Inform the user of what we're going to do
00981         $count = count( $old );
00982         $this->output( "$count inactive items found.\n" );
00983 
00984         # Delete as appropriate
00985         if ( $delete && $count ) {
00986             $this->output( 'Deleting...' );
00987             $dbw->delete( 'text', array( 'old_id' => $old ), __METHOD__ );
00988             $this->output( "done.\n" );
00989         }
00990 
00991         # Done
00992         $dbw->commit( __METHOD__ );
00993     }
00994 
00999     protected function getDir() {
01000         return __DIR__;
01001     }
01002 
01010     protected function &getDB( $db, $groups = array(), $wiki = false ) {
01011         if ( is_null( $this->mDb ) ) {
01012             return wfGetDB( $db, $groups, $wiki );
01013         } else {
01014             return $this->mDb;
01015         }
01016     }
01017 
01023     public function setDB( &$db ) {
01024         $this->mDb = $db;
01025     }
01026 
01031     private function lockSearchindex( &$db ) {
01032         $write = array( 'searchindex' );
01033         $read = array( 'page', 'revision', 'text', 'interwiki', 'l10n_cache', 'user' );
01034         $db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ );
01035     }
01036 
01041     private function unlockSearchindex( &$db ) {
01042         $db->unlockTables( __CLASS__ . '::' . __METHOD__ );
01043     }
01044 
01050     private function relockSearchindex( &$db ) {
01051         $this->unlockSearchindex( $db );
01052         $this->lockSearchindex( $db );
01053     }
01054 
01062     public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) {
01063         $lockTime = time();
01064 
01065         # Lock searchindex
01066         if ( $maxLockTime ) {
01067             $this->output( "   --- Waiting for lock ---" );
01068             $this->lockSearchindex( $dbw );
01069             $lockTime = time();
01070             $this->output( "\n" );
01071         }
01072 
01073         # Loop through the results and do a search update
01074         foreach ( $results as $row ) {
01075             # Allow reads to be processed
01076             if ( $maxLockTime && time() > $lockTime + $maxLockTime ) {
01077                 $this->output( "    --- Relocking ---" );
01078                 $this->relockSearchindex( $dbw );
01079                 $lockTime = time();
01080                 $this->output( "\n" );
01081             }
01082             call_user_func( $callback, $dbw, $row );
01083         }
01084 
01085         # Unlock searchindex
01086         if ( $maxLockTime ) {
01087             $this->output( "    --- Unlocking --" );
01088             $this->unlockSearchindex( $dbw );
01089             $this->output( "\n" );
01090         }
01091 
01092     }
01093 
01100     public function updateSearchIndexForPage( $dbw, $pageId ) {
01101         // Get current revision
01102         $rev = Revision::loadFromPageId( $dbw, $pageId );
01103         $title = null;
01104         if ( $rev ) {
01105             $titleObj = $rev->getTitle();
01106             $title = $titleObj->getPrefixedDBkey();
01107             $this->output( "$title..." );
01108             # Update searchindex
01109             $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getContent() );
01110             $u->doUpdate();
01111             $this->output( "\n" );
01112         }
01113         return $title;
01114     }
01115 
01124     public static function posix_isatty( $fd ) {
01125         if ( !function_exists( 'posix_isatty' ) ) {
01126             return !$fd;
01127         } else {
01128             return posix_isatty( $fd );
01129         }
01130     }
01131 
01137     public static function readconsole( $prompt = '> ' ) {
01138         static $isatty = null;
01139         if ( is_null( $isatty ) ) {
01140             $isatty = self::posix_isatty( 0 /*STDIN*/ );
01141         }
01142 
01143         if ( $isatty && function_exists( 'readline' ) ) {
01144             return readline( $prompt );
01145         } else {
01146             if ( $isatty ) {
01147                 $st = self::readlineEmulation( $prompt );
01148             } else {
01149                 if ( feof( STDIN ) ) {
01150                     $st = false;
01151                 } else {
01152                     $st = fgets( STDIN, 1024 );
01153                 }
01154             }
01155             if ( $st === false ) {
01156                 return false;
01157             }
01158             $resp = trim( $st );
01159             return $resp;
01160         }
01161     }
01162 
01168     private static function readlineEmulation( $prompt ) {
01169         $bash = Installer::locateExecutableInDefaultPaths( array( 'bash' ) );
01170         if ( !wfIsWindows() && $bash ) {
01171             $retval = false;
01172             $encPrompt = wfEscapeShellArg( $prompt );
01173             $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
01174             $encCommand = wfEscapeShellArg( $command );
01175             $line = wfShellExec( "$bash -c $encCommand", $retval, array(), array( 'walltime' => 0 ) );
01176 
01177             if ( $retval == 0 ) {
01178                 return $line;
01179             } elseif ( $retval == 127 ) {
01180                 // Couldn't execute bash even though we thought we saw it.
01181                 // Shell probably spit out an error message, sorry :(
01182                 // Fall through to fgets()...
01183             } else {
01184                 // EOF/ctrl+D
01185                 return false;
01186             }
01187         }
01188 
01189         // Fallback... we'll have no editing controls, EWWW
01190         if ( feof( STDIN ) ) {
01191             return false;
01192         }
01193         print $prompt;
01194         return fgets( STDIN, 1024 );
01195     }
01196 }
01197 
01201 class FakeMaintenance extends Maintenance {
01202     protected $mSelf = "FakeMaintenanceScript";
01203     public function execute() {
01204         return;
01205     }
01206 }
01207 
01212 abstract class LoggedUpdateMaintenance extends Maintenance {
01213     public function __construct() {
01214         parent::__construct();
01215         $this->addOption( 'force', 'Run the update even if it was completed already' );
01216         $this->setBatchSize( 200 );
01217     }
01218 
01219     public function execute() {
01220         $db = $this->getDB( DB_MASTER );
01221         $key = $this->getUpdateKey();
01222 
01223         if ( !$this->hasOption( 'force' )
01224             && $db->selectRow( 'updatelog', '1', array( 'ul_key' => $key ), __METHOD__ )
01225         ) {
01226             $this->output( "..." . $this->updateSkippedMessage() . "\n" );
01227             return true;
01228         }
01229 
01230         if ( !$this->doDBUpdates() ) {
01231             return false;
01232         }
01233 
01234         if ( $db->insert( 'updatelog', array( 'ul_key' => $key ), __METHOD__, 'IGNORE' ) ) {
01235             return true;
01236         } else {
01237             $this->output( $this->updatelogFailedMessage() . "\n" );
01238             return false;
01239         }
01240     }
01241 
01246     protected function updateSkippedMessage() {
01247         $key = $this->getUpdateKey();
01248         return "Update '{$key}' already logged as completed.";
01249     }
01250 
01255     protected function updatelogFailedMessage() {
01256         $key = $this->getUpdateKey();
01257         return "Unable to log update '{$key}' as completed.";
01258     }
01259 
01265     abstract protected function doDBUpdates();
01266 
01271     abstract protected function getUpdateKey();
01272 }