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