MediaWiki
REL1_23
|
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 }