From 59b60fc31194b3b09bba2c95b8ddf85c3f9e81bb Mon Sep 17 00:00:00 2001 From: Chad Horohoe Date: Wed, 24 Jun 2009 02:02:37 +0000 Subject: [PATCH] Merge maintenance-work branch: * (bug 16322) Allow maint scripts to accept DB user/pass over input or params if no AdminSettings.php * (bug 18768) Remove AdminSettings.php from MediaWiki core * (bug 19157) createAndPromote error on bad password * (bug 14201) Create AdminSettings.php during wiki installation, in the same way as LocalSettings.php * Introduce new Maintenance class framework and port a good number of scripts over; the ones that are left are a little more complicated. Read the docs. * Not deleting "unused" files yet, don't want to break everything at once :) --- AdminSettings.sample | 32 -- RELEASE-NOTES | 8 +- UPGRADE | 19 +- config/index.php | 12 + docs/maintenance.txt | 54 +++ docs/scripts.txt | 7 +- includes/SiteStats.php | 62 ++- maintenance/Doxyfile | 2 +- maintenance/Maintenance.php | 639 ++++++++++++++++++++++++++ maintenance/README | 4 +- maintenance/attachLatest.php | 89 ++-- maintenance/benchmarkPurge.php | 141 +++--- maintenance/changePassword.php | 65 +-- maintenance/checkAutoLoader.php | 53 ++- maintenance/checkBadRedirects.php | 58 ++- maintenance/checkImages.php | 104 +++-- maintenance/checkUsernames.php | 25 +- maintenance/clear_interwiki_cache.php | 39 +- maintenance/clear_stats.php | 53 +-- maintenance/createAndPromote.php | 104 ++--- maintenance/deleteBatch.php | 156 +++---- maintenance/deleteDefaultMessages.php | 69 +-- maintenance/deleteImageMemcached.php | 53 +-- maintenance/deleteRevision.php | 78 ++-- maintenance/doMaintenance.php | 60 +++ maintenance/eval.php | 103 +++-- maintenance/fetchText.php | 66 +-- maintenance/getLagTimes.php | 43 +- maintenance/getSlaveServer.php | 44 +- maintenance/initStats.php | 29 +- maintenance/mctest.php | 95 ++-- maintenance/moveBatch.php | 140 +++--- maintenance/nextJobDB.php | 106 +++-- maintenance/nukeNS.php | 140 +++--- maintenance/nukePage.php | 98 +++- maintenance/populateLogSearch.php | 89 +++- maintenance/populateParentId.php | 105 ++++- maintenance/purgeOldText.php | 31 +- maintenance/reassignEdits.php | 181 ++++++-- maintenance/rebuildFileCache.php | 159 ++++--- maintenance/refreshImageCount.php | 49 +- maintenance/removeUnusedAccounts.php | 134 +++--- maintenance/renameDbPrefix.php | 109 +++-- maintenance/renderDump.php | 75 ++- maintenance/runJobs.php | 124 ++--- maintenance/showJobs.php | 19 +- maintenance/showStats.php | 62 +-- maintenance/sql.php | 86 ++-- maintenance/stats.php | 111 +++-- maintenance/undelete.php | 51 +- maintenance/updateArticleCount.php | 98 ++-- maintenance/updateSearchIndex.php | 181 ++++++-- maintenance/updateSpecialPages.php | 200 ++++---- maintenance/updaters.inc | 5 +- maintenance/waitForSlave.php | 17 +- profileinfo.php | 1 - t/Search.inc | 3 +- 57 files changed, 3092 insertions(+), 1648 deletions(-) delete mode 100644 AdminSettings.sample create mode 100644 docs/maintenance.txt create mode 100644 maintenance/Maintenance.php create mode 100644 maintenance/doMaintenance.php diff --git a/AdminSettings.sample b/AdminSettings.sample deleted file mode 100644 index 8b6fe99351..0000000000 --- a/AdminSettings.sample +++ /dev/null @@ -1,32 +0,0 @@ -Environment check $conf->RootUser = importPost( "RootUser", "root" ); $conf->RootPW = importPost( "RootPW", "" ); $useRoot = importCheck( 'useroot', false ); + $conf->populateadmin = importCheck( 'populateadmin', false ); $conf->LanguageCode = importPost( "LanguageCode", "en" ); ## MySQL specific: $conf->DBprefix = importPost( "DBprefix" ); @@ -1527,6 +1528,8 @@ if( count( $errs ) ) { checked="checked" />   + populateadmin ) { ?>checked="checked" /> +  
@@ -1792,6 +1795,11 @@ function writeLocalSettings( $conf ) { # Needs literal string interpolation for the current style path $slconf['RightsIcon'] = $conf->RightsIcon; } + + if( $conf->populateadmin ) { + $slconf['DBadminuser'] = $conf->RootUser; + $slconf['DBadminpassword'] = $conf->RootPW; + } if( $conf->DBtype == 'mysql' ) { $dbsettings = @@ -1899,6 +1907,10 @@ if ( \$wgCommandLineMode ) { {$dbsettings} +## Database admin settings, used for maintenance scripts +\$wgDBadminuser = \"{$slconf['DBadminuser']}\"; +\$wgDBadminpassword = \"{$slconf['DBadminpassword']}\"; + ## Shared memory settings \$wgMainCacheType = $cacheType; \$wgMemCachedServers = $mcservers; diff --git a/docs/maintenance.txt b/docs/maintenance.txt new file mode 100644 index 0000000000..d46d12e27c --- /dev/null +++ b/docs/maintenance.txt @@ -0,0 +1,54 @@ +Prior to version 1.16, maintenance scripts were a hodgepodge of code that +had no cohesion or formal method of action. Beginning in 1.16, maintenance +scripts have been cleaned up to use a unified class. + +1. Directory structure +2. How to run a script +3. How to write your own + +1. DIRECTORY STRUCTURE + The /maintenance directory of a MediaWiki installation contains several +subdirectories, all of which have unique purposes. + +2. HOW TO RUN A SCRIPT + Ridiculously simple, just call 'php someScript.php' that's in the top- +level /maintenance directory. + +Example: + php clear_stats.php + +The following parameters are available to all maintenance scripts +--help : Print a help message +--quiet : Quiet non-error output +--dbuser : The database user to use for the script (if needed) +--dbpass : Same as above (if needed) + +3. HOW TO WRITE YOUR OWN +Make a file in the maintenance directory called myScript.php or something. +In it, write the following: + +==BEGIN== + +selectField( 'revision', 'COUNT(*)', '', __METHOD__ ); + $edits += $dbr->selectField( 'archive', 'COUNT(*)', '', __METHOD__ ); + wfOut( "{$edits}\nCounting number of articles..." ); + + global $wgContentNamespaces; + $good = $dbr->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $wgContentNamespaces, 'page_is_redirect' => 0, 'page_len > 0' ), __METHOD__ ); + wfOut( "{$good}\nCounting total pages..." ); + + $pages = $dbr->selectField( 'page', 'COUNT(*)', '', __METHOD__ ); + wfOut( "{$pages}\nCounting number of users..." ); + + $users = $dbr->selectField( 'user', 'COUNT(*)', '', __METHOD__ ); + wfOut( "{$users}\nCounting number of admins..." ); + + $admin = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), __METHOD__ ); + wfOut( "{$admin}\nCounting number of images..." ); + + $image = $dbr->selectField( 'image', 'COUNT(*)', '', __METHOD__ ); + wfOut( "{$image}\n" ); + + if( !$noViews ) { + wfOut( "Counting total page views..." ); + $views = $dbr->selectField( 'page', 'SUM(page_counter)', '', __METHOD__ ); + wfOut( "{$views}\n" ); + } + + wfOut( "\nUpdating site statistics..." ); + + $dbw = wfGetDB( DB_MASTER ); + $values = array( 'ss_total_edits' => $edits, + 'ss_good_articles' => $good, + 'ss_total_pages' => $pages, + 'ss_users' => $users, + 'ss_admins' => $admin, + 'ss_images' => $image ); + $conds = array( 'ss_row_id' => 1 ); + $views = array( 'ss_total_views' => isset( $views ) ? $views : 0 ); + + if( $update ) { + $dbw->update( 'site_stats', $values, $conds, __METHOD__ ); + } else { + $dbw->delete( 'site_stats', $conds, __METHOD__ ); + $dbw->insert( 'site_stats', array_merge( $values, $conds, $views ), __METHOD__ ); + } + + wfOut( "done.\n" ); + } } diff --git a/maintenance/Doxyfile b/maintenance/Doxyfile index cdc748d8e9..e4014e83db 100644 --- a/maintenance/Doxyfile +++ b/maintenance/Doxyfile @@ -135,7 +135,7 @@ FILE_PATTERNS = *.c \ RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = YES -EXCLUDE_PATTERNS = LocalSettings.php AdminSettings.php +EXCLUDE_PATTERNS = LocalSettings.php EXAMPLE_PATH = EXAMPLE_PATTERNS = * EXAMPLE_RECURSIVE = NO diff --git a/maintenance/Maintenance.php b/maintenance/Maintenance.php new file mode 100644 index 0000000000..6b5eda599a --- /dev/null +++ b/maintenance/Maintenance.php @@ -0,0 +1,639 @@ + + * @since 1.16 + * @ingroup Maintenance + */ +abstract class Maintenance { + + /** + * Constants for DB access type + * @see Maintenance::getDbType() + */ + const NO_DB = 0; + const NORMAL_DB = 1; + const ADMIN_DB = 2; + + // This is the desired params + private $mParams = array(); + + // Array of desired args + private $mArgList = array(); + + // This is the list of options that were actually passed + private $mOptions = array(); + + // This is the list of arguments that were actually passed + protected $mArgs = array(); + + // Name of the script currently running + protected $mSelf; + + // Special vars for params that are always used + private $mQuiet = false; + private $mDbUser, $mDbPass; + + // A description of the script, children should change this + protected $mDescription = ''; + + // Have we already loaded our user input? + private $inputLoaded = false; + + // Batch size + protected $mBatchSize = 100; + + /** + * Default constructor. Children should call this if implementing + * their own constructors + */ + public function __construct() { + $this->addDefaultParams(); + } + + /** + * Do the actual work. All child classes will need to implement this + */ + abstract public function execute(); + + /** + * Add a parameter to the script. Will be displayed on --help + * with the associated description + * + * @param $name String The name of the param (help, version, etc) + * @param $description String The description of the param to show on --help + * @param $required boolean Is the param required? + * @param $withArg Boolean Is an argument required with this option? + */ + protected function addParam( $name, $description, $required = false, $withArg = false ) { + $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 ] ); + } + + /** + * Get an option, or return the default + * @param $name String The name of the param + * @param $default mixed Anything you want, default null + * @return mixed + */ + protected function getOption( $name, $default = null ) { + if( $this->hasOption($name) ) { + return $this->mOptions[$name]; + } else { + // Set it so we don't have to provide the default again + $this->mOptions[$name] = $default; + return $this->mOptions[$name]; + } + } + + /** + * Add some args that are needed. Used in formatting help + */ + protected function addArgs( $args ) { + $this->mArgList = array_merge( $this->mArgList, $args ); + } + + /** + * 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 ] ) ; + } + + /** + * Get an argument. + * @param $argId int The integer value (from zero) for the arg + * @param $default mixed The default if it doesn't exist + * @return mixed + */ + protected function getArg( $argId = 0, $default = null ) { + return $this->hasArg($name) ? $this->mArgs[$name] : $default; + } + + /** + * Set the batch size. + * @param $s int The number of operations to do in a batch + */ + protected function setBatchSize( $s = 0 ) { + $this->mBatchSize = $s; + } + + /** + * Return input from stdin. + * @param $length int The number of bytes to read. If null, + * just return the handle + * @return mixed + */ + protected function getStdin( $len = null ) { + $f = fopen( 'php://stdin', 'rt' ); + if( !$len ) { + return $f; + } + $input = fgets( $f, $len ); + fclose ( $f ); + return rtrim( $input ); + } + + /** + * 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 + */ + protected function output( $out ) { + if( $this->mQuiet ) { + return; + } + $f = fopen( 'php://stdout', 'w' ); + fwrite( $f, $out ); + fclose( $f ); + } + + /** + * Throw an error to the user. Doesn't respect --quiet, so don't use + * this for non-error output + * @param $err String The error to display + * @param $die boolean If true, go ahead and die out. + */ + protected function error( $err, $die = false ) { + $f = fopen( 'php://stderr', 'w' ); + fwrite( $f, $err ); + fclose( $f ); + if( $die ) die(); + } + + /** + * Does the script need normal DB access? By default, we give Maintenance + * scripts admin rights to the DB (when available). Sometimes, a script needs + * normal access for a reason and sometimes they want no access. Subclasses + * should override and return one of the following values, as needed: + * Maintenance::NO_DB - For no DB access at all + * Maintenance::NORMAL_DB - For normal DB access + * Maintenance::ADMIN_DB - For admin DB access, default + * @return int + */ + protected function getDbType() { + return Maintenance :: ADMIN_DB; + } + + /** + * Add the default parameters to the scripts + */ + private function addDefaultParams() { + $this->addParam( 'help', "Display this help message" ); + $this->addParam( 'quiet', "Whether to supress non-error output" ); + $this->addParam( 'conf', "Location of LocalSettings.php, if not default", false, true ); + $this->addParam( 'wiki', "For specifying the wiki ID", false, true ); + if( $this->getDbType() > 0 ) { + $this->addParam( 'dbuser', "The DB user to use for this script", false, true ); + $this->addParam( 'dbpass', "The password to use for this script", false, true ); + } + } + + /** + * Spawn 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 ) { + // If we haven't already specified, kill setup procedures + // for child scripts, we've already got a sane environment + if( !defined( 'MW_NO_SETUP' ) ) { + define( 'MW_NO_SETUP', true ); + } + + // Make sure the class is loaded first + if( !class_exists( $maintClass ) ) { + if( $classFile ) { + require_once( $classFile ); + } + if( !class_exists( $maintClass ) ) { + $this->error( "Cannot spawn child: $maintClass\n" ); + } + } + + $child = new $maintClass(); + $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs ); + return $child; + } + + /** + * Do some sanity checking and basic setup + */ + public function setup() { + global $IP, $wgCommandLineMode, $wgUseNormalUser, $wgRequestTime; + + # Abort if called from a web server + if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) { + $this->error( "This script must be run from the command line\n", true ); + } + + # Make sure we can handle script parameters + if( !ini_get( 'register_argc_argv' ) ) { + $this->error( "Cannot get command line arguments, register_argc_argv is set to false", true ); + } + + # Make sure we're on PHP5 or better + if( version_compare( PHP_VERSION, '5.0.0' ) < 0 ) { + $this->error( "Sorry! This version of MediaWiki requires PHP 5; you are running " . + PHP_VERSION . ".\n\n" . + "If you are sure you already have PHP 5 installed, it may be installed\n" . + "in a different path from PHP 4. Check with your system administrator.\n", true ); + } + + if( version_compare( phpversion(), '5.2.4' ) >= 0 ) { + // Send PHP warnings and errors to stderr instead of stdout. + // This aids in diagnosing problems, while keeping messages + // out of redirected output. + if( ini_get( 'display_errors' ) ) { + ini_set( 'display_errors', 'stderr' ); + } + + // Don't touch the setting on earlier versions of PHP, + // as setting it would disable output if you'd wanted it. + + // Note that exceptions are also sent to stderr when + // command-line mode is on, regardless of PHP version. + } + + # Set the memory limit + ini_set( 'memory_limit', -1 ); + + $wgRequestTime = microtime(true); + + # 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') + : 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(); + } + + /** + * Clear all params and arguments. + */ + public function clearParamsAndArgs() { + $this->mOptions = array(); + $this->mArgs = array(); + $this->inputLoaded = false; + } + + /** + * Process command line arguments + * $mOptions becomes an array with keys set to the option names + * $mArgs becomes a zero-based array containing the non-option arguments + * + * @param $self String The name of the script, if any + * @param $opts Array An array of options, in form of key=>value + * @param $args Array An array of command line arguments + */ + public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) { + # If we were given opts or args, set those and return early + if( $self ) { + $this->mSelf = $self; + $this->inputLoaded = true; + } + if( $opts ) { + $this->mOptions = $opts; + $this->inputLoaded = true; + } + if( $args ) { + $this->mArgs = $args; + $this->inputLoaded = true; + } + + # If we've already loaded input (either by user values or from $argv) + # skip on loading it again. The array_shift() will corrupt values if + # it's run again and again + if( $this->inputLoaded ) { + $this->loadSpecialVars(); + return; + } + + global $argv; + $this->mSelf = array_shift( $argv ); + + $options = array(); + $args = array(); + + # Parse arguments + for( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) { + if ( $arg == '--' ) { + # End of options, remainder should be considered arguments + $arg = next( $argv ); + while( $arg !== false ) { + $args[] = $arg; + $arg = next( $argv ); + } + break; + } elseif ( substr( $arg, 0, 2 ) == '--' ) { + # Long options + $option = substr( $arg, 2 ); + if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) { + $param = next( $argv ); + if ( $param === false ) { + $this->error( "$arg needs a value after it\n", true ); + } + $options[$option] = $param; + } else { + $bits = explode( '=', $option, 2 ); + if( count( $bits ) > 1 ) { + $option = $bits[0]; + $param = $bits[1]; + } else { + $param = 1; + } + $options[$option] = $param; + } + } elseif ( substr( $arg, 0, 1 ) == '-' ) { + # Short options + for ( $p=1; $pmParams[$option]['withArg'] ) ) { + $param = next( $argv ); + if ( $param === false ) { + $this->error( "$arg needs a value after it\n", true ); + } + $options[$option] = $param; + } else { + $options[$option] = 1; + } + } + } else { + $args[] = $arg; + } + } + + # Check to make sure we've got all the required ones + foreach( $this->mParams as $opt => $info ) { + if( $info['require'] && !$this->hasOption($opt) ) { + $this->error( "Param $opt required.\n", true ); + } + } + + # Also make sure we've got enough arguments + if ( count( $args ) < count( $this->mArgList ) ) { + $this->error( "Not enough arguments passed", true ); + } + + $this->mOptions = $options; + $this->mArgs = $args; + $this->loadSpecialVars(); + $this->inputLoaded = true; + } + + /** + * Handle the special variables that are global to all scripts + */ + private function loadSpecialVars() { + if( $this->hasOption( 'dbuser' ) ) + $this->mDbUser = $this->getOption( 'dbuser' ); + if( $this->hasOption( 'dbpass' ) ) + $this->mDbPass = $this->getOption( 'dbpass' ); + if( $this->hasOption( 'quiet' ) ) + $this->mQuiet = true; + } + + /** + * Maybe show the help. + * @param $force boolean Whether to force the help to show, default false + */ + private function maybeHelp( $force = false ) { + if( $this->hasOption('help') || in_array( 'help', $this->mArgs ) || $force ) { + $this->mQuiet = false; + if( $this->mDescription ) { + $this->output( $this->mDescription . "\n" ); + } + $this->output( "\nUsage: php " . $this->mSelf ); + if( $this->mParams ) { + $this->output( " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]" ); + } + if( $this->mArgList ) { + $this->output( " <" . implode( $this->mArgList, "> <" ) . ">" ); + } + $this->output( "\n" ); + foreach( $this->mParams as $par => $info ) { + $this->output( "\t$par : " . $info['desc'] . "\n" ); + } + die( 1 ); + } + } + + /** + * Handle some last-minute setup here. + */ + private function finalSetup() { + global $wgCommandLineMode, $wgUseNormalUser, $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(); + } + # Same with these + $wgCommandLineMode = true; + + # If these were passed, use them + if( $this->mDbUser ) + $wgDBadminuser = $this->mDbUser; + if( $this->mDbPass ) + $wgDBadminpass = $this->mDbPass; + + if ( empty( $wgUseNormalUser ) && isset( $wgDBadminuser ) ) { + $wgDBuser = $wgDBadminuser; + $wgDBpassword = $wgDBadminpassword; + + if( $wgDBservers ) { + foreach ( $wgDBservers as $i => $server ) { + $wgDBservers[$i]['user'] = $wgDBuser; + $wgDBservers[$i]['password'] = $wgDBpassword; + } + } + if( isset( $wgLBFactoryConf['serverTemplate'] ) ) { + $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser; + $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword; + } + } + + if ( defined( 'MW_CMDLINE_CALLBACK' ) ) { + $fn = MW_CMDLINE_CALLBACK; + $fn(); + } + + $wgShowSQLErrors = true; + @set_time_limit( 0 ); + + $wgProfiling = false; // only for Profiler.php mode; avoids OOM errors + } + + /** + * Do setup specific to WMF + */ + public function loadWikimediaSettings() { + global $IP, $wgNoDBParam, $wgUseNormalUser, $wgConf; + + if ( empty( $wgNoDBParam ) ) { + # Check if we were passed a db name + if ( isset( $this->mOptions['wiki'] ) ) { + $db = $this->mOptions['wiki']; + } else { + $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 ) { + $lang = 'aa'; + } else { + $lang = $db; + } + if ( isset( $this->mArgs[0] ) ) { + $site = array_shift( $this->mArgs ); + } else { + $site = 'wikipedia'; + } + } + } else { + $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 ); + } + } + + /** + * Generic setup for most installs. Returns the location of LocalSettings + * @return String + */ + public function loadSettings() { + global $wgWikiFarm, $wgCommandLineMode, $IP, $DP; + + $wgWikiFarm = false; + if ( isset( $this->mOptions['conf'] ) ) { + $settingsFile = $this->mOptions['conf']; + } else { + $settingsFile = "$IP/LocalSettings.php"; + } + if ( isset( $this->mOptions['wiki'] ) ) { + $bits = explode( '-', $this->mOptions['wiki'] ); + if ( count( $bits ) == 1 ) { + $bits[] = ''; + } + define( 'MW_DB', $bits[0] ); + define( 'MW_PREFIX', $bits[1] ); + } + + if ( ! is_readable( $settingsFile ) ) { + $this->error( "A copy of your installation's LocalSettings.php\n" . + "must exist and be readable in the source directory.\n", 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 + * @author Rob Church + */ + protected function purgeRedundantText( $delete = true ) { + # Data should come off the master, wrapped in a transaction + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin(); + + $tbl_arc = $dbw->tableName( 'archive' ); + $tbl_rev = $dbw->tableName( 'revision' ); + $tbl_txt = $dbw->tableName( 'text' ); + + # 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 ) ) { + $cur[] = $row->rev_text_id; + } + $this->output( "done.\n" ); + + # 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 ) ) { + $cur[] = $row->ar_text_id; + } + $this->output( "done.\n" ); + + # Get the IDs of all text records not in these sets + $this->output( "Searching for inactive text records..." ); + $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 ) ) { + $old[] = $row->old_id; + } + $this->output( "done.\n" ); + + # Inform the user of what we're going to do + $count = count( $old ); + $this->output( "$count inactive items found.\n" ); + + # Delete as appropriate + if( $delete && $count ) { + $this->output( "Deleting..." ); + $set = implode( ', ', $old ); + $dbw->query( "DELETE FROM $tbl_txt WHERE old_id IN ( $set )" ); + $this->output( "done.\n" ); + } + + # Done + $dbw->commit(); + + } +} + diff --git a/maintenance/README b/maintenance/README index e2215c1dd9..6b23937ae0 100644 --- a/maintenance/README +++ b/maintenance/README @@ -10,8 +10,8 @@ proper installation. Certain scripts will require elevated access to the database. In order to provide this, first create a MySQL user with "all" permissions on the wiki -database, and then place their username and password in an AdminSettings.php -file in the directory above. See AdminSettings.sample for specifics on this. +database, and then set $wgDBadminuser and $wgDBadminpassword in your +LocalSettings.php === Brief explanation of files === diff --git a/maintenance/attachLatest.php b/maintenance/attachLatest.php index 8d680afaaa..ca8d1aa7d5 100644 --- a/maintenance/attachLatest.php +++ b/maintenance/attachLatest.php @@ -25,49 +25,58 @@ * @ingroup Maintenance */ -require_once( 'commandLine.inc' ); +require_once( "Maintenance.php" ); -$fixit = isset( $options['fix'] ); -$fname = 'attachLatest'; - -echo "Looking for pages with page_latest set to 0...\n"; -$dbw = wfGetDB( DB_MASTER ); -$result = $dbw->select( 'page', - array( 'page_id', 'page_namespace', 'page_title' ), - array( 'page_latest' => 0 ), - $fname ); - -$n = 0; -while( $row = $dbw->fetchObject( $result ) ) { - $pageId = intval( $row->page_id ); - $title = Title::makeTitle( $row->page_namespace, $row->page_title ); - $name = $title->getPrefixedText(); - $latestTime = $dbw->selectField( 'revision', - 'MAX(rev_timestamp)', - array( 'rev_page' => $pageId ), - $fname ); - if( !$latestTime ) { - echo wfWikiID()." $pageId [[$name]] can't find latest rev time?!\n"; - continue; +class AttachLatest extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->addParam( "fix", "Actually fix the entries, will dry run otherwise" ); + $this->mDescription = "Fix page_latest entries in the page table"; } + + public function execute() { + $this->output( "Looking for pages with page_latest set to 0...\n" ); + $dbw = wfGetDB( DB_MASTER ); + $result = $dbw->select( 'page', + array( 'page_id', 'page_namespace', 'page_title' ), + array( 'page_latest' => 0 ), + __METHOD__ ); - $revision = Revision::loadFromTimestamp( $dbw, $title, $latestTime ); - if( is_null( $revision ) ) { - echo wfWikiID()." $pageId [[$name]] latest time $latestTime, can't find revision id\n"; - continue; - } - $id = $revision->getId(); - echo wfWikiID()." $pageId [[$name]] latest time $latestTime, rev id $id\n"; - if( $fixit ) { - $article = new Article( $title ); - $article->updateRevisionOn( $dbw, $revision ); + $n = 0; + while( $row = $dbw->fetchObject( $result ) ) { + $pageId = intval( $row->page_id ); + $title = Title::makeTitle( $row->page_namespace, $row->page_title ); + $name = $title->getPrefixedText(); + $latestTime = $dbw->selectField( 'revision', + 'MAX(rev_timestamp)', + array( 'rev_page' => $pageId ), + __METHOD__ ); + if( !$latestTime ) { + $this->output( wfWikiID()." $pageId [[$name]] can't find latest rev time?!\n" ); + continue; + } + + $revision = Revision::loadFromTimestamp( $dbw, $title, $latestTime ); + if( is_null( $revision ) ) { + $this->output( wfWikiID()." $pageId [[$name]] latest time $latestTime, can't find revision id\n" ); + continue; + } + $id = $revision->getId(); + $this->output( wfWikiID()." $pageId [[$name]] latest time $latestTime, rev id $id\n" ); + if( $this->hasOption('fix') ) { + $article = new Article( $title ); + $article->updateRevisionOn( $dbw, $revision ); + } + $n++; + } + $dbw->freeResult( $result ); + $this->output( "Done! Processed $n pages.\n" ); + if( !$this->hasOption('fix') ) { + $this->output( "This was a dry run; rerun with --fix to update page_latest.\n" ); + } } - $n++; } -$dbw->freeResult( $result ); -echo "Done! Processed $n pages.\n"; -if( !$fixit ) { - echo "This was a dry run; rerun with --fix to update page_latest.\n"; -} - +$maintClass = "AttachLatest"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/benchmarkPurge.php b/maintenance/benchmarkPurge.php index 796e1da207..f67c6c811d 100644 --- a/maintenance/benchmarkPurge.php +++ b/maintenance/benchmarkPurge.php @@ -6,74 +6,87 @@ * @ingroup Maintenance */ -/** */ -require_once( "commandLine.inc" ); +require_once( "Maintenance.php" ); -/** - * Run a bunch of URLs through SquidUpdate::purge() - * to benchmark Squid response times. - * @param $urls array A bunch of URLs to purge - * @param $trials int How many times to run the test? - */ -function benchSquid( $urls, $trials = 1 ) { - $start = wfTime(); - for( $i = 0; $i < $trials; $i++) { - SquidUpdate::purge( $urls ); +class BenchmarkPurge extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->addParams( "count", "How many URLs to feed to Squid for purging", false, true ); + $this->mDescription = "Benchmark the Squid purge functions."; } - $delta = wfTime() - $start; - $pertrial = $delta / $trials; - $pertitle = $pertrial / count( $urls ); - return sprintf( "%4d titles in %6.2fms (%6.2fms each)", - count( $urls ), $pertrial * 1000.0, $pertitle * 1000.0 ); -} - -/** - * Get an array of randomUrl()'s. - * @param $length int How many urls to add to the array - */ -function randomUrlList( $length ) { - $list = array(); - for( $i = 0; $i < $length; $i++ ) { - $list[] = randomUrl(); + + public function execute() { + global $wgUseSquid; + if( !$wgUseSquid ) { + $this->error( "Squid purge benchmark doesn't do much without squid support on.\n". true ); + } else { + $this->output( "There are " . count( $wgSquidServers ) . " defined squid servers:\n" ); + if( $this->hasOption( 'count' ) ) { + $lengths = array( intval( $this->getOption('count') ) ); + } else { + $lengths = array( 1, 10, 100 ); + } + foreach( $lengths as $length ) { + $urls = $this->randomUrlList( $length ); + $trial = $this->benchSquid( $urls ); + $this->output( $trial . "\n" ); + } + } } - return $list; -} - -/** - * Return a random URL of the wiki. Not necessarily an actual title in the - * database, but at least a URL that looks like one. - */ -function randomUrl() { - global $wgServer, $wgArticlePath; - return $wgServer . str_replace( '$1', randomTitle(), $wgArticlePath ); -} - -/** - * Create a random title string (not necessarily a Title object). - * For use with randomUrl(). - */ -function randomTitle() { - $str = ''; - $length = mt_rand( 1, 20 ); - for( $i = 0; $i < $length; $i++ ) { - $str .= chr( mt_rand( ord('a'), ord('z') ) ); + + /** + * Run a bunch of URLs through SquidUpdate::purge() + * to benchmark Squid response times. + * @param $urls array A bunch of URLs to purge + * @param $trials int How many times to run the test? + */ + private function benchSquid( $urls, $trials = 1 ) { + $start = wfTime(); + for( $i = 0; $i < $trials; $i++) { + SquidUpdate::purge( $urls ); + } + $delta = wfTime() - $start; + $pertrial = $delta / $trials; + $pertitle = $pertrial / count( $urls ); + return sprintf( "%4d titles in %6.2fms (%6.2fms each)", + count( $urls ), $pertrial * 1000.0, $pertitle * 1000.0 ); } - return ucfirst( $str ); -} - -if( !$wgUseSquid ) { - wfDie( "Squid purge benchmark doesn't do much without squid support on.\n" ); -} else { - printf( "There are %d defined squid servers:\n", count( $wgSquidServers ) ); - #echo implode( "\n", $wgSquidServers ) . "\n"; - if( isset( $options['count'] ) ) { - $lengths = array( intval( $options['count'] ) ); - } else { - $lengths = array( 1, 10, 100 ); + + /** + * Get an array of randomUrl()'s. + * @param $length int How many urls to add to the array + */ + private function randomUrlList( $length ) { + $list = array(); + for( $i = 0; $i < $length; $i++ ) { + $list[] = $this->randomUrl(); + } + return $list; + } + + /** + * Return a random URL of the wiki. Not necessarily an actual title in the + * database, but at least a URL that looks like one. + */ + private function randomUrl() { + global $wgServer, $wgArticlePath; + return $wgServer . str_replace( '$1', $this->randomTitle(), $wgArticlePath ); } - foreach( $lengths as $length ) { - $urls = randomUrlList( $length ); - $trial = benchSquid( $urls ); - print "$trial\n"; + + /** + * Create a random title string (not necessarily a Title object). + * For use with randomUrl(). + */ + private function randomTitle() { + $str = ''; + $length = mt_rand( 1, 20 ); + for( $i = 0; $i < $length; $i++ ) { + $str .= chr( mt_rand( ord('a'), ord('z') ) ); + } + return ucfirst( $str ); } } + +$maintClass = "BenchmarkPurge"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/changePassword.php b/maintenance/changePassword.php index 0fe8c0bea3..006562a06f 100644 --- a/maintenance/changePassword.php +++ b/maintenance/changePassword.php @@ -10,47 +10,32 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ -$optionsWithArgs = array( 'user', 'password' ); -require_once 'commandLine.inc'; - -$USAGE = - "Usage: php changePassword.php [--user=user --password=password | --help]\n" . - "\toptions:\n" . - "\t\t--help show this message\n" . - "\t\t--user the username to operate on\n" . - "\t\t--password the password to use\n"; - -if( in_array( '--help', $argv ) ) - wfDie( $USAGE ); - -$cp = new ChangePassword( @$options['user'], @$options['password'] ); -$cp->main(); - -/** - * @ingroup Maintenance - */ -class ChangePassword { - var $dbw; - var $user, $password; - - function ChangePassword( $user, $password ) { - global $USAGE; - if( !strlen( $user ) or !strlen( $password ) ) { - wfDie( $USAGE ); +require_once( "Maintenance.php" ); + +class ChangePassword extends Maintenance { + public function __construct() { + parent::__construct(); + $this->addParam( "user", "The username to operate on", true, true ); + $this->addParam( "password", "The password to use", true, true ); + $this->mDescription = "Change a user's password." + } + + public function execute() { + if( !$this->hasOption('user') || !$this->hasOption('password') ) { + $this->error( "Username or password not provided, halting.", true ); } - - $this->user = User::newFromName( $user ); - if ( !$this->user->getId() ) { - die ( "No such user: $user\n" ); + $user = User::newFromName( $this->getOption('user') ); + if( !$user->getId() ) { + $this->error( "No such user: " . $this->getOption('user') . "\n", true ); + } + try { + $user->setPassword( $this->getOption('password') ); + $user->saveSettings(); + } catch( PasswordError $pwe ) { + $this->error( $pwe->getText(), true ); } - - $this->password = $password; - - $this->dbw = wfGetDB( DB_MASTER ); - } - - function main() { - $this->user->setPassword( $this->password ); - $this->user->saveSettings(); } } + +$maintClass = "ChangePassword"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/checkAutoLoader.php b/maintenance/checkAutoLoader.php index 554395ca68..30e9069a32 100644 --- a/maintenance/checkAutoLoader.php +++ b/maintenance/checkAutoLoader.php @@ -1,29 +1,40 @@ mDescription = "AutoLoader sanity checks"; } - foreach ( $classes as $class ) { - if ( !isset( $wgAutoloadLocalClasses[$class] ) ) { - //printf( "%-50s Unlisted, in %s\n", $class, $file ); - echo " '$class' => '$file',\n"; - } elseif ( $wgAutoloadLocalClasses[$class] !== $file ) { - echo "$class: Wrong file: found in $file, listed in " . $wgAutoloadLocalClasses[$class] . "\n"; + public function execute() { + global $wgAutoloadLocalClasses, $IP; + $files = array_unique( $wgAutoloadLocalClasses ); + + foreach( $files as $file ) { + if( function_exists( 'parsekit_compile_file' ) ){ + $parseInfo = parsekit_compile_file( "$IP/$file" ); + $classes = array_keys( $parseInfo['class_table'] ); + } else { + $contents = file_get_contents( "$IP/$file" ); + $m = array(); + preg_match_all( '/\n\s*class\s+([a-zA-Z0-9_]+)/', $contents, $m, PREG_PATTERN_ORDER ); + $classes = $m[1]; + } + foreach ( $classes as $class ) { + if ( !isset( $wgAutoloadLocalClasses[$class] ) ) { + //printf( "%-50s Unlisted, in %s\n", $class, $file ); + $this->output( "\t'$class' => '$file',\n" ); + } elseif ( $wgAutoloadLocalClasses[$class] !== $file ) { + $this->output( "$class: Wrong file: found in $file, listed in " . $wgAutoloadLocalClasses[$class] . "\n" ); + } + } } } - } - +$maintClass = "CheckAutoLoader"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/checkBadRedirects.php b/maintenance/checkBadRedirects.php index 48a4b0e6d1..6a49f25285 100644 --- a/maintenance/checkBadRedirects.php +++ b/maintenance/checkBadRedirects.php @@ -1,30 +1,42 @@ select( - array( 'page' ), - array( 'page_namespace','page_title', 'page_latest' ), - array( 'page_is_redirect' => 1 ) ); - -$count = $result->numRows(); -echo "Found $count total redirects.\n"; -echo "Looking for bad redirects:\n"; -echo "\n"; +class CheckBadRedirects extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Look for bad redirects"; + } -foreach( $result as $row ) { - $title = Title::makeTitle( $row->page_namespace, $row->page_title ); - $rev = Revision::newFromId( $row->page_latest ); - if( $rev ) { - $target = Title::newFromRedirect( $rev->getText() ); - if( !$target ) { - echo $title->getPrefixedText(); - echo "\n"; + public function execute() { + $this->output( "Fetching redirects...\n" ); + $dbr = wfGetDB( DB_SLAVE ); + $result = $dbr->select( + array( 'page' ), + array( 'page_namespace','page_title', 'page_latest' ), + array( 'page_is_redirect' => 1 ) ); + + $count = $result->numRows(); + $this->output( "Found $count total redirects.\n" . + "Looking for bad redirects:\n\n" ); + + foreach( $result as $row ) { + $title = Title::makeTitle( $row->page_namespace, $row->page_title ); + $rev = Revision::newFromId( $row->page_latest ); + if( $rev ) { + $target = Title::newFromRedirect( $rev->getText() ); + if( !$target ) { + $this->output( $title->getPrefixedText() . "\n" ); + } + } } + $this->output( "\ndone.\n" ); } } -echo "\n"; -echo "done.\n"; +$maintClass = "CheckBadRedirects"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/checkImages.php b/maintenance/checkImages.php index 378caa348b..2102bcf60c 100644 --- a/maintenance/checkImages.php +++ b/maintenance/checkImages.php @@ -1,51 +1,63 @@ getLocalRepo(); - -$numImages = 0; -$numGood = 0; - -do { - $res = $dbr->select( 'image', '*', array( 'img_name > ' . $dbr->addQuotes( $start ) ), - 'checkImages.php', array( 'LIMIT' => $batchSize ) ); - foreach ( $res as $row ) { - $numImages++; - $start = $row->img_name; - $file = $localRepo->newFileFromRow( $row ); - $path = $file->getPath(); - if ( !$path ) { - echo "{$row->img_name}: not locally accessible\n"; - continue; - } - $stat = @stat( $file->getPath() ); - if ( !$stat ) { - echo "{$row->img_name}: missing\n"; - continue; - } - - if ( $stat['mode'] & 040000 ) { - echo "{$row->img_name}: is a directory\n"; - continue; - } - - if ( $stat['size'] == 0 && $row->img_size != 0 ) { - echo "{$row->img_name}: truncated, was {$row->img_size}\n"; - continue; - } - - if ( $stat['size'] != $row->img_size ) { - echo "{$row->img_name}: size mismatch DB={$row->img_size}, actual={$stat['size']}\n"; - continue; - } - - $numGood++; + public function __construct() { + parent::__construct(); + $this->mDescription = "Check images to see if they exist, are readable, etc"; } + + public function execute() { + $batchSize = 1000; + $start = ''; + $dbr = wfGetDB( DB_SLAVE ); + + $numImages = 0; + $numGood = 0; + + do { + $res = $dbr->select( 'image', '*', array( 'img_name > ' . $dbr->addQuotes( $start ) ), + __METHOD__, array( 'LIMIT' => $batchSize ) ); + foreach ( $res as $row ) { + $numImages++; + $start = $row->img_name; + $file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row ); + $path = $file->getPath(); + if ( !$path ) { + $this->output( "{$row->img_name}: not locally accessible\n"; + continue; + } + $stat = @stat( $file->getPath() ); + if ( !$stat ) { + $this->output( "{$row->img_name}: missing\n" ); + continue; + } + + if ( $stat['mode'] & 040000 ) { + $this->output( "{$row->img_name}: is a directory\n" ); + continue; + } + + if ( $stat['size'] == 0 && $row->img_size != 0 ) { + $this->output( "{$row->img_name}: truncated, was {$row->img_size}\n" ); + continue; + } + + if ( $stat['size'] != $row->img_size ) { + $this->output( "{$row->img_name}: size mismatch DB={$row->img_size}, actual={$stat['size']}\n" ); + continue; + } + + $numGood++; + } + + } while ( $res->numRows() ); + + $this->output( "Good images: $numGood/$numImages\n" ); + } +} -} while ( $res->numRows() ); - -echo "Good images: $numGood/$numImages\n"; diff --git a/maintenance/checkUsernames.php b/maintenance/checkUsernames.php index 77565b9906..e530a3bcb7 100644 --- a/maintenance/checkUsernames.php +++ b/maintenance/checkUsernames.php @@ -7,36 +7,33 @@ * @ingroup Maintenance */ -error_reporting(E_ALL ^ E_NOTICE); -require_once 'commandLine.inc'; -class checkUsernames { - var $stderr, $log; +require_once( "Maintenance.php" ); - function checkUsernames() { - $this->stderr = fopen( 'php://stderr', 'wt' ); +class CheckUsernames extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = "Verify that database usernames are actually valid"; } - function main() { - $fname = 'checkUsernames::main'; + function execute() { $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'user', array( 'user_id', 'user_name' ), null, - $fname + __METHOD__ ); while ( $row = $dbr->fetchObject( $res ) ) { if ( ! User::isValidUserName( $row->user_name ) ) { - $out = sprintf( "%s: %6d: '%s'\n", wfWikiID(), $row->user_id, $row->user_name ); - fwrite( $this->stderr, $out ); + $this->error( sprintf( "%s: %6d: '%s'\n", wfWikiID(), $row->user_id, $row->user_name ) ); wfDebugLog( 'checkUsernames', $out ); } } } } -$cun = new checkUsernames(); -$cun->main(); - +$maintClass = "CheckUsernames"; +require_once( "doMaintenance.php" ); diff --git a/maintenance/clear_interwiki_cache.php b/maintenance/clear_interwiki_cache.php index ce154779c1..88d08ba858 100644 --- a/maintenance/clear_interwiki_cache.php +++ b/maintenance/clear_interwiki_cache.php @@ -3,25 +3,36 @@ * This script is used to clear the interwiki links for ALL languages in * memcached. * - * @file * @ingroup Maintenance */ -/** */ -require_once('commandLine.inc'); +require_once( "Maintenance.php" ); -$dbr = wfGetDB( DB_SLAVE ); -$res = $dbr->select( 'interwiki', array( 'iw_prefix' ), false ); -$prefixes = array(); -while ( $row = $dbr->fetchObject( $res ) ) { - $prefixes[] = $row->iw_prefix; -} +class ClearInterwikiCache extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = "Clear all interwiki links for all languages from the cache"; + } + + public function execute() { + global $wgLocalDatabases; + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'interwiki', array( 'iw_prefix' ), false ); + $prefixes = array(); + while ( $row = $dbr->fetchObject( $res ) ) { + $prefixes[] = $row->iw_prefix; + } -foreach ( $wgLocalDatabases as $db ) { - print "$db "; - foreach ( $prefixes as $prefix ) { - $wgMemc->delete("$db:interwiki:$prefix"); + foreach ( $wgLocalDatabases as $db ) { + $this->output( "$db..." ); + foreach ( $prefixes as $prefix ) { + $wgMemc->delete("$db:interwiki:$prefix"); + } + $this->output( "done\n" ); + } } } -print "\n"; +$maintClass = "ClearInterwikiCache"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/clear_stats.php b/maintenance/clear_stats.php index 4cacd74c3d..f2a128f4ca 100644 --- a/maintenance/clear_stats.php +++ b/maintenance/clear_stats.php @@ -6,33 +6,34 @@ * @ingroup Maintenance */ -require_once('commandLine.inc'); +require_once( 'Maintenance.php' ); -foreach ( $wgLocalDatabases as $db ) { - noisyDelete("$db:stats:request_with_session"); - noisyDelete("$db:stats:request_without_session"); - noisyDelete("$db:stats:pcache_hit"); - noisyDelete("$db:stats:pcache_miss_invalid"); - noisyDelete("$db:stats:pcache_miss_expired"); - noisyDelete("$db:stats:pcache_miss_absent"); - noisyDelete("$db:stats:pcache_miss_stub"); - noisyDelete("$db:stats:image_cache_hit"); - noisyDelete("$db:stats:image_cache_miss"); - noisyDelete("$db:stats:image_cache_update"); - noisyDelete("$db:stats:diff_cache_hit"); - noisyDelete("$db:stats:diff_cache_miss"); - noisyDelete("$db:stats:diff_uncacheable"); -} +class clear_stats extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = "Remove all statistics tracking from memcached"; + } -function noisyDelete( $key ) { - global $wgMemc; - /* - print "$key "; - if ( $wgMemc->delete($key) ) { - print "deleted\n"; - } else { - print "FAILED\n"; - }*/ - $wgMemc->delete($key); + public function execute() { + global $wgLocalDatabases, $wgMemc; + foreach ( $wgLocalDatabases as $db ) { + $wgMemc->delete("$db:stats:request_with_session"); + $wgMemc->delete("$db:stats:request_without_session"); + $wgMemc->delete("$db:stats:pcache_hit"); + $wgMemc->delete("$db:stats:pcache_miss_invalid"); + $wgMemc->delete("$db:stats:pcache_miss_expired"); + $wgMemc->delete("$db:stats:pcache_miss_absent"); + $wgMemc->delete("$db:stats:pcache_miss_stub"); + $wgMemc->delete("$db:stats:image_cache_hit"); + $wgMemc->delete("$db:stats:image_cache_miss"); + $wgMemc->delete("$db:stats:image_cache_update"); + $wgMemc->delete("$db:stats:diff_cache_hit"); + $wgMemc->delete("$db:stats:diff_cache_miss"); + $wgMemc->delete("$db:stats:diff_uncacheable"); + } + } } +$maintClass = "clear_stats"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/createAndPromote.php b/maintenance/createAndPromote.php index a5a8f88d34..a424336ebd 100644 --- a/maintenance/createAndPromote.php +++ b/maintenance/createAndPromote.php @@ -8,61 +8,53 @@ * @author Rob Church */ -$options = array( 'help', 'bureaucrat' ); -require_once( 'commandLine.inc' ); - -if( isset( $options['help'] ) ) { - showHelp(); - exit( 1 ); -} - -if( count( $args ) < 2 ) { - echo( "Please provide a username and password for the new account.\n" ); - die( 1 ); -} - -$username = $args[0]; -$password = $args[1]; - -echo( wfWikiID() . ": Creating and promoting User:{$username}..." ); - -# Validate username and check it doesn't exist -$user = User::newFromName( $username ); -if( !is_object( $user ) ) { - echo( "invalid username.\n" ); - die( 1 ); -} elseif( 0 != $user->idForName() ) { - echo( "account exists.\n" ); - die( 1 ); +require_once( "Maintenance.php" ); + +class CreateAndPromote extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = "Create a new user account with administrator rights"; + $this->addParam( "bureaucrat", "Grant the account bureaucrat rights" ); + $this->addArgs( array( "username", "password" ) ); + } + + public function execute() { + $username = $this->getArg(0); + $password = $this->getArg(1); + + $this->output( wfWikiID() . ": Creating and promoting User:{$username}..." ); + + $user = User::newFromName( $username ); + if( !is_object( $user ) ) { + $this->error( "invalid username.\n", true ); + } elseif( 0 != $user->idForName() ) { + $this->error( "account exists.\n", true ); + } + + # Try to set the password + try { + $user->setPassword( $password ); + } catch( PasswordError $pwe ) { + $this->error( $pwe->getText(), true ); + } + + # Insert the account into the database + $user->addToDatabase(); + $user->saveSettings(); + + # Promote user + $user->addGroup( 'sysop' ); + if( $this->hasOption( 'bureaucrat' ) ) + $user->addGroup( 'bureaucrat' ); + + # Increment site_stats.ss_users + $ssu = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); + $ssu->doUpdate(); + + $this->output( "done.\n" ); + } } -# Insert the account into the database -$user->addToDatabase(); -$user->setPassword( $password ); -$user->saveSettings(); - -# Promote user -$user->addGroup( 'sysop' ); -if( isset( $option['bureaucrat'] ) ) - $user->addGroup( 'bureaucrat' ); - -# Increment site_stats.ss_users -$ssu = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); -$ssu->doUpdate(); - -echo( "done.\n" ); - -function showHelp() { - echo( << - - --bureaucrat - Grant the account bureaucrat rights - --help - Show this help information - -EOT - ); -} \ No newline at end of file +$maintClass = "CreateAndPromote"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/deleteBatch.php b/maintenance/deleteBatch.php index 5aeea7816b..6e44d63f47 100644 --- a/maintenance/deleteBatch.php +++ b/maintenance/deleteBatch.php @@ -1,5 +1,4 @@ ] [-r ] [-i ] [listfile] @@ -13,86 +12,87 @@ * @file * @ingroup Maintenance */ - -$oldCwd = getcwd(); -$optionsWithArgs = array( 'u', 'r', 'i' ); -require_once( 'commandLine.inc' ); - -chdir( $oldCwd ); - -# Options processing - -$filename = 'php://stdin'; -$user = 'Delete page script'; -$reason = ''; -$interval = 0; - -if ( isset( $args[0] ) ) { - $filename = $args[0]; -} -if ( isset( $options['u'] ) ) { - $user = $options['u']; -} -if ( isset( $options['r'] ) ) { - $reason = $options['r']; -} -if ( isset( $options['i'] ) ) { - $interval = $options['i']; -} - -$wgUser = User::newFromName( $user ); - - -# Setup complete, now start - -$file = fopen( $filename, 'r' ); -if ( !$file ) { - print "Unable to read file, exiting\n"; - exit; -} - -$dbw = wfGetDB( DB_MASTER ); - -for ( $linenum = 1; !feof( $file ); $linenum++ ) { - $line = trim( fgets( $file ) ); - if ( $line == '' ) { - continue; - } - $page = Title::newFromText( $line ); - if ( is_null( $page ) ) { - print "Invalid title '$line' on line $linenum\n"; - continue; + +require_once( "Maintenance.php" ); + +class DeleteBatch extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = "Deletes a batch of pages"; + $this->addParam( 'u', "User to perform deletion", false, true ); + $this->addParam( 'r', "Reason to delete page", false, true ); + $this->addParam( 'i', "Interval to sleep between deletions" ); + $this->addArgs( array( 'listfile' ) ); } - if( !$page->exists() ) { - print "Skipping nonexistent page '$line'\n"; - continue; - } - - - print $page->getPrefixedText(); - $dbw->begin(); - if( $page->getNamespace() == NS_FILE ) { - $art = new ImagePage( $page ); - $img = wfFindFile( $art->mTitle ); - if( !$img || !$img->delete( $reason ) ) { - print "FAILED to delete image file... "; + + public function execute() { + global $wgUser; + + # Change to current working directory + $oldCwd = getcwd(); + chdir( $oldCwd ); + + # Options processing + $user = $this->getOption( 'u', 'Delete page script' ); + $reason = $this->getOption( 'r', '' ); + $interval = $this->getOption( 'i', 0 ); + if( $this->hasArg() ) { + $file = fopen( $this->getArg(), 'r' ); + } else { + $file = $this->getStdin(); } - } else { - $art = new Article( $page ); - } - $success = $art->doDeleteArticle( $reason ); - $dbw->immediateCommit(); - if ( $success ) { - print "\n"; - } else { - print " FAILED to delete image page\n"; - } - if ( $interval ) { - sleep( $interval ); + # Setup + if( !$file ) { + $this->error( "Unable to read file, exiting\n", true ); + } + $wgUser = User::newFromName( $user ); + $dbw = wfGetDB( DB_MASTER ); + + # Handle each entry + for ( $linenum = 1; !feof( $file ); $linenum++ ) { + $line = trim( fgets( $file ) ); + if ( $line == '' ) { + continue; + } + $page = Title::newFromText( $line ); + if ( is_null( $page ) ) { + $this->output( "Invalid title '$line' on line $linenum\n" ); + continue; + } + if( !$page->exists() ) { + $this->output( "Skipping nonexistent page '$line'\n" ); + continue; + } + + + $this->output( $page->getPrefixedText() ); + $dbw->begin(); + if( $page->getNamespace() == NS_FILE ) { + $art = new ImagePage( $page ); + $img = wfFindFile( $art->mTitle ); + if( !$img || !$img->delete( $reason ) ) { + $this->output( "FAILED to delete image file... " ); + } + } else { + $art = new Article( $page ); + } + $success = $art->doDeleteArticle( $reason ); + $dbw->immediateCommit(); + if ( $success ) { + $this->output( "\n" ); + } else { + $this->output( " FAILED to delete article\n" ); + } + + if ( $interval ) { + sleep( $interval ); + } + wfWaitForSlaves( 5 ); +} } - wfWaitForSlaves( 5 ); } - - +$maintClass = "DeleteBatch"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/deleteDefaultMessages.php b/maintenance/deleteDefaultMessages.php index 77e8574194..868fe7d626 100644 --- a/maintenance/deleteDefaultMessages.php +++ b/maintenance/deleteDefaultMessages.php @@ -1,48 +1,53 @@ mDescription = "Deletes all pages in the MediaWiki namespace" . + " which were last edited by \"MediaWiki default\""; + } -function deleteDefaultMessages() { - $user = 'MediaWiki default'; - $reason = 'No longer required'; + public function execute() { + $user = 'MediaWiki default'; + $reason = 'No longer required'; - global $wgUser; - $wgUser = User::newFromName( $user ); - $wgUser->addGroup( 'bot' ); - - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( array( 'page', 'revision' ), - array( 'page_namespace', 'page_title' ), - array( - 'page_namespace' => NS_MEDIAWIKI, - 'page_latest=rev_id', - 'rev_user_text' => 'MediaWiki default', - ) - ); + global $wgUser; + $wgUser = User::newFromName( $user ); + $wgUser->addGroup( 'bot' ); - $dbw = wfGetDB( DB_MASTER ); + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( array( 'page', 'revision' ), + array( 'page_namespace', 'page_title' ), + array( + 'page_namespace' => NS_MEDIAWIKI, + 'page_latest=rev_id', + 'rev_user_text' => 'MediaWiki default', + ) + ); - while ( $row = $dbr->fetchObject( $res ) ) { - if ( function_exists( 'wfWaitForSlaves' ) ) { - wfWaitForSlaves( 5 ); + $dbw = wfGetDB( DB_MASTER ); + + while ( $row = $dbr->fetchObject( $res ) ) { + if ( function_exists( 'wfWaitForSlaves' ) ) { + wfWaitForSlaves( 5 ); + } + $dbw->ping(); + $title = Title::makeTitle( $row->page_namespace, $row->page_title ); + $article = new Article( $title ); + $dbw->begin(); + $article->doDeleteArticle( $reason ); + $dbw->commit(); } - $dbw->ping(); - $title = Title::makeTitle( $row->page_namespace, $row->page_title ); - $article = new Article( $title ); - $dbw->begin(); - $article->doDeleteArticle( $reason ); - $dbw->commit(); } } +$maintClass = "DeleteDefaultMessages"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/deleteImageMemcached.php b/maintenance/deleteImageMemcached.php index 2c3afa86ef..caa48a186a 100644 --- a/maintenance/deleteImageMemcached.php +++ b/maintenance/deleteImageMemcached.php @@ -3,31 +3,27 @@ * This script delete image information from memcached. * * Usage example: - * php deleteImageMemcached.php --until "2005-09-05 00:00:00" --sleep 0 --report 10 + * php deleteImageMemcached.php --until "2005-09-05 00:00:00" --sleep 0 * * @file * @ingroup Maintenance */ -$optionsWithArgs = array( 'until', 'sleep', 'report' ); +require_once( "Maintenance.php" ); -require_once 'commandLine.inc'; - -/** - * @ingroup Maintenance - */ -class DeleteImageCache { - var $until, $sleep, $report; - - function DeleteImageCache( $until, $sleep, $report ) { - $this->until = $until; - $this->sleep = $sleep; - $this->report = $report; +class DeleteImageCache extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Delete image information from memcached"; + $this->addParam( 'sleep', 'How many seconds to sleep between deletions', true, true ); + $this->addParam( 'until', 'Timestamp to delete all entries prior to', true, true ); } - function main() { + public function execute() { global $wgMemc; - $fname = 'DeleteImageCache::main'; + + $until = preg_replace( "/[^\d]/", '', $this->getOption('until') ); + $sleep = (int)$this->getOption('sleep') * 1000; // milliseconds ini_set( 'display_errors', false ); @@ -35,8 +31,8 @@ class DeleteImageCache { $res = $dbr->select( 'image', array( 'img_name' ), - array( "img_timestamp < {$this->until}" ), - $fname + array( "img_timestamp < {$until}" ), + __METHOD__ ); $i = 0; @@ -44,29 +40,22 @@ class DeleteImageCache { while ( $row = $dbr->fetchObject( $res ) ) { if ($i % $this->report == 0) - printf("%s: %13s done (%s)\n", wfWikiID(), "$i/$total", wfPercent( $i / $total * 100 )); + $this->output( sprintf("%s: %13s done (%s)\n", wfWikiID(), "$i/$total", wfPercent( $i / $total * 100 ) ) ); $md5 = md5( $row->img_name ); $wgMemc->delete( wfMemcKey( 'Image', $md5 ) ); - if ($this->sleep != 0) - usleep( $this->sleep ); + if ($sleep != 0) + usleep( $sleep ); ++$i; } } - function getImageCount() { - $fname = 'DeleteImageCache::getImageCount'; - + private function getImageCount() { $dbr = wfGetDB( DB_SLAVE ); - return $dbr->selectField( 'image', 'COUNT(*)', array(), $fname ); + return $dbr->selectField( 'image', 'COUNT(*)', array(), __METHOD__ ); } } -$until = preg_replace( "/[^\d]/", '', $options['until'] ); -$sleep = (int)$options['sleep'] * 1000; // milliseconds -$report = (int)$options['report']; - -$dic = new DeleteImageCache( $until, $sleep, $report ); -$dic->main(); - +$maintClass = "DeleteImageCache"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/deleteRevision.php b/maintenance/deleteRevision.php index 0c203ab050..6623de1a3d 100644 --- a/maintenance/deleteRevision.php +++ b/maintenance/deleteRevision.php @@ -6,42 +6,52 @@ * @ingroup Maintenance */ -require_once( 'commandLine.inc' ); +require_once( "Maintenance.php" ); -$dbw = wfGetDB( DB_MASTER ); - -if ( count( $args ) == 0 ) { - echo "Usage: php deleteRevision.php [ ...]\n"; - exit(1); -} - -echo "Deleting revision(s) " . implode( ',', $args ) . " from ".wfWikiID()."...\n"; +class DeleteRevision extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = "Delete one or more revisions by moving them to the archive table"; + } + + public function execute() { + if( count( $this->mArgs ) == 0 ) { + $this->error( "No revisions specified", true ); + } -$affected = 0; -foreach ( $args as $revID ) { - $dbw->insertSelect( 'archive', array( 'page', 'revision' ), - array( - 'ar_namespace' => 'page_namespace', - 'ar_title' => 'page_title', - 'ar_comment' => 'rev_comment', - 'ar_user' => 'rev_user', - 'ar_user_text' => 'rev_user_text', - 'ar_timestamp' => 'rev_timestamp', - 'ar_minor_edit' => 'rev_minor_edit', - 'ar_rev_id' => 'rev_id', - 'ar_text_id' => 'rev_text_id', - ), array( - 'rev_id' => $revID, - 'page_id = rev_page' - ), $fname - ); - if ( !$dbw->affectedRows() ) { - echo "Revision $revID not found\n"; - } else { - $affected += $dbw->affectedRows(); - $dbw->delete( 'revision', array( 'rev_id' => $revID ) ); + $this->output( "Deleting revision(s) " . implode( ',', $this->mArgs ) . + " from " . wfWikiID() . "...\n" ); + $dbw = wfGetDB( DB_MASTER ); + + $affected = 0; + foreach ( $this->mArgs as $revID ) { + $dbw->insertSelect( 'archive', array( 'page', 'revision' ), + array( + 'ar_namespace' => 'page_namespace', + 'ar_title' => 'page_title', + 'ar_comment' => 'rev_comment', + 'ar_user' => 'rev_user', + 'ar_user_text' => 'rev_user_text', + 'ar_timestamp' => 'rev_timestamp', + 'ar_minor_edit' => 'rev_minor_edit', + 'ar_rev_id' => 'rev_id', + 'ar_text_id' => 'rev_text_id', + ), array( + 'rev_id' => $revID, + 'page_id = rev_page' + ), __METHOD__ + ); + if ( !$dbw->affectedRows() ) { + $this->output( "Revision $revID not found\n" ); + } else { + $affected += $dbw->affectedRows(); + $dbw->delete( 'revision', array( 'rev_id' => $revID ) ); + } + } + $this->output( "Deleted $affected revisions\n" ); } } -print "Deleted $affected revisions\n"; - +$maintClass = "DeleteRevision"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/doMaintenance.php b/maintenance/doMaintenance.php new file mode 100644 index 0000000000..dbb03529bb --- /dev/null +++ b/maintenance/doMaintenance.php @@ -0,0 +1,60 @@ +setup(); + +# Setup the profiler +if ( file_exists( "$IP/StartProfiler.php" ) ) { + require_once( "$IP/StartProfiler.php" ); +} else { + require_once( "$IP/includes/ProfilerStub.php" ); +} + +// Load settings, using wikimedia-mode if needed +if( file_exists( dirname(__FILE__).'/wikimedia-mode' ) ) { + # TODO FIXME! Wikimedia-specific stuff needs to go away to an ext + # Maybe a hook? + global $cluster; + $wgWikiFarm = true; + $cluster = 'pmtma'; + require_once( "$IP/includes/AutoLoader.php" ); + require_once( "$IP/includes/SiteConfiguration.php" ); + require( "$IP/wgConf.php" ); + $maintenance->loadWikimediaSettings(); + require( $IP.'/includes/Defines.php' ); + require( $IP.'/CommonSettings.php' ); +} else { + require_once( "$IP/includes/AutoLoader.php" ); + require_once( "$IP/includes/Defines.php" ); + require_once( $maintenance->loadSettings() ); +} +// Some last includes +require_once( "$IP/includes/Setup.php" ); +require_once( "$IP/install-utils.inc" ); + +$wgTitle = null; # Much much faster startup than creating a title object + +try { + $maintenance->execute(); +} catch( MWException $mwe ) { + echo( $mwe->getText() ); +} \ No newline at end of file diff --git a/maintenance/eval.php b/maintenance/eval.php index a990a4d845..7b90012d2e 100644 --- a/maintenance/eval.php +++ b/maintenance/eval.php @@ -16,57 +16,66 @@ * @ingroup Maintenance */ -$wgUseNormalUser = (bool)getenv('MW_WIKIUSER'); +require_once( "Maintenance.php" ); -$optionsWithArgs = array( 'd' ); +class EvalPrompt extends Maintenance { -/** */ -require_once( "commandLine.inc" ); - -if ( isset( $options['d'] ) ) { - $d = $options['d']; - if ( $d > 0 ) { - $wgDebugLogFile = '/dev/stdout'; + public function __construct() { + parent::__construct(); + $this->mDescription = "This script lets a command-line user start up the wiki engine and then poke\n" . + "about by issuing PHP commands directly."; + $this->addParam( 'd', "Enable MediaWiki debug output", false, true ); } - if ( $d > 1 ) { - $lb = wfGetLB(); - foreach ( $lb->mServers as $i => $server ) { - $lb->mServers[$i]['flags'] |= DBO_DEBUG; + + public function execute() { + global $wgUseNormalUser; + $wgUseNormalUser = (bool)getenv('MW_WIKIUSER'); + if ( $this->hasOption('d') ) { + $d = $this->getOption('d'); + if ( $d > 0 ) { + $wgDebugLogFile = '/dev/stdout'; + } + if ( $d > 1 ) { + $lb = wfGetLB(); + foreach ( $lb->mServers as $i => $server ) { + $lb->mServers[$i]['flags'] |= DBO_DEBUG; + } + } + if ( $d > 2 ) { + $wgDebugFunctionEntry = true; + } } - } - if ( $d > 2 ) { - $wgDebugFunctionEntry = true; - } -} - -if ( function_exists( 'readline_add_history' ) - && function_exists( 'posix_isatty' ) && posix_isatty( 0 /*STDIN*/ ) ) -{ - $useReadline = true; -} else { - $useReadline = false; -} - -if ( $useReadline ) { - $historyFile = "{$_ENV['HOME']}/.mweval_history"; - readline_read_history( $historyFile ); -} - -while ( ( $line = readconsole( '> ' ) ) !== false ) { - if ( $useReadline ) { - readline_add_history( $line ); - readline_write_history( $historyFile ); - } - $val = eval( $line . ";" ); - if( is_null( $val ) ) { - echo "\n"; - } elseif( is_string( $val ) || is_numeric( $val ) ) { - echo "$val\n"; - } else { - var_dump( $val ); + + if ( function_exists( 'readline_add_history' ) + && function_exists( 'posix_isatty' ) && posix_isatty( 0 /*STDIN*/ ) ) + { + $useReadline = true; + } else { + $useReadline = false; + } + + if ( $useReadline ) { + $historyFile = "{$_ENV['HOME']}/.mweval_history"; + readline_read_history( $historyFile ); + } + + while ( ( $line = readconsole( '> ' ) ) !== false ) { + if ( $useReadline ) { + readline_add_history( $line ); + readline_write_history( $historyFile ); + } + $val = eval( $line . ";" ); + if( is_null( $val ) ) { + echo "\n"; + } elseif( is_string( $val ) || is_numeric( $val ) ) { + echo "$val\n"; + } else { + var_dump( $val ); + } + } + print "\n"; } } -print "\n"; - - +$maintClass = "EvalPrompt"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/fetchText.php b/maintenance/fetchText.php index 91b78be3fe..f300318e37 100644 --- a/maintenance/fetchText.php +++ b/maintenance/fetchText.php @@ -6,34 +6,48 @@ * @ingroup Maintenance */ -require "commandLine.inc"; +require_once( "Maintenance.php" ); -$db = wfGetDB( DB_SLAVE ); -$stdin = fopen( "php://stdin", "rt" ); -while( !feof( $stdin ) ) { - $line = fgets( $stdin ); - if( $line === false ) { - // We appear to have lost contact... - break; +class FetchText extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Fetch the revision text from an old_id"; } - $textId = intval( $line ); - $text = doGetText( $db, $textId ); - echo strlen( $text ) . "\n"; - echo $text; -} -/** - * May throw a database error if, say, the server dies during query. - */ -function doGetText( $db, $id ) { - $id = intval( $id ); - $row = $db->selectRow( 'text', - array( 'old_text', 'old_flags' ), - array( 'old_id' => $id ), - 'TextPassDumper::getText' ); - $text = Revision::getRevisionText( $row ); - if( $text === false ) { - return false; + public function execute() { + $db = wfGetDB( DB_SLAVE ); + $stdin = $this->getStdin(); + while( !feof( $stdin ) ) { + $line = fgets( $stdin ); + if( $line === false ) { + // We appear to have lost contact... + break; + } + $textId = intval( $line ); + $text = $this->doGetText( $db, $textId ); + $this->output( strlen( $text ) . "\n". $text ); + } + } + + /** + * May throw a database error if, say, the server dies during query. + * @param $db Database object + * @param $id int The old_id + * @return String + */ + private function doGetText( $db, $id ) { + $id = intval( $id ); + $row = $db->selectRow( 'text', + array( 'old_text', 'old_flags' ), + array( 'old_id' => $id ), + 'TextPassDumper::getText' ); + $text = Revision::getRevisionText( $row ); + if( $text === false ) { + return false; + } + return $text; } - return $text; } + +$maintClass = "FetchText"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/getLagTimes.php b/maintenance/getLagTimes.php index 0f750caf59..49197e6fb7 100644 --- a/maintenance/getLagTimes.php +++ b/maintenance/getLagTimes.php @@ -4,26 +4,37 @@ * @ingroup Maintenance */ -require 'commandLine.inc'; +require_once( "Maintenance.php" ); -$lb = wfGetLB(); +class GetLagTimes extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Dump replication lag times"; + } + + public function execute() { + $lb = wfGetLB(); -if( $lb->getServerCount() == 1 ) { - echo "This script dumps replication lag times, but you don't seem to have\n"; - echo "a multi-host db server configuration.\n"; -} else { - $lags = $lb->getLagTimes(); - foreach( $lags as $n => $lag ) { - $host = $lb->getServerName( $n ); - if( IP::isValid( $host ) ) { - $ip = $host; - $host = gethostbyaddr( $host ); + if( $lb->getServerCount() == 1 ) { + $this->error( "This script dumps replication lag times, but you don't seem to have\n" + . "a multi-host db server configuration.\n" ); } else { - $ip = gethostbyname( $host ); + $lags = $lb->getLagTimes(); + foreach( $lags as $n => $lag ) { + $host = $lb->getServerName( $n ); + if( IP::isValid( $host ) ) { + $ip = $host; + $host = gethostbyaddr( $host ); + } else { + $ip = gethostbyname( $host ); + } + $starLen = min( intval( $lag ), 40 ); + $stars = str_repeat( '*', $starLen ); + $this->output( sprintf( "%10s %20s %3d %s\n", $ip, $host, $lag, $stars ) ); + } } - $starLen = min( intval( $lag ), 40 ); - $stars = str_repeat( '*', $starLen ); - printf( "%10s %20s %3d %s\n", $ip, $host, $lag, $stars ); } } +$maintClass = "GetLagTimes"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/getSlaveServer.php b/maintenance/getSlaveServer.php index 25258267e2..72bc1387a3 100644 --- a/maintenance/getSlaveServer.php +++ b/maintenance/getSlaveServer.php @@ -5,24 +5,32 @@ * @file * @ingroup Maintenance */ + +require_once( "Maintenance.php" ); -require_once( dirname(__FILE__).'/commandLine.inc' ); - -if ( $wgAllDBsAreLocalhost ) { - # Can't fool the backup script - print "localhost\n"; - exit; -} - -if( isset( $options['group'] ) ) { - $db = wfGetDB( DB_SLAVE, $options['group'] ); - $host = $db->getServer(); -} else { - $lb = wfGetLB(); - $i = $lb->getReaderIndex(); - $host = $lb->getServerName( $i ); +class GetSlaveServer extends Maintenance { + public function __construct() { + parent::__construct(); + $this->addParam( "group", "Query group to check specifically" ); + $this->mDescription = "Report the hostname of a slave server"; + } + public function execute() { + global $wgAllDBsAreLocalhost; + if( $wgAllDBsAreLocalhost ) { + $host = 'localhost'; + } else { + if( $this->hasOption('group') ) { + $db = wfGetDB( DB_SLAVE, $this->getOption('group') ); + $host = $db->getServer(); + } else { + $lb = wfGetLB(); + $i = $lb->getReaderIndex(); + $host = $lb->getServerName( $i ); + } + } + $this->output( "$host\n" ); + } } -print "$host\n"; - - +$maintClass = "GetSlaveServer"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/initStats.php b/maintenance/initStats.php index bb0cf304f5..2365718cc6 100644 --- a/maintenance/initStats.php +++ b/maintenance/initStats.php @@ -9,23 +9,22 @@ * @author Rob Church * @licence GNU General Public Licence 2.0 or later */ - -$options = array( 'help', 'update', 'noviews' ); -require_once( 'commandLine.inc' ); -echo( "Refresh Site Statistics\n\n" ); -if( isset( $options['help'] ) ) { - showHelp(); - exit(1); -} +require_once( "Maintenance.php" ); -require "$IP/maintenance/initStats.inc"; -wfInitStats( $options ); +class InitStats extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Re-initialise the site statistics tables"; + $this->addParam( 'update', 'Update the existing statistics (preserves the ss_total_views field)' ); + $this->addParam( 'noviews', "Don't update the page view counter" ); + } -function showHelp() { - echo( "Re-initialise the site statistics tables.\n\n" ); - echo( "Usage: php initStats.php [--update|--noviews]\n\n" ); - echo( " --update : Update the existing statistics (preserves the ss_total_views field)\n" ); - echo( "--noviews : Don't update the page view counter\n\n" ); + public function execute() { + $this->output( "Refresh Site Statistics\n\n" ); + SiteStats::init( $this->hasOption('update'), $this->hasOption('noviews') ); + } } +$maintClass = "InitStats"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/mctest.php b/maintenance/mctest.php index 64fea06f31..7cd5172db4 100644 --- a/maintenance/mctest.php +++ b/maintenance/mctest.php @@ -8,58 +8,63 @@ * @ingroup Maintenance */ -$optionsWithArgs = array( 'i' ); +require_once( "Maintenance.php" ); -require_once('commandLine.inc'); - -function microtime_float() -{ - list($usec, $sec) = explode(" ", microtime()); - return ((float)$usec + (float)$sec); -} +class mcTest extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Makes several 'set', 'incr' and 'get' requests on every" + . " memcached server and shows a report"; + $this->addParam( 'i', 'Number of iterations', false, true ); + $this->addArgs( array( 'server' ) ); + } + public function execute() { + global $wgMemCachedServers; -#$wgDebugLogFile = '/dev/stdout'; + $iterations = $this->getOption( 'i', 100 ); + if( $this->hasArg() ) + $wgMemCachedServers = array( $this->getArg() ); -if ( isset( $args[0] ) ) { - $wgMemCachedServers = array( $args[0] ); -} -if ( isset( $options['i'] ) ) { - $iterations = $options['i']; -} else { - $iterations = 100; -} - -foreach ( $wgMemCachedServers as $server ) { - print "$server "; - $mcc = new MemCachedClientforWiki( array('persistant' => true) ); - $mcc->set_servers( array( $server ) ); - $set = 0; - $incr = 0; - $get = 0; - $time_start=microtime_float(); - for ( $i=1; $i<=$iterations; $i++ ) { - if ( !is_null( $mcc->set( "test$i", $i ) ) ) { - $set++; + foreach ( $wgMemCachedServers as $server ) { + $this->output( $server . " " ); + $mcc = new MemCachedClientforWiki( array('persistant' => true) ); + $mcc->set_servers( array( $server ) ); + $set = 0; + $incr = 0; + $get = 0; + $time_start = $this->microtime_float(); + for ( $i=1; $i<=$iterations; $i++ ) { + if ( !is_null( $mcc->set( "test$i", $i ) ) ) { + $set++; + } + } + for ( $i=1; $i<=$iterations; $i++ ) { + if ( !is_null( $mcc->incr( "test$i", $i ) ) ) { + $incr++; + } + } + for ( $i=1; $i<=$iterations; $i++ ) { + $value = $mcc->get( "test$i" ); + if ( $value == $i*2 ) { + $get++; + } + } + $exectime = $this->microtime_float() - $time_start; + + $this->output( "set: $set incr: $incr get: $get time: $exectime\n" ); } } - for ( $i=1; $i<=$iterations; $i++ ) { - if ( !is_null( $mcc->incr( "test$i", $i ) ) ) { - $incr++; - } + /** + * Return microtime() as a float + * @return float + */ + private function microtime_float() { + list($usec, $sec) = explode(" ", microtime()); + return ((float)$usec + (float)$sec); } - - for ( $i=1; $i<=$iterations; $i++ ) { - $value = $mcc->get( "test$i" ); - if ( $value == $i*2 ) { - $get++; - } - } - $exectime=microtime_float()-$time_start; - - print "set: $set incr: $incr get: $get time: $exectime\n"; } - - +$maintClass = "mcTest"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/moveBatch.php b/maintenance/moveBatch.php index 427e5d091d..13dd40a459 100644 --- a/maintenance/moveBatch.php +++ b/maintenance/moveBatch.php @@ -1,9 +1,7 @@ mDescription = "Moves a batch of pages"; + $this->addParam( 'u', "User to perform move", false, true ); + $this->addParam( 'r', "Reason to move page", false, true ); + $this->addParam( 'i', "Interval to sleep between moves" ); + $this->addArgs( array( 'listfile' ) ); } - $parts = array_map( 'trim', explode( '|', $line ) ); - if ( count( $parts ) != 2 ) { - print "Error on line $linenum, no pipe character\n"; - continue; + + public function execute() { + global $wgUser; + + # Change to current working directory + $oldCwd = getcwd(); + chdir( $oldCwd ); + + # Options processing + $user = $this->getOption( 'u', 'Move page script' ); + $reason = $this->getOption( 'r', '' ); + $interval = $this->getOption( 'i', 0 ); + if( $this->hasArg() ) { + $file = fopen( $this->getArg(), 'r' ); + } else { + $file = $this->getStdin(); + } + + # Setup + if( !$file ) { + $this->error( "Unable to read file, exiting\n", true ); + } + $wgUser = User::newFromName( $user ); + + # Setup complete, now start + $dbw = wfGetDB( DB_MASTER ); + for ( $linenum = 1; !feof( $file ); $linenum++ ) { + $line = fgets( $file ); + if ( $line === false ) { + break; + } + $parts = array_map( 'trim', explode( '|', $line ) ); + if ( count( $parts ) != 2 ) { + $this->error( "Error on line $linenum, no pipe character\n" ); + continue; + } + $source = Title::newFromText( $parts[0] ); + $dest = Title::newFromText( $parts[1] ); + if ( is_null( $source ) || is_null( $dest ) ) { + $this->error( "Invalid title on line $linenum\n" ); + continue; + } + + + $this->output( $source->getPrefixedText() . ' --> ' . $dest->getPrefixedText() ); + $dbw->begin(); + $err = $source->moveTo( $dest, false, $reason ); + if( $err !== true ) { + $this->output( "\nFAILED: $err" ); + } + $dbw->immediateCommit(); + $this->output( "\n" ); + + if ( $interval ) { + sleep( $interval ); + } + wfWaitForSlaves( 5 ); + } } - $source = Title::newFromText( $parts[0] ); - $dest = Title::newFromText( $parts[1] ); - if ( is_null( $source ) || is_null( $dest ) ) { - print "Invalid title on line $linenum\n"; - continue; - } - - - print $source->getPrefixedText() . ' --> ' . $dest->getPrefixedText(); - $dbw->begin(); - $err = $source->moveTo( $dest, false, $reason ); - if( $err !== true ) { - print "\nFAILED: $err"; - } - $dbw->immediateCommit(); - print "\n"; - - if ( $interval ) { - sleep( $interval ); - } - wfWaitForSlaves( 5 ); } - - +$maintClass = "MoveBatch"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/nextJobDB.php b/maintenance/nextJobDB.php index 6af5cbecec..fde6b49ec2 100644 --- a/maintenance/nextJobDB.php +++ b/maintenance/nextJobDB.php @@ -6,55 +6,71 @@ * @ingroup Maintenance */ -$options = array( 'type' ); +require_once( "Maintenance.php" ); -require_once( 'commandLine.inc' ); - -$type = isset($options['type']) - ? $options['type'] - : false; - -$mckey = $type === false - ? "jobqueue:dbs" - : "jobqueue:dbs:$type"; - -$pendingDBs = $wgMemc->get( $mckey ); -if ( !$pendingDBs ) { - $pendingDBs = array(); - # Cross-reference DBs by master DB server - $dbsByMaster = array(); - foreach ( $wgLocalDatabases as $db ) { - $lb = wfGetLB( $db ); - $dbsByMaster[$lb->getServerName(0)][] = $db; +class nextJobDB extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Pick a database that has pending jobs"; + $this->addParam( 'type', "The type of job to search for", false, true ); } - - foreach ( $dbsByMaster as $master => $dbs ) { - $dbConn = wfGetDB( DB_MASTER, array(), $dbs[0] ); - $stype = $dbConn->addQuotes($type); - - # Padding row for MySQL bug - $sql = "(SELECT '-------------------------------------------')"; - foreach ( $dbs as $dbName ) { - if ( $sql != '' ) { - $sql .= ' UNION '; - } - if ($type === false) - $sql .= "(SELECT '$dbName' FROM `$dbName`.job LIMIT 1)"; - else - $sql .= "(SELECT '$dbName' FROM `$dbName`.job WHERE job_cmd=$stype LIMIT 1)"; + public function execute() { + global $wgMemc; + $type = $this->getParam( 'type', false ); + $mckey = $type === false + ? "jobqueue:dbs" + : "jobqueue:dbs:$type"; + $pendingDBs = $wgMemcKey->get( $mckey ); + + # If we didn't get it from the cache + if( !$pendingDBs ) { + $pendingDBs = $this->getPendingDbs( $type ); + $wgMemc->get( $mckey, $pendingDBs, 300 ) } - $res = $dbConn->query( $sql, 'nextJobDB.php' ); - $row = $dbConn->fetchRow( $res ); // discard padding row - while ( $row = $dbConn->fetchRow( $res ) ) { - $pendingDBs[] = $row[0]; + # If we've got a pending job in a db, display it. + if ( $pendingDBs ) { + $this->output( $pendingDBs[mt_rand(0, count( $pendingDBs ) - 1)] ); + } + } + + /** + * Get all databases that have a pending job + * @param $type String Job type + * @return array + */ + private function getPendingDbs( $type ) { + $pendingDBs = array(); + # Cross-reference DBs by master DB server + $dbsByMaster = array(); + foreach ( $wgLocalDatabases as $db ) { + $lb = wfGetLB( $db ); + $dbsByMaster[$lb->getServerName(0)][] = $db; + } + + foreach ( $dbsByMaster as $master => $dbs ) { + $dbConn = wfGetDB( DB_MASTER, array(), $dbs[0] ); + $stype = $dbConn->addQuotes($type); + $jobTable = $dbConn->getTable( 'job' ); + + # Padding row for MySQL bug + $sql = "(SELECT '-------------------------------------------')"; + foreach ( $dbs as $dbName ) { + if ( $sql != '' ) { + $sql .= ' UNION '; + } + if ($type === false) + $sql .= "(SELECT '$dbName' FROM `$dbName`.$jobTable LIMIT 1)"; + else + $sql .= "(SELECT '$dbName' FROM `$dbName`.$jobTable WHERE job_cmd=$stype LIMIT 1)"; + } + $res = $dbConn->query( $sql, __METHOD__ ); + $row = $dbConn->fetchRow( $res ); // discard padding row + while ( $row = $dbConn->fetchRow( $res ) ) { + $pendingDBs[] = $row[0]; + } } } - - $wgMemc->set( $mckey, $pendingDBs, 300 ); -} - -if ( $pendingDBs ) { - echo $pendingDBs[mt_rand(0, count( $pendingDBs ) - 1)]; } - +$maintClass = "nextJobDb"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/nukeNS.php b/maintenance/nukeNS.php index 4f9fe926d2..bd4eae093b 100644 --- a/maintenance/nukeNS.php +++ b/maintenance/nukeNS.php @@ -19,90 +19,80 @@ * based on nukePage by Rob Church */ -require_once( 'commandLine.inc' ); -require_once( 'nukePage.inc' ); +require_once( "Maintenance.php" ); -$ns = NS_MEDIAWIKI; -$delete = false; +class NukeNS extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Remove pages with only 1 revision from any namespace"; + $this->addParam( 'delete', "Actually delete the page" ); + $this->addParam( 'ns', 'Namespace to delete from, default NS_MEDIAWIKI', false, true ); + } -if (isset($options['ns'])) -{ - $ns = $options['ns']; -} + public function execute() { + $ns = $this->getOption( 'ns', NS_MEDIAWIKI ); + $delete = $this->getOption( 'delete', false ); + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin(); -if (isset( $options['delete'] ) and $options['delete']) -{ - $delete = true; -} + $tbl_pag = $dbw->tableName( 'page' ); + $tbl_rev = $dbw->tableName( 'revision' ); + $res = $dbw->query( "SELECT page_title FROM $tbl_pag WHERE page_namespace = $ns" ); + $n_deleted = 0; -NukeNS( $ns, $delete); + while( $row = $dbw->fetchObject( $res ) ) { + //echo "$ns_name:".$row->page_title, "\n"; + $title = Title::newFromText($row->page_title, $ns); + $id = $title->getArticleID(); -function NukeNS($ns_no, $delete) { + // Get corresponding revisions + $res2 = $dbw->query( "SELECT rev_id FROM $tbl_rev WHERE rev_page = $id" ); + $revs = array(); - $dbw = wfGetDB( DB_MASTER ); - $dbw->begin(); - - $tbl_pag = $dbw->tableName( 'page' ); - $tbl_rev = $dbw->tableName( 'revision' ); - $res = $dbw->query( "SELECT page_title FROM $tbl_pag WHERE page_namespace = $ns_no" ); + while( $row2 = $dbw->fetchObject( $res2 ) ) { + $revs[] = $row2->rev_id; + } + $count = count( $revs ); - $n_deleted = 0; - - while( $row = $dbw->fetchObject( $res ) ) { - //echo "$ns_name:".$row->page_title, "\n"; - $title = Title::newFromText($row->page_title, $ns_no); - $id = $title->getArticleID(); + //skip anything that looks modified (i.e. multiple revs) + if (($count == 1)) { + #echo $title->getPrefixedText(), "\t", $count, "\n"; + $this->output( "delete: ", $title->getPrefixedText(), "\n" ); - // Get corresponding revisions - $res2 = $dbw->query( "SELECT rev_id FROM $tbl_rev WHERE rev_page = $id" ); - $revs = array(); - - while( $row2 = $dbw->fetchObject( $res2 ) ) { - $revs[] = $row2->rev_id; - } - $count = count( $revs ); + //as much as I hate to cut & paste this, it's a little different, and + //I already have the id & revs + if( $delete ) { + $dbw->query( "DELETE FROM $tbl_pag WHERE page_id = $id" ); + $dbw->commit(); + // Delete revisions as appropriate + $child = $this->spawnChild( 'NukePage', 'NukePage.php' ); + $child->deleteRevisions( $revs ); + $this->purgeRedundantText( true ); + $n_deleted ++; + } + } else { + $this->output( "skip: ", $title->getPrefixedText(), "\n" ); + } + } + $dbw->commit(); - //skip anything that looks modified (i.e. multiple revs) - if (($count == 1)) { - #echo $title->getPrefixedText(), "\t", $count, "\n"; - echo "delete: ", $title->getPrefixedText(), "\n"; - - //as much as I hate to cut & paste this, it's a little different, and - //I already have the id & revs - - if( $delete ) { - $dbw->query( "DELETE FROM $tbl_pag WHERE page_id = $id" ); - $dbw->commit(); - // Delete revisions as appropriate - DeleteRevisions( $revs ); - PurgeRedundantText( true ); - $n_deleted ++; - } - } else { - echo "skip: ", $title->getPrefixedText(), "\n"; - } - - - } - $dbw->commit(); - - if ($n_deleted > 0) { - #update statistics - better to decrement existing count, or just count - #the page table? - $pages = $dbw->selectField('site_stats', 'ss_total_pages'); - $pages -= $n_deleted; - $dbw->update( 'site_stats', - array('ss_total_pages' => $pages ), - array( 'ss_row_id' => 1), - __METHOD__ ); - - } - - if (!$delete) { - echo( "To update the database, run the script with the --delete option.\n" ); - } - + if ($n_deleted > 0) { + #update statistics - better to decrement existing count, or just count + #the page table? + $pages = $dbw->selectField('site_stats', 'ss_total_pages'); + $pages -= $n_deleted; + $dbw->update( 'site_stats', + array('ss_total_pages' => $pages ), + array( 'ss_row_id' => 1), + __METHOD__ ); + } + + if (!$delete) { + $this->output( "To update the database, run the script with the --delete option.\n" ); + } + } } - +$maintClass = "NukeNS"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/nukePage.php b/maintenance/nukePage.php index b3bfc7626d..ac2059f223 100644 --- a/maintenance/nukePage.php +++ b/maintenance/nukePage.php @@ -1,5 +1,4 @@ */ -require_once( 'commandLine.inc' ); -require_once( 'nukePage.inc' ); +require_once( "Maintenance.php" ); -echo( "Erase Page Record\n\n" ); +class NukePage extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Remove a page record from the database"; + $this->addParam( 'delete', "Actually delete the page" ); + $this->addArgs( array( 'title' ) ); + } -if( isset( $args[0] ) ) { - NukePage( $args[0], true ); -} else { - ShowUsage(); -} + public function execute() { + + $name = $this->getArg(); + $delete = $this->getOption( 'delete', false ); + + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin(); + + $tbl_pag = $dbw->tableName( 'page' ); + $tbl_rec = $dbw->tableName( 'recentchanges' ); + $tbl_rev = $dbw->tableName( 'revision' ); + + # Get page ID + $this->output( "Searching for \"$name\"..." ); + $title = Title::newFromText( $name ); + if( $title ) { + $id = $title->getArticleID(); + $real = $title->getPrefixedText(); + $isGoodArticle = $title->isContentPage(); + $this->output( "found \"$real\" with ID $id.\n" ); + + # Get corresponding revisions + $this->output( "Searching for revisions..." ); + $res = $dbw->query( "SELECT rev_id FROM $tbl_rev WHERE rev_page = $id" ); + while( $row = $dbw->fetchObject( $res ) ) { + $revs[] = $row->rev_id; + } + $count = count( $revs ); + $this->output( "found $count.\n" ); + + # Delete the page record and associated recent changes entries + if( $delete ) { + $this->output( "Deleting page record..." ); + $dbw->query( "DELETE FROM $tbl_pag WHERE page_id = $id" ); + $this->output( "done.\n" ); + $this->output( "Cleaning up recent changes..." ); + $dbw->query( "DELETE FROM $tbl_rec WHERE rc_cur_id = $id" ); + $this->output( "done.\n" ); + } + + $dbw->commit(); + + # Delete revisions as appropriate + if( $delete && $count ) { + $this->output( "Deleting revisions..." ); + $this->deleteRevisions( $revs ); + $this->output( "done.\n" ); + $this->purgeRedundantText( true ); + } + + # Update stats as appropriate + if ( $delete ) { + $this->output( "Updating site stats..." ); + $ga = $isGoodArticle ? -1 : 0; // if it was good, decrement that too + $stats = new SiteStatsUpdate( 0, -$count, $ga, -1 ); + $stats->doUpdate(); + $this->output( "done.\n" ); + } + } else { + $this->output( "not found in database.\n" ); + $dbw->commit(); + } + } + + public function deleteRevisions( $ids ) { + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin(); + + $tbl_rev = $dbw->tableName( 'revision' ); + + $set = implode( ', ', $ids ); + $dbw->query( "DELETE FROM $tbl_rev WHERE rev_id IN ( $set )" ); -/** Show script usage information */ -function ShowUsage() { - echo( "Remove a page record from the database.\n\n" ); - echo( "Usage: php nukePage.php \n\n" ); - echo( " <title> : Page title; spaces escaped with underscores\n\n" ); + $dbw->commit(); + } } +$maintClass = "NukePage"; +require_once( DO_MAINTENANCE ); \ No newline at end of file diff --git a/maintenance/populateLogSearch.php b/maintenance/populateLogSearch.php index 3679f7efee..01b04b5f01 100644 --- a/maintenance/populateLogSearch.php +++ b/maintenance/populateLogSearch.php @@ -9,13 +9,88 @@ * @ingroup Maintenance */ -require_once 'commandLine.inc'; -require_once 'populateLogSearch.inc'; +require_once( "Maintenance.php" ); + +class PopulateLogSearch extends Maintenance { + + const LOG_SEARCH_BATCH_SIZE = 100; + + public function __construct() { + parent::__construct(); + $this->mDescription = "Migrate log params to new table and index for searching"; + } + + public function execute() { + $db = wfGetDB( DB_MASTER ); + if ( !$db->tableExists( 'log_search' ) ) { + $this->error( "log_search does not exist\n", true ); + } + $start = $db->selectField( 'logging', 'MIN(log_id)', false, __FUNCTION__ ); + if( !$start ) { + $this->output( "Nothing to do.\n" ); + return true; + } + $end = $db->selectField( 'logging', 'MAX(log_id)', false, __FUNCTION__ ); -$db =& wfGetDB( DB_MASTER ); -if ( !$db->tableExists( 'log_search' ) ) { - echo "log_search does not exist\n"; - exit( 1 ); + # Do remaining chunk + $end += self::LOG_SEARCH_BATCH_SIZE - 1; + $blockStart = $start; + $blockEnd = $start + self::LOG_SEARCH_BATCH_SIZE - 1; + while( $blockEnd <= $end ) { + $this->output( "...doing log_id from $blockStart to $blockEnd\n" ); + $cond = "log_id BETWEEN $blockStart AND $blockEnd"; + $res = $db->select( 'logging', '*', $cond, __FUNCTION__ ); + $batch = array(); + while( $row = $db->fetchObject( $res ) ) { + // RevisionDelete logs - revisions + if( LogEventsList::typeAction( $row, array('delete','suppress'), 'revision' ) ) { + $params = LogPage::extractParams( $row->log_params ); + // Param format: <urlparam> <item CSV> [<ofield> <nfield>] + if( count($params) >= 2 ) { + $field = RevisionDeleter::getRelationType($params[0]); + // B/C, the params may start with a title key + if( $field == null ) { + array_shift($params); + $field = RevisionDeleter::getRelationType($params[0]); + } + if( $field == null ) { + $this->output( "Invalid param type for $row->log_id\n" ); + continue; // skip this row + } + $items = explode(',',$params[1]); + $log = new LogPage( $row->log_type ); + $log->addRelations( $field, $items, $row->log_id ); + } + // RevisionDelete logs - log events + } else if( LogEventsList::typeAction( $row, array('delete','suppress'), 'event' ) ) { + $params = LogPage::extractParams( $row->log_params ); + // Param format: <item CSV> [<ofield> <nfield>] + if( count($params) >= 1 ) { + $items = explode(',',$params[0]); + $log = new LogPage( $row->log_type ); + $log->addRelations( 'log_id', $items, $row->log_id ); + } + } + } + $blockStart += self::LOG_SEARCH_BATCH_SIZE; + $blockEnd += self::LOG_SEARCH_BATCH_SIZE; + wfWaitForSlaves( 5 ); + } + if( $db->insert( + 'updatelog', + array( 'ul_key' => 'populate log_search' ), + __FUNCTION__, + 'IGNORE' + ) + ) { + $this->output( "log_search population complete.\n" ); + return true; + } else { + $this->output( "Could not insert log_search population row.\n" ); + return false; + } + } } -migrate_log_params( $db ); +$maintClass = "PopulateLogSearch"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/populateParentId.php b/maintenance/populateParentId.php index 0173003342..dd114fdd26 100644 --- a/maintenance/populateParentId.php +++ b/maintenance/populateParentId.php @@ -1,18 +1,105 @@ <?php - /* * Makes the required database updates for rev_parent_id * to be of any use. It can be used for some simple tracking * and to find new page edits by users. */ -require_once 'commandLine.inc'; -require_once 'populateParentId.inc'; - -$db =& wfGetDB( DB_MASTER ); -if ( !$db->tableExists( 'revision' ) ) { - echo "revision table does not exist\n"; - exit( 1 ); +require_once( "Maintenance.php" ); + +class PopulateParentId extends Maintenance { + + // Batch size + const BATCH_SIZE = 200; + + public function __construct() { + parent::__construct(); + $this->mDescription = "Populates rev_parent_id"; + } + + public function execute() { + $db = wfGetDB( DB_MASTER ); + if ( !$db->tableExists( 'revision' ) ) { + $this->error( "revision table does not exist\n", true ); + } + $this->output( "Populating rev_parent_id column\n" ); + $start = $db->selectField( 'revision', 'MIN(rev_id)', false, __FUNCTION__ ); + $end = $db->selectField( 'revision', 'MAX(rev_id)', false, __FUNCTION__ ); + if( is_null( $start ) || is_null( $end ) ){ + $this->output( "...revision table seems to be empty.\n" ); + $db->insert( 'updatelog', + array( 'ul_key' => 'populate rev_parent_id' ), + __FUNCTION__, + 'IGNORE' ); + return; + } + # Do remaining chunk + $end += self::BATCH_SIZE - 1; + $blockStart = intval( $start ); + $blockEnd = intval( $start ) + self::BATCH_SIZE - 1; + $count = 0; + $changed = 0; + while( $blockEnd <= $end ) { + $this->output( "...doing rev_id from $blockStart to $blockEnd\n" ); + $cond = "rev_id BETWEEN $blockStart AND $blockEnd"; + $res = $db->select( 'revision', + array('rev_id','rev_page','rev_timestamp','rev_parent_id'), + $cond, __FUNCTION__ ); + # Go through and update rev_parent_id from these rows. + # Assume that the previous revision of the title was + # the original previous revision of the title when the + # edit was made... + foreach( $res as $row ) { + # First, check rows with the same timestamp other than this one + # with a smaller rev ID. The highest ID "wins". This avoids loops + # as timestamp can only decrease and never loops with IDs (from parent to parent) + $previousID = $db->selectField( 'revision', 'rev_id', + array( 'rev_page' => $row->rev_page, 'rev_timestamp' => $row->rev_timestamp, + "rev_id < " . intval( $row->rev_id ) ), + __FUNCTION__, + array( 'ORDER BY' => 'rev_id DESC' ) ); + # If there are none, check the the highest ID with a lower timestamp + if( !$previousID ) { + # Get the highest older timestamp + $lastTimestamp = $db->selectField( 'revision', 'rev_timestamp', + array( 'rev_page' => $row->rev_page, "rev_timestamp < " . $db->addQuotes( $row->rev_timestamp ) ), + __FUNCTION__, + array( 'ORDER BY' => 'rev_timestamp DESC' ) ); + # If there is one, let the highest rev ID win + if( $lastTimestamp ) { + $previousID = $db->selectField( 'revision', 'rev_id', + array( 'rev_page' => $row->rev_page, 'rev_timestamp' => $lastTimestamp ), + __FUNCTION__, + array( 'ORDER BY' => 'rev_id DESC' ) ); + } + } + $previousID = intval($previousID); + if( $previousID != $row->rev_parent_id ) + $changed++; + # Update the row... + $db->update( 'revision', + array( 'rev_parent_id' => $previousID ), + array( 'rev_id' => $row->rev_id ), + __FUNCTION__ ); + $count++; + } + $blockStart += self::BATCH_SIZE - 1; + $blockEnd += self::BATCH_SIZE - 1; + wfWaitForSlaves( 5 ); + } + $logged = $db->insert( 'updatelog', + array( 'ul_key' => 'populate rev_parent_id' ), + __FUNCTION__, + 'IGNORE' ); + if( $logged ) { + $this->output( "rev_parent_id population complete ... {$count} rows [{$changed} changed]\n" ); + return true; + } else { + $this->output( "Could not insert rev_parent_id population row.\n" ); + return false; + } + } } -populate_rev_parent_id( $db ); +$maintClass = "PopulateParentId"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/purgeOldText.php b/maintenance/purgeOldText.php index 4a4be4822b..08f5ef1aac 100644 --- a/maintenance/purgeOldText.php +++ b/maintenance/purgeOldText.php @@ -1,29 +1,24 @@ <?php - /** * Purge old text records from the database * - * @file * @ingroup Maintenance * @author Rob Church <robchur@gmail.com> */ -$options = array( 'purge', 'help' ); -require_once( 'commandLine.inc' ); -require_once( 'purgeOldText.inc' ); - -echo( "Purge Old Text\n\n" ); - -if( @$options['help'] ) { - ShowUsage(); -} else { - PurgeRedundantText( @$options['purge'] ); -} +require_once( "Maintenance.php" ); -function ShowUsage() { - echo( "Prunes unused text records from the database.\n\n" ); - echo( "Usage: php purgeOldText.php [--purge]\n\n" ); - echo( "purge : Performs the deletion\n" ); - echo( " help : Show this usage information\n" ); +class PurgeOldText extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Purge old text records from the database"; + $this->addOption( 'purge', 'Performs the deletion' ); + } + + public function execute() { + $this->purgeRedundantText( $this->hasOption('purge') ); + } } +$maintClass = "PurgeOldText"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/reassignEdits.php b/maintenance/reassignEdits.php index 5825644e80..daed21954c 100644 --- a/maintenance/reassignEdits.php +++ b/maintenance/reassignEdits.php @@ -1,56 +1,153 @@ <?php - /** * Reassign edits from a user or IP address to another user * - * @file * @ingroup Maintenance * @author Rob Church <robchur@gmail.com> * @licence GNU General Public Licence 2.0 or later */ -$options = array( 'force', 'norc', 'quiet', 'report' ); -require_once( 'commandLine.inc' ); -require_once( 'reassignEdits.inc' ); - -# Set silent mode; --report overrides --quiet -if( !@$options['report'] && @$options['quiet'] ) - setSilent(); - -out( "Reassign Edits\n\n" ); - -if( @$args[0] && @$args[1] ) { - - # Set up the users involved - $from =& initialiseUser( $args[0] ); - $to =& initialiseUser( $args[1] ); - - # If the target doesn't exist, and --force is not set, stop here - if( $to->getId() || @$options['force'] ) { - # Reassign the edits - $report = @$options['report']; - $count = reassignEdits( $from, $to, !@$options['norc'], $report ); - # If reporting, and there were items, advise the user to run without --report - if( $report ) - out( "Run the script again without --report to update.\n" ); - } else { - $ton = $to->getName(); - echo( "User '{$ton}' not found.\n" ); +require_once( "Maintenance.php" ); + +class ReassignEdits extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Reassign edits from one user to another"; + $this->addParam( "force", "Reassign even if the target user doesn't exist" ); + $this->addParam( "norc", "Don't update the recent changes table" ); + $this->addParam( "report", "Print out details of what would be changed, but don't update it" ); + $this->addArgs( array( 'from', 'to' ) ); } -} else { - ShowUsage(); -} + public function execute() { + if( $this->hasArg(0) && $this->hasArg(1) ) { + # Set up the users involved + $from =& $this->initialiseUser( $this->getArg(0) ); + $to =& $this->initialiseUser( $this->getArg(1) ); + + # If the target doesn't exist, and --force is not set, stop here + if( $to->getId() || $this->hasOption('force') ) { + # Reassign the edits + $report = $this->hasOption('report'); + $count = $this->reassignEdits( $from, $to, !$this->hasOption('norc'), $report ); + # If reporting, and there were items, advise the user to run without --report + if( $report ) + $this->output( "Run the script again without --report to update.\n" ); + } else { + $ton = $to->getName(); + $this->error( "User '{$ton}' not found.\n" ); + } + } + } + + /** + * Reassign edits from one user to another + * + * @param $from User to take edits from + * @param $to User to assign edits to + * @param $rc Update the recent changes table + * @param $report Don't change things; just echo numbers + * @return integer Number of entries changed, or that would be changed + */ + private function reassignEdits( &$from, &$to, $rc = false, $report = false ) { + $dbw = wfGetDB( DB_MASTER ); + $dbw->immediateBegin(); + + # Count things + $this->output( "Checking current edits..." ); + $res = $dbw->select( 'revision', 'COUNT(*) AS count', $this->userConditions( $from, 'rev_user', 'rev_user_text' ), __METHOD__ ); + $row = $dbw->fetchObject( $res ); + $cur = $row->count; + $this->output( "found {$cur}.\n" ); + + $this->output( "Checking deleted edits..." ); + $res = $dbw->select( 'archive', 'COUNT(*) AS count', $this->userConditions( $from, 'ar_user', 'ar_user_text' ), __METHOD__ ); + $row = $dbw->fetchObject( $res ); + $del = $row->count; + $this->output( "found {$del}.\n" ); + + # Don't count recent changes if we're not supposed to + if( $rc ) { + $this->output( "Checking recent changes..." ); + $res = $dbw->select( 'recentchanges', 'COUNT(*) AS count', $this->userConditions( $from, 'rc_user', 'rc_user_text' ), __METHOD__ ); + $row = $dbw->fetchObject( $res ); + $rec = $row->count; + $this->output( "found {$rec}.\n" ); + } else { + $rec = 0; + } + + $total = $cur + $del + $rec; + $this->output( "\nTotal entries to change: {$total}\n" ); + + if( !$report ) { + if( $total ) { + # Reassign edits + $this->output( "\nReassigning current edits..." ); + $res = $dbw->update( 'revision', userSpecification( $to, 'rev_user', 'rev_user_text' ), $this->userConditions( $from, 'rev_user', 'rev_user_text' ), __METHOD__ ); + $this->output( "done.\nReassigning deleted edits..." ); + $res = $dbw->update( 'archive', userSpecification( $to, 'ar_user', 'ar_user_text' ), $this->userConditions( $from, 'ar_user', 'ar_user_text' ), __METHOD__ ); + $this->output( "done.\n" ); + # Update recent changes if required + if( $rc ) { + $this->output( "Updating recent changes..." ); + $res = $dbw->update( 'recentchanges', $this->userSpecification( $to, 'rc_user', 'rc_user_text' ), $this->userConditions( $from, 'rc_user', 'rc_user_text' ), __METHOD__ ); + $this->output( "done.\n" ); + } + } + } + + $dbw->immediateCommit(); + return (int)$total; + } + + /** + * Return the most efficient set of user conditions + * i.e. a user => id mapping, or a user_text => text mapping + * + * @param $user User for the condition + * @param $idfield Field name containing the identifier + * @param $utfield Field name containing the user text + * @return array + */ + private function userConditions( &$user, $idfield, $utfield ) { + return $user->getId() ? array( $idfield => $user->getId() ) : array( $utfield => $user->getName() ); + } + + /** + * Return user specifications + * i.e. user => id, user_text => text + * + * @param $user User for the spec + * @param $idfield Field name containing the identifier + * @param $utfield Field name containing the user text + * @return array + */ + private function userSpecification( &$user, $idfield, $utfield ) { + return array( $idfield => $user->getId(), $utfield => $user->getName() ); + } + + /** + * Initialise the user object + * + * @param $username Username or IP address + * @return User + */ + private function initialiseUser( $username ) { + if( User::isIP( $username ) ) { + $user = new User(); + $user->setId( 0 ); + $user->setName( $username ); + } else { + $user = User::newFromName( $username ); + } + $user->load(); + return $user; + } + -/** Show script usage information */ -function ShowUsage() { - echo( "Reassign edits from one user to another.\n\n" ); - echo( "Usage: php reassignEdits.php [--force|--quiet|--norc|--report] <from> <to>\n\n" ); - echo( " <from> : Name of the user to assign edits from\n" ); - echo( " <to> : Name of the user to assign edits to\n" ); - echo( " --force : Reassign even if the target user doesn't exist\n" ); - echo( " --quiet : Don't print status information (except for errors)\n" ); - echo( " --norc : Don't update the recent changes table\n" ); - echo( " --report : Print out details of what would be changed, but don't update it\n\n" ); } +$maintClass = "ReassignEdits"; +require_once( DO_MAINTENANCE ); + diff --git a/maintenance/rebuildFileCache.php b/maintenance/rebuildFileCache.php index 8c01b90fa8..c24ad612d8 100644 --- a/maintenance/rebuildFileCache.php +++ b/maintenance/rebuildFileCache.php @@ -6,87 +6,96 @@ * @ingroup Maintenance */ -/** */ -require_once( "commandLine.inc" ); -if( !$wgUseFileCache ) { - echo "Nothing to do -- \$wgUseFileCache is disabled.\n"; - exit(0); -} -$wgDisableCounters = false; // no real hits here - -$start = isset($args[0]) ? intval($args[0]) : 0; -$overwrite = isset( $args[1] ) && $args[1] === 'overwrite'; -echo "Building content page file cache from page {$start}!\n"; -echo "Format: <start> [overwrite]\n"; - -$dbr = wfGetDB( DB_SLAVE ); -$start = $start > 0 ? $start : $dbr->selectField( 'page', 'MIN(page_id)', false, __FUNCTION__ ); -$end = $dbr->selectField( 'page', 'MAX(page_id)', false, __FUNCTION__ ); -if( !$start ) { - die("Nothing to do.\n"); -} - -$_SERVER['HTTP_ACCEPT_ENCODING'] = 'bgzip'; // hack, no real client -OutputPage::setEncodings(); # Not really used yet +require_once( "Maintenance.php" ); -$BATCH_SIZE = 100; -# Do remaining chunk -$end += $BATCH_SIZE - 1; -$blockStart = $start; -$blockEnd = $start + $BATCH_SIZE - 1; +class RebuildFileCache extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Build file cache for content pages"; + $this->addArgs( array( 'start', 'overwrite' ) ); + } -$dbw = wfGetDB( DB_MASTER ); -// Go through each page and save the output -while( $blockEnd <= $end ) { - // Get the pages - $res = $dbr->select( 'page', array('page_namespace','page_title','page_id'), - array('page_namespace' => $wgContentNamespaces, - "page_id BETWEEN $blockStart AND $blockEnd" ), - array('ORDER BY' => 'page_id ASC','USE INDEX' => 'PRIMARY') - ); - while( $row = $dbr->fetchObject( $res ) ) { - $rebuilt = false; - $wgTitle = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); - if( null == $wgTitle ) { - echo "Page {$row->page_id} bad title\n"; - continue; // broken title? + public function execute() { + global $wgUseFileCache, $wgDisableCounters, $wgTitle, $wgArticle, $wgOut; + if( !$wgUseFileCache ) { + $this->error( "Nothing to do -- \$wgUseFileCache is disabled.\n", true ); + } + $wgDisableCounters = false; + $start = intval( $this->getArg( 0, 0 ) ); + $overwrite = $this->hasArg(1) && $this->getArg(1) === 'overwrite'; + $this->output( "Building content page file cache from page {$start}!\n" ); + + $dbr = wfGetDB( DB_SLAVE ); + $start = $start > 0 ? $start : $dbr->selectField( 'page', 'MIN(page_id)', false, __FUNCTION__ ); + $end = $dbr->selectField( 'page', 'MAX(page_id)', false, __FUNCTION__ ); + if( !$start ) { + $this->error( "Nothing to do.\n", true ); } - $wgArticle = new Article( $wgTitle ); - // If the article is cacheable, then load it - if( $wgArticle->isFileCacheable() ) { - $cache = new HTMLFileCache( $wgTitle ); - if( $cache->isFileCacheGood() ) { - if( $overwrite ) { - $rebuilt = true; + + $_SERVER['HTTP_ACCEPT_ENCODING'] = 'bgzip'; // hack, no real client + OutputPage::setEncodings(); # Not really used yet + + $BATCH_SIZE = 100; + # Do remaining chunk + $end += $BATCH_SIZE - 1; + $blockStart = $start; + $blockEnd = $start + $BATCH_SIZE - 1; + + $dbw = wfGetDB( DB_MASTER ); + // Go through each page and save the output + while( $blockEnd <= $end ) { + // Get the pages + $res = $dbr->select( 'page', array('page_namespace','page_title','page_id'), + array('page_namespace' => $wgContentNamespaces, + "page_id BETWEEN $blockStart AND $blockEnd" ), + array('ORDER BY' => 'page_id ASC','USE INDEX' => 'PRIMARY') + ); + while( $row = $dbr->fetchObject( $res ) ) { + $rebuilt = false; + $wgTitle = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); + if( null == $wgTitle ) { + $this->output( "Page {$row->page_id} bad title\n" ); + continue; // broken title? + } + $wgArticle = new Article( $wgTitle ); + // If the article is cacheable, then load it + if( $wgArticle->isFileCacheable() ) { + $cache = new HTMLFileCache( $wgTitle ); + if( $cache->isFileCacheGood() ) { + if( $overwrite ) { + $rebuilt = true; + } else { + $this->output( "Page {$row->page_id} already cached\n" ); + continue; // done already! + } + } + ob_start( array(&$cache, 'saveToFileCache' ) ); // save on ob_end_clean() + $wgUseFileCache = false; // hack, we don't want $wgArticle fiddling with filecache + $wgArticle->view(); + @$wgOut->output(); // header notices + $wgUseFileCache = true; + ob_end_clean(); // clear buffer + $wgOut = new OutputPage(); // empty out any output page garbage + if( $rebuilt ) + $this->output( "Re-cached page {$row->page_id}\n" ); + else + $this->output( "Cached page {$row->page_id}\n" ); } else { - echo "Page {$row->page_id} already cached\n"; - continue; // done already! + $this->output( "Page {$row->page_id} not cacheable\n" ); } + $dbw->commit(); // commit any changes } - ob_start( array(&$cache, 'saveToFileCache' ) ); // save on ob_end_clean() - $wgUseFileCache = false; // hack, we don't want $wgArticle fiddling with filecache - $wgArticle->view(); - @$wgOut->output(); // header notices - $wgUseFileCache = true; - ob_end_clean(); // clear buffer - $wgOut = new OutputPage(); // empty out any output page garbage - if( $rebuilt ) - echo "Re-cached page {$row->page_id}\n"; - else - echo "Cached page {$row->page_id}\n"; - } else { - echo "Page {$row->page_id} not cacheable\n"; + $blockStart += $BATCH_SIZE; + $blockEnd += $BATCH_SIZE; + wfWaitForSlaves( 5 ); } - $dbw->commit(); // commit any changes + $this->output( "Done!\n" ); + + // Remove these to be safe + if( isset($wgTitle) ) + unset($wgTitle); + if( isset($wgArticle) ) + unset($wgArticle); } - $blockStart += $BATCH_SIZE; - $blockEnd += $BATCH_SIZE; - wfWaitForSlaves( 5 ); } -echo "Done!\n"; - -// Remove these to be safe -if( isset($wgTitle) ) - unset($wgTitle); -if( isset($wgArticle) ) - unset($wgArticle); +require_once( "commandLine.inc" ); diff --git a/maintenance/refreshImageCount.php b/maintenance/refreshImageCount.php index 14f842b950..e0f22b839d 100644 --- a/maintenance/refreshImageCount.php +++ b/maintenance/refreshImageCount.php @@ -7,23 +7,34 @@ * @ingroup Maintenance */ -require_once( "commandLine.inc" ); - -$dbw = wfGetDB( DB_MASTER ); - -// Load the current value from the master -$count = $dbw->selectField( 'site_stats', 'ss_images' ); - -echo wfWikiID().": forcing ss_images to $count\n"; - -// First set to NULL so that it changes on the master -$dbw->update( 'site_stats', - array( 'ss_images' => null ), - array( 'ss_row_id' => 1 ) ); - -// Now this update will be forced to go out -$dbw->update( 'site_stats', - array( 'ss_images' => $count ), - array( 'ss_row_id' => 1 ) ); - +require_once( "Maintenance.php" ); + +class RefreshImageCount extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Resets ss_image count, forcing slaves to pick it up."; + } + + public function execute() { + $dbw = wfGetDB( DB_MASTER ); + + // Load the current value from the master + $count = $dbw->selectField( 'site_stats', 'ss_images' ); + + $this->output( wfWikiID() . ": forcing ss_images to $count\n" ); + + // First set to NULL so that it changes on the master + $dbw->update( 'site_stats', + array( 'ss_images' => null ), + array( 'ss_row_id' => 1 ) ); + + // Now this update will be forced to go out + $dbw->update( 'site_stats', + array( 'ss_images' => $count ), + array( 'ss_row_id' => 1 ) ); + } +} + +$maintClass = "RefreshImageCount"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/removeUnusedAccounts.php b/maintenance/removeUnusedAccounts.php index 5f74b65538..bf03154572 100644 --- a/maintenance/removeUnusedAccounts.php +++ b/maintenance/removeUnusedAccounts.php @@ -8,63 +8,89 @@ * @author Rob Church <robchur@gmail.com> */ -$options = array( 'help', 'delete' ); -require_once( 'commandLine.inc' ); -require_once( 'removeUnusedAccounts.inc' ); -echo( "Remove Unused Accounts\n\n" ); -$fname = 'removeUnusedAccounts'; +require_once( "Maintenance.php" ); -if( isset( $options['help'] ) ) { - showHelp(); - exit(1); -} +class RemoveUnusedAccounts extends Maintenance { + public function __construct() { + parent::__construct(); + $this->addParam( 'delete', 'Actually delete the account' ); + $this->addParam( 'ignore-groups', 'List of comma-separated groups to exclude', false, true ); + $this->addParam( 'ignore-touched', 'Skip accounts touched in last N days', false, true ); + } + + public function execute() { -# Do an initial scan for inactive accounts and report the result -echo( "Checking for unused user accounts...\n" ); -$del = array(); -$dbr = wfGetDB( DB_SLAVE ); -$res = $dbr->select( 'user', array( 'user_id', 'user_name', 'user_touched' ), '', $fname ); -if( isset( $options['ignore-groups'] ) ) { - $excludedGroups = explode( ',', $options['ignore-groups'] ); -} else { $excludedGroups = array(); } -$touchedSeconds = 0; -if( isset( $options['ignore-touched'] ) ) { - $touchedParamError = 0; - if( ctype_digit( $options['ignore-touched'] ) ) { - if( $options['ignore-touched'] <= 0 ) { - $touchedParamError = 1; + $this->output( "Remove unused accounts\n\n" ); + + # Do an initial scan for inactive accounts and report the result + $this->output( "Checking for unused user accounts...\n" ); + $del = array(); + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'user', array( 'user_id', 'user_name', 'user_touched' ), '', __METHOD__ ); + if( $this->hasOption('ignore-groups') ) { + $excludedGroups = explode( ',', $this->getOption('ignore-groups') ); + } else { + $excludedGroups = array(); } - } else { $touchedParamError = 1; } - if( $touchedParamError == 1 ) { - die( "Please put a valid positive integer on the --ignore-touched parameter.\n" ); - } else { $touchedSeconds = 86400 * $options['ignore-touched']; } -} -while( $row = $dbr->fetchObject( $res ) ) { - # Check the account, but ignore it if it's within a $excludedGroups group or if it's touched within the $touchedSeconds seconds. - $instance = User::newFromId( $row->user_id ); - if( count( array_intersect( $instance->getEffectiveGroups(), $excludedGroups ) ) == 0 - && isInactiveAccount( $row->user_id, true ) - && wfTimestamp( TS_UNIX, $row->user_touched ) < wfTimestamp( TS_UNIX, time() - $touchedSeconds ) - ) { - # Inactive; print out the name and flag it - $del[] = $row->user_id; - echo( $row->user_name . "\n" ); + $touched = $this->getOption( 'ignore-touched', "1" ); + if( !ctype_digit( $touched ) ) { + $this->error( "Please put a valid positive integer on the --ignore-touched parameter.\n", true ); + } + $touchedSeconds = 86400 * $touched; + while( $row = $dbr->fetchObject( $res ) ) { + # Check the account, but ignore it if it's within a $excludedGroups group or if it's touched within the $touchedSeconds seconds. + $instance = User::newFromId( $row->user_id ); + if( count( array_intersect( $instance->getEffectiveGroups(), $excludedGroups ) ) == 0 + && $this->isInactiveAccount( $row->user_id, true ) + && wfTimestamp( TS_UNIX, $row->user_touched ) < wfTimestamp( TS_UNIX, time() - $touchedSeconds ) + ) { + # Inactive; print out the name and flag it + $del[] = $row->user_id; + $this->output( $row->user_name . "\n" ); + } + } + $count = count( $del ); + $this->output( "...found {$count}.\n" ); + + # If required, go back and delete each marked account + if( $count > 0 && $this->hasOption('delete') ) { + $this->output( "\nDeleting inactive accounts..." ); + $dbw = wfGetDB( DB_MASTER ); + $dbw->delete( 'user', array( 'user_id' => $del ), __METHOD__ ); + $this->output( "done.\n" ); + # Update the site_stats.ss_users field + $users = $dbw->selectField( 'user', 'COUNT(*)', array(), __METHOD__ ); + $dbw->update( 'site_stats', array( 'ss_users' => $users ), array( 'ss_row_id' => 1 ), __METHOD__ ); + } elseif( $count > 0 ) { + $this->output( "\nRun the script again with --delete to remove them from the database.\n" ); + } + $this->output( "\n" ); + } + + /** + * Could the specified user account be deemed inactive? + * (No edits, no deleted edits, no log entries, no current/old uploads) + * + * @param $id User's ID + * @param $master Perform checking on the master + * @return bool + */ + private function isInactiveAccount( $id, $master = false ) { + $dbo = wfGetDB( $master ? DB_MASTER : DB_SLAVE ); + $checks = array( 'revision' => 'rev', 'archive' => 'ar', 'logging' => 'log', + 'image' => 'img', 'oldimage' => 'oi' ); + $count = 0; + + $dbo->immediateBegin(); + foreach( $checks as $table => $fprefix ) { + $conds = array( $fprefix . '_user' => $id ); + $count += (int)$dbo->selectField( $table, 'COUNT(*)', $conds, __METHOD__ ); + } + $dbo->immediateCommit(); + + return $count == 0; } } -$count = count( $del ); -echo( "...found {$count}.\n" ); -# If required, go back and delete each marked account -if( $count > 0 && isset( $options['delete'] ) ) { - echo( "\nDeleting inactive accounts..." ); - $dbw = wfGetDB( DB_MASTER ); - $dbw->delete( 'user', array( 'user_id' => $del ), $fname ); - echo( "done.\n" ); - # Update the site_stats.ss_users field - $users = $dbw->selectField( 'user', 'COUNT(*)', array(), $fname ); - $dbw->update( 'site_stats', array( 'ss_users' => $users ), array( 'ss_row_id' => 1 ), $fname ); -} else { - if( $count > 0 ) - echo( "\nRun the script again with --delete to remove them from the database.\n" ); -} -echo( "\n" ); +$maintClass = "RemoveUnusedAccounts"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/renameDbPrefix.php b/maintenance/renameDbPrefix.php index 277f3b95cb..562f143bcc 100644 --- a/maintenance/renameDbPrefix.php +++ b/maintenance/renameDbPrefix.php @@ -6,62 +6,59 @@ * @file * @ingroup Maintenance */ -$optionsWithArgs = array( 'old', 'new', 'help' ); + +require_once( "Maintenance.php" ); -require_once( 'commandLine.inc' ); - -if( @$options['help'] || !isset( $options['old'] ) || !isset( $options['new'] ) ) { - print "usage: renameDbPrefix.php [--help] [--old x] [new y]\n"; - print " --help : this help message\n"; - print " --old x : old db prefix x\n"; - print " --old 0 : EMPTY old db prefix x\n"; - print " --new y : new db prefix y\n"; - print " --new 0 : EMPTY new db prefix\n"; - wfDie(); -} - -// Allow for no old prefix -if( $options['old'] === '0' ) { - $old = ''; -} else { - // Use nice safe, sane, prefixes - preg_match( '/^[a-zA-Z]+_$/', $options['old'], $m ); - $old = isset( $m[0] ) ? $m[0] : false; -} -// Allow for no new prefix -if( $options['new'] === '0' ) { - $new = ''; -} else { - // Use nice safe, sane, prefixes - preg_match( '/^[a-zA-Z]+_$/', $options['new'], $m ); - $new = isset( $m[0] ) ? $m[0] : false; -} - -if( $old === false || $new === false ) { - print "Invalid prefix!\n"; - wfDie(); -} -if( $old === $new ) { - print "Same prefix. Nothing to rename!\n"; - wfDie(); -} - -print "Renaming DB prefix for tables of $wgDBname from '$old' to '$new'\n"; -$count = 0; - -$dbw = wfGetDB( DB_MASTER ); -$res = $dbw->query( "SHOW TABLES LIKE '".$dbw->escapeLike( $old )."%'" ); -foreach( $res as $row ) { - // XXX: odd syntax. MySQL outputs an oddly cased "Tables of X" - // sort of message. Best not to try $row->x stuff... - $fields = get_object_vars( $row ); - // Silly for loop over one field... - foreach( $fields as $resName => $table ) { - // $old should be regexp safe ([a-zA-Z_]) - $newTable = preg_replace( '/^'.$old.'/', $new, $table ); - print "Renaming table $table to $newTable\n"; - $dbw->query( "RENAME TABLE $table TO $newTable" ); +class RenameDbPrefix extends Maintenance { + public function __construct() { + parent::__construct(); + $this->addParam( "old", "Old db prefix [0 for none]", true, true ); + $this->addParam( "new", "New db prefix [0 for none]", true, true ); + } + + public function execute() { + // Allow for no old prefix + if( $this->getOption( 'old', 0 ) === '0' ) { + $old = ''; + } else { + // Use nice safe, sane, prefixes + preg_match( '/^[a-zA-Z]+_$/', $this->getOption('old'), $m ); + $old = isset( $m[0] ) ? $m[0] : false; + } + // Allow for no new prefix + if( $this->getOption( 'new', 0 ) === '0' ) { + $new = ''; + } else { + // Use nice safe, sane, prefixes + preg_match( '/^[a-zA-Z]+_$/', $this->getOption('new'), $m ); + $new = isset( $m[0] ) ? $m[0] : false; + } + + if( $old === false || $new === false ) { + $this->error( "Invalid prefix!\n", true ); + } + if( $old === $new ) { + $this->( "Same prefix. Nothing to rename!\n", true ); + } + + $this->output( "Renaming DB prefix for tables of $wgDBname from '$old' to '$new'\n" ); + $count = 0; + + $dbw = wfGetDB( DB_MASTER ); + $res = $dbw->query( "SHOW TABLES LIKE '".$dbw->escapeLike( $old )."%'" ); + foreach( $res as $row ) { + // XXX: odd syntax. MySQL outputs an oddly cased "Tables of X" + // sort of message. Best not to try $row->x stuff... + $fields = get_object_vars( $row ); + // Silly for loop over one field... + foreach( $fields as $resName => $table ) { + // $old should be regexp safe ([a-zA-Z_]) + $newTable = preg_replace( '/^'.$old.'/', $new, $table ); + $this->output( "Renaming table $table to $newTable\n" ); + $dbw->query( "RENAME TABLE $table TO $newTable" ); + } + $count++; + } + $this->output( "Done! [$count tables]\n" ); } - $count++; } -print "Done! [$count tables]\n"; \ No newline at end of file diff --git a/maintenance/renderDump.php b/maintenance/renderDump.php index 97797792a0..fe2d22b6b7 100644 --- a/maintenance/renderDump.php +++ b/maintenance/renderDump.php @@ -27,42 +27,61 @@ * @file * @ingroup Maintenance */ + +require_once( "Maintenance.php" ); -$optionsWithArgs = array( 'report' ); +class DumpRenderer extends Maintenance { -require_once( 'commandLine.inc' ); + private $count = 0; + private $outputDirectory, $startTime; -class DumpRenderer { - function __construct( $dir ) { - $this->stderr = fopen( "php://stderr", "wt" ); - $this->outputDirectory = $dir; - $this->count = 0; + public function __construct() { + parent::__construct(); + $this->mDescription = "Take page text out of an XML dump file and render basic HTML out to files"; + $this->addParam( 'output-dir', 'The directory to output the HTML files to', true, true ); } - function handleRevision( $rev ) { + public function execute() { + $this->outputDirectory = $this->getOption( 'output-dir' ); + $this->startTime = wfTime(); + + $source = new ImportStreamSource( $this->getStdin() ); + $importer = new WikiImporter( $source ); + + $importer->setRevisionCallback( + array( &$this, 'handleRevision' ) ); + + return $importer->doImport(); + } + + /** + * Callback function for each revision, turn into HTML and save + * @param $rev Revision + */ + private function handleRevision( $rev ) { $title = $rev->getTitle(); if (!$title) { - fprintf( $this->stderr, "Got bogus revision with null title!" ); + $this->error( "Got bogus revision with null title!" ); return; } $display = $title->getPrefixedText(); - + $this->count++; - + $sanitized = rawurlencode( $display ); $filename = sprintf( "%s/wiki-%07d-%s.html", $this->outputDirectory, $this->count, $sanitized ); - fprintf( $this->stderr, "%s\n", $filename, $display ); - - // fixme + $this->output( sprintf( $this->stderr, "%s\n", $filename, $display ) ); + + // fixme (what?) $user = new User(); $parser = new Parser(); $options = ParserOptions::newFromUser( $user ); - + $output = $parser->parse( $rev->getText(), $title, $options ); - + file_put_contents( $filename, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " . "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" . @@ -76,27 +95,7 @@ class DumpRenderer { "</body>\n" . "</html>" ); } - - function run() { - $this->startTime = wfTime(); - - $file = fopen( 'php://stdin', 'rt' ); - $source = new ImportStreamSource( $file ); - $importer = new WikiImporter( $source ); - - $importer->setRevisionCallback( - array( &$this, 'handleRevision' ) ); - - return $importer->doImport(); - } } -if( isset( $options['output-dir'] ) ) { - $dir = $options['output-dir']; -} else { - wfDie( "Must use --output-dir=/some/dir\n" ); -} -$render = new DumpRenderer( $dir ); -$render->run(); - - +$maintClass = "DumpRenderer"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/runJobs.php b/maintenance/runJobs.php index 1340a85770..202c1e7f4f 100644 --- a/maintenance/runJobs.php +++ b/maintenance/runJobs.php @@ -10,71 +10,77 @@ * @ingroup Maintenance */ -$optionsWithArgs = array( 'maxjobs', 'type', 'procs' ); -$wgUseNormalUser = true; -require_once( 'commandLine.inc' ); +require_once( "Maintenance.php" ); -if ( isset( $options['procs'] ) ) { - $procs = intval( $options['procs'] ); - if ( $procs < 1 || $procs > 1000 ) { - echo "Invalid argument to --procs\n"; - exit( 1 ); +class RunJobs extends Maintenance { + public function __construct() { + global $wgUseNormalUser; + parent::__construct(); + $this->mDescription = "Run pending jobs"; + $this->addParam( 'maxjobs', 'Maximum number of jobs to run', false, true ); + $this->addParam( 'type', 'Type of job to run', false, true ); + $this->addParam( 'procs', 'Number of processes to use', false, true ); + $wgUseNormalUser = true; } - $fc = new ForkController( $procs ); - if ( $fc->start( $procs ) != 'child' ) { - exit( 0 ); - } -} - -if ( isset( $options['maxjobs'] ) ) { - $maxJobs = $options['maxjobs']; -} else { - $maxJobs = 10000; -} - -$type = false; -if ( isset( $options['type'] ) ) - $type = $options['type']; - -$wgTitle = Title::newFromText( 'RunJobs.php' ); - -$dbw = wfGetDB( DB_MASTER ); -$n = 0; -$conds = ''; -if ($type !== false) - $conds = "job_cmd = " . $dbw->addQuotes($type); -while ( $dbw->selectField( 'job', 'job_id', $conds, 'runJobs.php' ) ) { - $offset=0; - for (;;) { - $job = ($type == false) ? - Job::pop($offset) - : Job::pop_type($type); - - if ($job == false) - break; - - wfWaitForSlaves( 5 ); - $t = microtime( true ); - $offset=$job->id; - $status = $job->run(); - $t = microtime( true ) - $t; - $timeMs = intval( $t * 1000 ); - if ( !$status ) { - runJobsLog( $job->toString() . " t=$timeMs error={$job->error}" ); - } else { - runJobsLog( $job->toString() . " t=$timeMs good" ); + public function execute() { + global $wgTitle; + if ( $this->hasOption( 'procs' ) ) { + $procs = intval( $this->getOption('procs') ); + if ( $procs < 1 || $procs > 1000 ) { + $this->error( "Invalid argument to --procs\n", true ); + } + $fc = new ForkController( $procs ); + if ( $fc->start( $procs ) != 'child' ) { + exit( 0 ); + } } - if ( $maxJobs && ++$n > $maxJobs ) { - break 2; + $maxJobs = $this->getOption( 'maxjobs', 10000 ); + $type = $this->getOption( 'type', false ); + $wgTitle = Title::newFromText( 'RunJobs.php' ); + $dbw = wfGetDB( DB_MASTER ); + $n = 0; + $conds = ''; + if ($type !== false) + $conds = "job_cmd = " . $dbw->addQuotes($type); + + while ( $dbw->selectField( 'job', 'job_id', $conds, 'runJobs.php' ) ) { + $offset=0; + for (;;) { + $job = ($type == false) ? + Job::pop($offset) + : Job::pop_type($type); + + if ($job == false) + break; + + wfWaitForSlaves( 5 ); + $t = microtime( true ); + $offset=$job->id; + $status = $job->run(); + $t = microtime( true ) - $t; + $timeMs = intval( $t * 1000 ); + if ( !$status ) { + $this->runJobsLog( $job->toString() . " t=$timeMs error={$job->error}" ); + } else { + $this->runJobsLog( $job->toString() . " t=$timeMs good" ); + } + if ( $maxJobs && ++$n > $maxJobs ) { + break 2; + } + } } } -} - -function runJobsLog( $msg ) { - print wfTimestamp( TS_DB ) . " $msg\n"; - wfDebugLog( 'runJobs', $msg ); + /** + * Log the job message + * @param $msg String The message to log + */ + private function runJobsLog( $msg ) { + $this->output( wfTimestamp( TS_DB ) . " $msg\n" ); + wfDebugLog( 'runJobs', $msg ); + } } - +$maintClass = "RunJobs"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/showJobs.php b/maintenance/showJobs.php index 6e38c854c3..49b825f791 100644 --- a/maintenance/showJobs.php +++ b/maintenance/showJobs.php @@ -9,10 +9,19 @@ * @author Tim Starling * @author Ashar Voultoiz */ -require_once( 'commandLine.inc' ); - -$dbw = wfGetDB( DB_MASTER ); -$count = $dbw->selectField( 'job', 'count(*)', '', 'runJobs.php' ); -print $count."\n"; + +require_once( "Maintenance.php" ); +class ShowJobs extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Show number of jobs waiting in master database"; + } + public function execute() { + $dbw = wfGetDB( DB_MASTER ); + $this->output( $dbw->selectField( 'job', 'count(*)', '', 'runJobs.php' ) . "\n" ); + } +} +$maintClass = "ShowJobs"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/showStats.php b/maintenance/showStats.php index ef13f6541a..fc0a1410c9 100644 --- a/maintenance/showStats.php +++ b/maintenance/showStats.php @@ -14,35 +14,41 @@ * @license GNU General Public License 2.0 or later */ -require_once( 'commandLine.inc' ); +require_once( "Maintenance.php" ); -# -# Configuration -# -$fields = array( - 'ss_total_views' => 'Total views', - 'ss_total_edits' => 'Total edits', - 'ss_good_articles' => 'Number of articles', - 'ss_total_pages' => 'Total pages', - 'ss_users' => 'Number of users', - 'ss_admins' => 'Number of admins', - 'ss_images' => 'Number of images', -); - -// Get cached stats from slave database -$dbr = wfGetDB( DB_SLAVE ); -$fname = 'showStats'; -$stats = $dbr->selectRow( 'site_stats', '*', '' ); - -// Get maximum size for each column -$max_length_value = $max_length_desc = 0; -foreach( $fields as $field => $desc ) { - $max_length_value = max( $max_length_value, strlen( $stats->$field ) ); - $max_length_desc = max( $max_length_desc , strlen( $desc )) ; +class ShowStats extends Maintenance { + public function __construct() { + $this->mDescription = "Show the cached statistics"; + } + public function execute() { + $fields = array( + 'ss_total_views' => 'Total views', + 'ss_total_edits' => 'Total edits', + 'ss_good_articles' => 'Number of articles', + 'ss_total_pages' => 'Total pages', + 'ss_users' => 'Number of users', + 'ss_admins' => 'Number of admins', + 'ss_images' => 'Number of images', + ); + + // Get cached stats from slave database + $dbr = wfGetDB( DB_SLAVE ); + $stats = $dbr->selectRow( 'site_stats', '*', '', __METHOD__ ); + + // Get maximum size for each column + $max_length_value = $max_length_desc = 0; + foreach( $fields as $field => $desc ) { + $max_length_value = max( $max_length_value, strlen( $stats->$field ) ); + $max_length_desc = max( $max_length_desc , strlen( $desc )) ; + } + + // Show them + foreach( $fields as $field => $desc ) { + $this->output( sprintf( "%-{$max_length_desc}s: %{$max_length_value}d\n", $desc, $stats->$field ) ); + } + } } -// Show them -foreach( $fields as $field => $desc ) { - printf( "%-{$max_length_desc}s: %{$max_length_value}d\n", $desc, $stats->$field ); -} +$maintClass = "ShowStats"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/sql.php b/maintenance/sql.php index ab6546b9e0..1dbbe550ac 100644 --- a/maintenance/sql.php +++ b/maintenance/sql.php @@ -7,38 +7,56 @@ * @ingroup Database Maintenance */ -require_once( dirname(__FILE__) . '/' . 'commandLine.inc' ); +require_once( "Maintenance.php" ); -if ( isset( $options['help'] ) ) { - echo "Send SQL queries to a MediaWiki database.\nUsage: php sql.php [<file>]\n"; - exit( 1 ); -} +class MwSql extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Send SQL queries to a MediaWiki database"; + } -if ( isset( $args[0] ) ) { - $fileName = $args[0]; - $file = fopen( $fileName, 'r' ); - $promptCallback = false; -} else { - $file = STDIN; - $promptObject = new SqlPromptPrinter( "> " ); - $promptCallback = $promptObject->cb(); -} + public function execute() { + if ( $this->hasArg() ) { + $fileName = $this->getArg(); + $file = fopen( $fileName, 'r' ); + $promptCallback = false; + } else { + $file = $this->getStdin(); + $promptObject = new SqlPromptPrinter( "> " ); + $promptCallback = $promptObject->cb(); + } + + if ( !$file ) + $this->error( "Unable to open input file\n", true ); -if ( !$file ) { - echo "Unable to open input file\n"; - exit( 1 ); -} + $dbw = wfGetDB( DB_MASTER ); + $error = $dbw->sourceStream( $file, $promptCallback, array( $this, 'sqlPrintResult' ) ); + if ( $error !== true ) { + $this->error( $error, true ); + } else { + exit( 0 ); + } + } -$dbw =& wfGetDB( DB_MASTER ); -$error = $dbw->sourceStream( $file, $promptCallback, 'sqlPrintResult' ); -if ( $error !== true ) { - echo $error; - exit( 1 ); -} else { - exit( 0 ); + /** + * Print the results, callback for $db->sourceStream() + * @param $res The results object + * @param $db Database object + */ + public function sqlPrintResult( $res, $db ) { + if ( !$res ) { + // Do nothing + } elseif ( is_object( $res ) && $res->numRows() ) { + while ( $row = $res->fetchObject() ) { + $this->output( print_r( $row, true ) ); + } + } else { + $affected = $db->affectedRows(); + $this->output( "Query OK, $affected row(s) affected\n" ); + } + } } -//----------------------------------------------------------------------------- class SqlPromptPrinter { function __construct( $prompt ) { $this->prompt = $prompt; @@ -53,17 +71,5 @@ class SqlPromptPrinter { } } -function sqlPrintResult( $res, $db ) { - if ( !$res ) { - // Do nothing - } elseif ( is_object( $res ) && $res->numRows() ) { - while ( $row = $res->fetchObject() ) { - print_r( $row ); - } - } else { - $affected = $db->affectedRows(); - echo "Query OK, $affected row(s) affected\n"; - } -} - - +$maintClass = "MwSql"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/stats.php b/maintenance/stats.php index 00f79ded81..0e530333c3 100644 --- a/maintenance/stats.php +++ b/maintenance/stats.php @@ -1,58 +1,73 @@ <?php /** * Show statistics from memcached - * - * @file * @ingroup Maintenance */ -require_once('commandLine.inc'); +require_once( "Maintenance.php" ); -if( get_class( $wgMemc ) == 'FakeMemCachedClient' ) { - die("You are running FakeMemCachedClient, I can not provide any statistics.\n"); -} -$session = intval($wgMemc->get(wfMemcKey('stats','request_with_session'))); -$noSession = intval($wgMemc->get(wfMemcKey('stats','request_without_session'))); -$total = $session + $noSession; -if ( $total == 0 ) { - die("You either have no stats or memcached isn't running. Aborting.\n"); +class MemcachedStats extends Maintenance { + + public function __construct() { + $this->mDescription = "Show statistics from memcached"; + } + + public function execute() { + global $wgMemc; + + // Can't do stats if + if( get_class( $wgMemc ) == 'FakeMemCachedClient' ) { + $this->error( "You are running FakeMemCachedClient, I can not provide any statistics.\n", true ); + } + $session = intval($wgMemc->get(wfMemcKey('stats','request_with_session'))); + $noSession = intval($wgMemc->get(wfMemcKey('stats','request_without_session'))); + $total = $session + $noSession; + if ( $total == 0 ) { + $this->error( "You either have no stats or memcached isn't running. Aborting.\n", true ); + } + $this->output( "Requests\n" ); + $this->output( sprintf( "with session: %-10d %6.2f%%\n", $session, $session/$total*100 ); + $this->output( sprintf( "without session: %-10d %6.2f%%\n", $noSession, $noSession/$total*100 ); + $this->output( sprintf( "total: %-10d %6.2f%%\n", $total, 100 ); + + + $this->output( "\nParser cache\n" ); + $hits = intval($wgMemc->get(wfMemcKey('stats','pcache_hit'))); + $invalid = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_invalid'))); + $expired = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_expired'))); + $absent = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_absent'))); + $stub = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_stub'))); + $total = $hits + $invalid + $expired + $absent + $stub; + $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 ) ); + $this->output( sprintf( "invalid: %-10d %6.2f%%\n", $invalid, $invalid/$total*100 ) ); + $this->output( sprintf( "expired: %-10d %6.2f%%\n", $expired, $expired/$total*100 ) ); + $this->output( sprintf( "absent: %-10d %6.2f%%\n", $absent, $absent/$total*100 ) ); + $this->output( sprintf( "stub threshold: %-10d %6.2f%%\n", $stub, $stub/$total*100 ) ); + $this->output( sprintf( "total: %-10d %6.2f%%\n", $total, 100 ) ); + + $hits = intval($wgMemc->get(wfMemcKey('stats','image_cache_hit'))); + $misses = intval($wgMemc->get(wfMemcKey('stats','image_cache_miss'))); + $updates = intval($wgMemc->get(wfMemcKey('stats','image_cache_update'))); + $total = $hits + $misses; + $this->output("\nImage cache\n"); + $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 ) ); + $this->output( sprintf( "misses: %-10d %6.2f%%\n", $misses, $misses/$total*100 ) ); + $this->output( sprintf( "updates: %-10d\n", $updates ) ); + + $hits = intval($wgMemc->get(wfMemcKey('stats','diff_cache_hit'))); + $misses = intval($wgMemc->get(wfMemcKey('stats','diff_cache_miss'))); + $uncacheable = intval($wgMemc->get(wfMemcKey('stats','diff_uncacheable'))); + $total = $hits + $misses + $uncacheable; + $this->output("\nDiff cache\n"); + $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 ); + $this->output( sprintf( "misses: %-10d %6.2f%%\n", $misses, $misses/$total*100 ); + $this->output( sprintf( "uncacheable: %-10d %6.2f%%\n", $uncacheable, $uncacheable/$total*100 ); + } } -print "Requests\n"; -printf( "with session: %-10d %6.2f%%\n", $session, $session/$total*100 ); -printf( "without session: %-10d %6.2f%%\n", $noSession, $noSession/$total*100 ); -printf( "total: %-10d %6.2f%%\n", $total, 100 ); - - -print "\nParser cache\n"; -$hits = intval($wgMemc->get(wfMemcKey('stats','pcache_hit'))); -$invalid = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_invalid'))); -$expired = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_expired'))); -$absent = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_absent'))); -$stub = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_stub'))); -$total = $hits + $invalid + $expired + $absent + $stub; -printf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 ); -printf( "invalid: %-10d %6.2f%%\n", $invalid, $invalid/$total*100 ); -printf( "expired: %-10d %6.2f%%\n", $expired, $expired/$total*100 ); -printf( "absent: %-10d %6.2f%%\n", $absent, $absent/$total*100 ); -printf( "stub threshold: %-10d %6.2f%%\n", $stub, $stub/$total*100 ); -printf( "total: %-10d %6.2f%%\n", $total, 100 ); - -$hits = intval($wgMemc->get(wfMemcKey('stats','image_cache_hit'))); -$misses = intval($wgMemc->get(wfMemcKey('stats','image_cache_miss'))); -$updates = intval($wgMemc->get(wfMemcKey('stats','image_cache_update'))); -$total = $hits + $misses; -print("\nImage cache\n"); -printf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 ); -printf( "misses: %-10d %6.2f%%\n", $misses, $misses/$total*100 ); -printf( "updates: %-10d\n", $updates ); - -$hits = intval($wgMemc->get(wfMemcKey('stats','diff_cache_hit'))); -$misses = intval($wgMemc->get(wfMemcKey('stats','diff_cache_miss'))); -$uncacheable = intval($wgMemc->get(wfMemcKey('stats','diff_uncacheable'))); -$total = $hits + $misses + $uncacheable; -print("\nDiff cache\n"); -printf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 ); -printf( "misses: %-10d %6.2f%%\n", $misses, $misses/$total*100 ); -printf( "uncacheable: %-10d %6.2f%%\n", $uncacheable, $uncacheable/$total*100 ); + +$maintClass = "MemcachedStats"; +require_once( DO_MAINTENANCE ); + + diff --git a/maintenance/undelete.php b/maintenance/undelete.php index b7b7df979e..9328f12db6 100644 --- a/maintenance/undelete.php +++ b/maintenance/undelete.php @@ -6,34 +6,35 @@ * @ingroup Maintenance */ -$usage = <<<EOT -Undelete a page -Usage: php undelete.php [-u <user>] [-r <reason>] <pagename> +require_once( "Maintenance.php" ); -EOT; +class Undelete extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Undelete a page"; + $this->addParam( 'u', 'The user to perform the undeletion', false, true ); + $this->addParam( 'r', 'The reason to undelete', false, true ); + $this->addArgs( array( 'pagename' ) ); + } -$optionsWithArgs = array( 'u', 'r' ); -require_once( 'commandLine.inc' ); + public function execute() { + global $wgUser; -$user = 'Command line script'; -$reason = ''; + $user = $this->getOption( 'u', 'Command line script' ); + $reason = $this->getOption( 'r', '' ); + $pageName = $this->getArg(); -if ( isset( $options['u'] ) ) { - $user = $options['u']; + $title = Title::newFromText( $pageName ); + if ( !$title ) { + $this->error( "Invalid title", true ); + } + $wgUser = User::newFromName( $user ); + $archive = new PageArchive( $title ); + $this->output( "Undeleting " . $title->getPrefixedDBkey() . '...' ); + $archive->undelete( array(), $reason ); + $this->output( "done\n" ); + } } -if ( isset( $options['r'] ) ) { - $reason = $options['r']; -} -$pageName = @$args[0]; -$title = Title::newFromText( $pageName ); -if ( !$title ) { - echo $usage; - exit( 1 ); -} -$wgUser = User::newFromName( $user ); -$archive = new PageArchive( $title ); -echo "Undeleting " . $title->getPrefixedDBkey() . '...'; -$archive->undelete( array(), $reason ); -echo "done\n"; - +$maintClass = "Undelete"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/updateArticleCount.php b/maintenance/updateArticleCount.php index 15033ad309..3b704ebf98 100644 --- a/maintenance/updateArticleCount.php +++ b/maintenance/updateArticleCount.php @@ -3,38 +3,84 @@ * Maintenance script to provide a better count of the number of articles * and update the site statistics table, if desired * - * @file * @ingroup Maintenance * @author Rob Church <robchur@gmail.com> */ -$options = array( 'update', 'help' ); -require_once( 'commandLine.inc' ); -require_once( 'updateArticleCount.inc' ); -echo( "Update Article Count\n\n" ); +require_once( "Maintenance.php" ); -if( isset( $options['help'] ) && $options['help'] ) { - echo( "Usage: php updateArticleCount.php [--update]\n\n" ); - echo( "--update : Update site statistics table\n" ); - exit( 0 ); -} +class UpdateArticleCount extends Maintenance { + + // Content namespaces + private $namespaces; + + public function __construct() { + global $wgContentNamespaces; + parent::__construct(); + $this->mDescription = "Count of the number of articles and update the site statistics table"; + $this->addParam( 'update', 'Update the site_stats table with the new count' ); + $this->namespaces = $wgContentNamespaces; + } + + public function execute() { + $this->output( "Counting articles..." ); + $result = $this->count(); + + if( $result !== false ) { + $this->output( "found {$result}.\n" ); + if( isset( $options['update'] ) && $options['update'] ) { + $this->output( "Updating site statistics table... " ); + $dbw = wfGetDB( DB_MASTER ); + $dbw->update( 'site_stats', array( 'ss_good_articles' => $result ), array( 'ss_row_id' => 1 ), __METHOD__ ); + $this->output( "done.\n" ); + } else { + $this->output( "To update the site statistics table, run the script with the --update option.\n" ); + } + } else { + $this->output( "failed.\n" ); + } + } + + /** + * Produce a comma-delimited set of namespaces + * Includes paranoia + * + * @return string + */ + private function makeNsSet() { + foreach( $this->namespaces as $namespace ) + $namespaces[] = intval( $namespace ); + return implode( ', ', $namespaces ); + } + + /** + * Produce SQL for the query + * + * @param $dbr Database handle + * @return string + */ + private function makeSql( $dbr ) { + list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' ); + $nsset = $this->makeNsSet(); + return "SELECT COUNT(DISTINCT page_namespace, page_title) AS pagecount " . + "FROM $page, $pagelinks " . + "WHERE pl_from=page_id and page_namespace IN ( $nsset ) " . + "AND page_is_redirect = 0 AND page_len > 0"; + } -echo( "Counting articles..." ); -$counter = new ArticleCounter(); -$result = $counter->count(); - -if( $result !== false ) { - echo( "found {$result}.\n" ); - if( isset( $options['update'] ) && $options['update'] ) { - echo( "Updating site statistics table... " ); - $dbw = wfGetDB( DB_MASTER ); - $dbw->update( 'site_stats', array( 'ss_good_articles' => $result ), array( 'ss_row_id' => 1 ), __METHOD__ ); - echo( "done.\n" ); - } else { - echo( "To update the site statistics table, run the script with the --update option.\n" ); + /** + * Count the number of valid content pages in the wiki + * + * @return mixed Integer, or false if there's a problem + */ + private function count() { + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->query( $this->makeSql( $dbr ), __METHOD__ ); + $row = $dbr->fetchObject( $res ); + $dbr->freeResult( $res ); + return $row->pagecount; } -} else { - echo( "failed.\n" ); } -echo( "\n" ); +$maintClass = "UpdateArticleCount"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/updateSearchIndex.php b/maintenance/updateSearchIndex.php index 5fa24ad691..ee1bb2f7ea 100644 --- a/maintenance/updateSearchIndex.php +++ b/maintenance/updateSearchIndex.php @@ -5,58 +5,159 @@ * Usage: php updateSearchIndex.php [-s START] [-e END] [-p POSFILE] [-l LOCKTIME] [-q] * Where START is the starting timestamp * END is the ending timestamp - * POSFILE is a file to load timestamps from and save them to, searchUpdate.pos by default - * LOCKTIME is how long the searchindex and cur tables will be locked for + * POSFILE is a file to load timestamps from and save them to, searchUpdate.WIKI_ID.pos by default + * LOCKTIME is how long the searchindex and revision tables will be locked for * -q means quiet * - * @file * @ingroup Maintenance */ + +require_once( "Maintenance.php" ); -/** */ -$optionsWithArgs = array( 's', 'e', 'p' ); +class UpdateSearchIndex extends Maintenance { -require_once( 'commandLine.inc' ); -require_once( 'updateSearchIndex.inc' ); + public function __construct() { + parent::__construct(); + $this->mDescription = "Script for periodic off-peak updating of the search index"; + $this->addParam( 's', 'starting timestamp', false, true ); + $this->addParam( 'e', 'Ending timestamp', false, true ); + $this->addParam( 'p', 'File for saving/loading timestamps, searchUpdate.WIKI_ID.pos by default', false, true ); + $this->addParam( 'l', 'How long the searchindex and revision tables will be locked for', false, true ); + } -if ( isset( $options['p'] ) ) { - $posFile = $options['p']; -} else { - $posFile = 'searchUpdate.' . wfWikiId() . '.pos'; -} + public function execute() { + $posFile = $this->getOption( 'p', 'searchUpdate.' . wfWikiId() . '.pos' ); + $end = $this->getOption( 'e', wfTimestampNow() ); + if ( $this->hasOption( 's' ) ) { + $start = $this->getOption('s'); + } elseif( is_readable( 'searchUpdate.pos' ) ) { + # B/c to the old position file name which was hardcoded + # We can safely delete the file when we're done though. + $start = file_get_contents( 'searchUpdate.pos' ); + unlink( 'searchUpdate.pos' ); + } else { + $start = @file_get_contents( $posFile ); + if ( !$start ) { + $start = wfTimestamp( TS_MW, time() - 86400 ); + } + } + $lockTime = $this->getOption( 'l', 20 ); + + $this->updateSearchIndex( $start, $end, $lockTime ); + $file = fopen( $posFile, 'w' ); + fwrite( $file, $end ); + fclose( $file ); + } + + private function updateSearchIndex( $start, $end, $maxLockTime ) { + global $wgDisableSearchUpdate; -if ( isset( $options['e'] ) ) { - $end = $options['e']; -} else { - $end = wfTimestampNow(); -} + $wgDisableSearchUpdate = false; -if ( isset( $options['s'] ) ) { - $start = $options['s']; -} elseif( is_readable( 'searchUpdate.pos' ) ) { - # B/c to the old position file name which was hardcoded - # We can safely delete the file when we're done though. - $start = file_get_contents( 'searchUpdate.pos' ); - unlink( 'searchUpdate.pos' ); -} else { - $start = @file_get_contents( $posFile ); - if ( !$start ) { - $start = wfTimestamp( TS_MW, time() - 86400 ); - } -} + $dbw = wfGetDB( DB_MASTER ); + $recentchanges = $dbw->tableName( 'recentchanges' ); -if ( isset( $options['l'] ) ) { - $lockTime = $options['l']; -} else { - $lockTime = 20; -} + $this->output( "Updating searchindex between $start and $end\n" ); + + # Select entries from recentchanges which are on top and between the specified times + $start = $dbw->strencode( $start ); + $end = $dbw->strencode( $end ); -$quiet = (bool)(@$options['q']); + $page = $dbw->tableName( 'page' ); + $sql = "SELECT rc_cur_id,rc_type,rc_moved_to_ns,rc_moved_to_title FROM $recentchanges + JOIN $page ON rc_cur_id=page_id AND rc_this_oldid=page_latest + WHERE rc_timestamp BETWEEN '$start' AND '$end' + "; + $res = $dbw->query( $sql, __METHOD__ ); -updateSearchIndex( $start, $end, $lockTime, $quiet ); -$file = fopen( $posFile, 'w' ); -fwrite( $file, $end ); -fclose( $file ); + # Lock searchindex + if ( $maxLockTime ) { + $this->output( " --- Waiting for lock ---" ); + $this->lockSearchindex( $dbw ); + $lockTime = time(); + $this->output( "\n" ); + } + # Loop through the results and do a search update + while ( $row = $dbw->fetchObject( $res ) ) { + # Allow reads to be processed + if ( $maxLockTime && time() > $lockTime + $maxLockTime ) { + $this->output( " --- Relocking ---" ); + $this->relockSearchindex( $dbw ); + $lockTime = time(); + $this->output( "\n" ); + } + if ( $row->rc_type == RC_LOG ) { + continue; + } elseif ( $row->rc_type == RC_MOVE || $row->rc_type == RC_MOVE_OVER_REDIRECT ) { + # Rename searchindex entry + $titleObj = Title::makeTitle( $row->rc_moved_to_ns, $row->rc_moved_to_title ); + $title = $titleObj->getPrefixedDBkey(); + $this->output( "$title..." ); + $u = new SearchUpdate( $row->rc_cur_id, $title, false ); + $this->output( "\n" ); + } else { + // Get current revision + $rev = Revision::loadFromPageId( $dbw, $row->rc_cur_id ); + if( $rev ) { + $titleObj = $rev->getTitle(); + $title = $titleObj->getPrefixedDBkey(); + $this->output( $title ); + # Update searchindex + $u = new SearchUpdate( $row->rc_cur_id, $titleObj->getText(), $rev->getText() ); + $u->doUpdate(); + $this->output( "\n" ); + } + } + } + + # Unlock searchindex + if ( $maxLockTime ) { + $this->output( " --- Unlocking --" ); + $this->unlockSearchindex( $dbw ); + $this->output( "\n" ); + } + $this->output( "Done\n" ); + } + + /** + * Lock the search index + * @param &$db Database object + */ + private function lockSearchindex( &$db ) { + $write = array( 'searchindex' ); + $read = array( 'page', 'revision', 'text', 'interwiki' ); + $items = array(); + + foreach( $write as $table ) { + $items[] = $db->tableName( $table ) . ' LOW_PRIORITY WRITE'; + } + foreach( $read as $table ) { + $items[] = $db->tableName( $table ) . ' READ'; + } + $sql = "LOCK TABLES " . implode( ',', $items ); + $db->query( $sql, 'updateSearchIndex.php ' . __METHOD__ ); + } + + /** + * Unlock the tables + * @param &$db Database object + */ + private function unlockSearchindex( &$db ) { + $db->query( "UNLOCK TABLES", 'updateSearchIndex.php ' . __METHOD__ ); + } + + /** + * Unlock and lock again + * Since the lock is low-priority, queued reads will be able to complete + * @param &$db Database object + */ + private function relockSearchindex( &$db ) { + $this->unlockSearchindex( $db ); + $this->lockSearchindex( $db ); + } +} +$maintClass = "UpdateSearchIndex"; +require_once( DO_MAINTENANCE ); diff --git a/maintenance/updateSpecialPages.php b/maintenance/updateSpecialPages.php index 3eaa62056b..bbe4727518 100644 --- a/maintenance/updateSpecialPages.php +++ b/maintenance/updateSpecialPages.php @@ -6,114 +6,114 @@ * @file * @ingroup Maintenance */ -$options = array('only','help'); + +require_once( "Maintenance.php" ); -require_once( 'commandLine.inc' ); - -require_once( "$IP/includes/SpecialPage.php" ); -require_once( "$IP/includes/QueryPage.php" ); - -if(@$options['help']) { - print "usage:updateSpecialPages.php [--help] [--only=page]\n"; - print " --help : this help message\n"; - print " --list : list special pages names\n"; - print " --only=page : only update 'page'. Ex: --only=BrokenRedirects\n"; - print " --override : update even pages which have had updates disabled\n"; - wfDie(); -} - -$wgOut->disable(); -$dbw = wfGetDB( DB_MASTER ); - -foreach( $wgSpecialPageCacheUpdates as $special => $call ) { - if( !is_callable($call) ) { - print "Uncallable function $call!\n"; - continue; - } - $t1 = explode( ' ', microtime() ); - call_user_func( $call, $dbw ); - $t2 = explode( ' ', microtime() ); - printf( '%-30s ', $special ); - $elapsed = ($t2[0] - $t1[0]) + ($t2[1] - $t1[1]); - $hours = intval( $elapsed / 3600 ); - $minutes = intval( $elapsed % 3600 / 60 ); - $seconds = $elapsed - $hours * 3600 - $minutes * 60; - if ( $hours ) { - print $hours . 'h '; +class UpdateSpecialPages extends Maintenance { + public function __construct() { + parent::__construct(); + $this->addParam( 'list', 'List special page names' ); + $this->addParam( 'only', 'Only update "page". Ex: --only=BrokenRedirects', false, true ); + $this->addParam( 'override', 'Also update pages that have updates disabled' ); } - if ( $minutes ) { - print $minutes . 'm '; - } - printf( "completed in %.2fs\n", $seconds ); - # Wait for the slave to catch up - wfWaitForSlaves( 5 ); -} -foreach( $wgQueryPages as $page ) { - @list( $class, $special, $limit ) = $page; + public function execute() { + global $wgOut; + $wgOut->disable(); + $dbw = wfGetDB( DB_MASTER ); - # --list : just show the name of pages - if( @$options['list'] ) { - print "$special\n"; - continue; - } - - if ( !isset( $options['override'] ) && $wgDisableQueryPageUpdate && in_array( $special, $wgDisableQueryPageUpdate ) ) { - printf("%-30s disabled\n", $special); - continue; - } - - $specialObj = SpecialPage::getPage( $special ); - if ( !$specialObj ) { - print "No such special page: $special\n"; - exit; - } - if ( !class_exists( $class ) ) { - $file = $specialObj->getFile(); - require_once( $file ); - } - $queryPage = new $class; - - if( !isset($options['only']) or $options['only'] == $queryPage->getName() ) { - printf( '%-30s ', $special ); - if ( $queryPage->isExpensive() ) { + foreach( $wgSpecialPageCacheUpdates as $special => $call ) { + if( !is_callable($call) ) { + $this->error( "Uncallable function $call!\n" ); + continue; + } $t1 = explode( ' ', microtime() ); - # Do the query - $num = $queryPage->recache( $limit === null ? $wgQueryCacheLimit : $limit ); + call_user_func( $call, $dbw ); $t2 = explode( ' ', microtime() ); - if ( $num === false ) { - print "FAILED: database error\n"; - } else { - print "got $num rows in "; - - $elapsed = ($t2[0] - $t1[0]) + ($t2[1] - $t1[1]); - $hours = intval( $elapsed / 3600 ); - $minutes = intval( $elapsed % 3600 / 60 ); - $seconds = $elapsed - $hours * 3600 - $minutes * 60; - if ( $hours ) { - print $hours . 'h '; + $this->output( sprintf( '%-30s ', $special ) ); + $elapsed = ($t2[0] - $t1[0]) + ($t2[1] - $t1[1]); + $hours = intval( $elapsed / 3600 ); + $minutes = intval( $elapsed % 3600 / 60 ); + $seconds = $elapsed - $hours * 3600 - $minutes * 60; + if ( $hours ) { + $this->output( $hours . 'h ' ); + } + if ( $minutes ) { + $this->output( $minutes . 'm ' ); + } + $this->output( sprintf( "completed in %.2fs\n", $seconds ) ); + # Wait for the slave to catch up + wfWaitForSlaves( 5 ); + } + + foreach( $wgQueryPages as $page ) { + @list( $class, $special, $limit ) = $page; + + # --list : just show the name of pages + if( $this->hasOption('list') ) { + $this->output( "$special\n" ); + continue; + } + + if ( $this->hasOption('override') && $wgDisableQueryPageUpdate && in_array( $special, $wgDisableQueryPageUpdate ) ) { + $this->output( sprintf( "%-30s disabled\n", $special ) ); + continue; + } + + $specialObj = SpecialPage::getPage( $special ); + if ( !$specialObj ) { + $this->output( "No such special page: $special\n" ); + exit; + } + if ( !class_exists( $class ) ) { + $file = $specialObj->getFile(); + require_once( $file ); + } + $queryPage = new $class; + + if( !$this->hasOption('only') || $this->getOption('only') == $queryPage->getName() ) { + $this->output( sprintf( '%-30s ', $special ) ); + if ( $queryPage->isExpensive() ) { + $t1 = explode( ' ', microtime() ); + # Do the query + $num = $queryPage->recache( $limit === null ? $wgQueryCacheLimit : $limit ); + $t2 = explode( ' ', microtime() ); + if ( $num === false ) { + $this->output( "FAILED: database error\n" ); + } else { + $this->output( "got $num rows in " ); + + $elapsed = ($t2[0] - $t1[0]) + ($t2[1] - $t1[1]); + $hours = intval( $elapsed / 3600 ); + $minutes = intval( $elapsed % 3600 / 60 ); + $seconds = $elapsed - $hours * 3600 - $minutes * 60; + if ( $hours ) { + $this->output( $hours . 'h ' ); + } + if ( $minutes ) { + $this->output( $minutes . 'm ' ); + } + $this->output( sprintf( "%.2fs\n", $seconds ) ); } - if ( $minutes ) { - print $minutes . 'm '; + # Reopen any connections that have closed + if ( !wfGetLB()->pingAll()) { + $this->output( "\n" ); + do { + $this->error( "Connection failed, reconnecting in 10 seconds...\n" ); + sleep(10); + } while ( !wfGetLB()->pingAll() ); + $this->output( "Reconnected\n\n" ); + } else { + # Commit the results + $dbw->immediateCommit(); } - printf( "%.2fs\n", $seconds ); - } - # Reopen any connections that have closed - if ( !wfGetLB()->pingAll()) { - print "\n"; - do { - print "Connection failed, reconnecting in 10 seconds...\n"; - sleep(10); - } while ( !wfGetLB()->pingAll() ); - print "Reconnected\n\n"; - } else { - # Commit the results - $dbw->immediateCommit(); - } - # Wait for the slave to catch up - wfWaitForSlaves( 5 ); - } else { - print "cheap, skipped\n"; + # Wait for the slave to catch up + wfWaitForSlaves( 5 ); + } else { + $this->output( "cheap, skipped\n" ); + } + } } } } + diff --git a/maintenance/updaters.inc b/maintenance/updaters.inc index 4b77af4723..5d3d22ec69 100644 --- a/maintenance/updaters.inc +++ b/maintenance/updaters.inc @@ -1038,10 +1038,7 @@ function do_stats_init() { wfOut( "ok.\n" ); return; } - - global $IP; - require_once "$IP/maintenance/initStats.inc"; - wfInitStats(); + SiteStats::init( false ); } function do_active_users_init() { diff --git a/maintenance/waitForSlave.php b/maintenance/waitForSlave.php index 309d0e7d36..edeede88e2 100644 --- a/maintenance/waitForSlave.php +++ b/maintenance/waitForSlave.php @@ -5,11 +5,16 @@ * @ingroup Maintenance */ -require_once( "commandLine.inc" ); -if ( isset( $args[0] ) ) { - wfWaitForSlaves($args[0]); -} else { - wfWaitForSlaves(10); -} +require_once( "Maintenance.php" ); +class WaitForSlave extends Maintenance { + public function __construct() { + $this->addArgs( array( 'maxlag' ) ); + } + public function execute() { + wfWaitForSlaves( $this->getArg( 0, 10 ) ); + } +} +$maintClass = "WaitForSlave"; +require_once( DO_MAINTENANCE ); diff --git a/profileinfo.php b/profileinfo.php index fcce6d71c6..afc05eb5c0 100644 --- a/profileinfo.php +++ b/profileinfo.php @@ -4,7 +4,6 @@ ini_set( 'zlib.output_compression', 'off' ); $wgEnableProfileInfo = $wgProfileToDatabase = false; require_once( './includes/WebStart.php' ); -@include_once( './AdminSettings.php' ); ?> <!-- diff --git a/t/Search.inc b/t/Search.inc index 252293065a..a76acfd08b 100644 --- a/t/Search.inc +++ b/t/Search.inc @@ -7,11 +7,10 @@ require 't/Test.php'; require 'includes/Defines.php'; require 'includes/ProfilerStub.php'; require 'LocalSettings.php'; -require 'AdminSettings.php'; require 'includes/Setup.php'; function buildTestDatabase( $tables ) { - global $wgDBprefix, $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname, $wgDBtype; + global $wgDBprefix, $wgDBserver, $wgDBname, $wgDBtype; $oldPrefix = $wgDBprefix; $wgDBprefix = 'parsertest'; -- 2.20.1