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