MediaWiki
master
|
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 00116 protected static $mCoreScripts = null; 00117 00122 public function __construct() { 00123 // Setup $IP, using MW_INSTALL_PATH if it exists 00124 global $IP; 00125 $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== '' 00126 ? getenv( 'MW_INSTALL_PATH' ) 00127 : realpath( __DIR__ . '/..' ); 00128 00129 $this->addDefaultParams(); 00130 register_shutdown_function( array( $this, 'outputChanneled' ), false ); 00131 } 00132 00140 public static function shouldExecute() { 00141 $bt = debug_backtrace(); 00142 $count = count( $bt ); 00143 if ( $count < 2 ) { 00144 return false; // sanity 00145 } 00146 if ( $bt[0]['class'] !== 'Maintenance' || $bt[0]['function'] !== 'shouldExecute' ) { 00147 return false; // last call should be to this function 00148 } 00149 $includeFuncs = array( 'require_once', 'require', 'include', 'include_once' ); 00150 for( $i=1; $i < $count; $i++ ) { 00151 if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) { 00152 return false; // previous calls should all be "requires" 00153 } 00154 } 00155 return true; 00156 } 00157 00161 abstract public function execute(); 00162 00173 protected function addOption( $name, $description, $required = false, $withArg = false, $shortName = false ) { 00174 $this->mParams[$name] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg, 'shortName' => $shortName ); 00175 if ( $shortName !== false ) { 00176 $this->mShortParamsMap[$shortName] = $name; 00177 } 00178 } 00179 00185 protected function hasOption( $name ) { 00186 return isset( $this->mOptions[$name] ); 00187 } 00188 00195 protected function getOption( $name, $default = null ) { 00196 if ( $this->hasOption( $name ) ) { 00197 return $this->mOptions[$name]; 00198 } else { 00199 // Set it so we don't have to provide the default again 00200 $this->mOptions[$name] = $default; 00201 return $this->mOptions[$name]; 00202 } 00203 } 00204 00211 protected function addArg( $arg, $description, $required = true ) { 00212 $this->mArgList[] = array( 00213 'name' => $arg, 00214 'desc' => $description, 00215 'require' => $required 00216 ); 00217 } 00218 00223 protected function deleteOption( $name ) { 00224 unset( $this->mParams[$name] ); 00225 } 00226 00231 protected function addDescription( $text ) { 00232 $this->mDescription = $text; 00233 } 00234 00240 protected function hasArg( $argId = 0 ) { 00241 return isset( $this->mArgs[$argId] ); 00242 } 00243 00250 protected function getArg( $argId = 0, $default = null ) { 00251 return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default; 00252 } 00253 00258 protected function setBatchSize( $s = 0 ) { 00259 $this->mBatchSize = $s; 00260 00261 // If we support $mBatchSize, show the option. 00262 // Used to be in addDefaultParams, but in order for that to 00263 // work, subclasses would have to call this function in the constructor 00264 // before they called parent::__construct which is just weird 00265 // (and really wasn't done). 00266 if ( $this->mBatchSize ) { 00267 $this->addOption( 'batch-size', 'Run this many operations ' . 00268 'per batch, default: ' . $this->mBatchSize, false, true ); 00269 if ( isset( $this->mParams['batch-size'] ) ) { 00270 // This seems a little ugly... 00271 $this->mDependantParameters['batch-size'] = $this->mParams['batch-size']; 00272 } 00273 } 00274 } 00275 00280 public function getName() { 00281 return $this->mSelf; 00282 } 00283 00291 protected function getStdin( $len = null ) { 00292 if ( $len == Maintenance::STDIN_ALL ) { 00293 return file_get_contents( 'php://stdin' ); 00294 } 00295 $f = fopen( 'php://stdin', 'rt' ); 00296 if ( !$len ) { 00297 return $f; 00298 } 00299 $input = fgets( $f, $len ); 00300 fclose( $f ); 00301 return rtrim( $input ); 00302 } 00303 00307 public function isQuiet() { 00308 return $this->mQuiet; 00309 } 00310 00318 protected function output( $out, $channel = null ) { 00319 if ( $this->mQuiet ) { 00320 return; 00321 } 00322 if ( $channel === null ) { 00323 $this->cleanupChanneled(); 00324 print( $out ); 00325 } else { 00326 $out = preg_replace( '/\n\z/', '', $out ); 00327 $this->outputChanneled( $out, $channel ); 00328 } 00329 } 00330 00337 protected function error( $err, $die = 0 ) { 00338 $this->outputChanneled( false ); 00339 if ( php_sapi_name() == 'cli' ) { 00340 fwrite( STDERR, $err . "\n" ); 00341 } else { 00342 print $err; 00343 } 00344 $die = intval( $die ); 00345 if ( $die > 0 ) { 00346 die( $die ); 00347 } 00348 } 00349 00350 private $atLineStart = true; 00351 private $lastChannel = null; 00352 00356 public function cleanupChanneled() { 00357 if ( !$this->atLineStart ) { 00358 print "\n"; 00359 $this->atLineStart = true; 00360 } 00361 } 00362 00371 public function outputChanneled( $msg, $channel = null ) { 00372 if ( $msg === false ) { 00373 $this->cleanupChanneled(); 00374 return; 00375 } 00376 00377 // End the current line if necessary 00378 if ( !$this->atLineStart && $channel !== $this->lastChannel ) { 00379 print "\n"; 00380 } 00381 00382 print $msg; 00383 00384 $this->atLineStart = false; 00385 if ( $channel === null ) { 00386 // For unchanneled messages, output trailing newline immediately 00387 print "\n"; 00388 $this->atLineStart = true; 00389 } 00390 $this->lastChannel = $channel; 00391 } 00392 00403 public function getDbType() { 00404 return Maintenance::DB_STD; 00405 } 00406 00410 protected function addDefaultParams() { 00411 00412 # Generic (non script dependant) options: 00413 00414 $this->addOption( 'help', 'Display this help message', false, false, 'h' ); 00415 $this->addOption( 'quiet', 'Whether to supress non-error output', false, false, 'q' ); 00416 $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true ); 00417 $this->addOption( 'wiki', 'For specifying the wiki ID', false, true ); 00418 $this->addOption( 'globals', 'Output globals at the end of processing for debugging' ); 00419 $this->addOption( 'memory-limit', 'Set a specific memory limit for the script, "max" for no limit or "default" to avoid changing it' ); 00420 $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " . 00421 "http://en.wikipedia.org. This is sometimes necessary because " . 00422 "server name detection may fail in command line scripts.", 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 ( !MWInit::classExists( $maintClass ) ) { 00450 if ( $classFile ) { 00451 require_once( $classFile ); 00452 } 00453 if ( !MWInit::classExists( $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 $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 # Make sure we can handle script parameters 00481 if ( !function_exists( 'hphp_thread_set_warmup_enabled' ) && !ini_get( 'register_argc_argv' ) ) { 00482 $this->error( 'Cannot get command line arguments, register_argc_argv is set to false', true ); 00483 } 00484 00485 // Send PHP warnings and errors to stderr instead of stdout. 00486 // This aids in diagnosing problems, while keeping messages 00487 // out of redirected output. 00488 if ( ini_get( 'display_errors' ) ) { 00489 ini_set( 'display_errors', 'stderr' ); 00490 } 00491 00492 $this->loadParamsAndArgs(); 00493 $this->maybeHelp(); 00494 00495 # Set the memory limit 00496 # Note we need to set it again later in cache LocalSettings changed it 00497 $this->adjustMemoryLimit(); 00498 00499 # Set max execution time to 0 (no limit). PHP.net says that 00500 # "When running PHP from the command line the default setting is 0." 00501 # But sometimes this doesn't seem to be the case. 00502 ini_set( 'max_execution_time', 0 ); 00503 00504 $wgRequestTime = microtime( true ); 00505 00506 # Define us as being in MediaWiki 00507 define( 'MEDIAWIKI', true ); 00508 00509 $wgCommandLineMode = true; 00510 # Turn off output buffering if it's on 00511 @ob_end_flush(); 00512 00513 $this->validateParamsAndArgs(); 00514 } 00515 00525 public function memoryLimit() { 00526 $limit = $this->getOption( 'memory-limit', 'max' ); 00527 $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood 00528 return $limit; 00529 } 00530 00534 protected function adjustMemoryLimit() { 00535 $limit = $this->memoryLimit(); 00536 if ( $limit == 'max' ) { 00537 $limit = -1; // no memory limit 00538 } 00539 if ( $limit != 'default' ) { 00540 ini_set( 'memory_limit', $limit ); 00541 } 00542 } 00543 00547 public function clearParamsAndArgs() { 00548 $this->mOptions = array(); 00549 $this->mArgs = array(); 00550 $this->mInputLoaded = false; 00551 } 00552 00562 public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) { 00563 # If we were given opts or args, set those and return early 00564 if ( $self ) { 00565 $this->mSelf = $self; 00566 $this->mInputLoaded = true; 00567 } 00568 if ( $opts ) { 00569 $this->mOptions = $opts; 00570 $this->mInputLoaded = true; 00571 } 00572 if ( $args ) { 00573 $this->mArgs = $args; 00574 $this->mInputLoaded = true; 00575 } 00576 00577 # If we've already loaded input (either by user values or from $argv) 00578 # skip on loading it again. The array_shift() will corrupt values if 00579 # it's run again and again 00580 if ( $this->mInputLoaded ) { 00581 $this->loadSpecialVars(); 00582 return; 00583 } 00584 00585 global $argv; 00586 $this->mSelf = array_shift( $argv ); 00587 00588 $options = array(); 00589 $args = array(); 00590 00591 # Parse arguments 00592 for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) { 00593 if ( $arg == '--' ) { 00594 # End of options, remainder should be considered arguments 00595 $arg = next( $argv ); 00596 while ( $arg !== false ) { 00597 $args[] = $arg; 00598 $arg = next( $argv ); 00599 } 00600 break; 00601 } elseif ( substr( $arg, 0, 2 ) == '--' ) { 00602 # Long options 00603 $option = substr( $arg, 2 ); 00604 if ( array_key_exists( $option, $options ) ) { 00605 $this->error( "\nERROR: $option parameter given twice\n" ); 00606 $this->maybeHelp( true ); 00607 } 00608 if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) { 00609 $param = next( $argv ); 00610 if ( $param === false ) { 00611 $this->error( "\nERROR: $option parameter needs a value after it\n" ); 00612 $this->maybeHelp( true ); 00613 } 00614 $options[$option] = $param; 00615 } else { 00616 $bits = explode( '=', $option, 2 ); 00617 if ( count( $bits ) > 1 ) { 00618 $option = $bits[0]; 00619 $param = $bits[1]; 00620 } else { 00621 $param = 1; 00622 } 00623 $options[$option] = $param; 00624 } 00625 } elseif ( substr( $arg, 0, 1 ) == '-' ) { 00626 # Short options 00627 for ( $p = 1; $p < strlen( $arg ); $p++ ) { 00628 $option = $arg { $p } ; 00629 if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) { 00630 $option = $this->mShortParamsMap[$option]; 00631 } 00632 if ( array_key_exists( $option, $options ) ) { 00633 $this->error( "\nERROR: $option parameter given twice\n" ); 00634 $this->maybeHelp( true ); 00635 } 00636 if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) { 00637 $param = next( $argv ); 00638 if ( $param === false ) { 00639 $this->error( "\nERROR: $option parameter needs a value after it\n" ); 00640 $this->maybeHelp( true ); 00641 } 00642 $options[$option] = $param; 00643 } else { 00644 $options[$option] = 1; 00645 } 00646 } 00647 } else { 00648 $args[] = $arg; 00649 } 00650 } 00651 00652 $this->mOptions = $options; 00653 $this->mArgs = $args; 00654 $this->loadSpecialVars(); 00655 $this->mInputLoaded = true; 00656 } 00657 00661 protected function validateParamsAndArgs() { 00662 $die = false; 00663 # Check to make sure we've got all the required options 00664 foreach ( $this->mParams as $opt => $info ) { 00665 if ( $info['require'] && !$this->hasOption( $opt ) ) { 00666 $this->error( "Param $opt required!" ); 00667 $die = true; 00668 } 00669 } 00670 # Check arg list too 00671 foreach ( $this->mArgList as $k => $info ) { 00672 if ( $info['require'] && !$this->hasArg( $k ) ) { 00673 $this->error( 'Argument <' . $info['name'] . '> required!' ); 00674 $die = true; 00675 } 00676 } 00677 00678 if ( $die ) { 00679 $this->maybeHelp( true ); 00680 } 00681 } 00682 00686 protected function loadSpecialVars() { 00687 if ( $this->hasOption( 'dbuser' ) ) { 00688 $this->mDbUser = $this->getOption( 'dbuser' ); 00689 } 00690 if ( $this->hasOption( 'dbpass' ) ) { 00691 $this->mDbPass = $this->getOption( 'dbpass' ); 00692 } 00693 if ( $this->hasOption( 'quiet' ) ) { 00694 $this->mQuiet = true; 00695 } 00696 if ( $this->hasOption( 'batch-size' ) ) { 00697 $this->mBatchSize = intval( $this->getOption( 'batch-size' ) ); 00698 } 00699 } 00700 00705 protected function maybeHelp( $force = false ) { 00706 if( !$force && !$this->hasOption( 'help' ) ) { 00707 return; 00708 } 00709 00710 $screenWidth = 80; // TODO: Caculate this! 00711 $tab = " "; 00712 $descWidth = $screenWidth - ( 2 * strlen( $tab ) ); 00713 00714 ksort( $this->mParams ); 00715 $this->mQuiet = false; 00716 00717 // Description ... 00718 if ( $this->mDescription ) { 00719 $this->output( "\n" . $this->mDescription . "\n" ); 00720 } 00721 $output = "\nUsage: php " . basename( $this->mSelf ); 00722 00723 // ... append parameters ... 00724 if ( $this->mParams ) { 00725 $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]"; 00726 } 00727 00728 // ... and append arguments. 00729 if ( $this->mArgList ) { 00730 $output .= ' '; 00731 foreach ( $this->mArgList as $k => $arg ) { 00732 if ( $arg['require'] ) { 00733 $output .= '<' . $arg['name'] . '>'; 00734 } else { 00735 $output .= '[' . $arg['name'] . ']'; 00736 } 00737 if ( $k < count( $this->mArgList ) - 1 ) 00738 $output .= ' '; 00739 } 00740 } 00741 $this->output( "$output\n\n" ); 00742 00743 # TODO abstract some repetitive code below 00744 00745 // Generic parameters 00746 $this->output( "Generic maintenance parameters:\n" ); 00747 foreach ( $this->mGenericParameters as $par => $info ) { 00748 if ( $info['shortName'] !== false ) { 00749 $par .= " (-{$info['shortName']})"; 00750 } 00751 $this->output( 00752 wordwrap( "$tab--$par: " . $info['desc'], $descWidth, 00753 "\n$tab$tab" ) . "\n" 00754 ); 00755 } 00756 $this->output( "\n" ); 00757 00758 $scriptDependantParams = $this->mDependantParameters; 00759 if( count($scriptDependantParams) > 0 ) { 00760 $this->output( "Script dependant parameters:\n" ); 00761 // Parameters description 00762 foreach ( $scriptDependantParams as $par => $info ) { 00763 if ( $info['shortName'] !== false ) { 00764 $par .= " (-{$info['shortName']})"; 00765 } 00766 $this->output( 00767 wordwrap( "$tab--$par: " . $info['desc'], $descWidth, 00768 "\n$tab$tab" ) . "\n" 00769 ); 00770 } 00771 $this->output( "\n" ); 00772 } 00773 00774 00775 // Script specific parameters not defined on construction by 00776 // Maintenance::addDefaultParams() 00777 $scriptSpecificParams = array_diff_key( 00778 # all script parameters: 00779 $this->mParams, 00780 # remove the Maintenance default parameters: 00781 $this->mGenericParameters, 00782 $this->mDependantParameters 00783 ); 00784 if( count($scriptSpecificParams) > 0 ) { 00785 $this->output( "Script specific parameters:\n" ); 00786 // Parameters description 00787 foreach ( $scriptSpecificParams as $par => $info ) { 00788 if ( $info['shortName'] !== false ) { 00789 $par .= " (-{$info['shortName']})"; 00790 } 00791 $this->output( 00792 wordwrap( "$tab--$par: " . $info['desc'], $descWidth, 00793 "\n$tab$tab" ) . "\n" 00794 ); 00795 } 00796 $this->output( "\n" ); 00797 } 00798 00799 // Print arguments 00800 if( count( $this->mArgList ) > 0 ) { 00801 $this->output( "Arguments:\n" ); 00802 // Arguments description 00803 foreach ( $this->mArgList as $info ) { 00804 $openChar = $info['require'] ? '<' : '['; 00805 $closeChar = $info['require'] ? '>' : ']'; 00806 $this->output( 00807 wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " . 00808 $info['desc'], $descWidth, "\n$tab$tab" ) . "\n" 00809 ); 00810 } 00811 $this->output( "\n" ); 00812 } 00813 00814 die( 1 ); 00815 } 00816 00820 public function finalSetup() { 00821 global $wgCommandLineMode, $wgShowSQLErrors, $wgServer; 00822 global $wgDBadminuser, $wgDBadminpassword; 00823 global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf; 00824 00825 # Turn off output buffering again, it might have been turned on in the settings files 00826 if ( ob_get_level() ) { 00827 ob_end_flush(); 00828 } 00829 # Same with these 00830 $wgCommandLineMode = true; 00831 00832 # Override $wgServer 00833 if( $this->hasOption( 'server') ) { 00834 $wgServer = $this->getOption( 'server', $wgServer ); 00835 } 00836 00837 # If these were passed, use them 00838 if ( $this->mDbUser ) { 00839 $wgDBadminuser = $this->mDbUser; 00840 } 00841 if ( $this->mDbPass ) { 00842 $wgDBadminpassword = $this->mDbPass; 00843 } 00844 00845 if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) { 00846 $wgDBuser = $wgDBadminuser; 00847 $wgDBpassword = $wgDBadminpassword; 00848 00849 if ( $wgDBservers ) { 00853 foreach ( $wgDBservers as $i => $server ) { 00854 $wgDBservers[$i]['user'] = $wgDBuser; 00855 $wgDBservers[$i]['password'] = $wgDBpassword; 00856 } 00857 } 00858 if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) { 00859 $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser; 00860 $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword; 00861 } 00862 LBFactory::destroyInstance(); 00863 } 00864 00865 $this->afterFinalSetup(); 00866 00867 $wgShowSQLErrors = true; 00868 @set_time_limit( 0 ); 00869 $this->adjustMemoryLimit(); 00870 } 00871 00875 protected function afterFinalSetup() { 00876 if ( defined( 'MW_CMDLINE_CALLBACK' ) ) { 00877 call_user_func( MW_CMDLINE_CALLBACK ); 00878 } 00879 } 00880 00885 public function globals() { 00886 if ( $this->hasOption( 'globals' ) ) { 00887 print_r( $GLOBALS ); 00888 } 00889 } 00890 00895 public function loadSettings() { 00896 global $wgCommandLineMode, $IP; 00897 00898 if ( isset( $this->mOptions['conf'] ) ) { 00899 $settingsFile = $this->mOptions['conf']; 00900 } elseif ( defined("MW_CONFIG_FILE") ) { 00901 $settingsFile = MW_CONFIG_FILE; 00902 } else { 00903 $settingsFile = "$IP/LocalSettings.php"; 00904 } 00905 if ( isset( $this->mOptions['wiki'] ) ) { 00906 $bits = explode( '-', $this->mOptions['wiki'] ); 00907 if ( count( $bits ) == 1 ) { 00908 $bits[] = ''; 00909 } 00910 define( 'MW_DB', $bits[0] ); 00911 define( 'MW_PREFIX', $bits[1] ); 00912 } 00913 00914 if ( !is_readable( $settingsFile ) ) { 00915 $this->error( "A copy of your installation's LocalSettings.php\n" . 00916 "must exist and be readable in the source directory.\n" . 00917 "Use --conf to specify it." , true ); 00918 } 00919 $wgCommandLineMode = true; 00920 return $settingsFile; 00921 } 00922 00928 public function purgeRedundantText( $delete = true ) { 00929 # Data should come off the master, wrapped in a transaction 00930 $dbw = $this->getDB( DB_MASTER ); 00931 $dbw->begin( __METHOD__ ); 00932 00933 $tbl_arc = $dbw->tableName( 'archive' ); 00934 $tbl_rev = $dbw->tableName( 'revision' ); 00935 $tbl_txt = $dbw->tableName( 'text' ); 00936 00937 # Get "active" text records from the revisions table 00938 $this->output( 'Searching for active text records in revisions table...' ); 00939 $res = $dbw->query( "SELECT DISTINCT rev_text_id FROM $tbl_rev" ); 00940 foreach ( $res as $row ) { 00941 $cur[] = $row->rev_text_id; 00942 } 00943 $this->output( "done.\n" ); 00944 00945 # Get "active" text records from the archive table 00946 $this->output( 'Searching for active text records in archive table...' ); 00947 $res = $dbw->query( "SELECT DISTINCT ar_text_id FROM $tbl_arc" ); 00948 foreach ( $res as $row ) { 00949 $cur[] = $row->ar_text_id; 00950 } 00951 $this->output( "done.\n" ); 00952 00953 # Get the IDs of all text records not in these sets 00954 $this->output( 'Searching for inactive text records...' ); 00955 $set = implode( ', ', $cur ); 00956 $res = $dbw->query( "SELECT old_id FROM $tbl_txt WHERE old_id NOT IN ( $set )" ); 00957 $old = array(); 00958 foreach ( $res as $row ) { 00959 $old[] = $row->old_id; 00960 } 00961 $this->output( "done.\n" ); 00962 00963 # Inform the user of what we're going to do 00964 $count = count( $old ); 00965 $this->output( "$count inactive items found.\n" ); 00966 00967 # Delete as appropriate 00968 if ( $delete && $count ) { 00969 $this->output( 'Deleting...' ); 00970 $set = implode( ', ', $old ); 00971 $dbw->query( "DELETE FROM $tbl_txt WHERE old_id IN ( $set )" ); 00972 $this->output( "done.\n" ); 00973 } 00974 00975 # Done 00976 $dbw->commit( __METHOD__ ); 00977 } 00978 00983 protected function getDir() { 00984 return __DIR__; 00985 } 00986 00993 public static function getMaintenanceScripts() { 00994 global $wgMaintenanceScripts; 00995 return $wgMaintenanceScripts + self::getCoreScripts(); 00996 } 00997 01002 protected static function getCoreScripts() { 01003 if ( !self::$mCoreScripts ) { 01004 $paths = array( 01005 __DIR__, 01006 __DIR__ . '/language', 01007 __DIR__ . '/storage', 01008 ); 01009 self::$mCoreScripts = array(); 01010 foreach ( $paths as $p ) { 01011 $handle = opendir( $p ); 01012 while ( ( $file = readdir( $handle ) ) !== false ) { 01013 if ( $file == 'Maintenance.php' ) { 01014 continue; 01015 } 01016 $file = $p . '/' . $file; 01017 if ( is_dir( $file ) || !strpos( $file, '.php' ) || 01018 ( strpos( file_get_contents( $file ), '$maintClass' ) === false ) ) { 01019 continue; 01020 } 01021 require( $file ); 01022 $vars = get_defined_vars(); 01023 if ( array_key_exists( 'maintClass', $vars ) ) { 01024 self::$mCoreScripts[$vars['maintClass']] = $file; 01025 } 01026 } 01027 closedir( $handle ); 01028 } 01029 } 01030 return self::$mCoreScripts; 01031 } 01032 01040 protected function &getDB( $db, $groups = array(), $wiki = false ) { 01041 if ( is_null( $this->mDb ) ) { 01042 return wfGetDB( $db, $groups, $wiki ); 01043 } else { 01044 return $this->mDb; 01045 } 01046 } 01047 01053 public function setDB( &$db ) { 01054 $this->mDb = $db; 01055 } 01056 01061 private function lockSearchindex( &$db ) { 01062 $write = array( 'searchindex' ); 01063 $read = array( 'page', 'revision', 'text', 'interwiki', 'l10n_cache', 'user' ); 01064 $db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ ); 01065 } 01066 01071 private function unlockSearchindex( &$db ) { 01072 $db->unlockTables( __CLASS__ . '::' . __METHOD__ ); 01073 } 01074 01080 private function relockSearchindex( &$db ) { 01081 $this->unlockSearchindex( $db ); 01082 $this->lockSearchindex( $db ); 01083 } 01084 01092 public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) { 01093 $lockTime = time(); 01094 01095 # Lock searchindex 01096 if ( $maxLockTime ) { 01097 $this->output( " --- Waiting for lock ---" ); 01098 $this->lockSearchindex( $dbw ); 01099 $lockTime = time(); 01100 $this->output( "\n" ); 01101 } 01102 01103 # Loop through the results and do a search update 01104 foreach ( $results as $row ) { 01105 # Allow reads to be processed 01106 if ( $maxLockTime && time() > $lockTime + $maxLockTime ) { 01107 $this->output( " --- Relocking ---" ); 01108 $this->relockSearchindex( $dbw ); 01109 $lockTime = time(); 01110 $this->output( "\n" ); 01111 } 01112 call_user_func( $callback, $dbw, $row ); 01113 } 01114 01115 # Unlock searchindex 01116 if ( $maxLockTime ) { 01117 $this->output( " --- Unlocking --" ); 01118 $this->unlockSearchindex( $dbw ); 01119 $this->output( "\n" ); 01120 } 01121 01122 } 01123 01130 public function updateSearchIndexForPage( $dbw, $pageId ) { 01131 // Get current revision 01132 $rev = Revision::loadFromPageId( $dbw, $pageId ); 01133 $title = null; 01134 if ( $rev ) { 01135 $titleObj = $rev->getTitle(); 01136 $title = $titleObj->getPrefixedDBkey(); 01137 $this->output( "$title..." ); 01138 # Update searchindex 01139 # TODO: pass the Content object to SearchUpdate, let the search engine decide how to deal with it. 01140 $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getContent()->getTextForSearchIndex() ); 01141 $u->doUpdate(); 01142 $this->output( "\n" ); 01143 } 01144 return $title; 01145 } 01146 01155 public static function posix_isatty( $fd ) { 01156 if ( !MWInit::functionExists( 'posix_isatty' ) ) { 01157 return !$fd; 01158 } else { 01159 return posix_isatty( $fd ); 01160 } 01161 } 01162 01168 public static function readconsole( $prompt = '> ' ) { 01169 static $isatty = null; 01170 if ( is_null( $isatty ) ) { 01171 $isatty = self::posix_isatty( 0 /*STDIN*/ ); 01172 } 01173 01174 if ( $isatty && function_exists( 'readline' ) ) { 01175 return readline( $prompt ); 01176 } else { 01177 if ( $isatty ) { 01178 $st = self::readlineEmulation( $prompt ); 01179 } else { 01180 if ( feof( STDIN ) ) { 01181 $st = false; 01182 } else { 01183 $st = fgets( STDIN, 1024 ); 01184 } 01185 } 01186 if ( $st === false ) return false; 01187 $resp = trim( $st ); 01188 return $resp; 01189 } 01190 } 01191 01197 private static function readlineEmulation( $prompt ) { 01198 $bash = Installer::locateExecutableInDefaultPaths( array( 'bash' ) ); 01199 if ( !wfIsWindows() && $bash ) { 01200 $retval = false; 01201 $encPrompt = wfEscapeShellArg( $prompt ); 01202 $command = "read -er -p $encPrompt && echo \"\$REPLY\""; 01203 $encCommand = wfEscapeShellArg( $command ); 01204 $line = wfShellExec( "$bash -c $encCommand", $retval ); 01205 01206 if ( $retval == 0 ) { 01207 return $line; 01208 } elseif ( $retval == 127 ) { 01209 // Couldn't execute bash even though we thought we saw it. 01210 // Shell probably spit out an error message, sorry :( 01211 // Fall through to fgets()... 01212 } else { 01213 // EOF/ctrl+D 01214 return false; 01215 } 01216 } 01217 01218 // Fallback... we'll have no editing controls, EWWW 01219 if ( feof( STDIN ) ) { 01220 return false; 01221 } 01222 print $prompt; 01223 return fgets( STDIN, 1024 ); 01224 } 01225 } 01226 01230 class FakeMaintenance extends Maintenance { 01231 protected $mSelf = "FakeMaintenanceScript"; 01232 public function execute() { 01233 return; 01234 } 01235 } 01236 01241 abstract class LoggedUpdateMaintenance extends Maintenance { 01242 public function __construct() { 01243 parent::__construct(); 01244 $this->addOption( 'force', 'Run the update even if it was completed already' ); 01245 $this->setBatchSize( 200 ); 01246 } 01247 01248 public function execute() { 01249 $db = $this->getDB( DB_MASTER ); 01250 $key = $this->getUpdateKey(); 01251 01252 if ( !$this->hasOption( 'force' ) && 01253 $db->selectRow( 'updatelog', '1', array( 'ul_key' => $key ), __METHOD__ ) ) 01254 { 01255 $this->output( "..." . $this->updateSkippedMessage() . "\n" ); 01256 return true; 01257 } 01258 01259 if ( !$this->doDBUpdates() ) { 01260 return false; 01261 } 01262 01263 if ( 01264 $db->insert( 'updatelog', array( 'ul_key' => $key ), __METHOD__, 'IGNORE' ) ) 01265 { 01266 return true; 01267 } else { 01268 $this->output( $this->updatelogFailedMessage() . "\n" ); 01269 return false; 01270 } 01271 } 01272 01277 protected function updateSkippedMessage() { 01278 $key = $this->getUpdateKey(); 01279 return "Update '{$key}' already logged as completed."; 01280 } 01281 01286 protected function updatelogFailedMessage() { 01287 $key = $this->getUpdateKey(); 01288 return "Unable to log update '{$key}' as completed."; 01289 } 01290 01296 abstract protected function doDBUpdates(); 01297 01302 abstract protected function getUpdateKey(); 01303 }