MediaWiki  REL1_24
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 {
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 
00108     private $mDb = null;
00109 
00114     public $fileHandle;
00115 
00121     private $config;
00122 
00127     public function __construct() {
00128         // Setup $IP, using MW_INSTALL_PATH if it exists
00129         global $IP;
00130         $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== ''
00131             ? getenv( 'MW_INSTALL_PATH' )
00132             : realpath( __DIR__ . '/..' );
00133 
00134         $this->addDefaultParams();
00135         register_shutdown_function( array( $this, 'outputChanneled' ), false );
00136     }
00137 
00145     public static function shouldExecute() {
00146         global $wgCommandLineMode;
00147 
00148         if ( !function_exists( 'debug_backtrace' ) ) {
00149             // If someone has a better idea...
00150             return $wgCommandLineMode;
00151         }
00152 
00153         $bt = debug_backtrace();
00154         $count = count( $bt );
00155         if ( $count < 2 ) {
00156             return false; // sanity
00157         }
00158         if ( $bt[0]['class'] !== 'Maintenance' || $bt[0]['function'] !== 'shouldExecute' ) {
00159             return false; // last call should be to this function
00160         }
00161         $includeFuncs = array( 'require_once', 'require', 'include', 'include_once' );
00162         for ( $i = 1; $i < $count; $i++ ) {
00163             if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) {
00164                 return false; // previous calls should all be "requires"
00165             }
00166         }
00167 
00168         return true;
00169     }
00170 
00174     abstract public function execute();
00175 
00186     protected function addOption( $name, $description, $required = false,
00187         $withArg = false, $shortName = false
00188     ) {
00189         $this->mParams[$name] = array(
00190             'desc' => $description,
00191             'require' => $required,
00192             'withArg' => $withArg,
00193             'shortName' => $shortName
00194         );
00195 
00196         if ( $shortName !== false ) {
00197             $this->mShortParamsMap[$shortName] = $name;
00198         }
00199     }
00200 
00206     protected function hasOption( $name ) {
00207         return isset( $this->mOptions[$name] );
00208     }
00209 
00216     protected function getOption( $name, $default = null ) {
00217         if ( $this->hasOption( $name ) ) {
00218             return $this->mOptions[$name];
00219         } else {
00220             // Set it so we don't have to provide the default again
00221             $this->mOptions[$name] = $default;
00222 
00223             return $this->mOptions[$name];
00224         }
00225     }
00226 
00233     protected function addArg( $arg, $description, $required = true ) {
00234         $this->mArgList[] = array(
00235             'name' => $arg,
00236             'desc' => $description,
00237             'require' => $required
00238         );
00239     }
00240 
00245     protected function deleteOption( $name ) {
00246         unset( $this->mParams[$name] );
00247     }
00248 
00253     protected function addDescription( $text ) {
00254         $this->mDescription = $text;
00255     }
00256 
00262     protected function hasArg( $argId = 0 ) {
00263         return isset( $this->mArgs[$argId] );
00264     }
00265 
00272     protected function getArg( $argId = 0, $default = null ) {
00273         return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default;
00274     }
00275 
00280     protected function setBatchSize( $s = 0 ) {
00281         $this->mBatchSize = $s;
00282 
00283         // If we support $mBatchSize, show the option.
00284         // Used to be in addDefaultParams, but in order for that to
00285         // work, subclasses would have to call this function in the constructor
00286         // before they called parent::__construct which is just weird
00287         // (and really wasn't done).
00288         if ( $this->mBatchSize ) {
00289             $this->addOption( 'batch-size', 'Run this many operations ' .
00290                 'per batch, default: ' . $this->mBatchSize, false, true );
00291             if ( isset( $this->mParams['batch-size'] ) ) {
00292                 // This seems a little ugly...
00293                 $this->mDependantParameters['batch-size'] = $this->mParams['batch-size'];
00294             }
00295         }
00296     }
00297 
00302     public function getName() {
00303         return $this->mSelf;
00304     }
00305 
00312     protected function getStdin( $len = null ) {
00313         if ( $len == Maintenance::STDIN_ALL ) {
00314             return file_get_contents( 'php://stdin' );
00315         }
00316         $f = fopen( 'php://stdin', 'rt' );
00317         if ( !$len ) {
00318             return $f;
00319         }
00320         $input = fgets( $f, $len );
00321         fclose( $f );
00322 
00323         return rtrim( $input );
00324     }
00325 
00329     public function isQuiet() {
00330         return $this->mQuiet;
00331     }
00332 
00339     protected function output( $out, $channel = null ) {
00340         if ( $this->mQuiet ) {
00341             return;
00342         }
00343         if ( $channel === null ) {
00344             $this->cleanupChanneled();
00345             print $out;
00346         } else {
00347             $out = preg_replace( '/\n\z/', '', $out );
00348             $this->outputChanneled( $out, $channel );
00349         }
00350     }
00351 
00358     protected function error( $err, $die = 0 ) {
00359         $this->outputChanneled( false );
00360         if ( PHP_SAPI == 'cli' ) {
00361             fwrite( STDERR, $err . "\n" );
00362         } else {
00363             print $err;
00364         }
00365         $die = intval( $die );
00366         if ( $die > 0 ) {
00367             die( $die );
00368         }
00369     }
00370 
00371     private $atLineStart = true;
00372     private $lastChannel = null;
00373 
00377     public function cleanupChanneled() {
00378         if ( !$this->atLineStart ) {
00379             print "\n";
00380             $this->atLineStart = true;
00381         }
00382     }
00383 
00392     public function outputChanneled( $msg, $channel = null ) {
00393         if ( $msg === false ) {
00394             $this->cleanupChanneled();
00395 
00396             return;
00397         }
00398 
00399         // End the current line if necessary
00400         if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
00401             print "\n";
00402         }
00403 
00404         print $msg;
00405 
00406         $this->atLineStart = false;
00407         if ( $channel === null ) {
00408             // For unchanneled messages, output trailing newline immediately
00409             print "\n";
00410             $this->atLineStart = true;
00411         }
00412         $this->lastChannel = $channel;
00413     }
00414 
00425     public function getDbType() {
00426         return Maintenance::DB_STD;
00427     }
00428 
00432     protected function addDefaultParams() {
00433 
00434         # Generic (non script dependant) options:
00435 
00436         $this->addOption( 'help', 'Display this help message', false, false, 'h' );
00437         $this->addOption( 'quiet', 'Whether to supress non-error output', false, false, 'q' );
00438         $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true );
00439         $this->addOption( 'wiki', 'For specifying the wiki ID', false, true );
00440         $this->addOption( 'globals', 'Output globals at the end of processing for debugging' );
00441         $this->addOption(
00442             'memory-limit',
00443             'Set a specific memory limit for the script, '
00444                 . '"max" for no limit or "default" to avoid changing it'
00445         );
00446         $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " .
00447             "http://en.wikipedia.org. This is sometimes necessary because " .
00448             "server name detection may fail in command line scripts.", false, true );
00449         $this->addOption( 'profiler', 'Set to "text" or "trace" to show profiling output', false, true );
00450 
00451         # Save generic options to display them separately in help
00452         $this->mGenericParameters = $this->mParams;
00453 
00454         # Script dependant options:
00455 
00456         // If we support a DB, show the options
00457         if ( $this->getDbType() > 0 ) {
00458             $this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
00459             $this->addOption( 'dbpass', 'The password to use for this script', false, true );
00460         }
00461 
00462         # Save additional script dependant options to display
00463         # them separately in help
00464         $this->mDependantParameters = array_diff_key( $this->mParams, $this->mGenericParameters );
00465     }
00466 
00471     public function getConfig() {
00472         if ( $this->config === null ) {
00473             $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
00474         }
00475 
00476         return $this->config;
00477     }
00478 
00483     public function setConfig( Config $config ) {
00484         $this->config = $config;
00485     }
00486 
00494     public function runChild( $maintClass, $classFile = null ) {
00495         // Make sure the class is loaded first
00496         if ( !class_exists( $maintClass ) ) {
00497             if ( $classFile ) {
00498                 require_once $classFile;
00499             }
00500             if ( !class_exists( $maintClass ) ) {
00501                 $this->error( "Cannot spawn child: $maintClass" );
00502             }
00503         }
00504 
00508         $child = new $maintClass();
00509         $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs );
00510         if ( !is_null( $this->mDb ) ) {
00511             $child->setDB( $this->mDb );
00512         }
00513 
00514         return $child;
00515     }
00516 
00520     public function setup() {
00521         global $IP, $wgCommandLineMode, $wgRequestTime;
00522 
00523         # Abort if called from a web server
00524         if ( isset( $_SERVER ) && isset( $_SERVER['REQUEST_METHOD'] ) ) {
00525             $this->error( 'This script must be run from the command line', true );
00526         }
00527 
00528         if ( $IP === null ) {
00529             $this->error( "\$IP not set, aborting!\n" .
00530                 '(Did you forget to call parent::__construct() in your maintenance script?)', 1 );
00531         }
00532 
00533         # Make sure we can handle script parameters
00534         if ( !defined( 'HPHP_VERSION' ) && !ini_get( 'register_argc_argv' ) ) {
00535             $this->error( 'Cannot get command line arguments, register_argc_argv is set to false', true );
00536         }
00537 
00538         // Send PHP warnings and errors to stderr instead of stdout.
00539         // This aids in diagnosing problems, while keeping messages
00540         // out of redirected output.
00541         if ( ini_get( 'display_errors' ) ) {
00542             ini_set( 'display_errors', 'stderr' );
00543         }
00544 
00545         $this->loadParamsAndArgs();
00546         $this->maybeHelp();
00547 
00548         # Set the memory limit
00549         # Note we need to set it again later in cache LocalSettings changed it
00550         $this->adjustMemoryLimit();
00551 
00552         # Set max execution time to 0 (no limit). PHP.net says that
00553         # "When running PHP from the command line the default setting is 0."
00554         # But sometimes this doesn't seem to be the case.
00555         ini_set( 'max_execution_time', 0 );
00556 
00557         $wgRequestTime = microtime( true );
00558 
00559         # Define us as being in MediaWiki
00560         define( 'MEDIAWIKI', true );
00561 
00562         $wgCommandLineMode = true;
00563 
00564         # Turn off output buffering if it's on
00565         while ( ob_get_level() > 0 ) {
00566             ob_end_flush();
00567         }
00568 
00569         $this->validateParamsAndArgs();
00570     }
00571 
00581     public function memoryLimit() {
00582         $limit = $this->getOption( 'memory-limit', 'max' );
00583         $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood
00584         return $limit;
00585     }
00586 
00590     protected function adjustMemoryLimit() {
00591         $limit = $this->memoryLimit();
00592         if ( $limit == 'max' ) {
00593             $limit = -1; // no memory limit
00594         }
00595         if ( $limit != 'default' ) {
00596             ini_set( 'memory_limit', $limit );
00597         }
00598     }
00599 
00603     public function clearParamsAndArgs() {
00604         $this->mOptions = array();
00605         $this->mArgs = array();
00606         $this->mInputLoaded = false;
00607     }
00608 
00618     public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
00619         # If we were given opts or args, set those and return early
00620         if ( $self ) {
00621             $this->mSelf = $self;
00622             $this->mInputLoaded = true;
00623         }
00624         if ( $opts ) {
00625             $this->mOptions = $opts;
00626             $this->mInputLoaded = true;
00627         }
00628         if ( $args ) {
00629             $this->mArgs = $args;
00630             $this->mInputLoaded = true;
00631         }
00632 
00633         # If we've already loaded input (either by user values or from $argv)
00634         # skip on loading it again. The array_shift() will corrupt values if
00635         # it's run again and again
00636         if ( $this->mInputLoaded ) {
00637             $this->loadSpecialVars();
00638 
00639             return;
00640         }
00641 
00642         global $argv;
00643         $this->mSelf = array_shift( $argv );
00644 
00645         $options = array();
00646         $args = array();
00647 
00648         # Parse arguments
00649         for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
00650             if ( $arg == '--' ) {
00651                 # End of options, remainder should be considered arguments
00652                 $arg = next( $argv );
00653                 while ( $arg !== false ) {
00654                     $args[] = $arg;
00655                     $arg = next( $argv );
00656                 }
00657                 break;
00658             } elseif ( substr( $arg, 0, 2 ) == '--' ) {
00659                 # Long options
00660                 $option = substr( $arg, 2 );
00661                 if ( array_key_exists( $option, $options ) ) {
00662                     $this->error( "\nERROR: $option parameter given twice\n" );
00663                     $this->maybeHelp( true );
00664                 }
00665                 if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) {
00666                     $param = next( $argv );
00667                     if ( $param === false ) {
00668                         $this->error( "\nERROR: $option parameter needs a value after it\n" );
00669                         $this->maybeHelp( true );
00670                     }
00671                     $options[$option] = $param;
00672                 } else {
00673                     $bits = explode( '=', $option, 2 );
00674                     if ( count( $bits ) > 1 ) {
00675                         $option = $bits[0];
00676                         $param = $bits[1];
00677                     } else {
00678                         $param = 1;
00679                     }
00680                     $options[$option] = $param;
00681                 }
00682             } elseif ( substr( $arg, 0, 1 ) == '-' ) {
00683                 # Short options
00684                 $argLength = strlen( $arg );
00685                 for ( $p = 1; $p < $argLength; $p++ ) {
00686                     $option = $arg[$p];
00687                     if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) {
00688                         $option = $this->mShortParamsMap[$option];
00689                     }
00690                     if ( array_key_exists( $option, $options ) ) {
00691                         $this->error( "\nERROR: $option parameter given twice\n" );
00692                         $this->maybeHelp( true );
00693                     }
00694                     if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) {
00695                         $param = next( $argv );
00696                         if ( $param === false ) {
00697                             $this->error( "\nERROR: $option parameter needs a value after it\n" );
00698                             $this->maybeHelp( true );
00699                         }
00700                         $options[$option] = $param;
00701                     } else {
00702                         $options[$option] = 1;
00703                     }
00704                 }
00705             } else {
00706                 $args[] = $arg;
00707             }
00708         }
00709 
00710         $this->mOptions = $options;
00711         $this->mArgs = $args;
00712         $this->loadSpecialVars();
00713         $this->mInputLoaded = true;
00714     }
00715 
00719     protected function validateParamsAndArgs() {
00720         $die = false;
00721         # Check to make sure we've got all the required options
00722         foreach ( $this->mParams as $opt => $info ) {
00723             if ( $info['require'] && !$this->hasOption( $opt ) ) {
00724                 $this->error( "Param $opt required!" );
00725                 $die = true;
00726             }
00727         }
00728         # Check arg list too
00729         foreach ( $this->mArgList as $k => $info ) {
00730             if ( $info['require'] && !$this->hasArg( $k ) ) {
00731                 $this->error( 'Argument <' . $info['name'] . '> required!' );
00732                 $die = true;
00733             }
00734         }
00735 
00736         if ( $die ) {
00737             $this->maybeHelp( true );
00738         }
00739     }
00740 
00744     protected function loadSpecialVars() {
00745         if ( $this->hasOption( 'dbuser' ) ) {
00746             $this->mDbUser = $this->getOption( 'dbuser' );
00747         }
00748         if ( $this->hasOption( 'dbpass' ) ) {
00749             $this->mDbPass = $this->getOption( 'dbpass' );
00750         }
00751         if ( $this->hasOption( 'quiet' ) ) {
00752             $this->mQuiet = true;
00753         }
00754         if ( $this->hasOption( 'batch-size' ) ) {
00755             $this->mBatchSize = intval( $this->getOption( 'batch-size' ) );
00756         }
00757     }
00758 
00763     protected function maybeHelp( $force = false ) {
00764         if ( !$force && !$this->hasOption( 'help' ) ) {
00765             return;
00766         }
00767 
00768         $screenWidth = 80; // TODO: Calculate this!
00769         $tab = "    ";
00770         $descWidth = $screenWidth - ( 2 * strlen( $tab ) );
00771 
00772         ksort( $this->mParams );
00773         $this->mQuiet = false;
00774 
00775         // Description ...
00776         if ( $this->mDescription ) {
00777             $this->output( "\n" . $this->mDescription . "\n" );
00778         }
00779         $output = "\nUsage: php " . basename( $this->mSelf );
00780 
00781         // ... append parameters ...
00782         if ( $this->mParams ) {
00783             $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]";
00784         }
00785 
00786         // ... and append arguments.
00787         if ( $this->mArgList ) {
00788             $output .= ' ';
00789             foreach ( $this->mArgList as $k => $arg ) {
00790                 if ( $arg['require'] ) {
00791                     $output .= '<' . $arg['name'] . '>';
00792                 } else {
00793                     $output .= '[' . $arg['name'] . ']';
00794                 }
00795                 if ( $k < count( $this->mArgList ) - 1 ) {
00796                     $output .= ' ';
00797                 }
00798             }
00799         }
00800         $this->output( "$output\n\n" );
00801 
00802         # TODO abstract some repetitive code below
00803 
00804         // Generic parameters
00805         $this->output( "Generic maintenance parameters:\n" );
00806         foreach ( $this->mGenericParameters as $par => $info ) {
00807             if ( $info['shortName'] !== false ) {
00808                 $par .= " (-{$info['shortName']})";
00809             }
00810             $this->output(
00811                 wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
00812                     "\n$tab$tab" ) . "\n"
00813             );
00814         }
00815         $this->output( "\n" );
00816 
00817         $scriptDependantParams = $this->mDependantParameters;
00818         if ( count( $scriptDependantParams ) > 0 ) {
00819             $this->output( "Script dependant parameters:\n" );
00820             // Parameters description
00821             foreach ( $scriptDependantParams as $par => $info ) {
00822                 if ( $info['shortName'] !== false ) {
00823                     $par .= " (-{$info['shortName']})";
00824                 }
00825                 $this->output(
00826                     wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
00827                         "\n$tab$tab" ) . "\n"
00828                 );
00829             }
00830             $this->output( "\n" );
00831         }
00832 
00833         // Script specific parameters not defined on construction by
00834         // Maintenance::addDefaultParams()
00835         $scriptSpecificParams = array_diff_key(
00836             # all script parameters:
00837             $this->mParams,
00838             # remove the Maintenance default parameters:
00839             $this->mGenericParameters,
00840             $this->mDependantParameters
00841         );
00842         if ( count( $scriptSpecificParams ) > 0 ) {
00843             $this->output( "Script specific parameters:\n" );
00844             // Parameters description
00845             foreach ( $scriptSpecificParams as $par => $info ) {
00846                 if ( $info['shortName'] !== false ) {
00847                     $par .= " (-{$info['shortName']})";
00848                 }
00849                 $this->output(
00850                     wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
00851                         "\n$tab$tab" ) . "\n"
00852                 );
00853             }
00854             $this->output( "\n" );
00855         }
00856 
00857         // Print arguments
00858         if ( count( $this->mArgList ) > 0 ) {
00859             $this->output( "Arguments:\n" );
00860             // Arguments description
00861             foreach ( $this->mArgList as $info ) {
00862                 $openChar = $info['require'] ? '<' : '[';
00863                 $closeChar = $info['require'] ? '>' : ']';
00864                 $this->output(
00865                     wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " .
00866                         $info['desc'], $descWidth, "\n$tab$tab" ) . "\n"
00867                 );
00868             }
00869             $this->output( "\n" );
00870         }
00871 
00872         die( 1 );
00873     }
00874 
00878     public function finalSetup() {
00879         global $wgCommandLineMode, $wgShowSQLErrors, $wgServer;
00880         global $wgDBadminuser, $wgDBadminpassword;
00881         global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf;
00882 
00883         # Turn off output buffering again, it might have been turned on in the settings files
00884         if ( ob_get_level() ) {
00885             ob_end_flush();
00886         }
00887         # Same with these
00888         $wgCommandLineMode = true;
00889 
00890         # Override $wgServer
00891         if ( $this->hasOption( 'server' ) ) {
00892             $wgServer = $this->getOption( 'server', $wgServer );
00893         }
00894 
00895         # If these were passed, use them
00896         if ( $this->mDbUser ) {
00897             $wgDBadminuser = $this->mDbUser;
00898         }
00899         if ( $this->mDbPass ) {
00900             $wgDBadminpassword = $this->mDbPass;
00901         }
00902 
00903         if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) {
00904             $wgDBuser = $wgDBadminuser;
00905             $wgDBpassword = $wgDBadminpassword;
00906 
00907             if ( $wgDBservers ) {
00911                 foreach ( $wgDBservers as $i => $server ) {
00912                     $wgDBservers[$i]['user'] = $wgDBuser;
00913                     $wgDBservers[$i]['password'] = $wgDBpassword;
00914                 }
00915             }
00916             if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
00917                 $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
00918                 $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
00919             }
00920             LBFactory::destroyInstance();
00921         }
00922 
00923         $this->afterFinalSetup();
00924 
00925         $wgShowSQLErrors = true;
00926 
00927         // @codingStandardsIgnoreStart Allow error supppression. wfSuppressWarnings()
00928         // is not avaiable.
00929         @set_time_limit( 0 );
00930         // @codingStandardsIgnoreStart
00931 
00932         $this->adjustMemoryLimit();
00933 
00934         // Per-script profiling; useful for debugging
00935         $forcedProfiler = $this->getOption( 'profiler' );
00936         if ( $forcedProfiler === 'text' ) {
00937             Profiler::setInstance( new ProfilerSimpleText( array() ) );
00938             Profiler::instance()->setTemplated( true );
00939         } elseif ( $forcedProfiler === 'trace' ) {
00940             Profiler::setInstance( new ProfilerSimpleTrace( array() ) );
00941             Profiler::instance()->setTemplated( true );
00942         }
00943     }
00944 
00948     protected function afterFinalSetup() {
00949         if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
00950             call_user_func( MW_CMDLINE_CALLBACK );
00951         }
00952     }
00953 
00958     public function globals() {
00959         if ( $this->hasOption( 'globals' ) ) {
00960             print_r( $GLOBALS );
00961         }
00962     }
00963 
00968     public function loadSettings() {
00969         global $wgCommandLineMode, $IP;
00970 
00971         if ( isset( $this->mOptions['conf'] ) ) {
00972             $settingsFile = $this->mOptions['conf'];
00973         } elseif ( defined( "MW_CONFIG_FILE" ) ) {
00974             $settingsFile = MW_CONFIG_FILE;
00975         } else {
00976             $settingsFile = "$IP/LocalSettings.php";
00977         }
00978         if ( isset( $this->mOptions['wiki'] ) ) {
00979             $bits = explode( '-', $this->mOptions['wiki'] );
00980             if ( count( $bits ) == 1 ) {
00981                 $bits[] = '';
00982             }
00983             define( 'MW_DB', $bits[0] );
00984             define( 'MW_PREFIX', $bits[1] );
00985         }
00986 
00987         if ( !is_readable( $settingsFile ) ) {
00988             $this->error( "A copy of your installation's LocalSettings.php\n" .
00989                 "must exist and be readable in the source directory.\n" .
00990                 "Use --conf to specify it.", true );
00991         }
00992         $wgCommandLineMode = true;
00993 
00994         return $settingsFile;
00995     }
00996 
01002     public function purgeRedundantText( $delete = true ) {
01003         # Data should come off the master, wrapped in a transaction
01004         $dbw = $this->getDB( DB_MASTER );
01005         $dbw->begin( __METHOD__ );
01006 
01007         # Get "active" text records from the revisions table
01008         $this->output( 'Searching for active text records in revisions table...' );
01009         $res = $dbw->select( 'revision', 'rev_text_id', array(), __METHOD__, array( 'DISTINCT' ) );
01010         foreach ( $res as $row ) {
01011             $cur[] = $row->rev_text_id;
01012         }
01013         $this->output( "done.\n" );
01014 
01015         # Get "active" text records from the archive table
01016         $this->output( 'Searching for active text records in archive table...' );
01017         $res = $dbw->select( 'archive', 'ar_text_id', array(), __METHOD__, array( 'DISTINCT' ) );
01018         foreach ( $res as $row ) {
01019             # old pre-MW 1.5 records can have null ar_text_id's.
01020             if ( $row->ar_text_id !== null ) {
01021                 $cur[] = $row->ar_text_id;
01022             }
01023         }
01024         $this->output( "done.\n" );
01025 
01026         # Get the IDs of all text records not in these sets
01027         $this->output( 'Searching for inactive text records...' );
01028         $cond = 'old_id NOT IN ( ' . $dbw->makeList( $cur ) . ' )';
01029         $res = $dbw->select( 'text', 'old_id', array( $cond ), __METHOD__, array( 'DISTINCT' ) );
01030         $old = array();
01031         foreach ( $res as $row ) {
01032             $old[] = $row->old_id;
01033         }
01034         $this->output( "done.\n" );
01035 
01036         # Inform the user of what we're going to do
01037         $count = count( $old );
01038         $this->output( "$count inactive items found.\n" );
01039 
01040         # Delete as appropriate
01041         if ( $delete && $count ) {
01042             $this->output( 'Deleting...' );
01043             $dbw->delete( 'text', array( 'old_id' => $old ), __METHOD__ );
01044             $this->output( "done.\n" );
01045         }
01046 
01047         # Done
01048         $dbw->commit( __METHOD__ );
01049     }
01050 
01055     protected function getDir() {
01056         return __DIR__;
01057     }
01058 
01066     protected function &getDB( $db, $groups = array(), $wiki = false ) {
01067         if ( is_null( $this->mDb ) ) {
01068             return wfGetDB( $db, $groups, $wiki );
01069         } else {
01070             return $this->mDb;
01071         }
01072     }
01073 
01079     public function setDB( &$db ) {
01080         $this->mDb = $db;
01081     }
01082 
01087     private function lockSearchindex( &$db ) {
01088         $write = array( 'searchindex' );
01089         $read = array( 'page', 'revision', 'text', 'interwiki', 'l10n_cache', 'user' );
01090         $db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ );
01091     }
01092 
01097     private function unlockSearchindex( &$db ) {
01098         $db->unlockTables( __CLASS__ . '::' . __METHOD__ );
01099     }
01100 
01106     private function relockSearchindex( &$db ) {
01107         $this->unlockSearchindex( $db );
01108         $this->lockSearchindex( $db );
01109     }
01110 
01118     public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) {
01119         $lockTime = time();
01120 
01121         # Lock searchindex
01122         if ( $maxLockTime ) {
01123             $this->output( "   --- Waiting for lock ---" );
01124             $this->lockSearchindex( $dbw );
01125             $lockTime = time();
01126             $this->output( "\n" );
01127         }
01128 
01129         # Loop through the results and do a search update
01130         foreach ( $results as $row ) {
01131             # Allow reads to be processed
01132             if ( $maxLockTime && time() > $lockTime + $maxLockTime ) {
01133                 $this->output( "    --- Relocking ---" );
01134                 $this->relockSearchindex( $dbw );
01135                 $lockTime = time();
01136                 $this->output( "\n" );
01137             }
01138             call_user_func( $callback, $dbw, $row );
01139         }
01140 
01141         # Unlock searchindex
01142         if ( $maxLockTime ) {
01143             $this->output( "    --- Unlocking --" );
01144             $this->unlockSearchindex( $dbw );
01145             $this->output( "\n" );
01146         }
01147     }
01148 
01155     public function updateSearchIndexForPage( $dbw, $pageId ) {
01156         // Get current revision
01157         $rev = Revision::loadFromPageId( $dbw, $pageId );
01158         $title = null;
01159         if ( $rev ) {
01160             $titleObj = $rev->getTitle();
01161             $title = $titleObj->getPrefixedDBkey();
01162             $this->output( "$title..." );
01163             # Update searchindex
01164             $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getContent() );
01165             $u->doUpdate();
01166             $this->output( "\n" );
01167         }
01168 
01169         return $title;
01170     }
01171 
01180     public static function posix_isatty( $fd ) {
01181         if ( !function_exists( 'posix_isatty' ) ) {
01182             return !$fd;
01183         } else {
01184             return posix_isatty( $fd );
01185         }
01186     }
01187 
01193     public static function readconsole( $prompt = '> ' ) {
01194         static $isatty = null;
01195         if ( is_null( $isatty ) ) {
01196             $isatty = self::posix_isatty( 0 /*STDIN*/ );
01197         }
01198 
01199         if ( $isatty && function_exists( 'readline' ) ) {
01200             return readline( $prompt );
01201         } else {
01202             if ( $isatty ) {
01203                 $st = self::readlineEmulation( $prompt );
01204             } else {
01205                 if ( feof( STDIN ) ) {
01206                     $st = false;
01207                 } else {
01208                     $st = fgets( STDIN, 1024 );
01209                 }
01210             }
01211             if ( $st === false ) {
01212                 return false;
01213             }
01214             $resp = trim( $st );
01215 
01216             return $resp;
01217         }
01218     }
01219 
01225     private static function readlineEmulation( $prompt ) {
01226         $bash = Installer::locateExecutableInDefaultPaths( array( 'bash' ) );
01227         if ( !wfIsWindows() && $bash ) {
01228             $retval = false;
01229             $encPrompt = wfEscapeShellArg( $prompt );
01230             $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
01231             $encCommand = wfEscapeShellArg( $command );
01232             $line = wfShellExec( "$bash -c $encCommand", $retval, array(), array( 'walltime' => 0 ) );
01233 
01234             if ( $retval == 0 ) {
01235                 return $line;
01236             } elseif ( $retval == 127 ) {
01237                 // Couldn't execute bash even though we thought we saw it.
01238                 // Shell probably spit out an error message, sorry :(
01239                 // Fall through to fgets()...
01240             } else {
01241                 // EOF/ctrl+D
01242                 return false;
01243             }
01244         }
01245 
01246         // Fallback... we'll have no editing controls, EWWW
01247         if ( feof( STDIN ) ) {
01248             return false;
01249         }
01250         print $prompt;
01251 
01252         return fgets( STDIN, 1024 );
01253     }
01254 }
01255 
01259 class FakeMaintenance extends Maintenance {
01260     protected $mSelf = "FakeMaintenanceScript";
01261 
01262     public function execute() {
01263         return;
01264     }
01265 }
01266 
01271 abstract class LoggedUpdateMaintenance extends Maintenance {
01272     public function __construct() {
01273         parent::__construct();
01274         $this->addOption( 'force', 'Run the update even if it was completed already' );
01275         $this->setBatchSize( 200 );
01276     }
01277 
01278     public function execute() {
01279         $db = $this->getDB( DB_MASTER );
01280         $key = $this->getUpdateKey();
01281 
01282         if ( !$this->hasOption( 'force' )
01283             && $db->selectRow( 'updatelog', '1', array( 'ul_key' => $key ), __METHOD__ )
01284         ) {
01285             $this->output( "..." . $this->updateSkippedMessage() . "\n" );
01286 
01287             return true;
01288         }
01289 
01290         if ( !$this->doDBUpdates() ) {
01291             return false;
01292         }
01293 
01294         if ( $db->insert( 'updatelog', array( 'ul_key' => $key ), __METHOD__, 'IGNORE' ) ) {
01295             return true;
01296         } else {
01297             $this->output( $this->updatelogFailedMessage() . "\n" );
01298 
01299             return false;
01300         }
01301     }
01302 
01307     protected function updateSkippedMessage() {
01308         $key = $this->getUpdateKey();
01309 
01310         return "Update '{$key}' already logged as completed.";
01311     }
01312 
01317     protected function updatelogFailedMessage() {
01318         $key = $this->getUpdateKey();
01319 
01320         return "Unable to log update '{$key}' as completed.";
01321     }
01322 
01328     abstract protected function doDBUpdates();
01329 
01334     abstract protected function getUpdateKey();
01335 }