MediaWiki
REL1_22
|
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 00122 protected static $mCoreScripts = null; 00123 00128 public function __construct() { 00129 // Setup $IP, using MW_INSTALL_PATH if it exists 00130 global $IP; 00131 $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== '' 00132 ? getenv( 'MW_INSTALL_PATH' ) 00133 : realpath( __DIR__ . '/..' ); 00134 00135 $this->addDefaultParams(); 00136 register_shutdown_function( array( $this, 'outputChanneled' ), false ); 00137 } 00138 00146 public static function shouldExecute() { 00147 $bt = debug_backtrace(); 00148 $count = count( $bt ); 00149 if ( $count < 2 ) { 00150 return false; // sanity 00151 } 00152 if ( $bt[0]['class'] !== 'Maintenance' || $bt[0]['function'] !== 'shouldExecute' ) { 00153 return false; // last call should be to this function 00154 } 00155 $includeFuncs = array( 'require_once', 'require', 'include', 'include_once' ); 00156 for ( $i = 1; $i < $count; $i++ ) { 00157 if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) { 00158 return false; // previous calls should all be "requires" 00159 } 00160 } 00161 return true; 00162 } 00163 00167 abstract public function execute(); 00168 00179 protected function addOption( $name, $description, $required = false, $withArg = false, $shortName = false ) { 00180 $this->mParams[$name] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg, 'shortName' => $shortName ); 00181 if ( $shortName !== false ) { 00182 $this->mShortParamsMap[$shortName] = $name; 00183 } 00184 } 00185 00191 protected function hasOption( $name ) { 00192 return isset( $this->mOptions[$name] ); 00193 } 00194 00201 protected function getOption( $name, $default = null ) { 00202 if ( $this->hasOption( $name ) ) { 00203 return $this->mOptions[$name]; 00204 } else { 00205 // Set it so we don't have to provide the default again 00206 $this->mOptions[$name] = $default; 00207 return $this->mOptions[$name]; 00208 } 00209 } 00210 00217 protected function addArg( $arg, $description, $required = true ) { 00218 $this->mArgList[] = array( 00219 'name' => $arg, 00220 'desc' => $description, 00221 'require' => $required 00222 ); 00223 } 00224 00229 protected function deleteOption( $name ) { 00230 unset( $this->mParams[$name] ); 00231 } 00232 00237 protected function addDescription( $text ) { 00238 $this->mDescription = $text; 00239 } 00240 00246 protected function hasArg( $argId = 0 ) { 00247 return isset( $this->mArgs[$argId] ); 00248 } 00249 00256 protected function getArg( $argId = 0, $default = null ) { 00257 return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default; 00258 } 00259 00264 protected function setBatchSize( $s = 0 ) { 00265 $this->mBatchSize = $s; 00266 00267 // If we support $mBatchSize, show the option. 00268 // Used to be in addDefaultParams, but in order for that to 00269 // work, subclasses would have to call this function in the constructor 00270 // before they called parent::__construct which is just weird 00271 // (and really wasn't done). 00272 if ( $this->mBatchSize ) { 00273 $this->addOption( 'batch-size', 'Run this many operations ' . 00274 'per batch, default: ' . $this->mBatchSize, false, true ); 00275 if ( isset( $this->mParams['batch-size'] ) ) { 00276 // This seems a little ugly... 00277 $this->mDependantParameters['batch-size'] = $this->mParams['batch-size']; 00278 } 00279 } 00280 } 00281 00286 public function getName() { 00287 return $this->mSelf; 00288 } 00289 00297 protected function getStdin( $len = null ) { 00298 if ( $len == Maintenance::STDIN_ALL ) { 00299 return file_get_contents( 'php://stdin' ); 00300 } 00301 $f = fopen( 'php://stdin', 'rt' ); 00302 if ( !$len ) { 00303 return $f; 00304 } 00305 $input = fgets( $f, $len ); 00306 fclose( $f ); 00307 return rtrim( $input ); 00308 } 00309 00313 public function isQuiet() { 00314 return $this->mQuiet; 00315 } 00316 00324 protected function output( $out, $channel = null ) { 00325 if ( $this->mQuiet ) { 00326 return; 00327 } 00328 if ( $channel === null ) { 00329 $this->cleanupChanneled(); 00330 print $out; 00331 } else { 00332 $out = preg_replace( '/\n\z/', '', $out ); 00333 $this->outputChanneled( $out, $channel ); 00334 } 00335 } 00336 00343 protected function error( $err, $die = 0 ) { 00344 $this->outputChanneled( false ); 00345 if ( PHP_SAPI == 'cli' ) { 00346 fwrite( STDERR, $err . "\n" ); 00347 } else { 00348 print $err; 00349 } 00350 $die = intval( $die ); 00351 if ( $die > 0 ) { 00352 die( $die ); 00353 } 00354 } 00355 00356 private $atLineStart = true; 00357 private $lastChannel = null; 00358 00362 public function cleanupChanneled() { 00363 if ( !$this->atLineStart ) { 00364 print "\n"; 00365 $this->atLineStart = true; 00366 } 00367 } 00368 00377 public function outputChanneled( $msg, $channel = null ) { 00378 if ( $msg === false ) { 00379 $this->cleanupChanneled(); 00380 return; 00381 } 00382 00383 // End the current line if necessary 00384 if ( !$this->atLineStart && $channel !== $this->lastChannel ) { 00385 print "\n"; 00386 } 00387 00388 print $msg; 00389 00390 $this->atLineStart = false; 00391 if ( $channel === null ) { 00392 // For unchanneled messages, output trailing newline immediately 00393 print "\n"; 00394 $this->atLineStart = true; 00395 } 00396 $this->lastChannel = $channel; 00397 } 00398 00409 public function getDbType() { 00410 return Maintenance::DB_STD; 00411 } 00412 00416 protected function addDefaultParams() { 00417 00418 # Generic (non script dependant) options: 00419 00420 $this->addOption( 'help', 'Display this help message', false, false, 'h' ); 00421 $this->addOption( 'quiet', 'Whether to supress non-error output', false, false, 'q' ); 00422 $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true ); 00423 $this->addOption( 'wiki', 'For specifying the wiki ID', false, true ); 00424 $this->addOption( 'globals', 'Output globals at the end of processing for debugging' ); 00425 $this->addOption( 'memory-limit', 'Set a specific memory limit for the script, "max" for no limit or "default" to avoid changing it' ); 00426 $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " . 00427 "http://en.wikipedia.org. This is sometimes necessary because " . 00428 "server name detection may fail in command line scripts.", false, true ); 00429 $this->addOption( 'profiler', 'Set to "text" or "trace" to show profiling output', false, true ); 00430 00431 # Save generic options to display them separately in help 00432 $this->mGenericParameters = $this->mParams; 00433 00434 # Script dependant options: 00435 00436 // If we support a DB, show the options 00437 if ( $this->getDbType() > 0 ) { 00438 $this->addOption( 'dbuser', 'The DB user to use for this script', false, true ); 00439 $this->addOption( 'dbpass', 'The password to use for this script', false, true ); 00440 } 00441 00442 # Save additional script dependant options to display 00443 # them separately in help 00444 $this->mDependantParameters = array_diff_key( $this->mParams, $this->mGenericParameters ); 00445 } 00446 00454 public function runChild( $maintClass, $classFile = null ) { 00455 // Make sure the class is loaded first 00456 if ( !class_exists( $maintClass ) ) { 00457 if ( $classFile ) { 00458 require_once $classFile; 00459 } 00460 if ( !class_exists( $maintClass ) ) { 00461 $this->error( "Cannot spawn child: $maintClass" ); 00462 } 00463 } 00464 00468 $child = new $maintClass(); 00469 $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs ); 00470 if ( !is_null( $this->mDb ) ) { 00471 $child->setDB( $this->mDb ); 00472 } 00473 return $child; 00474 } 00475 00479 public function setup() { 00480 global $IP, $wgCommandLineMode, $wgRequestTime; 00481 00482 # Abort if called from a web server 00483 if ( isset( $_SERVER ) && isset( $_SERVER['REQUEST_METHOD'] ) ) { 00484 $this->error( 'This script must be run from the command line', true ); 00485 } 00486 00487 if ( $IP === null ) { 00488 $this->error( "\$IP not set, aborting!\n" . 00489 '(Did you forget to call parent::__construct() in your maintenance script?)', 1 ); 00490 } 00491 00492 # Make sure we can handle script parameters 00493 if ( !defined( 'HPHP_VERSION' ) && !ini_get( 'register_argc_argv' ) ) { 00494 $this->error( 'Cannot get command line arguments, register_argc_argv is set to false', true ); 00495 } 00496 00497 // Send PHP warnings and errors to stderr instead of stdout. 00498 // This aids in diagnosing problems, while keeping messages 00499 // out of redirected output. 00500 if ( ini_get( 'display_errors' ) ) { 00501 ini_set( 'display_errors', 'stderr' ); 00502 } 00503 00504 $this->loadParamsAndArgs(); 00505 $this->maybeHelp(); 00506 00507 # Set the memory limit 00508 # Note we need to set it again later in cache LocalSettings changed it 00509 $this->adjustMemoryLimit(); 00510 00511 # Set max execution time to 0 (no limit). PHP.net says that 00512 # "When running PHP from the command line the default setting is 0." 00513 # But sometimes this doesn't seem to be the case. 00514 ini_set( 'max_execution_time', 0 ); 00515 00516 $wgRequestTime = microtime( true ); 00517 00518 # Define us as being in MediaWiki 00519 define( 'MEDIAWIKI', true ); 00520 00521 $wgCommandLineMode = true; 00522 00523 # Turn off output buffering if it's on 00524 while ( ob_get_level() > 0 ) { 00525 ob_end_flush(); 00526 } 00527 00528 $this->validateParamsAndArgs(); 00529 } 00530 00540 public function memoryLimit() { 00541 $limit = $this->getOption( 'memory-limit', 'max' ); 00542 $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood 00543 return $limit; 00544 } 00545 00549 protected function adjustMemoryLimit() { 00550 $limit = $this->memoryLimit(); 00551 if ( $limit == 'max' ) { 00552 $limit = -1; // no memory limit 00553 } 00554 if ( $limit != 'default' ) { 00555 ini_set( 'memory_limit', $limit ); 00556 } 00557 } 00558 00562 public function clearParamsAndArgs() { 00563 $this->mOptions = array(); 00564 $this->mArgs = array(); 00565 $this->mInputLoaded = false; 00566 } 00567 00577 public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) { 00578 # If we were given opts or args, set those and return early 00579 if ( $self ) { 00580 $this->mSelf = $self; 00581 $this->mInputLoaded = true; 00582 } 00583 if ( $opts ) { 00584 $this->mOptions = $opts; 00585 $this->mInputLoaded = true; 00586 } 00587 if ( $args ) { 00588 $this->mArgs = $args; 00589 $this->mInputLoaded = true; 00590 } 00591 00592 # If we've already loaded input (either by user values or from $argv) 00593 # skip on loading it again. The array_shift() will corrupt values if 00594 # it's run again and again 00595 if ( $this->mInputLoaded ) { 00596 $this->loadSpecialVars(); 00597 return; 00598 } 00599 00600 global $argv; 00601 $this->mSelf = array_shift( $argv ); 00602 00603 $options = array(); 00604 $args = array(); 00605 00606 # Parse arguments 00607 for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) { 00608 if ( $arg == '--' ) { 00609 # End of options, remainder should be considered arguments 00610 $arg = next( $argv ); 00611 while ( $arg !== false ) { 00612 $args[] = $arg; 00613 $arg = next( $argv ); 00614 } 00615 break; 00616 } elseif ( substr( $arg, 0, 2 ) == '--' ) { 00617 # Long options 00618 $option = substr( $arg, 2 ); 00619 if ( array_key_exists( $option, $options ) ) { 00620 $this->error( "\nERROR: $option parameter given twice\n" ); 00621 $this->maybeHelp( true ); 00622 } 00623 if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) { 00624 $param = next( $argv ); 00625 if ( $param === false ) { 00626 $this->error( "\nERROR: $option parameter needs a value after it\n" ); 00627 $this->maybeHelp( true ); 00628 } 00629 $options[$option] = $param; 00630 } else { 00631 $bits = explode( '=', $option, 2 ); 00632 if ( count( $bits ) > 1 ) { 00633 $option = $bits[0]; 00634 $param = $bits[1]; 00635 } else { 00636 $param = 1; 00637 } 00638 $options[$option] = $param; 00639 } 00640 } elseif ( substr( $arg, 0, 1 ) == '-' ) { 00641 # Short options 00642 for ( $p = 1; $p < strlen( $arg ); $p++ ) { 00643 $option = $arg { $p }; 00644 if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) { 00645 $option = $this->mShortParamsMap[$option]; 00646 } 00647 if ( array_key_exists( $option, $options ) ) { 00648 $this->error( "\nERROR: $option parameter given twice\n" ); 00649 $this->maybeHelp( true ); 00650 } 00651 if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) { 00652 $param = next( $argv ); 00653 if ( $param === false ) { 00654 $this->error( "\nERROR: $option parameter needs a value after it\n" ); 00655 $this->maybeHelp( true ); 00656 } 00657 $options[$option] = $param; 00658 } else { 00659 $options[$option] = 1; 00660 } 00661 } 00662 } else { 00663 $args[] = $arg; 00664 } 00665 } 00666 00667 $this->mOptions = $options; 00668 $this->mArgs = $args; 00669 $this->loadSpecialVars(); 00670 $this->mInputLoaded = true; 00671 } 00672 00676 protected function validateParamsAndArgs() { 00677 $die = false; 00678 # Check to make sure we've got all the required options 00679 foreach ( $this->mParams as $opt => $info ) { 00680 if ( $info['require'] && !$this->hasOption( $opt ) ) { 00681 $this->error( "Param $opt required!" ); 00682 $die = true; 00683 } 00684 } 00685 # Check arg list too 00686 foreach ( $this->mArgList as $k => $info ) { 00687 if ( $info['require'] && !$this->hasArg( $k ) ) { 00688 $this->error( 'Argument <' . $info['name'] . '> required!' ); 00689 $die = true; 00690 } 00691 } 00692 00693 if ( $die ) { 00694 $this->maybeHelp( true ); 00695 } 00696 } 00697 00701 protected function loadSpecialVars() { 00702 if ( $this->hasOption( 'dbuser' ) ) { 00703 $this->mDbUser = $this->getOption( 'dbuser' ); 00704 } 00705 if ( $this->hasOption( 'dbpass' ) ) { 00706 $this->mDbPass = $this->getOption( 'dbpass' ); 00707 } 00708 if ( $this->hasOption( 'quiet' ) ) { 00709 $this->mQuiet = true; 00710 } 00711 if ( $this->hasOption( 'batch-size' ) ) { 00712 $this->mBatchSize = intval( $this->getOption( 'batch-size' ) ); 00713 } 00714 } 00715 00720 protected function maybeHelp( $force = false ) { 00721 if ( !$force && !$this->hasOption( 'help' ) ) { 00722 return; 00723 } 00724 00725 $screenWidth = 80; // TODO: Caculate this! 00726 $tab = " "; 00727 $descWidth = $screenWidth - ( 2 * strlen( $tab ) ); 00728 00729 ksort( $this->mParams ); 00730 $this->mQuiet = false; 00731 00732 // Description ... 00733 if ( $this->mDescription ) { 00734 $this->output( "\n" . $this->mDescription . "\n" ); 00735 } 00736 $output = "\nUsage: php " . basename( $this->mSelf ); 00737 00738 // ... append parameters ... 00739 if ( $this->mParams ) { 00740 $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]"; 00741 } 00742 00743 // ... and append arguments. 00744 if ( $this->mArgList ) { 00745 $output .= ' '; 00746 foreach ( $this->mArgList as $k => $arg ) { 00747 if ( $arg['require'] ) { 00748 $output .= '<' . $arg['name'] . '>'; 00749 } else { 00750 $output .= '[' . $arg['name'] . ']'; 00751 } 00752 if ( $k < count( $this->mArgList ) - 1 ) { 00753 $output .= ' '; 00754 } 00755 } 00756 } 00757 $this->output( "$output\n\n" ); 00758 00759 # TODO abstract some repetitive code below 00760 00761 // Generic parameters 00762 $this->output( "Generic maintenance parameters:\n" ); 00763 foreach ( $this->mGenericParameters as $par => $info ) { 00764 if ( $info['shortName'] !== false ) { 00765 $par .= " (-{$info['shortName']})"; 00766 } 00767 $this->output( 00768 wordwrap( "$tab--$par: " . $info['desc'], $descWidth, 00769 "\n$tab$tab" ) . "\n" 00770 ); 00771 } 00772 $this->output( "\n" ); 00773 00774 $scriptDependantParams = $this->mDependantParameters; 00775 if ( count( $scriptDependantParams ) > 0 ) { 00776 $this->output( "Script dependant parameters:\n" ); 00777 // Parameters description 00778 foreach ( $scriptDependantParams as $par => $info ) { 00779 if ( $info['shortName'] !== false ) { 00780 $par .= " (-{$info['shortName']})"; 00781 } 00782 $this->output( 00783 wordwrap( "$tab--$par: " . $info['desc'], $descWidth, 00784 "\n$tab$tab" ) . "\n" 00785 ); 00786 } 00787 $this->output( "\n" ); 00788 } 00789 00790 00791 // Script specific parameters not defined on construction by 00792 // Maintenance::addDefaultParams() 00793 $scriptSpecificParams = array_diff_key( 00794 # all script parameters: 00795 $this->mParams, 00796 # remove the Maintenance default parameters: 00797 $this->mGenericParameters, 00798 $this->mDependantParameters 00799 ); 00800 if ( count( $scriptSpecificParams ) > 0 ) { 00801 $this->output( "Script specific parameters:\n" ); 00802 // Parameters description 00803 foreach ( $scriptSpecificParams as $par => $info ) { 00804 if ( $info['shortName'] !== false ) { 00805 $par .= " (-{$info['shortName']})"; 00806 } 00807 $this->output( 00808 wordwrap( "$tab--$par: " . $info['desc'], $descWidth, 00809 "\n$tab$tab" ) . "\n" 00810 ); 00811 } 00812 $this->output( "\n" ); 00813 } 00814 00815 // Print arguments 00816 if ( count( $this->mArgList ) > 0 ) { 00817 $this->output( "Arguments:\n" ); 00818 // Arguments description 00819 foreach ( $this->mArgList as $info ) { 00820 $openChar = $info['require'] ? '<' : '['; 00821 $closeChar = $info['require'] ? '>' : ']'; 00822 $this->output( 00823 wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " . 00824 $info['desc'], $descWidth, "\n$tab$tab" ) . "\n" 00825 ); 00826 } 00827 $this->output( "\n" ); 00828 } 00829 00830 die( 1 ); 00831 } 00832 00836 public function finalSetup() { 00837 global $wgCommandLineMode, $wgShowSQLErrors, $wgServer; 00838 global $wgDBadminuser, $wgDBadminpassword; 00839 global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf; 00840 00841 # Turn off output buffering again, it might have been turned on in the settings files 00842 if ( ob_get_level() ) { 00843 ob_end_flush(); 00844 } 00845 # Same with these 00846 $wgCommandLineMode = true; 00847 00848 # Override $wgServer 00849 if ( $this->hasOption( 'server' ) ) { 00850 $wgServer = $this->getOption( 'server', $wgServer ); 00851 } 00852 00853 # If these were passed, use them 00854 if ( $this->mDbUser ) { 00855 $wgDBadminuser = $this->mDbUser; 00856 } 00857 if ( $this->mDbPass ) { 00858 $wgDBadminpassword = $this->mDbPass; 00859 } 00860 00861 if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) { 00862 $wgDBuser = $wgDBadminuser; 00863 $wgDBpassword = $wgDBadminpassword; 00864 00865 if ( $wgDBservers ) { 00869 foreach ( $wgDBservers as $i => $server ) { 00870 $wgDBservers[$i]['user'] = $wgDBuser; 00871 $wgDBservers[$i]['password'] = $wgDBpassword; 00872 } 00873 } 00874 if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) { 00875 $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser; 00876 $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword; 00877 } 00878 LBFactory::destroyInstance(); 00879 } 00880 00881 $this->afterFinalSetup(); 00882 00883 $wgShowSQLErrors = true; 00884 @set_time_limit( 0 ); 00885 $this->adjustMemoryLimit(); 00886 00887 // Per-script profiling; useful for debugging 00888 $forcedProfiler = $this->getOption( 'profiler' ); 00889 if ( $forcedProfiler === 'text' ) { 00890 Profiler::setInstance( new ProfilerSimpleText( array() ) ); 00891 Profiler::instance()->setTemplated( true ); 00892 } elseif ( $forcedProfiler === 'trace' ) { 00893 Profiler::setInstance( new ProfilerSimpleTrace( array() ) ); 00894 Profiler::instance()->setTemplated( true ); 00895 } 00896 } 00897 00901 protected function afterFinalSetup() { 00902 if ( defined( 'MW_CMDLINE_CALLBACK' ) ) { 00903 call_user_func( MW_CMDLINE_CALLBACK ); 00904 } 00905 } 00906 00911 public function globals() { 00912 if ( $this->hasOption( 'globals' ) ) { 00913 print_r( $GLOBALS ); 00914 } 00915 } 00916 00921 public function loadSettings() { 00922 global $wgCommandLineMode, $IP; 00923 00924 if ( isset( $this->mOptions['conf'] ) ) { 00925 $settingsFile = $this->mOptions['conf']; 00926 } elseif ( defined( "MW_CONFIG_FILE" ) ) { 00927 $settingsFile = MW_CONFIG_FILE; 00928 } else { 00929 $settingsFile = "$IP/LocalSettings.php"; 00930 } 00931 if ( isset( $this->mOptions['wiki'] ) ) { 00932 $bits = explode( '-', $this->mOptions['wiki'] ); 00933 if ( count( $bits ) == 1 ) { 00934 $bits[] = ''; 00935 } 00936 define( 'MW_DB', $bits[0] ); 00937 define( 'MW_PREFIX', $bits[1] ); 00938 } 00939 00940 if ( !is_readable( $settingsFile ) ) { 00941 $this->error( "A copy of your installation's LocalSettings.php\n" . 00942 "must exist and be readable in the source directory.\n" . 00943 "Use --conf to specify it.", true ); 00944 } 00945 $wgCommandLineMode = true; 00946 return $settingsFile; 00947 } 00948 00954 public function purgeRedundantText( $delete = true ) { 00955 # Data should come off the master, wrapped in a transaction 00956 $dbw = $this->getDB( DB_MASTER ); 00957 $dbw->begin( __METHOD__ ); 00958 00959 # Get "active" text records from the revisions table 00960 $this->output( 'Searching for active text records in revisions table...' ); 00961 $res = $dbw->select( 'revision', 'rev_text_id', array(), __METHOD__, array( 'DISTINCT' ) ); 00962 foreach ( $res as $row ) { 00963 $cur[] = $row->rev_text_id; 00964 } 00965 $this->output( "done.\n" ); 00966 00967 # Get "active" text records from the archive table 00968 $this->output( 'Searching for active text records in archive table...' ); 00969 $res = $dbw->select( 'archive', 'ar_text_id', array(), __METHOD__, array( 'DISTINCT' ) ); 00970 foreach ( $res as $row ) { 00971 # old pre-MW 1.5 records can have null ar_text_id's. 00972 if ( $row->ar_text_id !== null ) { 00973 $cur[] = $row->ar_text_id; 00974 } 00975 } 00976 $this->output( "done.\n" ); 00977 00978 # Get the IDs of all text records not in these sets 00979 $this->output( 'Searching for inactive text records...' ); 00980 $cond = 'old_id NOT IN ( ' . $dbw->makeList( $cur ) . ' )'; 00981 $res = $dbw->select( 'text', 'old_id', array( $cond ), __METHOD__, array( 'DISTINCT' ) ); 00982 $old = array(); 00983 foreach ( $res as $row ) { 00984 $old[] = $row->old_id; 00985 } 00986 $this->output( "done.\n" ); 00987 00988 # Inform the user of what we're going to do 00989 $count = count( $old ); 00990 $this->output( "$count inactive items found.\n" ); 00991 00992 # Delete as appropriate 00993 if ( $delete && $count ) { 00994 $this->output( 'Deleting...' ); 00995 $dbw->delete( 'text', array( 'old_id' => $old ), __METHOD__ ); 00996 $this->output( "done.\n" ); 00997 } 00998 00999 # Done 01000 $dbw->commit( __METHOD__ ); 01001 } 01002 01007 protected function getDir() { 01008 return __DIR__; 01009 } 01010 01017 public static function getMaintenanceScripts() { 01018 global $wgMaintenanceScripts; 01019 return $wgMaintenanceScripts + self::getCoreScripts(); 01020 } 01021 01026 protected static function getCoreScripts() { 01027 if ( !self::$mCoreScripts ) { 01028 $paths = array( 01029 __DIR__, 01030 __DIR__ . '/language', 01031 __DIR__ . '/storage', 01032 ); 01033 self::$mCoreScripts = array(); 01034 foreach ( $paths as $p ) { 01035 $handle = opendir( $p ); 01036 while ( ( $file = readdir( $handle ) ) !== false ) { 01037 if ( $file == 'Maintenance.php' ) { 01038 continue; 01039 } 01040 $file = $p . '/' . $file; 01041 if ( is_dir( $file ) || !strpos( $file, '.php' ) || 01042 ( strpos( file_get_contents( $file ), '$maintClass' ) === false ) ) { 01043 continue; 01044 } 01045 require $file; 01046 $vars = get_defined_vars(); 01047 if ( array_key_exists( 'maintClass', $vars ) ) { 01048 self::$mCoreScripts[$vars['maintClass']] = $file; 01049 } 01050 } 01051 closedir( $handle ); 01052 } 01053 } 01054 return self::$mCoreScripts; 01055 } 01056 01064 protected function &getDB( $db, $groups = array(), $wiki = false ) { 01065 if ( is_null( $this->mDb ) ) { 01066 return wfGetDB( $db, $groups, $wiki ); 01067 } else { 01068 return $this->mDb; 01069 } 01070 } 01071 01077 public function setDB( &$db ) { 01078 $this->mDb = $db; 01079 } 01080 01085 private function lockSearchindex( &$db ) { 01086 $write = array( 'searchindex' ); 01087 $read = array( 'page', 'revision', 'text', 'interwiki', 'l10n_cache', 'user' ); 01088 $db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ ); 01089 } 01090 01095 private function unlockSearchindex( &$db ) { 01096 $db->unlockTables( __CLASS__ . '::' . __METHOD__ ); 01097 } 01098 01104 private function relockSearchindex( &$db ) { 01105 $this->unlockSearchindex( $db ); 01106 $this->lockSearchindex( $db ); 01107 } 01108 01116 public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) { 01117 $lockTime = time(); 01118 01119 # Lock searchindex 01120 if ( $maxLockTime ) { 01121 $this->output( " --- Waiting for lock ---" ); 01122 $this->lockSearchindex( $dbw ); 01123 $lockTime = time(); 01124 $this->output( "\n" ); 01125 } 01126 01127 # Loop through the results and do a search update 01128 foreach ( $results as $row ) { 01129 # Allow reads to be processed 01130 if ( $maxLockTime && time() > $lockTime + $maxLockTime ) { 01131 $this->output( " --- Relocking ---" ); 01132 $this->relockSearchindex( $dbw ); 01133 $lockTime = time(); 01134 $this->output( "\n" ); 01135 } 01136 call_user_func( $callback, $dbw, $row ); 01137 } 01138 01139 # Unlock searchindex 01140 if ( $maxLockTime ) { 01141 $this->output( " --- Unlocking --" ); 01142 $this->unlockSearchindex( $dbw ); 01143 $this->output( "\n" ); 01144 } 01145 01146 } 01147 01154 public function updateSearchIndexForPage( $dbw, $pageId ) { 01155 // Get current revision 01156 $rev = Revision::loadFromPageId( $dbw, $pageId ); 01157 $title = null; 01158 if ( $rev ) { 01159 $titleObj = $rev->getTitle(); 01160 $title = $titleObj->getPrefixedDBkey(); 01161 $this->output( "$title..." ); 01162 # Update searchindex 01163 $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getContent() ); 01164 $u->doUpdate(); 01165 $this->output( "\n" ); 01166 } 01167 return $title; 01168 } 01169 01178 public static function posix_isatty( $fd ) { 01179 if ( !function_exists( 'posix_isatty' ) ) { 01180 return !$fd; 01181 } else { 01182 return posix_isatty( $fd ); 01183 } 01184 } 01185 01191 public static function readconsole( $prompt = '> ' ) { 01192 static $isatty = null; 01193 if ( is_null( $isatty ) ) { 01194 $isatty = self::posix_isatty( 0 /*STDIN*/ ); 01195 } 01196 01197 if ( $isatty && function_exists( 'readline' ) ) { 01198 return readline( $prompt ); 01199 } else { 01200 if ( $isatty ) { 01201 $st = self::readlineEmulation( $prompt ); 01202 } else { 01203 if ( feof( STDIN ) ) { 01204 $st = false; 01205 } else { 01206 $st = fgets( STDIN, 1024 ); 01207 } 01208 } 01209 if ( $st === false ) { 01210 return false; 01211 } 01212 $resp = trim( $st ); 01213 return $resp; 01214 } 01215 } 01216 01222 private static function readlineEmulation( $prompt ) { 01223 $bash = Installer::locateExecutableInDefaultPaths( array( 'bash' ) ); 01224 if ( !wfIsWindows() && $bash ) { 01225 $retval = false; 01226 $encPrompt = wfEscapeShellArg( $prompt ); 01227 $command = "read -er -p $encPrompt && echo \"\$REPLY\""; 01228 $encCommand = wfEscapeShellArg( $command ); 01229 $line = wfShellExec( "$bash -c $encCommand", $retval, array(), array( 'walltime' => 0 ) ); 01230 01231 if ( $retval == 0 ) { 01232 return $line; 01233 } elseif ( $retval == 127 ) { 01234 // Couldn't execute bash even though we thought we saw it. 01235 // Shell probably spit out an error message, sorry :( 01236 // Fall through to fgets()... 01237 } else { 01238 // EOF/ctrl+D 01239 return false; 01240 } 01241 } 01242 01243 // Fallback... we'll have no editing controls, EWWW 01244 if ( feof( STDIN ) ) { 01245 return false; 01246 } 01247 print $prompt; 01248 return fgets( STDIN, 1024 ); 01249 } 01250 } 01251 01255 class FakeMaintenance extends Maintenance { 01256 protected $mSelf = "FakeMaintenanceScript"; 01257 public function execute() { 01258 return; 01259 } 01260 } 01261 01266 abstract class LoggedUpdateMaintenance extends Maintenance { 01267 public function __construct() { 01268 parent::__construct(); 01269 $this->addOption( 'force', 'Run the update even if it was completed already' ); 01270 $this->setBatchSize( 200 ); 01271 } 01272 01273 public function execute() { 01274 $db = $this->getDB( DB_MASTER ); 01275 $key = $this->getUpdateKey(); 01276 01277 if ( !$this->hasOption( 'force' ) && 01278 $db->selectRow( 'updatelog', '1', array( 'ul_key' => $key ), __METHOD__ ) ) 01279 { 01280 $this->output( "..." . $this->updateSkippedMessage() . "\n" ); 01281 return true; 01282 } 01283 01284 if ( !$this->doDBUpdates() ) { 01285 return false; 01286 } 01287 01288 if ( 01289 $db->insert( 'updatelog', array( 'ul_key' => $key ), __METHOD__, 'IGNORE' ) ) 01290 { 01291 return true; 01292 } else { 01293 $this->output( $this->updatelogFailedMessage() . "\n" ); 01294 return false; 01295 } 01296 } 01297 01302 protected function updateSkippedMessage() { 01303 $key = $this->getUpdateKey(); 01304 return "Update '{$key}' already logged as completed."; 01305 } 01306 01311 protected function updatelogFailedMessage() { 01312 $key = $this->getUpdateKey(); 01313 return "Unable to log update '{$key}' as completed."; 01314 } 01315 01321 abstract protected function doDBUpdates(); 01322 01327 abstract protected function getUpdateKey(); 01328 }