X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_ecrire%28%22statistiques_visites%22%2C%22%22%29%20.%20%22?a=blobdiff_plain;f=maintenance%2FMaintenance.php;h=8dc9d5324a820a98c96ea110dd43e75efa465b6e;hb=fe866da9a6f47b693540a3cf2cbf501b93236500;hp=f2118223222a0e7335ee4ae49f8ed24048ebb790;hpb=3a5a5abaa93491e01220c6daa711dd1e1423a19e;p=lhc%2Fweb%2Fwiklou.git diff --git a/maintenance/Maintenance.php b/maintenance/Maintenance.php index f211822322..8dc9d5324a 100644 --- a/maintenance/Maintenance.php +++ b/maintenance/Maintenance.php @@ -1,6 +1,13 @@ addDefaultParams(); + register_shutdown_function( array( $this, 'outputChanneled' ), false ); } /** @@ -108,18 +116,18 @@ abstract class Maintenance { * @param $withArg Boolean Is an argument required with this option? */ protected function addOption( $name, $description, $required = false, $withArg = false ) { - $this->mParams[ $name ] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg ); + $this->mParams[$name] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg ); } - + /** * Checks to see if a particular param exists. * @param $name String The name of the param * @return boolean */ protected function hasOption( $name ) { - return isset( $this->mOptions[ $name ] ); + return isset( $this->mOptions[$name] ); } - + /** * Get an option, or return the default * @param $name String The name of the param @@ -127,7 +135,7 @@ abstract class Maintenance { * @return mixed */ protected function getOption( $name, $default = null ) { - if( $this->hasOption($name) ) { + if( $this->hasOption( $name ) ) { return $this->mOptions[$name]; } else { // Set it so we don't have to provide the default again @@ -135,21 +143,28 @@ abstract class Maintenance { return $this->mOptions[$name]; } } - + /** - * Add some args that are needed. Used in formatting help + * Add some args that are needed + * @param $arg String Name of the arg, like 'start' + * @param $description String Short description of the arg + * @param $required Boolean Is this required? */ - protected function addArgs( $args ) { - $this->mArgList = array_merge( $this->mArgList, $args ); + protected function addArg( $arg, $description, $required = true ) { + $this->mArgList[] = array( + 'name' => $arg, + 'desc' => $description, + 'require' => $required + ); } - + /** * Does a given argument exist? * @param $argId int The integer value (from zero) for the arg * @return boolean */ protected function hasArg( $argId = 0 ) { - return isset( $this->mArgs[ $argId ] ) ; + return isset( $this->mArgs[$argId] ); } /** @@ -159,7 +174,7 @@ abstract class Maintenance { * @return mixed */ protected function getArg( $argId = 0, $default = null ) { - return $this->hasArg($argId) ? $this->mArgs[$argId] : $default; + return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default; } /** @@ -192,7 +207,7 @@ abstract class Maintenance { if( !$len ) return $f; $input = fgets( $f, $len ); - fclose ( $f ); + fclose( $f ); return rtrim( $input ); } @@ -200,14 +215,14 @@ abstract class Maintenance { * Throw some output to the user. Scripts can call this with no fears, * as we handle all --quiet stuff here * @param $out String The text to show to the user + * @param $channel Mixed Unique identifier for the channel. See function outputChanneled. */ - protected function output( $out ) { + protected function output( $out, $channel = null ) { if( $this->mQuiet ) { return; } - $f = fopen( 'php://stdout', 'w' ); - fwrite( $f, $out ); - fclose( $f ); + $out = preg_replace( '/\n\z/', '', $out ); + $this->outputChanneled( $out, $channel ); } /** @@ -217,12 +232,56 @@ abstract class Maintenance { * @param $die boolean If true, go ahead and die out. */ protected function error( $err, $die = false ) { - $f = fopen( 'php://stderr', 'w' ); - fwrite( $f, $err . "\n" ); - fclose( $f ); + $this->outputChanneled( false ); + if ( php_sapi_name() == 'cli' ) { + fwrite( STDERR, $err . "\n" ); + } else { + $f = fopen( 'php://stderr', 'w' ); + fwrite( $f, $err . "\n" ); + fclose( $f ); + } if( $die ) die(); } + private $atLineStart = true; + private $lastChannel = null; + + /** + * Message outputter with channeled message support. Messages on the + * same channel are concatenated, but any intervening messages in another + * channel start a new line. + * @param $msg String The message without trailing newline + * @param $channel Channel identifier or null for no channel. Channel comparison uses ===. + */ + public function outputChanneled( $msg, $channel = null ) { + $handle = fopen( 'php://stdout', 'w' ); + + if ( $msg === false ) { + // For cleanup + if ( !$this->atLineStart ) fwrite( $handle, "\n" ); + fclose( $handle ); + return; + } + + // End the current line if necessary + if ( !$this->atLineStart && $channel !== $this->lastChannel ) { + fwrite( $handle, "\n" ); + } + + fwrite( $handle, $msg ); + + $this->atLineStart = false; + if ( $channel === null ) { + // For unchanneled messages, output trailing newline immediately + fwrite( $handle, "\n" ); + $this->atLineStart = true; + } + $this->lastChannel = $channel; + + // Cleanup handle + fclose( $handle ); + } + /** * Does the script need different DB access? By default, we give Maintenance * scripts normal rights to the DB. Sometimes, a script needs admin rights @@ -240,7 +299,7 @@ abstract class Maintenance { /** * Add the default parameters to the scripts */ - private function addDefaultParams() { + protected function addDefaultParams() { $this->addOption( 'help', "Display this help message" ); $this->addOption( 'quiet', "Whether to supress non-error output" ); $this->addOption( 'conf', "Location of LocalSettings.php, if not default", false, true ); @@ -259,17 +318,17 @@ abstract class Maintenance { } /** - * Spawn a child maintenance script. Pass all of the current arguments + * Run a child maintenance script. Pass all of the current arguments * to it. * @param $maintClass String A name of a child maintenance class * @param $classFile String Full path of where the child is * @return Maintenance child */ - protected function spawnChild( $maintClass, $classFile = null ) { + protected function runChild( $maintClass, $classFile = null ) { // If we haven't already specified, kill setup procedures // for child scripts, we've already got a sane environment self::disableSetup(); - + // Make sure the class is loaded first if( !class_exists( $maintClass ) ) { if( $classFile ) { @@ -279,7 +338,7 @@ abstract class Maintenance { $this->error( "Cannot spawn child: $maintClass" ); } } - + $child = new $maintClass(); $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs ); return $child; @@ -297,7 +356,7 @@ abstract class Maintenance { * Do some sanity checking and basic setup */ public function setup() { - global $IP, $wgCommandLineMode, $wgUseNormalUser, $wgRequestTime; + global $IP, $wgCommandLineMode, $wgRequestTime; # Abort if called from a web server if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) { @@ -325,31 +384,42 @@ abstract class Maintenance { } # Set the memory limit - ini_set( 'memory_limit', -1 ); + # Note we need to set it again later in cache LocalSettings changed it + ini_set( 'memory_limit', $this->memoryLimit() ); + + # Set max execution time to 0 (no limit). PHP.net says that + # "When running PHP from the command line the default setting is 0." + # But sometimes this doesn't seem to be the case. + ini_set( 'max_execution_time', 0 ); - $wgRequestTime = microtime(true); + $wgRequestTime = microtime( true ); - # Define us as being in Mediawiki + # Define us as being in MediaWiki define( 'MEDIAWIKI', true ); # Setup $IP, using MW_INSTALL_PATH if it exists - $IP = strval( getenv('MW_INSTALL_PATH') ) !== '' - ? getenv('MW_INSTALL_PATH') + $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== '' + ? getenv( 'MW_INSTALL_PATH' ) : realpath( dirname( __FILE__ ) . '/..' ); - + $wgCommandLineMode = true; # Turn off output buffering if it's on @ob_end_flush(); - if (!isset( $wgUseNormalUser ) ) { - $wgUseNormalUser = false; - } - $this->loadParamsAndArgs(); $this->maybeHelp(); $this->validateParamsAndArgs(); } + /** + * Normally we disable the memory_limit when running admin scripts. + * Some scripts may wish to actually set a limit, however, to avoid + * blowing up unexpectedly. + */ + public function memoryLimit() { + return -1; + } + /** * Clear all params and arguments. */ @@ -431,7 +501,7 @@ abstract class Maintenance { # Short options for ( $p=1; $pmParams[$option]['withArg'] ) { + if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) { $param = next( $argv ); if ( $param === false ) { $this->error( "\nERROR: $option needs a value after it\n" ); @@ -456,24 +526,30 @@ abstract class Maintenance { /** * Run some validation checks on the params, etc */ - private function validateParamsAndArgs() { - # Check to make sure we've got all the required ones + protected function validateParamsAndArgs() { + $die = false; + # Check to make sure we've got all the required options foreach( $this->mParams as $opt => $info ) { - if( $info['require'] && !$this->hasOption($opt) ) { - $this->error( "Param $opt required.", true ); + if( $info['require'] && !$this->hasOption( $opt ) ) { + $this->error( "Param $opt required!" ); + $die = true; } } - - # Also make sure we've got enough arguments - if ( count( $this->mArgs ) < count( $this->mArgList ) ) { - $this->error( "Not enough arguments passed", true ); + # Check arg list too + foreach( $this->mArgList as $k => $info ) { + if( $info['require'] && !$this->hasArg($k) ) { + $this->error( "Argument <" . $info['name'] . "> required!" ); + $die = true; + } } + + if( $die ) $this->maybeHelp( true ); } - + /** * Handle the special variables that are global to all scripts */ - private function loadSpecialVars() { + protected function loadSpecialVars() { if( $this->hasOption( 'dbuser' ) ) $this->mDbUser = $this->getOption( 'dbuser' ); if( $this->hasOption( 'dbpass' ) ) @@ -488,36 +564,45 @@ abstract class Maintenance { * Maybe show the help. * @param $force boolean Whether to force the help to show, default false */ - private function maybeHelp( $force = false ) { + protected function maybeHelp( $force = false ) { ksort( $this->mParams ); - if( $this->hasOption('help') || in_array( 'help', $this->mArgs ) || $force ) { + if( $this->hasOption( 'help' ) || $force ) { $this->mQuiet = false; + if( $this->mDescription ) { $this->output( "\n" . $this->mDescription . "\n" ); } - $this->output( "\nUsage: php " . $this->mSelf ); + $output = "\nUsage: php " . $this->mSelf; if( $this->mParams ) { - $this->output( " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]" ); + $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]"; } if( $this->mArgList ) { - $this->output( " <" . implode( $this->mArgList, "> <" ) . ">" ); + $output .= " <"; + foreach( $this->mArgList as $k => $arg ) { + $output .= $arg['name'] . ">"; + if( $k < count( $this->mArgList ) - 1 ) + $output .= " <"; + } } - $this->output( "\n" ); + $this->output( "$output\n" ); foreach( $this->mParams as $par => $info ) { $this->output( "\t$par : " . $info['desc'] . "\n" ); } + foreach( $this->mArgList as $info ) { + $this->output( "\t<" . $info['name'] . "> : " . $info['desc'] . "\n" ); + } die( 1 ); } } - + /** * Handle some last-minute setup here. */ - private function finalSetup() { - global $wgCommandLineMode, $wgUseNormalUser, $wgShowSQLErrors; + public function finalSetup() { + global $wgCommandLineMode, $wgShowSQLErrors; global $wgTitle, $wgProfiling, $IP, $wgDBadminuser, $wgDBadminpassword; global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf; - + # Turn off output buffering again, it might have been turned on in the settings files if( ob_get_level() ) { ob_end_flush(); @@ -529,12 +614,12 @@ abstract class Maintenance { if( $this->mDbUser ) $wgDBadminuser = $this->mDbUser; if( $this->mDbPass ) - $wgDBadminpass = $this->mDbPass; + $wgDBadminpassword = $this->mDbPass; - if ( empty( $wgUseNormalUser ) && isset( $wgDBadminuser ) ) { + if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) { $wgDBuser = $wgDBadminuser; $wgDBpassword = $wgDBadminpassword; - + if( $wgDBservers ) { foreach ( $wgDBservers as $i => $server ) { $wgDBservers[$i]['user'] = $wgDBuser; @@ -546,15 +631,16 @@ abstract class Maintenance { $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword; } } - + if ( defined( 'MW_CMDLINE_CALLBACK' ) ) { $fn = MW_CMDLINE_CALLBACK; $fn(); } - + $wgShowSQLErrors = true; @set_time_limit( 0 ); - + ini_set( 'memory_limit', $this->memoryLimit() ); + $wgProfiling = false; // only for Profiler.php mode; avoids OOM errors } @@ -582,7 +668,7 @@ abstract class Maintenance { $db = array_shift( $this->mArgs ); } list( $site, $lang ) = $wgConf->siteFromDB( $db ); - + # If not, work out the language and site the old way if ( is_null( $site ) || is_null( $lang ) ) { if ( !$db ) { @@ -600,24 +686,22 @@ abstract class Maintenance { $lang = 'aa'; $site = 'wikipedia'; } - + # This is for the IRC scripts, which now run as the apache user # The apache user doesn't have access to the wikiadmin_pass command if ( $_ENV['USER'] == 'apache' ) { #if ( posix_geteuid() == 48 ) { $wgUseNormalUser = true; } - + putenv( 'wikilang=' . $lang ); - + $DP = $IP; ini_set( 'include_path', ".:$IP:$IP/includes:$IP/languages:$IP/maintenance" ); - + if ( $lang == 'test' && $site == 'wikipedia' ) { define( 'TESTWIKI', 1 ); } - - $this->finalSetup(); } /** @@ -641,17 +725,16 @@ abstract class Maintenance { define( 'MW_DB', $bits[0] ); define( 'MW_PREFIX', $bits[1] ); } - - if ( ! is_readable( $settingsFile ) ) { + + if ( !is_readable( $settingsFile ) ) { $this->error( "A copy of your installation's LocalSettings.php\n" . "must exist and be readable in the source directory.", true ); } $wgCommandLineMode = true; $DP = $IP; - $this->finalSetup(); return $settingsFile; } - + /** * Support function for cleaning up redundant text records * @param $delete boolean Whether or not to actually delete the records @@ -669,7 +752,7 @@ abstract class Maintenance { # Get "active" text records from the revisions table $this->output( "Searching for active text records in revisions table..." ); $res = $dbw->query( "SELECT DISTINCT rev_text_id FROM $tbl_rev" ); - while( $row = $dbw->fetchObject( $res ) ) { + foreach( $res as $row ) { $cur[] = $row->rev_text_id; } $this->output( "done.\n" ); @@ -677,7 +760,7 @@ abstract class Maintenance { # Get "active" text records from the archive table $this->output( "Searching for active text records in archive table..." ); $res = $dbw->query( "SELECT DISTINCT ar_text_id FROM $tbl_arc" ); - while( $row = $dbw->fetchObject( $res ) ) { + foreach( $res as $row ) { $cur[] = $row->ar_text_id; } $this->output( "done.\n" ); @@ -687,7 +770,7 @@ abstract class Maintenance { $set = implode( ', ', $cur ); $res = $dbw->query( "SELECT old_id FROM $tbl_txt WHERE old_id NOT IN ( $set )" ); $old = array(); - while( $row = $dbw->fetchObject( $res ) ) { + foreach( $res as $row ) { $old[] = $row->old_id; } $this->output( "done.\n" ); @@ -707,7 +790,7 @@ abstract class Maintenance { # Done $dbw->commit(); } - + /** * Get the maintenance directory. */ @@ -725,12 +808,12 @@ abstract class Maintenance { global $wgMaintenanceScripts; return $wgMaintenanceScripts + self::getCoreScripts(); } - + /** * Return all of the core maintenance scripts * @return array */ - private static function getCoreScripts() { + protected static function getCoreScripts() { if( !self::$mCoreScripts ) { self::disableSetup(); $paths = array(