diff options
Diffstat (limited to 'maintenance')
247 files changed, 6716 insertions, 13258 deletions
diff --git a/maintenance/7zip.inc b/maintenance/7zip.inc index 590cad23..751a1311 100644 --- a/maintenance/7zip.inc +++ b/maintenance/7zip.inc @@ -3,7 +3,7 @@ * 7z stream wrapper * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,6 +36,7 @@ class SevenZipStream { private function stripPath( $path ) { $prefix = 'mediawiki.compress.7z://'; + return substr( $path, strlen( $prefix ) ); } @@ -91,4 +92,5 @@ class SevenZipStream { return fseek( $this->stream, $offset, $whence ); } } + stream_wrapper_register( 'mediawiki.compress.7z', 'SevenZipStream' ); diff --git a/maintenance/Maintenance.php b/maintenance/Maintenance.php index 30e93c90..8d30df4c 100644 --- a/maintenance/Maintenance.php +++ b/maintenance/Maintenance.php @@ -49,7 +49,6 @@ $maintClass = false; * @ingroup Maintenance */ abstract class Maintenance { - /** * Constants for DB access type * @see Maintenance::getDbType() @@ -115,11 +114,11 @@ abstract class Maintenance { public $fileHandle; /** - * List of all the core maintenance scripts. This is added - * to scripts added by extensions in $wgMaintenanceScripts - * and returned by getMaintenanceScripts() + * Accessible via getConfig() + * + * @var Config */ - protected static $mCoreScripts = null; + private $config; /** * Default constructor. Children should call this *first* if implementing @@ -141,9 +140,16 @@ abstract class Maintenance { * as a standalone class? It checks that the call stack only includes this * function and "requires" (meaning was called from the file scope) * - * @return Boolean + * @return bool */ public static function shouldExecute() { + global $wgCommandLineMode; + + if ( !function_exists( 'debug_backtrace' ) ) { + // If someone has a better idea... + return $wgCommandLineMode; + } + $bt = debug_backtrace(); $count = count( $bt ); if ( $count < 2 ) { @@ -158,6 +164,7 @@ abstract class Maintenance { return false; // previous calls should all be "requires" } } + return true; } @@ -170,14 +177,22 @@ abstract class Maintenance { * 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? - * @param $shortName String: character to use as short name + * @param string $name The name of the param (help, version, etc) + * @param string $description The description of the param to show on --help + * @param bool $required Is the param required? + * @param bool $withArg Is an argument required with this option? + * @param string $shortName Character to use as short name */ - protected function addOption( $name, $description, $required = false, $withArg = false, $shortName = false ) { - $this->mParams[$name] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg, 'shortName' => $shortName ); + protected function addOption( $name, $description, $required = false, + $withArg = false, $shortName = false + ) { + $this->mParams[$name] = array( + 'desc' => $description, + 'require' => $required, + 'withArg' => $withArg, + 'shortName' => $shortName + ); + if ( $shortName !== false ) { $this->mShortParamsMap[$shortName] = $name; } @@ -185,8 +200,8 @@ abstract class Maintenance { /** * Checks to see if a particular param exists. - * @param $name String: the name of the param - * @return Boolean + * @param string $name The name of the param + * @return bool */ protected function hasOption( $name ) { return isset( $this->mOptions[$name] ); @@ -194,9 +209,9 @@ abstract class Maintenance { /** * 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 + * @param string $name The name of the param + * @param mixed $default Anything you want, default null + * @return mixed */ protected function getOption( $name, $default = null ) { if ( $this->hasOption( $name ) ) { @@ -204,15 +219,16 @@ abstract class Maintenance { } 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 - * @param $arg String: name of the arg, like 'start' - * @param $description String: short description of the arg - * @param $required Boolean: is this required? + * @param string $arg Name of the arg, like 'start' + * @param string $description Short description of the arg + * @param bool $required Is this required? */ protected function addArg( $arg, $description, $required = true ) { $this->mArgList[] = array( @@ -224,7 +240,7 @@ abstract class Maintenance { /** * Remove an option. Useful for removing options that won't be used in your script. - * @param $name String: the option to remove. + * @param string $name The option to remove. */ protected function deleteOption( $name ) { unset( $this->mParams[$name] ); @@ -232,7 +248,7 @@ abstract class Maintenance { /** * Set the description text. - * @param $text String: the text of the description + * @param string $text The text of the description */ protected function addDescription( $text ) { $this->mDescription = $text; @@ -240,8 +256,8 @@ abstract class Maintenance { /** * Does a given argument exist? - * @param $argId Integer: the integer value (from zero) for the arg - * @return Boolean + * @param int $argId The integer value (from zero) for the arg + * @return bool */ protected function hasArg( $argId = 0 ) { return isset( $this->mArgs[$argId] ); @@ -249,8 +265,8 @@ abstract class Maintenance { /** * Get an argument. - * @param $argId Integer: the integer value (from zero) for the arg - * @param $default Mixed: the default if it doesn't exist + * @param int $argId The integer value (from zero) for the arg + * @param mixed $default The default if it doesn't exist * @return mixed */ protected function getArg( $argId = 0, $default = null ) { @@ -259,7 +275,7 @@ abstract class Maintenance { /** * Set the batch size. - * @param $s Integer: the number of operations to do in a batch + * @param int $s The number of operations to do in a batch */ protected function setBatchSize( $s = 0 ) { $this->mBatchSize = $s; @@ -281,7 +297,7 @@ abstract class Maintenance { /** * Get the script's name - * @return String + * @return string */ public function getName() { return $this->mSelf; @@ -289,10 +305,9 @@ abstract class Maintenance { /** * Return input from stdin. - * @param $len Integer: the number of bytes to read. If null, - * just return the handle. Maintenance::STDIN_ALL returns - * the full length - * @return Mixed + * @param int $len The number of bytes to read. If null, just return the handle. + * Maintenance::STDIN_ALL returns the full length + * @return mixed */ protected function getStdin( $len = null ) { if ( $len == Maintenance::STDIN_ALL ) { @@ -304,6 +319,7 @@ abstract class Maintenance { } $input = fgets( $f, $len ); fclose( $f ); + return rtrim( $input ); } @@ -317,9 +333,8 @@ abstract class Maintenance { /** * Throw some output to the user. Scripts can call this with no fears, * as we handle all --quiet stuff here - * @param $out String: the text to show to the user - * @param $channel Mixed: unique identifier for the channel. See - * function outputChanneled. + * @param string $out The text to show to the user + * @param mixed $channel Unique identifier for the channel. See function outputChanneled. */ protected function output( $out, $channel = null ) { if ( $this->mQuiet ) { @@ -337,8 +352,8 @@ abstract class Maintenance { /** * 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 Int: if > 0, go ahead and die out using this int as the code + * @param string $err The error to display + * @param int $die If > 0, go ahead and die out using this int as the code */ protected function error( $err, $die = 0 ) { $this->outputChanneled( false ); @@ -370,13 +385,14 @@ abstract class Maintenance { * Message outputter with channeled message support. Messages on the * same channel are concatenated, but any intervening messages in another * channel start a new line. - * @param $msg String: the message without trailing newline - * @param $channel string Channel identifier or null for no + * @param string $msg The message without trailing newline + * @param string $channel Channel identifier or null for no * channel. Channel comparison uses ===. */ public function outputChanneled( $msg, $channel = null ) { if ( $msg === false ) { $this->cleanupChanneled(); + return; } @@ -404,7 +420,7 @@ abstract class Maintenance { * Maintenance::DB_NONE - For no DB access at all * Maintenance::DB_STD - For normal DB access, default * Maintenance::DB_ADMIN - For admin DB access - * @return Integer + * @return int */ public function getDbType() { return Maintenance::DB_STD; @@ -422,10 +438,14 @@ abstract class Maintenance { $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true ); $this->addOption( 'wiki', 'For specifying the wiki ID', false, true ); $this->addOption( 'globals', 'Output globals at the end of processing for debugging' ); - $this->addOption( 'memory-limit', 'Set a specific memory limit for the script, "max" for no limit or "default" to avoid changing it' ); + $this->addOption( + 'memory-limit', + 'Set a specific memory limit for the script, ' + . '"max" for no limit or "default" to avoid changing it' + ); $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " . - "http://en.wikipedia.org. This is sometimes necessary because " . - "server name detection may fail in command line scripts.", false, true ); + "http://en.wikipedia.org. This is sometimes necessary because " . + "server name detection may fail in command line scripts.", false, true ); $this->addOption( 'profiler', 'Set to "text" or "trace" to show profiling output', false, true ); # Save generic options to display them separately in help @@ -445,11 +465,31 @@ abstract class Maintenance { } /** + * @since 1.24 + * @return Config + */ + public function getConfig() { + if ( $this->config === null ) { + $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); + } + + return $this->config; + } + + /** + * @since 1.24 + * @param Config $config + */ + public function setConfig( Config $config ) { + $this->config = $config; + } + + /** * Run a child maintenance script. Pass all of the current arguments * to it. - * @param $maintClass String: a name of a child maintenance class - * @param $classFile String: full path of where the child is - * @return Maintenance child + * @param string $maintClass A name of a child maintenance class + * @param string $classFile Full path of where the child is + * @return Maintenance */ public function runChild( $maintClass, $classFile = null ) { // Make sure the class is loaded first @@ -470,6 +510,7 @@ abstract class Maintenance { if ( !is_null( $this->mDb ) ) { $child->setDB( $this->mDb ); } + return $child; } @@ -570,9 +611,9 @@ abstract class Maintenance { * $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 + * @param string $self The name of the script, if any + * @param array $opts An array of options, in form of key=>value + * @param array $args 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 @@ -594,6 +635,7 @@ abstract class Maintenance { # it's run again and again if ( $this->mInputLoaded ) { $this->loadSpecialVars(); + return; } @@ -639,8 +681,9 @@ abstract class Maintenance { } } elseif ( substr( $arg, 0, 1 ) == '-' ) { # Short options - for ( $p = 1; $p < strlen( $arg ); $p++ ) { - $option = $arg { $p }; + $argLength = strlen( $arg ); + for ( $p = 1; $p < $argLength; $p++ ) { + $option = $arg[$p]; if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) { $option = $this->mShortParamsMap[$option]; } @@ -715,14 +758,14 @@ abstract class Maintenance { /** * Maybe show the help. - * @param $force boolean Whether to force the help to show, default false + * @param bool $force Whether to force the help to show, default false */ protected function maybeHelp( $force = false ) { if ( !$force && !$this->hasOption( 'help' ) ) { return; } - $screenWidth = 80; // TODO: Caculate this! + $screenWidth = 80; // TODO: Calculate this! $tab = " "; $descWidth = $screenWidth - ( 2 * strlen( $tab ) ); @@ -766,7 +809,7 @@ abstract class Maintenance { } $this->output( wordwrap( "$tab--$par: " . $info['desc'], $descWidth, - "\n$tab$tab" ) . "\n" + "\n$tab$tab" ) . "\n" ); } $this->output( "\n" ); @@ -781,13 +824,12 @@ abstract class Maintenance { } $this->output( wordwrap( "$tab--$par: " . $info['desc'], $descWidth, - "\n$tab$tab" ) . "\n" + "\n$tab$tab" ) . "\n" ); } $this->output( "\n" ); } - // Script specific parameters not defined on construction by // Maintenance::addDefaultParams() $scriptSpecificParams = array_diff_key( @@ -806,7 +848,7 @@ abstract class Maintenance { } $this->output( wordwrap( "$tab--$par: " . $info['desc'], $descWidth, - "\n$tab$tab" ) . "\n" + "\n$tab$tab" ) . "\n" ); } $this->output( "\n" ); @@ -881,7 +923,12 @@ abstract class Maintenance { $this->afterFinalSetup(); $wgShowSQLErrors = true; + + // @codingStandardsIgnoreStart Allow error supppression. wfSuppressWarnings() + // is not avaiable. @set_time_limit( 0 ); + // @codingStandardsIgnoreStart + $this->adjustMemoryLimit(); // Per-script profiling; useful for debugging @@ -916,7 +963,7 @@ abstract class Maintenance { /** * Generic setup for most installs. Returns the location of LocalSettings - * @return String + * @return string */ public function loadSettings() { global $wgCommandLineMode, $IP; @@ -939,16 +986,17 @@ abstract class Maintenance { 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" . - "Use --conf to specify it.", true ); + "must exist and be readable in the source directory.\n" . + "Use --conf to specify it.", true ); } $wgCommandLineMode = true; + return $settingsFile; } /** * Support function for cleaning up redundant text records - * @param $delete Boolean: whether or not to actually delete the records + * @param bool $delete Whether or not to actually delete the records * @author Rob Church <robchur@gmail.com> */ public function purgeRedundantText( $delete = true ) { @@ -1009,52 +1057,6 @@ abstract class Maintenance { } /** - * Get the list of available maintenance scripts. Note - * that if you call this _before_ calling doMaintenance - * you won't have any extensions in it yet - * @return Array - */ - public static function getMaintenanceScripts() { - global $wgMaintenanceScripts; - return $wgMaintenanceScripts + self::getCoreScripts(); - } - - /** - * Return all of the core maintenance scripts - * @return array - */ - protected static function getCoreScripts() { - if ( !self::$mCoreScripts ) { - $paths = array( - __DIR__, - __DIR__ . '/language', - __DIR__ . '/storage', - ); - self::$mCoreScripts = array(); - foreach ( $paths as $p ) { - $handle = opendir( $p ); - while ( ( $file = readdir( $handle ) ) !== false ) { - if ( $file == 'Maintenance.php' ) { - continue; - } - $file = $p . '/' . $file; - if ( is_dir( $file ) || !strpos( $file, '.php' ) || - ( strpos( file_get_contents( $file ), '$maintClass' ) === false ) ) { - continue; - } - require $file; - $vars = get_defined_vars(); - if ( array_key_exists( 'maintClass', $vars ) ) { - self::$mCoreScripts[$vars['maintClass']] = $file; - } - } - closedir( $handle ); - } - } - return self::$mCoreScripts; - } - - /** * Returns a database to be used by current maintenance script. It can be set by setDB(). * If not set, wfGetDB() will be used. * This function has the same parameters as wfGetDB() @@ -1072,7 +1074,7 @@ abstract class Maintenance { /** * Sets database object to be returned by getDB(). * - * @param $db DatabaseBase: Database object to be used + * @param DatabaseBase $db Database object to be used */ public function setDB( &$db ) { $this->mDb = $db; @@ -1080,7 +1082,7 @@ abstract class Maintenance { /** * Lock the search index - * @param &$db DatabaseBase object + * @param DatabaseBase &$db */ private function lockSearchindex( &$db ) { $write = array( 'searchindex' ); @@ -1090,7 +1092,7 @@ abstract class Maintenance { /** * Unlock the tables - * @param &$db DatabaseBase object + * @param DatabaseBase &$db */ private function unlockSearchindex( &$db ) { $db->unlockTables( __CLASS__ . '::' . __METHOD__ ); @@ -1099,7 +1101,7 @@ abstract class Maintenance { /** * Unlock and lock again * Since the lock is low-priority, queued reads will be able to complete - * @param &$db DatabaseBase object + * @param DatabaseBase &$db */ private function relockSearchindex( &$db ) { $this->unlockSearchindex( $db ); @@ -1108,10 +1110,10 @@ abstract class Maintenance { /** * Perform a search index update with locking - * @param $maxLockTime Integer: the maximum time to keep the search index locked. - * @param $callback callback String: the function that will update the function. - * @param $dbw DatabaseBase object - * @param $results + * @param int $maxLockTime The maximum time to keep the search index locked. + * @param string $callback The function that will update the function. + * @param DatabaseBase $dbw + * @param array $results */ public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) { $lockTime = time(); @@ -1142,13 +1144,12 @@ abstract class Maintenance { $this->unlockSearchindex( $dbw ); $this->output( "\n" ); } - } /** * Update the searchindex table for a given pageid - * @param $dbw DatabaseBase a database write handle - * @param $pageId Integer: the page ID to update. + * @param DatabaseBase $dbw A database write handle + * @param int $pageId The page ID to update. * @return null|string */ public function updateSearchIndexForPage( $dbw, $pageId ) { @@ -1164,6 +1165,7 @@ abstract class Maintenance { $u->doUpdate(); $this->output( "\n" ); } + return $title; } @@ -1172,7 +1174,7 @@ abstract class Maintenance { * We default as considering stdin a tty (for nice readline methods) * but treating stout as not a tty to avoid color codes * - * @param $fd int File descriptor + * @param int $fd File descriptor * @return bool */ public static function posix_isatty( $fd ) { @@ -1185,8 +1187,8 @@ abstract class Maintenance { /** * Prompt the console for input - * @param $prompt String what to begin the line with, like '> ' - * @return String response + * @param string $prompt What to begin the line with, like '> ' + * @return string Response */ public static function readconsole( $prompt = '> ' ) { static $isatty = null; @@ -1210,14 +1212,15 @@ abstract class Maintenance { return false; } $resp = trim( $st ); + return $resp; } } /** * Emulate readline() - * @param $prompt String what to begin the line with, like '> ' - * @return String + * @param string $prompt What to begin the line with, like '> ' + * @return string */ private static function readlineEmulation( $prompt ) { $bash = Installer::locateExecutableInDefaultPaths( array( 'bash' ) ); @@ -1245,6 +1248,7 @@ abstract class Maintenance { return false; } print $prompt; + return fgets( STDIN, 1024 ); } } @@ -1254,6 +1258,7 @@ abstract class Maintenance { */ class FakeMaintenance extends Maintenance { protected $mSelf = "FakeMaintenanceScript"; + public function execute() { return; } @@ -1274,10 +1279,11 @@ abstract class LoggedUpdateMaintenance extends Maintenance { $db = $this->getDB( DB_MASTER ); $key = $this->getUpdateKey(); - if ( !$this->hasOption( 'force' ) && - $db->selectRow( 'updatelog', '1', array( 'ul_key' => $key ), __METHOD__ ) ) - { + if ( !$this->hasOption( 'force' ) + && $db->selectRow( 'updatelog', '1', array( 'ul_key' => $key ), __METHOD__ ) + ) { $this->output( "..." . $this->updateSkippedMessage() . "\n" ); + return true; } @@ -1285,44 +1291,45 @@ abstract class LoggedUpdateMaintenance extends Maintenance { return false; } - if ( - $db->insert( 'updatelog', array( 'ul_key' => $key ), __METHOD__, 'IGNORE' ) ) - { + if ( $db->insert( 'updatelog', array( 'ul_key' => $key ), __METHOD__, 'IGNORE' ) ) { return true; } else { $this->output( $this->updatelogFailedMessage() . "\n" ); + return false; } } /** * Message to show that the update was done already and was just skipped - * @return String + * @return string */ protected function updateSkippedMessage() { $key = $this->getUpdateKey(); + return "Update '{$key}' already logged as completed."; } /** * Message to show the the update log was unable to log the completion of this update - * @return String + * @return string */ protected function updatelogFailedMessage() { $key = $this->getUpdateKey(); + return "Unable to log update '{$key}' as completed."; } /** * Do the actual work. All child classes will need to implement this. * Return true to log the update as done or false (usually on failure). - * @return Bool + * @return bool */ abstract protected function doDBUpdates(); /** * Get the update key name to go in the update log table - * @return String + * @return string */ abstract protected function getUpdateKey(); } diff --git a/maintenance/README b/maintenance/README index 5cb6f5f5..30bb6adb 100644 --- a/maintenance/README +++ b/maintenance/README @@ -51,7 +51,7 @@ installations. edit.php Edit a page to change its content - findhooks.php + findHooks.php Find hooks that aren't documented in docs/hooks.txt importDump.php @@ -60,9 +60,6 @@ installations. importImages.php Import images into the wiki - importTextFile.php - Import the contents of a text file into a wiki page - moveBatch.php Move a batch of pages diff --git a/maintenance/archives/patch-drop-rc_cur_time.sql b/maintenance/archives/patch-drop-rc_cur_time.sql new file mode 100644 index 00000000..f1bc9e8b --- /dev/null +++ b/maintenance/archives/patch-drop-rc_cur_time.sql @@ -0,0 +1,2 @@ +-- rc_cur_time is no longer used, delete the field +ALTER TABLE /*$wgDBprefix*/recentchanges DROP COLUMN rc_cur_time;
\ No newline at end of file diff --git a/maintenance/archives/patch-fa_major_mime-chemical.sql b/maintenance/archives/patch-fa_major_mime-chemical.sql new file mode 100644 index 00000000..be9b0ff5 --- /dev/null +++ b/maintenance/archives/patch-fa_major_mime-chemical.sql @@ -0,0 +1,3 @@ +ALTER TABLE /*$wgDBprefix*/filearchive + CHANGE fa_major_mime fa_major_mime ENUM('unknown','application','audio','image','text','video','message','model','multipart','chemical'); + diff --git a/maintenance/archives/patch-hitcounter.sql b/maintenance/archives/patch-hitcounter.sql index c87c9592..2d698f68 100644 --- a/maintenance/archives/patch-hitcounter.sql +++ b/maintenance/archives/patch-hitcounter.sql @@ -6,4 +6,4 @@ CREATE TABLE /*$wgDBprefix*/hitcounter ( hc_id INTEGER UNSIGNED NOT NULL -) ENGINE=HEAP MAX_ROWS=25000; +) ENGINE=MEMORY MAX_ROWS=25000; diff --git a/maintenance/archives/patch-il_from_namespace.sql b/maintenance/archives/patch-il_from_namespace.sql new file mode 100644 index 00000000..4c858f44 --- /dev/null +++ b/maintenance/archives/patch-il_from_namespace.sql @@ -0,0 +1,4 @@ +ALTER TABLE /*_*/imagelinks + ADD COLUMN il_from_namespace int NOT NULL default 0; + +CREATE INDEX /*i*/il_backlinks_namespace ON /*_*/imagelinks (il_to,il_from_namespace,il_from);
\ No newline at end of file diff --git a/maintenance/archives/patch-img_major_mime-chemical.sql b/maintenance/archives/patch-img_major_mime-chemical.sql new file mode 100644 index 00000000..4bde446e --- /dev/null +++ b/maintenance/archives/patch-img_major_mime-chemical.sql @@ -0,0 +1,3 @@ +ALTER TABLE /*$wgDBprefix*/image + CHANGE img_major_mime img_major_mime ENUM('unknown','application','audio','image','text','video','message','model','multipart','chemical'); + diff --git a/maintenance/archives/patch-logging_user_text_time_index.sql b/maintenance/archives/patch-logging_user_text_time_index.sql new file mode 100644 index 00000000..06f29861 --- /dev/null +++ b/maintenance/archives/patch-logging_user_text_time_index.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/log_user_text_time ON /*_*/logging (log_user_text, log_timestamp); diff --git a/maintenance/archives/patch-logging_user_text_type_time_index.sql b/maintenance/archives/patch-logging_user_text_type_time_index.sql new file mode 100644 index 00000000..2801bc86 --- /dev/null +++ b/maintenance/archives/patch-logging_user_text_type_time_index.sql @@ -0,0 +1 @@ +CREATE INDEX /*i*/log_user_text_type_time ON /*_*/logging (log_user_text, log_type, log_timestamp); diff --git a/maintenance/archives/patch-mimesearch-indexes.sql b/maintenance/archives/patch-mimesearch-indexes.sql index bd348c46..8d9426ea 100644 --- a/maintenance/archives/patch-mimesearch-indexes.sql +++ b/maintenance/archives/patch-mimesearch-indexes.sql @@ -1,4 +1,4 @@ --- Add indexes to the mime types in image for use on Special:MIMEsearch, +-- Add indexes to the MIME types in image for use on Special:MIMEsearch, -- changes a query like -- -- SELECT img_name FROM image WHERE img_major_mime = "image" AND img_minor_mime = "svg"; diff --git a/maintenance/archives/patch-oi_major_mime-chemical.sql b/maintenance/archives/patch-oi_major_mime-chemical.sql new file mode 100644 index 00000000..e3b4552d --- /dev/null +++ b/maintenance/archives/patch-oi_major_mime-chemical.sql @@ -0,0 +1,3 @@ +ALTER TABLE /*$wgDBprefix*/oldimage + CHANGE oi_major_mime oi_major_mime ENUM('unknown','application','audio','image','text','video','message','model','multipart','chemical'); + diff --git a/maintenance/archives/patch-page_lang.sql b/maintenance/archives/patch-page_lang.sql new file mode 100644 index 00000000..c792b4ad --- /dev/null +++ b/maintenance/archives/patch-page_lang.sql @@ -0,0 +1,2 @@ +ALTER TABLE /*$wgDBprefix*/page + ADD page_lang varbinary(35) DEFAULT NULL; diff --git a/maintenance/archives/patch-page_links_updated.sql b/maintenance/archives/patch-page_links_updated.sql new file mode 100644 index 00000000..18d9e2d9 --- /dev/null +++ b/maintenance/archives/patch-page_links_updated.sql @@ -0,0 +1,2 @@ +ALTER TABLE /*$wgDBprefix*/page + ADD page_links_updated varbinary(14) NULL default NULL; diff --git a/maintenance/archives/patch-pl_from_namespace.sql b/maintenance/archives/patch-pl_from_namespace.sql new file mode 100644 index 00000000..2f7ff046 --- /dev/null +++ b/maintenance/archives/patch-pl_from_namespace.sql @@ -0,0 +1,4 @@ +ALTER TABLE /*_*/pagelinks + ADD COLUMN pl_from_namespace int NOT NULL default 0; + +CREATE INDEX /*i*/pl_backlinks_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from_namespace,pl_from); diff --git a/maintenance/archives/patch-pp_sortkey.sql b/maintenance/archives/patch-pp_sortkey.sql new file mode 100644 index 00000000..b13b6055 --- /dev/null +++ b/maintenance/archives/patch-pp_sortkey.sql @@ -0,0 +1,8 @@ +-- Add a 'sortkey' field to page_props so pages can be efficiently +-- queried by the numeric value of a property. + +ALTER TABLE /*_*/page_props + ADD pp_sortkey float DEFAULT NULL; + +CREATE UNIQUE INDEX /*i*/pp_propname_sortkey_page + ON /*_*/page_props ( pp_propname, pp_sortkey, pp_page ); diff --git a/maintenance/archives/patch-profiling.sql b/maintenance/archives/patch-profiling.sql index 0a0e4e1a..6ad16224 100644 --- a/maintenance/archives/patch-profiling.sql +++ b/maintenance/archives/patch-profiling.sql @@ -7,6 +7,6 @@ CREATE TABLE /*_*/profiling ( pf_memory float NOT NULL default 0, pf_name varchar(255) NOT NULL default '', pf_server varchar(30) NOT NULL default '' -) ENGINE=HEAP; +) ENGINE=MEMORY; CREATE UNIQUE INDEX /*i*/pf_name_server ON /*_*/profiling (pf_name, pf_server);
\ No newline at end of file diff --git a/maintenance/archives/patch-rc_source.sql b/maintenance/archives/patch-rc_source.sql new file mode 100644 index 00000000..7dedd745 --- /dev/null +++ b/maintenance/archives/patch-rc_source.sql @@ -0,0 +1,16 @@ +-- first step of migrating recentchanges rc_type to rc_source +ALTER TABLE /*$wgDBprefix*/recentchanges + ADD rc_source varbinary(16) NOT NULL default ''; + +-- Populate rc_source field with the data from rc_type +-- Large wiki's might prefer the PopulateRecentChangeSource maintenance +-- script to batch updates into groups rather than all at once. +UPDATE /*$wgDBprefix*/recentchanges + SET rc_source = CASE + WHEN rc_type = 0 THEN 'mw.edit' + WHEN rc_type = 1 THEN 'mw.new' + WHEN rc_type = 3 THEN 'mw.log' + WHEN rc_type = 5 THEN 'mw.external' + ELSE '' + END +WHERE rc_source = ''; diff --git a/maintenance/archives/patch-restructure.sql b/maintenance/archives/patch-restructure.sql deleted file mode 100644 index a5bc3e52..00000000 --- a/maintenance/archives/patch-restructure.sql +++ /dev/null @@ -1,146 +0,0 @@ --- The Great Restructuring of October 2004 --- Creates 'page', 'revision' tables and transforms the classic --- cur+old into a separate page+revision+text structure. --- --- The pre-conversion 'old' table is renamed to 'text' and used --- without internal restructuring to avoid rebuilding the entire --- table. (This can be done separately if desired.) --- --- The pre-conversion 'cur' table is now redundant and can be --- discarded when done. - -CREATE TABLE /*$wgDBprefix*/page ( - page_id int unsigned NOT NULL auto_increment, - page_namespace tinyint NOT NULL, - page_title varchar(255) binary NOT NULL, - page_restrictions tinyblob NOT NULL, - page_counter bigint unsigned NOT NULL default '0', - page_is_redirect tinyint unsigned NOT NULL default '0', - page_is_new tinyint unsigned NOT NULL default '0', - page_random real unsigned NOT NULL, - page_touched binary(14) NOT NULL default '', - page_latest int unsigned NOT NULL, - page_len int unsigned NOT NULL, - - PRIMARY KEY page_id (page_id), - UNIQUE INDEX name_title (page_namespace,page_title), - INDEX (page_random), - INDEX (page_len) -); - -CREATE TABLE /*$wgDBprefix*/revision ( - rev_id int unsigned NOT NULL auto_increment, - rev_page int unsigned NOT NULL, - rev_comment tinyblob NOT NULL, - rev_user int unsigned NOT NULL default '0', - rev_user_text varchar(255) binary NOT NULL default '', - rev_timestamp binary(14) NOT NULL default '', - rev_minor_edit tinyint unsigned NOT NULL default '0', - rev_deleted tinyint unsigned NOT NULL default '0', - - PRIMARY KEY rev_page_id (rev_page, rev_id), - UNIQUE INDEX rev_id (rev_id), - INDEX rev_timestamp (rev_timestamp), - INDEX page_timestamp (rev_page,rev_timestamp), - INDEX user_timestamp (rev_user,rev_timestamp), - INDEX usertext_timestamp (rev_user_text,rev_timestamp) -); - --- If creating new 'text' table it would look like this: --- --- CREATE TABLE /*$wgDBprefix*/text ( --- old_id int(8) unsigned NOT NULL auto_increment, --- old_text mediumtext NOT NULL, --- old_flags tinyblob NOT NULL, --- --- PRIMARY KEY old_id (old_id) --- ); - - --- Lock! -LOCK TABLES /*$wgDBprefix*/page WRITE, /*$wgDBprefix*/revision WRITE, /*$wgDBprefix*/old WRITE, /*$wgDBprefix*/cur WRITE; - --- Save the last old_id value for later -SELECT (@maxold:=MAX(old_id)) FROM /*$wgDBprefix*/old; - --- First, copy all current entries into the old table. -INSERT - INTO /*$wgDBprefix*/old - (old_namespace, - old_title, - old_text, - old_comment, - old_user, - old_user_text, - old_timestamp, - old_minor_edit, - old_flags) - SELECT - cur_namespace, - cur_title, - cur_text, - cur_comment, - cur_user, - cur_user_text, - cur_timestamp, - cur_minor_edit, - '' - FROM /*$wgDBprefix*/cur; - --- Now, copy all old data except the text into revisions -INSERT - INTO /*$wgDBprefix*/revision - (rev_id, - rev_page, - rev_comment, - rev_user, - rev_user_text, - rev_timestamp, - rev_minor_edit) - SELECT - old_id, - cur_id, - old_comment, - old_user, - old_user_text, - old_timestamp, - old_minor_edit - FROM /*$wgDBprefix*/old,/*$wgDBprefix*/cur - WHERE old_namespace=cur_namespace - AND old_title=cur_title; - --- And, copy the cur data into page -INSERT - INTO /*$wgDBprefix*/page - (page_id, - page_namespace, - page_title, - page_restrictions, - page_counter, - page_is_redirect, - page_is_new, - page_random, - page_touched, - page_latest) - SELECT - cur_id, - cur_namespace, - cur_title, - cur_restrictions, - cur_counter, - cur_is_redirect, - cur_is_new, - cur_random, - cur_touched, - rev_id - FROM /*$wgDBprefix*/cur,/*$wgDBprefix*/revision - WHERE cur_id=rev_page - AND rev_timestamp=cur_timestamp - AND rev_id > @maxold; - -UNLOCK TABLES; - --- Keep the old table around as the text store. --- Its extra fields will be ignored, but trimming them is slow --- so we won't bother doing it for now. -ALTER TABLE /*$wgDBprefix*/old RENAME TO /*$wgDBprefix*/text; diff --git a/maintenance/archives/patch-tl_from_namespace.sql b/maintenance/archives/patch-tl_from_namespace.sql new file mode 100644 index 00000000..8d6c76b8 --- /dev/null +++ b/maintenance/archives/patch-tl_from_namespace.sql @@ -0,0 +1,4 @@ +ALTER TABLE /*_*/templatelinks + ADD COLUMN tl_from_namespace int NOT NULL default 0; + +CREATE INDEX /*i*/tl_backlinks_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from_namespace,tl_from); diff --git a/maintenance/archives/patch-uploadstash.sql b/maintenance/archives/patch-uploadstash.sql index 14eaeab0..c1d93ef3 100644 --- a/maintenance/archives/patch-uploadstash.sql +++ b/maintenance/archives/patch-uploadstash.sql @@ -26,10 +26,10 @@ CREATE TABLE /*_*/uploadstash ( us_status varchar(50) not null, - -- file properties from File::getPropsFromPath. these may prove unnecessary. + -- file properties from FSFile::getProps(). these may prove unnecessary. -- us_size int unsigned NOT NULL, - -- this hash comes from File::sha1Base36(), and is 31 characters + -- this hash comes from FSFile::getSha1Base36(), and is 31 characters us_sha1 varchar(31) NOT NULL, us_mime varchar(255), -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table diff --git a/maintenance/archives/patch-user_password_expire.sql b/maintenance/archives/patch-user_password_expire.sql new file mode 100644 index 00000000..3e716d33 --- /dev/null +++ b/maintenance/archives/patch-user_password_expire.sql @@ -0,0 +1,3 @@ +-- For setting a password expiration date for users +ALTER TABLE /*$wgDBprefix*/user + ADD COLUMN user_password_expires varbinary(14) DEFAULT NULL; diff --git a/maintenance/archives/patch-val_ip.sql b/maintenance/archives/patch-val_ip.sql deleted file mode 100644 index 9214218d..00000000 --- a/maintenance/archives/patch-val_ip.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Column added 2005-05-24 - -ALTER TABLE /*$wgDBprefix*/validate - ADD COLUMN val_ip varchar(20) NOT NULL default ''; diff --git a/maintenance/archives/patch-validate.sql b/maintenance/archives/patch-validate.sql deleted file mode 100644 index 9701083c..00000000 --- a/maintenance/archives/patch-validate.sql +++ /dev/null @@ -1,13 +0,0 @@ --- For article validation - -DROP TABLE IF EXISTS /*$wgDBprefix*/validate; -CREATE TABLE /*$wgDBprefix*/validate ( - `val_user` int(11) NOT NULL default '0', - `val_page` int(11) unsigned NOT NULL default '0', - `val_revision` int(11) unsigned NOT NULL default '0', - `val_type` int(11) unsigned NOT NULL default '0', - `val_value` int(11) default '0', - `val_comment` varchar(255) NOT NULL default '', - `val_ip` varchar(20) NOT NULL default '', - KEY `val_user` (`val_user`,`val_revision`) -) /*$wgDBTableOptions*/; diff --git a/maintenance/archives/patch-watchlist-user-notificationtimestamp-index.sql b/maintenance/archives/patch-watchlist-user-notificationtimestamp-index.sql new file mode 100644 index 00000000..22ae44f1 --- /dev/null +++ b/maintenance/archives/patch-watchlist-user-notificationtimestamp-index.sql @@ -0,0 +1,4 @@ +-- +-- Creates the wl_user_notificationtimestamp index for the watchlist table +-- +CREATE INDEX /*i*/wl_user_notificationtimestamp ON /*_*/watchlist (wl_user, wl_notificationtimestamp); diff --git a/maintenance/archives/upgradeLogging.php b/maintenance/archives/upgradeLogging.php index 0749bbf6..aeadc93d 100644 --- a/maintenance/archives/upgradeLogging.php +++ b/maintenance/archives/upgradeLogging.php @@ -52,6 +52,7 @@ class UpdateLogging { if ( $this->dbw->tableExists( 'logging_pre_1_10' ) ) { echo "This script has already been run to completion\n"; + return; } @@ -124,6 +125,8 @@ EOT; /** * Copy all rows from $srcTable to $dstTable + * @param string $srcTable + * @param string $dstTable */ function sync( $srcTable, $dstTable ) { $batchSize = 1000; @@ -158,7 +161,7 @@ EOT; $srcRes = $this->dbw->select( $srcTable, '*', $conds, __METHOD__, array( 'LIMIT' => $batchSize, 'ORDER BY' => 'log_timestamp' ) ); - if ( ! $srcRes->numRows() ) { + if ( !$srcRes->numRows() ) { # All done break; } @@ -205,6 +208,7 @@ EOT; } } } + return $numRowsCopied; } } diff --git a/maintenance/attachLatest.php b/maintenance/attachLatest.php index 2cf277fe..fba6b92d 100644 --- a/maintenance/attachLatest.php +++ b/maintenance/attachLatest.php @@ -3,7 +3,7 @@ * Corrects wrong values in the `page_latest` field in the database. * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,19 +33,24 @@ require_once __DIR__ . '/Maintenance.php'; * @ingroup Maintenance */ class AttachLatest extends Maintenance { - public function __construct() { parent::__construct(); $this->addOption( "fix", "Actually fix the entries, will dry run otherwise" ); + $this->addOption( "regenerate-all", + "Regenerate the page_latest field for all records in table page" ); $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 ); + $conds = array( 'page_latest' => 0 ); + if ( $this->hasOption( 'regenerate-all' ) ) { + $conds = ''; + } $result = $dbw->select( 'page', array( 'page_id', 'page_namespace', 'page_title' ), - array( 'page_latest' => 0 ), + $conds, __METHOD__ ); $n = 0; @@ -64,7 +69,8 @@ class AttachLatest extends Maintenance { $revision = Revision::loadFromTimestamp( $dbw, $title, $latestTime ); if ( is_null( $revision ) ) { - $this->output( wfWikiID() . " $pageId [[$name]] latest time $latestTime, can't find revision id\n" ); + $this->output( wfWikiID() + . " $pageId [[$name]] latest time $latestTime, can't find revision id\n" ); continue; } $id = $revision->getId(); diff --git a/maintenance/backup.inc b/maintenance/backup.inc index 3dc94c88..222c538b 100644 --- a/maintenance/backup.inc +++ b/maintenance/backup.inc @@ -3,7 +3,7 @@ * Base classes for database dumpers * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,28 +37,30 @@ class DumpDBZip2Output extends DumpPipeOutput { * @ingroup Dump Maintenance */ class BackupDumper { - var $reportingInterval = 100; - var $reporting = true; - var $pageCount = 0; - var $revCount = 0; - var $server = null; // use default - var $pages = null; // all pages - var $skipHeader = false; // don't output <mediawiki> and <siteinfo> - var $skipFooter = false; // don't output </mediawiki> - var $startId = 0; - var $endId = 0; - var $revStartId = 0; - var $revEndId = 0; - var $sink = null; // Output filters - var $stubText = false; // include rev_text_id instead of text; for 2-pass dump - var $dumpUploads = false; - var $dumpUploadFileContents = false; - var $lastTime = 0; - var $pageCountLast = 0; - var $revCountLast = 0; - var $ID = 0; - - var $outputTypes = array(), $filterTypes = array(); + public $reporting = true; + public $pages = null; // all pages + public $skipHeader = false; // don't output <mediawiki> and <siteinfo> + public $skipFooter = false; // don't output </mediawiki> + public $startId = 0; + public $endId = 0; + public $revStartId = 0; + public $revEndId = 0; + public $dumpUploads = false; + public $dumpUploadFileContents = false; + + protected $reportingInterval = 100; + protected $pageCount = 0; + protected $revCount = 0; + protected $server = null; // use default + protected $sink = null; // Output filters + protected $lastTime = 0; + protected $pageCountLast = 0; + protected $revCountLast = 0; + + protected $outputTypes = array(); + protected $filterTypes = array(); + + protected $ID = 0; /** * The dependency-injected database to use. @@ -69,11 +71,12 @@ class BackupDumper { */ protected $forcedDb = null; - /** - * @var LoadBalancer - */ + /** @var LoadBalancer */ protected $lb; + // @todo Unused? + private $stubText = false; // include rev_text_id instead of text; for 2-pass dump + function __construct( $args ) { $this->stderr = fopen( "php://stderr", "wt" ); @@ -92,16 +95,16 @@ class BackupDumper { } /** - * @param $name String - * @param $class String: name of output filter plugin class + * @param string $name + * @param string $class Name of output filter plugin class */ function registerOutput( $name, $class ) { $this->outputTypes[$name] = $class; } /** - * @param $name String - * @param $class String: name of filter plugin class + * @param string $name + * @param string $class Name of filter plugin class */ function registerFilter( $name, $class ) { $this->filterTypes[$name] = $class; @@ -110,9 +113,9 @@ class BackupDumper { /** * Load a plugin and register it * - * @param $class String: name of plugin class; must have a static 'register' - * method that takes a BackupDumper as a parameter. - * @param $file String: full or relative path to the PHP file to load, or empty + * @param string $class Name of plugin class; must have a static 'register' + * method that takes a BackupDumper as a parameter. + * @param string $file Full or relative path to the PHP file to load, or empty */ function loadPlugin( $class, $file ) { if ( $file != '' ) { @@ -123,8 +126,8 @@ class BackupDumper { } /** - * @param $args Array - * @return Array + * @param array $args + * @return array */ function processArgs( $args ) { $sink = null; @@ -132,50 +135,53 @@ class BackupDumper { foreach ( $args as $arg ) { $matches = array(); if ( preg_match( '/^--(.+?)(?:=(.+?)(?::(.+?))?)?$/', $arg, $matches ) ) { - @list( /* $full */ , $opt, $val, $param ) = $matches; + wfSuppressWarnings(); + list( /* $full */, $opt, $val, $param ) = $matches; + wfRestoreWarnings(); + switch ( $opt ) { - case "plugin": - $this->loadPlugin( $val, $param ); - break; - case "output": - if ( !is_null( $sink ) ) { - $sinks[] = $sink; - } - if ( !isset( $this->outputTypes[$val] ) ) { - $this->fatalError( "Unrecognized output sink type '$val'" ); - } - $type = $this->outputTypes[$val]; - $sink = new $type( $param ); - break; - case "filter": - if ( is_null( $sink ) ) { - $sink = new DumpOutput(); - } - if ( !isset( $this->filterTypes[$val] ) ) { - $this->fatalError( "Unrecognized filter type '$val'" ); - } - $type = $this->filterTypes[$val]; - $filter = new $type( $sink, $param ); - - // references are lame in php... - unset( $sink ); - $sink = $filter; - - break; - case "report": - $this->reportingInterval = intval( $val ); - break; - case "server": - $this->server = $val; - break; - case "force-normal": - if ( !function_exists( 'utf8_normalize' ) ) { - $this->fatalError( "UTF-8 normalization extension not loaded. " . - "Install or remove --force-normal parameter to use slower code." ); - } - break; - default: - $this->processOption( $opt, $val, $param ); + case "plugin": + $this->loadPlugin( $val, $param ); + break; + case "output": + if ( !is_null( $sink ) ) { + $sinks[] = $sink; + } + if ( !isset( $this->outputTypes[$val] ) ) { + $this->fatalError( "Unrecognized output sink type '$val'" ); + } + $type = $this->outputTypes[$val]; + $sink = new $type( $param ); + break; + case "filter": + if ( is_null( $sink ) ) { + $sink = new DumpOutput(); + } + if ( !isset( $this->filterTypes[$val] ) ) { + $this->fatalError( "Unrecognized filter type '$val'" ); + } + $type = $this->filterTypes[$val]; + $filter = new $type( $sink, $param ); + + // references are lame in php... + unset( $sink ); + $sink = $filter; + + break; + case "report": + $this->reportingInterval = intval( $val ); + break; + case "server": + $this->server = $val; + break; + case "force-normal": + if ( !function_exists( 'utf8_normalize' ) ) { + $this->fatalError( "UTF-8 normalization extension not loaded. " . + "Install or remove --force-normal parameter to use slower code." ); + } + break; + default: + $this->processOption( $opt, $val, $param ); } } } @@ -223,8 +229,8 @@ class BackupDumper { } else { $exporter->allLogs(); } - # Page dumps: all or by page ID range } elseif ( is_null( $this->pages ) ) { + # Page dumps: all or by page ID range if ( $this->startId || $this->endId ) { $exporter->pagesByRange( $this->startId, $this->endId ); } elseif ( $this->revStartId || $this->revEndId ) { @@ -232,8 +238,8 @@ class BackupDumper { } else { $exporter->allPages(); } - # Dump of specific pages } else { + # Dump of specific pages $exporter->pagesByName( $this->pages ); } @@ -248,7 +254,7 @@ class BackupDumper { * Initialise starting time and maximum revision count. * We'll make ETA calculations based an progress, assuming relatively * constant per-revision rate. - * @param $history Integer: WikiExporter::CURRENT or WikiExporter::FULL + * @param int $history WikiExporter::CURRENT or WikiExporter::FULL */ function initProgress( $history = WikiExporter::FULL ) { $table = ( $history == WikiExporter::CURRENT ) ? 'page' : 'revision'; @@ -276,7 +282,7 @@ class BackupDumper { } $this->lb = wfGetLBFactory()->newMainLB(); - $db = $this->lb->getConnection( DB_SLAVE, 'backup' ); + $db = $this->lb->getConnection( DB_SLAVE, 'dump' ); // Discourage the server from disconnecting us if it takes a long time // to read out the big ol' batch query. @@ -289,9 +295,8 @@ class BackupDumper { * Force the dump to use the provided database connection for database * operations, wherever possible. * - * @param $db DatabaseBase|null: (Optional) the database connection to - * use. If null, resort to use the globally provided ways to - * get database connections. + * @param DatabaseBase|null $db (Optional) the database connection to use. If null, resort to + * use the globally provided ways to get database connections. */ function setDb( DatabaseBase $db = null ) { $this->forcedDb = $db; @@ -305,6 +310,7 @@ class BackupDumper { function backupServer() { global $wgDBserver; + return $this->server ? $this->server : $wgDBserver; @@ -352,8 +358,13 @@ class BackupDumper { $pageRatePart = '-'; $revRatePart = '-'; } - $this->progress( sprintf( "%s: %s (ID %d) %d pages (%0.1f|%0.1f/sec all|curr), %d revs (%0.1f|%0.1f/sec all|curr), ETA %s [max %d]", - $now, wfWikiID(), $this->ID, $this->pageCount, $pageRate, $pageRatePart, $this->revCount, $revRate, $revRatePart, $etats, $this->maxCount ) ); + $this->progress( sprintf( + "%s: %s (ID %d) %d pages (%0.1f|%0.1f/sec all|curr), " + . "%d revs (%0.1f|%0.1f/sec all|curr), ETA %s [max %d]", + $now, wfWikiID(), $this->ID, $this->pageCount, $pageRate, + $pageRatePart, $this->revCount, $revRate, $revRatePart, $etats, + $this->maxCount + ) ); $this->lastTime = $nowts; $this->revCountLast = $this->revCount; } diff --git a/maintenance/backupPrefetch.inc b/maintenance/backupPrefetch.inc index 04352b9b..7bfb7345 100644 --- a/maintenance/backupPrefetch.inc +++ b/maintenance/backupPrefetch.inc @@ -3,7 +3,7 @@ * Helper class for the --prefetch option of dumpTextPass.php * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,21 +40,20 @@ * @ingroup Maintenance */ class BaseDump { - var $reader = null; - var $atEnd = false; - var $atPageEnd = false; - var $lastPage = 0; - var $lastRev = 0; - var $infiles = null; - - function BaseDump( $infile ) { + protected $reader = null; + protected $atEnd = false; + protected $atPageEnd = false; + protected $lastPage = 0; + protected $lastRev = 0; + protected $infiles = null; + + public function __construct( $infile ) { $this->infiles = explode( ';', $infile ); $this->reader = new XMLReader(); $infile = array_shift( $this->infiles ); if ( defined( 'LIBXML_PARSEHUGE' ) ) { $this->reader->open( $infile, null, LIBXML_PARSEHUGE ); - } - else { + } else { $this->reader->open( $infile ); } } @@ -64,9 +63,9 @@ class BaseDump { * from the dump stream. May return null if the page is * unavailable. * - * @param $page Integer: ID number of page to read - * @param $rev Integer: ID number of revision to read - * @return string or null + * @param int $page ID number of page to read + * @param int $rev ID number of revision to read + * @return string|null */ function prefetch( $page, $rev ) { $page = intval( $page ); @@ -76,18 +75,24 @@ class BaseDump { $this->nextPage(); } if ( $this->lastPage > $page || $this->atEnd ) { - $this->debug( "BaseDump::prefetch already past page $page looking for rev $rev [$this->lastPage, $this->lastRev]" ); + $this->debug( "BaseDump::prefetch already past page $page " + . "looking for rev $rev [$this->lastPage, $this->lastRev]" ); + return null; } while ( $this->lastRev < $rev && !$this->atEnd && !$this->atPageEnd ) { - $this->debug( "BaseDump::prefetch at page $this->lastPage, rev $this->lastRev, looking for $page, $rev" ); + $this->debug( "BaseDump::prefetch at page $this->lastPage, rev $this->lastRev, " + . "looking for $page, $rev" ); $this->nextRev(); } if ( $this->lastRev == $rev && !$this->atEnd ) { $this->debug( "BaseDump::prefetch hit on $page, $rev [$this->lastPage, $this->lastRev]" ); + return $this->nextText(); } else { - $this->debug( "BaseDump::prefetch already past rev $rev on page $page [$this->lastPage, $this->lastRev]" ); + $this->debug( "BaseDump::prefetch already past rev $rev on page $page " + . "[$this->lastPage, $this->lastRev]" ); + return null; } } @@ -137,13 +142,14 @@ class BaseDump { */ function nextText() { $this->skipTo( 'text' ); + return strval( $this->nodeContents() ); } /** * @access private - * @param $name string - * @param $parent string + * @param string $name + * @param string $parent * @return bool|null */ function skipTo( $name, $parent = 'page' ) { @@ -151,16 +157,20 @@ class BaseDump { return false; } while ( $this->reader->read() ) { - if ( $this->reader->nodeType == XMLReader::ELEMENT && - $this->reader->name == $name ) { + if ( $this->reader->nodeType == XMLReader::ELEMENT + && $this->reader->name == $name + ) { return true; } - if ( $this->reader->nodeType == XMLReader::END_ELEMENT && - $this->reader->name == $parent ) { + if ( $this->reader->nodeType == XMLReader::END_ELEMENT + && $this->reader->name == $parent + ) { $this->debug( "BaseDump::skipTo found </$parent> searching for <$name>" ); + return false; } } + return $this->close(); } @@ -169,7 +179,7 @@ class BaseDump { * Fetches text contents of the current element, assuming * no sub-elements or such scary things. * - * @return String + * @return string * @access private */ function nodeContents() { @@ -182,15 +192,16 @@ class BaseDump { $buffer = ""; while ( $this->reader->read() ) { switch ( $this->reader->nodeType ) { - case XMLReader::TEXT: -// case XMLReader::WHITESPACE: - case XMLReader::SIGNIFICANT_WHITESPACE: - $buffer .= $this->reader->value; - break; - case XMLReader::END_ELEMENT: - return $buffer; + case XMLReader::TEXT: + //case XMLReader::WHITESPACE: + case XMLReader::SIGNIFICANT_WHITESPACE: + $buffer .= $this->reader->value; + break; + case XMLReader::END_ELEMENT: + return $buffer; } } + return $this->close(); } @@ -201,6 +212,7 @@ class BaseDump { function close() { $this->reader->close(); $this->atEnd = true; + return null; } } diff --git a/maintenance/backupTextPass.inc b/maintenance/backupTextPass.inc index c515c6fe..5f776373 100644 --- a/maintenance/backupTextPass.inc +++ b/maintenance/backupTextPass.inc @@ -3,7 +3,7 @@ * BackupDumper that postprocesses XML dumps from dumpBackup.php to add page text * * Copyright (C) 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,59 +30,60 @@ require_once __DIR__ . '/backup.inc'; * @ingroup Maintenance */ class TextPassDumper extends BackupDumper { - var $prefetch = null; - var $input = "php://stdin"; - var $history = WikiExporter::FULL; - var $fetchCount = 0; - var $prefetchCount = 0; - var $prefetchCountLast = 0; - var $fetchCountLast = 0; + public $prefetch = null; - var $maxFailures = 5; - var $maxConsecutiveFailedTextRetrievals = 200; - var $failureTimeout = 5; // Seconds to sleep after db failure + // when we spend more than maxTimeAllowed seconds on this run, we continue + // processing until we write out the next complete page, then save output file(s), + // rename it/them and open new one(s) + public $maxTimeAllowed = 0; // 0 = no limit - var $php = "php"; - var $spawn = false; + protected $input = "php://stdin"; + protected $history = WikiExporter::FULL; + protected $fetchCount = 0; + protected $prefetchCount = 0; + protected $prefetchCountLast = 0; + protected $fetchCountLast = 0; + + protected $maxFailures = 5; + protected $maxConsecutiveFailedTextRetrievals = 200; + protected $failureTimeout = 5; // Seconds to sleep after db failure + + protected $php = "php"; + protected $spawn = false; /** * @var bool|resource */ - var $spawnProc = false; + protected $spawnProc = false; /** * @var bool|resource */ - var $spawnWrite = false; + protected $spawnWrite = false; /** * @var bool|resource */ - var $spawnRead = false; + protected $spawnRead = false; /** * @var bool|resource */ - var $spawnErr = false; + protected $spawnErr = false; - var $xmlwriterobj = false; + protected $xmlwriterobj = false; - // when we spend more than maxTimeAllowed seconds on this run, we continue - // processing until we write out the next complete page, then save output file(s), - // rename it/them and open new one(s) - var $maxTimeAllowed = 0; // 0 = no limit - var $timeExceeded = false; - var $firstPageWritten = false; - var $lastPageWritten = false; - var $checkpointJustWritten = false; - var $checkpointFiles = array(); + protected $timeExceeded = false; + protected $firstPageWritten = false; + protected $lastPageWritten = false; + protected $checkpointJustWritten = false; + protected $checkpointFiles = array(); /** * @var DatabaseBase */ protected $db; - /** * Drop the database connection $this->db and try to get a new one. * @@ -103,6 +104,7 @@ class TextPassDumper extends BackupDumper { if ( $this->forcedDb !== null ) { $this->db = $this->forcedDb; + return; } @@ -120,19 +122,19 @@ class TextPassDumper extends BackupDumper { try { $this->lb = wfGetLBFactory()->newMainLB(); } catch ( Exception $e ) { - throw new MWException( __METHOD__ . " rotating DB failed to obtain new load balancer (" . $e->getMessage() . ")" ); + throw new MWException( __METHOD__ + . " rotating DB failed to obtain new load balancer (" . $e->getMessage() . ")" ); } - // 2. The Connection, through the load balancer. try { - $this->db = $this->lb->getConnection( DB_SLAVE, 'backup' ); + $this->db = $this->lb->getConnection( DB_SLAVE, 'dump' ); } catch ( Exception $e ) { - throw new MWException( __METHOD__ . " rotating DB failed to obtain new database (" . $e->getMessage() . ")" ); + throw new MWException( __METHOD__ + . " rotating DB failed to obtain new database (" . $e->getMessage() . ")" ); } } - function initProgress( $history = WikiExporter::FULL ) { parent::initProgress(); $this->timeOfCheckpoint = $this->startTime; @@ -184,31 +186,31 @@ class TextPassDumper extends BackupDumper { $url = $this->processFileOpt( $val, $param ); switch ( $opt ) { - case 'prefetch': - require_once "$IP/maintenance/backupPrefetch.inc"; - $this->prefetch = new BaseDump( $url ); - break; - case 'stub': - $this->input = $url; - break; - case 'maxtime': - $this->maxTimeAllowed = intval( $val ) * 60; - break; - case 'checkpointfile': - $this->checkpointFiles[] = $val; - break; - case 'current': - $this->history = WikiExporter::CURRENT; - break; - case 'full': - $this->history = WikiExporter::FULL; - break; - case 'spawn': - $this->spawn = true; - if ( $val ) { - $this->php = $val; - } - break; + case 'prefetch': + require_once "$IP/maintenance/backupPrefetch.inc"; + $this->prefetch = new BaseDump( $url ); + break; + case 'stub': + $this->input = $url; + break; + case 'maxtime': + $this->maxTimeAllowed = intval( $val ) * 60; + break; + case 'checkpointfile': + $this->checkpointFiles[] = $val; + break; + case 'current': + $this->history = WikiExporter::CURRENT; + break; + case 'full': + $this->history = WikiExporter::FULL; + break; + case 'spawn': + $this->spawn = true; + if ( $val ) { + $this->php = $val; + } + break; } } @@ -234,6 +236,7 @@ class TextPassDumper extends BackupDumper { $newFileURIs[] = $newURI; } $val = implode( ';', $newFileURIs ); + return $val; } @@ -243,6 +246,7 @@ class TextPassDumper extends BackupDumper { function showReport() { if ( !$this->prefetch ) { parent::showReport(); + return; } @@ -279,14 +283,19 @@ class TextPassDumper extends BackupDumper { } $pageRatePart = $this->pageCountPart / $deltaPart; $revRatePart = $this->revCountPart / $deltaPart; - } else { $fetchRatePart = '-'; $pageRatePart = '-'; $revRatePart = '-'; } - $this->progress( sprintf( "%s: %s (ID %d) %d pages (%0.1f|%0.1f/sec all|curr), %d revs (%0.1f|%0.1f/sec all|curr), %0.1f%%|%0.1f%% prefetched (all|curr), ETA %s [max %d]", - $now, wfWikiID(), $this->ID, $this->pageCount, $pageRate, $pageRatePart, $this->revCount, $revRate, $revRatePart, $fetchRate, $fetchRatePart, $etats, $this->maxCount ) ); + $this->progress( sprintf( + "%s: %s (ID %d) %d pages (%0.1f|%0.1f/sec all|curr), " + . "%d revs (%0.1f|%0.1f/sec all|curr), %0.1f%%|%0.1f%% " + . "prefetched (all|curr), ETA %s [max %d]", + $now, wfWikiID(), $this->ID, $this->pageCount, $pageRate, + $pageRatePart, $this->revCount, $revRate, $revRatePart, + $fetchRate, $fetchRatePart, $etats, $this->maxCount + ) ); $this->lastTime = $nowts; $this->revCountLast = $this->revCount; $this->prefetchCountLast = $this->prefetchCount; @@ -299,35 +308,43 @@ class TextPassDumper extends BackupDumper { } function checkIfTimeExceeded() { - if ( $this->maxTimeAllowed && ( $this->lastTime - $this->timeOfCheckpoint > $this->maxTimeAllowed ) ) { + if ( $this->maxTimeAllowed + && ( $this->lastTime - $this->timeOfCheckpoint > $this->maxTimeAllowed ) + ) { return true; } + return false; } function finalOptionCheck() { - if ( ( $this->checkpointFiles && ! $this->maxTimeAllowed ) || - ( $this->maxTimeAllowed && !$this->checkpointFiles ) ) { + if ( ( $this->checkpointFiles && !$this->maxTimeAllowed ) + || ( $this->maxTimeAllowed && !$this->checkpointFiles ) + ) { throw new MWException( "Options checkpointfile and maxtime must be specified together.\n" ); } foreach ( $this->checkpointFiles as $checkpointFile ) { - $count = substr_count ( $checkpointFile, "%s" ); + $count = substr_count( $checkpointFile, "%s" ); if ( $count != 2 ) { - throw new MWException( "Option checkpointfile must contain two '%s' for substitution of first and last pageids, count is $count instead, file is $checkpointFile.\n" ); + throw new MWException( "Option checkpointfile must contain two '%s' " + . "for substitution of first and last pageids, count is $count instead, " + . "file is $checkpointFile.\n" ); } } if ( $this->checkpointFiles ) { $filenameList = (array)$this->egress->getFilenames(); if ( count( $filenameList ) != count( $this->checkpointFiles ) ) { - throw new MWException( "One checkpointfile must be specified for each output option, if maxtime is used.\n" ); + throw new MWException( "One checkpointfile must be specified " + . "for each output option, if maxtime is used.\n" ); } } } /** * @throws MWException Failure to parse XML input - * @return true + * @param string $input + * @return bool */ function readDump( $input ) { $this->buffer = ""; @@ -341,7 +358,11 @@ class TextPassDumper extends BackupDumper { $parser = xml_parser_create( "UTF-8" ); xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); - xml_set_element_handler( $parser, array( &$this, 'startElement' ), array( &$this, 'endElement' ) ); + xml_set_element_handler( + $parser, + array( &$this, 'startElement' ), + array( &$this, 'endElement' ) + ); xml_set_character_data_handler( $parser, array( &$this, 'characterData' ) ); $offset = 0; // for context extraction on error reporting @@ -359,7 +380,7 @@ class TextPassDumper extends BackupDumper { 'XML import parse failure', xml_get_current_line_number( $parser ), xml_get_current_column_number( $parser ), - $byte . ( is_null( $chunk ) ? null : ( '; "' . substr( $chunk, $byte -$offset, 16 ) . '"' ) ), + $byte . ( is_null( $chunk ) ? null : ( '; "' . substr( $chunk, $byte - $offset, 16 ) . '"' ) ), xml_error_string( xml_get_error_code( $parser ) ) )->escaped(); xml_parser_free( $parser ); @@ -378,15 +399,16 @@ class TextPassDumper extends BackupDumper { # there's no pageID 0 so we use that. the caller is responsible # for deciding what to do with a file containing only the # siteinfo information and the mw tags. - if ( ! $this->firstPageWritten ) { + if ( !$this->firstPageWritten ) { $firstPageID = str_pad( 0, 9, "0", STR_PAD_LEFT ); $lastPageID = str_pad( 0, 9, "0", STR_PAD_LEFT ); - } - else { + } else { $firstPageID = str_pad( $this->firstPageWritten, 9, "0", STR_PAD_LEFT ); $lastPageID = str_pad( $this->lastPageWritten, 9, "0", STR_PAD_LEFT ); } - for ( $i = 0; $i < count( $filenameList ); $i++ ) { + + $filenameCount = count( $filenameList ); + for ( $i = 0; $i < $filenameCount; $i++ ) { $checkpointNameFilledIn = sprintf( $this->checkpointFiles[$i], $firstPageID, $lastPageID ); $fileinfo = pathinfo( $filenameList[$i] ); $newFilenames[] = $fileinfo['dirname'] . '/' . $checkpointNameFilledIn; @@ -408,7 +430,7 @@ class TextPassDumper extends BackupDumper { * $this->maxConsecutiveFailedTextRetrievals consecutive calls to getText, MWException * is thrown. * - * @param $id string The revision id to get the text for + * @param string $id The revision id to get the text for * * @return string The revision text for $id, or "" * @throws MWException @@ -420,8 +442,8 @@ class TextPassDumper extends BackupDumper { $text = false; // The candidate for a good text. false if no proper value. $failures = 0; // The number of times, this invocation of getText already failed. - static $consecutiveFailedTextRetrievals = 0; // The number of times getText failed without - // yielding a good text in between. + // The number of times getText failed without yielding a good text in between. + static $consecutiveFailedTextRetrievals = 0; $this->fetchCount++; @@ -478,7 +500,7 @@ class TextPassDumper extends BackupDumper { // Step 2: Checking for plausibility and return the text if it is // plausible $revID = intval( $this->thisRev ); - if ( ! isset( $this->db ) ) { + if ( !isset( $this->db ) ) { throw new MWException( "No database available" ); } @@ -497,9 +519,7 @@ class TextPassDumper extends BackupDumper { $revLength = $row->rev_len; } } - - } - else { + } else { $revLength = $this->db->selectField( 'revision', 'rev_len', array( 'rev_id' => $revID ) ); } @@ -507,12 +527,12 @@ class TextPassDumper extends BackupDumper { if ( $tryIsPrefetch ) { $this->prefetchCount++; } + return $text; } $text = false; throw new MWException( "Received text is unplausible for id " . $id ); - } catch ( Exception $e ) { $msg = "getting/checking text " . $id . " failed (" . $e->getMessage() . ")"; if ( $failures + 1 < $this->maxFailures ) { @@ -525,7 +545,7 @@ class TextPassDumper extends BackupDumper { $failures++; // A failure in a prefetch hit does not warrant resetting db connection etc. - if ( ! $tryIsPrefetch ) { + if ( !$tryIsPrefetch ) { // After backing off for some time, we try to reboot the whole process as // much as possible to not carry over failures from one part to the other // parts @@ -556,16 +576,15 @@ class TextPassDumper extends BackupDumper { return ""; } - /** * May throw a database error if, say, the server dies during query. - * @param $id + * @param int $id * @return bool|string * @throws MWException */ private function getTextDb( $id ) { global $wgContLang; - if ( ! isset( $this->db ) ) { + if ( !isset( $this->db ) ) { throw new MWException( __METHOD__ . "No database available" ); } $row = $this->db->selectRow( 'text', @@ -578,6 +597,7 @@ class TextPassDumper extends BackupDumper { } $stripped = str_replace( "\r", "", $text ); $normalized = $wgContLang->normalize( $stripped ); + return $normalized; } @@ -589,6 +609,7 @@ class TextPassDumper extends BackupDumper { } $text = $this->getTextSpawnedOnce( $id ); wfRestoreWarnings(); + return $text; } @@ -603,8 +624,7 @@ class TextPassDumper extends BackupDumper { "$IP/../multiversion/MWScript.php", "fetchText.php", '--wiki', wfWikiID() ) ) ); - } - else { + } else { $cmd = implode( " ", array_map( 'wfEscapeShellArg', array( @@ -623,11 +643,12 @@ class TextPassDumper extends BackupDumper { if ( !$this->spawnProc ) { // shit $this->progress( "Subprocess spawn failed." ); + return false; } list( $this->spawnWrite, // -> stdin - $this->spawnRead, // <- stdout + $this->spawnRead, // <- stdout ) = $pipes; return true; @@ -705,12 +726,14 @@ class TextPassDumper extends BackupDumper { $gotbytes = strlen( $text ); if ( $gotbytes != $nbytes ) { $this->progress( "Expected $nbytes bytes from database subprocess, got $gotbytes " ); + return false; } // Do normalization in the dump thread... $stripped = str_replace( "\r", "", $text ); $normalized = $wgContLang->normalize( $stripped ); + return $normalized; } @@ -758,7 +781,7 @@ class TextPassDumper extends BackupDumper { $this->buffer = ""; $this->thisRev = ""; } elseif ( $name == 'page' ) { - if ( ! $this->firstPageWritten ) { + if ( !$this->firstPageWritten ) { $this->firstPageWritten = trim( $this->thisPage ); } $this->lastPageWritten = trim( $this->thisPage ); @@ -779,7 +802,8 @@ class TextPassDumper extends BackupDumper { $newFilenames = array(); $firstPageID = str_pad( $this->firstPageWritten, 9, "0", STR_PAD_LEFT ); $lastPageID = str_pad( $this->lastPageWritten, 9, "0", STR_PAD_LEFT ); - for ( $i = 0; $i < count( $filenameList ); $i++ ) { + $filenamesCount = count( $filenameList ); + for ( $i = 0; $i < $filenamesCount; $i++ ) { $checkpointNameFilledIn = sprintf( $this->checkpointFiles[$i], $firstPageID, $lastPageID ); $fileinfo = pathinfo( $filenameList[$i] ); $newFilenames[] = $fileinfo['dirname'] . '/' . $checkpointNameFilledIn; @@ -790,13 +814,11 @@ class TextPassDumper extends BackupDumper { $this->timeOfCheckpoint = $this->lastTime; $this->firstPageWritten = false; $this->checkpointJustWritten = true; - } - else { + } else { $this->egress->writeClosePage( $this->buffer ); $this->buffer = ""; $this->thisPage = ""; } - } elseif ( $name == 'mediawiki' ) { $this->egress->writeCloseStream( $this->buffer ); $this->buffer = ""; diff --git a/maintenance/benchmarks/Benchmarker.php b/maintenance/benchmarks/Benchmarker.php index dd558f32..3f8a8990 100644 --- a/maintenance/benchmarks/Benchmarker.php +++ b/maintenance/benchmarks/Benchmarker.php @@ -46,38 +46,38 @@ abstract class Benchmarker extends Maintenance { $bench_number = 0; $count = $this->getOption( 'count', 100 ); - foreach( $benchs as $bench ) { + foreach ( $benchs as $bench ) { // handle empty args - if( !array_key_exists( 'args', $bench ) ) { + if ( !array_key_exists( 'args', $bench ) ) { $bench['args'] = array(); } $bench_number++; $start = microtime( true ); - for( $i = 0; $i < $count; $i++ ) { + for ( $i = 0; $i < $count; $i++ ) { call_user_func_array( $bench['function'], $bench['args'] ); } $delta = microtime( true ) - $start; // function passed as a callback - if( is_array( $bench['function'] ) ) { + if ( is_array( $bench['function'] ) ) { $ret = get_class( $bench['function'][0] ) . '->' . $bench['function'][1]; $bench['function'] = $ret; } $this->results[$bench_number] = array( - 'function' => $bench['function'], + 'function' => $bench['function'], 'arguments' => $bench['args'], - 'count' => $count, - 'delta' => $delta, - 'average' => $delta / $count, - ); + 'count' => $count, + 'delta' => $delta, + 'average' => $delta / $count, + ); } } public function getFormattedResults() { $ret = ''; - foreach( $this->results as $res ) { + foreach ( $this->results as $res ) { // show function with args $ret .= sprintf( "%s times: function %s(%s) :\n", $res['count'], @@ -89,6 +89,7 @@ abstract class Benchmarker extends Maintenance { $res['average'] * 1000 ); } + return $ret; } } diff --git a/maintenance/benchmarks/bench_HTTP_HTTPS.php b/maintenance/benchmarks/bench_HTTP_HTTPS.php index 6f800fb3..bb7499b7 100644 --- a/maintenance/benchmarks/bench_HTTP_HTTPS.php +++ b/maintenance/benchmarks/bench_HTTP_HTTPS.php @@ -31,8 +31,7 @@ require_once __DIR__ . '/Benchmarker.php'; * * @ingroup Benchmark */ -class bench_HTTP_HTTPS extends Benchmarker { - +class BenchHttpHttps extends Benchmarker { public function __construct() { parent::__construct(); $this->mDescription = "Benchmark HTTP request vs HTTPS request."; @@ -42,7 +41,7 @@ class bench_HTTP_HTTPS extends Benchmarker { $this->bench( array( array( 'function' => array( $this, 'getHTTP' ) ), array( 'function' => array( $this, 'getHTTPS' ) ), - )); + ) ); print $this->getFormattedResults(); } @@ -61,5 +60,5 @@ class bench_HTTP_HTTPS extends Benchmarker { } } -$maintClass = 'bench_HTTP_HTTPS'; +$maintClass = 'BenchHttpHttps'; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/benchmarks/bench_delete_truncate.php b/maintenance/benchmarks/bench_delete_truncate.php index 3eff534b..8ae4f030 100644 --- a/maintenance/benchmarks/bench_delete_truncate.php +++ b/maintenance/benchmarks/bench_delete_truncate.php @@ -29,7 +29,6 @@ require_once __DIR__ . '/Benchmarker.php'; * @ingroup Benchmark */ class BenchmarkDeleteTruncate extends Benchmarker { - public function __construct() { parent::__construct(); $this->mDescription = "Benchmarks SQL DELETE vs SQL TRUNCATE."; @@ -70,20 +69,20 @@ class BenchmarkDeleteTruncate extends Benchmarker { } /** - * @param $dbw DatabaseBase + * @param DatabaseBase $dbw * @return void */ private function insertData( $dbw ) { $range = range( 0, 1024 ); $data = array(); - foreach( $range as $r ) { + foreach ( $range as $r ) { $data[] = array( 'text' => $r ); } $dbw->insert( 'test', $data, __METHOD__ ); } /** - * @param $dbw DatabaseBase + * @param DatabaseBase $dbw * @return void */ private function delete( $dbw ) { @@ -91,7 +90,7 @@ class BenchmarkDeleteTruncate extends Benchmarker { } /** - * @param $dbw DatabaseBase + * @param DatabaseBase $dbw * @return void */ private function truncate( $dbw ) { diff --git a/maintenance/benchmarks/bench_if_switch.php b/maintenance/benchmarks/bench_if_switch.php index 80fd9623..698a0f0a 100644 --- a/maintenance/benchmarks/bench_if_switch.php +++ b/maintenance/benchmarks/bench_if_switch.php @@ -31,8 +31,7 @@ require_once __DIR__ . '/Benchmarker.php'; * * @ingroup Maintenance */ -class bench_if_switch extends Benchmarker { - +class BenchIfSwitch extends Benchmarker { public function __construct() { parent::__construct(); $this->mDescription = "Benchmark if elseif... versus switch case."; @@ -42,55 +41,71 @@ class bench_if_switch extends Benchmarker { $this->bench( array( array( 'function' => array( $this, 'doElseIf' ) ), array( 'function' => array( $this, 'doSwitch' ) ), - )); + ) ); print $this->getFormattedResults(); } // bench function 1 function doElseIf() { $a = 'z'; - if( $a == 'a') {} - elseif( $a == 'b') {} - elseif( $a == 'c') {} - elseif( $a == 'd') {} - elseif( $a == 'e') {} - elseif( $a == 'f') {} - elseif( $a == 'g') {} - elseif( $a == 'h') {} - elseif( $a == 'i') {} - elseif( $a == 'j') {} - elseif( $a == 'k') {} - elseif( $a == 'l') {} - elseif( $a == 'm') {} - elseif( $a == 'n') {} - elseif( $a == 'o') {} - elseif( $a == 'p') {} - else {} + if ( $a == 'a' ) { + } elseif ( $a == 'b' ) { + } elseif ( $a == 'c' ) { + } elseif ( $a == 'd' ) { + } elseif ( $a == 'e' ) { + } elseif ( $a == 'f' ) { + } elseif ( $a == 'g' ) { + } elseif ( $a == 'h' ) { + } elseif ( $a == 'i' ) { + } elseif ( $a == 'j' ) { + } elseif ( $a == 'k' ) { + } elseif ( $a == 'l' ) { + } elseif ( $a == 'm' ) { + } elseif ( $a == 'n' ) { + } elseif ( $a == 'o' ) { + } elseif ( $a == 'p' ) { + } else { + } } // bench function 2 function doSwitch() { $a = 'z'; - switch( $a ) { - case 'b': break; - case 'c': break; - case 'd': break; - case 'e': break; - case 'f': break; - case 'g': break; - case 'h': break; - case 'i': break; - case 'j': break; - case 'k': break; - case 'l': break; - case 'm': break; - case 'n': break; - case 'o': break; - case 'p': break; + switch ( $a ) { + case 'b': + break; + case 'c': + break; + case 'd': + break; + case 'e': + break; + case 'f': + break; + case 'g': + break; + case 'h': + break; + case 'i': + break; + case 'j': + break; + case 'k': + break; + case 'l': + break; + case 'm': + break; + case 'n': + break; + case 'o': + break; + case 'p': + break; default: } } } -$maintClass = 'bench_if_switch'; +$maintClass = 'BenchIfSwitch'; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/benchmarks/bench_strtr_str_replace.php b/maintenance/benchmarks/bench_strtr_str_replace.php index bd21b186..44c8e032 100644 --- a/maintenance/benchmarks/bench_strtr_str_replace.php +++ b/maintenance/benchmarks/bench_strtr_str_replace.php @@ -38,8 +38,7 @@ function bfNormalizeTitleStrReplace( $str ) { * * @ingroup Benchmark */ -class bench_strtr_str_replace extends Benchmarker { - +class BenchStrtrStrReplace extends Benchmarker { public function __construct() { parent::__construct(); $this->mDescription = "Benchmark for strtr() vs str_replace()."; @@ -51,7 +50,7 @@ class bench_strtr_str_replace extends Benchmarker { array( 'function' => array( $this, 'benchstr_replace' ) ), array( 'function' => array( $this, 'benchstrtr_indirect' ) ), array( 'function' => array( $this, 'benchstr_replace_indirect' ) ), - )); + ) ); print $this->getFormattedResults(); } @@ -60,10 +59,9 @@ class bench_strtr_str_replace extends Benchmarker { } function benchstr_replace() { - str_replace( "_", " ", "[[MediaWiki:Some_random_test_page]]"); + str_replace( "_", " ", "[[MediaWiki:Some_random_test_page]]" ); } - function benchstrtr_indirect() { bfNormalizeTitleStrTr( "[[MediaWiki:Some_random_test_page]]" ); } @@ -71,8 +69,7 @@ class bench_strtr_str_replace extends Benchmarker { function benchstr_replace_indirect() { bfNormalizeTitleStrReplace( "[[MediaWiki:Some_random_test_page]]" ); } - } -$maintClass = 'bench_strtr_str_replace'; +$maintClass = 'BenchStrtrStrReplace'; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/benchmarks/bench_utf8_title_check.php b/maintenance/benchmarks/bench_utf8_title_check.php index 078293eb..b742f666 100644 --- a/maintenance/benchmarks/bench_utf8_title_check.php +++ b/maintenance/benchmarks/bench_utf8_title_check.php @@ -29,8 +29,7 @@ require_once __DIR__ . '/Benchmarker.php'; * * @ingroup Benchmark */ -class bench_utf8_title_check extends Benchmarker { - +class BenchUtf8TitleCheck extends Benchmarker { private $canRun; private $data; @@ -38,6 +37,7 @@ class bench_utf8_title_check extends Benchmarker { public function __construct() { parent::__construct(); + // @codingStandardsIgnoreStart Ignore long line warnings. $this->data = array( "", "United States of America", // 7bit ASCII @@ -59,11 +59,13 @@ class bench_utf8_title_check extends Benchmarker { . "Sara%20Sidle%7CSofia%20Curtis%7CS%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e%7CWallace%20Langham%7C" . "Warrick%20Brown%7CWendy%20Simms%7C%C3%89tats-Unis" ); + // @codingStandardsIgnoreEnd - $this->canRun = function_exists ( 'mb_check_encoding' ); + $this->canRun = function_exists( 'mb_check_encoding' ); if ( $this->canRun ) { - $this->mDescription = "Benchmark for using a regexp vs. mb_check_encoding to check for UTF-8 encoding."; + $this->mDescription = "Benchmark for using a regexp vs. mb_check_encoding " . + "to check for UTF-8 encoding."; mb_internal_encoding( 'UTF-8' ); } else { $this->mDescription = "CANNOT RUN benchmark using mb_check_encoding: function not available."; @@ -75,22 +77,22 @@ class bench_utf8_title_check extends Benchmarker { return; } $benchmarks = array(); - foreach ($this->data as $val) { + foreach ( $this->data as $val ) { $benchmarks[] = array( 'function' => array( $this, 'use_regexp' ), - 'args' => array( rawurldecode ( $val ) ) + 'args' => array( rawurldecode( $val ) ) ); $benchmarks[] = array( 'function' => array( $this, 'use_regexp_non_capturing' ), - 'args' => array( rawurldecode ( $val ) ) + 'args' => array( rawurldecode( $val ) ) ); $benchmarks[] = array( 'function' => array( $this, 'use_regexp_once_only' ), - 'args' => array( rawurldecode ( $val ) ) + 'args' => array( rawurldecode( $val ) ) ); $benchmarks[] = array( 'function' => array( $this, 'use_mb_check_encoding' ), - 'args' => array( rawurldecode ( $val ) ) + 'args' => array( rawurldecode( $val ) ) ); } $this->bench( $benchmarks ); @@ -101,26 +103,25 @@ class bench_utf8_title_check extends Benchmarker { function use_regexp( $s ) { $this->isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' . - '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s ); + '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s ); } function use_regexp_non_capturing( $s ) { // Same as above with a non-capturing subgroup. $this->isutf8 = preg_match( '/^(?:[\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' . - '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s ); + '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s ); } function use_regexp_once_only( $s ) { // Same as above with a once-only subgroup. $this->isutf8 = preg_match( '/^(?>[\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' . - '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s ); + '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s ); } function use_mb_check_encoding( $s ) { $this->isutf8 = mb_check_encoding( $s, 'UTF-8' ); } - } -$maintClass = 'bench_utf8_title_check'; +$maintClass = 'BenchUtf8TitleCheck'; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/benchmarks/bench_wfBaseConvert.php b/maintenance/benchmarks/bench_wfBaseConvert.php index f8a21562..b4be12bc 100644 --- a/maintenance/benchmarks/bench_wfBaseConvert.php +++ b/maintenance/benchmarks/bench_wfBaseConvert.php @@ -29,8 +29,7 @@ require_once __DIR__ . '/Benchmarker.php'; * * @ingroup Benchmark */ -class bench_wfBaseConvert extends Benchmarker { - +class BenchWfBaseConvert extends Benchmarker { public function __construct() { parent::__construct(); $this->mDescription = "Benchmark for wfBaseConvert."; @@ -58,7 +57,7 @@ class bench_wfBaseConvert extends Benchmarker { 'function' => 'wfBaseConvert', 'args' => array( $number, $inbase, $outbase, 0, true, 'gmp' ) ), - )); + ) ); $this->output( $this->getFormattedResults() ); } @@ -66,12 +65,13 @@ class bench_wfBaseConvert extends Benchmarker { protected static function makeRandomNumber( $base, $length ) { $baseChars = "0123456789abcdefghijklmnopqrstuvwxyz"; $res = ""; - for( $i = 0; $i < $length; $i++ ) { - $res .= $baseChars[mt_rand(0, $base - 1)]; + for ( $i = 0; $i < $length; $i++ ) { + $res .= $baseChars[mt_rand( 0, $base - 1 )]; } + return $res; } } -$maintClass = 'bench_wfBaseConvert'; +$maintClass = 'BenchWfBaseConvert'; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/benchmarks/bench_wfIsWindows.php b/maintenance/benchmarks/bench_wfIsWindows.php index 1cd2016b..8446694b 100644 --- a/maintenance/benchmarks/bench_wfIsWindows.php +++ b/maintenance/benchmarks/bench_wfIsWindows.php @@ -31,8 +31,7 @@ require_once __DIR__ . '/Benchmarker.php'; * * @ingroup Benchmark */ -class bench_wfIsWindows extends Benchmarker { - +class BenchWfIsWindows extends Benchmarker { public function __construct() { parent::__construct(); $this->mDescription = "Benchmark for wfIsWindows."; @@ -42,12 +41,12 @@ class bench_wfIsWindows extends Benchmarker { $this->bench( array( array( 'function' => array( $this, 'wfIsWindows' ) ), array( 'function' => array( $this, 'wfIsWindowsCached' ) ), - )); + ) ); print $this->getFormattedResults(); } static function is_win() { - return substr( php_uname(), 0, 7 ) == 'Windows' ; + return substr( php_uname(), 0, 7 ) == 'Windows'; } // bench function 1 @@ -58,12 +57,13 @@ class bench_wfIsWindows extends Benchmarker { // bench function 2 function wfIsWindowsCached() { static $isWindows = null; - if( $isWindows == null ) { + if ( $isWindows == null ) { $isWindows = self::is_win(); } + return $isWindows; } } -$maintClass = 'bench_wfIsWindows'; +$maintClass = 'BenchWfIsWindows'; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/benchmarks/benchmarkHooks.php b/maintenance/benchmarks/benchmarkHooks.php index 3f5d6db0..fb25b9d9 100644 --- a/maintenance/benchmarks/benchmarkHooks.php +++ b/maintenance/benchmarks/benchmarkHooks.php @@ -29,7 +29,6 @@ require_once __DIR__ . '/Benchmarker.php'; * @ingroup Benchmark */ class BenchmarkHooks extends Benchmarker { - public function __construct() { parent::__construct(); $this->mDescription = 'Benchmark MediaWiki Hooks.'; @@ -46,13 +45,13 @@ class BenchmarkHooks extends Benchmarker { $time = $this->benchHooks(); $this->output( 'Loaded (one) hook: ' . $time . "\n" ); - for( $i = 0; $i < 9; $i++ ) { + for ( $i = 0; $i < 9; $i++ ) { $wgHooks['Test'][] = array( $this, 'test' ); } $time = $this->benchHooks(); $this->output( 'Loaded (ten) hook: ' . $time . "\n" ); - for( $i = 0; $i < 90; $i++ ) { + for ( $i = 0; $i < 90; $i++ ) { $wgHooks['Test'][] = array( $this, 'test' ); } $time = $this->benchHooks(); @@ -61,7 +60,7 @@ class BenchmarkHooks extends Benchmarker { } /** - * @param $trials int + * @param int $trials * @return string */ private function benchHooks( $trials = 10 ) { @@ -71,6 +70,7 @@ class BenchmarkHooks extends Benchmarker { } $delta = microtime( true ) - $start; $pertrial = $delta / $trials; + return sprintf( "Took %6.3fms", $pertrial * 1000 ); } diff --git a/maintenance/benchmarks/benchmarkParse.php b/maintenance/benchmarks/benchmarkParse.php new file mode 100644 index 00000000..ce38dad6 --- /dev/null +++ b/maintenance/benchmarks/benchmarkParse.php @@ -0,0 +1,174 @@ +<?php +/** + * Benchmark script for parse operations + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @author Tim Starling <tstarling@wikimedia.org> + * @ingroup Benchmark + */ + +require __DIR__ . '/../Maintenance.php'; + +/** + * Maintenance script to benchmark how long it takes to parse a given title at an optionally + * specified timestamp + * + * @since 1.23 + */ +class BenchmarkParse extends Maintenance { + /** @var string MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS) */ + private $templateTimestamp = null; + + /** @var array Cache that maps a Title DB key to revision ID for the requested timestamp */ + private $idCache = array(); + + function __construct() { + parent::__construct(); + $this->addDescription( 'Benchmark parse operation' ); + $this->addArg( 'title', 'The name of the page to parse' ); + $this->addOption( 'warmup', 'Repeat the parse operation this number of times to warm the cache', + false, true ); + $this->addOption( 'loops', 'Number of times to repeat parse operation post-warmup', + false, true ); + $this->addOption( 'page-time', + 'Use the version of the page which was current at the given time', + false, true ); + $this->addOption( 'tpl-time', + 'Use templates which were current at the given time (except that moves and ' . + 'deletes are not handled properly)', + false, true ); + } + + function execute() { + if ( $this->hasOption( 'tpl-time' ) ) { + $this->templateTimestamp = wfTimestamp( TS_MW, strtotime( $this->getOption( 'tpl-time' ) ) ); + Hooks::register( 'BeforeParserFetchTemplateAndtitle', array( $this, 'onFetchTemplate' ) ); + } + + $title = Title::newFromText( $this->getArg() ); + if ( !$title ) { + $this->error( "Invalid title" ); + exit( 1 ); + } + + if ( $this->hasOption( 'page-time' ) ) { + $pageTimestamp = wfTimestamp( TS_MW, strtotime( $this->getOption( 'page-time' ) ) ); + $id = $this->getRevIdForTime( $title, $pageTimestamp ); + if ( !$id ) { + $this->error( "The page did not exist at that time" ); + exit( 1 ); + } + + $revision = Revision::newFromId( $id ); + } else { + $revision = Revision::newFromTitle( $title ); + } + + if ( !$revision ) { + $this->error( "Unable to load revision, incorrect title?" ); + exit( 1 ); + } + + $warmup = $this->getOption( 'warmup', 1 ); + for ( $i = 0; $i < $warmup; $i++ ) { + $this->runParser( $revision ); + } + + $loops = $this->getOption( 'loops', 1 ); + if ( $loops < 1 ) { + $this->error( 'Invalid number of loops specified', true ); + } + $startUsage = getrusage(); + $startTime = microtime( true ); + for ( $i = 0; $i < $loops; $i++ ) { + $this->runParser( $revision ); + } + $endUsage = getrusage(); + $endTime = microtime( true ); + + printf( "CPU time = %.3f s, wall clock time = %.3f s\n", + // CPU time + ( $endUsage['ru_utime.tv_sec'] + $endUsage['ru_utime.tv_usec'] * 1e-6 + - $startUsage['ru_utime.tv_sec'] - $startUsage['ru_utime.tv_usec'] * 1e-6 ) / $loops, + // Wall clock time + ( $endTime - $startTime ) / $loops + ); + } + + /** + * Fetch the ID of the revision of a Title that occurred + * + * @param Title $title + * @param string $timestamp + * @return bool|string Revision ID, or false if not found or error + */ + function getRevIdForTime( Title $title, $timestamp ) { + $dbr = wfGetDB( DB_SLAVE ); + + $id = $dbr->selectField( + array( 'revision', 'page' ), + 'rev_id', + array( + 'page_namespace' => $title->getNamespace(), + 'page_title' => $title->getDBkey(), + 'rev_timestamp <= ' . $dbr->addQuotes( $timestamp ) + ), + __METHOD__, + array( 'ORDER BY' => 'rev_timestamp DESC', 'LIMIT' => 1 ), + array( 'revision' => array( 'INNER JOIN', 'rev_page=page_id' ) ) + ); + + return $id; + } + + /** + * Parse the text from a given Revision + * + * @param Revision $revision + */ + function runParser( Revision $revision ) { + $content = $revision->getContent(); + $content->getParserOutput( $revision->getTitle(), $revision->getId() ); + } + + /** + * Hook into the parser's revision ID fetcher. Make sure that the parser only + * uses revisions around the specified timestamp. + * + * @param Parser $parser + * @param Title $title + * @param bool &$skip + * @param string|bool &$id + * @return bool + */ + function onFetchTemplate( Parser $parser, Title $title, &$skip, &$id ) { + $pdbk = $title->getPrefixedDBkey(); + if ( !isset( $this->idCache[$pdbk] ) ) { + $proposedId = $this->getRevIdForTime( $title, $this->templateTimestamp ); + $this->idCache[$pdbk] = $proposedId; + } + if ( $this->idCache[$pdbk] !== false ) { + $id = $this->idCache[$pdbk]; + } + + return true; + } +} + +$maintClass = 'BenchmarkParse'; +require RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/benchmarks/benchmarkPurge.php b/maintenance/benchmarks/benchmarkPurge.php index fd863d52..42c1eb78 100644 --- a/maintenance/benchmarks/benchmarkPurge.php +++ b/maintenance/benchmarks/benchmarkPurge.php @@ -29,7 +29,6 @@ require_once __DIR__ . '/Benchmarker.php'; * @ingroup Benchmark */ class BenchmarkPurge extends Benchmarker { - public function __construct() { parent::__construct(); $this->mDescription = "Benchmark the Squid purge functions."; @@ -57,8 +56,8 @@ class BenchmarkPurge extends Benchmarker { /** * 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? + * @param array $urls A bunch of URLs to purge + * @param int $trials How many times to run the test? * @return string */ private function benchSquid( $urls, $trials = 1 ) { @@ -69,13 +68,14 @@ class BenchmarkPurge extends Benchmarker { $delta = microtime( true ) - $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 + * @param int $length How many urls to add to the array * @return array */ private function randomUrlList( $length ) { @@ -83,6 +83,7 @@ class BenchmarkPurge extends Benchmarker { for ( $i = 0; $i < $length; $i++ ) { $list[] = $this->randomUrl(); } + return $list; } @@ -93,6 +94,7 @@ class BenchmarkPurge extends Benchmarker { */ private function randomUrl() { global $wgServer, $wgArticlePath; + return $wgServer . str_replace( '$1', $this->randomTitle(), $wgArticlePath ); } @@ -107,6 +109,7 @@ class BenchmarkPurge extends Benchmarker { for ( $i = 0; $i < $length; $i++ ) { $str .= chr( mt_rand( ord( 'a' ), ord( 'z' ) ) ); } + return ucfirst( $str ); } } diff --git a/maintenance/cdb.php b/maintenance/cdb.php index d42f9f7a..86c686b4 100644 --- a/maintenance/cdb.php +++ b/maintenance/cdb.php @@ -52,7 +52,7 @@ do { $bad = false; $showhelp = false; $quit = false; - static $fileHandle; + static $fileHandle = false; $line = Maintenance::readconsole(); if ( $line === false ) { @@ -75,7 +75,11 @@ do { } $file = $args[0]; print "Loading cdb file $file..."; - $fileHandle = CdbReader::open( $file ); + try { + $fileHandle = CdbReader::open( $file ); + } catch ( CdbException $e ) { + } + if ( !$fileHandle ) { print "not a cdb file or unable to read it\n"; } else { @@ -91,7 +95,12 @@ do { print "Need to specify a key, Luke\n"; break; } - $res = $fileHandle->get( $args[0] ); + try { + $res = $fileHandle->get( $args[0] ); + } catch ( CdbException $e ) { + print "Unable to read key from file\n"; + break; + } if ( $res === false ) { print "No such key/value pair\n"; } elseif ( is_string( $res ) ) { diff --git a/maintenance/checkBadRedirects.php b/maintenance/checkBadRedirects.php index a96e9b80..fec92910 100644 --- a/maintenance/checkBadRedirects.php +++ b/maintenance/checkBadRedirects.php @@ -44,7 +44,7 @@ class CheckBadRedirects extends Maintenance { $count = $result->numRows(); $this->output( "Found $count redirects.\n" . - "Checking for bad redirects:\n\n" ); + "Checking for bad redirects:\n\n" ); foreach ( $result as $row ) { $title = Title::makeTitle( $row->page_namespace, $row->page_title ); diff --git a/maintenance/checkImages.php b/maintenance/checkImages.php index e6aea537..3921c079 100644 --- a/maintenance/checkImages.php +++ b/maintenance/checkImages.php @@ -73,13 +73,13 @@ class CheckImages extends Maintenance { } if ( $stat['size'] != $row->img_size ) { - $this->output( "{$row->img_name}: size mismatch DB={$row->img_size}, actual={$stat['size']}\n" ); + $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" ); diff --git a/maintenance/checkLess.php b/maintenance/checkLess.php index d02d8a7b..b97e1b0b 100644 --- a/maintenance/checkLess.php +++ b/maintenance/checkLess.php @@ -22,49 +22,33 @@ */ require_once __DIR__ . '/Maintenance.php'; +require_once 'PHPUnit/Autoload.php'; /** * @ingroup Maintenance */ class CheckLess extends Maintenance { + public function __construct() { parent::__construct(); - $this->mDescription = 'Checks LESS files for errors'; + $this->mDescription = + 'Checks LESS files for errors by running the LessTestSuite PHPUnit test suite'; } public function execute() { - $result = false; - $resourceLoader = new ResourceLoader(); - foreach ( $resourceLoader->getModuleNames() as $name ) { - /** @var ResourceLoaderFileModule $module */ - $module = $resourceLoader->getModule( $name ); - if ( !$module || !$module instanceof ResourceLoaderFileModule ) { - continue; - } + global $IP; + + // NOTE (phuedx, 2014-03-26) wgAutoloadClasses isn't set up + // by either of the dependencies at the top of the file, so + // require it here. + require_once __DIR__ . '/../tests/TestsAutoLoader.php'; - $hadErrors = false; - foreach ( $module->getAllStyleFiles() as $file ) { - if ( $module->getStyleSheetLang( $file ) !== 'less' ) { - continue; - } - try { - $compiler = ResourceLoader::getLessCompiler(); - $compiler->compileFile( $file ); - } catch ( Exception $e ) { - if ( !$hadErrors ) { - $this->error( "Errors checking module $name:\n" ); - $hadErrors = true; - } - $this->error( $e->getMessage() . "\n" ); - $result = true; - } - } - } - if ( !$result ) { - $this->output( "No errors found\n" ); - } else { - die( 1 ); - } + $textUICommand = new PHPUnit_TextUI_Command(); + $argv = array( + "$IP/tests/phpunit/phpunit.php", + "$IP/tests/phpunit/suites/LessTestSuite.php" + ); + $textUICommand->run( $argv ); } } diff --git a/maintenance/checkSyntax.php b/maintenance/checkSyntax.php index dc8626df..d1c2edd0 100644 --- a/maintenance/checkSyntax.php +++ b/maintenance/checkSyntax.php @@ -38,10 +38,23 @@ class CheckSyntax extends Maintenance { parent::__construct(); $this->mDescription = "Check syntax for all PHP files in MediaWiki"; $this->addOption( 'with-extensions', 'Also recurse the extensions folder' ); - $this->addOption( 'path', 'Specific path (file or directory) to check, either with absolute path or relative to the root of this MediaWiki installation', - false, true ); - $this->addOption( 'list-file', 'Text file containing list of files or directories to check', false, true ); - $this->addOption( 'modified', 'Check only files that were modified (requires Git command-line client)' ); + $this->addOption( + 'path', + 'Specific path (file or directory) to check, either with absolute path or ' + . 'relative to the root of this MediaWiki installation', + false, + true + ); + $this->addOption( + 'list-file', + 'Text file containing list of files or directories to check', + false, + true + ); + $this->addOption( + 'modified', + 'Check only files that were modified (requires Git command-line client)' + ); $this->addOption( 'syntax-only', 'Check for syntax validity only, skip code style warnings' ); } @@ -53,7 +66,8 @@ class CheckSyntax extends Maintenance { $this->buildFileList(); // ParseKit is broken on PHP 5.3+, disabled until this is fixed - $useParseKit = function_exists( 'parsekit_compile_file' ) && version_compare( PHP_VERSION, '5.3', '<' ); + $useParseKit = function_exists( 'parsekit_compile_file' ) + && version_compare( PHP_VERSION, '5.3', '<' ); $str = 'Checking syntax (using ' . ( $useParseKit ? 'parsekit' : ' php -l, this can take a long time' ) . ")\n"; @@ -82,7 +96,7 @@ class CheckSyntax extends Maintenance { $this->mIgnorePaths = array( // Compat stuff, explodes on PHP 5.3 "includes/NamespaceCompat.php$", - ); + ); $this->mNoStyleCheckPaths = array( // Third-party code we don't care about @@ -96,13 +110,14 @@ class CheckSyntax extends Maintenance { "QPoll/Excel/", "/geshi/", "/smarty/", - ); + ); if ( $this->hasOption( 'path' ) ) { $path = $this->getOption( 'path' ); if ( !$this->addPath( $path ) ) { $this->error( "Error: can't find file or directory $path\n", true ); } + return; // process only this path } elseif ( $this->hasOption( 'list-file' ) ) { $file = $this->getOption( 'list-file' ); @@ -117,6 +132,7 @@ class CheckSyntax extends Maintenance { $this->addPath( $path ); } fclose( $f ); + return; } elseif ( $this->hasOption( 'modified' ) ) { $this->output( "Retrieving list from Git... " ); @@ -127,6 +143,7 @@ class CheckSyntax extends Maintenance { $this->mFiles[] = $file; } } + return; } @@ -153,17 +170,14 @@ class CheckSyntax extends Maintenance { if ( file_exists( "$IP/LocalSettings.php" ) ) { $this->mFiles[] = "$IP/LocalSettings.php"; } - if ( file_exists( "$IP/AdminSettings.php" ) ) { - $this->mFiles[] = "$IP/AdminSettings.php"; - } $this->output( 'done.', 'listfiles' ); } /** * Returns a list of tracked files in a Git work tree differing from the master branch. - * @param $path string: Path to the repository - * @return array: Resulting list of changed files + * @param string $path Path to the repository + * @return array Resulting list of changed files */ private function getGitModifiedFiles( $path ) { @@ -215,7 +229,7 @@ class CheckSyntax extends Maintenance { /** * Returns true if $file is of a type we can check - * @param $file string + * @param string $file * @return bool */ private function isSuitableFile( $file ) { @@ -230,22 +244,24 @@ class CheckSyntax extends Maintenance { return false; } } + return true; } /** * Add given path to file list, searching it in include path if needed - * @param $path string + * @param string $path * @return bool */ private function addPath( $path ) { global $IP; + return $this->addFileOrDir( $path ) || $this->addFileOrDir( "$IP/$path" ); } /** * Add given file to file list, or, if it's a directory, add its content - * @param $path string + * @param string $path * @return bool */ private function addFileOrDir( $path ) { @@ -256,13 +272,14 @@ class CheckSyntax extends Maintenance { } else { return false; } + return true; } /** * Add all suitable files in given directory or its subdirectories to the file list * - * @param $dir String: directory to process + * @param string $dir Directory to process */ private function addDirectoryContent( $dir ) { $iterator = new RecursiveIteratorIterator( @@ -279,8 +296,8 @@ class CheckSyntax extends Maintenance { /** * Check a file for syntax errors using Parsekit. Shamelessly stolen * from tools/lint.php by TimStarling - * @param $file String Path to a file to check for syntax errors - * @return boolean + * @param string $file Path to a file to check for syntax errors + * @return bool */ private function checkFileWithParsekit( $file ) { static $okErrors = array( @@ -302,21 +319,24 @@ class CheckSyntax extends Maintenance { $this->mFailures[$file] = $errors; } } + return $ret; } /** * Check a file for syntax errors using php -l - * @param $file String Path to a file to check for syntax errors - * @return boolean + * @param string $file Path to a file to check for syntax errors + * @return bool */ private function checkFileWithCli( $file ) { $res = exec( 'php -l ' . wfEscapeShellArg( $file ) ); if ( strpos( $res, 'No syntax errors detected' ) === false ) { $this->mFailures[$file] = $res; $this->output( $res . "\n" ); + return false; } + return true; } @@ -324,8 +344,7 @@ class CheckSyntax extends Maintenance { * Check a file for non-fatal coding errors, such as byte-order marks in the beginning * or pointless ?> closing tags at the end. * - * @param $file String String Path to a file to check for errors - * @return boolean + * @param string $file String Path to a file to check for errors */ private function checkForMistakes( $file ) { foreach ( $this->mNoStyleCheckPaths as $regex ) { diff --git a/maintenance/checkUsernames.php b/maintenance/checkUsernames.php index 6df189fc..777c8334 100644 --- a/maintenance/checkUsernames.php +++ b/maintenance/checkUsernames.php @@ -55,7 +55,7 @@ class CheckUsernames extends Maintenance { ); foreach ( $res as $row ) { - if ( ! User::isValidUserName( $row->user_name ) ) { + if ( !User::isValidUserName( $row->user_name ) ) { $this->error( sprintf( "%s: %6d: '%s'\n", wfWikiID(), $row->user_id, $row->user_name ) ); wfDebugLog( 'checkUsernames', $row->user_name ); } diff --git a/maintenance/cleanupAncientTables.php b/maintenance/cleanupAncientTables.php index 694efaa6..2dbf8bc1 100644 --- a/maintenance/cleanupAncientTables.php +++ b/maintenance/cleanupAncientTables.php @@ -40,8 +40,9 @@ class CleanupAncientTables extends Maintenance { public function execute() { if ( !$this->hasOption( 'force' ) ) { $this->error( "This maintenance script will remove old columns and indexes.\n" - . "It is recommended to backup your database first, and ensure all your data has been migrated to newer tables\n" - . "If you want to continue, run this script again with the --force \n" + . "It is recommended to backup your database first, and ensure all your data has\n" + . "been migrated to newer tables. If you want to continue, run this script again\n" + . "with --force.\n" ); } @@ -82,7 +83,7 @@ class CleanupAncientTables extends Maintenance { if ( $db->indexExists( 'text', $index, __METHOD__ ) ) { $this->output( "Dropping index $index from the text table..." ); $db->query( "DROP INDEX " . $db->addIdentifierQuotes( $index ) - . " ON " . $db->tableName( 'text' ) ); + . " ON " . $db->tableName( 'text' ) ); $this->output( "done.\n" ); } } @@ -101,7 +102,7 @@ class CleanupAncientTables extends Maintenance { if ( $db->fieldExists( 'text', $field, __METHOD__ ) ) { $this->output( "Dropping the $field field from the text table..." ); $db->query( "ALTER TABLE " . $db->tableName( 'text' ) - . " DROP COLUMN " . $db->addIdentifierQuotes( $field ) ); + . " DROP COLUMN " . $db->addIdentifierQuotes( $field ) ); $this->output( "done.\n" ); } } diff --git a/maintenance/cleanupCaps.php b/maintenance/cleanupCaps.php index 1a47ac4e..9e88c135 100644 --- a/maintenance/cleanupCaps.php +++ b/maintenance/cleanupCaps.php @@ -7,7 +7,7 @@ * --dry-run don't actually try moving them * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -71,6 +71,7 @@ class CapsCleanup extends TableCleanup { $lower = $wgContLang->lcfirst( $row->page_title ); if ( $upper == $lower ) { $this->output( "\"$display\" already lowercase.\n" ); + return $this->progress( 0 ); } @@ -78,6 +79,7 @@ class CapsCleanup extends TableCleanup { $targetDisplay = $target->getPrefixedText(); if ( $target->exists() ) { $this->output( "\"$display\" skipped; \"$targetDisplay\" already exists\n" ); + return $this->progress( 0 ); } @@ -98,6 +100,7 @@ class CapsCleanup extends TableCleanup { } } } + return $this->progress( 0 ); } } diff --git a/maintenance/cleanupImages.php b/maintenance/cleanupImages.php index 0e0b6194..915a2c08 100644 --- a/maintenance/cleanupImages.php +++ b/maintenance/cleanupImages.php @@ -7,7 +7,7 @@ * --fix Actually clean up titles; otherwise just checks for them * * Copyright © 2005-2006 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -56,6 +56,7 @@ class ImageCleanup extends TableCleanup { if ( $source == '' ) { // Ye olde empty rows. Just kill them. $this->killRow( $source ); + return $this->progress( 1 ); } @@ -82,6 +83,7 @@ class ImageCleanup extends TableCleanup { return $this->progress( 0 ); } $this->pokeFile( $source, $safe ); + return $this->progress( 1 ); } @@ -89,6 +91,7 @@ class ImageCleanup extends TableCleanup { $munged = $title->getDBkey(); $this->output( "page $source ($munged) doesn't match self.\n" ); $this->pokeFile( $source, $munged ); + return $this->progress( 1 ); } @@ -96,7 +99,7 @@ class ImageCleanup extends TableCleanup { } /** - * @param $name string + * @param string $name */ private function killRow( $name ) { if ( $this->dryrun ) { @@ -114,6 +117,7 @@ class ImageCleanup extends TableCleanup { if ( !isset( $this->repo ) ) { $this->repo = RepoGroup::singleton()->getLocalRepo(); } + return $this->repo->getRootDirectory() . '/' . $this->repo->getHashPath( $name ) . $name; } @@ -122,7 +126,12 @@ class ImageCleanup extends TableCleanup { } private function pageExists( $name, $db ) { - return $db->selectField( 'page', '1', array( 'page_namespace' => NS_FILE, 'page_title' => $name ), __METHOD__ ); + return $db->selectField( + 'page', + '1', + array( 'page_namespace' => NS_FILE, 'page_title' => $name ), + __METHOD__ + ); } private function pokeFile( $orig, $new ) { @@ -130,6 +139,7 @@ class ImageCleanup extends TableCleanup { if ( !file_exists( $path ) ) { $this->output( "missing file: $path\n" ); $this->killRow( $orig ); + return; } @@ -145,7 +155,7 @@ class ImageCleanup extends TableCleanup { $version = 0; $final = $new; $conflict = ( $this->imageExists( $final, $db ) || - ( $this->pageExists( $orig, $db ) && $this->pageExists( $final, $db ) ) ); + ( $this->pageExists( $orig, $db ) && $this->pageExists( $final, $db ) ) ); while ( $conflict ) { $this->output( "Rename conflicts with '$final'...\n" ); @@ -179,6 +189,7 @@ class ImageCleanup extends TableCleanup { if ( !wfMkdirParents( $dir, null, __METHOD__ ) ) { $this->output( "RENAME FAILED, COULD NOT CREATE $dir" ); $db->rollback( __METHOD__ ); + return; } } @@ -205,6 +216,7 @@ class ImageCleanup extends TableCleanup { $test = Title::makeTitleSafe( NS_FILE, $x ); if ( is_null( $test ) || $test->getDBkey() !== $x ) { $this->error( "Unable to generate safe title from '$name', got '$x'" ); + return false; } diff --git a/maintenance/cleanupRemovedModules.php b/maintenance/cleanupRemovedModules.php index 84eec289..e1d0ed6e 100644 --- a/maintenance/cleanupRemovedModules.php +++ b/maintenance/cleanupRemovedModules.php @@ -36,12 +36,18 @@ class CleanupRemovedModules extends Maintenance { parent::__construct(); $this->mDescription = 'Remove cache entries for removed ResourceLoader modules from the database'; $this->addOption( 'batchsize', 'Delete rows in batches of this size. Default: 500', false, true ); - $this->addOption( 'max-slave-lag', 'If the slave lag exceeds this many seconds, wait until it drops below this value. Default: 5', false, true ); + $this->addOption( + 'max-slave-lag', + 'If the slave lag exceeds this many seconds, wait until it drops below this value. ' + . 'Default: 5', + false, + true + ); } public function execute() { $dbw = wfGetDB( DB_MASTER ); - $rl = new ResourceLoader(); + $rl = new ResourceLoader( ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) ); $moduleNames = $rl->getModuleNames(); $moduleList = implode( ', ', array_map( array( $dbw, 'addQuotes' ), $moduleNames ) ); $limit = max( 1, intval( $this->getOption( 'batchsize', 500 ) ) ); diff --git a/maintenance/cleanupSpam.php b/maintenance/cleanupSpam.php index 4b8c9feb..f4a5147a 100644 --- a/maintenance/cleanupSpam.php +++ b/maintenance/cleanupSpam.php @@ -35,11 +35,14 @@ class CleanupSpam extends Maintenance { $this->mDescription = "Cleanup all spam from a given hostname"; $this->addOption( 'all', 'Check all wikis in $wgLocalDatabases' ); $this->addOption( 'delete', 'Delete pages containing only spam instead of blanking them' ); - $this->addArg( 'hostname', 'Hostname that was spamming, single * wildcard in the beginning allowed' ); + $this->addArg( + 'hostname', + 'Hostname that was spamming, single * wildcard in the beginning allowed' + ); } public function execute() { - global $wgLocalDatabases, $wgUser; + global $IP, $wgLocalDatabases, $wgUser; $username = wfMessage( 'spambot_username' )->text(); $wgUser = User::newFromName( $username ); @@ -67,7 +70,9 @@ class CleanupSpam extends Maintenance { array( 'el_index' . $dbr->buildLike( $like ) ), __METHOD__ ); if ( $count ) { $found = true; - passthru( "php cleanupSpam.php --wiki='$wikiID' $spec | sed 's/^/$wikiID: /'" ); + $cmd = wfShellWikiCmd( "$IP/maintenance/cleanupSpam.php", + array( '--wiki', $wikiID, $spec ) ); + passthru( "$cmd | sed 's/^/$wikiID: /'" ); } } if ( $found ) { @@ -96,6 +101,7 @@ class CleanupSpam extends Maintenance { $title = Title::newFromID( $id ); if ( !$title ) { $this->error( "Internal error: no page for ID $id" ); + return; } @@ -104,7 +110,8 @@ class CleanupSpam extends Maintenance { $currentRevId = $rev->getId(); while ( $rev && ( $rev->isDeleted( Revision::DELETED_TEXT ) - || LinkFilter::matchEntry( $rev->getContent( Revision::RAW ), $domain ) ) ) { + || LinkFilter::matchEntry( $rev->getContent( Revision::RAW ), $domain ) ) + ) { $rev = $rev->getPrevious(); } @@ -121,19 +128,28 @@ class CleanupSpam extends Maintenance { $content = $rev->getContent( Revision::RAW ); $this->output( "reverting\n" ); - $page->doEditContent( $content, wfMessage( 'spam_reverting', $domain )->inContentLanguage()->text(), - EDIT_UPDATE, $rev->getId() ); + $page->doEditContent( + $content, + wfMessage( 'spam_reverting', $domain )->inContentLanguage()->text(), + EDIT_UPDATE, + $rev->getId() + ); } elseif ( $this->hasOption( 'delete' ) ) { // Didn't find a non-spammy revision, blank the page $this->output( "deleting\n" ); - $page->doDeleteArticle( wfMessage( 'spam_deleting', $domain )->inContentLanguage()->text() ); + $page->doDeleteArticle( + wfMessage( 'spam_deleting', $domain )->inContentLanguage()->text() + ); } else { // Didn't find a non-spammy revision, blank the page $handler = ContentHandler::getForTitle( $title ); $content = $handler->makeEmptyContent(); $this->output( "blanking\n" ); - $page->doEditContent( $content, wfMessage( 'spam_blanking', $domain )->inContentLanguage()->text() ); + $page->doEditContent( + $content, + wfMessage( 'spam_blanking', $domain )->inContentLanguage()->text() + ); } $dbw->commit( __METHOD__ ); } diff --git a/maintenance/cleanupTitles.php b/maintenance/cleanupTitles.php index 5b5ef184..0df9e7fe 100644 --- a/maintenance/cleanupTitles.php +++ b/maintenance/cleanupTitles.php @@ -7,7 +7,7 @@ * --fix Actually clean up titles; otherwise just checks for them * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -148,9 +148,15 @@ class TitleCleanup extends TableCleanup { $ns = 0; } + # Namespace which no longer exists. Put the page in the main namespace + # since we don't have any idea of the old namespace name. See bug 68501. + if ( !MWNamespace::exists( $ns ) ) { + $ns = 0; + } + $clean = 'Broken/' . $prior; $verified = Title::makeTitleSafe( $ns, $clean ); - if ( $verified->exists() ) { + if ( !$verified || $verified->exists() ) { $blah = "Broken/id:" . $row->page_id; $this->output( "Couldn't legalize; form '$clean' exists; using '$blah'\n" ); $verified = Title::makeTitleSafe( $ns, $blah ); diff --git a/maintenance/cleanupUploadStash.php b/maintenance/cleanupUploadStash.php index c2ba5558..24b63a8b 100644 --- a/maintenance/cleanupUploadStash.php +++ b/maintenance/cleanupUploadStash.php @@ -38,6 +38,7 @@ class UploadStashCleanup extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Clean up abandoned files in temporary uploaded file stash"; + $this->setBatchSize( 50 ); } public function execute() { @@ -81,10 +82,9 @@ class UploadStashCleanup extends Maintenance { try { $stash->getFile( $key, true ); $stash->removeFileNoAuth( $key ); - } catch ( UploadStashBadPathException $ex ) { - $this->output( "Failed removing stashed upload with key: $key\n" ); - } catch ( UploadStashZeroLengthFileException $ex ) { - $this->output( "Failed removing stashed upload with key: $key\n" ); + } catch ( UploadStashException $ex ) { + $type = get_class( $ex ); + $this->output( "Failed removing stashed upload with key: $key ($type)\n" ); } if ( $i % 100 == 0 ) { $this->output( "$i\n" ); @@ -95,20 +95,25 @@ class UploadStashCleanup extends Maintenance { // Delete all the corresponding thumbnails... $dir = $tempRepo->getZonePath( 'thumb' ); - $iterator = $tempRepo->getBackend()->getFileList( array( 'dir' => $dir ) ); + $iterator = $tempRepo->getBackend()->getFileList( array( 'dir' => $dir, 'adviseStat' => 1 ) ); $this->output( "Deleting old thumbnails...\n" ); $i = 0; + $batch = array(); // operation batch foreach ( $iterator as $file ) { if ( wfTimestamp( TS_UNIX, $tempRepo->getFileTimestamp( "$dir/$file" ) ) < $cutoff ) { - $status = $tempRepo->quickPurge( "$dir/$file" ); - if ( !$status->isOK() ) { - $this->error( print_r( $status->getErrorsArray(), true ) ); - } - if ( ( ++$i % 100 ) == 0 ) { + $batch[] = array( 'op' => 'delete', 'src' => "$dir/$file" ); + if ( count( $batch ) >= $this->mBatchSize ) { + $this->doOperations( $tempRepo, $batch ); + $i += count( $batch ); + $batch = array(); $this->output( "$i\n" ); } } } + if ( count( $batch ) ) { + $this->doOperations( $tempRepo, $batch ); + $i += count( $batch ); + } $this->output( "$i done\n" ); // Apparently lots of stash files are not registered in the DB... @@ -119,24 +124,31 @@ class UploadStashCleanup extends Maintenance { $this->error( "Temp repo is not using the temp container.", 1 ); // die } $i = 0; + $batch = array(); // operation batch foreach ( $iterator as $file ) { - // Absolute sanity check for stashed files and file segments - if ( !preg_match( '#(^\d{14}!|\.\d+\.\w+\.\d+$)#', basename( $file ) ) ) { - $this->output( "Skipped non-stash $file\n" ); - continue; - } if ( wfTimestamp( TS_UNIX, $tempRepo->getFileTimestamp( "$dir/$file" ) ) < $cutoff ) { - $status = $tempRepo->quickPurge( "$dir/$file" ); - if ( !$status->isOK() ) { - $this->error( print_r( $status->getErrorsArray(), true ) ); - } - if ( ( ++$i % 100 ) == 0 ) { + $batch[] = array( 'op' => 'delete', 'src' => "$dir/$file" ); + if ( count( $batch ) >= $this->mBatchSize ) { + $this->doOperations( $tempRepo, $batch ); + $i += count( $batch ); + $batch = array(); $this->output( "$i\n" ); } } } + if ( count( $batch ) ) { + $this->doOperations( $tempRepo, $batch ); + $i += count( $batch ); + } $this->output( "$i done\n" ); } + + protected function doOperations( FileRepo $tempRepo, array $ops ) { + $status = $tempRepo->getBackend()->doQuickOperations( $ops ); + if ( !$status->isOK() ) { + $this->error( print_r( $status->getErrorsArray(), true ) ); + } + } } $maintClass = "UploadStashCleanup"; diff --git a/maintenance/cleanupWatchlist.php b/maintenance/cleanupWatchlist.php index f1a7b481..eb7d7b18 100644 --- a/maintenance/cleanupWatchlist.php +++ b/maintenance/cleanupWatchlist.php @@ -7,7 +7,7 @@ * --fix Actually remove entries; without will only report. * * Copyright © 2005,2006 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -65,9 +65,11 @@ class WatchlistCleanup extends TableCleanup { $title = Title::newFromText( $verified ); if ( $row->wl_user == 0 || is_null( $title ) || !$title->equals( $current ) ) { - $this->output( "invalid watch by {$row->wl_user} for ({$row->wl_namespace}, \"{$row->wl_title}\")\n" ); + $this->output( "invalid watch by {$row->wl_user} for " + . "({$row->wl_namespace}, \"{$row->wl_title}\")\n" ); $updated = $this->removeWatch( $row ); $this->progress( $updated ); + return; } $this->progress( 0 ); @@ -76,12 +78,16 @@ class WatchlistCleanup extends TableCleanup { private function removeWatch( $row ) { if ( !$this->dryrun && $this->hasOption( 'fix' ) ) { $dbw = wfGetDB( DB_MASTER ); - $dbw->delete( 'watchlist', array( + $dbw->delete( + 'watchlist', array( 'wl_user' => $row->wl_user, 'wl_namespace' => $row->wl_namespace, 'wl_title' => $row->wl_title ), - __METHOD__ ); + __METHOD__ + ); + $this->output( "- removed\n" ); + return 1; } else { return 0; diff --git a/maintenance/commandLine.inc b/maintenance/commandLine.inc index be071422..88776f4f 100644 --- a/maintenance/commandLine.inc +++ b/maintenance/commandLine.inc @@ -23,14 +23,18 @@ require_once __DIR__ . '/Maintenance.php'; +// @codingStandardsIgnoreStart MediaWiki.NamingConventions.ValidGlobalName.wgPrefix global $optionsWithArgs; +// @codingStandardsIgnoreEnd if ( !isset( $optionsWithArgs ) ) { $optionsWithArgs = array(); } class CommandLineInc extends Maintenance { public function __construct() { + // @codingStandardsIgnoreStart MediaWiki.NamingConventions.ValidGlobalName.wgPrefix global $optionsWithArgs; + // @codingStandardsIgnoreEnd parent::__construct(); foreach ( $optionsWithArgs as $name ) { $this->addOption( $name, '', false, true ); @@ -39,6 +43,7 @@ class CommandLineInc extends Maintenance { /** * No help, it would just be misleading since it misses custom options + * @param bool $force */ protected function maybeHelp( $force = false ) { if ( !$force ) { @@ -48,7 +53,9 @@ class CommandLineInc extends Maintenance { } public function execute() { + // @codingStandardsIgnoreStart MediaWiki.NamingConventions.ValidGlobalName.wgPrefix global $args, $options; + // @codingStandardsIgnoreEnd $args = $this->mArgs; $options = $this->mOptions; } diff --git a/maintenance/compareParserCache.php b/maintenance/compareParserCache.php new file mode 100644 index 00000000..98441b60 --- /dev/null +++ b/maintenance/compareParserCache.php @@ -0,0 +1,104 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + */ + +require_once __DIR__ . '/Maintenance.php'; + +/** + * @ingroup Maintenance + */ +class CompareParserCache extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Parse random pages and compare output to cache."; + $this->addOption( 'namespace', 'Page namespace number', true, true ); + $this->addOption( 'maxpages', 'Number of pages to try', true, true ); + } + + public function execute() { + $pages = $this->getOption( 'maxpages' ); + + $dbr = $this->getDB( DB_SLAVE ); + + $totalsec = 0.0; + $scanned = 0; + $withcache = 0; + $withdiff = 0; + while ( $pages-- > 0 ) { + $row = $dbr->selectRow( 'page', '*', + array( + 'page_namespace' => $this->getOption( 'namespace' ), + 'page_is_redirect' => 0, + 'page_random >= ' . wfRandom() + ), + __METHOD__, + array( + 'ORDER BY' => 'page_random', + ) + ); + + if ( !$row ) { + continue; + } + ++$scanned; + + $title = Title::newFromRow( $row ); + $page = WikiPage::factory( $title ); + $revision = $page->getRevision(); + $content = $revision->getContent( Revision::RAW ); + + $parserOptions = $page->makeParserOptions( 'canonical' ); + + $parserOutputOld = ParserCache::singleton()->get( $page, $parserOptions ); + + if ( $parserOutputOld ) { + $t1 = microtime( true ); + $parserOutputNew = $content->getParserOutput( + $title, $revision->getId(), $parserOptions, false ); + $sec = microtime( true ) - $t1; + $totalsec += $sec; + + $this->output( "Parsed '{$title->getPrefixedText()}' in $sec seconds.\n" ); + + $this->output( "Found cache entry found for '{$title->getPrefixedText()}'..." ); + $oldHtml = trim( preg_replace( '#<!-- .+-->#Us', '', $parserOutputOld->getText() ) ); + $newHtml = trim( preg_replace( '#<!-- .+-->#Us', '', $parserOutputNew->getText() ) ); + $diff = wfDiff( $oldHtml, $newHtml ); + if ( strlen( $diff ) ) { + $this->output( "differences found:\n\n$diff\n\n" ); + ++$withdiff; + } else { + $this->output( "No differences found.\n" ); + } + ++$withcache; + } else { + $this->output( "No parser cache entry found for '{$title->getPrefixedText()}'.\n" ); + } + } + + $ave = $totalsec ? $totalsec / $scanned : 0; + $this->output( "Checked $scanned pages; $withcache had prior cache entries.\n" ); + $this->output( "Pages with differences found: $withdiff\n" ); + $this->output( "Average parse time: $ave sec\n" ); + } +} + +$maintClass = "CompareParserCache"; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/compareParsers.php b/maintenance/compareParsers.php index fabc2571..e67c439b 100644 --- a/maintenance/compareParsers.php +++ b/maintenance/compareParsers.php @@ -7,7 +7,7 @@ * Templates etc are pulled from the local wiki database, not from the dump. * * Copyright © 2011 Platonides - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,11 +47,31 @@ class CompareParsers extends DumpIterator { $this->addOption( 'parser1', 'The first parser to compare.', true, true ); $this->addOption( 'parser2', 'The second parser to compare.', true, true ); $this->addOption( 'tidy', 'Run tidy on the articles.', false, false ); - $this->addOption( 'save-failed', 'Folder in which articles which differ will be stored.', false, true ); + $this->addOption( + 'save-failed', + 'Folder in which articles which differ will be stored.', + false, + true + ); $this->addOption( 'show-diff', 'Show a diff of the two renderings.', false, false ); - $this->addOption( 'diff-bin', 'Binary to use for diffing (can also be provided by DIFF env var).', false, false ); - $this->addOption( 'strip-parameters', 'Remove parameters of html tags to increase readability.', false, false ); - $this->addOption( 'show-parsed-output', 'Show the parsed html if both Parsers give the same output.', false, false ); + $this->addOption( + 'diff-bin', + 'Binary to use for diffing (can also be provided by DIFF env var).', + false, + false + ); + $this->addOption( + 'strip-parameters', + 'Remove parameters of html tags to increase readability.', + false, + false + ); + $this->addOption( + 'show-parsed-output', + 'Show the parsed html if both Parsers give the same output.', + false, + false + ); } public function checkOptions() { @@ -96,12 +116,13 @@ class CompareParsers extends DumpIterator { if ( !$this->stripParametersEnabled ) { return $text; } + return preg_replace( '/(<a) [^>]+>/', '$1>', $text ); } /** * Callback function for each revision, parse with both parsers and compare - * @param $rev Revision + * @param Revision $rev */ public function processRevision( $rev ) { $title = $rev->getTitle(); @@ -118,7 +139,9 @@ class CompareParsers extends DumpIterator { $content = $rev->getContent(); if ( $content->getModel() !== CONTENT_MODEL_WIKITEXT ) { - $this->error( "Page {$title->getPrefixedText()} does not contain wikitext but {$content->getModel()}\n" ); + $this->error( "Page {$title->getPrefixedText()} does not contain wikitext " + . "but {$content->getModel()}\n" ); + return; } @@ -132,13 +155,21 @@ class CompareParsers extends DumpIterator { $this->error( "Parsing for {$title->getPrefixedText()} differs\n" ); if ( $this->saveFailed ) { - file_put_contents( $this->saveFailed . '/' . rawurlencode( $title->getPrefixedText() ) . ".txt", $text ); + file_put_contents( + $this->saveFailed . '/' . rawurlencode( $title->getPrefixedText() ) . ".txt", + $text + ); } if ( $this->showDiff ) { - $this->output( wfDiff( $this->stripParameters( $output1->getText() ), $this->stripParameters( $output2->getText() ), '' ) ); + $this->output( wfDiff( + $this->stripParameters( $output1->getText() ), + $this->stripParameters( $output2->getText() ), + '' + ) ); } } else { $this->output( $title->getPrefixedText() . "\tOK\n" ); + if ( $this->showParsedOutput ) { $this->output( $this->stripParameters( $output1->getText() ) ); } @@ -149,10 +180,9 @@ class CompareParsers extends DumpIterator { /* Look for the parser in a file appropiately named in the current folder */ if ( !class_exists( $parserName ) && file_exists( "$parserName.php" ) ) { global $wgAutoloadClasses; - $wgAutoloadClasses[ $parserName ] = realpath( '.' ) . "/$parserName.php"; + $wgAutoloadClasses[$parserName] = realpath( '.' ) . "/$parserName.php"; } } - } $maintClass = "CompareParsers"; diff --git a/maintenance/convertLinks.php b/maintenance/convertLinks.php index 17b91110..221ebe37 100644 --- a/maintenance/convertLinks.php +++ b/maintenance/convertLinks.php @@ -36,14 +36,29 @@ class ConvertLinks extends Maintenance { public function __construct() { parent::__construct(); - $this->mDescription = "Convert from the old links schema (string->ID) to the new schema (ID->ID). -The wiki should be put into read-only mode while this script executes"; + $this->mDescription = + "Convert from the old links schema (string->ID) to the new schema (ID->ID)." + . "The wiki should be put into read-only mode while this script executes"; $this->addArg( 'logperformance', "Log performance to perfLogFilename.", false ); - $this->addArg( 'perfLogFilename', "Filename where performance is logged if --logperformance was set (defaults to 'convLinksPerf.txt').", false ); - $this->addArg( 'keep-links-table', "Don't overwrite the old links table with the new one, leave the new table at links_temp.", false ); - $this->addArg( 'nokeys', "Don't create keys, and so allow duplicates in the new links table.\n -This gives a huge speed improvement for very large links tables which are MyISAM." /* (What about InnoDB?) */, false ); + $this->addArg( + 'perfLogFilename', + "Filename where performance is logged if --logperformance was set " + . "(defaults to 'convLinksPerf.txt').", + false + ); + $this->addArg( + 'keep-links-table', + "Don't overwrite the old links table with the new one, leave the new table at links_temp.", + false + ); + $this->addArg( + 'nokeys', + /* (What about InnoDB?) */ + "Don't create keys, and so allow duplicates in the new links table.\n" + . "This gives a huge speed improvement for very large links tables which are MyISAM.", + false + ); } public function getDbType() { @@ -56,22 +71,34 @@ This gives a huge speed improvement for very large links tables which are MyISAM $type = $dbw->getType(); if ( $type != 'mysql' ) { $this->output( "Link table conversion not necessary for $type\n" ); + return; } global $wgContLang; - $numBadLinks = $curRowsRead = 0; # counters etc - $totalTuplesInserted = 0; # total tuples INSERTed into links_temp + # counters etc + $numBadLinks = $curRowsRead = 0; + + # total tuples INSERTed into links_temp + $totalTuplesInserted = 0; + + # whether or not to give progress reports while reading IDs from cur table + $reportCurReadProgress = true; - $reportCurReadProgress = true; # whether or not to give progress reports while reading IDs from cur table - $curReadReportInterval = 1000; # number of rows between progress reports + # number of rows between progress reports + $curReadReportInterval = 1000; - $reportLinksConvProgress = true; # whether or not to give progress reports during conversion - $linksConvInsertInterval = 1000; # number of rows per INSERT + # whether or not to give progress reports during conversion + $reportLinksConvProgress = true; + + # number of rows per INSERT + $linksConvInsertInterval = 1000; $initialRowOffset = 0; - # $finalRowOffset = 0; # not used yet; highest row number from links table to process + + # not used yet; highest row number from links table to process + # $finalRowOffset = 0; $overwriteLinksTable = !$this->hasOption( 'keep-links-table' ); $noKeys = $this->hasOption( 'noKeys' ); @@ -80,16 +107,19 @@ This gives a huge speed improvement for very large links tables which are MyISAM # -------------------------------------------------------------------- - list( $cur, $links, $links_temp, $links_backup ) = $dbw->tableNamesN( 'cur', 'links', 'links_temp', 'links_backup' ); + list( $cur, $links, $links_temp, $links_backup ) = + $dbw->tableNamesN( 'cur', 'links', 'links_temp', 'links_backup' ); if ( $dbw->tableExists( 'pagelinks' ) ) { $this->output( "...have pagelinks; skipping old links table updates\n" ); + return; } $res = $dbw->query( "SELECT l_from FROM $links LIMIT 1" ); if ( $dbw->fieldType( $res, 0 ) == "int" ) { $this->output( "Schema already converted\n" ); + return; } @@ -104,17 +134,17 @@ This gives a huge speed improvement for very large links tables which are MyISAM } else { $fh = false; if ( $this->logPerformance ) { - $fh = fopen ( $perfLogFilename, "w" ); + $fh = fopen( $perfLogFilename, "w" ); if ( !$fh ) { $this->error( "Couldn't open $perfLogFilename" ); $this->logPerformance = false; } } - $baseTime = $startTime = $this->getMicroTime(); + $baseTime = $startTime = microtime( true ); # Create a title -> cur_id map $this->output( "Loading IDs from $cur table...\n" ); - $this->performanceLog ( $fh, "Reading $numRows rows from cur table...\n" ); - $this->performanceLog ( $fh, "rows read vs seconds elapsed:\n" ); + $this->performanceLog( $fh, "Reading $numRows rows from cur table...\n" ); + $this->performanceLog( $fh, "rows read vs seconds elapsed:\n" ); $dbw->bufferResults( false ); $res = $dbw->query( "SELECT cur_namespace,cur_title,cur_id FROM $cur" ); @@ -129,7 +159,10 @@ This gives a huge speed improvement for very large links tables which are MyISAM $curRowsRead++; if ( $reportCurReadProgress ) { if ( ( $curRowsRead % $curReadReportInterval ) == 0 ) { - $this->performanceLog( $fh, $curRowsRead . " " . ( $this->getMicroTime() - $baseTime ) . "\n" ); + $this->performanceLog( + $fh, + $curRowsRead . " " . ( microtime( true ) - $baseTime ) . "\n" + ); $this->output( "\t$curRowsRead rows of $cur table read.\n" ); } } @@ -137,7 +170,10 @@ This gives a huge speed improvement for very large links tables which are MyISAM $dbw->freeResult( $res ); $dbw->bufferResults( true ); $this->output( "Finished loading IDs.\n\n" ); - $this->performanceLog( $fh, "Took " . ( $this->getMicroTime() - $baseTime ) . " seconds to load IDs.\n\n" ); + $this->performanceLog( + $fh, + "Took " . ( microtime( true ) - $baseTime ) . " seconds to load IDs.\n\n" + ); # -------------------------------------------------------------------- @@ -145,12 +181,14 @@ This gives a huge speed improvement for very large links tables which are MyISAM # convert, and write to the new table. $this->createTempTable(); $this->performanceLog( $fh, "Resetting timer.\n\n" ); - $baseTime = $this->getMicroTime(); + $baseTime = microtime( true ); $this->output( "Processing $numRows rows from $links table...\n" ); $this->performanceLog( $fh, "Processing $numRows rows from $links table...\n" ); $this->performanceLog( $fh, "rows inserted vs seconds elapsed:\n" ); - for ( $rowOffset = $initialRowOffset; $rowOffset < $numRows; $rowOffset += $linksConvInsertInterval ) { + for ( $rowOffset = $initialRowOffset; $rowOffset < $numRows; + $rowOffset += $linksConvInsertInterval + ) { $sqlRead = "SELECT * FROM $links "; $sqlRead = $dbw->limitResult( $sqlRead, $linksConvInsertInterval, $rowOffset ); $res = $dbw->query( $sqlRead ); @@ -176,7 +214,8 @@ This gives a huge speed improvement for very large links tables which are MyISAM } } $dbw->freeResult( $res ); - # $this->output( "rowOffset: $rowOffset\ttuplesAdded: $tuplesAdded\tnumBadLinks: $numBadLinks\n" ); + # $this->output( "rowOffset: $rowOffset\ttuplesAdded: " + # . "$tuplesAdded\tnumBadLinks: $numBadLinks\n" ); if ( $tuplesAdded != 0 ) { if ( $reportLinksConvProgress ) { $this->output( "Inserting $tuplesAdded tuples into $links_temp..." ); @@ -185,15 +224,25 @@ This gives a huge speed improvement for very large links tables which are MyISAM $totalTuplesInserted += $tuplesAdded; if ( $reportLinksConvProgress ) { $this->output( " done. Total $totalTuplesInserted tuples inserted.\n" ); - $this->performanceLog( $fh, $totalTuplesInserted . " " . ( $this->getMicroTime() - $baseTime ) . "\n" ); + $this->performanceLog( + $fh, + $totalTuplesInserted . " " . ( microtime( true ) - $baseTime ) . "\n" + ); } } } - $this->output( "$totalTuplesInserted valid titles and $numBadLinks invalid titles were processed.\n\n" ); - $this->performanceLog( $fh, "$totalTuplesInserted valid titles and $numBadLinks invalid titles were processed.\n" ); - $this->performanceLog( $fh, "Total execution time: " . ( $this->getMicroTime() - $startTime ) . " seconds.\n" ); + $this->output( "$totalTuplesInserted valid titles and " + . "$numBadLinks invalid titles were processed.\n\n" ); + $this->performanceLog( + $fh, + "$totalTuplesInserted valid titles and $numBadLinks invalid titles were processed.\n" + ); + $this->performanceLog( + $fh, + "Total execution time: " . ( microtime( true ) - $startTime ) . " seconds.\n" + ); if ( $this->logPerformance ) { - fclose ( $fh ); + fclose( $fh ); } } # -------------------------------------------------------------------- @@ -209,7 +258,6 @@ This gives a huge speed improvement for very large links tables which are MyISAM $dbw->query( "RENAME TABLE links TO $links_backup, $links_temp TO $links", __METHOD__ ); $this->output( " done.\n\n" ); - $dbw->close(); $this->output( "Conversion complete. The old table remains at $links_backup;\n" ); $this->output( "delete at your leisure.\n" ); } else { @@ -223,6 +271,7 @@ This gives a huge speed improvement for very large links tables which are MyISAM if ( !( $dbConn->isOpen() ) ) { $this->output( "Opening connection to database failed.\n" ); + return; } $links_temp = $dbConn->tableName( 'links_temp' ); @@ -234,14 +283,14 @@ This gives a huge speed improvement for very large links tables which are MyISAM $this->output( "Creating temporary links table..." ); if ( $this->hasOption( 'noKeys' ) ) { $dbConn->query( "CREATE TABLE $links_temp ( " . - "l_from int(8) unsigned NOT NULL default '0', " . - "l_to int(8) unsigned NOT NULL default '0')" ); + "l_from int(8) unsigned NOT NULL default '0', " . + "l_to int(8) unsigned NOT NULL default '0')" ); } else { $dbConn->query( "CREATE TABLE $links_temp ( " . - "l_from int(8) unsigned NOT NULL default '0', " . - "l_to int(8) unsigned NOT NULL default '0', " . - "UNIQUE KEY l_from(l_from,l_to), " . - "KEY (l_to))" ); + "l_from int(8) unsigned NOT NULL default '0', " . + "l_to int(8) unsigned NOT NULL default '0', " . + "UNIQUE KEY l_from(l_from,l_to), " . + "KEY (l_to))" ); } $this->output( " done.\n\n" ); } @@ -251,11 +300,6 @@ This gives a huge speed improvement for very large links tables which are MyISAM fwrite( $fh, $text ); } } - - private function getMicroTime() { # return time in seconds, with microsecond accuracy - list( $usec, $sec ) = explode( " ", microtime() ); - return ( (float)$usec + (float)$sec ); - } } $maintClass = "ConvertLinks"; diff --git a/maintenance/convertUserOptions.php b/maintenance/convertUserOptions.php index 34c643bb..1542a8c3 100644 --- a/maintenance/convertUserOptions.php +++ b/maintenance/convertUserOptions.php @@ -26,8 +26,6 @@ require_once __DIR__ . '/Maintenance.php'; /** * Maintenance script to convert user options to the new `user_properties` table. * - * Do each user sequentially, since accounts can't be deleted - * * @ingroup Maintenance */ class ConvertUserOptions extends Maintenance { @@ -37,6 +35,7 @@ class ConvertUserOptions extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Convert user options from old to new system"; + $this->setBatchSize( 50 ); } public function execute() { @@ -46,17 +45,23 @@ class ConvertUserOptions extends Maintenance { if ( !$dbw->fieldExists( 'user', 'user_options', __METHOD__ ) ) { $this->output( "nothing to migrate. " ); + return; } while ( $id !== null ) { - $idCond = 'user_id > ' . $dbw->addQuotes( $id ); - $optCond = "user_options != " . $dbw->addQuotes( '' ); // For compatibility - $res = $dbw->select( 'user', '*', - array( $optCond, $idCond ), __METHOD__, - array( 'LIMIT' => 50, 'FOR UPDATE' ) + $res = $dbw->select( 'user', + array( 'user_id', 'user_options' ), + array( + 'user_id > ' . $dbw->addQuotes( $id ), + "user_options != " . $dbw->addQuotes( '' ), + ), + __METHOD__, + array( + 'ORDER BY' => 'user_id', + 'LIMIT' => $this->mBatchSize, + ) ); $id = $this->convertOptionBatch( $res, $dbw ); - $dbw->commit( __METHOD__ ); wfWaitForSlaves(); @@ -68,20 +73,37 @@ class ConvertUserOptions extends Maintenance { } /** - * @param $res - * @param $dbw DatabaseBase + * @param ResultWrapper $res + * @param DatabaseBase $dbw * @return null|int */ function convertOptionBatch( $res, $dbw ) { $id = null; foreach ( $res as $row ) { $this->mConversionCount++; + $insertRows = array(); + foreach ( explode( "\n", $row->user_options ) as $s ) { + $m = array(); + if ( !preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) { + continue; + } - $u = User::newFromRow( $row ); + // MW < 1.16 would save even default values. Filter them out + // here (as in User) to avoid adding many unnecessary rows. + $defaultOption = User::getDefaultOption( $m[1] ); + if ( is_null( $defaultOption ) || $m[2] != $defaultOption ) { + $insertRows[] = array( + 'up_user' => $row->user_id, + 'up_property' => $m[1], + 'up_value' => $m[2], + ); + } + } - $u->saveSettings(); + if ( count( $insertRows ) ) { + $dbw->insert( 'user_properties', $insertRows, __METHOD__, array( 'IGNORE' ) ); + } - // Do this here as saveSettings() doesn't set user_options to '' anymore! $dbw->update( 'user', array( 'user_options' => '' ), diff --git a/maintenance/copyFileBackend.php b/maintenance/copyFileBackend.php index 21ef4ffa..9ed63c3c 100644 --- a/maintenance/copyFileBackend.php +++ b/maintenance/copyFileBackend.php @@ -35,7 +35,8 @@ require_once __DIR__ . '/Maintenance.php'; * @ingroup Maintenance */ class CopyFileBackend extends Maintenance { - protected $statCache = array(); + /** @var array|null (path sha1 => stat) Pre-computed dst stat entries from listings */ + protected $statCache = null; public function __construct() { parent::__construct(); @@ -98,7 +99,7 @@ class CopyFileBackend extends Maintenance { if ( $dstPathsRel === null ) { $this->error( "Could not list files in $container.", 1 ); // die } - $this->statCache = array(); // clear + $this->statCache = array(); foreach ( $dstPathsRel as $dstPathRel ) { $path = $dst->getRootStoragePath() . "/$backendRel/$dstPathRel"; $this->statCache[sha1( $path )] = $dst->getFileStat( array( 'src' => $path ) ); @@ -238,8 +239,8 @@ class CopyFileBackend extends Maintenance { $this->error( "$wikiId: Detected illegal (non-UTF8) path for $srcPath." ); continue; } elseif ( !$this->hasOption( 'missingonly' ) - && $this->filesAreSame( $src, $dst, $srcPath, $dstPath ) ) - { + && $this->filesAreSame( $src, $dst, $srcPath, $dstPath ) + ) { $this->output( "\tAlready have $srcPathRel.\n" ); continue; // assume already copied... } @@ -338,18 +339,42 @@ class CopyFileBackend extends Maintenance { $skipHash = $this->hasOption( 'skiphash' ); $srcStat = $src->getFileStat( array( 'src' => $sPath ) ); $dPathSha1 = sha1( $dPath ); - $dstStat = isset( $this->statCache[$dPathSha1] ) - ? $this->statCache[$dPathSha1] - : $dst->getFileStat( array( 'src' => $dPath ) ); - return ( + if ( $this->statCache !== null ) { + // All dst files are already in stat cache + $dstStat = isset( $this->statCache[$dPathSha1] ) + ? $this->statCache[$dPathSha1] + : false; + } else { + $dstStat = $dst->getFileStat( array( 'src' => $dPath ) ); + } + // Initial fast checks to see if files are obviously different + $sameFast = ( is_array( $srcStat ) // sanity check that source exists && is_array( $dstStat ) // dest exists && $srcStat['size'] === $dstStat['size'] - && ( !$skipHash || $srcStat['mtime'] <= $dstStat['mtime'] ) - && ( $skipHash || $src->getFileSha1Base36( array( 'src' => $sPath, 'latest' => 1 ) ) - === $dst->getFileSha1Base36( array( 'src' => $dPath, 'latest' => 1 ) ) - ) ); + // More thorough checks against files + if ( !$sameFast ) { + $same = false; // no need to look farther + } elseif ( isset( $srcStat['md5'] ) && isset( $dstStat['md5'] ) ) { + // If MD5 was already in the stat info, just use it. + // This is useful as many objects stores can return this in object listing, + // so we can use it to avoid slow per-file HEADs. + $same = ( $srcStat['md5'] === $dstStat['md5'] ); + } elseif ( $skipHash ) { + // This mode is good for copying to a backup location or resyncing clone + // backends in FileBackendMultiWrite (since they get writes second, they have + // higher timestamps). However, when copying the other way, this hits loads of + // false positives (possibly 100%) and wastes a bunch of time on GETs/PUTs. + $same = ( $srcStat['mtime'] <= $dstStat['mtime'] ); + } else { + // This is the slowest method which does many per-file HEADs (unless an object + // store tracks SHA-1 in listings). + $same = ( $src->getFileSha1Base36( array( 'src' => $sPath, 'latest' => 1 ) ) + === $dst->getFileSha1Base36( array( 'src' => $dPath, 'latest' => 1 ) ) ); + } + + return $same; } } diff --git a/maintenance/copyJobQueue.php b/maintenance/copyJobQueue.php index e833115b..a9c9547e 100644 --- a/maintenance/copyJobQueue.php +++ b/maintenance/copyJobQueue.php @@ -78,19 +78,18 @@ class CopyJobQueue extends Maintenance { ++$total; $batch[] = $job; if ( count( $batch ) >= $this->mBatchSize ) { - if ( $dst->push( $batch ) ) { - $totalOK += count( $batch ); - } + $dst->push( $batch ); + $totalOK += count( $batch ); $batch = array(); $dst->waitForBackups(); } } if ( count( $batch ) ) { - if ( $dst->push( $batch ) ) { - $totalOK += count( $batch ); - } + $dst->push( $batch ); + $totalOK += count( $batch ); $dst->waitForBackups(); } + return array( $total, $totalOK ); } } diff --git a/maintenance/createAndPromote.php b/maintenance/createAndPromote.php index aa25ee60..79f72542 100644 --- a/maintenance/createAndPromote.php +++ b/maintenance/createAndPromote.php @@ -31,13 +31,15 @@ require_once __DIR__ . '/Maintenance.php'; * @ingroup Maintenance */ class CreateAndPromote extends Maintenance { - - static $permitRoles = array( 'sysop', 'bureaucrat', 'bot' ); + private static $permitRoles = array( 'sysop', 'bureaucrat', 'bot' ); public function __construct() { parent::__construct(); $this->mDescription = "Create a new user account and/or grant it additional rights"; - $this->addOption( "force", "If acccount exists already, just grant it rights or change password." ); + $this->addOption( + 'force', + 'If acccount exists already, just grant it rights or change password.' + ); foreach ( self::$permitRoles as $role ) { $this->addOption( $role, "Add the account to the {$role} group" ); } @@ -67,10 +69,14 @@ class CreateAndPromote extends Maintenance { $inGroups = $user->getGroups(); } - $promotions = array_diff( array_filter( self::$permitRoles, array( $this, 'hasOption' ) ), $inGroups ); + $promotions = array_diff( + array_filter( self::$permitRoles, array( $this, 'hasOption' ) ), + $inGroups + ); if ( $exists && !$password && count( $promotions ) === 0 ) { $this->output( "Account exists and nothing to do.\n" ); + return; } elseif ( count( $promotions ) !== 0 ) { $promoText = "User:{$username} into " . implode( ', ', $promotions ) . "...\n"; diff --git a/maintenance/cssjanus/COPYING b/maintenance/cssjanus/COPYING deleted file mode 100644 index 3f2c8953..00000000 --- a/maintenance/cssjanus/COPYING +++ /dev/null @@ -1,13 +0,0 @@ - Copyright 2008 Google Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/maintenance/cssjanus/LICENSE b/maintenance/cssjanus/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/maintenance/cssjanus/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/maintenance/cssjanus/README b/maintenance/cssjanus/README deleted file mode 100644 index 1b96d1a2..00000000 --- a/maintenance/cssjanus/README +++ /dev/null @@ -1,91 +0,0 @@ -=CSSJanus= - -_Flips CSS from LTR to an RTL orienation and vice-versa_ - -Author: `Lindsey Simon <elsigh@google.com>` - -==Introduction== - -CSSJanus is CSS parser utility designed to aid the conversion of a website's -layout from left-to-right(LTR) to right-to-left(RTL). The script was born out of -a need to convert CSS for RTL languages when tables are not being used for layout (since tables will automatically reorder TD's in RTL). -CSSJanus will change most of the obvious CSS property names and their values as -well as some not-so-obvious ones (cursor, background-position %, etc...). -The script is designed to offer flexibility to account for cases when you do -not want to change certain rules which exist to account for bidirectional text -display bugs, as well as situations where you may or may not want to flip annotations inside of the background url string. -Note that you can disable CSSJanus from running on an entire class or any -rule within a class by prepending a /* @noflip */ comment before the rule(s) -you want CSSJanus to ignore. - -CSSJanus itself is not always enough to make a website that works in a LTR -language context work in a RTL language all the way, but it is a start. - -==Getting the code== - -View the trunk at: - - http://cssjanus.googlecode.com/svn/trunk/ - -Check out the latest development version anonymously with: - -{{{ - $ svn checkout http://cssjanus.googlecode.com/svn/trunk/ cssjanus -}}} - -==Using== - -Usage: - ./cssjanus.py < file.css > file-rtl.css -Flags: - --swap_left_right_in_url: Fixes "left"/"right" string within urls. - Ex: ./cssjanus.py --swap_left_right_in_url < file.css > file_rtl.css - --swap_ltr_rtl_in_url: Fixes "ltr"/"rtl" string within urls. - Ex: ./cssjanus.py --swap_ltr_rtl_in_url < file.css > file_rtl.css - -If you'd like to make use of the webapp version of cssjanus, you'll need to -download the Google App Engine SDK - http://code.google.com/appengine/downloads.html -and also drop a "django" directory into this directory, with the latest svn -from django. You should be good to go with that setup. Please let me know -otherwise. - -==Bugs, Patches== - -Patches and bug reports are welcome, just please keep the style -consistent with the original source. If you find a bug, please include a diff -of cssjanus_test.py with the bug included as a new unit test which fails. It -will make understanding and fixing the bug easier. - -==Todo== - -* Include some helpers for some typical bidi text solutions? -* Aural CSS (azimuth) swapping? - -==Contributors== - -Additional thanks to Mike Samuel for his work on csslex.py, Andy Perelson for -his help coding and reviewing, Stephen Zabel for his help with i18n and my sanity, -and to Eric Meyer for his thoughtful input. -Thanks to Junyu Wang for the Chinese translation. -Thanks to Masashi Kawashima for the Japanese translation. -Thanks to Taaryk Taar and Tariq Al-Omaireeni for an updated Arabic translation. -Thanks to Jens Meiert for the German translation. - -==License== - -{{{ - Copyright 2008 Google Inc. All Rights Reserved. - - Licensed under the Apache License, Version 2.0 (the 'License'); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an 'AS IS' BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -}}} diff --git a/maintenance/cssjanus/cssjanus.py b/maintenance/cssjanus/cssjanus.py deleted file mode 100644 index dd14bd58..00000000 --- a/maintenance/cssjanus/cssjanus.py +++ /dev/null @@ -1,574 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2008 Google Inc. All Rights Reserved. - -"""Converts a LeftToRight Cascading Style Sheet into a RightToLeft one. - - This is a utility script for replacing "left" oriented things in a CSS file - like float, padding, margin with "right" oriented values. - It also does the opposite. - The goal is to be able to conditionally serve one large, cat'd, compiled CSS - file appropriate for LeftToRight oriented languages and RightToLeft ones. - This utility will hopefully help your structural layout done in CSS in - terms of its RTL compatibility. It will not help with some of the more - complicated bidirectional text issues. -""" - -__author__ = 'elsigh@google.com (Lindsey Simon)' -__version__ = '0.1' - -import logging -import re -import sys -import getopt -import os - -import csslex - -logging.getLogger().setLevel(logging.INFO) - -# Global for the command line flags. -SWAP_LTR_RTL_IN_URL_DEFAULT = False -SWAP_LEFT_RIGHT_IN_URL_DEFAULT = False -FLAGS = {'swap_ltr_rtl_in_url': SWAP_LTR_RTL_IN_URL_DEFAULT, - 'swap_left_right_in_url': SWAP_LEFT_RIGHT_IN_URL_DEFAULT} - -# Generic token delimiter character. -TOKEN_DELIMITER = '~' - -# This is a temporary match token we use when swapping strings. -TMP_TOKEN = '%sTMP%s' % (TOKEN_DELIMITER, TOKEN_DELIMITER) - -# Token to be used for joining lines. -TOKEN_LINES = '%sJ%s' % (TOKEN_DELIMITER, TOKEN_DELIMITER) - -# Global constant text strings for CSS value matches. -LTR = 'ltr' -RTL = 'rtl' -LEFT = 'left' -RIGHT = 'right' - -# This is a lookbehind match to ensure that we don't replace instances -# of our string token (left, rtl, etc...) if there's a letter in front of it. -# Specifically, this prevents replacements like 'background: url(bright.png)'. -LOOKBEHIND_NOT_LETTER = r'(?<![a-zA-Z])' - -# This is a lookahead match to make sure we don't replace left and right -# in actual classnames, so that we don't break the HTML/CSS dependencies. -# Read literally, it says ignore cases where the word left, for instance, is -# directly followed by valid classname characters and a curly brace. -# ex: .column-left {float: left} will become .column-left {float: right} -LOOKAHEAD_NOT_OPEN_BRACE = (r'(?!(?:%s|%s|%s|#|\:|\.|\,|\+|>)*?{)' % - (csslex.NMCHAR, TOKEN_LINES, csslex.SPACE)) - - -# These two lookaheads are to test whether or not we are within a -# background: url(HERE) situation. -# Ref: http://www.w3.org/TR/CSS21/syndata.html#uri -VALID_AFTER_URI_CHARS = r'[\'\"]?%s' % csslex.WHITESPACE -LOOKAHEAD_NOT_CLOSING_PAREN = r'(?!%s?%s\))' % (csslex.URL_CHARS, - VALID_AFTER_URI_CHARS) -LOOKAHEAD_FOR_CLOSING_PAREN = r'(?=%s?%s\))' % (csslex.URL_CHARS, - VALID_AFTER_URI_CHARS) - -# Compile a regex to swap left and right values in 4 part notations. -# We need to match negatives and decimal numeric values. -# ex. 'margin: .25em -2px 3px 0' becomes 'margin: .25em 0 3px -2px'. -POSSIBLY_NEGATIVE_QUANTITY = r'((?:-?%s)|(?:inherit|auto))' % csslex.QUANTITY -POSSIBLY_NEGATIVE_QUANTITY_SPACE = r'%s%s%s' % (POSSIBLY_NEGATIVE_QUANTITY, - csslex.SPACE, - csslex.WHITESPACE) -FOUR_NOTATION_QUANTITY_RE = re.compile(r'%s%s%s%s' % - (POSSIBLY_NEGATIVE_QUANTITY_SPACE, - POSSIBLY_NEGATIVE_QUANTITY_SPACE, - POSSIBLY_NEGATIVE_QUANTITY_SPACE, - POSSIBLY_NEGATIVE_QUANTITY), - re.I) -COLOR = r'(%s|%s)' % (csslex.NAME, csslex.HASH) -COLOR_SPACE = r'%s%s' % (COLOR, csslex.SPACE) -FOUR_NOTATION_COLOR_RE = re.compile(r'(-color%s:%s)%s%s%s(%s)' % - (csslex.WHITESPACE, - csslex.WHITESPACE, - COLOR_SPACE, - COLOR_SPACE, - COLOR_SPACE, - COLOR), - re.I) - -# Compile the cursor resize regexes -CURSOR_EAST_RE = re.compile(LOOKBEHIND_NOT_LETTER + '([ns]?)e-resize') -CURSOR_WEST_RE = re.compile(LOOKBEHIND_NOT_LETTER + '([ns]?)w-resize') - -# Matches the condition where we need to replace the horizontal component -# of a background-position value when expressed in horizontal percentage. -# Had to make two regexes because in the case of position-x there is only -# one quantity, and otherwise we don't want to match and change cases with only -# one quantity. -BG_HORIZONTAL_PERCENTAGE_RE = re.compile(r'background(-position)?(%s:%s)' - '([^%%]*?)(%s)%%' - '(%s(?:%s|%s))' % (csslex.WHITESPACE, - csslex.WHITESPACE, - csslex.NUM, - csslex.WHITESPACE, - csslex.QUANTITY, - csslex.IDENT)) - -BG_HORIZONTAL_PERCENTAGE_X_RE = re.compile(r'background-position-x(%s:%s)' - '(%s)%%' % (csslex.WHITESPACE, - csslex.WHITESPACE, - csslex.NUM)) - -# Matches the opening of a body selector. -BODY_SELECTOR = r'body%s{%s' % (csslex.WHITESPACE, csslex.WHITESPACE) - -# Matches anything up until the closing of a selector. -CHARS_WITHIN_SELECTOR = r'[^\}]*?' - -# Matches the direction property in a selector. -DIRECTION_RE = r'direction%s:%s' % (csslex.WHITESPACE, csslex.WHITESPACE) - -# These allow us to swap "ltr" with "rtl" and vice versa ONLY within the -# body selector and on the same line. -BODY_DIRECTION_LTR_RE = re.compile(r'(%s)(%s)(%s)(ltr)' % - (BODY_SELECTOR, CHARS_WITHIN_SELECTOR, - DIRECTION_RE), - re.I) -BODY_DIRECTION_RTL_RE = re.compile(r'(%s)(%s)(%s)(rtl)' % - (BODY_SELECTOR, CHARS_WITHIN_SELECTOR, - DIRECTION_RE), - re.I) - - -# Allows us to swap "direction:ltr" with "direction:rtl" and -# vice versa anywhere in a line. -DIRECTION_LTR_RE = re.compile(r'%s(ltr)' % DIRECTION_RE) -DIRECTION_RTL_RE = re.compile(r'%s(rtl)' % DIRECTION_RE) - -# We want to be able to switch left with right and vice versa anywhere -# we encounter left/right strings, EXCEPT inside the background:url(). The next -# two regexes are for that purpose. We have alternate IN_URL versions of the -# regexes compiled in case the user passes the flag that they do -# actually want to have left and right swapped inside of background:urls. -LEFT_RE = re.compile('%s(%s)%s%s' % (LOOKBEHIND_NOT_LETTER, - LEFT, - LOOKAHEAD_NOT_CLOSING_PAREN, - LOOKAHEAD_NOT_OPEN_BRACE), - re.I) -RIGHT_RE = re.compile('%s(%s)%s%s' % (LOOKBEHIND_NOT_LETTER, - RIGHT, - LOOKAHEAD_NOT_CLOSING_PAREN, - LOOKAHEAD_NOT_OPEN_BRACE), - re.I) -LEFT_IN_URL_RE = re.compile('%s(%s)%s' % (LOOKBEHIND_NOT_LETTER, - LEFT, - LOOKAHEAD_FOR_CLOSING_PAREN), - re.I) -RIGHT_IN_URL_RE = re.compile('%s(%s)%s' % (LOOKBEHIND_NOT_LETTER, - RIGHT, - LOOKAHEAD_FOR_CLOSING_PAREN), - re.I) -LTR_IN_URL_RE = re.compile('%s(%s)%s' % (LOOKBEHIND_NOT_LETTER, - LTR, - LOOKAHEAD_FOR_CLOSING_PAREN), - re.I) -RTL_IN_URL_RE = re.compile('%s(%s)%s' % (LOOKBEHIND_NOT_LETTER, - RTL, - LOOKAHEAD_FOR_CLOSING_PAREN), - re.I) - -COMMENT_RE = re.compile('(%s)' % csslex.COMMENT, re.I) - -NOFLIP_TOKEN = r'\@noflip' -# The NOFLIP_TOKEN inside of a comment. For now, this requires that comments -# be in the input, which means users of a css compiler would have to run -# this script first if they want this functionality. -NOFLIP_ANNOTATION = r'/\*%s%s%s\*/' % (csslex.WHITESPACE, - NOFLIP_TOKEN, - csslex. WHITESPACE) - -# After a NOFLIP_ANNOTATION, and within a class selector, we want to be able -# to set aside a single rule not to be flipped. We can do this by matching -# our NOFLIP annotation and then using a lookahead to make sure there is not -# an opening brace before the match. -NOFLIP_SINGLE_RE = re.compile(r'(%s%s[^;}]+;?)' % (NOFLIP_ANNOTATION, - LOOKAHEAD_NOT_OPEN_BRACE), - re.I) - -# After a NOFLIP_ANNOTATION, we want to grab anything up until the next } which -# means the entire following class block. This will prevent all of its -# declarations from being flipped. -NOFLIP_CLASS_RE = re.compile(r'(%s%s})' % (NOFLIP_ANNOTATION, - CHARS_WITHIN_SELECTOR), - re.I) - - -class Tokenizer: - """Replaces any CSS comments with string tokens and vice versa.""" - - def __init__(self, token_re, token_string): - """Constructor for the Tokenizer. - - Args: - token_re: A regex for the string to be replace by a token. - token_string: The string to put between token delimiters when tokenizing. - """ - logging.debug('Tokenizer::init token_string=%s' % token_string) - self.token_re = token_re - self.token_string = token_string - self.originals = [] - - def Tokenize(self, line): - """Replaces any string matching token_re in line with string tokens. - - By passing a function as an argument to the re.sub line below, we bypass - the usual rule where re.sub will only replace the left-most occurrence of - a match by calling the passed in function for each occurrence. - - Args: - line: A line to replace token_re matches in. - - Returns: - line: A line with token_re matches tokenized. - """ - line = self.token_re.sub(self.TokenizeMatches, line) - logging.debug('Tokenizer::Tokenize returns: %s' % line) - return line - - def DeTokenize(self, line): - """Replaces tokens with the original string. - - Args: - line: A line with tokens. - - Returns: - line with any tokens replaced by the original string. - """ - - # Put all of the comments back in by their comment token. - for i, original in enumerate(self.originals): - token = '%s%s_%s%s' % (TOKEN_DELIMITER, self.token_string, i + 1, - TOKEN_DELIMITER) - line = line.replace(token, original) - logging.debug('Tokenizer::DeTokenize i:%s w/%s' % (i, token)) - logging.debug('Tokenizer::DeTokenize returns: %s' % line) - return line - - def TokenizeMatches(self, m): - """Replaces matches with tokens and stores the originals. - - Args: - m: A match object. - - Returns: - A string token which replaces the CSS comment. - """ - logging.debug('Tokenizer::TokenizeMatches %s' % m.group(1)) - self.originals.append(m.group(1)) - return '%s%s_%s%s' % (TOKEN_DELIMITER, - self.token_string, - len(self.originals), - TOKEN_DELIMITER) - - -def FixBodyDirectionLtrAndRtl(line): - """Replaces ltr with rtl and vice versa ONLY in the body direction. - - Args: - line: A string to replace instances of ltr with rtl. - Returns: - line with direction: ltr and direction: rtl swapped only in body selector. - line = FixBodyDirectionLtrAndRtl('body { direction:ltr }') - line will now be 'body { direction:rtl }'. - """ - - line = BODY_DIRECTION_LTR_RE.sub('\\1\\2\\3%s' % TMP_TOKEN, line) - line = BODY_DIRECTION_RTL_RE.sub('\\1\\2\\3%s' % LTR, line) - line = line.replace(TMP_TOKEN, RTL) - logging.debug('FixBodyDirectionLtrAndRtl returns: %s' % line) - return line - - -def FixLeftAndRight(line): - """Replaces left with right and vice versa in line. - - Args: - line: A string in which to perform the replacement. - - Returns: - line with left and right swapped. For example: - line = FixLeftAndRight('padding-left: 2px; margin-right: 1px;') - line will now be 'padding-right: 2px; margin-left: 1px;'. - """ - - line = LEFT_RE.sub(TMP_TOKEN, line) - line = RIGHT_RE.sub(LEFT, line) - line = line.replace(TMP_TOKEN, RIGHT) - logging.debug('FixLeftAndRight returns: %s' % line) - return line - - -def FixLeftAndRightInUrl(line): - """Replaces left with right and vice versa ONLY within background urls. - - Args: - line: A string in which to replace left with right and vice versa. - - Returns: - line with left and right swapped in the url string. For example: - line = FixLeftAndRightInUrl('background:url(right.png)') - line will now be 'background:url(left.png)'. - """ - - line = LEFT_IN_URL_RE.sub(TMP_TOKEN, line) - line = RIGHT_IN_URL_RE.sub(LEFT, line) - line = line.replace(TMP_TOKEN, RIGHT) - logging.debug('FixLeftAndRightInUrl returns: %s' % line) - return line - - -def FixLtrAndRtlInUrl(line): - """Replaces ltr with rtl and vice versa ONLY within background urls. - - Args: - line: A string in which to replace ltr with rtl and vice versa. - - Returns: - line with left and right swapped. For example: - line = FixLtrAndRtlInUrl('background:url(rtl.png)') - line will now be 'background:url(ltr.png)'. - """ - - line = LTR_IN_URL_RE.sub(TMP_TOKEN, line) - line = RTL_IN_URL_RE.sub(LTR, line) - line = line.replace(TMP_TOKEN, RTL) - logging.debug('FixLtrAndRtlInUrl returns: %s' % line) - return line - - -def FixCursorProperties(line): - """Fixes directional CSS cursor properties. - - Args: - line: A string to fix CSS cursor properties in. - - Returns: - line reformatted with the cursor properties substituted. For example: - line = FixCursorProperties('cursor: ne-resize') - line will now be 'cursor: nw-resize'. - """ - - line = CURSOR_EAST_RE.sub('\\1' + TMP_TOKEN, line) - line = CURSOR_WEST_RE.sub('\\1e-resize', line) - line = line.replace(TMP_TOKEN, 'w-resize') - logging.debug('FixCursorProperties returns: %s' % line) - return line - - -def FixFourPartNotation(line): - """Fixes the second and fourth positions in 4 part CSS notation. - - Args: - line: A string to fix 4 part CSS notation in. - - Returns: - line reformatted with the 4 part notations swapped. For example: - line = FixFourPartNotation('padding: 1px 2px 3px 4px') - line will now be 'padding: 1px 4px 3px 2px'. - """ - line = FOUR_NOTATION_QUANTITY_RE.sub('\\1 \\4 \\3 \\2', line) - line = FOUR_NOTATION_COLOR_RE.sub('\\1\\2 \\5 \\4 \\3', line) - logging.debug('FixFourPartNotation returns: %s' % line) - return line - - -def FixBackgroundPosition(line): - """Fixes horizontal background percentage values in line. - - Args: - line: A string to fix horizontal background position values in. - - Returns: - line reformatted with the 4 part notations swapped. - """ - line = BG_HORIZONTAL_PERCENTAGE_RE.sub(CalculateNewBackgroundPosition, line) - line = BG_HORIZONTAL_PERCENTAGE_X_RE.sub(CalculateNewBackgroundPositionX, - line) - logging.debug('FixBackgroundPosition returns: %s' % line) - return line - - -def CalculateNewBackgroundPosition(m): - """Fixes horizontal background-position percentages. - - This function should be used as an argument to re.sub since it needs to - perform replacement specific calculations. - - Args: - m: A match object. - - Returns: - A string with the horizontal background position percentage fixed. - BG_HORIZONTAL_PERCENTAGE_RE.sub(FixBackgroundPosition, - 'background-position: 75% 50%') - will return 'background-position: 25% 50%'. - """ - - # The flipped value is the offset from 100% - new_x = str(100-int(m.group(4))) - - # Since m.group(1) may very well be None type and we need a string.. - if m.group(1): - position_string = m.group(1) - else: - position_string = '' - - return 'background%s%s%s%s%%%s' % (position_string, m.group(2), m.group(3), - new_x, m.group(5)) - - -def CalculateNewBackgroundPositionX(m): - """Fixes percent based background-position-x. - - This function should be used as an argument to re.sub since it needs to - perform replacement specific calculations. - - Args: - m: A match object. - - Returns: - A string with the background-position-x percentage fixed. - BG_HORIZONTAL_PERCENTAGE_X_RE.sub(CalculateNewBackgroundPosition, - 'background-position-x: 75%') - will return 'background-position-x: 25%'. - """ - - # The flipped value is the offset from 100% - new_x = str(100-int(m.group(2))) - - return 'background-position-x%s%s%%' % (m.group(1), new_x) - - -def ChangeLeftToRightToLeft(lines, - swap_ltr_rtl_in_url=None, - swap_left_right_in_url=None): - """Turns lines into a stream and runs the fixing functions against it. - - Args: - lines: An list of CSS lines. - swap_ltr_rtl_in_url: Overrides this flag if param is set. - swap_left_right_in_url: Overrides this flag if param is set. - - Returns: - The same lines, but with left and right fixes. - """ - - global FLAGS - - # Possibly override flags with params. - logging.debug('ChangeLeftToRightToLeft swap_ltr_rtl_in_url=%s, ' - 'swap_left_right_in_url=%s' % (swap_ltr_rtl_in_url, - swap_left_right_in_url)) - if swap_ltr_rtl_in_url is None: - swap_ltr_rtl_in_url = FLAGS['swap_ltr_rtl_in_url'] - if swap_left_right_in_url is None: - swap_left_right_in_url = FLAGS['swap_left_right_in_url'] - - # Turns the array of lines into a single line stream. - logging.debug('LINES COUNT: %s' % len(lines)) - line = TOKEN_LINES.join(lines) - - # Tokenize any single line rules with the /* noflip */ annotation. - noflip_single_tokenizer = Tokenizer(NOFLIP_SINGLE_RE, 'NOFLIP_SINGLE') - line = noflip_single_tokenizer.Tokenize(line) - - # Tokenize any class rules with the /* noflip */ annotation. - noflip_class_tokenizer = Tokenizer(NOFLIP_CLASS_RE, 'NOFLIP_CLASS') - line = noflip_class_tokenizer.Tokenize(line) - - # Tokenize the comments so we can preserve them through the changes. - comment_tokenizer = Tokenizer(COMMENT_RE, 'C') - line = comment_tokenizer.Tokenize(line) - - # Here starteth the various left/right orientation fixes. - line = FixBodyDirectionLtrAndRtl(line) - - if swap_left_right_in_url: - line = FixLeftAndRightInUrl(line) - - if swap_ltr_rtl_in_url: - line = FixLtrAndRtlInUrl(line) - - line = FixLeftAndRight(line) - line = FixCursorProperties(line) - line = FixFourPartNotation(line) - line = FixBackgroundPosition(line) - - # DeTokenize the single line noflips. - line = noflip_single_tokenizer.DeTokenize(line) - - # DeTokenize the class-level noflips. - line = noflip_class_tokenizer.DeTokenize(line) - - # DeTokenize the comments. - line = comment_tokenizer.DeTokenize(line) - - # Rejoin the lines back together. - lines = line.split(TOKEN_LINES) - - return lines - -def usage(): - """Prints out usage information.""" - - print 'Usage:' - print ' ./cssjanus.py < file.css > file-rtl.css' - print 'Flags:' - print ' --swap_left_right_in_url: Fixes "left"/"right" string within urls.' - print ' Ex: ./cssjanus.py --swap_left_right_in_url < file.css > file_rtl.css' - print ' --swap_ltr_rtl_in_url: Fixes "ltr"/"rtl" string within urls.' - print ' Ex: ./cssjanus --swap_ltr_rtl_in_url < file.css > file_rtl.css' - -def setflags(opts): - """Parse the passed in command line arguments and set the FLAGS global. - - Args: - opts: getopt iterable intercepted from argv. - """ - - global FLAGS - - # Parse the arguments. - for opt, arg in opts: - logging.debug('opt: %s, arg: %s' % (opt, arg)) - if opt in ("-h", "--help"): - usage() - sys.exit() - elif opt in ("-d", "--debug"): - logging.getLogger().setLevel(logging.DEBUG) - elif opt == '--swap_ltr_rtl_in_url': - FLAGS['swap_ltr_rtl_in_url'] = True - elif opt == '--swap_left_right_in_url': - FLAGS['swap_left_right_in_url'] = True - - -def main(argv): - """Sends stdin lines to ChangeLeftToRightToLeft and writes to stdout.""" - - # Define the flags. - try: - opts, args = getopt.getopt(argv, 'hd', ['help', 'debug', - 'swap_left_right_in_url', - 'swap_ltr_rtl_in_url']) - except getopt.GetoptError: - usage() - sys.exit(2) - - # Parse and set the flags. - setflags(opts) - - # Call the main routine with all our functionality. - fixed_lines = ChangeLeftToRightToLeft(sys.stdin.readlines()) - sys.stdout.write(''.join(fixed_lines)) - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/maintenance/cssjanus/csslex.py b/maintenance/cssjanus/csslex.py deleted file mode 100644 index 1fc7304e..00000000 --- a/maintenance/cssjanus/csslex.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2007 Google Inc. All Rights Reserved. - -"""CSS Lexical Grammar rules. - -CSS lexical grammar from http://www.w3.org/TR/CSS21/grammar.html -""" - -__author__ = ['elsigh@google.com (Lindsey Simon)', - 'msamuel@google.com (Mike Samuel)'] - -# public symbols -__all__ = [ "NEWLINE", "HEX", "NON_ASCII", "UNICODE", "ESCAPE", "NMSTART", "NMCHAR", "STRING1", "STRING2", "IDENT", "NAME", "HASH", "NUM", "STRING", "URL", "SPACE", "WHITESPACE", "COMMENT", "QUANTITY", "PUNC" ] - -# The comments below are mostly copied verbatim from the grammar. - -# "@import" {return IMPORT_SYM;} -# "@page" {return PAGE_SYM;} -# "@media" {return MEDIA_SYM;} -# "@charset" {return CHARSET_SYM;} -KEYWORD = r'(?:\@(?:import|page|media|charset))' - -# nl \n|\r\n|\r|\f ; a newline -NEWLINE = r'\n|\r\n|\r|\f' - -# h [0-9a-f] ; a hexadecimal digit -HEX = r'[0-9a-f]' - -# nonascii [\200-\377] -NON_ASCII = r'[\200-\377]' - -# unicode \\{h}{1,6}(\r\n|[ \t\r\n\f])? -UNICODE = r'(?:(?:\\' + HEX + r'{1,6})(?:\r\n|[ \t\r\n\f])?)' - -# escape {unicode}|\\[^\r\n\f0-9a-f] -ESCAPE = r'(?:' + UNICODE + r'|\\[^\r\n\f0-9a-f])' - -# nmstart [_a-z]|{nonascii}|{escape} -NMSTART = r'(?:[_a-z]|' + NON_ASCII + r'|' + ESCAPE + r')' - -# nmchar [_a-z0-9-]|{nonascii}|{escape} -NMCHAR = r'(?:[_a-z0-9-]|' + NON_ASCII + r'|' + ESCAPE + r')' - -# ident -?{nmstart}{nmchar}* -IDENT = r'-?' + NMSTART + NMCHAR + '*' - -# name {nmchar}+ -NAME = NMCHAR + r'+' - -# hash -HASH = r'#' + NAME - -# string1 \"([^\n\r\f\\"]|\\{nl}|{escape})*\" ; "string" -STRING1 = r'"(?:[^\"\\]|\\.)*"' - -# string2 \'([^\n\r\f\\']|\\{nl}|{escape})*\' ; 'string' -STRING2 = r"'(?:[^\'\\]|\\.)*'" - -# string {string1}|{string2} -STRING = '(?:' + STRING1 + r'|' + STRING2 + ')' - -# num [0-9]+|[0-9]*"."[0-9]+ -NUM = r'(?:[0-9]*\.[0-9]+|[0-9]+)' - -# s [ \t\r\n\f] -SPACE = r'[ \t\r\n\f]' - -# w {s}* -WHITESPACE = '(?:' + SPACE + r'*)' - -# url special chars -URL_SPECIAL_CHARS = r'[!#$%&*-~]' - -# url chars ({url_special_chars}|{nonascii}|{escape})* -URL_CHARS = r'(?:%s|%s|%s)*' % (URL_SPECIAL_CHARS, NON_ASCII, ESCAPE) - -# url -URL = r'url\(%s(%s|%s)%s\)' % (WHITESPACE, STRING, URL_CHARS, WHITESPACE) - -# comments -# see http://www.w3.org/TR/CSS21/grammar.html -COMMENT = r'/\*[^*]*\*+([^/*][^*]*\*+)*/' - -# {E}{M} {return EMS;} -# {E}{X} {return EXS;} -# {P}{X} {return LENGTH;} -# {C}{M} {return LENGTH;} -# {M}{M} {return LENGTH;} -# {I}{N} {return LENGTH;} -# {P}{T} {return LENGTH;} -# {P}{C} {return LENGTH;} -# {D}{E}{G} {return ANGLE;} -# {R}{A}{D} {return ANGLE;} -# {G}{R}{A}{D} {return ANGLE;} -# {M}{S} {return TIME;} -# {S} {return TIME;} -# {H}{Z} {return FREQ;} -# {K}{H}{Z} {return FREQ;} -# % {return PERCENTAGE;} -UNIT = r'(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)' - -# {num}{UNIT|IDENT} {return NUMBER;} -QUANTITY = '%s(?:%s%s|%s)?' % (NUM, WHITESPACE, UNIT, IDENT) - -# "<!--" {return CDO;} -# "-->" {return CDC;} -# "~=" {return INCLUDES;} -# "|=" {return DASHMATCH;} -# {w}"{" {return LBRACE;} -# {w}"+" {return PLUS;} -# {w}">" {return GREATER;} -# {w}"," {return COMMA;} -PUNC = r'<!--|-->|~=|\|=|[\{\+>,:;]' diff --git a/maintenance/deleteArchivedFiles.inc b/maintenance/deleteArchivedFiles.inc index d58e9a40..0c0b34a3 100644 --- a/maintenance/deleteArchivedFiles.inc +++ b/maintenance/deleteArchivedFiles.inc @@ -39,6 +39,10 @@ class DeleteArchivedFilesImplementation { $count = 0; foreach ( $res as $row ) { $key = $row->fa_storage_key; + if ( !strlen( $key ) ) { + $output->handleOutput( "Entry with ID {$row->fa_id} has empty key, skipping\n" ); + continue; + } $group = $row->fa_storage_group; $id = $row->fa_id; $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key; @@ -49,9 +53,13 @@ class DeleteArchivedFilesImplementation { $sha1 = LocalRepo::getHashFromKey( $key ); } // Check if the file is used anywhere... - $inuse = $dbw->selectField( 'oldimage', '1', - array( 'oi_sha1' => $sha1, - 'oi_deleted & ' . File::DELETED_FILE => File::DELETED_FILE ), + $inuse = $dbw->selectField( + 'oldimage', + '1', + array( + 'oi_sha1' => $sha1, + 'oi_deleted & ' . File::DELETED_FILE => File::DELETED_FILE + ), __METHOD__, array( 'FOR UPDATE' ) ); diff --git a/maintenance/deleteArchivedFiles.php b/maintenance/deleteArchivedFiles.php index ad7b54d0..286b1f24 100644 --- a/maintenance/deleteArchivedFiles.php +++ b/maintenance/deleteArchivedFiles.php @@ -47,6 +47,7 @@ class DeleteArchivedFiles extends Maintenance { public function execute() { if ( !$this->hasOption( 'delete' ) ) { $this->output( "Use --delete to actually confirm this script\n" ); + return; } $force = $this->hasOption( 'force' ); diff --git a/maintenance/deleteArchivedRevisions.inc b/maintenance/deleteArchivedRevisions.inc index dd8e3dd4..ed620ee3 100644 --- a/maintenance/deleteArchivedRevisions.inc +++ b/maintenance/deleteArchivedRevisions.inc @@ -30,8 +30,7 @@ class DeleteArchivedRevisionsImplementation { /** * Perform the delete on archived revisions. - - * @param $maint Object An object (typically of class Maintenance) + * @param object $maint An object (typically of class Maintenance) * that implements two methods: handleOutput() and * purgeRedundantText(). See Maintenance for a description of * those methods. diff --git a/maintenance/deleteArchivedRevisions.php b/maintenance/deleteArchivedRevisions.php index ffd581c1..30883ba4 100644 --- a/maintenance/deleteArchivedRevisions.php +++ b/maintenance/deleteArchivedRevisions.php @@ -36,7 +36,8 @@ require_once __DIR__ . '/deleteArchivedRevisions.inc'; class DeleteArchivedRevisions extends Maintenance { public function __construct() { parent::__construct(); - $this->mDescription = "Deletes all archived revisions\nThese revisions will no longer be restorable"; + $this->mDescription = + "Deletes all archived revisions\nThese revisions will no longer be restorable"; $this->addOption( 'delete', 'Performs the deletion' ); } @@ -53,7 +54,8 @@ class DeleteArchivedRevisions extends Maintenance { $dbw = wfGetDB( DB_MASTER ); $res = $dbw->selectRow( 'archive', 'COUNT(*) as count', array(), __FUNCTION__ ); $this->output( "Found {$res->count} revisions to delete.\n" ); - $this->output( "Please run the script again with the --delete option to really delete the revisions.\n" ); + $this->output( "Please run the script again with the --delete option " + . "to really delete the revisions.\n" ); } } } diff --git a/maintenance/deleteBatch.php b/maintenance/deleteBatch.php index c1cc03cd..93507b34 100644 --- a/maintenance/deleteBatch.php +++ b/maintenance/deleteBatch.php @@ -3,11 +3,11 @@ * Deletes a batch of pages. * Usage: php deleteBatch.php [-u <user>] [-r <reason>] [-i <interval>] [listfile] * where - * [listfile] is a file where each line contains the title of a page to be - * deleted, standard input is used if listfile is not given. - * <user> is the username - * <reason> is the delete reason - * <interval> is the number of seconds to sleep for after each delete + * [listfile] is a file where each line contains the title of a page to be + * deleted, standard input is used if listfile is not given. + * <user> is the username + * <reason> is the delete reason + * <interval> is the number of seconds to sleep for after each delete * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -79,7 +79,9 @@ class DeleteBatch extends Maintenance { $dbw = wfGetDB( DB_MASTER ); # Handle each entry + // @codingStandardsIgnoreStart Ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed for ( $linenum = 1; !feof( $file ); $linenum++ ) { + // @codingStandardsIgnoreEnd $line = trim( fgets( $file ) ); if ( $line == '' ) { continue; @@ -97,7 +99,7 @@ class DeleteBatch extends Maintenance { $this->output( $title->getPrefixedText() ); $dbw->begin( __METHOD__ ); if ( $title->getNamespace() == NS_FILE ) { - $img = wfFindFile( $title ); + $img = wfFindFile( $title, array( 'ignoreRedirect' => true ) ); if ( $img && $img->isLocal() && !$img->delete( $reason ) ) { $this->output( " FAILED to delete associated file... " ); } diff --git a/maintenance/deleteDefaultMessages.php b/maintenance/deleteDefaultMessages.php index 7d8c80e4..5aeeb8e1 100644 --- a/maintenance/deleteDefaultMessages.php +++ b/maintenance/deleteDefaultMessages.php @@ -34,7 +34,7 @@ class DeleteDefaultMessages extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Deletes all pages in the MediaWiki namespace" . - " which were last edited by \"MediaWiki default\""; + " which were last edited by \"MediaWiki default\""; } public function execute() { @@ -54,6 +54,7 @@ class DeleteDefaultMessages extends Maintenance { if ( $dbr->numRows( $res ) == 0 ) { # No more messages left $this->output( "done.\n" ); + return; } diff --git a/maintenance/deleteEqualMessages.php b/maintenance/deleteEqualMessages.php index 81758913..dbe96982 100644 --- a/maintenance/deleteEqualMessages.php +++ b/maintenance/deleteEqualMessages.php @@ -30,15 +30,18 @@ require_once __DIR__ . '/Maintenance.php'; class DeleteEqualMessages extends Maintenance { public function __construct() { parent::__construct(); - $this->mDescription = "Deletes all pages in the MediaWiki namespace that are equal to the default message"; + $this->mDescription = 'Deletes all pages in the MediaWiki namespace that are equal to ' + . 'the default message'; $this->addOption( 'delete', 'Actually delete the pages (default: dry run)' ); $this->addOption( 'delete-talk', 'Don\'t leave orphaned talk pages behind during deletion' ); - $this->addOption( 'lang-code', 'Check for subpages of this language code (default: root page against content language). ' . - 'Use value "*" to run for all mwfile language code subpages (including the base pages that override content language).', false, true ); + $this->addOption( 'lang-code', 'Check for subpages of this language code (default: root ' + . 'page against content language). Use value "*" to run for all mwfile language code ' + . 'subpages (including the base pages that override content language).', false, true ); } /** * @param string|bool $langCode See --lang-code option. + * @param array &$messageInfo */ protected function fetchMessageInfo( $langCode, array &$messageInfo ) { global $wgContLang; @@ -59,7 +62,8 @@ class DeleteEqualMessages extends Maintenance { // Normalise message names for NS_MEDIAWIKI page_title $messageNames = array_map( array( $wgContLang, 'ucfirst' ), $messageNames ); - $statuses = AllmessagesTablePager::getCustomisedStatuses( $messageNames, $langCode, $nonContLang ); + $statuses = AllMessagesTablePager::getCustomisedStatuses( + $messageNames, $langCode, $nonContLang ); // getCustomisedStatuses is stripping the sub page from the page titles, add it back $titleSuffix = $nonContLang ? "/$langCode" : ''; @@ -130,11 +134,14 @@ class DeleteEqualMessages extends Maintenance { if ( $messageInfo['equalPages'] === 0 ) { // No more equal messages left $this->output( "\ndone.\n" ); + return; } - $this->output( "\n{$messageInfo['relevantPages']} pages in the MediaWiki namespace override messages." ); - $this->output( "\n{$messageInfo['equalPages']} pages are equal to the default message (+ {$messageInfo['equalPagesTalks']} talk pages).\n" ); + $this->output( "\n{$messageInfo['relevantPages']} pages in the MediaWiki namespace " + . "override messages." ); + $this->output( "\n{$messageInfo['equalPages']} pages are equal to the default message " + . "(+ {$messageInfo['equalPagesTalks']} talk pages).\n" ); if ( !$doDelete ) { $list = ''; @@ -151,6 +158,7 @@ class DeleteEqualMessages extends Maintenance { $this->output( " (include --delete-talk to also delete the talk pages)" ); } $this->output( "\n" ); + return; } @@ -170,7 +178,6 @@ class DeleteEqualMessages extends Maintenance { foreach ( $messageInfo['results'] as $result ) { wfWaitForSlaves(); $dbw->ping(); - $dbw->begin( __METHOD__ ); $title = Title::makeTitle( NS_MEDIAWIKI, $result['title'] ); $this->output( "\n* [[$title]]" ); $page = WikiPage::factory( $title ); @@ -181,9 +188,9 @@ class DeleteEqualMessages extends Maintenance { $this->output( "\n* [[$title]]" ); $page = WikiPage::factory( $title ); $error = ''; // Passed by ref - $page->doDeleteArticle( 'Orphaned talk page of no longer required message', false, 0, false, $error, $user ); + $page->doDeleteArticle( 'Orphaned talk page of no longer required message', + false, 0, false, $error, $user ); } - $dbw->commit( __METHOD__ ); } $this->output( "\n\ndone!\n" ); } diff --git a/maintenance/deleteImageMemcached.php b/maintenance/deleteImageMemcached.php index 835de352..4799e5e0 100644 --- a/maintenance/deleteImageMemcached.php +++ b/maintenance/deleteImageMemcached.php @@ -60,7 +60,12 @@ class DeleteImageCache extends Maintenance { foreach ( $res as $row ) { if ( $i % $this->report == 0 ) { - $this->output( sprintf( "%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 ) ); @@ -75,6 +80,7 @@ class DeleteImageCache extends Maintenance { private function getImageCount() { $dbr = wfGetDB( DB_SLAVE ); + return $dbr->selectField( 'image', 'COUNT(*)', array(), __METHOD__ ); } } diff --git a/maintenance/deleteOrphanedRevisions.php b/maintenance/deleteOrphanedRevisions.php index f0a96928..7f1ffe41 100644 --- a/maintenance/deleteOrphanedRevisions.php +++ b/maintenance/deleteOrphanedRevisions.php @@ -49,7 +49,8 @@ class DeleteOrphanedRevisions extends Maintenance { # Find all the orphaned revisions $this->output( "Checking for orphaned revisions..." ); - $sql = "SELECT rev_id FROM {$revision} LEFT JOIN {$page} ON rev_page = page_id WHERE page_namespace IS NULL"; + $sql = "SELECT rev_id FROM {$revision} LEFT JOIN {$page} ON rev_page = page_id " + . "WHERE page_namespace IS NULL"; $res = $dbw->query( $sql, 'deleteOrphanedRevisions' ); # Stash 'em all up for deletion (if needed) @@ -80,8 +81,8 @@ class DeleteOrphanedRevisions extends Maintenance { * Delete one or more revisions from the database * Do this inside a transaction * - * @param $id Array of revision id values - * @param $dbw DatabaseBase class (needs to be a master) + * @param array $id Array of revision id values + * @param DatabaseBase $dbw DatabaseBase class (needs to be a master) */ private function deleteRevs( $id, &$dbw ) { if ( !is_array( $id ) ) { diff --git a/maintenance/deleteRevision.php b/maintenance/deleteRevision.php index 6bc0f7cd..818ee360 100644 --- a/maintenance/deleteRevision.php +++ b/maintenance/deleteRevision.php @@ -42,41 +42,64 @@ class DeleteRevision extends Maintenance { } $this->output( "Deleting revision(s) " . implode( ',', $this->mArgs ) . - " from " . wfWikiID() . "...\n" ); + " 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_page_id' => 'page_id', - 'ar_comment' => 'rev_comment', - 'ar_user' => 'rev_user', - 'ar_user_text' => 'rev_user_text', - 'ar_timestamp' => 'rev_timestamp', + 'ar_namespace' => 'page_namespace', + 'ar_title' => 'page_title', + 'ar_page_id' => 'page_id', + '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', - 'ar_deleted' => 'rev_deleted', - 'ar_len' => 'rev_len', - ), array( + 'ar_rev_id' => 'rev_id', + 'ar_text_id' => 'rev_text_id', + 'ar_deleted' => 'rev_deleted', + 'ar_len' => 'rev_len', + ), + array( 'rev_id' => $revID, 'page_id = rev_page' - ), __METHOD__ + ), + __METHOD__ ); if ( !$dbw->affectedRows() ) { $this->output( "Revision $revID not found\n" ); } else { $affected += $dbw->affectedRows(); - $pageID = $dbw->selectField( 'revision', 'rev_page', array( 'rev_id' => $revID ), __METHOD__ ); - $pageLatest = $dbw->selectField( 'page', 'page_latest', array( 'page_id' => $pageID ), __METHOD__ ); + $pageID = $dbw->selectField( + 'revision', + 'rev_page', + array( 'rev_id' => $revID ), + __METHOD__ + ); + $pageLatest = $dbw->selectField( + 'page', + 'page_latest', + array( 'page_id' => $pageID ), + __METHOD__ + ); $dbw->delete( 'revision', array( 'rev_id' => $revID ) ); if ( $pageLatest == $revID ) { // Database integrity - $newLatest = $dbw->selectField( 'revision', 'rev_id', array( 'rev_page' => $pageID ), __METHOD__, array( 'ORDER BY' => 'rev_timestamp DESC' ) ); - $dbw->update( 'page', array( 'page_latest' => $newLatest ), array( 'page_id' => $pageID ), __METHOD__ ); + $newLatest = $dbw->selectField( + 'revision', + 'rev_id', + array( 'rev_page' => $pageID ), + __METHOD__, + array( 'ORDER BY' => 'rev_timestamp DESC' ) + ); + $dbw->update( + 'page', + array( 'page_latest' => $newLatest ), + array( 'page_id' => $pageID ), + __METHOD__ + ); } } } diff --git a/maintenance/dev/includes/php.sh b/maintenance/dev/includes/php.sh index 7ce87944..3c5bef0d 100644 --- a/maintenance/dev/includes/php.sh +++ b/maintenance/dev/includes/php.sh @@ -4,7 +4,7 @@ # and previous home directory location # The binary path is returned in $PHP if any -for binary in $PHP `which php || true` "$DEV/php/bin/php" "$HOME/.mediawiki/php/bin/php" "$HOME/.mwphp/bin/php" ]; do +for binary in $PHP $(which php || true) "$DEV/php/bin/php" "$HOME/.mediawiki/php/bin/php" "$HOME/.mwphp/bin/php" ]; do if [ -x "$binary" ]; then if "$binary" -r 'exit((int)!version_compare(PHP_VERSION, "5.4", ">="));'; then PHP="$binary" diff --git a/maintenance/dev/includes/router.php b/maintenance/dev/includes/router.php index a3cc0ba3..0a65e31e 100644 --- a/maintenance/dev/includes/router.php +++ b/maintenance/dev/includes/router.php @@ -59,6 +59,7 @@ if ( $ext == 'php' || $ext == 'php5' ) { # the php webserver will discard post data and things like login # will not function in the dev environment. require $file; + return true; } $mime = false; @@ -79,7 +80,7 @@ if ( !$mime ) { } } if ( $mime ) { - # Use custom handling to serve files with a known mime type + # Use custom handling to serve files with a known MIME type # This way we can serve things like .svg files that the built-in # PHP webserver doesn't understand. # ;) Nicely enough we just happen to bundle a mime.types file @@ -93,6 +94,7 @@ if ( $mime ) { header( "Content-Length: " . filesize( $file ) ); // Stream that out to the browser fpassthru( $f ); + return true; } diff --git a/maintenance/dictionary/mediawiki.dic b/maintenance/dictionary/mediawiki.dic index 164b5b05..df8a34ca 100644 --- a/maintenance/dictionary/mediawiki.dic +++ b/maintenance/dictionary/mediawiki.dic @@ -1,10 +1,10 @@ -ænglisc -ævar &add & &bar +&img &sim &url +&wap ABNF API Aacute @@ -90,7 +90,6 @@ Edit Editor Education Egrave -Ehcache Elig Email Empty @@ -367,6 +366,7 @@ abusive ac acad accel +acceptbilling acceptlang accessdenied accesskey @@ -431,6 +431,8 @@ ai aifc aiff aiprop +airtel +aisort ajaxwatch al alefsym @@ -442,6 +444,7 @@ allcategories alldata alle allexamples +allfileusages allhidden allimages allimit @@ -459,6 +462,8 @@ allpagesbadtitle allpagesprefix allpagesredirect allpagessubmit +allpartners +allredirects allrev alltitles alltransclusions @@ -471,15 +476,16 @@ alreadyexists alreadyrolled alunique am +analyticsconfig anchor anchorclose anchorencode and andconvert +andreescu andtitle anon anoneditwarning -anonlogin anonnotice anononly anonpreviewwarning @@ -656,6 +662,7 @@ badversion balancer balancers banjar +barebone barstein base basefont @@ -685,6 +692,7 @@ bgcolor bgzip bidi bigdelete +bingbot binhex bitdepth bitfield @@ -742,11 +750,13 @@ bmwschema bmysql bname bodycontent +bogo boldening bolding booksources bool boolean +bordercolor borderhack bot botedit @@ -831,12 +841,14 @@ capitalizeallnouns captchaid captchas captchaword +carriersnoips cascade cascadeable cascadeon cascadeprotected cascadeprotectedwarning cascading +cascadinglevels cascadingness categories categories's @@ -850,6 +862,7 @@ categorypage categoryviewer catids catlinks +catmsg catpage catrope cattitles @@ -863,6 +876,7 @@ cedil ceebc cellpadding cellspacing +cellulant central centralauth centralnotice @@ -941,7 +955,6 @@ collapsable collectionsaveascommunitypage collectionsaveasuserpage colname -cologneblue colonseparator colorer colspan @@ -1070,6 +1083,7 @@ danga danielc darr datalen +datapath dataset datasets datasize @@ -1110,6 +1124,7 @@ defaultcontentmodel defaultmessagetext defaultmissing defaultns +defaultoptions defaultsort defaultval deferr @@ -1170,6 +1185,7 @@ devangari devel df dflt +dflts dhtml diams didn @@ -1197,7 +1213,6 @@ disabled disabledtranscode disablemail disablepp -disablesuggest disclaimerpage diskussion displayname @@ -1237,6 +1252,7 @@ domainpart domainparts domas doms +dont dotdotcount dotm dotsc @@ -1254,6 +1270,7 @@ dropdown dump dumpfm dupfunc +dupl duplicatefiles duplicatesoffile dvips @@ -1314,7 +1331,6 @@ editusercssjs edituserjs edoe egrave -ehcache ei eich eiinvalidparammix @@ -1322,7 +1338,9 @@ eimissingparam eititle el elapsedreal +elastica elemname +elems elink eltitle email @@ -1385,6 +1403,7 @@ epcampus epcoordinator epinstructor eponline +eqiad erevoke errno error @@ -1441,6 +1460,7 @@ externaldberror externaldiff externaledit externaleditor +externalimages externallinks externalstore extet @@ -1450,6 +1470,7 @@ extlinks extracts extradata extrafields +extralanglink extraq extratags exturlusage @@ -1520,6 +1541,7 @@ filepage filepath filerenameerror filerepo +filerepoinfo filerevert filerevisions files @@ -1558,6 +1580,7 @@ flagtype flatlist flds float +flrevs fmttime fname fnof @@ -1576,6 +1599,7 @@ forcebot forceditsummary forceeditsummary forcelinkupdate +forcerecursivelinkupdate forcetoc forcontent formaction @@ -1593,6 +1617,7 @@ found founder fr frac +frameborder frameless framesets frasl @@ -1629,12 +1654,15 @@ gadgetcategories gadgets gaid gaifilterredir +gaifrom gallerybox gallerycaption gallerytext gapdir gapfilterredir +gapfrom gaplimit +gapnamespace gapprefix garber gblblock @@ -1648,8 +1676,10 @@ general generatexml generator geocoordinate +geodata geosearch gerrit +geshi getcookie getenv getheader @@ -1686,6 +1716,7 @@ globe gmail gmdate goodtitle +googlebot gopher graymap grayscale @@ -1782,8 +1813,8 @@ hit hitcount hitcounter hits +hlist hmac -hmtl hobby homelink hookaborted @@ -1847,9 +1878,11 @@ ilfrom ilto im image +imagecolorallocate imagegetsize imageinfo imageinvalidfilename +imagelimits imagelinks imagemagick imagemaxsize @@ -1862,6 +1895,7 @@ imagesize imagetype imagetypemismatch imageusage +imagewhitelistenabled imagick imgmultigo imgmultigoto @@ -1933,6 +1967,7 @@ interwiki interwikimap interwikipage interwikis +interwikisearchinfo interwikisource intnull intoken @@ -1980,6 +2015,8 @@ ipchain ipedits iphash ipinrange +ipset +ipsets ipusers iquest irc @@ -1991,12 +2028,14 @@ isconnected iscur isin isip +islocal ismap isminor ismodsince ismulti isnew ispermalink +isroot isself isset istainted @@ -2024,6 +2063,7 @@ iwltitle iwprefix iwtitle iwurl +ized javascript javascripttest jbartsh @@ -2042,6 +2082,7 @@ jslint jsmimetype jsminplus json +jsonconfig jsonfm jsparse jstext @@ -2060,6 +2101,7 @@ keyname keynames keytype khash +kikongo kludgy knownnamespace konqueror @@ -2075,11 +2117,13 @@ langcode langcodes langconversion langlinks +langname langprop langs language languagelinks languages +languageselection languageshtml laquo large @@ -2090,6 +2134,7 @@ lastdot lastedit lasteditor lastedittime +lastfile lastlink lastmod lastmodifiedat @@ -2140,6 +2185,8 @@ link linkarr linkcolour linkprefix +linkprefixcharset +linkpurge links linkstoimage linktbl @@ -2175,6 +2222,7 @@ localdayname localdow locale localhour +localinterwiki localmonth localmonthabbrev localmonthname @@ -2205,6 +2253,7 @@ loginerror loginfo loginlanguagelinks loginlink +loginout loginprompt loginreqlink loginreqpagetext @@ -2241,7 +2290,6 @@ ltitle ltrimmed lurl lysator -möller macr magicarr magicfile @@ -2260,6 +2308,7 @@ mailtext mailto mainmodule mainpage +maint maintainership makesafe male @@ -2295,16 +2344,19 @@ maxwidth mazeland mbresponse mbstring +mccmnc mckey mcklmqw mcrypt mcvalue md mdash +mdot medialink mediaqueries mediatype mediawarning +mediawiki mediawiki's mediawikipage megapixels @@ -2385,6 +2437,7 @@ mkdir mms mobile mobileformat +mobilelanding mobileview modified modifiedarticleprotection @@ -2405,6 +2458,7 @@ moodbar moredotdotdot morelinkstoimage morethan +mouseup move movedarticleprotection moveddeleted @@ -2442,6 +2496,7 @@ msgsmall msgtext msie msmetafile +msnbot mssql msvideo msword @@ -2449,6 +2504,7 @@ mtime mtype mullane multi +multiactions multibyte multicast multipage @@ -2486,10 +2542,13 @@ mysqldump mytalk mytext mywatchlist +möller nabla name namehidden nameinlowercase +namelookup +namemsg names namespace namespacealiases @@ -2530,9 +2589,7 @@ newheader newid newimages newlen -newmessagesdifflink newmessagesdifflinkplural -newmessageslink newmessageslinkplural newname newnames @@ -2548,6 +2605,7 @@ newpos newquery newrevid news +newsectionheaderdefaultlevel newsectionlink newsectionsummary newset @@ -2574,6 +2632,7 @@ nextredirect nextrevision nextval nfkc +nfkd nginx nheight niklas @@ -2787,6 +2846,7 @@ noto notoc notoggle notoken +notpatrollable notransform notreviewable notrustworthy @@ -2888,6 +2948,7 @@ oldtitle oldtitlemsg oline oname +onerror onkeyup online onload @@ -2898,6 +2959,7 @@ onlyquery onsubmit onthisday ontop +onuser openbasedir opendoc opendocument @@ -2906,6 +2968,7 @@ opensearchdescription openssl's openxml openxmlformats +operamini oplus oppositedm optgroup @@ -2920,6 +2983,7 @@ ordertype ordf ordm org +orghttp origcategory ortime oslash @@ -2991,6 +3055,7 @@ pagetriagetagging pagetriagetemplate pageurl pageview +pango param parameters paraminfo @@ -3091,8 +3156,10 @@ pltitles plusminus plusmn pname +png'd pnmtojpeg pnmtopng +pointsize poolcounter popts popularpages @@ -3117,17 +3184,18 @@ pptm pptx precaching precompiled +preemptively preferences preferencestoken prefill prefilled prefix prefixindex +prefixsearch prefixsearchdisabled prefs prefsection -prefsnologin -prefsnologintext +prefsnologintext2 prefsubmit preload preloads @@ -3139,6 +3207,7 @@ preprocessing preprocessors presentationml presep +pretransfer prevchar prevdiff previd @@ -3202,6 +3271,7 @@ protecttoken proto protocol protocols +protorel protos proxied proxyblocker @@ -3224,6 +3294,7 @@ purged qabardjajəbza qbar qbsettings +qlow qmoicj qp quasit @@ -3242,6 +3313,7 @@ querytype question queuefull quickbar +quicksorts quicktemplate quicktime qunit @@ -3337,6 +3409,7 @@ redirectable redirectcreated redirectedfrom redirections +redirector redirectpagesub redirectparams redirects @@ -3351,6 +3424,7 @@ redis redlink redlinks redocument +redux reedyboy reenables reencode @@ -3377,7 +3451,6 @@ relname relnamespace remarticle remembermypassword -rememberpassword removablegroups removal remove @@ -3389,6 +3462,8 @@ remstudent renameuser renaming renderable +renderesibanner +renderwarning renormalized repeating repl @@ -3574,6 +3649,7 @@ selflink selfmove semiglobal semiprotected +semiprotectedlevels semiprotectedpagewarning sendemail sendmail @@ -3692,9 +3768,14 @@ slideshow sm smaxage smil +smpp +sms's +smscontent +smslogs smtp snippet sodipodi +softredirect softtabstop solaris somecontent @@ -3748,6 +3829,7 @@ stabilize stable stablesettings stansvik +starcode start startid startime @@ -3755,6 +3837,7 @@ startsortkey startsortkeyprefix starttime starttimestamp +starttransfer stash stashfailed stashimageinfo @@ -3771,6 +3854,7 @@ stopwords storedversion strcasecmp strcmp +strftime string stripos stripslashes @@ -3887,6 +3971,7 @@ taglist tags tagset tagstack +tahoma tailorings talk talkable @@ -3904,8 +3989,8 @@ talkpagetext talkspace talkspacee talkto -taraškievica tarask +taraškievica target tb tbase @@ -3932,6 +4017,7 @@ test testclean testdata testmailuser +teston testpass testrunner testswarm @@ -3972,6 +4058,7 @@ thumbheight thumbhtml thumbimage thumbinner +thumblimits thumbmime thumbnail thumbnailing @@ -4045,6 +4132,7 @@ toparse topbar toplevel toplinks +topojson toponly torev torevid @@ -4076,7 +4164,9 @@ transwiki troff true truespeed +truncatedtext trustworthy +truteq truthy tsearch tsquery @@ -4099,6 +4189,7 @@ udpprofile ufffd ugrave ui +uids uint ulimit ulink @@ -4124,6 +4215,8 @@ undel undelete undeleted undeletion +undismissable +undismissible undo undoafter undofailure @@ -4138,6 +4231,7 @@ unhelpful unhidden unhide unidata +unidecode unindent unindexed uniq @@ -4165,6 +4259,7 @@ unprotect unprotectedarticle unprotection unprotectthispage +unreadcount unredacted unrequest unrequested @@ -4177,6 +4272,7 @@ unseed unserialization unserialize unserialized +unserializes unserializing unsetting unstub @@ -4220,6 +4316,7 @@ uploadscripted uploadsource uploadstash uploadvirus +uploadwarning uppercased upsih urandom @@ -4303,6 +4400,9 @@ usertoollinks useskin useto usort +usrmonth +ussd +ussdcontent ustar ustoken utfnormal @@ -4347,6 +4447,7 @@ viewdeleted viewhelppage viewmyprivateinfo viewmywatchlist +viewport viewprevnext viewsource viewsourcelink @@ -4363,6 +4464,7 @@ vorbis vpad vrml vslow +vumi vvcv vxml wais @@ -4400,6 +4502,7 @@ wbxml wddx wddxfm weblog +weblogs webm webp webrequest @@ -4442,17 +4545,23 @@ wikilove wikiloveimagelog wikimedia wikimediacommons +wikimediafoundation +wikinews wikipage wikipedia wikipedian wikipedias +wikiquote wikis +wikisource wikisyntax wikitable wikitables wikitech wikitext wikiuser +wikiversity +wikivoyage wiktionary wincache wininet @@ -4477,6 +4586,7 @@ wmls wmlsc wmlscript wmlscriptc +wmnet wordcount wordprocessingml wordwg @@ -4491,6 +4601,7 @@ writerequired writerights wrongpassword x +xanalytics xbitmap xcache xcancel @@ -4512,8 +4623,8 @@ xml xmldoublequote xmlfm xmlimport +xmlmeta xmlns -xmlsafe xmlselect xor xpinstall @@ -4526,6 +4637,7 @@ xxxxx yacute yaml yamlfm +yandex year yes youhavenewmessages @@ -4547,10 +4659,26 @@ yourvariant yourwiki yuml yyyymmddhhiiss +zcmd +zerobanner +zerobar +zerobutton +zeroconfig +zerodontask +zerodot +zeroinfo +zeronet +zeroportal +zfile zhdaemon zhengzhu zhtable zijdel +zlang zlib zoffset +zrma zwnj +ænglisc +ævar +świerkosz diff --git a/maintenance/doMaintenance.php b/maintenance/doMaintenance.php index 3bd508cb..46844c9d 100644 --- a/maintenance/doMaintenance.php +++ b/maintenance/doMaintenance.php @@ -44,6 +44,7 @@ if ( !$maintClass || !class_exists( $maintClass ) ) { } // Get an object to start us off +/** @var Maintenance $maintenance */ $maintenance = new $maintClass(); // Basic sanity checks and such @@ -77,37 +78,24 @@ if ( defined( 'MW_CONFIG_CALLBACK' ) ) { # Use a callback function to configure MediaWiki call_user_func( MW_CONFIG_CALLBACK ); } else { - if ( file_exists( "$IP/../wmf-config/wikimedia-mode" ) ) { - // Load settings, using wikimedia-mode if needed - // @todo FIXME: Replace this hack with general farm-friendly code - # @todo FIXME: Wikimedia-specific stuff needs to go away to an ext - # Maybe a hook? - global $cluster; - $cluster = 'pmtpa'; - require "$IP/../wmf-config/wgConf.php"; - } // Require the configuration (probably LocalSettings.php) require $maintenance->loadSettings(); } -if ( $maintenance->getDbType() === Maintenance::DB_ADMIN && - is_readable( "$IP/AdminSettings.php" ) ) -{ - require "$IP/AdminSettings.php"; -} - if ( $maintenance->getDbType() === Maintenance::DB_NONE ) { - if ( $wgLocalisationCacheConf['storeClass'] === false && ( $wgLocalisationCacheConf['store'] == 'db' || ( $wgLocalisationCacheConf['store'] == 'detect' && !$wgCacheDirectory ) ) ) { - $wgLocalisationCacheConf['storeClass'] = 'LCStore_Null'; + if ( $wgLocalisationCacheConf['storeClass'] === false + && ( $wgLocalisationCacheConf['store'] == 'db' + || ( $wgLocalisationCacheConf['store'] == 'detect' && !$wgCacheDirectory ) ) + ) { + $wgLocalisationCacheConf['storeClass'] = 'LCStoreNull'; } } + +$maintenance->setConfig( ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) ); $maintenance->finalSetup(); // Some last includes require_once "$IP/includes/Setup.php"; -// Much much faster startup than creating a title object -$wgTitle = null; - // Do the work try { $maintenance->execute(); diff --git a/maintenance/dumpBackup.php b/maintenance/dumpBackup.php index 25a777cd..18c78dcd 100644 --- a/maintenance/dumpBackup.php +++ b/maintenance/dumpBackup.php @@ -4,7 +4,7 @@ * wrapper format for export or backup * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -127,5 +127,5 @@ Fancy stuff: (Works? Add examples please.) --filter=<type>[:<options>] Add a filter on an output branch ENDS -); + ); } diff --git a/maintenance/dumpIterator.php b/maintenance/dumpIterator.php index dd468a9f..4b2ff717 100644 --- a/maintenance/dumpIterator.php +++ b/maintenance/dumpIterator.php @@ -5,7 +5,7 @@ * We implement below the simple task of searching inside a dump. * * Copyright © 2011 Platonides - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -57,8 +57,11 @@ abstract class DumpIterator extends Maintenance { $revision = new WikiRevision; $revision->setText( file_get_contents( $this->getOption( 'file' ) ) ); - $revision->setTitle( Title::newFromText( rawurldecode( basename( $this->getOption( 'file' ), '.txt' ) ) ) ); + $revision->setTitle( Title::newFromText( + rawurldecode( basename( $this->getOption( 'file' ), '.txt' ) ) + ) ); $this->handleRevision( $revision ); + return; } @@ -67,7 +70,8 @@ abstract class DumpIterator extends Maintenance { if ( $this->getOption( 'dump' ) == '-' ) { $source = new ImportStreamSource( $this->getStdin() ); } else { - $this->error( "Sorry, I don't support dump filenames yet. Use - and provide it on stdin on the meantime.", true ); + $this->error( "Sorry, I don't support dump filenames yet. " + . "Use - and provide it on stdin on the meantime.", true ); } $importer = new WikiImporter( $source ); @@ -86,8 +90,9 @@ abstract class DumpIterator extends Maintenance { $this->error( round( $this->count / $delta, 2 ) . " pages/sec" ); } - # Perform the memory_get_peak_usage() when all the other data has been output so there's no damage if it dies. - # It is only available since 5.2.0 (since 5.2.1 if you haven't compiled with --enable-memory-limit) + # Perform the memory_get_peak_usage() when all the other data has been + # output so there's no damage if it dies. It is only available since + # 5.2.0 (since 5.2.1 if you haven't compiled with --enable-memory-limit) $this->error( "Memory peak usage of " . memory_get_peak_usage() . " bytes\n" ); } @@ -97,7 +102,7 @@ abstract class DumpIterator extends Maintenance { if ( $this->getDbType() == Maintenance::DB_NONE ) { global $wgUseDatabaseMessages, $wgLocalisationCacheConf, $wgHooks; $wgUseDatabaseMessages = false; - $wgLocalisationCacheConf['storeClass'] = 'LCStore_Null'; + $wgLocalisationCacheConf['storeClass'] = 'LCStoreNull'; $wgHooks['InterwikiLoadPrefix'][] = 'DumpIterator::disableInterwikis'; } } @@ -112,12 +117,13 @@ abstract class DumpIterator extends Maintenance { /** * Callback function for each revision, child classes should override * processRevision instead. - * @param $rev Revision + * @param DatabaseBase $rev */ public function handleRevision( $rev ) { $title = $rev->getTitle(); if ( !$title ) { $this->error( "Got bogus revision with null title!" ); + return; } @@ -167,7 +173,7 @@ class SearchDump extends DumpIterator { } /** - * @param $rev Revision + * @param Revision $rev */ public function processRevision( $rev ) { if ( preg_match( $this->getOption( 'regex' ), $rev->getContent()->getTextForSearchIndex() ) ) { diff --git a/maintenance/dumpLinks.php b/maintenance/dumpLinks.php index be0b4633..888c2dc1 100644 --- a/maintenance/dumpLinks.php +++ b/maintenance/dumpLinks.php @@ -9,7 +9,7 @@ * Dumps ASCII text to stdout; command-line. * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -59,7 +59,7 @@ class DumpLinks extends Maintenance { $lastPage = null; foreach ( $result as $row ) { if ( $lastPage != $row->page_id ) { - if ( isset( $lastPage ) ) { + if ( $lastPage !== null ) { $this->output( "\n" ); } $page = Title::makeTitle( $row->page_namespace, $row->page_title ); @@ -69,7 +69,7 @@ class DumpLinks extends Maintenance { $link = Title::makeTitle( $row->pl_namespace, $row->pl_title ); $this->output( " " . $link->getPrefixedURL() ); } - if ( isset( $lastPage ) ) { + if ( $lastPage !== null ) { $this->output( "\n" ); } } diff --git a/maintenance/dumpSisterSites.php b/maintenance/dumpSisterSites.php index 5f0c5b7c..784dc7a8 100644 --- a/maintenance/dumpSisterSites.php +++ b/maintenance/dumpSisterSites.php @@ -4,7 +4,7 @@ * http://www.eekim.com/cgi-bin/wiki.pl?SisterSites * * Copyright © 2006 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/maintenance/dumpTextPass.php b/maintenance/dumpTextPass.php index 5d783cb9..7c176071 100644 --- a/maintenance/dumpTextPass.php +++ b/maintenance/dumpTextPass.php @@ -3,7 +3,7 @@ * Script that postprocesses XML dumps from dumpBackup.php to add page text * * Copyright (C) 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,7 +29,6 @@ $originalDir = getcwd(); require_once __DIR__ . '/commandLine.inc'; require_once __DIR__ . '/backupTextPass.inc'; - $dumper = new TextPassDumper( $argv ); if ( !isset( $options['help'] ) ) { @@ -62,5 +61,5 @@ Options: --spawn Spawn a subprocess for loading text records --help Display this help message ENDS -); + ); } diff --git a/maintenance/dumpUploads.php b/maintenance/dumpUploads.php index 1a9293cb..9d53f07c 100644 --- a/maintenance/dumpUploads.php +++ b/maintenance/dumpUploads.php @@ -64,7 +64,7 @@ By default, outputs relative paths against the parent directory of \$wgUploadDir $this->mSharedSupplement = true; } } - $this-> { $this->mAction } ( $this->mShared ); + $this->{$this->mAction} ( $this->mShared ); if ( $this->mSharedSupplement ) { $this->fetchUsed( true ); } @@ -73,7 +73,7 @@ By default, outputs relative paths against the parent directory of \$wgUploadDir /** * Fetch a list of used images from a particular image source. * - * @param $shared Boolean: true to pass shared-dir settings to hash func + * @param bool $shared True to pass shared-dir settings to hash func */ function fetchUsed( $shared ) { $dbr = wfGetDB( DB_SLAVE ); @@ -94,7 +94,7 @@ By default, outputs relative paths against the parent directory of \$wgUploadDir /** * Fetch a list of all images from a particular image source. * - * @param $shared Boolean: true to pass shared-dir settings to hash func + * @param bool $shared True to pass shared-dir settings to hash func */ function fetchLocal( $shared ) { $dbr = wfGetDB( DB_SLAVE ); diff --git a/maintenance/edit.php b/maintenance/edit.php index 7c24f0fa..75ec12bf 100644 --- a/maintenance/edit.php +++ b/maintenance/edit.php @@ -38,11 +38,13 @@ class EditCLI extends Maintenance { $this->addOption( 'bot', 'Bot edit', false, false, 'b' ); $this->addOption( 'autosummary', 'Enable autosummary', false, false, 'a' ); $this->addOption( 'no-rc', 'Do not show the change in recent changes', false, false, 'r' ); + $this->addOption( 'nocreate', 'Don\'t create new pages', false, false ); + $this->addOption( 'createonly', 'Only create new pages', false, false ); $this->addArg( 'title', 'Title of article to edit' ); } public function execute() { - global $wgUser, $wgTitle; + global $wgUser; $userName = $this->getOption( 'user', 'Maintenance script' ); $summary = $this->getOption( 'summary', '' ); @@ -52,8 +54,6 @@ class EditCLI extends Maintenance { $noRC = $this->hasOption( 'no-rc' ); $wgUser = User::newFromName( $userName ); - $context = RequestContext::getMain(); - $context->setUser( $wgUser ); if ( !$wgUser ) { $this->error( "Invalid username", true ); } @@ -61,17 +61,22 @@ class EditCLI extends Maintenance { $wgUser->addToDatabase(); } - $wgTitle = Title::newFromText( $this->getArg() ); - if ( !$wgTitle ) { + $title = Title::newFromText( $this->getArg() ); + if ( !$title ) { $this->error( "Invalid title", true ); } - $context->setTitle( $wgTitle ); - $page = WikiPage::factory( $wgTitle ); + if ( $this->hasOption( 'nocreate' ) && !$title->exists() ) { + $this->error( "Page does not exist", true ); + } elseif ( $this->hasOption( 'createonly' ) && $title->exists() ) { + $this->error( "Page already exists", true ); + } + + $page = WikiPage::factory( $title ); # Read the text $text = $this->getStdin( Maintenance::STDIN_ALL ); - $content = ContentHandler::makeContent( $text, $wgTitle ); + $content = ContentHandler::makeContent( $text, $title ); # Do the edit $this->output( "Saving... " ); diff --git a/maintenance/eraseArchivedFile.php b/maintenance/eraseArchivedFile.php index 1c3f0376..94ca604d 100644 --- a/maintenance/eraseArchivedFile.php +++ b/maintenance/eraseArchivedFile.php @@ -27,7 +27,7 @@ require_once __DIR__ . '/Maintenance.php'; /** * Maintenance script to delete archived (non-current) files from storage. * - * @TODO: Maybe add some simple logging + * @todo Maybe add some simple logging * * @ingroup Maintenance * @since 1.22 diff --git a/maintenance/eval.php b/maintenance/eval.php index abedc61a..51f2cace 100644 --- a/maintenance/eval.php +++ b/maintenance/eval.php @@ -1,6 +1,5 @@ <?php /** - * PHP lacks an interactive mode, but this can be very helpful when debugging. * This script lets a command-line user start up the wiki engine and then poke * about by issuing PHP commands directly. * @@ -56,7 +55,7 @@ if ( isset( $options['d'] ) ) { } $useReadline = function_exists( 'readline_add_history' ) - && Maintenance::posix_isatty( 0 /*STDIN*/ ); + && Maintenance::posix_isatty( 0 /*STDIN*/ ); if ( $useReadline ) { $historyFile = isset( $_ENV['HOME'] ) ? @@ -64,13 +63,28 @@ if ( $useReadline ) { readline_read_history( $historyFile ); } +$e = null; // PHP exception while ( ( $line = Maintenance::readconsole() ) !== false ) { + if ( $e && !preg_match( '/^(exit|die);?$/', $line ) ) { + // Internal state may be corrupted or fatals may occur later due + // to some object not being set. Don't drop out of eval in case + // lines were being pasted in (which would then get dumped to the shell). + // Instead, just absorb the remaning commands. Let "exit" through per DWIM. + echo "Exception was thrown before; please restart eval.php\n"; + continue; + } if ( $useReadline ) { readline_add_history( $line ); readline_write_history( $historyFile ); } - $val = eval( $line . ";" ); - if ( wfIsHipHop() || is_null( $val ) ) { + try { + $val = eval( $line . ";" ); + } catch ( Exception $e ) { + echo "Caught exception " . get_class( $e ) . + ": {$e->getMessage()}\n" . $e->getTraceAsString() . "\n"; + continue; + } + if ( wfIsHHVM() || is_null( $val ) ) { echo "\n"; } elseif ( is_string( $val ) || is_numeric( $val ) ) { echo "$val\n"; diff --git a/maintenance/fetchText.php b/maintenance/fetchText.php index 05470d30..fc676b89 100644 --- a/maintenance/fetchText.php +++ b/maintenance/fetchText.php @@ -37,11 +37,11 @@ class FetchText extends Maintenance { /** * returns a string containing the following in order: - * textid - * \n - * length of text (-1 on error = failure to retrieve/unserialize/gunzip/etc) - * \n - * text (may be empty) + * textid + * \n + * length of text (-1 on error = failure to retrieve/unserialize/gunzip/etc) + * \n + * text (may be empty) * * note that that the text string itself is *not* followed by newline */ @@ -59,8 +59,7 @@ class FetchText extends Maintenance { if ( $text === false ) { # actual error, not zero-length text $textLen = "-1"; - } - else { + } else { $textLen = strlen( $text ); } $this->output( $textId . "\n" . $textLen . "\n" . $text ); @@ -69,9 +68,9 @@ class FetchText extends Maintenance { /** * May throw a database error if, say, the server dies during query. - * @param $db DatabaseBase object - * @param $id int The old_id - * @return String + * @param DatabaseBase $db + * @param int $id The old_id + * @return string */ private function doGetText( $db, $id ) { $id = intval( $id ); @@ -83,6 +82,7 @@ class FetchText extends Maintenance { if ( $text === false ) { return false; } + return $text; } } diff --git a/maintenance/findHooks.php b/maintenance/findHooks.php index 373170ff..36760d7e 100644 --- a/maintenance/findHooks.php +++ b/maintenance/findHooks.php @@ -42,6 +42,11 @@ require_once __DIR__ . '/Maintenance.php'; * @ingroup Maintenance */ class FindHooks extends Maintenance { + /* + * Hooks that are ignored + */ + protected static $ignore = array( 'testRunLegacyHooks' ); + public function __construct() { parent::__construct(); $this->mDescription = 'Find hooks that are undocumented, missing, or just plain wrong'; @@ -58,34 +63,53 @@ class FindHooks extends Maintenance { $documented = $this->getHooksFromDoc( $IP . '/docs/hooks.txt' ); $potential = array(); $bad = array(); + + // TODO: Don't hardcode the list of directories $pathinc = array( $IP . '/', $IP . '/includes/', $IP . '/includes/actions/', $IP . '/includes/api/', $IP . '/includes/cache/', + $IP . '/includes/changes/', + $IP . '/includes/clientpool/', $IP . '/includes/content/', $IP . '/includes/context/', + $IP . '/includes/dao/', $IP . '/includes/db/', + $IP . '/includes/debug/', + $IP . '/includes/deferred/', $IP . '/includes/diff/', + $IP . '/includes/externalstore/', + $IP . '/includes/filebackend/', $IP . '/includes/filerepo/', $IP . '/includes/filerepo/file/', + $IP . '/includes/gallery/', + $IP . '/includes/htmlform/', $IP . '/includes/installer/', $IP . '/includes/interwiki/', + $IP . '/includes/jobqueue/', + $IP . '/includes/json/', $IP . '/includes/logging/', $IP . '/includes/media/', + $IP . '/includes/page/', $IP . '/includes/parser/', + $IP . '/includes/rcfeed/', $IP . '/includes/resourceloader/', $IP . '/includes/revisiondelete/', $IP . '/includes/search/', + $IP . '/includes/site/', + $IP . '/includes/skins/', + $IP . '/includes/specialpage/', $IP . '/includes/specials/', $IP . '/includes/upload/', + $IP . '/includes/utils/', $IP . '/languages/', $IP . '/maintenance/', + $IP . '/maintenance/language/', $IP . '/tests/', $IP . '/tests/parser/', $IP . '/tests/phpunit/suites/', - $IP . '/skins/', ); foreach ( $pathinc as $dir ) { @@ -103,15 +127,15 @@ class FindHooks extends Maintenance { $this->printArray( 'Documented and not found', $deprecated ); $this->printArray( 'Unclear hook calls', $bad ); - if ( count( $todo ) == 0 && count( $deprecated ) == 0 && count( $bad ) == 0 ) - { + if ( count( $todo ) == 0 && count( $deprecated ) == 0 && count( $bad ) == 0 ) { $this->output( "Looks good!\n" ); } } /** * Get the hook documentation, either locally or from MediaWiki.org - * @return array of documented hooks + * @param string $doc + * @return array Array of documented hooks */ private function getHooksFromDoc( $doc ) { if ( $this->hasOption( 'online' ) ) { @@ -123,62 +147,75 @@ class FindHooks extends Maintenance { /** * Get hooks from a local file (for example docs/hooks.txt) - * @param $doc string: filename to look in - * @return array of documented hooks + * @param string $doc Filename to look in + * @return array Array of documented hooks */ private function getHooksFromLocalDoc( $doc ) { - $m = array(); - $content = file_get_contents( $doc ); - preg_match_all( "/\n'(.*?)'/", $content, $m ); - return array_unique( $m[1] ); + $m = array(); + $content = file_get_contents( $doc ); + preg_match_all( "/\n'(.*?)':/", $content, $m ); + + return array_unique( $m[1] ); } /** * Get hooks from www.mediawiki.org using the API - * @return array of documented hooks + * @return array Array of documented hooks */ private function getHooksFromOnlineDoc() { - // All hooks - $allhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' ); - $allhookdata = unserialize( $allhookdata ); - $allhooks = array(); - foreach ( $allhookdata['query']['categorymembers'] as $page ) { - $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches ); - if ( $found ) { - $hook = str_replace( ' ', '_', $matches[1] ); - $allhooks[] = $hook; - } + // All hooks + $allhookdata = Http::get( + 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&' + . 'cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' + ); + $allhookdata = unserialize( $allhookdata ); + $allhooks = array(); + foreach ( $allhookdata['query']['categorymembers'] as $page ) { + $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches ); + if ( $found ) { + $hook = str_replace( ' ', '_', $matches[1] ); + $allhooks[] = $hook; } - // Removed hooks - $oldhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Removed_hooks&cmlimit=500&format=php' ); - $oldhookdata = unserialize( $oldhookdata ); - $removed = array(); - foreach ( $oldhookdata['query']['categorymembers'] as $page ) { - $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches ); - if ( $found ) { - $hook = str_replace( ' ', '_', $matches[1] ); - $removed[] = $hook; - } + } + // Removed hooks + $oldhookdata = Http::get( + 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&' + . 'cmtitle=Category:Removed_hooks&cmlimit=500&format=php' + ); + $oldhookdata = unserialize( $oldhookdata ); + $removed = array(); + foreach ( $oldhookdata['query']['categorymembers'] as $page ) { + $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches ); + if ( $found ) { + $hook = str_replace( ' ', '_', $matches[1] ); + $removed[] = $hook; } - return array_diff( $allhooks, $removed ); + } + + return array_diff( $allhooks, $removed ); } /** * Get hooks from a PHP file - * @param $file string Full filename to the PHP file. - * @return array of hooks found. + * @param string $file Full filename to the PHP file. + * @return array Array of hooks found */ private function getHooksFromFile( $file ) { $content = file_get_contents( $file ); $m = array(); - preg_match_all( '/(?:wfRunHooks|Hooks\:\:run|ContentHandler\:\:runLegacyHooks)\(\s*([\'"])(.*?)\1/', $content, $m ); + preg_match_all( + '/(?:wfRunHooks|Hooks\:\:run|ContentHandler\:\:runLegacyHooks)\(\s*([\'"])(.*?)\1/', + $content, + $m + ); + return $m[2]; } /** * Get hooks from the source code. - * @param $path Directory where the include files can be found - * @return array of hooks found. + * @param string $path Directory where the include files can be found + * @return array Array of hooks found */ private function getHooksFromPath( $path ) { $hooks = array(); @@ -191,13 +228,14 @@ class FindHooks extends Maintenance { } closedir( $dh ); } + return $hooks; } /** * Get bad hooks (where the hook name could not be determined) from a PHP file - * @param $file string Full filename to the PHP file. - * @return array of bad wfRunHooks() lines + * @param string $file Full filename to the PHP file. + * @return array Array of bad wfRunHooks() lines */ private function getBadHooksFromFile( $file ) { $content = file_get_contents( $file ); @@ -208,13 +246,14 @@ class FindHooks extends Maintenance { foreach ( $m[0] as $match ) { $list[] = $match . "(" . $file . ")"; } + return $list; } /** * Get bad hooks from the source code. - * @param $path Directory where the include files can be found - * @return array of bad wfRunHooks() lines + * @param string $path Directory where the include files can be found + * @return array Array of bad wfRunHooks() lines */ private function getBadHooksFromPath( $path ) { $hooks = array(); @@ -228,21 +267,25 @@ class FindHooks extends Maintenance { } closedir( $dh ); } + return $hooks; } /** * Nicely output the array - * @param $msg String: a message to show before the value - * @param $arr Array: an array - * @param $sort Boolean: whether to sort the array (Default: true) + * @param string $msg A message to show before the value + * @param array $arr + * @param bool $sort Whether to sort the array (Default: true) */ private function printArray( $msg, $arr, $sort = true ) { if ( $sort ) { asort( $arr ); } + foreach ( $arr as $v ) { - $this->output( "$msg: $v\n" ); + if ( !in_array( $v, self::$ignore ) ) { + $this->output( "$msg: $v\n" ); + } } } } diff --git a/maintenance/findMissingFiles.php b/maintenance/findMissingFiles.php new file mode 100644 index 00000000..5f9f643a --- /dev/null +++ b/maintenance/findMissingFiles.php @@ -0,0 +1,115 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @author Aaron Schulz + */ + +require_once __DIR__ . '/Maintenance.php'; + +class FindMissingFiles extends Maintenance { + function __construct() { + parent::__construct(); + + $this->mDescription = 'Find registered files with no corresponding file.'; + $this->addOption( 'start', 'Starting file name', false, true ); + $this->addOption( 'mtimeafter', 'Only include files changed since this time', false, true ); + $this->addOption( 'mtimebefore', 'Only includes files changed before this time', false, true ); + $this->setBatchSize( 300 ); + } + + function execute() { + $lastName = $this->getOption( 'start', '' ); + + $repo = RepoGroup::singleton()->getLocalRepo(); + $dbr = $repo->getSlaveDB(); + $be = $repo->getBackend(); + + $mtime1 = $dbr->timestampOrNull( $this->getOption( 'mtimeafter', null ) ); + $mtime2 = $dbr->timestampOrNull( $this->getOption( 'mtimebefore', null ) ); + + $joinTables = array( 'image' ); + $joinConds = array( 'image' => array( 'INNER JOIN', 'img_name = page_title' ) ); + if ( $mtime1 || $mtime2 ) { + $joinTables[] = 'logging'; + $on = array( 'log_page = page_id', 'log_type' => array( 'upload', 'move', 'delete' ) ); + if ( $mtime1 ) { + $on[] = "log_timestamp > {$dbr->addQuotes($mtime1)}"; + } + if ( $mtime2 ) { + $on[] = "log_timestamp < {$dbr->addQuotes($mtime2)}"; + } + $joinConds['logging'] = array( 'INNER JOIN', $on ); + } + + do { + $res = $dbr->select( + array_merge( array( 'page' ), $joinTables ), + array( 'img_name' => 'DISTINCT(page_title)' ), + array( 'page_namespace' => NS_FILE, + "page_title >= " . $dbr->addQuotes( $lastName ) ), + __METHOD__, + array( 'ORDER BY' => 'page_title', 'LIMIT' => $this->mBatchSize ), + $joinConds + ); + + // Check if any of these files are missing... + $pathsByName = array(); + foreach ( $res as $row ) { + $file = $repo->newFile( $row->img_name ); + $pathsByName[$row->img_name] = $file->getPath(); + $lastName = $row->img_name; + } + $be->preloadFileStat( array( 'srcs' => $pathsByName ) ); + foreach ( $pathsByName as $path ) { + if ( $be->fileExists( array( 'src' => $path ) ) === false ) { + $this->output( "$path\n" ); + } + } + + // Find all missing old versions of any of the files in this batch... + if ( count( $pathsByName ) ) { + $ores = $dbr->select( 'oldimage', + array( 'oi_name', 'oi_archive_name' ), + array( 'oi_name' => array_keys( $pathsByName ) ), + __METHOD__ + ); + + $checkPaths = array(); + foreach ( $ores as $row ) { + if ( !strlen( $row->oi_archive_name ) ) { + continue; // broken row + } + $file = $repo->newFromArchiveName( $row->oi_name, $row->oi_archive_name ); + $checkPaths[] = $file->getPath(); + } + + foreach ( array_chunk( $checkPaths, $this->mBatchSize ) as $paths ) { + $be->preloadFileStat( array( 'srcs' => $paths ) ); + foreach ( $paths as $path ) { + if ( $be->fileExists( array( 'src' => $path ) ) === false ) { + $this->output( "$path\n" ); + } + } + } + } + } while ( $res->numRows() >= $this->mBatchSize ); + } +} + +$maintClass = 'FindMissingFiles'; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/fixDoubleRedirects.php b/maintenance/fixDoubleRedirects.php index 523be7ef..95682847 100644 --- a/maintenance/fixDoubleRedirects.php +++ b/maintenance/fixDoubleRedirects.php @@ -3,7 +3,7 @@ * Fix double redirects. * * Copyright © 2011 Ilmari Karonen <nospam@vyznev.net> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,13 +44,14 @@ class FixDoubleRedirects extends Maintenance { public function execute() { $async = $this->getOption( 'async', false ); $dryrun = $this->getOption( 'dry-run', false ); - $title = $this->getOption( 'title' ); - if ( isset( $title ) ) { - $title = Title::newFromText( $title ); + if ( $this->hasOption( 'title' ) ) { + $title = Title::newFromText( $this->getOption( 'title' ) ); if ( !$title || !$title->isRedirect() ) { $this->error( $title->getPrefixedText() . " is not a redirect!\n", true ); } + } else { + $title = null; } $dbr = wfGetDB( DB_SLAVE ); @@ -71,11 +72,11 @@ class FixDoubleRedirects extends Maintenance { 'rd_from = pa.page_id', 'rd_namespace = pb.page_namespace', 'rd_title = pb.page_title', - '(rd_interwiki IS NULL OR rd_interwiki = "")', // bug 40352 + 'rd_interwiki IS NULL OR rd_interwiki = ' . $dbr->addQuotes( '' ), // bug 40352 'pb.page_is_redirect' => 1, ); - if ( isset( $title ) ) { + if ( $title != null ) { $conds['pb.page_namespace'] = $title->getNamespace(); $conds['pb.page_title'] = $title->getDBkey(); } @@ -85,6 +86,7 @@ class FixDoubleRedirects extends Maintenance { if ( !$res->numRows() ) { $this->output( "No double redirects found.\n" ); + return; } @@ -105,7 +107,8 @@ class FixDoubleRedirects extends Maintenance { if ( !$async ) { $success = ( $dryrun ? true : $job->run() ); if ( !$success ) { - $this->error( "Error fixing " . $titleA->getPrefixedText() . ": " . $job->getLastError() . "\n" ); + $this->error( "Error fixing " . $titleA->getPrefixedText() + . ": " . $job->getLastError() . "\n" ); } } else { $jobs[] = $job; diff --git a/maintenance/fixExtLinksProtocolRelative.php b/maintenance/fixExtLinksProtocolRelative.php index 02d65ed1..0c60e62c 100644 --- a/maintenance/fixExtLinksProtocolRelative.php +++ b/maintenance/fixExtLinksProtocolRelative.php @@ -34,7 +34,8 @@ require_once __DIR__ . '/Maintenance.php'; class FixExtLinksProtocolRelative extends LoggedUpdateMaintenance { public function __construct() { parent::__construct(); - $this->mDescription = "Fixes any entries in the externallinks table containing protocol-relative URLs"; + $this->mDescription = + "Fixes any entries in the externallinks table containing protocol-relative URLs"; } protected function getUpdateKey() { @@ -49,6 +50,7 @@ class FixExtLinksProtocolRelative extends LoggedUpdateMaintenance { $db = wfGetDB( DB_MASTER ); if ( !$db->tableExists( 'externallinks' ) ) { $this->error( "externallinks table does not exist" ); + return false; } $this->output( "Fixing protocol-relative entries in the externallinks table...\n" ); @@ -79,9 +81,18 @@ class FixExtLinksProtocolRelative extends LoggedUpdateMaintenance { ) ), __METHOD__, array( 'IGNORE' ) ); - $db->delete( 'externallinks', array( 'el_index' => $row->el_index, 'el_from' => $row->el_from, 'el_to' => $row->el_to ), __METHOD__ ); + $db->delete( + 'externallinks', + array( + 'el_index' => $row->el_index, + 'el_from' => $row->el_from, + 'el_to' => $row->el_to + ), + __METHOD__ + ); } $this->output( "Done, $count rows updated.\n" ); + return true; } } diff --git a/maintenance/fixSlaveDesync.php b/maintenance/fixSlaveDesync.php index e4e557fe..a5418ced 100644 --- a/maintenance/fixSlaveDesync.php +++ b/maintenance/fixSlaveDesync.php @@ -30,6 +30,9 @@ require_once __DIR__ . '/Maintenance.php'; * @ingroup Maintenance */ class FixSlaveDesync extends Maintenance { + /** @var array */ + private $slaveIndexes; + public function __construct() { parent::__construct(); $this->mDescription = ""; @@ -41,7 +44,8 @@ class FixSlaveDesync extends Maintenance { public function execute() { $this->slaveIndexes = array(); - for ( $i = 1; $i < wfGetLB()->getServerCount(); $i++ ) { + $serverCount = wfGetLB()->getServerCount(); + for ( $i = 1; $i < $serverCount; $i++ ) { if ( wfGetLB()->isNonZeroLoad( $i ) ) { $this->slaveIndexes[] = $i; } @@ -66,7 +70,12 @@ class FixSlaveDesync extends Maintenance { $n = 0; $dbw = wfGetDB( DB_MASTER ); $masterIDs = array(); - $res = $dbw->select( 'page', array( 'page_id', 'page_latest' ), array( 'page_id<6054123' ), __METHOD__ ); + $res = $dbw->select( + 'page', + array( 'page_id', 'page_latest' ), + array( 'page_id<6054123' ), + __METHOD__ + ); $this->output( "Number of pages: " . $res->numRows() . "\n" ); foreach ( $res as $row ) { $masterIDs[$row->page_id] = $row->page_latest; @@ -78,7 +87,12 @@ class FixSlaveDesync extends Maintenance { foreach ( $this->slaveIndexes as $i ) { $db = wfGetDB( $i ); - $res = $db->select( 'page', array( 'page_id', 'page_latest' ), array( 'page_id<6054123' ), __METHOD__ ); + $res = $db->select( + 'page', + array( 'page_id', 'page_latest' ), + array( 'page_id<6054123' ), + __METHOD__ + ); foreach ( $res as $row ) { if ( isset( $masterIDs[$row->page_id] ) && $masterIDs[$row->page_id] != $row->page_latest ) { $desync[$row->page_id] = true; @@ -87,12 +101,13 @@ class FixSlaveDesync extends Maintenance { } } $this->output( "\n" ); + return $desync; } /** * Fix a broken page entry - * @param $pageID int The page_id to fix + * @param int $pageID The page_id to fix */ private function desyncFixPage( $pageID ) { # Check for a corrupted page_latest @@ -122,6 +137,7 @@ class FixSlaveDesync extends Maintenance { if ( !$found ) { $this->output( "page_id $pageID seems fine\n" ); $dbw->commit( __METHOD__ ); + return; } @@ -141,7 +157,8 @@ class FixSlaveDesync extends Maintenance { if ( count( $masterIDs ) < count( $slaveIDs ) ) { $missingIDs = array_diff( $slaveIDs, $masterIDs ); if ( count( $missingIDs ) ) { - $this->output( "Found " . count( $missingIDs ) . " lost in master, copying from slave... " ); + $this->output( "Found " . count( $missingIDs ) + . " lost in master, copying from slave... " ); $dbFrom = $dbw; $found = true; $toMaster = true; @@ -151,7 +168,8 @@ class FixSlaveDesync extends Maintenance { } else { $missingIDs = array_diff( $masterIDs, $slaveIDs ); if ( count( $missingIDs ) ) { - $this->output( "Found " . count( $missingIDs ) . " missing revision(s), copying from master... " ); + $this->output( "Found " . count( $missingIDs ) + . " missing revision(s), copying from master... " ); $dbFrom = $dbw; $found = true; $toMaster = false; @@ -199,11 +217,23 @@ class FixSlaveDesync extends Maintenance { if ( $found ) { $this->output( "Fixing page_latest... " ); if ( $toMaster ) { - # $dbw->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), __METHOD__ ); + /* + $dbw->update( + 'page', + array( 'page_latest' => $realLatest ), + array( 'page_id' => $pageID ), + __METHOD__ + ); + */ } else { foreach ( $this->slaveIndexes as $i ) { $db = wfGetDB( $i ); - $db->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), __METHOD__ ); + $db->update( + 'page', + array( 'page_latest' => $realLatest ), + array( 'page_id' => $pageID ), + __METHOD__ + ); } } $this->output( "done\n" ); diff --git a/maintenance/fixTimestamps.php b/maintenance/fixTimestamps.php index b0609d17..5431cf2c 100644 --- a/maintenance/fixTimestamps.php +++ b/maintenance/fixTimestamps.php @@ -85,11 +85,11 @@ class FixTimestamps extends Maintenance { if ( $sign == 0 || $sign == $expectedSign ) { // Monotonic change $lastNormal = $timestamp; - ++ $numGoodRevs; + ++$numGoodRevs; continue; } elseif ( abs( $delta ) <= $grace ) { // Non-monotonic change within grace interval - ++ $numGoodRevs; + ++$numGoodRevs; continue; } else { // Non-monotonic change larger than grace interval @@ -100,7 +100,7 @@ class FixTimestamps extends Maintenance { $numBadRevs = count( $badRevs ); if ( $numBadRevs > $numGoodRevs ) { $this->error( - "The majority of revisions in the search interval are marked as bad. + "The majority of revisions in the search interval are marked as bad. Are you sure the offset ($offset) has the right sign? Positive means the clock was incorrectly set forward, negative means the clock was incorrectly set back. @@ -117,7 +117,8 @@ class FixTimestamps extends Maintenance { $fixup = -$offset; $sql = "UPDATE $revisionTable " . - "SET rev_timestamp=DATE_FORMAT(DATE_ADD(rev_timestamp, INTERVAL $fixup SECOND), '%Y%m%d%H%i%s') " . + "SET rev_timestamp=" + . "DATE_FORMAT(DATE_ADD(rev_timestamp, INTERVAL $fixup SECOND), '%Y%m%d%H%i%s') " . "WHERE rev_id IN (" . $dbw->makeList( $badRevs ) . ')'; $dbw->query( $sql, __METHOD__ ); $this->output( "Done\n" ); diff --git a/maintenance/fixUserRegistration.php b/maintenance/fixUserRegistration.php index 097936c9..878593c7 100644 --- a/maintenance/fixUserRegistration.php +++ b/maintenance/fixUserRegistration.php @@ -44,10 +44,20 @@ class FixUserRegistration extends Maintenance { foreach ( $res as $row ) { $id = $row->user_id; // Get first edit time - $timestamp = $dbr->selectField( 'revision', 'MIN(rev_timestamp)', array( 'rev_user' => $id ), __METHOD__ ); + $timestamp = $dbr->selectField( + 'revision', + 'MIN(rev_timestamp)', + array( 'rev_user' => $id ), + __METHOD__ + ); // Update if ( !empty( $timestamp ) ) { - $dbw->update( 'user', array( 'user_registration' => $timestamp ), array( 'user_id' => $id ), __METHOD__ ); + $dbw->update( + 'user', + array( 'user_registration' => $timestamp ), + array( 'user_id' => $id ), + __METHOD__ + ); $this->output( "$id $timestamp\n" ); } else { $this->output( "$id NULL\n" ); diff --git a/maintenance/fuzz-tester.php b/maintenance/fuzz-tester.php deleted file mode 100644 index 548bb2f2..00000000 --- a/maintenance/fuzz-tester.php +++ /dev/null @@ -1,2692 +0,0 @@ -<?php -/** - * Performs fuzz-style testing of MediaWiki's parser and forms. - * - * Copyright © 2006 Nick Jenkins - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup Maintenance - * @author Nick Jenkins ( http://nickj.org/ ). - - -Started: 18 May 2006. - -Description: - Performs fuzz-style testing of MediaWiki's parser and forms. - -How: - - Generate lots of nasty wiki text. - - Ask the Parser to render that wiki text to HTML, or ask MediaWiki's forms - to deal with that wiki text. - - Check MediaWiki's output for problems. - - Repeat. - -Why: - - To help find bugs. - - To help find security issues, or potential security issues. - -What type of problems are being checked for: - - Unclosed tags. - - Errors or interesting warnings from Tidy. - - PHP errors / warnings / notices. - - MediaWiki internal errors. - - Very slow responses. - - No response from apache. - - Optionally checking for malformed HTML using the W3C validator. - -Background: - Many of the wikiFuzz class methods are a modified PHP port, - of a "shameless" Python port, of LCAMTUF'S MANGELME: - - http://www.securiteam.com/tools/6Z00N1PBFK.html - - http://www.securityfocus.com/archive/1/378632/2004-10-15/2004-10-21/0 - -Video: - There's an XviD video discussing this fuzz tester. You can get it from: - http://files.nickj.org/MediaWiki/Fuzz-Testing-MediaWiki-xvid.avi - -Requirements: - To run this, you will need: - - Command-line PHP5, with PHP-curl enabled (not all installations have this - enabled - try "apt-get install php5-curl" if you're on Debian to install). - - the Tidy standalone executable. ("apt-get install tidy"). - -Optional: - - If you want to run the curl scripts, you'll need standalone curl installed - ("apt-get install curl") - - For viewing the W3C validator output on a command line, the "html2text" - program may be useful ("apt-get install html2text") - -Saving tests and test results: - Any of the fuzz tests which find problems are saved for later review. - In order to help track down problems, tests are saved in a number of - different formats. The default filename extensions and their meanings are: - - ".test.php" : PHP script that reproduces just that one problem using PHP-Curl. - - ".curl.sh" : Shell script that reproduces that problem using standalone curl. - - ".data.bin" : The serialized PHP data so that this script can re-run the test. - - ".info.txt" : A human-readable text file with details of the field contents. - -Wiki configuration for testing: - You should make some additions to LocalSettings.php in order to catch the most - errors. Note this configuration is for **TESTING PURPOSES ONLY**, and is IN NO - WAY, SHAPE, OR FORM suitable for deployment on a hostile network. That said, - personally I find these additions to be the most helpful for testing purposes: - - // --------- Start --------- - // Everyone can do everything. Very useful for testing, yet useless for deployment. - $wgGroupPermissions['*']['autoconfirmed'] = true; - $wgGroupPermissions['*']['block'] = true; - $wgGroupPermissions['*']['bot'] = true; - $wgGroupPermissions['*']['delete'] = true; - $wgGroupPermissions['*']['deletedhistory'] = true; - $wgGroupPermissions['*']['deleterevision'] = true; - $wgGroupPermissions['*']['editinterface'] = true; - $wgGroupPermissions['*']['hiderevision'] = true; - $wgGroupPermissions['*']['import'] = true; - $wgGroupPermissions['*']['importupload'] = true; - $wgGroupPermissions['*']['minoredit'] = true; - $wgGroupPermissions['*']['move'] = true; - $wgGroupPermissions['*']['patrol'] = true; - $wgGroupPermissions['*']['protect'] = true; - $wgGroupPermissions['*']['proxyunbannable'] = true; - $wgGroupPermissions['*']['renameuser'] = true; - $wgGroupPermissions['*']['reupload'] = true; - $wgGroupPermissions['*']['reupload-shared'] = true; - $wgGroupPermissions['*']['rollback'] = true; - $wgGroupPermissions['*']['siteadmin'] = true; - $wgGroupPermissions['*']['unwatchedpages'] = true; - $wgGroupPermissions['*']['upload'] = true; - $wgGroupPermissions['*']['userrights'] = true; - $wgGroupPermissions['*']['renameuser'] = true; - $wgGroupPermissions['*']['makebot'] = true; - $wgGroupPermissions['*']['makesysop'] = true; - - // Enable weird and wonderful options: - // Increase default error reporting level. - error_reporting (E_ALL); // At a later date could be increased to E_ALL | E_STRICT - $wgEnableUploads = true; // enable uploads. - $wgDBerrorLog = "/root/mediawiki-db-error-log.txt"; // log DB errors, replace with suitable path. - $wgShowSQLErrors = true; // Show SQL errors (instead of saying the query was hidden). - $wgShowExceptionDetails = true; // want backtraces. - $wgEnableAPI = true; // enable API. - $wgEnableWriteAPI = true; // enable API. - - // Install & enable Parser Hook extensions to increase code coverage. E.g.: - require_once "extensions/ParserFunctions/ParserFunctions.php"; - require_once "extensions/Cite/Cite.php"; - require_once "extensions/inputbox/inputbox.php"; - require_once "extensions/Sort/Sort.php"; - require_once "extensions/wikihiero/wikihiero.php"; - require_once "extensions/CharInsert/CharInsert.php"; - require_once "extensions/FixedImage/FixedImage.php"; - - // Install & enable Special Page extensions to increase code coverage. E.g.: - require_once "extensions/Cite/SpecialCite.php"; - require_once "extensions/Renameuser/SpecialRenameuser.php"; - // --------- End --------- - - If you want to try E_STRICT error logging, add this to the above: - // --------- Start --------- - error_reporting (E_ALL | E_STRICT); - set_error_handler( 'error_handler' ); - function error_handler ($type, $message, $file=__FILE__, $line=__LINE__) { - if ($message == "var: Deprecated. Please use the public/private/protected modifiers") return; - print "<br />\n<b>Strict Standards:</b> Type: <b>$type</b>: $message in <b>$file</b> on line <b>$line</b><br />\n"; - } - // --------- End --------- - - Also add/change this in LocalSettings.php: - // --------- Start --------- - $wgEnableProfileInfo = true; - $wgDBserver = "localhost"; // replace with DB server hostname - // --------- End --------- - -Usage: - Run with "php fuzz-tester.php". - To see the various command-line options, run "php fuzz-tester.php --help". - To stop the script, press Ctrl-C. - -Console output: - - If requested, first any previously failed tests will be rerun. - - Then new tests will be generated and run. Any tests that fail will be saved, - and a brief message about why they failed will be printed on the console. - - The console will show the number of tests run, time run, number of tests - failed, number of tests being done per minute, and the name of the current test. - -TODO: - Some known things that could improve this script: - - Logging in with cookie jar storage needed for some tests (as there are some - pages that cannot be tested without being logged in, and which are currently - untested - e.g. Special:Emailuser, Special:Preferences, adding to Watchist). - - Testing of Timeline extension (I cannot test as ploticus has/had issues on - my architecture). - -*/ - -// ///////////////////////// COMMAND LINE HELP //////////////////////////////////// - -// This is a command line script, load MediaWiki env (gives command line options); -require_once __DIR__ . '/commandLine.inc'; - -// if the user asked for an explanation of command line options. -if ( isset( $options["help"] ) ) { - print <<<ENDS -MediaWiki $wgVersion fuzz tester -Usage: php {$_SERVER["SCRIPT_NAME"]} [--quiet] [--base-url=<url-to-test-wiki>] - [--directory=<failed-test-path>] [--include-binary] - [--w3c-validate] [--delete-passed-retests] [--help] - [--user=<username>] [--password=<password>] - [--rerun-failed-tests] [--max-errors=<int>] - [--max-runtime=<num-minutes>] - [--specific-test=<test-name>] - -Options: - --quiet : Hides passed tests, shows only failed tests. - --base-url : URL to a wiki on which to run the tests. - The "http://" is optional and can be omitted. - --directory : Full path to directory for storing failed tests. - Will be created if it does not exist. - --include-binary : Includes non-alphanumeric characters in the tests. - --w3c-validate : Validates pages using the W3C's web validator. - Slow. Currently many pages fail validation. - --user : Login name of a valid user on your test wiki. - --password : Password for the valid user on your test wiki. - --delete-passed-retests : Will delete retests that now pass. - Requires --rerun-failed-tests to be meaningful. - --rerun-failed-tests : Whether to rerun any previously failed tests. - --max-errors : Maximum number of errors to report before exiting. - Does not include errors from --rerun-failed-tests - --max-runtime : Maximum runtime, in minutes, to run before exiting. - Only applies to new tests, not --rerun-failed-tests - --specific-test : Runs only the specified fuzz test. - Only applies to new tests, not --rerun-failed-tests - --keep-passed-tests : Saves all test files, even those that pass. - --help : Show this help message. - -Example: - If you wanted to fuzz test a nightly MediaWiki checkout using cron for 1 hour, - and only wanted to be informed of errors, and did not want to redo previously - failed tests, and wanted a maximum of 100 errors, then you could do: - php {$_SERVER["SCRIPT_NAME"]} --quiet --max-errors=100 --max-runtime=60 - - -ENDS; - - exit( 0 ); -} - - -// if we got command line options, check they look valid. -$validOptions = array ( "quiet", "base-url", "directory", "include-binary", - "w3c-validate", "user", "password", "delete-passed-retests", - "rerun-failed-tests", "max-errors", - "max-runtime", "specific-test", "keep-passed-tests", "help" ); -if ( !empty( $options ) ) { - $unknownArgs = array_diff ( array_keys( $options ), $validOptions ); - foreach ( $unknownArgs as $invalidArg ) { - print "Ignoring invalid command-line option: --$invalidArg\n"; - } -} - - -// /////////////////////////// CONFIGURATION //////////////////////////////////// - -// URL to some wiki on which we can run our tests. -if ( !empty( $options["base-url"] ) ) { - define( "WIKI_BASE_URL", $options["base-url"] ); -} else { - define( "WIKI_BASE_URL", $wgServer . $wgScriptPath . '/' ); -} - -// The directory name where we store the output. -// Example for Windows: "c:\\temp\\wiki-fuzz" -if ( !empty( $options["directory"] ) ) { - define( "DIRECTORY", $options["directory"] ); -} else { - define( "DIRECTORY", "{$wgUploadDirectory}/fuzz-tests" ); -} - -// Should our test fuzz data include binary strings? -define( "INCLUDE_BINARY", isset( $options["include-binary"] ) ); - -// Whether we want to validate HTML output on the web. -// At the moment very few generated pages will validate, so not recommended. -define( "VALIDATE_ON_WEB", isset( $options["w3c-validate"] ) ); -// URL to use to validate our output: -define( "VALIDATOR_URL", "http://validator.w3.org/check" ); - -// Location of Tidy standalone executable. -define( "PATH_TO_TIDY", "/usr/bin/tidy" ); - -// The name of a user who has edited on your wiki. Used -// when testing the Special:Contributions and Special:Userlogin page. -if ( !empty( $options["user"] ) ) { - define( "USER_ON_WIKI", $options["user"] ); -} else { - define( "USER_ON_WIKI", "nickj" ); -} - -// The password of the above user. Used when testing the login page, -// and to do this we sometimes need to login successfully. -if ( !empty( $options["password"] ) ) { - define( "USER_PASSWORD", $options["password"] ); -} else { - // And no, this is not a valid password on any public wiki. - define( "USER_PASSWORD", "nickj" ); -} - -// If we have a test that failed, and then we run it again, and it passes, -// do you want to delete it or keep it? -define( "DELETE_PASSED_RETESTS", isset( $options["delete-passed-retests"] ) ); - -// Do we want to rerun old saved tests at script startup? -// Set to true to help catch regressions, or false if you only want new stuff. -define( "RERUN_OLD_TESTS", isset( $options["rerun-failed-tests"] ) ); - -// File where the database errors are logged. Should be defined in LocalSettings.php. -define( "DB_ERROR_LOG_FILE", $wgDBerrorLog ); - -// Run in chatty mode (all output, default), or run in quiet mode (only prints out details of failed tests)? -define( "QUIET", isset( $options["quiet"] ) ); - -// Keep all test files, even those that pass. Potentially useful to tracking input that causes something -// unusual to happen, if you don't know what "unusual" is until later. -define( "KEEP_PASSED_TESTS", isset( $options["keep-passed-tests"] ) ); - -// The maximum runtime, if specified. -if ( !empty( $options["max-runtime"] ) && intval( $options["max-runtime"] ) > 0 ) { - define( "MAX_RUNTIME", intval( $options["max-runtime"] ) ); -} - -// The maximum number of problems to find, if specified. Excludes retest errors. -if ( !empty( $options["max-errors"] ) && intval( $options["max-errors"] ) > 0 ) { - define( "MAX_ERRORS", intval( $options["max-errors"] ) ); -} - -// if the user has requested a specific test (instead of all tests), and the test they asked for looks valid. -if ( !empty( $options["specific-test"] ) ) { - if ( class_exists( $options["specific-test"] ) && get_parent_class( $options["specific-test"] ) == "pageTest" ) { - define( "SPECIFIC_TEST", $options["specific-test"] ); - } - else { - print "Ignoring invalid --specific-test\n"; - } -} - -// Define the file extensions we'll use: -define( "PHP_TEST" , ".test.php" ); -define( "CURL_TEST", ".curl.sh" ); -define( "DATA_FILE", ".data.bin" ); -define( "INFO_FILE", ".info.txt" ); -define( "HTML_FILE", ".wiki_preview.html" ); - -// If it goes wrong, we want to know about it. -error_reporting( E_ALL | E_STRICT ); - -// ////////////// A CLASS THAT GENERATES RANDOM NASTY WIKI & HTML STRINGS ////////////////////// - -class wikiFuzz { - - // Only some HTML tags are understood with params by MediaWiki, the rest are ignored. - // List the tags that accept params below, as well as what those params are. - public static $data = array( - "B" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "CAPTION" => array( "CLASS", "ID", "STYLE", "align", "lang", "dir", "title" ), - "CENTER" => array( "CLASS", "STYLE", "ID", "lang", "dir", "title" ), - "DIV" => array( "CLASS", "STYLE", "ID", "align", "lang", "dir", "title" ), - "FONT" => array( "CLASS", "STYLE", "ID", "lang", "dir", "title", "face", "size", "color" ), - "H1" => array( "STYLE", "CLASS", "ID", "align", "lang", "dir", "title" ), - "H2" => array( "STYLE", "CLASS", "ID", "align", "lang", "dir", "title" ), - "HR" => array( "STYLE", "CLASS", "ID", "WIDTH", "lang", "dir", "title", "size", "noshade" ), - "LI" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "type", "value" ), - "TABLE" => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "BORDER", "CELLPADDING", - "CELLSPACING", "lang", "dir", "title", "summary", "frame", "rules" ), - "TD" => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "COLSPAN", "ROWSPAN", - "VALIGN", "abbr", "axis", "headers", "scope", "nowrap", "height", "lang", - "dir", "title", "char", "charoff" ), - "TH" => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "COLSPAN", "ROWSPAN", - "VALIGN", "abbr", "axis", "headers", "scope", "nowrap", "height", "lang", - "dir", "title", "char", "charoff" ), - "TR" => array( "CLASS", "STYLE", "ID", "BGCOLOR", "ALIGN", "VALIGN", "lang", "dir", "title", "char", "charoff" ), - "UL" => array( "CLASS", "STYLE", "ID", "lang", "dir", "title", "type" ), - "P" => array( "style", "class", "id", "align", "lang", "dir", "title" ), - "blockquote" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "cite" ), - "span" => array( "CLASS", "ID", "STYLE", "align", "lang", "dir", "title" ), - "code" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "tt" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "small" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "big" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "s" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "u" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "del" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "datetime", "cite" ), - "ins" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "datetime", "cite" ), - "sub" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "sup" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "ol" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "type", "start" ), - "br" => array( "CLASS", "ID", "STYLE", "title", "clear" ), - "cite" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "var" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "ruby" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "rt" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "rp" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "dt" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "dl" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "em" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "strong" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "i" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), - "thead" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign' ), - "tfoot" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign' ), - "tbody" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign' ), - "colgroup" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign', 'span', 'width' ), - "col" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign', 'span', 'width' ), - "pre" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "width" ), - - // extension tags that accept parameters: - "sort" => array( "order", "class" ), - "ref" => array( "name" ), - "categorytree" => array( "hideroot", "mode", "style" ), - "chemform" => array( "link", "wikilink", "query" ), - "section" => array( "begin", "new" ), - - // older MW transclusion. - "transclude" => array( "page" ), - ); - - // The types of the HTML that we will be testing were defined above - // Note: this needs to be initialized later to be equal to: array_keys(wikiFuzz::$data); - // as such, it also needs to also be publicly modifiable. - public static $types; - - - // Some attribute values. - static private $other = array( "&", "=", ":", "?", "\"", "\n", "%n%n%n%n%n%n%n%n%n%n%n%n", "\\" ); - static private $ints = array( - // various numbers - "0", "-1", "127", "-7897", "89000", "808080", "90928345", - "0xfffffff", "ffff", - - // Different ways of saying: ' - "'", // Long UTF-8 Unicode encoding - "'", // dec version. - "'", // hex version. - "§", // malformed hex variant, MSB not zero. - - // Different ways of saying: " - """, // Long UTF-8 Unicode encoding - """, - """, // hex version. - "¢", // malformed hex variant, MSB not zero. - - // Different ways of saying: < - "<", - "<", // Long UTF-8 Unicode encoding without semicolon (Mediawiki wants the colon) - "<", // Long UTF-8 Unicode encoding with semicolon - "<", - "<", // hex version. - "¼", // malformed hex variant, MSB not zero. - "<", // mid-length hex version - "<", // slightly longer hex version, with capital "X" - - // Different ways of saying: > - ">", - ">", // Long UTF-8 Unicode encoding - ">", - ">", // hex version. - "¾", // malformed variant, MSB not zero. - - // Different ways of saying: [ - "[", // Long UTF-8 Unicode encoding - "[", - "[", // hex version. - - // Different ways of saying: {{ - "{{", // Long UTF-8 Unicode encoding - "{{", - "{{", // hex version. - - // Different ways of saying: | - "|", // Long UTF-8 Unicode encoding - "|", - "|", // hex version. - "ü", // malformed hex variant, MSB not zero. - - // a "lignature" - http://www.robinlionheart.com/stds/html4/spchars#ligature - // ‌ == ‌ - "‌" - ); - - // Defines various wiki-related bits of syntax, that can potentially cause - // MediaWiki to do something other than just print that literal text. - static private $ext = array( - // links, templates, parameters. - "[[", "]]", "{{", "}}", "|", "[", "]", "{{{", "}}}", "|]]", - - // wiki tables. - "\n{|", "\n|}", - "!", - "\n!", - "!!", - "||", - "\n|-", "| ", "\n|", - - // section headings. - "=", "==", "===", "====", "=====", "======", - - // lists (ordered and unordered) and indentation. - "\n*", "*", "\n:", ":", - "\n#", "#", - - // definition lists (dl, dt, dd), newline, and newline with pre, and a tab. - "\n;", ";", "\n ", - - // Whitespace: newline, tab, space. - "\n", "\t", " ", - - // Some XSS attack vectors from http://ha.ckers.org/xss.html - "	", // tab - "
", // newline - "
", // carriage return - "\0", // null character - "  ", // spaces and meta characters - "'';!--\"<XSS>=&{()}", // compact injection of XSS & SQL tester - - // various NULL fields - "%00", - "�", - "\0", - - // horizontal rule. - "-----", "\n-----", - - // signature, redirect, bold, italics. - "~~~~", "#REDIRECT [[", "'''", "''", - - // comments. - "<!--", "-->", - - // quotes. - "\"", "'", - - // tag start and tag end. - "<", ">", - - // implicit link creation on URIs. - "http://", - "https://", - "ftp://", - "irc://", - "news:", - 'gopher://', - 'telnet://', - 'nntp://', - 'worldwind://', - 'mailto:', - - // images. - "[[image:", - ".gif", - ".png", - ".jpg", - ".jpeg", - 'thumbnail=', - 'thumbnail', - 'thumb=', - 'thumb', - 'right', - 'none', - 'left', - 'framed', - 'frame', - 'enframed', - 'centre', - 'center', - "Image:", - "[[:Image", - 'px', - 'upright=', - 'border', - - // misc stuff to throw at the Parser. - '%08X', - '/', - ":x{|", - "\n|+", - "<noinclude>", - "</noinclude>", - " \302\273", - " :", - " !", - " ;", - "\302\253", - "[[category:", - "?=", - "(", - ")", - "]]]", - "../", - "{{{{", - "}}}}", - "[[Special:", - "<includeonly>", - "</includeonly>", - "<!--MWTEMPLATESECTION=", - '<!--MWTOC-->', - - // implicit link creation on booknum, RFC, and PubMed ID usage (both with and without IDs) - "ISBN 2", - "RFC 000", - "PMID 000", - "ISBN ", - "RFC ", - "PMID ", - - // magic words: - '__NOTOC__', - '__FORCETOC__', - '__NOEDITSECTION__', - '__START__', - '__NOTITLECONVERT__', - '__NOCONTENTCONVERT__', - '__END__', - '__TOC__', - '__NOTC__', - '__NOCC__', - "__FORCETOC__", - "__NEWSECTIONLINK__", - "__NOGALLERY__", - - // more magic words / internal templates. - '{{PAGENAME}}', - '{{PAGENAMEE}}', - '{{NAMESPACE}}', - "{{MSG:", - "}}", - "{{MSGNW:", - "}}", - "{{INT:", - "}}", - '{{SITENAME}}', - "{{NS:", - "}}", - "{{LOCALURL:", - "}}", - "{{LOCALURLE:", - "}}", - "{{SCRIPTPATH}}", - "{{GRAMMAR:gentiv|", - "}}", - "{{REVISIONID}}", - "{{SUBPAGENAME}}", - "{{SUBPAGENAMEE}}", - "{{ns:0}}", - "{{fullurle:", - "}}", - "{{subst::", - "}}", - "{{UCFIRST:", - "}}", - "{{UC:", - '{{SERVERNAME}}', - '{{SERVER}}', - "{{RAW:", - "}}", - "{{PLURAL:", - "}}", - "{{LCFIRST:", - "}}", - "{{LC:", - "}}", - '{{CURRENTWEEK}}', - '{{CURRENTDOW}}', - "{{INT:{{LC:contribs-showhideminor}}|", - "}}", - "{{INT:googlesearch|", - "}}", - "{{ROOTPAGENAME}}", - "{{BASEPAGENAME}}", - "{{CONTENTLANGUAGE}}", - "{{PAGESINNAMESPACE:}}", - "{{#language:", - "}}", - "{{#special:", - "}}", - "{{#special:emailuser", - "}}", - - // Some raw link for magic words. - "{{NUMBEROFPAGES:R", - "}}", - "{{NUMBEROFUSERS:R", - "}}", - "{{NUMBEROFARTICLES:R", - "}}", - "{{NUMBEROFFILES:R", - "}}", - "{{NUMBEROFADMINS:R", - "}}", - "{{padleft:", - "}}", - "{{padright:", - "}}", - "{{DEFAULTSORT:", - "}}", - - // internal Math "extension": - "<math>", - "</math>", - - // Parser extension functions: - "{{#expr:", - "{{#if:", - "{{#ifeq:", - "{{#ifexist:", - "{{#ifexpr:", - "{{#switch:", - "{{#time:", - "}}", - - // references table for the Cite extension. - "<references/>", - - // Internal Parser tokens - try inserting some of these. - "UNIQ25f46b0524f13e67NOPARSE", - "UNIQ17197916557e7cd6-HTMLCommentStrip46238afc3bb0cf5f00000002", - "\x07UNIQ17197916557e7cd6-HTMLCommentStrip46238afc3bb0cf5f00000002-QINU", - - // Inputbox extension: - "<inputbox>\ntype=search\nsearchbuttonlabel=\n", - "</inputbox>", - - // charInsert extension: - "<charInsert>", - "</charInsert>", - - // wikiHiero extension: - "<hiero>", - "</hiero>", - - // Image gallery: - "<gallery>", - "</gallery>", - - // FixedImage extension. - "<fundraising/>", - - // Timeline extension: currently untested. - - // Nowiki: - "<nOwIkI>", - "</nowiki>", - - // an external image to test the external image displaying code - "http://debian.org/Pics/debian.png", - - // LabeledSectionTransclusion extension. - "{{#lstx:", - "}}", - "{{#lst:", - "}}", - "{{#lst:Main Page|", - "}}" - ); - - /** - ** Randomly returns one element of the input array. - */ - public static function chooseInput( array $input ) { - $randindex = wikiFuzz::randnum( count( $input ) - 1 ); - return $input[$randindex]; - } - - // Max number of parameters for HTML attributes. - static private $maxparams = 10; - - /** - * Returns random number between finish and start. - * @param $finish - * @param $start int - * @return int - */ - public static function randnum( $finish, $start = 0 ) { - return mt_rand( $start, $finish ); - } - - /** - * Returns a mix of random text and random wiki syntax. - * @return string - */ - private static function randstring() { - $thestring = ""; - - for ( $i = 0; $i < 40; $i++ ) { - $what = wikiFuzz::randnum( 1 ); - - if ( $what == 0 ) { // include some random wiki syntax - $which = wikiFuzz::randnum( count( wikiFuzz::$ext ) - 1 ); - $thestring .= wikiFuzz::$ext[$which]; - } - else { // include some random text - $char = INCLUDE_BINARY - // Decimal version: - // "&#" . wikiFuzz::randnum(255) . ";" - // Hex version: - ? "&#x" . str_pad( dechex( wikiFuzz::randnum( 255 ) ), wikiFuzz::randnum( 2, 7 ), "0", STR_PAD_LEFT ) . ";" - // A truly binary version: - // ? chr(wikiFuzz::randnum(0,255)) - : chr( wikiFuzz::randnum( 126, 32 ) ); - - $length = wikiFuzz::randnum( 8 ); - $thestring .= str_repeat ( $char, $length ); - } - } - return $thestring; - } - - /** - * Returns either random text, or random wiki syntax, or random data from "ints", - * or random data from "other". - * @return string - */ - private static function makestring() { - $what = wikiFuzz::randnum( 2 ); - if ( $what == 0 ) { - return wikiFuzz::randstring(); - } elseif ( $what == 1 ) { - return wikiFuzz::$ints[wikiFuzz::randnum( count( wikiFuzz::$ints ) - 1 )]; - } else { - return wikiFuzz::$other[wikiFuzz::randnum( count( wikiFuzz::$other ) - 1 )]; - } - } - - /** - * Returns the matched character slash-escaped as in a C string - * Helper for makeTitleSafe callback - * @param $matches - * @return string - */ - private static function stringEscape( $matches ) { - return sprintf( "\\x%02x", ord( $matches[1] ) ); - } - - /** - ** Strips out the stuff that Mediawiki balks at in a page's title. - ** Implementation copied/pasted from cleanupTable.inc & cleanupImages.php - * @param $str string - * @return string - */ - public static function makeTitleSafe( $str ) { - $legalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF"; - return preg_replace_callback( - "/([^$legalTitleChars])/", 'wikiFuzz::stringEscape', - $str ); - } - - /** - ** Returns a string of fuzz text. - * @return string - */ - private static function loop() { - switch ( wikiFuzz::randnum( 3 ) ) { - case 1: // an opening tag, with parameters. - $string = ""; - $i = wikiFuzz::randnum( count( wikiFuzz::$types ) - 1 ); - $t = wikiFuzz::$types[$i]; - $arr = wikiFuzz::$data[$t]; - $string .= "<" . $t . " "; - $num_params = min( wikiFuzz::$maxparams, count( $arr ) ); - for ( $z = 0; $z < $num_params; $z++ ) { - $badparam = $arr[wikiFuzz::randnum( count( $arr ) - 1 )]; - $badstring = wikiFuzz::makestring(); - $string .= $badparam . "=" . wikiFuzz::getRandQuote() . $badstring . wikiFuzz::getRandQuote() . " "; - } - $string .= ">\n"; - return $string; - case 2: // a closing tag. - $i = wikiFuzz::randnum( count( wikiFuzz::$types ) - 1 ); - return "</" . wikiFuzz::$types[$i] . ">"; - case 3: // a random string, between tags. - return wikiFuzz::makeString(); - } - return ""; // catch-all, should never be called. - } - - /** - * Returns one of the three styles of random quote: ', ", and nothing. - * @return string - */ - private static function getRandQuote() { - switch ( wikiFuzz::randnum( 3 ) ) { - case 1 : return "'"; - case 2 : return "\""; - default: return ""; - } - } - - /** - ** Returns fuzz text, with the parameter indicating approximately how many lines of text you want. - * @param $maxtypes int - * @return string - */ - public static function makeFuzz( $maxtypes = 2 ) { - $page = ""; - for ( $k = 0; $k < $maxtypes; $k++ ) { - $page .= wikiFuzz::loop(); - } - return $page; - } -} - - -// ////// MEDIAWIKI PAGES TO TEST, AND HOW TO TEST THEM /////// - -/** - ** A page test has just these things: - ** 1) Form parameters. - ** 2) the URL we are going to test those parameters on. - ** 3) Any cookies required for the test. - ** 4) Whether Tidy should validate the page. Defaults to true, but can be turned off. - ** Declared abstract because it should be extended by a class - ** that supplies these parameters. - */ -abstract class pageTest { - protected $params; - protected $pagePath; - protected $cookie = ""; - protected $tidyValidate = true; - - public function getParams() { - return $this->params; - } - - public function getPagePath() { - return $this->pagePath; - } - - public function getCookie() { - return $this->cookie; - } - - public function tidyValidate() { - return $this->tidyValidate; - } -} - - -/** - ** a page test for the "Edit" page. Tests Parser.php and Sanitizer.php. - */ -class editPageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=WIKIFUZZ"; - - $this->params = array ( - "action" => "submit", - "wpMinoredit" => wikiFuzz::makeFuzz( 2 ), - "wpPreview" => wikiFuzz::makeFuzz( 2 ), - "wpSection" => wikiFuzz::makeFuzz( 2 ), - "wpEdittime" => wikiFuzz::makeFuzz( 2 ), - "wpSummary" => wikiFuzz::makeFuzz( 2 ), - "wpScrolltop" => wikiFuzz::makeFuzz( 2 ), - "wpStarttime" => wikiFuzz::makeFuzz( 2 ), - "wpAutoSummary" => wikiFuzz::makeFuzz( 2 ), - "wpTextbox1" => wikiFuzz::makeFuzz( 40 ) // the main wiki text, need lots of this. - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpSection"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpEdittime"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpSummary"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpScrolltop"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpStarttime"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpAutoSummary"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpTextbox1"] ); - } -} - - -/** - ** a page test for "Special:Listusers". - */ -class listusersTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Listusers"; - - $this->params = array ( - "title" => wikiFuzz::makeFuzz( 2 ), - "group" => wikiFuzz::makeFuzz( 2 ), - "username" => wikiFuzz::makeFuzz( 2 ), - "Go" => wikiFuzz::makeFuzz( 2 ), - "limit" => wikiFuzz::chooseInput( array( "0", "-1", "---'----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - "offset" => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1", "81343242346234234", wikiFuzz::makeFuzz( 2 ) ) ) - ); - } -} - - -/** - ** a page test for "Special:Search". - */ -class searchTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Search"; - - $this->params = array ( - "action" => "index.php?title=Special:Search", - "ns0" => wikiFuzz::makeFuzz( 2 ), - "ns1" => wikiFuzz::makeFuzz( 2 ), - "ns2" => wikiFuzz::makeFuzz( 2 ), - "ns3" => wikiFuzz::makeFuzz( 2 ), - "ns4" => wikiFuzz::makeFuzz( 2 ), - "ns5" => wikiFuzz::makeFuzz( 2 ), - "ns6" => wikiFuzz::makeFuzz( 2 ), - "ns7" => wikiFuzz::makeFuzz( 2 ), - "ns8" => wikiFuzz::makeFuzz( 2 ), - "ns9" => wikiFuzz::makeFuzz( 2 ), - "ns10" => wikiFuzz::makeFuzz( 2 ), - "ns11" => wikiFuzz::makeFuzz( 2 ), - "ns12" => wikiFuzz::makeFuzz( 2 ), - "ns13" => wikiFuzz::makeFuzz( 2 ), - "ns14" => wikiFuzz::makeFuzz( 2 ), - "ns15" => wikiFuzz::makeFuzz( 2 ), - "redirs" => wikiFuzz::makeFuzz( 2 ), - "search" => wikiFuzz::makeFuzz( 2 ), - "offset" => wikiFuzz::chooseInput( array( "", "0", "-1", "--------'-----0", "+1", "81343242346234234", wikiFuzz::makeFuzz( 2 ) ) ), - "fulltext" => wikiFuzz::chooseInput( array( "", "0", "1", "--------'-----0", "+1", wikiFuzz::makeFuzz( 2 ) ) ), - "searchx" => wikiFuzz::chooseInput( array( "", "0", "1", "--------'-----0", "+1", wikiFuzz::makeFuzz( 2 ) ) ) - ); - } -} - - -/** - ** a page test for "Special:Recentchanges". - */ -class recentchangesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Recentchanges"; - - $this->params = array ( - "action" => wikiFuzz::makeFuzz( 2 ), - "title" => wikiFuzz::makeFuzz( 2 ), - "namespace" => wikiFuzz::chooseInput( range( -1, 15 ) ), - "Go" => wikiFuzz::makeFuzz( 2 ), - "invert" => wikiFuzz::chooseInput( array( "-1", "---'----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - "hideanons" => wikiFuzz::chooseInput( array( "-1", "------'-------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - 'limit' => wikiFuzz::chooseInput( array( "0", "-1", "---------'----0", "+1", "81340909772349234", wikiFuzz::makeFuzz( 2 ) ) ), - "days" => wikiFuzz::chooseInput( array( "-1", "----------'---0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - "hideminor" => wikiFuzz::chooseInput( array( "-1", "-----------'--0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - "hidebots" => wikiFuzz::chooseInput( array( "-1", "---------'----0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - "hideliu" => wikiFuzz::chooseInput( array( "-1", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - "hidepatrolled" => wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - "hidemyself" => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - 'categories_any' => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - 'categories' => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - 'feed' => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ) - ); - } -} - - -/** - ** a page test for "Special:Prefixindex". - */ -class prefixindexTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Prefixindex"; - - $this->params = array ( - "title" => "Special:Prefixindex", - "namespace" => wikiFuzz::randnum( 101, -10 ), - "Go" => wikiFuzz::makeFuzz( 2 ) - ); - - // sometimes we want 'prefix', sometimes we want 'from', and sometimes we want nothing. - if ( wikiFuzz::randnum( 3 ) == 0 ) { - $this->params["prefix"] = wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+++--+1", - wikiFuzz::randnum( 8134, -10 ), wikiFuzz::makeFuzz( 2 ) ) ); - } - if ( wikiFuzz::randnum( 3 ) == 0 ) { - $this->params["from"] = wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+++--+1", - wikiFuzz::randnum( 8134, -10 ), wikiFuzz::makeFuzz( 2 ) ) ); - } - } -} - - -/** - ** a page test for "Special:MIMEsearch". - */ -class mimeSearchTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:MIMEsearch"; - - $this->params = array ( - "action" => "index.php?title=Special:MIMEsearch", - "mime" => wikiFuzz::makeFuzz( 3 ), - 'limit' => wikiFuzz::chooseInput( array( "0", "-1", "-------'------0", "+1", "81342321351235325", wikiFuzz::makeFuzz( 2 ) ) ), - 'offset' => wikiFuzz::chooseInput( array( "0", "-1", "-----'--------0", "+1", "81341231235365252234324", wikiFuzz::makeFuzz( 2 ) ) ) - ); - } -} - - -/** - ** a page test for "Special:Log". - */ -class specialLogTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Log"; - - $this->params = array ( - "type" => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ), - "par" => wikiFuzz::makeFuzz( 2 ), - "user" => wikiFuzz::makeFuzz( 2 ), - "page" => wikiFuzz::makeFuzz( 2 ), - "from" => wikiFuzz::makeFuzz( 2 ), - "until" => wikiFuzz::makeFuzz( 2 ), - "title" => wikiFuzz::makeFuzz( 2 ) - ); - } -} - - -/** - ** a page test for "Special:Userlogin", with a successful login. - */ -class successfulUserLoginTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Userlogin&action=submitlogin&type=login&returnto=" . wikiFuzz::makeFuzz( 2 ); - - $this->params = array ( - "wpName" => USER_ON_WIKI, - // sometimes real password, sometimes not: - 'wpPassword' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), USER_PASSWORD ) ), - 'wpRemember' => wikiFuzz::makeFuzz( 2 ) - ); - - $this->cookie = "wikidb_session=" . wikiFuzz::chooseInput( array( "1" , wikiFuzz::makeFuzz( 2 ) ) ); - } -} - - -/** - ** a page test for "Special:Userlogin". - */ -class userLoginTest extends pageTest { - function __construct() { - - $this->pagePath = "index.php?title=Special:Userlogin"; - - $this->params = array ( - 'wpRetype' => wikiFuzz::makeFuzz( 2 ), - 'wpRemember' => wikiFuzz::makeFuzz( 2 ), - 'wpRealName' => wikiFuzz::makeFuzz( 2 ), - 'wpPassword' => wikiFuzz::makeFuzz( 2 ), - 'wpName' => wikiFuzz::makeFuzz( 2 ), - 'wpMailmypassword' => wikiFuzz::makeFuzz( 2 ), - 'wpLoginattempt' => wikiFuzz::makeFuzz( 2 ), - 'wpEmail' => wikiFuzz::makeFuzz( 2 ), - 'wpDomain' => wikiFuzz::chooseInput( array( "", "local", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpCreateaccountMail' => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpCreateaccount' => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpCookieCheck' => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ), - 'type' => wikiFuzz::chooseInput( array( "signup", "login", "", wikiFuzz::makeFuzz( 2 ) ) ), - 'returnto' => wikiFuzz::makeFuzz( 2 ), - 'action' => wikiFuzz::chooseInput( array( "", "submitlogin", wikiFuzz::makeFuzz( 2 ) ) ) - ); - - $this->cookie = "wikidb_session=" . wikiFuzz::chooseInput( array( "1" , wikiFuzz::makeFuzz( 2 ) ) ); - } -} - - -/** - ** a page test for "Special:Ipblocklist" (also includes unblocking) - */ -class ipblocklistTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Ipblocklist"; - - $this->params = array ( - 'wpUnblockAddress' => wikiFuzz::makeFuzz( 2 ), - 'ip' => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ), - // something like an IP address, sometimes invalid: - ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "." - . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ), - 'id' => wikiFuzz::makeFuzz( 2 ), - 'wpUnblockReason' => wikiFuzz::makeFuzz( 2 ), - 'action' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "success", "submit", "unblock" ) ), - 'wpEditToken' => wikiFuzz::makeFuzz( 2 ), - 'wpBlock' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "" ) ), - 'limit' => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1", - "09700982312351132098234", wikiFuzz::makeFuzz( 2 ) ) ), - 'offset' => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", - "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) ) - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] ); - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["ip"] ); - if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["id"] ); - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpUnblockAddress"] ); - } -} - - -/** - ** a page test for "Special:Newimages". - */ -class newImagesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Newimages"; - - $this->params = array ( - 'hidebots' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "1", "", "-1" ) ), - 'wpIlMatch' => wikiFuzz::makeFuzz( 2 ), - 'until' => wikiFuzz::makeFuzz( 2 ), - 'from' => wikiFuzz::makeFuzz( 2 ) - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["until"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["from"] ); - } -} - - -/** - ** a page test for the "Special:Imagelist" page. - */ -class imagelistTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Imagelist"; - - $this->params = array ( - 'sort' => wikiFuzz::chooseInput( array( "bysize", "byname" , "bydate", wikiFuzz::makeFuzz( 2 ) ) ), - 'limit' => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1", "09700982312351132098234", wikiFuzz::makeFuzz( 2 ) ) ), - 'offset' => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpIlMatch' => wikiFuzz::makeFuzz( 2 ) - ); - } -} - - -/** - ** a page test for "Special:Export". - */ -class specialExportTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Export"; - - $this->params = array ( - 'action' => wikiFuzz::chooseInput( array( "submit", "", wikiFuzz::makeFuzz( 2 ) ) ), - 'pages' => wikiFuzz::makeFuzz( 2 ), - 'curonly' => wikiFuzz::chooseInput( array( "", "0", "-1", wikiFuzz::makeFuzz( 2 ) ) ), - 'listauthors' => wikiFuzz::chooseInput( array( "", "0", "-1", wikiFuzz::makeFuzz( 2 ) ) ), - 'history' => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) ), - - ); - - // For the time being, need to disable "submit" action as Tidy barfs on MediaWiki's XML export. - if ( $this->params['action'] == 'submit' ) $this->params['action'] = ''; - - // Sometimes remove the history field. - if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["history"] ); - - // page does not produce HTML. - $this->tidyValidate = false; - } -} - - -/** - ** a page test for "Special:Booksources". - */ -class specialBooksourcesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Booksources"; - - $this->params = array ( - 'go' => wikiFuzz::makeFuzz( 2 ), - // ISBN codes have to contain some semi-numeric stuff or will be ignored: - 'isbn' => "0X0" . wikiFuzz::makeFuzz( 2 ) - ); - } -} - - -/** - ** a page test for "Special:Allpages". - */ -class specialAllpagesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special%3AAllpages"; - - $this->params = array ( - 'from' => wikiFuzz::makeFuzz( 2 ), - 'namespace' => wikiFuzz::chooseInput( range( -1, 15 ) ), - 'go' => wikiFuzz::makeFuzz( 2 ) - ); - } -} - - -/** - ** a page test for the page History. - */ -class pageHistoryTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Main_Page&action=history"; - - $this->params = array ( - 'limit' => wikiFuzz::chooseInput( array( "-1", "0", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - 'offset' => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) ), - "go" => wikiFuzz::chooseInput( array( "first", "last", wikiFuzz::makeFuzz( 2 ) ) ), - "dir" => wikiFuzz::chooseInput( array( "prev", "next", wikiFuzz::makeFuzz( 2 ) ) ), - "diff" => wikiFuzz::chooseInput( array( "-1", "--------'-----0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - "oldid" => wikiFuzz::chooseInput( array( "prev", "-1", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - "feed" => wikiFuzz::makeFuzz( 2 ) - ); - } -} - - -/** - ** a page test for the Special:Contributions". - */ -class contributionsTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Contributions/" . USER_ON_WIKI; - - $this->params = array ( - 'target' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "newbies", USER_ON_WIKI ) ), - 'namespace' => wikiFuzz::chooseInput( array( -1, 15, 1, wikiFuzz::makeFuzz( 2 ) ) ), - 'offset' => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "982342131232131231241", wikiFuzz::makeFuzz( 2 ) ) ), - 'bot' => wikiFuzz::chooseInput( array( "", "-1", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), - 'go' => wikiFuzz::chooseInput( array( "-1", 'prev', 'next', wikiFuzz::makeFuzz( 2 ) ) ) - ); - } -} - - -/** - ** a page test for viewing a normal page, whilst posting various params. - */ -class viewPageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Main_Page"; - - $this->params = array ( - "useskin" => wikiFuzz::chooseInput( array( "chick", "cologneblue", "myskin", - "nostalgia", "simple", "standard", wikiFuzz::makeFuzz( 2 ) ) ), - "uselang" => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), - "ab", "af", "an", "ar", "arc", "as", "ast", "av", "ay", "az", "ba", - "bat-smg", "be", "bg", "bm", "bn", "bo", "bpy", "br", "bs", "ca", - "ce", "cs", "csb", "cv", "cy", "da", "de", "dv", "dz", "el", "en", - "eo", "es", "et", "eu", "fa", "fi", "fo", "fr", "fur", "fy", "ga", - "gn", "gsw", "gu", "he", "hi", "hr", "hu", "ia", "id", "ii", "is", - "it", "ja", "jv", "ka", "km", "kn", "ko", "ks", "ku", "kv", "la", - "li", "lo", "lt", "lv", "mk", "ml", "ms", "nah", "nap", "nds", - "nds-nl", "nl", "nn", "no", "non", "nv", "oc", "or", "os", "pa", - "pl", "pms", "ps", "pt", "pt-br", "qu", "rmy", "ro", "ru", "sc", - "sd", "sk", "sl", "sq", "sr", "sr-ec", "sr-el", - "su", "sv", "ta", "te", "th", "tr", "tt", "ty", "tyv", "udm", - "ug", "uk", "ur", "utf8", "vec", "vi", "wa", "xal", "yi", "za", - "zh", "zh-cn", "zh-hk", "zh-sg", "zh-tw", "zh-tw" ) ), - "returnto" => wikiFuzz::makeFuzz( 2 ), - "feed" => wikiFuzz::chooseInput( array( "atom", "rss", wikiFuzz::makeFuzz( 2 ) ) ), - "rcid" => wikiFuzz::makeFuzz( 2 ), - "action" => wikiFuzz::chooseInput( array( "view", "raw", "render", wikiFuzz::makeFuzz( 2 ), "markpatrolled" ) ), - "printable" => wikiFuzz::makeFuzz( 2 ), - "oldid" => wikiFuzz::makeFuzz( 2 ), - "redirect" => wikiFuzz::makeFuzz( 2 ), - "diff" => wikiFuzz::makeFuzz( 2 ), - "search" => wikiFuzz::makeFuzz( 2 ), - "rdfrom" => wikiFuzz::makeFuzz( 2 ), // things from Article.php from here on: - "token" => wikiFuzz::makeFuzz( 2 ), - "tbid" => wikiFuzz::makeFuzz( 2 ), - // @todo FIXME: Duplicate array key. - "action" => wikiFuzz::chooseInput( array( "purge", wikiFuzz::makeFuzz( 2 ) ) ), - "wpReason" => wikiFuzz::makeFuzz( 2 ), - "wpEditToken" => wikiFuzz::makeFuzz( 2 ), - "from" => wikiFuzz::makeFuzz( 2 ), - "bot" => wikiFuzz::makeFuzz( 2 ), - "summary" => wikiFuzz::makeFuzz( 2 ), - "direction" => wikiFuzz::chooseInput( array( "next", "prev", wikiFuzz::makeFuzz( 2 ) ) ), - "section" => wikiFuzz::makeFuzz( 2 ), - "preload" => wikiFuzz::makeFuzz( 2 ), - - ); - - // Tidy does not know how to valid atom or rss, so exclude from testing for the time being. - if ( $this->params["feed"] == "atom" ) { unset( $this->params["feed"] ); } - elseif ( $this->params["feed"] == "rss" ) { unset( $this->params["feed"] ); } - - // Raw pages cannot really be validated - if ( $this->params["action"] == "raw" ) unset( $this->params["action"] ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["rcid"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["diff"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["rdfrom"] ); - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["oldid"] ); - - // usually don't want action == purge. - if ( wikiFuzz::randnum( 6 ) > 1 ) unset( $this->params["action"] ); - } -} - - -/** - ** a page test for "Special:Allmessages". - */ -class specialAllmessagesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Allmessages"; - - // only really has one parameter - $this->params = array ( - "ot" => wikiFuzz::chooseInput( array( "php", "html", wikiFuzz::makeFuzz( 2 ) ) ) - ); - } -} - -/** - ** a page test for "Special:Newpages". - */ -class specialNewpagesPageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Newpages"; - - $this->params = array ( - "namespace" => wikiFuzz::chooseInput( range( -1, 15 ) ), - "feed" => wikiFuzz::chooseInput( array( "atom", "rss", wikiFuzz::makeFuzz( 2 ) ) ), - 'limit' => wikiFuzz::chooseInput( array( "-1", "0", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), - 'offset' => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) ) - ); - - // Tidy does not know how to valid atom or rss, so exclude from testing for the time being. - if ( $this->params["feed"] == "atom" ) { unset( $this->params["feed"] ); } - elseif ( $this->params["feed"] == "rss" ) { unset( $this->params["feed"] ); } - } -} - -/** - ** a page test for "redirect.php" - */ -class redirectTest extends pageTest { - function __construct() { - $this->pagePath = "redirect.php"; - - $this->params = array ( - "wpDropdown" => wikiFuzz::makeFuzz( 2 ) - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpDropdown"] ); - } -} - - -/** - ** a page test for "Special:Confirmemail" - */ -class confirmEmail extends pageTest { - function __construct() { - // sometimes we send a bogus confirmation code, and sometimes we don't. - $this->pagePath = "index.php?title=Special:Confirmemail" . wikiFuzz::chooseInput( array( "", "/" . wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 1 ) ) ) ); - - $this->params = array ( - "token" => wikiFuzz::makeFuzz( 2 ) - ); - } -} - - -/** - ** a page test for "Special:Watchlist" - ** Note: this test would be better if we were logged in. - */ -class watchlistTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Watchlist"; - - $this->params = array ( - "remove" => wikiFuzz::chooseInput( array( "Remove checked items from watchlist", wikiFuzz::makeFuzz( 2 ) ) ), - 'days' => wikiFuzz::chooseInput( array( 0, -1, -230, "--", 3, 9, wikiFuzz::makeFuzz( 2 ) ) ), - 'hideOwn' => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), - 'hideBots' => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), - 'namespace' => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), - 'action' => wikiFuzz::chooseInput( array( "submit", "clear", wikiFuzz::makeFuzz( 2 ) ) ), - 'id[]' => wikiFuzz::makeFuzz( 2 ), - 'edit' => wikiFuzz::makeFuzz( 2 ), - 'token' => wikiFuzz::chooseInput( array( "", "1243213", wikiFuzz::makeFuzz( 2 ) ) ) - ); - - // sometimes we specifiy "reset", and sometimes we don't. - if ( wikiFuzz::randnum( 3 ) == 0 ) $this->params["reset"] = wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ); - } -} - -/** - ** a page test for "Special:Movepage" - */ -class specialMovePage extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Movepage"; - - $this->params = array ( - "action" => wikiFuzz::chooseInput( array( "success", "submit", "", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpEditToken' => wikiFuzz::chooseInput( array( '', 0, 34987987, wikiFuzz::makeFuzz( 2 ) ) ), - 'target' => wikiFuzz::chooseInput( array( "x", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ) ) ), - 'wpOldTitle' => wikiFuzz::chooseInput( array( "z", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ), wikiFuzz::makeFuzz( 2 ) ) ), - 'wpNewTitle' => wikiFuzz::chooseInput( array( "y", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ), wikiFuzz::makeFuzz( 2 ) ) ), - 'wpReason' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ) ) ), - 'wpMovetalk' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpDeleteAndMove' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpConfirm' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - 'talkmoved' => wikiFuzz::chooseInput( array( "1", wikiFuzz::makeFuzz( 2 ), "articleexists", 'notalkpage' ) ), - 'oldtitle' => wikiFuzz::makeFuzz( 2 ), - 'newtitle' => wikiFuzz::makeFuzz( 2 ), - 'wpMovetalk' => wikiFuzz::chooseInput( array( "1", "0", wikiFuzz::makeFuzz( 2 ) ) ) - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["wpEditToken"] ); - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["target"] ); - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpNewTitle"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpReason"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpOldTitle"] ); - } -} - - -/** - ** a page test for "Special:Undelete" - */ -class specialUndeletePageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Undelete"; - - $this->params = array ( - "action" => wikiFuzz::chooseInput( array( "submit", "", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpEditToken' => wikiFuzz::chooseInput( array( '', 0, 34987987, wikiFuzz::makeFuzz( 2 ) ) ), - 'target' => wikiFuzz::chooseInput( array( "x", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ) ) ), - 'timestamp' => wikiFuzz::chooseInput( array( "125223", wikiFuzz::makeFuzz( 2 ) ) ), - 'file' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - 'restore' => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), - 'preview' => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpComment' => wikiFuzz::makeFuzz( 2 ) - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["wpEditToken"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["target"] ); - if ( wikiFuzz::randnum( 1 ) == 0 ) unset( $this->params["restore"] ); - if ( wikiFuzz::randnum( 1 ) == 0 ) unset( $this->params["preview"] ); - } -} - - -/** - ** a page test for "Special:Unlockdb" - */ -class specialUnlockdbPageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Unlockdb"; - - $this->params = array ( - "action" => wikiFuzz::chooseInput( array( "submit", "success", "", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpEditToken' => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpLockConfirm' => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) ) - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpEditToken"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpLockConfirm"] ); - } -} - - -/** - ** a page test for "Special:Lockdb" - */ -class specialLockdbPageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Lockdb"; - - $this->params = array ( - "action" => wikiFuzz::chooseInput( array( "submit", "success", "", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpEditToken' => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpLockReason' => wikiFuzz::makeFuzz( 2 ), - 'wpLockConfirm' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ) - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpEditToken"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpLockConfirm"] ); - } -} - - -/** - ** a page test for "Special:Userrights" - */ -class specialUserrights extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Userrights"; - - $this->params = array ( - 'wpEditToken' => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), - 'user-editname' => wikiFuzz::chooseInput( array( "Nickj2", "Nickj2\n<xyz>", wikiFuzz::makeFuzz( 2 ) ) ), - 'ssearchuser' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - 'saveusergroups' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ), "Save User Groups" ), - 'member[]' => wikiFuzz::chooseInput( array( "0", "bot", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - "available[]" => wikiFuzz::chooseInput( array( "0", "sysop", "bureaucrat", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ) - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['ssearchuser'] ); - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['saveusergroups'] ); - } -} - - -/** - ** a test for page protection and unprotection. - */ -class pageProtectionForm extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Main_Page"; - - $this->params = array ( - "action" => "protect", - 'wpEditToken' => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), - "mwProtect-level-edit" => wikiFuzz::chooseInput( array( '', 'autoconfirmed', 'sysop', wikiFuzz::makeFuzz( 2 ) ) ), - "mwProtect-level-move" => wikiFuzz::chooseInput( array( '', 'autoconfirmed', 'sysop', wikiFuzz::makeFuzz( 2 ) ) ), - "mwProtectUnchained" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - 'mwProtect-reason' => wikiFuzz::chooseInput( array( "because it was there", wikiFuzz::makeFuzz( 2 ) ) ) - ); - - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["mwProtectUnchained"] ); - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['mwProtect-reason'] ); - } -} - - -/** - ** a page test for "Special:Blockip". - */ -class specialBlockip extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Blockip"; - - $this->params = array ( - "action" => wikiFuzz::chooseInput( array( "submit", "", wikiFuzz::makeFuzz( 2 ) ) ), - 'wpEditToken' => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), - "wpBlockAddress" => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ), - // something like an IP address, sometimes invalid: - ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "." - . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ), - "ip" => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ), - // something like an IP address, sometimes invalid: - ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "." - . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ), - "wpBlockOther" => wikiFuzz::chooseInput( array( '', 'Nickj2', wikiFuzz::makeFuzz( 2 ) ) ), - "wpBlockExpiry" => wikiFuzz::chooseInput( array( "other", "2 hours", "1 day", "3 days", "1 week", "2 weeks", - "1 month", "3 months", "6 months", "1 year", "infinite", wikiFuzz::makeFuzz( 2 ) ) ), - "wpBlockReason" => wikiFuzz::chooseInput( array( "because it was there", wikiFuzz::makeFuzz( 2 ) ) ), - "wpAnonOnly" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - "wpCreateAccount" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - "wpBlock" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ) - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockOther"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockExpiry"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockReason"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpAnonOnly"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpCreateAccount"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockAddress"] ); - if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["ip"] ); - } -} - - -/** - ** a test for the imagepage. - */ -class imagepageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Image:Small-email.png"; - - $this->params = array ( - "image" => wikiFuzz::chooseInput( array( "Small-email.png", wikiFuzz::makeFuzz( 2 ) ) ), - "wpReason" => wikiFuzz::makeFuzz( 2 ), - "oldimage" => wikiFuzz::chooseInput( array( "Small-email.png", wikiFuzz::makeFuzz( 2 ) ) ), - "wpEditToken" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["image"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpReason"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldimage"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpEditToken"] ); - } -} - - -/** - ** a test for page deletion form. - */ -class pageDeletion extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Main_Page&action=delete"; - - $this->params = array ( - "wpEditToken" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), - "wpReason" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - "wpConfirm" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpReason"] ); - if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpEditToken"] ); - if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpConfirm"] ); - } -} - - - -/** - ** a test for Revision Deletion. - */ -class specialRevisionDeletePageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Revisiondelete"; - - $this->params = array ( - "target" => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ), - "oldid" => wikiFuzz::makeFuzz( 2 ), - "oldid[]" => wikiFuzz::makeFuzz( 2 ), - "wpReason" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - "revdelete-hide-text" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - "revdelete-hide-comment" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - "revdelete-hide-user" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - "revdelete-hide-restricted" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["target"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldid"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldid[]"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpReason"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-text"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-comment"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-user"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-restricted"] ); - } -} - - -/** - ** a test for Special:Import. - */ -class specialImportPageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Import"; - - $this->params = array ( - "action" => "submit", - "source" => wikiFuzz::chooseInput( array( "upload", "interwiki", wikiFuzz::makeFuzz( 2 ) ) ), - "MAX_FILE_SIZE" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - "xmlimport" => wikiFuzz::chooseInput( array( "/var/www/hosts/mediawiki/wiki/AdminSettings.php", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), - "namespace" => wikiFuzz::chooseInput( array( wikiFuzz::randnum( 30, -6 ), wikiFuzz::makeFuzz( 2 ) ) ), - "interwiki" => wikiFuzz::makeFuzz( 2 ), - "interwikiHistory" => wikiFuzz::makeFuzz( 2 ), - "frompage" => wikiFuzz::makeFuzz( 2 ), - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["action"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["source"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["MAX_FILE_SIZE"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["xmlimport"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["interwiki"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["interwikiHistory"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["frompage"] ); - - // Note: Need to do a file upload to fully test this Special page. - } -} - - -/** - ** a test for thumb.php - */ -class thumbTest extends pageTest { - function __construct() { - $this->pagePath = "thumb.php"; - - $this->params = array ( - "f" => wikiFuzz::chooseInput( array( "..", "\\", "small-email.png", wikiFuzz::makeFuzz( 2 ) ) ), - "w" => wikiFuzz::chooseInput( array( "80", wikiFuzz::randnum( 6000, -200 ), wikiFuzz::makeFuzz( 2 ) ) ), - "r" => wikiFuzz::chooseInput( array( "0", wikiFuzz::makeFuzz( 2 ) ) ), - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["f"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["w"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["r"] ); - } -} - -/** - ** a test for profileinfo.php - */ -class profileInfo extends pageTest { - function __construct() { - $this->pagePath = "profileinfo.php"; - - $this->params = array ( - "expand" => wikiFuzz::makeFuzz( 2 ), - "sort" => wikiFuzz::chooseInput( array( "time", "count", "name", wikiFuzz::makeFuzz( 2 ) ) ), - "filter" => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ), - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["sort"] ); - if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["filter"] ); - } -} - - -/** - ** a test for Special:Cite (extension Special page). - */ -class specialCitePageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Cite"; - - $this->params = array ( - "page" => wikiFuzz::chooseInput( array( "\" onmouseover=\"alert(1);\"", "Main Page", wikiFuzz::makeFuzz( 2 ) ) ), - "id" => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "-9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) ), - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["page"] ); - if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["id"] ); - } -} - - -/** - ** a test for Special:Filepath (extension Special page). - */ -class specialFilepathPageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Filepath"; - - $this->params = array ( - "file" => wikiFuzz::chooseInput( array( "Small-email.png", "Small-email.png" . wikiFuzz::makeFuzz( 1 ), wikiFuzz::makeFuzz( 2 ) ) ), - ); - } -} - - -/** - ** a test for Special:Renameuser (extension Special page). - */ -class specialRenameuserPageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Renameuser"; - - $this->params = array ( - "oldusername" => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ), - "newusername" => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ), - "token" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), - ); - } -} - - -/** - ** a test for Special:Linksearch (extension Special page). - */ -class specialLinksearch extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special%3ALinksearch"; - - $this->params = array ( - "target" => wikiFuzz::makeFuzz( 2 ), - ); - - // sometimes we don't want to specify certain parameters. - if ( wikiFuzz::randnum( 10 ) == 0 ) unset( $this->params["target"] ); - } -} - - -/** - ** a test for Special:CategoryTree (extension Special page). - */ -class specialCategoryTree extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:CategoryTree"; - - $this->params = array ( - "target" => wikiFuzz::makeFuzz( 2 ), - "from" => wikiFuzz::makeFuzz( 2 ), - "until" => wikiFuzz::makeFuzz( 2 ), - "showas" => wikiFuzz::makeFuzz( 2 ), - "mode" => wikiFuzz::chooseInput( array( "pages", "categories", "all", wikiFuzz::makeFuzz( 2 ) ) ), - ); - - // sometimes we do want to specify certain parameters. - if ( wikiFuzz::randnum( 5 ) == 0 ) $this->params["notree"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) ); - } -} - - -/** - ** a test for "Special:Chemicalsources" (extension Special page). - */ -class specialChemicalsourcesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Chemicalsources"; - - // choose an input format to use. - $format = wikiFuzz::chooseInput( - array( 'go', - 'CAS', - 'EINECS', - 'CHEBI', - 'PubChem', - 'SMILES', - 'InChI', - 'ATCCode', - 'KEGG', - 'RTECS', - 'ECNumber', - 'DrugBank', - 'Formula', - 'Name' - ) - ); - - // values for different formats usually start with either letters or numbers. - switch ( $format ) { - case 'Name' : $value = "A"; break; - case 'InChI' : - case 'SMILES' : - case 'Formula': $value = "C"; break; - default : $value = "0"; break; - } - - // and then we append the fuzz input. - $this->params = array ( $format => $value . wikiFuzz::makeFuzz( 2 ) ); - } -} - - -/** - ** A test for api.php (programmatic interface to MediaWiki in XML/JSON/RSS/etc formats). - ** Quite involved to test because there are lots of options/parameters, and because - ** for a lot of the functionality if all the parameters don't make sense then it just - ** returns the help screen - so currently a lot of the tests aren't actually doing much - ** because something wasn't right in the query. - ** - ** @todo Incomplete / unfinished; Runs too fast (suggests not much testing going on). - */ -class api extends pageTest { - - // API login mode. - private static function loginMode() { - $arr = array ( "lgname" => wikiFuzz::makeFuzz( 2 ), - "lgpassword" => wikiFuzz::makeFuzz( 2 ), - ); - // sometimes we want to specify the extra "lgdomain" parameter. - if ( wikiFuzz::randnum( 3 ) == 0 ) { - $arr["lgdomain"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) ); - } - - return $arr; - } - - // API OpenSearch mode. - private static function opensearchMode() { - return array ( "search" => wikiFuzz::makeFuzz( 2 ) ); - } - - // API watchlist feed mode. - private static function feedwatchlistMode() { - // @todo FIXME: Add "wikiFuzz::makeFuzz(2)" as possible value below? - return array ( "feedformat" => wikiFuzz::chooseInput( array( "rss", "atom" ) ) ); - } - - // API query mode. - private static function queryMode() { - // @todo FIXME: Add "wikiFuzz::makeFuzz(2)" as possible params for the elements below? - // Suspect this will stuff up the tests more, but need to check. - $params = array ( - // @todo FIXME: More titles. - "titles" => wikiFuzz::chooseInput( array( "Main Page" ) ), - // @todo FIXME: More pageids. - "pageids" => 1, - "prop" => wikiFuzz::chooseInput( array( "info", "revisions", "watchlist" ) ), - "list" => wikiFuzz::chooseInput( array( "allpages", "logevents", "watchlist", "usercontribs", "recentchanges", "backlinks", "embeddedin", "imagelinks" ) ), - "meta" => wikiFuzz::chooseInput( array( "siteinfo" ) ), - "generator" => wikiFuzz::chooseInput( array( "allpages", "logevents", "watchlist", "info", "revisions" ) ), - "siprop" => wikiFuzz::chooseInput( array( "general", "namespaces", "general|namespaces" ) ), - ); - - // Add extra parameters based on what list choice we got. - switch ( $params["list"] ) { - case "usercontribs" : self::addListParams ( $params, "uc", array( "limit", "start", "end", "user", "dir" ) ); break; - case "allpages" : self::addListParams ( $params, "ap", array( "from", "prefix", "namespace", "filterredir", "limit" ) ); break; - case "watchlist" : self::addListParams ( $params, "wl", array( "allrev", "start", "end", "namespace", "dir", "limit", "prop" ) ); break; - case "logevents" : self::addListParams ( $params, "le", array( "limit", "type", "start", "end", "user", "dir" ) ); break; - case "recentchanges": self::addListParams ( $params, "rc", array( "limit", "prop", "show", "namespace", "start", "end", "dir" ) ); break; - case "backlinks" : self::addListParams ( $params, "bl", array( "continue", "namespace", "redirect", "limit" ) ); break; - case "embeddedin" : self::addListParams ( $params, "ei", array( "continue", "namespace", "redirect", "limit" ) ); break; - case "imagelinks" : self::addListParams ( $params, "il", array( "continue", "namespace", "redirect", "limit" ) ); break; - } - - if ( $params["prop"] == "revisions" ) { - self::addListParams ( $params, "rv", array( "prop", "limit", "startid", "endid", "end", "dir" ) ); - } - - // Sometimes we want redirects, sometimes we don't. - if ( wikiFuzz::randnum( 3 ) == 0 ) { - $params["redirects"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) ); - } - - return $params; - } - - // Adds all the elements to the array, using the specified prefix. - private static function addListParams( &$array, $prefix, $elements ) { - foreach ( $elements as $element ) { - $array[$prefix . $element] = self::getParamDetails( $element ); - } - } - - // For a given element name, returns the data for that element. - private static function getParamDetails( $element ) { - switch ( $element ) { - case 'startid' : - case 'endid' : - case 'start' : - case 'end' : - case 'limit' : return wikiFuzz::chooseInput( array( "0", "-1", "---'----------0", "+1", "8134", "320742734234235", "20060230121212", wikiFuzz::randnum( 9000, -100 ), wikiFuzz::makeFuzz( 2 ) ) ); - case 'dir' : return wikiFuzz::chooseInput( array( "newer", "older", wikiFuzz::makeFuzz( 2 ) ) ); - case 'user' : return wikiFuzz::chooseInput( array( USER_ON_WIKI, wikiFuzz::makeFuzz( 2 ) ) ); - case 'namespace' : return wikiFuzz::chooseInput( array( -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 200000, wikiFuzz::makeFuzz( 2 ) ) ); - case 'filterredir': return wikiFuzz::chooseInput( array( "all", "redirects", "nonredirectsallpages", wikiFuzz::makeFuzz( 2 ) ) ); - case 'allrev' : return wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) ); - case 'prop' : return wikiFuzz::chooseInput( array( "user", "comment", "timestamp", "patrol", "flags", "user|user|comment|flags", wikiFuzz::makeFuzz( 2 ) ) ); - case 'type' : return wikiFuzz::chooseInput( array( "block", "protect", "rights", "delete", "upload", "move", "import", "renameuser", "newusers", "makebot", wikiFuzz::makeFuzz( 2 ) ) ); - case 'hide' : return wikiFuzz::chooseInput( array( "minor", "bots", "anons", "liu", "liu|bots|", wikiFuzz::makeFuzz( 2 ) ) ); - case 'show' : return wikiFuzz::chooseInput( array( 'minor', '!minor', 'bot', '!bot', 'anon', '!anon', wikiFuzz::makeFuzz( 2 ) ) ); - default : return wikiFuzz::makeFuzz( 2 ); - } - } - - // Entry point. - function __construct() { - $this->pagePath = "api.php"; - - $modes = array ( "help", - "login", - "opensearch", - "feedwatchlist", - "query" ); - $action = wikiFuzz::chooseInput( array_merge ( $modes, array( wikiFuzz::makeFuzz( 2 ) ) ) ); - - switch ( $action ) { - case "login" : $this->params = self::loginMode(); - break; - case "opensearch" : $this->params = self::opensearchMode(); - break; - case "feedwatchlist" : $this->params = self::feedwatchlistMode(); - break; - case "query" : $this->params = self::queryMode(); - break; - case "help" : - default : // Do something random - "Crazy Ivan" mode. - $random_mode = wikiFuzz::chooseInput( $modes ) . "Mode"; - // There is no "helpMode". - if ( $random_mode == "helpMode" ) $random_mode = "queryMode"; - $this->params = self::$random_mode(); - break; - } - - // Save the selected action. - $this->params["action"] = $action; - - // Set the cookie: - // @todo FIXME: Need to get this cookie dynamically set, rather than hard-coded. - $this->cookie = "wikidbUserID=10001; wikidbUserName=Test; wikidb_session=178df0fe68c75834643af65dec9ec98a; wikidbToken=1adc6753d62c44aec950c024d7ae0540"; - - // Output format - $this->params["format"] = wikiFuzz::chooseInput( array( "json", "jsonfm", "php", "phpfm", - "wddx", "wddxfm", "xml", "xmlfm", - "yaml", "yamlfm", "raw", "rawfm", - wikiFuzz::makeFuzz( 2 ) ) ); - - // Page does not produce HTML (sometimes). - $this->tidyValidate = false; - } -} - - -/** - ** a page test for the GeSHi extension. - */ -class GeSHi_Test extends pageTest { - - private function getGeSHiContent() { - return "<source lang=\"" . $this->getLang() . "\" " - . ( wikiFuzz::randnum( 2 ) == 0 ? "line " : "" ) - . ( wikiFuzz::randnum( 2 ) == 0 ? "strict " : "" ) - . "start=" . wikiFuzz::chooseInput( array( wikiFuzz::randnum( 6000, -6000 ), wikiFuzz::makeFuzz( 2 ) ) ) - . ">" - . wikiFuzz::makeFuzz( 2 ) - . "</source>"; - } - - private function getLang() { - return wikiFuzz::chooseInput( array( "actionscript", "ada", "apache", "applescript", "asm", "asp", "autoit", "bash", "blitzbasic", "bnf", "c", "c_mac", "caddcl", "cadlisp", - "cfdg", "cfm", "cpp", "cpp-qt", "csharp", "css", "d", "delphi", "diff", "div", "dos", "eiffel", "fortran", "freebasic", "gml", "groovy", "html4strict", "idl", - "ini", "inno", "io", "java", "java5", "javascript", "latex", "lisp", "lua", "matlab", "mirc", "mpasm", "mysql", "nsis", "objc", "ocaml", "ocaml-brief", "oobas", - "oracle8", "pascal", "perl", "php", "php-brief", "plsql", "python", "qbasic", "rails", "reg", "robots", "ruby", "sas", "scheme", "sdlbasic", "smalltalk", "smarty", - "sql", "tcl", "text", "thinbasic", "tsql", "vb", "vbnet", "vhdl", "visualfoxpro", "winbatch", "xml", "xpp", "z80", wikiFuzz::makeFuzz( 1 ) ) ); - } - - function __construct() { - $this->pagePath = "index.php?title=WIKIFUZZ"; - - $this->params = array ( - "action" => "submit", - "wpMinoredit" => "test", - "wpPreview" => "test", - "wpSection" => "test", - "wpEdittime" => "test", - "wpSummary" => "test", - "wpScrolltop" => "test", - "wpStarttime" => "test", - "wpAutoSummary" => "test", - "wpTextbox1" => $this->getGeSHiContent() // the main wiki text, contains fake GeSHi content. - ); - } -} - -/** - ** selects a page test to run. - * @param $count - * @return \api|\confirmEmail|\contributionsTest|\editPageTest|\imagelistTest|\imagepageTest|\ipblocklistTest|\listusersTest|\mimeSearchTest|\newImagesTest|\pageDeletion|\pageHistoryTest|\pageProtectionForm|\prefixindexTest|\profileInfo|\recentchangesTest|\redirectTest|\searchTest|\specialAllmessagesTest|\specialAllpagesTest|\specialBlockip|\specialBooksourcesTest|\specialCategoryTree|\specialChemicalsourcesTest|\specialCitePageTest|\specialExportTest|\specialFilepathPageTest|\specialImportPageTest|\specialLinksearch|\specialLockdbPageTest|\specialLogTest|\specialMovePage|\specialNewpagesPageTest|\specialRenameuserPageTest|\specialRevisionDeletePageTest|\specialUndeletePageTest|\specialUnlockdbPageTest|\specialUserrights|\successfulUserLoginTest|\thumbTest|\userLoginTest|\viewPageTest|\watchlistTest - */ -function selectPageTest( $count ) { - - // if the user only wants a specific test, then only ever give them that. - if ( defined( "SPECIFIC_TEST" ) ) { - $testType = SPECIFIC_TEST; - return new $testType (); - } - - // Some of the time we test Special pages, the remaining - // time we test using the standard edit page. - switch ( $count % 100 ) { - case 0 : return new successfulUserLoginTest(); - case 1 : return new listusersTest(); - case 2 : return new searchTest(); - case 3 : return new recentchangesTest(); - case 4 : return new prefixindexTest(); - case 5 : return new mimeSearchTest(); - case 6 : return new specialLogTest(); - case 7 : return new userLoginTest(); - case 8 : return new ipblocklistTest(); - case 9 : return new newImagesTest(); - case 10: return new imagelistTest(); - case 11: return new specialExportTest(); - case 12: return new specialBooksourcesTest(); - case 13: return new specialAllpagesTest(); - case 14: return new pageHistoryTest(); - case 15: return new contributionsTest(); - case 16: return new viewPageTest(); - case 17: return new specialAllmessagesTest(); - case 18: return new specialNewpagesPageTest(); - case 19: return new searchTest(); - case 20: return new redirectTest(); - case 21: return new confirmEmail(); - case 22: return new watchlistTest(); - case 24: return new specialUndeletePageTest(); - case 25: return new specialMovePage(); - case 26: return new specialUnlockdbPageTest(); - case 27: return new specialLockdbPageTest(); - case 28: return new specialUserrights(); - case 29: return new pageProtectionForm(); - case 30: return new specialBlockip(); - case 31: return new imagepageTest(); - case 32: return new pageDeletion(); - case 33: return new specialRevisionDeletePageTest(); - case 34: return new specialImportPageTest(); - case 35: return new thumbTest(); - case 37: return new profileInfo(); - case 38: return new specialCitePageTest(); - case 39: return new specialFilepathPageTest(); - case 40: return new specialRenameuserPageTest(); - case 41: return new specialLinksearch(); - case 42: return new specialCategoryTree(); - case 43: return new api(); - case 44: return new specialChemicalsourcesTest(); - default: return new editPageTest(); - } -} - - -// ///////////////////// SAVING OUTPUT ///////////////////////// - -/** - ** Utility function for saving a file. Currently has no error checking. - */ -function saveFile( $data, $name ) { - file_put_contents( $name, $data ); -} - -/** - ** Returns a test as an experimental GET-to-POST URL. - ** This doesn't seem to always work though, and sometimes the output is too long - ** to be a valid GET URL, so we also save in other formats. - * @param $test pageTest - * @return string - */ -function getAsURL( pageTest $test ) { - $used_question_mark = ( strpos( $test->getPagePath(), "?" ) !== false ); - $retval = "http://get-to-post.nickj.org/?" . WIKI_BASE_URL . $test->getPagePath(); - foreach ( $test->getParams() as $param => $value ) { - if ( !$used_question_mark ) { - $retval .= "?"; - $used_question_mark = true; - } - else { - $retval .= "&"; - } - $retval .= $param . "=" . urlencode( $value ); - } - return $retval; -} - - -/** - ** Saves a plain-text human-readable version of a test. - */ -function saveTestAsText( pageTest $test, $filename ) { - $str = "Test: " . $test->getPagePath(); - foreach ( $test->getParams() as $param => $value ) { - $str .= "\n$param: $value"; - } - $str .= "\nGet-to-post URL: " . getAsURL( $test ) . "\n"; - saveFile( $str, $filename ); -} - - -/** - ** Saves a test as a standalone basic PHP script that shows this one problem. - ** Resulting script requires PHP-Curl be installed in order to work. - */ -function saveTestAsPHP( pageTest $test, $filename ) { - $str = "<?php\n" - . "\$params = " . var_export( escapeForCurl( $test->getParams() ), true ) . ";\n" - . "\$ch = curl_init();\n" - . "curl_setopt(\$ch, CURLOPT_POST, 1);\n" - . "curl_setopt(\$ch, CURLOPT_POSTFIELDS, \$params );\n" - . "curl_setopt(\$ch, CURLOPT_URL, " . var_export( WIKI_BASE_URL . $test->getPagePath(), true ) . ");\n" - . "curl_setopt(\$ch, CURLOPT_RETURNTRANSFER,1);\n" - . ( $test->getCookie() ? "curl_setopt(\$ch, CURLOPT_COOKIE, " . var_export( $test->getCookie(), true ) . ");\n" : "" ) - . "\$result=curl_exec(\$ch);\n" - . "curl_close (\$ch);\n" - . "print \$result;\n" - . "\n"; - saveFile( $str, $filename ); -} - -/** - * Escapes a value so that it can be used on the command line by Curl. - * Specifically, "<" and "@" need to be escaped if they are the first character, - * otherwise curl interprets these as meaning that we want to insert a file. - * @param $input_params array - * @return array - */ -function escapeForCurl( array $input_params ) { - $output_params = array(); - foreach ( $input_params as $param => $value ) { - if ( strlen( $value ) > 0 && ( $value[0] == "@" || $value[0] == "<" ) ) { - $value = "\\" . $value; - } - $output_params[$param] = $value; - } - return $output_params; -} - - -/** - ** Saves a test as a standalone CURL shell script that shows this one problem. - ** Resulting script requires standalone Curl be installed in order to work. - */ -function saveTestAsCurl( pageTest $test, $filename ) { - $str = "#!/bin/bash\n" - . "curl --silent --include --globoff \\\n" - . ( $test->getCookie() ? " --cookie " . escapeshellarg( $test->getCookie() ) . " \\\n" : "" ); - foreach ( escapeForCurl( $test->getParams() ) as $param => $value ) { - $str .= " -F " . escapeshellarg( $param ) . "=" . escapeshellarg( $value ) . " \\\n"; - } - $str .= " " . escapeshellarg( WIKI_BASE_URL . $test->getPagePath() ); // beginning space matters. - $str .= "\n"; - saveFile( $str, $filename ); - chmod( $filename, 0755 ); // make executable -} - - -/** - ** Saves the internal data structure to file. - */ -function saveTestData ( pageTest $test, $filename ) { - saveFile( serialize( $test ), $filename ); -} - - -/** - ** saves a test in the various formats. - */ -function saveTest( pageTest $test, $testname ) { - $base_name = DIRECTORY . "/" . $testname; - saveTestAsText( $test, $base_name . INFO_FILE ); - saveTestAsPHP ( $test, $base_name . PHP_TEST ); - saveTestAsCurl( $test, $base_name . CURL_TEST ); - saveTestData ( $test, $base_name . DATA_FILE ); -} - -// ////////////////// MEDIAWIKI OUTPUT ///////////////////////// - -/** - * Asks MediaWiki for the HTML output of a test. - * @param $test pageTest - * @return string - */ -function wikiTestOutput( pageTest $test ) { - - $ch = curl_init(); - - // specify the cookie, if required. - if ( $test->getCookie() ) { - curl_setopt( $ch, CURLOPT_COOKIE, $test->getCookie() ); - } - curl_setopt( $ch, CURLOPT_POST, 1 ); // save form using a POST - - $params = escapeForCurl( $test->getParams() ); - curl_setopt( $ch, CURLOPT_POSTFIELDS, $params ); // load the POST variables - - curl_setopt( $ch, CURLOPT_URL, WIKI_BASE_URL . $test->getPagePath() ); // set url to post to - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); // return into a variable - - $result = curl_exec ( $ch ); - - // if we encountered an error, then say so, and return an empty string. - if ( curl_error( $ch ) ) { - print "\nCurl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch ); - $result = ""; - } - - curl_close ( $ch ); - - return $result; -} - - -// ////////////////// HTML VALIDATION ///////////////////////// - -/** - * Asks the validator whether this is valid HTML, or not. - * @param $text string - * @return array - */ -function validateHTML( $text ) { - - $params = array ( "fragment" => $text ); - - $ch = curl_init(); - - curl_setopt( $ch, CURLOPT_POST, 1 ); // save form using a POST - curl_setopt( $ch, CURLOPT_POSTFIELDS, $params ); // load the POST variables - curl_setopt( $ch, CURLOPT_URL, VALIDATOR_URL ); // set url to post to - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); // return into a variable - - $result = curl_exec ( $ch ); - - // if we encountered an error, then log it, and exit. - if ( curl_error( $ch ) ) { - trigger_error( "Curl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch ) ); - print "Curl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch ) . " - exiting.\n"; - exit( 1 ); - } - - curl_close ( $ch ); - - $valid = ( strpos( $result, "Failed validation" ) === false ); - - return array( $valid, $result ); -} - -/** - * Get tidy to check for no HTML errors in the output file (e.g. unescaped strings). - * @param $name - * @return bool - */ -function tidyCheckFile( $name ) { - $file = DIRECTORY . "/" . $name; - $command = PATH_TO_TIDY . " -output /tmp/out.html -quiet $file 2>&1"; - $x = `$command`; - - // Look for the most interesting Tidy errors and warnings. - if ( strpos( $x, "end of file while parsing attributes" ) !== false - || strpos( $x, "attribute with missing trailing quote mark" ) !== false - || strpos( $x, "missing '>' for end of tag" ) !== false - || strpos( $x, "Error:" ) !== false ) { - print "\nTidy found something - view details with: $command"; - return false; - } else { - return true; - } -} - -/** - ** Returns whether or not an database error log file has changed in size since - ** the last time this was run. This is used to tell if a test caused a DB error. - * @return bool - */ -function dbErrorLogged() { - static $filesize; - - // first time running this function - if ( !isset( $filesize ) ) { - // create log if it does not exist - if ( DB_ERROR_LOG_FILE && !file_exists( DB_ERROR_LOG_FILE ) ) { - saveFile( '', DB_ERROR_LOG_FILE ); - } - $filesize = filesize( DB_ERROR_LOG_FILE ); - return false; - } - - $newsize = filesize( DB_ERROR_LOG_FILE ); - // if the log has grown, then assume the current test caused it. - if ( $newsize != $filesize ) { - $filesize = $newsize; - return true; - } - - return false; -} - -// //////////////// TOP-LEVEL PROBLEM-FINDING FUNCTION //////////////////////// - -/** - * takes a page test, and runs it and tests it for problems in the output. - * Returns: False on finding a problem, or True on no problems being found. - * @param $test pageTest - * @param $testname - * @param $can_overwrite bool - * @return bool - */ -function runWikiTest( pageTest $test, &$testname, $can_overwrite = false ) { - - // by default don't overwrite a previous test of the same name. - while ( ! $can_overwrite && file_exists( DIRECTORY . "/" . $testname . DATA_FILE ) ) { - $testname .= "-" . mt_rand( 0, 9 ); - } - - $filename = DIRECTORY . "/" . $testname . DATA_FILE; - - // Store the time before and after, to find slow pages. - $before = microtime( true ); - - // Get MediaWiki to give us the output of this test. - $wiki_preview = wikiTestOutput( $test ); - - $after = microtime( true ); - - // if we received no response, then that's interesting. - if ( $wiki_preview == "" ) { - print "\nNo response received for: $filename"; - return false; - } - - // save output HTML to file. - $html_file = DIRECTORY . "/" . $testname . HTML_FILE; - saveFile( $wiki_preview, $html_file ); - - // if there were PHP errors in the output, then that's interesting too. - if ( strpos( $wiki_preview, "<b>Warning</b>: " ) !== false - || strpos( $wiki_preview, "<b>Fatal error</b>: " ) !== false - || strpos( $wiki_preview, "<b>Notice</b>: " ) !== false - || strpos( $wiki_preview, "<b>Error</b>: " ) !== false - || strpos( $wiki_preview, "<b>Strict Standards:</b>" ) !== false - ) { - $error = substr( $wiki_preview, strpos( $wiki_preview, "</b>:" ) + 7, 50 ); - // Avoid probable PHP bug with bad session ids; http://bugs.php.net/bug.php?id=38224 - if ( $error != "Unknown: The session id contains illegal character" ) { - print "\nPHP error/warning/notice in HTML output: $html_file ; $error"; - return false; - } - } - - // if there was a MediaWiki Backtrace message in the output, then that's also interesting. - if ( strpos( $wiki_preview, "Backtrace:" ) !== false ) { - print "\nInternal MediaWiki error in HTML output: $html_file"; - return false; - } - - // if there was a Parser error comment in the output, then that's potentially interesting. - if ( strpos( $wiki_preview, "!-- ERR" ) !== false ) { - print "\nParser Error comment in HTML output: $html_file"; - return false; - } - - // if a database error was logged, then that's definitely interesting. - if ( dbErrorLogged() ) { - print "\nDatabase Error logged for: $filename"; - return false; - } - - // validate result - $valid = true; - if ( VALIDATE_ON_WEB ) { - list ( $valid, $validator_output ) = validateHTML( $wiki_preview ); - if ( !$valid ) print "\nW3C web validation failed - view details with: html2text " . DIRECTORY . "/" . $testname . ".validator_output.html"; - } - - // Get tidy to check the page, unless we already know it produces non-(X)HTML output. - if ( $test->tidyValidate() ) { - $valid = tidyCheckFile( $testname . HTML_FILE ) && $valid; - } - - // if it took more than 2 seconds to render, then it may be interesting too. (Possible DoS attack?) - if ( ( $after - $before ) >= 2 ) { - print "\nParticularly slow to render (" . round( $after - $before, 2 ) . " seconds): $filename"; - return false; - } - - if ( $valid ) { - // Remove temp HTML file if test was valid: - unlink( $html_file ); - } elseif ( VALIDATE_ON_WEB ) { - saveFile( $validator_output, DIRECTORY . "/" . $testname . ".validator_output.html" ); - } - - return $valid; -} - - -// ///////////////// RERUNNING OLD TESTS /////////////////// - -/** - ** We keep our failed tests so that they can be rerun. - ** This function does that retesting. - */ -function rerunPreviousTests() { - print "Retesting previously found problems.\n"; - - $dir_contents = scandir ( DIRECTORY ); - - // sort file into the order a normal person would use. - natsort ( $dir_contents ); - - foreach ( $dir_contents as $file ) { - - // if file is not a test, then skip it. - // Note we need to escape any periods or will be treated as "any character". - $matches = array(); - if ( !preg_match( "/(.*)" . str_replace( ".", "\.", DATA_FILE ) . "$/", $file, $matches ) ) continue; - - // reload the test. - $full_path = DIRECTORY . "/" . $file; - $test = unserialize( file_get_contents( $full_path ) ); - - // if this is not a valid test, then skip it. - if ( ! $test instanceof pageTest ) { - print "\nSkipping invalid test - $full_path"; - continue; - } - - // The date format is in Apache log format, which makes it easier to locate - // which retest caused which error in the Apache logs (only happens usually if - // apache segfaults). - if ( !QUIET ) print "[" . date ( "D M d H:i:s Y" ) . "] Retesting $file (" . get_class( $test ) . ")"; - - // run test - $testname = $matches[1]; - $valid = runWikiTest( $test, $testname, true ); - - if ( !$valid ) { - saveTest( $test, $testname ); - if ( QUIET ) { - print "\nTest: " . get_class( $test ) . " ; Testname: $testname\n------"; - } else { - print "\n"; - } - } - else { - if ( !QUIET ) print "\r"; - if ( DELETE_PASSED_RETESTS ) { - $prefix = DIRECTORY . "/" . $testname; - if ( is_file( $prefix . DATA_FILE ) ) unlink( $prefix . DATA_FILE ); - if ( is_file( $prefix . PHP_TEST ) ) unlink( $prefix . PHP_TEST ); - if ( is_file( $prefix . CURL_TEST ) ) unlink( $prefix . CURL_TEST ); - if ( is_file( $prefix . INFO_FILE ) ) unlink( $prefix . INFO_FILE ); - } - } - } - - print "\nDone retesting.\n"; -} - - -// //////////////////// MAIN LOOP //////////////////////// - - -// first check whether CURL is installed, because sometimes it's not. -if ( ! function_exists( 'curl_init' ) ) { - die( "Could not find 'curl_init' function. Is the curl extension compiled into PHP?\n" ); -} - -// Initialization of types. wikiFuzz doesn't have a constructor because we want to -// access it staticly and not have any globals. -wikiFuzz::$types = array_keys( wikiFuzz::$data ); - -// Make directory if doesn't exist -if ( !is_dir( DIRECTORY ) ) { - mkdir ( DIRECTORY, 0700 ); -} -// otherwise, we first retest the things that we have found in previous runs -elseif ( RERUN_OLD_TESTS ) { - rerunPreviousTests(); -} - -// main loop. -$start_time = date( "U" ); -$num_errors = 0; -if ( !QUIET ) { - print "Beginning main loop. Results are stored in the " . DIRECTORY . " directory.\n"; - print "Press CTRL+C to stop testing.\n"; -} - -for ( $count = 0; true; $count++ ) { - if ( !QUIET ) { - // spinning progress indicator. - switch( $count % 4 ) { - case '0': print "\r/"; break; - case '1': print "\r-"; break; - case '2': print "\r\\"; break; - case '3': print "\r|"; break; - } - print " $count"; - } - - // generate a page test to run. - $test = selectPageTest( $count ); - - $mins = ( date( "U" ) - $start_time ) / 60; - if ( !QUIET && $mins > 0 ) { - print ". $num_errors poss errors. " - . floor( $mins ) . " mins. " - . round ( $count / $mins, 0 ) . " tests/min. " - . get_class( $test ); // includes the current test name. - } - - // run this test against MediaWiki, and see if the output was valid. - $testname = $count; - $valid = runWikiTest( $test, $testname, false ); - - // save the failed test - if ( ! $valid ) { - if ( QUIET ) { - print "\nTest: " . get_class( $test ) . " ; Testname: $testname\n------"; - } else { - print "\n"; - } - saveTest( $test, $testname ); - $num_errors += 1; - } elseif ( KEEP_PASSED_TESTS ) { - // print current time, with microseconds (matches "strace" format), and the test name. - print " " . date( "H:i:s." ) . substr( current( explode( " ", microtime() ) ), 2 ) . " " . $testname; - saveTest( $test, $testname ); - } - - // stop if we have reached max number of errors. - if ( defined( "MAX_ERRORS" ) && $num_errors >= MAX_ERRORS ) { - break; - } - - // stop if we have reached max number of mins runtime. - if ( defined( "MAX_RUNTIME" ) && $mins >= MAX_RUNTIME ) { - break; - } -} diff --git a/maintenance/generateJsonI18n.php b/maintenance/generateJsonI18n.php new file mode 100644 index 00000000..22d99405 --- /dev/null +++ b/maintenance/generateJsonI18n.php @@ -0,0 +1,287 @@ +<?php + +/** + * Convert a PHP messages file to a set of JSON messages files. + * + * Usage: + * php generateJsonI18n.php ExtensionName.i18n.php i18n/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + */ + +require_once __DIR__ . '/Maintenance.php'; + +/** + * Maintenance script to generate JSON i18n files from a PHP i18n file. + * + * @ingroup Maintenance + */ +class GenerateJsonI18n extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Build JSON messages files from a PHP messages file"; + + $this->addArg( 'phpfile', 'PHP file defining a $messages array', false ); + $this->addArg( 'jsondir', 'Directory to write JSON files to', false ); + $this->addOption( 'langcode', 'Language code; only needed for converting core i18n files', + false, true ); + $this->addOption( 'extension', 'Perform default conversion on an extension', + false, true ); + $this->addOption( 'shim-only', 'Only create or update the backward-compatibility shim' ); + $this->addOption( 'supplementary', 'Find supplementary i18n files in subdirs and convert those', + false, false ); + } + + public function execute() { + global $IP; + + $phpfile = $this->getArg( 0 ); + $jsondir = $this->getArg( 1 ); + $extension = $this->getOption( 'extension' ); + $convertSupplementaryI18nFiles = $this->hasOption( 'supplementary' ); + + if ( $extension ) { + if ( $phpfile ) { + $this->error( "The phpfile is already specified, conflicts with --extension.\n", 1 ); + } + $phpfile = "$IP/extensions/$extension/$extension.i18n.php"; + } + + if ( !$phpfile ) { + $this->error( "I'm here for an argument!\n" ); + $this->maybeHelp( true ); + // dies. + } + + if ( $convertSupplementaryI18nFiles ) { + if ( is_readable( $phpfile ) ) { + $this->transformI18nFile( $phpfile, $jsondir ); + } else { + // This is non-fatal because we might want to continue searching for + // i18n files in subdirs even if the extension does not include a + // primary i18n.php. + $this->error( "Warning: no primary i18n file was found." ); + } + $this->output( "Searching for supplementary i18n files...\n" ); + $dir_iterator = new RecursiveDirectoryIterator( dirname( $phpfile ) ); + $iterator = new RecursiveIteratorIterator( + $dir_iterator, RecursiveIteratorIterator::LEAVES_ONLY ); + foreach ( $iterator as $path => $fileObject ) { + if ( fnmatch( "*.i18n.php", $fileObject->getFilename() ) ) { + $this->output( "Converting $path.\n" ); + $this->transformI18nFile( $path ); + } + } + } else { + // Just convert the primary i18n file. + $this->transformI18nFile( $phpfile, $jsondir ); + } + } + + public function transformI18nFile( $phpfile, $jsondir = null ) { + if ( !$jsondir ) { + // Assume the json directory should be in the same directory as the + // .i18n.php file. + $jsondir = dirname( $phpfile ) . "/i18n"; + } + if ( !is_dir( $jsondir ) ) { + $this->output( "Creating directory $jsondir.\n" ); + $success = mkdir( $jsondir ); + if ( !$success ) { + $this->error( "Could not create directory $jsondir\n", 1 ); + } + } + + if ( $this->hasOption( 'shim-only' ) ) { + $this->shimOnly( $phpfile, $jsondir ); + + return; + } + + if ( $jsondir === null ) { + $this->error( 'Argument [jsondir] is required unless --shim-only is specified.' ); + $this->maybeHelp( true ); + } + + if ( !is_readable( $phpfile ) ) { + $this->error( "Error reading $phpfile\n", 1 ); + } + include $phpfile; + $phpfileContents = file_get_contents( $phpfile ); + + if ( !isset( $messages ) ) { + $this->error( "PHP file $phpfile does not define \$messages array\n", 1 ); + } + + $extensionStyle = true; + if ( !isset( $messages['en'] ) || !is_array( $messages['en'] ) ) { + if ( !$this->hasOption( 'langcode' ) ) { + $this->error( "PHP file $phpfile does not set language codes, --langcode " . + "is required.\n", 1 ); + } + $extensionStyle = false; + $langcode = $this->getOption( 'langcode' ); + $messages = array( $langcode => $messages ); + } elseif ( $this->hasOption( 'langcode' ) ) { + $this->output( "Warning: --langcode option set but will not be used.\n" ); + } + + foreach ( $messages as $langcode => $langmsgs ) { + $authors = $this->getAuthorsFromComment( $this->findCommentBefore( + $extensionStyle ? "\$messages['$langcode'] =" : '$messages =', + $phpfileContents + ) ); + // Make sure the @metadata key is the first key in the output + $langmsgs = array_merge( + array( '@metadata' => array( 'authors' => $authors ) ), + $langmsgs + ); + + $jsonfile = "$jsondir/$langcode.json"; + $success = file_put_contents( + $jsonfile, + FormatJson::encode( $langmsgs, "\t", FormatJson::ALL_OK ) . "\n" + ); + if ( $success === false ) { + $this->error( "FAILED to write $jsonfile", 1 ); + } + $this->output( "$jsonfile\n" ); + } + + if ( !$this->hasOption( 'langcode' ) ) { + $shim = $this->doShim( $jsondir ); + file_put_contents( $phpfile, $shim ); + } + + $this->output( "All done.\n" ); + $this->output( "Also add \$wgMessagesDirs['YourExtension'] = __DIR__ . '/i18n';\n" ); + } + + protected function shimOnly( $phpfile, $jsondir ) { + if ( file_exists( $phpfile ) ) { + if ( !is_readable( $phpfile ) ) { + $this->error( "Error reading $phpfile\n", 1 ); + } + + $phpfileContents = file_get_contents( $phpfile ); + $m = array(); + if ( !preg_match( '!"/([^"$]+)/\$csCode.json";!', $phpfileContents, $m ) ) { + $this->error( "Cannot recognize $phpfile as a shim.\n", 1 ); + } + + if ( $jsondir === null ) { + $jsondir = $m[1]; + } + + $this->output( "Updating existing shim $phpfile\n" ); + } elseif ( $jsondir === null ) { + $this->error( "$phpfile does not exist.\n" . + "Argument [jsondir] is required in order to create a new shim.\n", 1 ); + } else { + $this->output( "Creating new shim $phpfile\n" ); + } + + $shim = $this->doShim( $jsondir ); + file_put_contents( $phpfile, $shim ); + $this->output( "All done.\n" ); + } + + protected function doShim( $jsondir ) { + $shim = <<<'PHP' +<?php +/** + * This is a backwards-compatibility shim, generated by: + * https://git.wikimedia.org/blob/mediawiki%2Fcore.git/HEAD/maintenance%2FgenerateJsonI18n.php + * + * Beginning with MediaWiki 1.23, translation strings are stored in json files, + * and the EXTENSION.i18n.php file only exists to provide compatibility with + * older releases of MediaWiki. For more information about this migration, see: + * https://www.mediawiki.org/wiki/Requests_for_comment/Localisation_format + * + * This shim maintains compatibility back to MediaWiki 1.17. + */ +$messages = array(); +if ( !function_exists( '{{FUNC}}' ) ) { + function {{FUNC}}( $cache, $code, &$cachedData ) { + $codeSequence = array_merge( array( $code ), $cachedData['fallbackSequence'] ); + foreach ( $codeSequence as $csCode ) { + $fileName = dirname( __FILE__ ) . "/{{OUT}}/$csCode.json"; + if ( is_readable( $fileName ) ) { + $data = FormatJson::decode( file_get_contents( $fileName ), true ); + foreach ( array_keys( $data ) as $key ) { + if ( $key === '' || $key[0] === '@' ) { + unset( $data[$key] ); + } + } + $cachedData['messages'] = array_merge( $data, $cachedData['messages'] ); + } + + $cachedData['deps'][] = new FileDependency( $fileName ); + } + return true; + } + + $GLOBALS['wgHooks']['LocalisationCacheRecache'][] = '{{FUNC}}'; +} + +PHP; + + $jsondir = str_replace( '\\', '/', $jsondir ); + $shim = str_replace( '{{OUT}}', $jsondir, $shim ); + $shim = str_replace( '{{FUNC}}', 'wfJsonI18nShim' . wfRandomString( 16 ), $shim ); + + return $shim; + } + + /** + * Find the documentation comment immediately before a given search string + * @param string $needle String to search for + * @param string $haystack String to search in + * @return string Substring of $haystack starting at '/**' ending right before $needle, or empty + */ + protected function findCommentBefore( $needle, $haystack ) { + $needlePos = strpos( $haystack, $needle ); + if ( $needlePos === false ) { + return ''; + } + // Need to pass a negative offset to strrpos() so it'll search backwards from the + // offset + $startPos = strrpos( $haystack, '/**', $needlePos - strlen( $haystack ) ); + if ( $startPos === false ) { + return ''; + } + + return substr( $haystack, $startPos, $needlePos - $startPos ); + } + + /** + * Get an array of author names from a documentation comment containing @author declarations. + * @param string $comment Documentation comment + * @return array Array of author names (strings) + */ + protected function getAuthorsFromComment( $comment ) { + $matches = null; + preg_match_all( '/@author (.*?)$/m', $comment, $matches ); + + return $matches && $matches[1] ? $matches[1] : array(); + } +} + +$maintClass = "GenerateJsonI18n"; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/generateSitemap.php b/maintenance/generateSitemap.php index 0b21a1fe..1930a22a 100644 --- a/maintenance/generateSitemap.php +++ b/maintenance/generateSitemap.php @@ -63,7 +63,8 @@ class GenerateSitemap extends Maintenance { public $fspath; /** - * The URL path to prepend to filenames in the index; should resolve to the same directory as $fspath + * The URL path to prepend to filenames in the index; + * should resolve to the same directory as $fspath. * * @var string */ @@ -125,7 +126,6 @@ class GenerateSitemap extends Maintenance { */ public $findex; - /** * A resource pointing to a sitemap file * @@ -146,11 +146,32 @@ class GenerateSitemap extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Creates a sitemap for the site"; - $this->addOption( 'fspath', 'The file system path to save to, e.g. /tmp/sitemap; defaults to current directory', false, true ); - $this->addOption( 'urlpath', 'The URL path corresponding to --fspath, prepended to filenames in the index; defaults to an empty string', false, true ); - $this->addOption( 'compress', 'Compress the sitemap files, can take value yes|no, default yes', false, true ); + $this->addOption( + 'fspath', + 'The file system path to save to, e.g. /tmp/sitemap; defaults to current directory', + false, + true + ); + $this->addOption( + 'urlpath', + 'The URL path corresponding to --fspath, prepended to filenames in the index; ' + . 'defaults to an empty string', + false, + true + ); + $this->addOption( + 'compress', + 'Compress the sitemap files, can take value yes|no, default yes', + false, + true + ); $this->addOption( 'skip-redirects', 'Do not include redirecting articles in the sitemap' ); - $this->addOption( 'identifier', 'What site identifier to use for the wiki, defaults to $wgDBname', false, true ); + $this->addOption( + 'identifier', + 'What site identifier to use for the wiki, defaults to $wgDBname', + false, + true + ); } /** @@ -219,13 +240,10 @@ class GenerateSitemap extends Maintenance { /** * Create directory if it does not exist and return pathname with a trailing slash - * @param $fspath string + * @param string $fspath * @return null|string */ private static function init_path( $fspath ) { - if ( !isset( $fspath ) ) { - return null; - } # Create directory if needed if ( $fspath && !is_dir( $fspath ) ) { wfMkdirParents( $fspath, null, __METHOD__ ) or die( "Can not create directory $fspath.\n" ); @@ -242,6 +260,7 @@ class GenerateSitemap extends Maintenance { global $wgSitemapNamespaces; if ( is_array( $wgSitemapNamespaces ) ) { $this->namespaces = $wgSitemapNamespaces; + return; } @@ -263,11 +282,13 @@ class GenerateSitemap extends Maintenance { /** * Get the priority of a given namespace * - * @param $namespace Integer: the namespace to get the priority for - * @return String + * @param int $namespace The namespace to get the priority for + * @return string */ function priority( $namespace ) { - return isset( $this->priorities[$namespace] ) ? $this->priorities[$namespace] : $this->guessPriority( $namespace ); + return isset( $this->priorities[$namespace] ) + ? $this->priorities[$namespace] + : $this->guessPriority( $namespace ); } /** @@ -275,17 +296,19 @@ class GenerateSitemap extends Maintenance { * default priority for the namespace, varies depending on whether it's * a talkpage or not. * - * @param $namespace Integer: the namespace to get the priority for - * @return String + * @param int $namespace The namespace to get the priority for + * @return string */ function guessPriority( $namespace ) { - return MWNamespace::isSubject( $namespace ) ? $this->priorities[self::GS_MAIN] : $this->priorities[self::GS_TALK]; + return MWNamespace::isSubject( $namespace ) + ? $this->priorities[self::GS_MAIN] + : $this->priorities[self::GS_TALK]; } /** * Return a database resolution of all the pages in a given namespace * - * @param $namespace Integer: limit the query to this namespace + * @param int $namespace Limit the query to this namespace * @return Resource */ function getPageRes( $namespace ) { @@ -318,14 +341,17 @@ class GenerateSitemap extends Maintenance { $fns = $wgContLang->getFormattedNsText( $namespace ); $this->output( "$namespace ($fns)\n" ); - $skippedRedirects = 0; // Number of redirects skipped for that namespace + $skippedRedirects = 0; // Number of redirects skipped for that namespace foreach ( $res as $row ) { if ( $this->skipRedirects && $row->page_is_redirect ) { $skippedRedirects++; continue; } - if ( $i++ === 0 || $i === $this->url_limit + 1 || $length + $this->limit[1] + $this->limit[2] > $this->size_limit ) { + if ( $i++ === 0 + || $i === $this->url_limit + 1 + || $length + $this->limit[1] + $this->limit[2] > $this->size_limit + ) { if ( $this->file !== false ) { $this->write( $this->file, $this->closeFile() ); $this->close( $this->file ); @@ -350,7 +376,11 @@ class GenerateSitemap extends Maintenance { if ( $vCode == $wgContLang->getCode() ) { continue; // we don't want default variant } - $entry = $this->fileEntry( $title->getCanonicalURL( '', $vCode ), $date, $this->priority( $namespace ) ); + $entry = $this->fileEntry( + $title->getCanonicalURL( '', $vCode ), + $date, + $this->priority( $namespace ) + ); $length += strlen( $entry ); $this->write( $this->file, $entry ); } @@ -373,18 +403,25 @@ class GenerateSitemap extends Maintenance { /** * gzopen() / fopen() wrapper * - * @return Resource + * @param string $file + * @param string $flags + * @return resource */ function open( $file, $flags ) { $resource = $this->compress ? gzopen( $file, $flags ) : fopen( $file, $flags ); if ( $resource === false ) { - throw new MWException( __METHOD__ . " error opening file $file with flags $flags. Check permissions?" ); + throw new MWException( __METHOD__ + . " error opening file $file with flags $flags. Check permissions?" ); } + return $resource; } /** * gzwrite() / fwrite() wrapper + * + * @param resource $handle + * @param string $str */ function write( &$handle, $str ) { if ( $handle === true || $handle === false ) { @@ -399,6 +436,8 @@ class GenerateSitemap extends Maintenance { /** * gzclose() / fclose() wrapper + * + * @param resource $handle */ function close( &$handle ) { if ( $this->compress ) { @@ -411,12 +450,13 @@ class GenerateSitemap extends Maintenance { /** * Get a sitemap filename * - * @param $namespace Integer: the namespace - * @param $count Integer: the count - * @return String + * @param int $namespace The namespace + * @param int $count The count + * @return string */ function sitemapFilename( $namespace, $count ) { $ext = $this->compress ? '.gz' : ''; + return "sitemap-{$this->identifier}-NS_$namespace-$count.xml$ext"; } @@ -432,7 +472,7 @@ class GenerateSitemap extends Maintenance { /** * Return the XML schema being used * - * @return String + * @return string */ function xmlSchema() { return 'http://www.sitemaps.org/schemas/sitemap/0.9'; @@ -441,7 +481,7 @@ class GenerateSitemap extends Maintenance { /** * Return the XML required to open a sitemap index file * - * @return String + * @return string */ function openIndex() { return $this->xmlHead() . '<sitemapindex xmlns="' . $this->xmlSchema() . '">' . "\n"; @@ -450,8 +490,8 @@ class GenerateSitemap extends Maintenance { /** * Return the XML for a single sitemap indexfile entry * - * @param $filename String: the filename of the sitemap file - * @return String + * @param string $filename The filename of the sitemap file + * @return string */ function indexEntry( $filename ) { return @@ -464,7 +504,7 @@ class GenerateSitemap extends Maintenance { /** * Return the XML required to close a sitemap index file * - * @return String + * @return string */ function closeIndex() { return "</sitemapindex>\n"; @@ -473,7 +513,7 @@ class GenerateSitemap extends Maintenance { /** * Return the XML required to open a sitemap file * - * @return String + * @return string */ function openFile() { return $this->xmlHead() . '<urlset xmlns="' . $this->xmlSchema() . '">' . "\n"; @@ -482,10 +522,10 @@ class GenerateSitemap extends Maintenance { /** * Return the XML for a single sitemap entry * - * @param $url String: an RFC 2396 compliant URL - * @param $date String: a ISO 8601 date - * @param $priority String: a priority indicator, 0.0 - 1.0 inclusive with a 0.1 stepsize - * @return String + * @param string $url An RFC 2396 compliant URL + * @param string $date A ISO 8601 date + * @param string $priority A priority indicator, 0.0 - 1.0 inclusive with a 0.1 stepsize + * @return string */ function fileEntry( $url, $date, $priority ) { return @@ -500,7 +540,7 @@ class GenerateSitemap extends Maintenance { /** * Return the XML required to close sitemap file * - * @return String + * @return string */ function closeFile() { return "</urlset>\n"; @@ -508,6 +548,8 @@ class GenerateSitemap extends Maintenance { /** * Populate $this->limit + * + * @param int $namespace */ function generateLimit( $namespace ) { // bug 17961: make a title with the longest possible URL in this namespace @@ -515,7 +557,11 @@ class GenerateSitemap extends Maintenance { $this->limit = array( strlen( $this->openFile() ), - strlen( $this->fileEntry( $title->getCanonicalURL(), wfTimestamp( TS_ISO_8601, wfTimestamp() ), $this->priority( $namespace ) ) ), + strlen( $this->fileEntry( + $title->getCanonicalURL(), + wfTimestamp( TS_ISO_8601, wfTimestamp() ), + $this->priority( $namespace ) + ) ), strlen( $this->closeFile() ) ); } diff --git a/maintenance/getConfiguration.php b/maintenance/getConfiguration.php index 5a5eb587..d5f68346 100644 --- a/maintenance/getConfiguration.php +++ b/maintenance/getConfiguration.php @@ -63,7 +63,7 @@ class GetConfiguration extends Maintenance { $format = strtolower( $this->getOption( 'format', 'PHP' ) ); $validFormat = in_array( $format, self::$outFormats ); - if ( ! $validFormat ) { + if ( !$validFormat ) { $this->error( "--format set to an unrecognized format", 0 ); $error_out = true; } @@ -87,11 +87,11 @@ class GetConfiguration extends Maintenance { public function finalSetup() { parent::finalSetup(); - $this->regex = $this->getOption( 'regex' ) ? : $this->getOption( 'iregex' ); + $this->regex = $this->getOption( 'regex' ) ?: $this->getOption( 'iregex' ); if ( $this->regex ) { $this->regex = '/' . $this->regex . '/'; if ( $this->hasOption( 'iregex' ) ) { - $this->regex .= 'i'; # case insensitive regex + $this->regex .= 'i'; # case insensitive regex } } @@ -115,7 +115,7 @@ class GetConfiguration extends Maintenance { $res = array(); # Sane default: dump any wg / wmg variable - if ( ! $this->regex && ! $this->getOption( 'settings' ) ) { + if ( !$this->regex && !$this->getOption( 'settings' ) ) { $this->regex = '/^wm?g/'; } @@ -165,7 +165,7 @@ class GetConfiguration extends Maintenance { protected function formatVarDump( $res ) { $ret = ''; foreach ( $res as $key => $value ) { - ob_start(); # intercept var_dump() output + ob_start(); # intercept var_dump() output print "\${$key} = "; var_dump( $value ); # grab var_dump() output and discard it from the output buffer @@ -182,10 +182,12 @@ class GetConfiguration extends Maintenance { return false; } } + return true; } elseif ( is_scalar( $value ) ) { return true; } + return false; } } diff --git a/maintenance/getSlaveServer.php b/maintenance/getSlaveServer.php index d618825f..68c19439 100644 --- a/maintenance/getSlaveServer.php +++ b/maintenance/getSlaveServer.php @@ -34,6 +34,7 @@ class GetSlaveServer extends Maintenance { $this->addOption( "group", "Query group to check specifically" ); $this->mDescription = "Report the hostname of a slave server"; } + public function execute() { global $wgAllDBsAreLocalhost; if ( $wgAllDBsAreLocalhost ) { diff --git a/maintenance/getText.php b/maintenance/getText.php index 9c4bdfb8..7d7c1cc4 100644 --- a/maintenance/getText.php +++ b/maintenance/getText.php @@ -52,7 +52,10 @@ class GetTextMaint extends Maintenance { $titleText = $title->getPrefixedText(); $this->error( "Page $titleText does not exist.\n", true ); } - $content = $rev->getContent( $this->hasOption( 'show-private' ) ? Revision::RAW : Revision::FOR_PUBLIC ); + $content = $rev->getContent( $this->hasOption( 'show-private' ) + ? Revision::RAW + : Revision::FOR_PUBLIC ); + if ( $content === false ) { $titleText = $title->getPrefixedText(); $this->error( "Couldn't extract the text from $titleText.\n", true ); diff --git a/maintenance/importDump.php b/maintenance/importDump.php index 1f47cf12..1f75bccf 100644 --- a/maintenance/importDump.php +++ b/maintenance/importDump.php @@ -3,7 +3,7 @@ * Import XML dump files into the current wiki. * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,8 +42,12 @@ class BackupReader extends Maintenance { function __construct() { parent::__construct(); - $gz = in_array( 'compress.zlib', stream_get_wrappers() ) ? 'ok' : '(disabled; requires PHP zlib module)'; - $bz2 = in_array( 'compress.bzip2', stream_get_wrappers() ) ? 'ok' : '(disabled; requires PHP bzip2 module)'; + $gz = in_array( 'compress.zlib', stream_get_wrappers() ) + ? 'ok' + : '(disabled; requires PHP zlib module)'; + $bz2 = in_array( 'compress.bzip2', stream_get_wrappers() ) + ? 'ok' + : '(disabled; requires PHP bzip2 module)'; $this->mDescription = <<<TEXT This script reads pages from an XML file as produced from Special:Export or @@ -56,7 +60,7 @@ Compressed XML files may be read directly: Note that for very large data sets, importDump.php may be slow; there are alternate methods which can be much faster for full site restoration: -<http://www.mediawiki.org/wiki/Manual:Importing_XML_dumps> +<https://www.mediawiki.org/wiki/Manual:Importing_XML_dumps> TEXT; $this->stderr = fopen( "php://stderr", "wt" ); $this->addOption( 'report', @@ -67,7 +71,10 @@ TEXT; $this->addOption( 'dry-run', 'Parse dump without actually importing pages' ); $this->addOption( 'debug', 'Output extra verbose debug information' ); $this->addOption( 'uploads', 'Process file upload data if included (experimental)' ); - $this->addOption( 'no-updates', 'Disable link table updates. Is faster but leaves the wiki in an inconsistent state' ); + $this->addOption( + 'no-updates', + 'Disable link table updates. Is faster but leaves the wiki in an inconsistent state' + ); $this->addOption( 'image-base-path', 'Import files from a specified path', false, true ); $this->addArg( 'file', 'Dump file to import [else use stdin]', false ); } @@ -104,6 +111,7 @@ TEXT; function setNsfilter( array $namespaces ) { if ( count( $namespaces ) == 0 ) { $this->nsFilter = false; + return; } $this->nsFilter = array_unique( array_map( array( $this, 'getNsIndex' ), $namespaces ) ); @@ -122,7 +130,7 @@ TEXT; } /** - * @param $obj Title|Revision + * @param Title|Revision $obj * @return bool */ private function skippedNamespace( $obj ) { @@ -133,9 +141,9 @@ TEXT; } elseif ( $obj instanceof WikiRevision ) { $ns = $obj->title->getNamespace(); } else { - echo wfBacktrace(); - $this->error( "Cannot get namespace of object in " . __METHOD__, true ); + throw new MWException( "Cannot get namespace of object in " . __METHOD__ ); } + return is_array( $this->nsFilter ) && !in_array( $ns, $this->nsFilter ); } @@ -144,13 +152,13 @@ TEXT; } /** - * @param $rev Revision - * @return mixed + * @param Revision $rev */ function handleRevision( $rev ) { $title = $rev->getTitle(); if ( !$title ) { $this->progress( "Got bogus revision with null title!" ); + return; } @@ -167,13 +175,13 @@ TEXT; } /** - * @param $revision Revision + * @param Revision $revision * @return bool */ function handleUpload( $revision ) { if ( $this->uploads ) { if ( $this->skippedNamespace( $revision ) ) { - return; + return false; } $this->uploadCount++; // $this->report(); @@ -183,9 +191,12 @@ TEXT; // bluuuh hack // call_user_func( $this->uploadCallback, $revision ); $dbw = wfGetDB( DB_MASTER ); + return $dbw->deadlockLoop( array( $revision, 'importUpload' ) ); } } + + return false; } function handleLogItem( $rev ) { @@ -242,6 +253,7 @@ TEXT; } $file = fopen( $filename, 'rt' ); + return $this->importFromHandle( $file ); } @@ -250,6 +262,7 @@ TEXT; if ( self::posix_isatty( $file ) ) { $this->maybeHelp( true ); } + return $this->importFromHandle( $file ); } diff --git a/maintenance/importImages.inc b/maintenance/importImages.inc index 5ae6d6be..b803e3da 100644 --- a/maintenance/importImages.inc +++ b/maintenance/importImages.inc @@ -26,10 +26,10 @@ /** * Search a directory for files with one of a set of extensions * - * @param $dir string Path to directory to search - * @param $exts Array of extensions to search for - * @param $recurse Bool Search subdirectories recursively - * @return mixed Array of filenames on success, or false on failure + * @param string $dir Path to directory to search + * @param array $exts Array of extensions to search for + * @param bool $recurse Search subdirectories recursively + * @return array|bool Array of filenames on success, or false on failure */ function findFiles( $dir, $exts, $recurse = false ) { if ( is_dir( $dir ) ) { @@ -46,6 +46,7 @@ function findFiles( $dir, $exts, $recurse = false ) { $files = array_merge( $files, findFiles( $dir . '/' . $file, $exts, true ) ); } } + return $files; } else { return array(); @@ -58,14 +59,15 @@ function findFiles( $dir, $exts, $recurse = false ) { /** * Split a filename into filename and extension * - * @param $filename string Filename + * @param string $filename Filename * @return array */ function splitFilename( $filename ) { $parts = explode( '.', $filename ); - $ext = $parts[ count( $parts ) - 1 ]; - unset( $parts[ count( $parts ) - 1 ] ); + $ext = $parts[count( $parts ) - 1]; + unset( $parts[count( $parts ) - 1] ); $fname = implode( '.', $parts ); + return array( $fname, $ext ); } @@ -78,10 +80,10 @@ function splitFilename( $filename ) { * files for acme.foo.bar and the extension ".txt". With $maxStrip = 2, * acme.txt would also be acceptable. * - * @param $file string base path - * @param $auxExtension string the extension to be appended to the base path - * @param $maxStrip int the maximum number of extensions to strip from the base path (default: 1) - * @return string or false + * @param string $file Base path + * @param string $auxExtension The extension to be appended to the base path + * @param int $maxStrip The maximum number of extensions to strip from the base path (default: 1) + * @return string|bool */ function findAuxFile( $file, $auxExtension, $maxStrip = 1 ) { if ( strpos( $auxExtension, '.' ) !== 0 ) { @@ -110,9 +112,11 @@ function findAuxFile( $file, $auxExtension, $maxStrip = 1 ) { return false; } -# FIXME: Access the api in a saner way and performing just one query (preferably batching files too). +# @todo FIXME: Access the api in a saner way and performing just one query +# (preferably batching files too). function getFileCommentFromSourceWiki( $wiki_host, $file ) { - $url = $wiki_host . '/api.php?action=query&format=xml&titles=File:' . rawurlencode( $file ) . '&prop=imageinfo&&iiprop=comment'; + $url = $wiki_host . '/api.php?action=query&format=xml&titles=File:' + . rawurlencode( $file ) . '&prop=imageinfo&&iiprop=comment'; $body = Http::get( $url ); if ( preg_match( '#<ii comment="([^"]*)" />#', $body, $matches ) == 0 ) { return false; @@ -122,7 +126,8 @@ function getFileCommentFromSourceWiki( $wiki_host, $file ) { } function getFileUserFromSourceWiki( $wiki_host, $file ) { - $url = $wiki_host . '/api.php?action=query&format=xml&titles=File:' . rawurlencode( $file ) . '&prop=imageinfo&&iiprop=user'; + $url = $wiki_host . '/api.php?action=query&format=xml&titles=File:' + . rawurlencode( $file ) . '&prop=imageinfo&&iiprop=user'; $body = Http::get( $url ); if ( preg_match( '#<ii user="([^"]*)" />#', $body, $matches ) == 0 ) { return false; diff --git a/maintenance/importImages.php b/maintenance/importImages.php index 54fd4e2d..ae70441f 100644 --- a/maintenance/importImages.php +++ b/maintenance/importImages.php @@ -4,7 +4,8 @@ * using the web-based interface. * * "Smart import" additions: - * - aim: preserve the essential metadata (user, description) when importing medias from an existing wiki + * - aim: preserve the essential metadata (user, description) when importing media + * files from an existing wiki. * - process: * - interface with the source wiki, don't use bare files only (see --source-wiki-url). * - fetch metadata from source wiki for each file to import. @@ -87,16 +88,24 @@ if ( isset( $options['check-userblock'] ) ) { } # Get --from -$from = @$options['from']; +wfSuppressWarnings(); +$from = $options['from']; +wfRestoreWarnings(); # Get sleep time. -$sleep = @$options['sleep']; +wfSuppressWarnings(); +$sleep = $options['sleep']; +wfRestoreWarnings(); + if ( $sleep ) { $sleep = (int)$sleep; } # Get limit number -$limit = @$options['limit']; +wfSuppressWarnings(); +$limit = $options['limit']; +wfRestoreWarnings(); + if ( $limit ) { $limit = (int)$limit; } @@ -167,7 +176,8 @@ if ( $count > 0 ) { } else { if ( isset( $options['skip-dupes'] ) ) { $repo = $image->getRepo(); - $sha1 = File::sha1Base36( $file ); # XXX: we end up calculating this again when actually uploading. that sucks. + # XXX: we end up calculating this again when actually uploading. that sucks. + $sha1 = FSFile::getSha1Base36FromPath( $file ); $dupes = $repo->findBySha1( $sha1 ); @@ -210,7 +220,8 @@ if ( $count > 0 ) { if ( $commentExt ) { $f = findAuxFile( $file, $commentExt ); if ( !$f ) { - echo " No comment file with extension {$commentExt} found for {$file}, using default comment. "; + echo " No comment file with extension {$commentExt} found " + . "for {$file}, using default comment. "; } else { $commentText = file_get_contents( $f ); if ( !$commentText ) { @@ -254,7 +265,13 @@ if ( $count > 0 ) { if ( isset( $options['dry'] ) ) { echo "done.\n"; - } elseif ( $image->recordUpload2( $archive->value, $summary, $commentText, $props, $timestamp ) ) { + } elseif ( $image->recordUpload2( + $archive->value, + $summary, + $commentText, + $props, + $timestamp + ) ) { # We're done! echo "done.\n"; @@ -273,25 +290,24 @@ if ( $count > 0 ) { } if ( $doProtect ) { - # Protect the file - echo "\nWaiting for slaves...\n"; - // Wait for slaves. - sleep( 2.0 ); # Why this sleep? - wfWaitForSlaves(); - - echo "\nSetting image restrictions ... "; - - $cascade = false; - $restrictions = array(); - foreach ( $title->getRestrictionTypes() as $type ) { - $restrictions[$type] = $protectLevel; - } + # Protect the file + echo "\nWaiting for slaves...\n"; + // Wait for slaves. + sleep( 2.0 ); # Why this sleep? + wfWaitForSlaves(); + + echo "\nSetting image restrictions ... "; + + $cascade = false; + $restrictions = array(); + foreach ( $title->getRestrictionTypes() as $type ) { + $restrictions[$type] = $protectLevel; + } - $page = WikiPage::factory( $title ); - $status = $page->doUpdateRestrictions( $restrictions, array(), $cascade, '', $user ); - echo ( $status->isOK() ? 'done' : 'failed' ) . "\n"; + $page = WikiPage::factory( $title ); + $status = $page->doUpdateRestrictions( $restrictions, array(), $cascade, '', $user ); + echo ( $status->isOK() ? 'done' : 'failed' ) . "\n"; } - } else { echo "failed. (at recordUpload stage)\n"; $svar = 'failed'; @@ -311,14 +327,21 @@ if ( $count > 0 ) { # Print out some statistics echo "\n"; - foreach ( array( 'count' => 'Found', 'limit' => 'Limit', 'ignored' => 'Ignored', - 'added' => 'Added', 'skipped' => 'Skipped', 'overwritten' => 'Overwritten', - 'failed' => 'Failed' ) as $var => $desc ) { + foreach ( + array( + 'count' => 'Found', + 'limit' => 'Limit', + 'ignored' => 'Ignored', + 'added' => 'Added', + 'skipped' => 'Skipped', + 'overwritten' => 'Overwritten', + 'failed' => 'Failed' + ) as $var => $desc + ) { if ( $$var > 0 ) { echo "{$desc}: {$$var}\n"; } } - } else { echo "No suitable files could be found for import.\n"; } @@ -337,28 +360,37 @@ USAGE: php importImages.php [options] <dir> <dir> : Path to the directory containing images to be imported Options: ---extensions=<exts> Comma-separated list of allowable extensions, defaults to \$wgFileExtensions ---overwrite Overwrite existing images with the same name (default is to skip them) ---limit=<num> Limit the number of images to process. Ignored or skipped images are not counted. ---from=<name> Ignore all files until the one with the given name. Useful for resuming - aborted imports. <name> should be the file's canonical database form. ---skip-dupes Skip images that were already uploaded under a different name (check SHA1) ---search-recursively Search recursively for files in subdirectories +--extensions=<exts> Comma-separated list of allowable extensions, defaults + to \$wgFileExtensions. +--overwrite Overwrite existing images with the same name (default + is to skip them). +--limit=<num> Limit the number of images to process. Ignored or + skipped images are not counted. +--from=<name> Ignore all files until the one with the given name. + Useful for resuming aborted imports. <name> should be + the file's canonical database form. +--skip-dupes Skip images that were already uploaded under a different + name (check SHA1). +--search-recursively Search recursively for files in subdirectories. --sleep=<sec> Sleep between files. Useful mostly for debugging. ---user=<username> Set username of uploader, default 'Maintenance script' +--user=<username> Set username of uploader, default 'Maintenance script'. --check-userblock Check if the user got blocked during import. --comment=<text> Set file description, default 'Importing file'. --comment-file=<file> Set description to the content of <file>. ---comment-ext=<ext> Causes the description for each file to be loaded from a file with the same name - but the extension <ext>. If a global description is also given, it is appended. ---license=<code> Use an optional license template ---dry Dry run, don't import anything ---protect=<protect> Specify the protect value (autoconfirmed,sysop) ---summary=<summary> Upload summary, description will be used if not provided ---timestamp=<timestamp> Override upload time/date, all MediaWiki timestamp formats are accepted ---unprotect Unprotects all uploaded images ---source-wiki-url If specified, take User and Comment data for each imported file from this URL. - For example, --source-wiki-url="http://en.wikipedia.org/" +--comment-ext=<ext> Causes the description for each file to be loaded from a + file with the same name, but the extension <ext>. If a + global description is also given, it is appended. +--license=<code> Use an optional license template. +--dry Dry run, don't import anything. +--protect=<protect> Specify the protect value (autoconfirmed,sysop). +--summary=<summary> Upload summary, description will be used if not + provided. +--timestamp=<timestamp> Override upload time/date, all MediaWiki timestamp + formats are accepted. +--unprotect Unprotects all uploaded images. +--source-wiki-url If specified, take User and Comment data for each + imported file from this URL. For example, + --source-wiki-url="http://en.wikipedia.org/." TEXT; exit( 1 ); diff --git a/maintenance/importSiteScripts.php b/maintenance/importSiteScripts.php index fd768b34..7705ec9c 100644 --- a/maintenance/importSiteScripts.php +++ b/maintenance/importSiteScripts.php @@ -65,13 +65,12 @@ class ImportSiteScripts extends Maintenance { $content = ContentHandler::makeContent( $text, $wikiPage->getTitle() ); $wikiPage->doEditContent( $content, "Importing from $url", 0, false, $user ); } - } protected function fetchScriptList() { $data = array( 'action' => 'query', - 'format' => 'php',//'json', + 'format' => 'php', //'json', 'list' => 'allpages', 'apnamespace' => '8', 'aplimit' => '500', @@ -100,7 +99,6 @@ class ImportSiteScripts extends Maintenance { } while ( isset( $result['query-continue'] ) ); return $pages; - } } diff --git a/maintenance/importTextFile.php b/maintenance/importTextFile.php deleted file mode 100644 index 4a07f2c7..00000000 --- a/maintenance/importTextFile.php +++ /dev/null @@ -1,110 +0,0 @@ -<?php -/** - * Create or edit pages using the contents of a text file. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup Maintenance - * @author Rob Church <robchur@gmail.com> - */ - -$options = array( 'help', 'nooverwrite', 'norc' ); -$optionsWithArgs = array( 'title', 'user', 'comment' ); -require_once __DIR__ . '/commandLine.inc'; -echo "Import Text File\n\n"; - -if ( count( $args ) < 1 || isset( $options['help'] ) ) { - showHelp(); -} else { - - $filename = $args[0]; - echo "Using {$filename}..."; - if ( is_file( $filename ) ) { - - $title = isset( $options['title'] ) ? $options['title'] : titleFromFilename( $filename ); - $title = Title::newFromURL( $title ); - - if ( is_object( $title ) ) { - - echo "\nUsing title '" . $title->getPrefixedText() . "'..."; - if ( !$title->exists() || !isset( $options['nooverwrite'] ) ) { - RequestContext::getMain()->setTitle( $title ); - - $text = file_get_contents( $filename ); - $user = isset( $options['user'] ) ? $options['user'] : 'Maintenance script'; - $user = User::newFromName( $user ); - - if ( is_object( $user ) ) { - - echo "\nUsing username '" . $user->getName() . "'..."; - $wgUser =& $user; - $comment = isset( $options['comment'] ) ? $options['comment'] : 'Importing text file'; - $flags = 0 | ( isset( $options['norc'] ) ? EDIT_SUPPRESS_RC : 0 ); - - echo "\nPerforming edit..."; - $page = WikiPage::factory( $title ); - $content = ContentHandler::makeContent( $text, $title ); - $page->doEditContent( $content, $comment, $flags, false, $user ); - echo "done.\n"; - - } else { - echo "invalid username.\n"; - } - - } else { - echo "page exists.\n"; - } - - } else { - echo "invalid title.\n"; - } - - } else { - echo "does not exist.\n"; - } - -} - -function titleFromFilename( $filename ) { - $parts = explode( '/', $filename ); - $parts = explode( '.', $parts[ count( $parts ) - 1 ] ); - return $parts[0]; -} - -function showHelp() { -print <<<EOF -USAGE: php importTextFile.php <options> <filename> - -<filename> : Path to the file containing page content to import - -Options: - ---title <title> - Title for the new page; default is to use the filename as a base ---user <user> - User to be associated with the edit ---comment <comment> - Edit summary ---nooverwrite - Don't overwrite existing content ---norc - Don't update recent changes ---help - Show this information - -EOF; -} diff --git a/maintenance/initEditCount.php b/maintenance/initEditCount.php index 4b046835..7c6e7d4f 100644 --- a/maintenance/initEditCount.php +++ b/maintenance/initEditCount.php @@ -47,7 +47,7 @@ in the load balancer, usually indicating a replication environment.' ); // Autodetect mode... $backgroundMode = wfGetLB()->getServerCount() > 1 || - ( $dbw instanceof DatabaseMysql && version_compare( $dbver, '4.1' ) < 0 ); + ( $dbw instanceof DatabaseMysql ); if ( $this->hasOption( 'background' ) ) { $backgroundMode = true; diff --git a/maintenance/initSiteStats.php b/maintenance/initSiteStats.php index 92268b3e..49e0e9d7 100644 --- a/maintenance/initSiteStats.php +++ b/maintenance/initSiteStats.php @@ -34,7 +34,10 @@ class InitSiteStats extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Re-initialise the site statistics tables"; - $this->addOption( 'update', 'Update the existing statistics (preserves the ss_total_views field)' ); + $this->addOption( + 'update', + 'Update the existing statistics (preserves the ss_total_views field)' + ); $this->addOption( 'noviews', "Don't update the page view counter" ); $this->addOption( 'active', 'Also update active users count' ); $this->addOption( 'use-master', 'Count using the master database' ); @@ -66,21 +69,19 @@ class InitSiteStats extends Maintenance { $this->output( "{$views}\n" ); } + if ( $this->hasOption( 'update' ) ) { + $this->output( "\nUpdating site statistics..." ); + $counter->refresh(); + $this->output( "done.\n" ); + } + if ( $this->hasOption( 'active' ) ) { - $this->output( "Counting active users..." ); + $this->output( "\nCounting and updating active users..." ); $active = SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) ); $this->output( "{$active}\n" ); } - $this->output( "\nUpdating site statistics..." ); - - if ( $this->hasOption( 'update' ) ) { - $counter->update(); - } else { - $counter->refresh(); - } - - $this->output( "done.\n" ); + $this->output( "\nDone.\n" ); } } diff --git a/maintenance/install.php b/maintenance/install.php index d118747a..b948b674 100644 --- a/maintenance/install.php +++ b/maintenance/install.php @@ -21,19 +21,20 @@ * @ingroup Maintenance */ -if ( !function_exists( 'version_compare' ) || ( version_compare( phpversion(), '5.3.2' ) < 0 ) ) { - require_once dirname( __FILE__ ) . '/../includes/PHPVersionError.php'; - wfPHPVersionError( 'cli' ); -} +// Checking for old versions of PHP is done in Maintenance.php +// We need to use dirname( __FILE__ ) here cause __DIR__ is PHP5.3+ +require_once dirname( __FILE__ ) . '/Maintenance.php'; define( 'MW_CONFIG_CALLBACK', 'Installer::overrideConfig' ); define( 'MEDIAWIKI_INSTALL', true ); -require_once dirname( __DIR__ ) . "/maintenance/Maintenance.php"; - /** * Maintenance script to install and configure MediaWiki * + * Default values for the options are defined in DefaultSettings.php + * (see the mapping in CliInstaller.php) + * Default for --dbpath (SQLite-specific) is defined in SqliteInstaller::getGlobalDefaults + * * @ingroup Maintenance */ class CommandLineInstaller extends Maintenance { @@ -41,13 +42,26 @@ class CommandLineInstaller extends Maintenance { parent::__construct(); global $IP; - $this->addArg( 'name', 'The name of the wiki', true ); + $this->addDescription( "CLI-based MediaWiki installation and configuration.\n" . + "Defaut options are indicated in parenthesis." ); + + $this->addArg( 'name', 'The name of the wiki (MediaWiki)', false ); - $this->addArg( 'admin', 'The username of the wiki administrator (WikiSysop)', true ); + $this->addArg( 'admin', 'The username of the wiki administrator.' ); $this->addOption( 'pass', 'The password for the wiki administrator.', false, true ); - $this->addOption( 'passfile', 'An alternative way to provide pass option, as the contents of this file', false, true ); + $this->addOption( + 'passfile', + 'An alternative way to provide pass option, as the contents of this file', + false, + true + ); /* $this->addOption( 'email', 'The email for the wiki administrator', false, true ); */ - $this->addOption( 'scriptpath', 'The relative path of the wiki in the web server (/wiki)', false, true ); + $this->addOption( + 'scriptpath', + 'The relative path of the wiki in the web server (/wiki)', + false, + true + ); $this->addOption( 'lang', 'The language to use (en)', false, true ); /* $this->addOption( 'cont-lang', 'The content language (en)', false, true ); */ @@ -56,32 +70,42 @@ class CommandLineInstaller extends Maintenance { $this->addOption( 'dbserver', 'The database host (localhost)', false, true ); $this->addOption( 'dbport', 'The database port; only for PostgreSQL (5432)', false, true ); $this->addOption( 'dbname', 'The database name (my_wiki)', false, true ); - $this->addOption( 'dbpath', 'The path for the SQLite DB (/var/data)', false, true ); + $this->addOption( 'dbpath', 'The path for the SQLite DB ($IP/data)', false, true ); $this->addOption( 'dbprefix', 'Optional database table name prefix', false, true ); $this->addOption( 'installdbuser', 'The user to use for installing (root)', false, true ); - $this->addOption( 'installdbpass', 'The pasword for the DB user to install as.', false, true ); + $this->addOption( 'installdbpass', 'The password for the DB user to install as.', false, true ); $this->addOption( 'dbuser', 'The user to use for normal operations (wikiuser)', false, true ); - $this->addOption( 'dbpass', 'The pasword for the DB user for normal operations', false, true ); - $this->addOption( 'dbpassfile', 'An alternative way to provide dbpass option, as the contents of this file', false, true ); - $this->addOption( 'confpath', "Path to write LocalSettings.php to, default $IP", false, true ); - /* $this->addOption( 'dbschema', 'The schema for the MediaWiki DB in pg (mediawiki)', false, true ); */ - /* $this->addOption( 'namespace', 'The project namespace (same as the name)', false, true ); */ + $this->addOption( 'dbpass', 'The password for the DB user for normal operations', false, true ); + $this->addOption( + 'dbpassfile', + 'An alternative way to provide dbpass option, as the contents of this file', + false, + true + ); + $this->addOption( 'confpath', "Path to write LocalSettings.php to ($IP)", false, true ); + $this->addOption( 'dbschema', 'The schema for the MediaWiki DB in ' + . 'PostgreSQL/Microsoft SQL Server (mediawiki)', false, true ); + /* + $this->addOption( 'namespace', 'The project namespace (same as the "name" argument)', + false, true ); + */ $this->addOption( 'env-checks', "Run environment checks only, don't change anything" ); } function execute() { - global $IP, $wgTitle; - $siteName = isset( $this->mArgs[0] ) ? $this->mArgs[0] : "Don't care"; // Will not be set if used with --env-checks - $adminName = isset( $this->mArgs[1] ) ? $this->mArgs[1] : null; - $wgTitle = Title::newFromText( 'Installer script' ); - - $dbpassfile = $this->getOption( 'dbpassfile', false ); - if ( $dbpassfile !== false ) { - if ( $this->getOption( 'dbpass', false ) !== false ) { - $this->error( 'WARNING: You provide the options "dbpass" and "dbpassfile". The content of "dbpassfile" overwrites "dbpass".' ); + global $IP; + + $siteName = $this->getArg( 0, 'MediaWiki' ); // Will not be set if used with --env-checks + $adminName = $this->getArg( 1 ); + + $dbpassfile = $this->getOption( 'dbpassfile' ); + if ( $dbpassfile !== null ) { + if ( $this->getOption( 'dbpass' ) !== null ) { + $this->error( 'WARNING: You have provided the options "dbpass" and "dbpassfile". ' + . 'The content of "dbpassfile" overrides "dbpass".' ); } wfSuppressWarnings(); - $dbpass = file_get_contents( $dbpassfile ); + $dbpass = file_get_contents( $dbpassfile ); // returns false on failure wfRestoreWarnings(); if ( $dbpass === false ) { $this->error( "Couldn't open $dbpassfile", true ); @@ -89,30 +113,31 @@ class CommandLineInstaller extends Maintenance { $this->mOptions['dbpass'] = trim( $dbpass, "\r\n" ); } - $passfile = $this->getOption( 'passfile', false ); - if ( $passfile !== false ) { - if ( $this->getOption( 'pass', false ) !== false ) { - $this->error( 'WARNING: You provide the options "pass" and "passfile". The content of "passfile" overwrites "pass".' ); + $passfile = $this->getOption( 'passfile' ); + if ( $passfile !== null ) { + if ( $this->getOption( 'pass' ) !== null ) { + $this->error( 'WARNING: You have provided the options "pass" and "passfile". ' + . 'The content of "passfile" overrides "pass".' ); } wfSuppressWarnings(); - $pass = file_get_contents( $passfile ); + $pass = file_get_contents( $passfile ); // returns false on failure wfRestoreWarnings(); if ( $pass === false ) { $this->error( "Couldn't open $passfile", true ); } - $this->mOptions['pass'] = str_replace( array( "\n", "\r" ), "", $pass ); - } elseif ( $this->getOption( 'pass', false ) === false ) { + $this->mOptions['pass'] = trim( $pass, "\r\n" ); + } elseif ( $this->getOption( 'pass' ) === null ) { $this->error( 'You need to provide the option "pass" or "passfile"', true ); } - $installer = - InstallerOverrides::getCliInstaller( $siteName, $adminName, $this->mOptions ); + $installer = InstallerOverrides::getCliInstaller( $siteName, $adminName, $this->mOptions ); $status = $installer->doEnvironmentChecks(); if ( $status->isGood() ) { $installer->showMessage( 'config-env-good' ); } else { $installer->showStatusMessage( $status ); + return; } if ( !$this->hasOption( 'env-checks' ) ) { @@ -128,6 +153,6 @@ class CommandLineInstaller extends Maintenance { } } -$maintClass = "CommandLineInstaller"; +$maintClass = 'CommandLineInstaller'; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/interwiki.list b/maintenance/interwiki.list index 179fa5c6..0660e55f 100644 --- a/maintenance/interwiki.list +++ b/maintenance/interwiki.list @@ -1,98 +1,77 @@ # Based more or less on the public interwiki map from MeatballWiki # Default interwiki prefixes... -acronym|http://www.acronymfinder.com/af-query.asp?String=exact&Acronym=$1|0 +acronym|http://www.acronymfinder.com/~/search/af.aspx?string=exact&Acronym=$1|0 advogato|http://www.advogato.org/$1|0 -annotationwiki|http://www.seedwiki.com/page.cfm?wikiid=368&doc=$1|0 arxiv|http://www.arxiv.org/abs/$1|0 c2find|http://c2.com/cgi/wiki?FindPage&value=$1|0 cache|http://www.google.com/search?q=cache:$1|0 -commons|http://commons.wikimedia.org/wiki/$1|0 -corpknowpedia|http://corpknowpedia.org/wiki/index.php/$1|0 +commons|https://commons.wikimedia.org/wiki/$1|0 dictionary|http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query=$1|0 -disinfopedia|http://www.disinfopedia.org/wiki.phtml?title=$1|0 -docbook|http://wiki.docbook.org/topic/$1|0 +docbook|http://wiki.docbook.org/$1|0 doi|http://dx.doi.org/$1|0 -drumcorpswiki|http://www.drumcorpswiki.com/index.php/$1|0 +drumcorpswiki|http://www.drumcorpswiki.com/$1|0 dwjwiki|http://www.suberic.net/cgi-bin/dwj/wiki.cgi?$1|0 elibre|http://enciclopedia.us.es/index.php/$1|0 emacswiki|http://www.emacswiki.org/cgi-bin/wiki.pl?$1|0 foldoc|http://foldoc.org/?$1|0 foxwiki|http://fox.wikis.com/wc.dll?Wiki~$1|0 freebsdman|http://www.FreeBSD.org/cgi/man.cgi?apropos=1&query=$1|0 -gej|http://www.esperanto.de/cgi-bin/aktivikio/wiki.pl?$1|0 +gej|http://www.esperanto.de/dej.malnova/aktivikio.pl?$1|0 gentoo-wiki|http://gentoo-wiki.com/$1|0 google|http://www.google.com/search?q=$1|0 googlegroups|http://groups.google.com/groups?q=$1|0 hammondwiki|http://www.dairiki.org/HammondWiki/$1|0 -hewikisource|http://he.wikisource.org/wiki/$1|1 -hrwiki|http://www.hrwiki.org/index.php/$1|0 -imdb|http://us.imdb.com/Title?$1|0 +hrwiki|http://www.hrwiki.org/wiki/$1|0 +imdb|http://www.imdb.com/find?q=$1&tt=on|0 jargonfile|http://sunir.org/apps/meta.pl?wiki=JargonFile&redirect=$1|0 -jspwiki|http://www.jspwiki.org/wiki/$1|0 -keiki|http://kei.ki/en/$1|0 kmwiki|http://kmwiki.wikispaces.com/$1|0 linuxwiki|http://linuxwiki.de/$1|0 lojban|http://www.lojban.org/tiki/tiki-index.php?page=$1|0 lqwiki|http://wiki.linuxquestions.org/wiki/$1|0 -lugkr|http://lug-kr.sourceforge.net/cgi-bin/lugwiki.pl?$1|0 -mathsongswiki|http://SeedWiki.com/page.cfm?wikiid=237&doc=$1|0 +lugkr|http://www.lug-kr.de/wiki/$1|0 meatball|http://www.usemod.com/cgi-bin/mb.pl?$1|0 -mediawikiwiki|http://www.mediawiki.org/wiki/$1|0 -mediazilla|https://bugzilla.wikimedia.org/$1|1 -memoryalpha|http://www.memory-alpha.org/en/index.php/$1|0 +mediawikiwiki|https://www.mediawiki.org/wiki/$1|0 +mediazilla|https://bugzilla.wikimedia.org/$1|0 +memoryalpha|http://en.memory-alpha.org/wiki/$1|0 metawiki|http://sunir.org/apps/meta.pl?$1|0 -metawikimedia|http://meta.wikimedia.org/wiki/$1|0 -moinmoin|http://purl.net/wiki/moin/$1|0 -mozillawiki|http://wiki.mozilla.org/index.php/$1|0 +metawikimedia|https://meta.wikimedia.org/wiki/$1|0 +mozillawiki|http://wiki.mozilla.org/$1|0 mw|http://www.mediawiki.org/wiki/$1|0 -oeis|http://www.research.att.com/cgi-bin/access.cgi/as/njas/sequences/eisA.cgi?Anum=$1|0 -openfacts|http://openfacts.berlios.de/index.phtml?title=$1|0 -openwiki|http://openwiki.com/?$1|0 -# patwiki|http://gauss.ffii.org/$1|0 # 2008-02-27: lots of spambots -pmeg|http://www.bertilow.com/pmeg/$1.php|0 +oeis|http://oeis.org/$1|0 +openwiki|http://openwiki.com/ow.asp?$1|0 ppr|http://c2.com/cgi/wiki?$1|0 pythoninfo|http://wiki.python.org/moin/$1|0 rfc|http://www.rfc-editor.org/rfc/rfc$1.txt|0 -s23wiki|http://is-root.de/wiki/index.php/$1|0 -seattlewiki|http://seattle.wikia.com/wiki/$1|0 -seattlewireless|http://seattlewireless.net/?$1|0 +s23wiki|http://s23.org/wiki/$1|0 +seattlewireless|http://seattlewireless.net/$1|0 senseislibrary|http://senseis.xmp.net/?$1|0 -# slashdot|http://slashdot.org/article.pl?sid=$1|0 # 2008-02-27: update me +shoutwiki|http://www.shoutwiki.com/wiki/$1|0 sourceforge|http://sourceforge.net/$1|0 +sourcewatch|http://www.sourcewatch.org/index.php?title=$1|0 squeak|http://wiki.squeak.org/squeak/$1|0 -susning|http://www.susning.nu/$1|0 -svgwiki|http://wiki.svg.org/$1|0 -tavi|http://tavi.sourceforge.net/$1|0 tejo|http://www.tejo.org/vikio/$1|0 tmbw|http://www.tmbw.net/wiki/$1|0 tmnet|http://www.technomanifestos.net/?$1|0 -tmwiki|http://www.EasyTopicMaps.com/?page=$1|0 theopedia|http://www.theopedia.com/$1|0 twiki|http://twiki.org/cgi-bin/view/$1|0 -uea|http://www.tejo.org/uea/$1|0 -unreal|http://wiki.beyondunreal.com/wiki/$1|0 +uea|http://uea.org/vikio/index.php/$1|0 +uncyclopedia|http://en.uncyclopedia.co/wiki/$1|0 +unreal|http://wiki.beyondunreal.com/$1|0 usemod|http://www.usemod.com/cgi-bin/wiki.pl?$1|0 -vinismo|http://vinismo.com/en/$1|0 webseitzwiki|http://webseitz.fluxent.com/wiki/$1|0 -why|http://clublet.com/c/c/why?$1|0 wiki|http://c2.com/cgi/wiki?$1|0 wikia|http://www.wikia.com/wiki/$1|0 -wikibooks|http://en.wikibooks.org/wiki/$1|1 -wikicities|http://www.wikia.com/wiki/$1|0 +wikibooks|https://en.wikibooks.org/wiki/$1|0 wikif1|http://www.wikif1.org/$1|0 wikihow|http://www.wikihow.com/$1|0 -wikinfo|http://www.wikinfo.org/index.php/$1|0 -# The following wik[it]* interwikis but wikitravel belong to the Wikimedia Family: -wikimedia|http://wikimediafoundation.org/wiki/$1|0 -wikinews|http://en.wikinews.org/wiki/$1|1 -wikipedia|http://en.wikipedia.org/wiki/$1|1 -wikiquote|http://en.wikiquote.org/wiki/$1|1 -wikisource|http://wikisource.org/wiki/$1|1 -wikispecies|http://species.wikimedia.org/wiki/$1|1 -wikitravel|http://wikitravel.org/en/$1|0 -wikiversity|http://en.wikiversity.org/wiki/$1|1 -wikt|http://en.wiktionary.org/wiki/$1|1 -wiktionary|http://en.wiktionary.org/wiki/$1|1 -wlug|http://www.wlug.org.nz/$1|0 -zwiki|http://zwiki.org/$1|0 -zzz wiki|http://wiki.zzz.ee/index.php/$1|0 +wikinfo|http://wikinfo.co/English/index.php/$1|0 +wikimedia|https://wikimediafoundation.org/wiki/$1|0 +wikinews|https://en.wikinews.org/wiki/$1|0 +wikipedia|https://en.wikipedia.org/wiki/$1|0 +wikiquote|https://en.wikiquote.org/wiki/$1|0 +wikisource|https://wikisource.org/wiki/$1|0 +wikispecies|https://species.wikimedia.org/wiki/$1|0 +wikiversity|https://en.wikiversity.org/wiki/$1|0 +wikivoyage|https://en.wikivoyage.org/wiki/$1|0 +wikt|https://en.wiktionary.org/wiki/$1|0 +wiktionary|https://en.wiktionary.org/wiki/$1|0 diff --git a/maintenance/interwiki.sql b/maintenance/interwiki.sql index 370460af..aad0cc3b 100644 --- a/maintenance/interwiki.sql +++ b/maintenance/interwiki.sql @@ -2,99 +2,79 @@ -- Default interwiki prefixes... REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES -('acronym','http://www.acronymfinder.com/af-query.asp?String=exact&Acronym=$1',0), +('acronym','http://www.acronymfinder.com/~/search/af.aspx?string=exact&Acronym=$1',0), ('advogato','http://www.advogato.org/$1',0), -('annotationwiki','http://www.seedwiki.com/page.cfm?wikiid=368&doc=$1',0), ('arxiv','http://www.arxiv.org/abs/$1',0), ('c2find','http://c2.com/cgi/wiki?FindPage&value=$1',0), ('cache','http://www.google.com/search?q=cache:$1',0), -('commons','http://commons.wikimedia.org/wiki/$1',0), -('corpknowpedia','http://corpknowpedia.org/wiki/index.php/$1',0), +('commons','https://commons.wikimedia.org/wiki/$1',0), ('dictionary','http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query=$1',0), -('disinfopedia','http://www.disinfopedia.org/wiki.phtml?title=$1',0), -('docbook','http://wiki.docbook.org/topic/$1',0), +('docbook','http://wiki.docbook.org/$1',0), ('doi','http://dx.doi.org/$1',0), -('drumcorpswiki','http://www.drumcorpswiki.com/index.php/$1',0), +('drumcorpswiki','http://www.drumcorpswiki.com/$1',0), ('dwjwiki','http://www.suberic.net/cgi-bin/dwj/wiki.cgi?$1',0), ('elibre','http://enciclopedia.us.es/index.php/$1',0), ('emacswiki','http://www.emacswiki.org/cgi-bin/wiki.pl?$1',0), ('foldoc','http://foldoc.org/?$1',0), ('foxwiki','http://fox.wikis.com/wc.dll?Wiki~$1',0), ('freebsdman','http://www.FreeBSD.org/cgi/man.cgi?apropos=1&query=$1',0), -('gej','http://www.esperanto.de/cgi-bin/aktivikio/wiki.pl?$1',0), +('gej','http://www.esperanto.de/dej.malnova/aktivikio.pl?$1',0), ('gentoo-wiki','http://gentoo-wiki.com/$1',0), ('google','http://www.google.com/search?q=$1',0), ('googlegroups','http://groups.google.com/groups?q=$1',0), ('hammondwiki','http://www.dairiki.org/HammondWiki/$1',0), -('hewikisource','http://he.wikisource.org/wiki/$1',1), -('hrwiki','http://www.hrwiki.org/index.php/$1',0), -('imdb','http://us.imdb.com/Title?$1',0), +('hrwiki','http://www.hrwiki.org/wiki/$1',0), +('imdb','http://www.imdb.com/find?q=$1&tt=on',0), ('jargonfile','http://sunir.org/apps/meta.pl?wiki=JargonFile&redirect=$1',0), -('jspwiki','http://www.jspwiki.org/wiki/$1',0), -('keiki','http://kei.ki/en/$1',0), ('kmwiki','http://kmwiki.wikispaces.com/$1',0), ('linuxwiki','http://linuxwiki.de/$1',0), ('lojban','http://www.lojban.org/tiki/tiki-index.php?page=$1',0), ('lqwiki','http://wiki.linuxquestions.org/wiki/$1',0), -('lugkr','http://lug-kr.sourceforge.net/cgi-bin/lugwiki.pl?$1',0), -('mathsongswiki','http://SeedWiki.com/page.cfm?wikiid=237&doc=$1',0), +('lugkr','http://www.lug-kr.de/wiki/$1',0), ('meatball','http://www.usemod.com/cgi-bin/mb.pl?$1',0), -('mediawikiwiki','http://www.mediawiki.org/wiki/$1',0), -('mediazilla','https://bugzilla.wikimedia.org/$1',1), -('memoryalpha','http://www.memory-alpha.org/en/index.php/$1',0), +('mediawikiwiki','https://www.mediawiki.org/wiki/$1',0), +('mediazilla','https://bugzilla.wikimedia.org/$1',0), +('memoryalpha','http://en.memory-alpha.org/wiki/$1',0), ('metawiki','http://sunir.org/apps/meta.pl?$1',0), -('metawikimedia','http://meta.wikimedia.org/wiki/$1',0), -('moinmoin','http://purl.net/wiki/moin/$1',0), -('mozillawiki','http://wiki.mozilla.org/index.php/$1',0), +('metawikimedia','https://meta.wikimedia.org/wiki/$1',0), +('mozillawiki','http://wiki.mozilla.org/$1',0), ('mw','http://www.mediawiki.org/wiki/$1',0), -('oeis','http://www.research.att.com/cgi-bin/access.cgi/as/njas/sequences/eisA.cgi?Anum=$1',0), -('openfacts','http://openfacts.berlios.de/index.phtml?title=$1',0), -('openwiki','http://openwiki.com/?$1',0), -('patwiki','http://gauss.ffii.org/$1',0), # 2008-02-27: lots of spambots -('pmeg','http://www.bertilow.com/pmeg/$1.php',0), +('oeis','http://oeis.org/$1',0), +('openwiki','http://openwiki.com/ow.asp?$1',0), ('ppr','http://c2.com/cgi/wiki?$1',0), ('pythoninfo','http://wiki.python.org/moin/$1',0), ('rfc','http://www.rfc-editor.org/rfc/rfc$1.txt',0), -('s23wiki','http://is-root.de/wiki/index.php/$1',0), -('seattlewiki','http://seattle.wikia.com/wiki/$1',0), -('seattlewireless','http://seattlewireless.net/?$1',0), +('s23wiki','http://s23.org/wiki/$1',0), +('seattlewireless','http://seattlewireless.net/$1',0), ('senseislibrary','http://senseis.xmp.net/?$1',0), -('slashdot','http://slashdot.org/article.pl?sid=$1',0), # 2008-02-27: update me +('shoutwiki','http://www.shoutwiki.com/wiki/$1',0), ('sourceforge','http://sourceforge.net/$1',0), +('sourcewatch','http://www.sourcewatch.org/index.php?title=$1',0), ('squeak','http://wiki.squeak.org/squeak/$1',0), -('susning','http://www.susning.nu/$1',0), -('svgwiki','http://wiki.svg.org/$1',0), -('tavi','http://tavi.sourceforge.net/$1',0), ('tejo','http://www.tejo.org/vikio/$1',0), ('tmbw','http://www.tmbw.net/wiki/$1',0), ('tmnet','http://www.technomanifestos.net/?$1',0), -('tmwiki','http://www.EasyTopicMaps.com/?page=$1',0), ('theopedia','http://www.theopedia.com/$1',0), ('twiki','http://twiki.org/cgi-bin/view/$1',0), -('uea','http://www.tejo.org/uea/$1',0), -('unreal','http://wiki.beyondunreal.com/wiki/$1',0), +('uea','http://uea.org/vikio/index.php/$1',0), +('uncyclopedia','http://en.uncyclopedia.co/wiki/$1',0), +('unreal','http://wiki.beyondunreal.com/$1',0), ('usemod','http://www.usemod.com/cgi-bin/wiki.pl?$1',0), -('vinismo','http://vinismo.com/en/$1',0), ('webseitzwiki','http://webseitz.fluxent.com/wiki/$1',0), -('why','http://clublet.com/c/c/why?$1',0), ('wiki','http://c2.com/cgi/wiki?$1',0), ('wikia','http://www.wikia.com/wiki/$1',0), -('wikibooks','http://en.wikibooks.org/wiki/$1',1), -('wikicities','http://www.wikia.com/wiki/$1',0), +('wikibooks','https://en.wikibooks.org/wiki/$1',0), ('wikif1','http://www.wikif1.org/$1',0), ('wikihow','http://www.wikihow.com/$1',0), -('wikinfo','http://www.wikinfo.org/index.php/$1',0), -# The following wik[it]* interwikis but wikitravel belong to the Wikimedia Family: -('wikimedia','http://wikimediafoundation.org/wiki/$1',0), -('wikinews','http://en.wikinews.org/wiki/$1',1), -('wikipedia','http://en.wikipedia.org/wiki/$1',1), -('wikiquote','http://en.wikiquote.org/wiki/$1',1), -('wikisource','http://wikisource.org/wiki/$1',1), -('wikispecies','http://species.wikimedia.org/wiki/$1',1), -('wikitravel','http://wikitravel.org/en/$1',0), -('wikiversity','http://en.wikiversity.org/wiki/$1',1), -('wikt','http://en.wiktionary.org/wiki/$1',1), -('wiktionary','http://en.wiktionary.org/wiki/$1',1), -('wlug','http://www.wlug.org.nz/$1',0), -('zwiki','http://zwiki.org/$1',0), -('zzz wiki','http://wiki.zzz.ee/index.php/$1',0); +('wikinfo','http://wikinfo.co/English/index.php/$1',0), +('wikimedia','https://wikimediafoundation.org/wiki/$1',0), +('wikinews','https://en.wikinews.org/wiki/$1',0), +('wikipedia','https://en.wikipedia.org/wiki/$1',0), +('wikiquote','https://en.wikiquote.org/wiki/$1',0), +('wikisource','https://wikisource.org/wiki/$1',0), +('wikispecies','https://species.wikimedia.org/wiki/$1',0), +('wikiversity','https://en.wikiversity.org/wiki/$1',0), +('wikivoyage','https://en.wikivoyage.org/wiki/$1',0), +('wikt','https://en.wiktionary.org/wiki/$1',0), +('wiktionary','https://en.wiktionary.org/wiki/$1',0) +; diff --git a/maintenance/jsduck/CustomTags.rb b/maintenance/jsduck/CustomTags.rb new file mode 100644 index 00000000..2aff9881 --- /dev/null +++ b/maintenance/jsduck/CustomTags.rb @@ -0,0 +1,116 @@ +# Custom tags for JSDuck 5.x +# See also: +# - https://github.com/senchalabs/jsduck/wiki/Tags +# - https://github.com/senchalabs/jsduck/wiki/Custom-tags +# - https://github.com/senchalabs/jsduck/wiki/Custom-tags/7f5c32e568eab9edc8e3365e935bcb836cb11f1d +require 'jsduck/tag/tag' + +class CommonTag < JsDuck::Tag::Tag + def initialize + @html_position = POS_DOC + 0.1 + @repeatable = true + end + + def parse_doc(scanner, position) + if @multiline + return { :tagname => @tagname, :doc => :multiline } + else + text = scanner.match(/.*$/) + return { :tagname => @tagname, :doc => text } + end + end + + def process_doc(context, tags, position) + context[@tagname] = tags + end + + def format(context, formatter) + context[@tagname].each do |tag| + tag[:doc] = formatter.format(tag[:doc]) + end + end +end + +class SourceTag < CommonTag + def initialize + @tagname = :source + @pattern = "source" + super + end + + def to_html(context) + context[@tagname].map do |source| + <<-EOHTML + <h3 class='pa'>Source</h3> + #{source[:doc]} + EOHTML + end.join + end +end + +class SeeTag < CommonTag + def initialize + @tagname = :see + @pattern = "see" + super + end + + def format(context, formatter) + position = context[:files][0] + context[@tagname].each do |tag| + tag[:doc] = '<li>' + render_long_see(tag[:doc], formatter, position) + '</li>' + end + end + + def to_html(context) + <<-EOHTML + <h3 class="pa">Related</h3> + <ul> + #{ context[@tagname].map {|tag| tag[:doc] }.join("\n") } + </ul> + EOHTML + end + + def render_long_see(tag, formatter, position) + if tag =~ /\A([^\s]+)( .*)?\Z/m + name = $1 + doc = $2 ? ': ' + $2 : '' + return formatter.format("{@link #{name}} #{doc}") + else + JsDuck::Logger.warn(nil, 'Unexpected @see argument: "'+tag+'"', position) + return tag + end + end +end + +class ContextTag < CommonTag + def initialize + @tagname = :context + @pattern = 'context' + super + end + + def format(context, formatter) + position = context[:files][0] + context[@tagname].each do |tag| + tag[:doc] = render_long_context(tag[:doc], formatter, position) + end + end + + def to_html(context) + <<-EOHTML + <h3 class="pa">Context</h3> + #{ context[@tagname].last[:doc] } + EOHTML + end + + def render_long_context(tag, formatter, position) + if tag =~ /\A([^\s]+)/m + name = $1 + return formatter.format("`context` : {@link #{name}}") + else + JsDuck::Logger.warn(nil, 'Unexpected @context argument: "'+tag+'"', position) + return tag + end + end +end diff --git a/maintenance/jsduck/MetaTags.rb b/maintenance/jsduck/MetaTags.rb deleted file mode 100644 index 83cc0884..00000000 --- a/maintenance/jsduck/MetaTags.rb +++ /dev/null @@ -1,69 +0,0 @@ -# See also: -# - https://github.com/senchalabs/jsduck/wiki/Tags -# - https://github.com/senchalabs/jsduck/wiki/Custom-tags -require 'jsduck/meta_tag' - -class SourceTag < JsDuck::MetaTag - def initialize - # This defines the name of the @tag - @name = 'source' - end - - # Generate HTML output for this tag. - # One can make use of the #format method to easily support - # Markdown and {@link} tags inside the contents of the tag. - # - # @param tags All matches of this tag on one class. - def to_html(tags) - '<h3 class="pa">Source</h3>' + tags.map {|tag| format(tag) }.join("\n") - end -end - -class ContextTag < JsDuck::MetaTag - def initialize - @name = 'context' - end - - # @param tags All matches of this tag on one class. - def to_html(tags) - return '<h3 class="pa">Context</h3>' + render_long_context(tags.last) - end - - def render_long_context(tag) - if tag =~ /\A([^\s]+)/m - name = $1 - return format("`this` : {@link #{name}}") - end - end -end - -class SeeTag < JsDuck::MetaTag - def initialize - @name = 'see' - @multiline = true - end - - # @param tags All matches of this tag on one class. - def to_html(tags) - doc = [] - doc << '<h3 class="pa">Related</h3>' - doc << [ - '<ul>', - tags.map {|tag| render_long_see(tag) }, - '</ul>', - ] - doc - end - - def render_long_see(tag) - if tag =~ /\A([^\s]+)( .*)?\Z/m - name = $1 - doc = $2 ? ': ' + $2 : '' - return [ - '<li>', - format("{@link #{name}} #{doc}"), - '</li>' - ] - end - end -end diff --git a/maintenance/jsduck/categories.json b/maintenance/jsduck/categories.json index f96902d8..d6163bde 100644 --- a/maintenance/jsduck/categories.json +++ b/maintenance/jsduck/categories.json @@ -9,7 +9,7 @@ "mw.Map", "mw.Message", "mw.loader", - "mw.log", + "mw.loader.store", "mw.html", "mw.html.Cdata", "mw.html.Raw", @@ -20,12 +20,13 @@ "name": "General", "classes": [ "mw.Title", - "mw.inspect", - "mw.inspect.reports", + "mw.Uri", "mw.notification", + "mw.Notification_", "mw.user", "mw.util", - "mw.plugin.*" + "mw.plugin.*", + "mw.cookie" ] }, { @@ -35,6 +36,42 @@ { "name": "API", "classes": ["mw.Api*"] + }, + { + "name": "Language", + "classes": [ + "mw.language*", + "mw.cldr", + "mw.jqueryMsg" + ] + }, + { + "name": "Page", + "classes": [ + "mw.page*" + ] + }, + { + "name": "Interfaces", + "classes": [ + "mw.Feedback" + ] + }, + { + "name": "Special", + "classes": [ + "mw.special*" + ] + }, + { + "name": "Development", + "classes": [ + "mw.log", + "mw.inspect", + "mw.inspect.reports", + "mw.Debug", + "mw.Debug.profile" + ] } ] }, @@ -43,7 +80,11 @@ "groups": [ { "name": "Plugins", - "classes": ["jQuery.plugin.*"] + "classes": [ + "jQuery.client", + "jQuery.colorUtil", + "jQuery.plugin.*" + ] } ] }, @@ -51,6 +92,10 @@ "name": "Upstream", "groups": [ { + "name": "OOJS", + "classes": ["OO", "OO.*"] + }, + { "name": "jQuery", "classes": ["jQuery", "jQuery.Event", "jQuery.Callbacks", "jQuery.Promise", "jQuery.Deferred", "jQuery.jqXHR", "QUnit"] }, diff --git a/maintenance/jsduck/config.json b/maintenance/jsduck/config.json index e6e0f658..e97f2923 100644 --- a/maintenance/jsduck/config.json +++ b/maintenance/jsduck/config.json @@ -1,27 +1,40 @@ { "--title": "MediaWiki core - Documentation", - "--footer": "Documentation for MediaWiki core. Generated on {DATE} by {JSDUCK} {VERSION}.", "--categories": "./categories.json", - "--meta-tags": "./MetaTags.rb", "--eg-iframe": "./eg-iframe.html", - "--warnings": ["-no_doc"], + "--tags": "./CustomTags.rb", + "--warnings": ["-nodoc(class,public)"], "--builtin-classes": true, + "--warnings-exit-nonzero": true, + "--external": "HTMLElement,HTMLDocument,Window,File", + "--footer": "Documentation for MediaWiki core. Generated on {DATE} by {JSDUCK} {VERSION}.", "--output": "../../docs/js", "--": [ "./external.js", - "../../resources/mediawiki/mediawiki.js", - "../../resources/mediawiki/mediawiki.log.js", - "../../resources/mediawiki/mediawiki.util.js", - "../../resources/mediawiki/mediawiki.Title.js", - "../../resources/mediawiki/mediawiki.inspect.js", - "../../resources/mediawiki/mediawiki.notify.js", - "../../resources/mediawiki/mediawiki.notification.js", - "../../resources/mediawiki/mediawiki.user.js", - "../../resources/mediawiki.action/mediawiki.action.edit.js", - "../../resources/mediawiki.action/mediawiki.action.view.postEdit.js", - "../../resources/mediawiki.page/mediawiki.page.startup.js", - "../../resources/mediawiki.api", - "../../resources/jquery/jquery.localize.js", - "../../resources/jquery/jquery.spinner.js" + "../../resources/src/mediawiki", + "../../resources/src/mediawiki.action", + "../../resources/src/mediawiki.api", + "../../resources/src/mediawiki.language", + "../../resources/src/mediawiki.page", + "../../resources/src/mediawiki.special", + "../../resources/src/jquery/jquery.accessKeyLabel.js", + "../../resources/src/jquery/jquery.arrowSteps.js", + "../../resources/src/jquery/jquery.autoEllipsis.js", + "../../resources/src/jquery/jquery.badge.js", + "../../resources/src/jquery/jquery.byteLength.js", + "../../resources/src/jquery/jquery.byteLimit.js", + "../../resources/src/jquery/jquery.checkboxShiftClick.js", + "../../resources/src/jquery/jquery.client.js", + "../../resources/src/jquery/jquery.colorUtil.js", + "../../resources/src/jquery/jquery.confirmable.js", + "../../resources/src/jquery/jquery.footHovzer.js", + "../../resources/src/jquery/jquery.getAttrs.js", + "../../resources/src/jquery/jquery.hidpi.js", + "../../resources/src/jquery/jquery.localize.js", + "../../resources/src/jquery/jquery.makeCollapsible.js", + "../../resources/src/jquery/jquery.spinner.js", + "../../resources/src/jquery/jquery.tabIndex.js", + "../../resources/lib/oojs", + "../../resources/lib/oojs-ui" ] } diff --git a/maintenance/jsduck/eg-iframe.html b/maintenance/jsduck/eg-iframe.html index 86eae4b6..7dc4afa8 100644 --- a/maintenance/jsduck/eg-iframe.html +++ b/maintenance/jsduck/eg-iframe.html @@ -1,88 +1,88 @@ <!DOCTYPE html> <html> <head> - <meta charset="utf-8"> - <title>MediaWiki Code Example</title> - <script src="modules/startup.js"></script> - <script> - function startUp() { - mw.config = new mw.Map(); - } - </script> - <script src="modules/jquery/jquery.js"></script> - <script src="modules/mediawiki/mediawiki.js"></script> - <style> - .mw-jsduck-log { - position: relative; - min-height: 3em; - margin-top: 2em; - background: #f7f7f7; - border: 1px solid #e4e4e4; - } + <meta charset="utf-8"> + <title>MediaWiki Code Example</title> + <script src="modules/startup.js"></script> + <script> + function startUp() { + mw.config = new mw.Map(); + } + </script> + <script src="modules/jquery/jquery.js"></script> + <script src="modules/mediawiki/mediawiki.js"></script> + <style> + .mw-jsduck-log { + position: relative; + min-height: 3em; + margin-top: 2em; + background: #f7f7f7; + border: 1px solid #e4e4e4; + } - .mw-jsduck-log::after { - position: absolute; - bottom: 100%; - right: -1px; - padding: 0.5em; - background: #fff; - border: 1px solid #e4e4e4; - border-bottom: 0; - border-radius: 0.5em 0.5em 0 0; - font: normal 0.5em sans-serif; - content: 'console'; - } + .mw-jsduck-log::after { + position: absolute; + bottom: 100%; + right: -1px; + padding: 0.5em; + background: #fff; + border: 1px solid #e4e4e4; + border-bottom: 0; + border-radius: 0.5em 0.5em 0 0; + font: normal 0.5em sans-serif; + content: 'console'; + } - .mw-jsduck-log-line { - padding: 0.2em 0.5em; - white-space: pre-wrap; - } + .mw-jsduck-log-line { + padding: 0.2em 0.5em; + white-space: pre-wrap; + } - .mw-jsduck-log-line:nth-child(odd) { - background: #fff; - } - </style> + .mw-jsduck-log-line:nth-child(odd) { + background: #fff; + } + </style> </head> <body> - <script> - /** - * Basic log console for the example iframe in documentation pages. - */ - ( function () { - var pre; - mw.log = function () { - var str, i, len, line; - if ( !pre ) { - pre = document.createElement( 'pre' ); - pre.className = 'mw-jsduck-log'; - document.body.appendChild( pre ); - } - str = []; - for ( i = 0, len = arguments.length; i < len; i++ ) { - str.push( String( arguments[ i ] ) ); - } - line = document.createElement( 'div' ); - line.className = 'mw-jsduck-log-line'; - line.appendChild( - document.createTextNode( str.join( ' , ' ) + '\n' ) - ); - pre.appendChild( line ); - }; - }() ); +<script> + /** + * Basic log console for the example iframe in documentation pages. + */ + ( function () { + var pre; + mw.log = function () { + var str, i, len, line; + if ( !pre ) { + pre = document.createElement( 'pre' ); + pre.className = 'mw-jsduck-log'; + document.body.appendChild( pre ); + } + str = []; + for ( i = 0, len = arguments.length; i < len; i++ ) { + str.push( String( arguments[ i ] ) ); + } + line = document.createElement( 'div' ); + line.className = 'mw-jsduck-log-line'; + line.appendChild( + document.createTextNode( str.join( ' , ' ) + '\n' ) + ); + pre.appendChild( line ); + }; + }() ); - /** - * Method called by jsduck to execute the example code. - */ - function loadInlineExample( code, options, callback ) { - try { - eval( code ); - callback && callback( true ); - } catch (e) { - mw.log( 'Uncaught exception: ' + e ); - callback && callback( false, e ); - throw e; - } - } - </script> + /** + * Method called by jsduck to execute the example code. + */ + function loadInlineExample( code, options, callback ) { + try { + eval( code ); + callback && callback( true ); + } catch ( e ) { + mw.log( 'Uncaught exception: ' + e ); + callback && callback( false, e ); + throw e; + } + } +</script> </body> </html> diff --git a/maintenance/lag.php b/maintenance/lag.php index 410bf756..52f8201a 100644 --- a/maintenance/lag.php +++ b/maintenance/lag.php @@ -39,7 +39,9 @@ class DatabaseLag extends Maintenance { if ( $this->hasOption( 'r' ) ) { $lb = wfGetLB(); echo 'time '; - for ( $i = 1; $i < $lb->getServerCount(); $i++ ) { + + $serverCount = $lb->getServerCount(); + for ( $i = 1; $i < $serverCount; $i++ ) { $hostname = $lb->getServerName( $i ); printf( "%-12s ", $hostname ); } diff --git a/maintenance/language/StatOutputs.php b/maintenance/language/StatOutputs.php index e9d8c86d..31ce7024 100644 --- a/maintenance/language/StatOutputs.php +++ b/maintenance/language/StatOutputs.php @@ -1,7 +1,4 @@ <?php -if ( !defined( 'MEDIAWIKI' ) ) { - die(); -} /** * Statistic output classes. * @@ -27,57 +24,78 @@ if ( !defined( 'MEDIAWIKI' ) ) { */ /** A general output object. Need to be overriden */ -class statsOutput { +class StatsOutput { function formatPercent( $subset, $total, $revert = false, $accuracy = 2 ) { - return @sprintf( '%.' . $accuracy . 'f%%', 100 * $subset / $total ); + wfSuppressWarnings(); + $return = sprintf( '%.' . $accuracy . 'f%%', 100 * $subset / $total ); + wfRestoreWarnings(); + + return $return; } # Override the following methods function heading() { } + function footer() { } + function blockstart() { } + function blockend() { } + function element( $in, $heading = false ) { } } /** Outputs WikiText */ -class wikiStatsOutput extends statsOutput { +class WikiStatsOutput extends StatsOutput { function heading() { global $wgDummyLanguageCodes; $version = SpecialVersion::getVersion( 'nodb' ); echo "'''Statistics are based on:''' <code>" . $version . "</code>\n\n"; - echo "'''Note:''' These statistics can be generated by running <code>php maintenance/language/transstat.php</code>.\n\n"; - echo "For additional information on specific languages (the message names, the actual problems, etc.), run <code>php maintenance/language/checkLanguage.php --lang=foo</code>.\n\n"; + echo "'''Note:''' These statistics can be generated by running " . + "<code>php maintenance/language/transstat.php</code>.\n\n"; + echo "For additional information on specific languages (the message names, the actual " . + "problems, etc.), run <code>php maintenance/language/checkLanguage.php --lang=foo</code>.\n\n"; echo 'English (en) is excluded because it is the default localization'; if ( is_array( $wgDummyLanguageCodes ) ) { $dummyCodes = array(); foreach ( $wgDummyLanguageCodes as $dummyCode => $correctCode ) { $dummyCodes[] = Language::fetchLanguageName( $dummyCode ) . ' (' . $dummyCode . ')'; } - echo ', as well as the following languages that are not intended for system message translations, usually because they redirect to other language codes: ' . implode( ', ', $dummyCodes ); + echo ', as well as the following languages that are not intended for ' . + 'system message translations, usually because they redirect to other ' . + 'language codes: ' . implode( ', ', $dummyCodes ); } echo ".\n\n"; # dot to end sentence - echo '{| class="sortable wikitable" border="2" style="background-color: #F9F9F9; border: 1px #AAAAAA solid; border-collapse: collapse; clear:both; width:100%;"' . "\n"; + echo '{| class="sortable wikitable" border="2" style="background-color: #F9F9F9; ' . + 'border: 1px #AAAAAA solid; border-collapse: collapse; clear:both; width:100%;"' . "\n"; } + function footer() { echo "|}\n"; } + function blockstart() { echo "|-\n"; } + function blockend() { echo ''; } + function element( $in, $heading = false ) { echo ( $heading ? '!' : '|' ) . "$in\n"; } + function formatPercent( $subset, $total, $revert = false, $accuracy = 2 ) { - $v = @round( 255 * $subset / $total ); + wfSuppressWarnings(); + $v = round( 255 * $subset / $total ); + wfRestoreWarnings(); + if ( $revert ) { # Weigh reverse with factor 20 so coloring takes effect more quickly as # this option is used solely for reporting 'bad' percentages. @@ -100,25 +118,28 @@ class wikiStatsOutput extends statsOutput { $color = $red . $green . $blue; $percent = parent::formatPercent( $subset, $total, $revert, $accuracy ); + return 'style="background-color:#' . $color . ';"|' . $percent; } } /** Output text. To be used on a terminal for example. */ -class textStatsOutput extends statsOutput { +class TextStatsOutput extends StatsOutput { function element( $in, $heading = false ) { echo $in . "\t"; } + function blockend() { echo "\n"; } } /** csv output. Some people love excel */ -class csvStatsOutput extends statsOutput { +class CsvStatsOutput extends StatsOutput { function element( $in, $heading = false ) { echo $in . ";"; } + function blockend() { echo "\n"; } diff --git a/maintenance/language/checkDupeMessages.php b/maintenance/language/checkDupeMessages.php index 381ddae1..5d482442 100644 --- a/maintenance/language/checkDupeMessages.php +++ b/maintenance/language/checkDupeMessages.php @@ -67,8 +67,7 @@ if ( $runTest ) { $messagesFileC = $messagesDir . 'Messages' . $langCodeFC . '.php'; if ( file_exists( $messagesFile ) && file_exists( $messagesFileC ) ) { $run = true; - } - else { + } else { echo "Messages file(s) could not be found.\nMake sure both files are exists.\n"; } } diff --git a/maintenance/language/checkLanguage.inc b/maintenance/language/checkLanguage.inc index 4b49ada3..990f2585 100644 --- a/maintenance/language/checkLanguage.inc +++ b/maintenance/language/checkLanguage.inc @@ -41,7 +41,7 @@ class CheckLanguageCLI { /** * Constructor. - * @param $options array Options for script. + * @param array $options Options for script. */ public function __construct( array $options ) { if ( isset( $options['help'] ) ) { @@ -89,7 +89,7 @@ class CheckLanguageCLI { $this->output = $options['output']; } - $this->L = new languages( $this->includeExif ); + $this->L = new Languages( $this->includeExif ); } /** @@ -118,7 +118,7 @@ class CheckLanguageCLI { /** * Get the checks that can easily be treated by non-speakers of the language. - * @return Array A list of the easy checks. + * @return array A list of the easy checks. */ protected function easyChecks() { return array( @@ -182,21 +182,25 @@ class CheckLanguageCLI { return array( 'untranslated' => '$1 message(s) of $2 are not translated to $3, but exist in en:', 'duplicate' => '$1 message(s) of $2 are translated the same in en and $3:', - 'obsolete' => '$1 message(s) of $2 do not exist in en or are in the ignore list, but exist in $3:', + 'obsolete' => + '$1 message(s) of $2 do not exist in en or are in the ignore list, but exist in $3:', 'variables' => '$1 message(s) of $2 in $3 don\'t match the variables used in en:', 'plural' => '$1 message(s) of $2 in $3 don\'t use {{plural}} while en uses:', 'empty' => '$1 message(s) of $2 in $3 are empty or -:', 'whitespace' => '$1 message(s) of $2 in $3 have trailing whitespace:', 'xhtml' => '$1 message(s) of $2 in $3 contain illegal XHTML:', - 'chars' => '$1 message(s) of $2 in $3 include hidden chars which should not be used in the messages:', + 'chars' => + '$1 message(s) of $2 in $3 include hidden chars which should not be used in the messages:', 'links' => '$1 message(s) of $2 in $3 have problematic link(s):', 'unbalanced' => '$1 message(s) of $2 in $3 have unbalanced {[]}:', 'namespace' => '$1 namespace name(s) of $2 are not translated to $3, but exist in en:', - 'projecttalk' => '$1 namespace name(s) and alias(es) in $3 are project talk namespaces without the parameter:', + 'projecttalk' => + '$1 namespace name(s) and alias(es) in $3 are project talk namespaces without the parameter:', 'magic' => '$1 magic word(s) of $2 are not translated to $3, but exist in en:', 'magic-old' => '$1 magic word(s) of $2 do not exist in en, but exist in $3:', 'magic-over' => '$1 magic word(s) of $2 in $3 do not contain the original en word(s):', - 'magic-case' => '$1 magic word(s) of $2 in $3 change the case-sensitivity of the original en word:', + 'magic-case' => + '$1 magic word(s) of $2 in $3 change the case-sensitivity of the original en word:', 'special' => '$1 special page alias(es) of $2 are not translated to $3, but exist in en:', 'special-old' => '$1 special page alias(es) of $2 do not exist in en, but exist in $3:', ); @@ -216,35 +220,49 @@ Parameters: --all: Check all customized languages. --level: Show the following display level (default: 2): * 0: Skip the checks (useful for checking syntax). - * 1: Show only the stub headers and number of wrong messages, without list of messages. - * 2: Show only the headers and the message keys, without the message values. - * 3: Show both the headers and the complete messages, with both keys and values. + * 1: Show only the stub headers and number of wrong messages, without + list of messages. + * 2: Show only the headers and the message keys, without the message + values. + * 3: Show both the headers and the complete messages, with both keys and + values. --links: Link the message values (default off). --prefix: prefix to add to links. - --wikilang: For the links, what is the content language of the wiki to display the output in (default en). - --noexif: Do not check for Exif messages (a bit hard and boring to translate), if you know - that they are currently not translated and want to focus on other problems (default off). + --wikilang: For the links, what is the content language of the wiki to + display the output in (default en). + --noexif: Do not check for Exif messages (a bit hard and boring to + translate), if you know what they are currently not translated and want + to focus on other problems (default off). --whitelist: Do only the following checks (form: code,code). --blacklist: Do not do the following checks (form: code,code). - --easy: Do only the easy checks, which can be treated by non-speakers of the language. - -Check codes (ideally, all of them should result 0; all the checks are executed by default (except language-specific check blacklists in checkLanguage.inc): - * untranslated: Messages which are required to translate, but are not translated. - * duplicate: Messages which translation equal to fallback - * obsolete: Messages which are untranslatable or do not exist, but are translated. - * variables: Messages without variables which should be used, or with variables which should not be used. + --easy: Do only the easy checks, which can be treated by non-speakers of + the language. + +Check codes (ideally, all of them should result 0; all the checks are executed +by default (except language-specific check blacklists in checkLanguage.inc): + * untranslated: Messages which are required to translate, but are not + translated. + * duplicate: Messages which translation equal to fallback. + * obsolete: Messages which are untranslatable or do not exist, but are + translated. + * variables: Messages without variables which should be used, or with + variables which should not be used. * empty: Empty messages and messages that contain only -. * whitespace: Messages which have trailing whitespace. - * xhtml: Messages which are not well-formed XHTML (checks only few common errors). + * xhtml: Messages which are not well-formed XHTML (checks only few common + errors). * chars: Messages with hidden characters. * links: Messages which contains broken links to pages (does not find all). - * unbalanced: Messages which contains unequal numbers of opening {[ and closing ]}. + * unbalanced: Messages which contains unequal numbers of opening {[ and + closing ]}. * namespace: Namespace names that were not translated. - * projecttalk: Namespace names and aliases where the project talk does not contain $1. + * projecttalk: Namespace names and aliases where the project talk does not + contain $1. * magic: Magic words that were not translated. * magic-old: Magic words which do not exist. * magic-over: Magic words that override the original English word. - * magic-case: Magic words whose translation changes the case-sensitivity of the original English word. + * magic-case: Magic words whose translation changes the case-sensitivity of + the original English word. * special: Special page names that were not translated. * special-old: Special page names which do not exist. @@ -291,6 +309,17 @@ ENDS; $this->results[$this->code] = $this->checkLanguage( $this->code ); } } + + $results = $this->results; + foreach ( $results as $code => $checks ) { + foreach ( $checks as $check => $messages ) { + foreach ( $messages as $key => $details ) { + if ( $this->isCheckBlacklisted( $check, $code, $key ) ) { + unset( $this->results[$code][$check][$key] ); + } + } + } + } } /** @@ -298,14 +327,58 @@ ENDS; * @return array The list of checks which should not be executed. */ protected function getCheckBlacklist() { + static $blacklist = null; + + if ( $blacklist !== null ) { + return $blacklist; + } + + // @codingStandardsIgnoreStart Ignore that globals should have a "wg" prefix. global $checkBlacklist; + // @codingStandardsIgnoreEnd + + $blacklist = $checkBlacklist; + + wfRunHooks( 'LocalisationChecksBlacklist', array( &$blacklist ) ); + + return $blacklist; + } + + /** + * Verify whether a check is blacklisted. + * + * @param string $check Check name + * @param string $code Language code + * @param string|bool $message Message name, or False for a whole language + * @return bool Whether the check is blacklisted + */ + protected function isCheckBlacklisted( $check, $code, $message ) { + $blacklist = $this->getCheckBlacklist(); + + foreach ( $blacklist as $item ) { + if ( isset( $item['check'] ) && $check !== $item['check'] ) { + continue; + } - return $checkBlacklist; + if ( isset( $item['code'] ) && !in_array( $code, $item['code'] ) ) { + continue; + } + + if ( isset( $item['message'] ) && + ( $message === false || !in_array( $message, $item['message'] ) ) + ) { + continue; + } + + return true; + } + + return false; } /** * Check a language. - * @param $code string The language code. + * @param string $code The language code. * @throws MWException * @return array The results. */ @@ -319,11 +392,8 @@ ENDS; } $checkFunctions = $this->getChecks(); - $checkBlacklist = $this->getCheckBlacklist(); foreach ( $this->checks as $check ) { - if ( isset( $checkBlacklist[$code] ) && - in_array( $check, $checkBlacklist[$code] ) - ) { + if ( $this->isCheckBlacklisted( $check, $code, false ) ) { $results[$check] = array(); continue; } @@ -340,8 +410,8 @@ ENDS; /** * Format a message key. - * @param $key string The message key. - * @param $code string The language code. + * @param string $key The message key. + * @param string $code The language code. * @return string The formatted message key. */ protected function formatKey( $key, $code ) { @@ -407,7 +477,8 @@ ENDS; */ function outputWiki() { $detailText = ''; - $rows[] = '! Language !! Code !! Total !! ' . implode( ' !! ', array_diff( $this->checks, $this->nonMessageChecks() ) ); + $rows[] = '! Language !! Code !! Total !! ' . + implode( ' !! ', array_diff( $this->checks, $this->nonMessageChecks() ) ); foreach ( $this->results as $code => $results ) { $detailTextForLang = "==$code==\n"; $numbers = array(); @@ -447,6 +518,7 @@ ENDS; $tableRows = implode( "\n|-\n", $rows ); $version = SpecialVersion::getVersion( 'nodb' ); + // @codingStandardsIgnoreStart Long line. echo <<<EOL '''Check results are for:''' <code>$version</code> @@ -458,6 +530,7 @@ $tableRows $detailText EOL; + // @codingStandardsIgnoreEnd } /** @@ -485,8 +558,8 @@ class CheckExtensionsCLI extends CheckLanguageCLI { /** * Constructor. - * @param $options array Options for script. - * @param $extension string The extension name (or names). + * @param array $options Options for script. + * @param string $extension The extension name (or names). */ public function __construct( array $options, $extension ) { if ( isset( $options['help'] ) ) { @@ -539,19 +612,19 @@ class CheckExtensionsCLI extends CheckLanguageCLI { if ( $extension == 'all' ) { foreach ( MessageGroups::singleton()->getGroups() as $group ) { if ( strpos( $group->getId(), 'ext-' ) === 0 && !$group->isMeta() ) { - $this->extensions[] = new extensionLanguages( $group ); + $this->extensions[] = new ExtensionLanguages( $group ); } } } elseif ( $extension == 'wikimedia' ) { $wikimedia = MessageGroups::getGroup( 'ext-0-wikimedia' ); foreach ( $wikimedia->wmfextensions() as $extension ) { $group = MessageGroups::getGroup( $extension ); - $this->extensions[] = new extensionLanguages( $group ); + $this->extensions[] = new ExtensionLanguages( $group ); } } elseif ( $extension == 'flaggedrevs' ) { foreach ( MessageGroups::singleton()->getGroups() as $group ) { if ( strpos( $group->getId(), 'ext-flaggedrevs-' ) === 0 && !$group->isMeta() ) { - $this->extensions[] = new extensionLanguages( $group ); + $this->extensions[] = new ExtensionLanguages( $group ); } } } else { @@ -559,7 +632,7 @@ class CheckExtensionsCLI extends CheckLanguageCLI { foreach ( $extensions as $extension ) { $group = MessageGroups::getGroup( 'ext-' . $extension ); if ( $group ) { - $extension = new extensionLanguages( $group ); + $extension = new ExtensionLanguages( $group ); $this->extensions[] = $extension; } else { print "No such extension $extension.\n"; @@ -589,7 +662,7 @@ class CheckExtensionsCLI extends CheckLanguageCLI { /** * Get the checks that can easily be treated by non-speakers of the language. - * @return arrayA list of the easy checks. + * @return array A list of the easy checks. */ protected function easyChecks() { return array( @@ -603,34 +676,50 @@ class CheckExtensionsCLI extends CheckLanguageCLI { */ protected function help() { return <<<ENDS -Run this script to check the status of a specific language in extensions, or all of them. -Command line settings are in form --parameter[=value], except for the first one. +Run this script to check the status of a specific language in extensions, or +all of them. Command line settings are in form --parameter[=value], except for +the first one. Parameters: - * First parameter (mandatory): Extension name, multiple extension names (separated by commas), "all" for all the extensions, "wikimedia" for extensions used by Wikimedia or "flaggedrevs" for all FLaggedRevs extension messages. + * First parameter (mandatory): Extension name, multiple extension names + (separated by commas), "all" for all the extensions, "wikimedia" for + extensions used by Wikimedia or "flaggedrevs" for all FLaggedRevs + extension messages. * lang: Language code (default: the installation default language). * help: Show this help. * level: Show the following display level (default: 2). * links: Link the message values (default off). - * wikilang: For the links, what is the content language of the wiki to display the output in (default en). + * wikilang: For the links, what is the content language of the wiki to + display the output in (default en). * whitelist: Do only the following checks (form: code,code). * blacklist: Do not perform the following checks (form: code,code). - * easy: Do only the easy checks, which can be treated by non-speakers of the language. -Check codes (ideally, all of them should result 0; all the checks are executed by default (except language-specific check blacklists in checkLanguage.inc): - * untranslated: Messages which are required to translate, but are not translated. - * duplicate: Messages which translation equal to fallback + * easy: Do only the easy checks, which can be treated by non-speakers of + the language. + +Check codes (ideally, all of them should result 0; all the checks are executed +by default (except language-specific check blacklists in checkLanguage.inc): + * untranslated: Messages which are required to translate, but are not + translated. + * duplicate: Messages which translation equal to fallback. * obsolete: Messages which are untranslatable, but translated. - * variables: Messages without variables which should be used, or with variables which should not be used. + * variables: Messages without variables which should be used, or with + variables which should not be used. * empty: Empty messages. * whitespace: Messages which have trailing whitespace. - * xhtml: Messages which are not well-formed XHTML (checks only few common errors). + * xhtml: Messages which are not well-formed XHTML (checks only few common + errors). * chars: Messages with hidden characters. * links: Messages which contains broken links to pages (does not find all). - * unbalanced: Messages which contains unequal numbers of opening {[ and closing ]}. + * unbalanced: Messages which contains unequal numbers of opening {[ and + closing ]}. + Display levels (default: 2): * 0: Skip the checks (useful for checking syntax). - * 1: Show only the stub headers and number of wrong messages, without list of messages. - * 2: Show only the headers and the message keys, without the message values. - * 3: Show both the headers and the complete messages, with both keys and values. + * 1: Show only the stub headers and number of wrong messages, without list + of messages. + * 2: Show only the headers and the message keys, without the message + values. + * 3: Show both the headers and the complete messages, with both keys and + values. ENDS; } @@ -644,7 +733,7 @@ ENDS; /** * Check a language and show the results. - * @param $code string The language code. + * @param string $code The language code. * @throws MWException */ protected function checkLanguage( $code ) { @@ -675,52 +764,21 @@ ENDS; } } -# Blacklist some checks for some languages +// Blacklist some checks for some languages or some messages +// Possible keys of the sub arrays are: 'check', 'code' and 'message'. $checkBlacklist = array( -#'code' => array( 'check1', 'check2' ... ) - 'az' => array( 'plural' ), - 'bo' => array( 'plural' ), - 'cdo' => array( 'plural' ), - 'dz' => array( 'plural' ), - 'id' => array( 'plural' ), - 'fa' => array( 'plural' ), - 'gan' => array( 'plural' ), - 'gan-hans' => array( 'plural' ), - 'gan-hant' => array( 'plural' ), - 'gn' => array( 'plural' ), - 'hak' => array( 'plural' ), - 'hu' => array( 'plural' ), - 'ja' => array( 'plural' ), // Does not use plural - 'jv' => array( 'plural' ), - 'ka' => array( 'plural' ), - 'kk-arab' => array( 'plural' ), - 'kk-cyrl' => array( 'plural' ), - 'kk-latn' => array( 'plural' ), - 'km' => array( 'plural' ), - 'kn' => array( 'plural' ), - 'ko' => array( 'plural' ), - 'lzh' => array( 'plural' ), - 'mn' => array( 'plural' ), - 'ms' => array( 'plural' ), - 'my' => array( 'plural', 'chars' ), // Uses a lot zwnj - 'sah' => array( 'plural' ), - 'sq' => array( 'plural' ), - 'tet' => array( 'plural' ), - 'th' => array( 'plural' ), - 'to' => array( 'plural' ), - 'tr' => array( 'plural' ), - 'vi' => array( 'plural' ), - 'wuu' => array( 'plural' ), - 'xmf' => array( 'plural' ), - 'yo' => array( 'plural' ), - 'yue' => array( 'plural' ), - 'zh' => array( 'plural' ), - 'zh-classical' => array( 'plural' ), - 'zh-cn' => array( 'plural' ), - 'zh-hans' => array( 'plural' ), - 'zh-hant' => array( 'plural' ), - 'zh-hk' => array( 'plural' ), - 'zh-sg' => array( 'plural' ), - 'zh-tw' => array( 'plural' ), - 'zh-yue' => array( 'plural' ), + array( + 'check' => 'plural', + 'code' => array( 'az', 'bo', 'cdo', 'dz', 'id', 'fa', 'gan', 'gan-hans', + 'gan-hant', 'gn', 'hak', 'hu', 'ja', 'jv', 'ka', 'kk-arab', + 'kk-cyrl', 'kk-latn', 'km', 'kn', 'ko', 'lzh', 'mn', 'ms', + 'my', 'sah', 'sq', 'tet', 'th', 'to', 'tr', 'vi', 'wuu', 'xmf', + 'yo', 'yue', 'zh', 'zh-classical', 'zh-cn', 'zh-hans', + 'zh-hant', 'zh-hk', 'zh-sg', 'zh-tw', 'zh-yue' + ), + ), + array( + 'check' => 'chars', + 'code' => array( 'my' ), + ), ); diff --git a/maintenance/language/countMessages.php b/maintenance/language/countMessages.php deleted file mode 100644 index 95a7154b..00000000 --- a/maintenance/language/countMessages.php +++ /dev/null @@ -1,72 +0,0 @@ -<?php -/** - * Count how many messages we have defined for each language. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup MaintenanceLanguage - */ - -require_once __DIR__ . '/../Maintenance.php'; - -/** - * Maintenance script that counts how many messages we have defined - * for each language. - * - * @ingroup MaintenanceLanguage - */ -class CountMessages extends Maintenance { - public function __construct() { - parent::__construct(); - $this->mDescription = "Count how many messages we have defined for each language"; - } - - public function execute() { - global $IP; - $dir = $this->getArg( 0, "$IP/languages/messages" ); - $total = 0; - $nonZero = 0; - foreach ( glob( "$dir/*.php" ) as $file ) { - $baseName = basename( $file ); - if ( !preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $baseName, $m ) ) { - continue; - } - - $numMessages = $this->getNumMessages( $file ); - // print "$code: $numMessages\n"; - $total += $numMessages; - if ( $numMessages > 0 ) { - $nonZero ++; - } - } - $this->output( "\nTotal: $total\n" ); - $this->output( "Languages: $nonZero\n" ); - } - - private function getNumMessages( $file ) { - // Separate function to limit scope - require $file; - if ( isset( $messages ) ) { - return count( $messages ); - } else { - return 0; - } - } -} - -$maintClass = "CountMessages"; -require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/language/generateCollationData.php b/maintenance/language/generateCollationData.php index fcf2c960..973cf7bc 100644 --- a/maintenance/language/generateCollationData.php +++ b/maintenance/language/generateCollationData.php @@ -48,7 +48,7 @@ class GenerateCollationData extends Maintenance { * Important tertiary weights from UTS #10 section 7.2 */ const NORMAL_UPPERCASE = 0x08; - const NORMAL_HIRAGANA = 0X0E; + const NORMAL_HIRAGANA = 0x0E; public function __construct() { parent::__construct(); @@ -158,7 +158,8 @@ class GenerateCollationData extends Maintenance { // people like to use that as a fake no header symbol. $category = substr( $data['gc'], 0, 1 ); if ( strpos( 'LNPS', $category ) === false - && $data['cp'] !== '0020' ) { + && $data['cp'] !== '0020' + ) { return; } $cp = hexdec( $data['cp'] ); @@ -178,8 +179,8 @@ class GenerateCollationData extends Maintenance { // Calculate implicit weight per UTS #10 v6.0.0, sec 7.1.3 if ( $data['UIdeo'] === 'Y' ) { if ( $data['block'] == 'CJK Unified Ideographs' - || $data['block'] == 'CJK Compatibility Ideographs' ) - { + || $data['block'] == 'CJK Compatibility Ideographs' + ) { $base = 0xFB40; } else { $base = 0xFB80; @@ -248,8 +249,8 @@ class GenerateCollationData extends Maintenance { } $this->weights[$cp] = $primary; if ( $tertiary === '.0008' - || $tertiary === '.000E' ) - { + || $tertiary === '.000E' + ) { $goodTertiaryChars[$cp] = true; } } @@ -325,7 +326,7 @@ class GenerateCollationData extends Maintenance { $char = codepointToUtf8( $cp ); $headerChars[] = $char; if ( $primaryCollator->compare( $char, $prevChar ) <= 0 ) { - $numOutOfOrder ++; + $numOutOfOrder++; /* printf( "Out of order: U+%05X > U+%05X\n", utf8ToCodepoint( $prevChar ), @@ -390,6 +391,7 @@ class UcdXmlReader { } while ( $this->xml->name !== 'ucd' && $this->xml->read() ); $this->xml->read(); + return $this->xml; } @@ -403,6 +405,7 @@ class UcdXmlReader { while ( $this->xml->moveToNextAttribute() ) { $attrs[$this->xml->name] = $this->xml->value; } + return $attrs; } @@ -460,9 +463,9 @@ class UcdXmlReader { } } $xml->close(); + return $this->blocks; } - } $maintClass = 'GenerateCollationData'; diff --git a/maintenance/language/generateNormalizerData.php b/maintenance/language/generateNormalizerDataAr.php index 216445e4..ece0450f 100644 --- a/maintenance/language/generateNormalizerData.php +++ b/maintenance/language/generateNormalizerDataAr.php @@ -1,6 +1,6 @@ <?php /** - * Generates normalizer data files for Arabic and Malayalam. + * Generates the normalizer data file for Arabic. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,46 +21,43 @@ * @ingroup MaintenanceLanguage */ -require_once __DIR__ . '/../../includes/normal/UtfNormalUtil.php'; - require_once __DIR__ . '/../Maintenance.php'; /** - * Generates normalizer data files for Arabic and Malayalam. + * Generates the normalizer data file for Arabic. * For NFC see includes/normal. * * @ingroup MaintenanceLanguage */ -class GenerateNormalizerData extends Maintenance { - public $dataFile; - +class GenerateNormalizerDataAr extends Maintenance { public function __construct() { parent::__construct(); + $this->mDescription = 'Generate the normalizer data file for Arabic'; $this->addOption( 'unicode-data-file', 'The local location of the data file ' . 'from http://unicode.org/Public/UNIDATA/UnicodeData.txt', false, true ); } + public function getDbType() { + return Maintenance::DB_NONE; + } + public function execute() { if ( !$this->hasOption( 'unicode-data-file' ) ) { - $this->dataFile = 'UnicodeData.txt'; - if ( !file_exists( $this->dataFile ) ) { - $this->error( "Unable to find UnicodeData.txt. Please specify its location with --unicode-data-file=<FILE>" ); + $dataFile = 'UnicodeData.txt'; + if ( !file_exists( $dataFile ) ) { + $this->error( "Unable to find UnicodeData.txt. Please specify " . + "its location with --unicode-data-file=<FILE>" ); exit( 1 ); } } else { - $this->dataFile = $this->getOption( 'unicode-data-file' ); - if ( !file_exists( $this->dataFile ) ) { + $dataFile = $this->getOption( 'unicode-data-file' ); + if ( !file_exists( $dataFile ) ) { $this->error( 'Unable to find the specified data file.' ); exit( 1 ); } } - $this->generateArabic(); - $this->generateMalayalam(); - } - - function generateArabic() { - $file = fopen( $this->dataFile, 'r' ); + $file = fopen( $dataFile, 'r' ); if ( !$file ) { $this->error( 'Unable to open the data file.' ); exit( 1 ); @@ -74,7 +71,9 @@ class GenerateNormalizerData extends Maintenance { 'Canonical_Combining_Class', 'Bidi_Class', 'Decomposition_Type_Mapping', - 'Numeric_Type_Value', + 'Numeric_Type_Value_6', + 'Numeric_Type_Value_7', + 'Numeric_Type_Value_8', 'Bidi_Mirrored', 'Unicode_1_Name', 'ISO_Comment', @@ -104,15 +103,15 @@ class GenerateNormalizerData extends Maintenance { $code = base_convert( $data['Code'], 16, 10 ); if ( ( $code >= 0xFB50 && $code <= 0xFDFF ) # Arabic presentation forms A - || ( $code >= 0xFE70 && $code <= 0xFEFF ) ) # Arabic presentation forms B - { + || ( $code >= 0xFE70 && $code <= 0xFEFF ) # Arabic presentation forms B + ) { if ( $data['Decomposition_Type_Mapping'] === '' ) { // No decomposition continue; } if ( !preg_match( '/^ *(<\w*>) +([0-9A-F ]*)$/', - $data['Decomposition_Type_Mapping'], $m ) ) - { + $data['Decomposition_Type_Mapping'], $m ) + ) { $this->error( "Can't parse Decomposition_Type/Mapping on line $lineNum" ); $this->error( $line ); continue; @@ -128,32 +127,7 @@ class GenerateNormalizerData extends Maintenance { file_put_contents( "$IP/serialized/normalize-ar.ser", serialize( $pairs ) ); echo "ar: " . count( $pairs ) . " pairs written.\n"; } - - function generateMalayalam() { - $hexPairs = array( - # From http://unicode.org/versions/Unicode5.1.0/#Malayalam_Chillu_Characters - '0D23 0D4D 200D' => '0D7A', - '0D28 0D4D 200D' => '0D7B', - '0D30 0D4D 200D' => '0D7C', - '0D32 0D4D 200D' => '0D7D', - '0D33 0D4D 200D' => '0D7E', - - # From http://permalink.gmane.org/gmane.science.linguistics.wikipedia.technical/46413 - '0D15 0D4D 200D' => '0D7F', - ); - - $pairs = array(); - foreach ( $hexPairs as $hexSource => $hexDest ) { - $source = hexSequenceToUtf8( $hexSource ); - $dest = hexSequenceToUtf8( $hexDest ); - $pairs[$source] = $dest; - } - - global $IP; - file_put_contents( "$IP/serialized/normalize-ml.ser", serialize( $pairs ) ); - echo "ml: " . count( $pairs ) . " pairs written.\n"; - } } -$maintClass = 'GenerateNormalizerData'; +$maintClass = 'GenerateNormalizerDataAr'; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/language/generateNormalizerDataMl.php b/maintenance/language/generateNormalizerDataMl.php new file mode 100644 index 00000000..c7237cfe --- /dev/null +++ b/maintenance/language/generateNormalizerDataMl.php @@ -0,0 +1,69 @@ +<?php +/** + * Generates the normalizer data file for Malayalam. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup MaintenanceLanguage + */ + +require_once __DIR__ . '/../Maintenance.php'; + +/** + * Generates the normalizer data file for Malayalam. + * For NFC see includes/normal. + * + * @ingroup MaintenanceLanguage + */ +class GenerateNormalizerDataMl extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = 'Generate the normalizer data file for Malayalam'; + } + + public function getDbType() { + return Maintenance::DB_NONE; + } + + public function execute() { + $hexPairs = array( + # From http://unicode.org/versions/Unicode5.1.0/#Malayalam_Chillu_Characters + '0D23 0D4D 200D' => '0D7A', + '0D28 0D4D 200D' => '0D7B', + '0D30 0D4D 200D' => '0D7C', + '0D32 0D4D 200D' => '0D7D', + '0D33 0D4D 200D' => '0D7E', + + # From http://permalink.gmane.org/gmane.science.linguistics.wikipedia.technical/46413 + '0D15 0D4D 200D' => '0D7F', + ); + + $pairs = array(); + foreach ( $hexPairs as $hexSource => $hexDest ) { + $source = hexSequenceToUtf8( $hexSource ); + $dest = hexSequenceToUtf8( $hexDest ); + $pairs[$source] = $dest; + } + + global $IP; + file_put_contents( "$IP/serialized/normalize-ml.ser", serialize( $pairs ) ); + echo "ml: " . count( $pairs ) . " pairs written.\n"; + } +} + +$maintClass = 'GenerateNormalizerDataMl'; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/language/generateUtf8Case.php b/maintenance/language/generateUtf8Case.php new file mode 100644 index 00000000..0fd32427 --- /dev/null +++ b/maintenance/language/generateUtf8Case.php @@ -0,0 +1,129 @@ +<?php +/** + * Generates Utf8Case.ser from the Unicode Character Database and + * supplementary files. + * + * Copyright © 2004, 2008 Brion Vibber <brion@pobox.com> + * https://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup MaintenanceLanguage + */ + +require_once __DIR__ . '/../Maintenance.php'; + +/** + * Generates Utf8Case.ser from the Unicode Character Database and + * supplementary files. + * + * @ingroup MaintenanceLanguage + */ +class GenerateUtf8Case extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = 'Generate Utf8Case.ser from the Unicode Character Database ' . + 'and supplementary files'; + $this->addOption( 'unicode-data-file', 'The local location of the data file ' . + 'from http://unicode.org/Public/UNIDATA/UnicodeData.txt', false, true ); + } + + public function getDbType() { + return Maintenance::DB_NONE; + } + + public function execute() { + if ( !$this->hasOption( 'unicode-data-file' ) ) { + $dataFile = 'UnicodeData.txt'; + if ( !file_exists( $dataFile ) ) { + $this->error( "Unable to find UnicodeData.txt. Please specify " . + "its location with --unicode-data-file=<FILE>" ); + exit( 1 ); + } + } else { + $dataFile = $this->getOption( 'unicode-data-file' ); + if ( !file_exists( $dataFile ) ) { + $this->error( 'Unable to find the specified data file.' ); + exit( 1 ); + } + } + + $file = fopen( $dataFile, 'r' ); + if ( !$file ) { + $this->error( 'Unable to open the data file.' ); + exit( 1 ); + } + + // For the file format, see http://www.unicode.org/reports/tr44/ + $fieldNames = array( + 'Code', + 'Name', + 'General_Category', + 'Canonical_Combining_Class', + 'Bidi_Class', + 'Decomposition_Type_Mapping', + 'Numeric_Type_Value_6', + 'Numeric_Type_Value_7', + 'Numeric_Type_Value_8', + 'Bidi_Mirrored', + 'Unicode_1_Name', + 'ISO_Comment', + 'Simple_Uppercase_Mapping', + 'Simple_Lowercase_Mapping', + 'Simple_Titlecase_Mapping' + ); + + $upper = array(); + $lower = array(); + + $lineNum = 0; + while ( false !== ( $line = fgets( $file ) ) ) { + ++$lineNum; + + # Strip comments + $line = trim( substr( $line, 0, strcspn( $line, '#' ) ) ); + if ( $line === '' ) { + continue; + } + + # Split fields + $numberedData = explode( ';', $line ); + $data = array(); + foreach ( $fieldNames as $number => $name ) { + $data[$name] = $numberedData[$number]; + } + + $source = hexSequenceToUtf8( $data['Code'] ); + if ( $data['Simple_Uppercase_Mapping'] ) { + $upper[$source] = hexSequenceToUtf8( $data['Simple_Uppercase_Mapping'] ); + } + if ( $data['Simple_Lowercase_Mapping'] ) { + $lower[$source] = hexSequenceToUtf8( $data['Simple_Lowercase_Mapping'] ); + } + } + + global $IP; + file_put_contents( "$IP/serialized/Utf8Case.ser", serialize( array( + 'wikiUpperChars' => $upper, + 'wikiLowerChars' => $lower, + ) ) ); + } +} + +$maintClass = 'GenerateUtf8Case'; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/language/langmemusage.php b/maintenance/language/langmemusage.php index 14485f98..32cfcd7d 100644 --- a/maintenance/language/langmemusage.php +++ b/maintenance/language/langmemusage.php @@ -43,7 +43,7 @@ class LangMemUsage extends Maintenance { $this->error( "You must compile PHP with --enable-memory-limit", true ); } - $langtool = new languages(); + $langtool = new Languages(); $memlast = $memstart = memory_get_usage(); $this->output( "Base memory usage: $memstart\n" ); diff --git a/maintenance/language/languages.inc b/maintenance/language/languages.inc index 6070f4ab..fb496cbc 100644 --- a/maintenance/language/languages.inc +++ b/maintenance/language/languages.inc @@ -24,35 +24,47 @@ /** * @ingroup MaintenanceLanguage */ -class languages { - protected $mLanguages; # List of languages +class Languages { + /** @var array List of languages */ + protected $mLanguages; - protected $mRawMessages; # Raw list of the messages in each language - protected $mMessages; # Messages in each language (except for English), divided to groups - protected $mFallback; # Fallback language in each language - protected $mGeneralMessages; # General messages in English, divided to groups - protected $mIgnoredMessages; # All the messages which should be exist only in the English file - protected $mOptionalMessages; # All the messages which may be translated or not, depending on the language + /** @var array Raw list of the messages in each language */ + protected $mRawMessages; - protected $mNamespaceNames; # Namespace names - protected $mNamespaceAliases; # Namespace aliases - protected $mMagicWords; # Magic words - protected $mSpecialPageAliases; # Special page aliases + /** @var array Messages in each language (except for English), divided to groups */ + protected $mMessages; + + /** @var array Fallback language in each language */ + protected $mFallback; + + /** @var array General messages in English, divided to groups */ + protected $mGeneralMessages; + + /** @var array All the messages which should be exist only in the English file */ + protected $mIgnoredMessages; + + /** @var array All the messages which may be translated or not, depending on the language */ + protected $mOptionalMessages; + + /** @var array Namespace names */ + protected $mNamespaceNames; + + /** @var array Namespace aliases */ + protected $mNamespaceAliases; + + /** @var array Magic words */ + protected $mMagicWords; + + /** @var array Special page aliases */ + protected $mSpecialPageAliases; /** * Load the list of languages: all the Messages*.php * files in the languages directory. - * - * @param $exif bool Treat the Exif messages? */ - function __construct( $exif = true ) { - require __DIR__ . '/messageTypes.inc'; - $this->mIgnoredMessages = $wgIgnoredMessages; - if ( $exif ) { - $this->mOptionalMessages = array_merge( $wgOptionalMessages ); - } else { - $this->mOptionalMessages = array_merge( $wgOptionalMessages, $wgEXIFMessages ); - } + function __construct() { + wfRunHooks( 'LocalisationIgnoredOptionalMessages', + array( &$this->mIgnoredMessages, &$this->mOptionalMessages ) ); $this->mLanguages = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) ); sort( $this->mLanguages ); @@ -88,7 +100,7 @@ class languages { /** * Load the language file. * - * @param $code string The language code. + * @param string $code The language code. */ protected function loadFile( $code ) { if ( isset( $this->mRawMessages[$code] ) && @@ -96,7 +108,8 @@ class languages { isset( $this->mNamespaceNames[$code] ) && isset( $this->mNamespaceAliases[$code] ) && isset( $this->mMagicWords[$code] ) && - isset( $this->mSpecialPageAliases[$code] ) ) { + isset( $this->mSpecialPageAliases[$code] ) + ) { return; } $this->mRawMessages[$code] = array(); @@ -105,12 +118,16 @@ class languages { $this->mNamespaceAliases[$code] = array(); $this->mMagicWords[$code] = array(); $this->mSpecialPageAliases[$code] = array(); + + $jsonfilename = Language::getJsonMessagesFileName( $code ); + if ( file_exists( $jsonfilename ) ) { + $json = Language::getLocalisationCache()->readJSONFile( $jsonfilename ); + $this->mRawMessages[$code] = $json['messages']; + } + $filename = Language::getMessagesFileName( $code ); if ( file_exists( $filename ) ) { require $filename; - if ( isset( $messages ) ) { - $this->mRawMessages[$code] = $messages; - } if ( isset( $fallback ) ) { $this->mFallback[$code] = $fallback; } @@ -130,14 +147,18 @@ class languages { } /** - * Load the messages for a specific language (which is not English) and divide them to groups: + * Load the messages for a specific language (which is not English) and divide them to + * groups: * all - all the messages. * required - messages which should be translated in order to get a complete translation. - * optional - messages which can be translated, the fallback translation is used if not translated. - * obsolete - messages which should not be translated, either because they do not exist, or they are ignored messages. - * translated - messages which are either required or optional, but translated from English and needed. + * optional - messages which can be translated, the fallback translation is used if not + * translated. + * obsolete - messages which should not be translated, either because they do not exist, + * or they are ignored messages. + * translated - messages which are either required or optional, but translated from + * English and needed. * - * @param $code string The language code. + * @param string $code The language code. */ private function loadMessages( $code ) { if ( isset( $this->mMessages[$code] ) ) { @@ -166,10 +187,13 @@ class languages { /** * Load the messages for English and divide them to groups: * all - all the messages. - * required - messages which should be translated to other languages in order to get a complete translation. - * optional - messages which can be translated to other languages, but it's not required for a complete translation. + * required - messages which should be translated to other languages in order to get a + * complete translation. + * optional - messages which can be translated to other languages, but it's not required + * for a complete translation. * ignored - messages which should not be translated to other languages. - * translatable - messages which are either required or optional, but can be translated from English. + * translatable - messages which are either required or optional, but can be translated + * from English. */ private function loadGeneralMessages() { if ( isset( $this->mGeneralMessages ) ) { @@ -199,111 +223,125 @@ class languages { * fallback language messages, divided to groups: * all - all the messages. * required - messages which should be translated in order to get a complete translation. - * optional - messages which can be translated, the fallback translation is used if not translated. - * obsolete - messages which should not be translated, either because they do not exist, or they are ignored messages. - * translated - messages which are either required or optional, but translated from English and needed. + * optional - messages which can be translated, the fallback translation is used if not + * translated. + * obsolete - messages which should not be translated, either because they do not exist, + * or they are ignored messages. + * translated - messages which are either required or optional, but translated from + * English and needed. * - * @param $code string The language code. + * @param string $code The language code. * * @return string The messages in this language. */ public function getMessages( $code ) { $this->loadMessages( $code ); + return $this->mMessages[$code]; } /** * Get all the general English messages, divided to groups: * all - all the messages. - * required - messages which should be translated to other languages in order to get a complete translation. - * optional - messages which can be translated to other languages, but it's not required for a complete translation. + * required - messages which should be translated to other languages in + * order to get a complete translation. + * optional - messages which can be translated to other languages, but it's + * not required for a complete translation. * ignored - messages which should not be translated to other languages. - * translatable - messages which are either required or optional, but can be translated from English. + * translatable - messages which are either required or optional, but can be + * translated from English. * * @return array The general English messages. */ public function getGeneralMessages() { $this->loadGeneralMessages(); + return $this->mGeneralMessages; } /** * Get fallback language code for a specific language. * - * @param $code string The language code. + * @param string $code The language code. * * @return string Fallback code. */ public function getFallback( $code ) { $this->loadFile( $code ); + return $this->mFallback[$code]; } /** * Get namespace names for a specific language. * - * @param $code string The language code. + * @param string $code The language code. * * @return array Namespace names. */ public function getNamespaceNames( $code ) { $this->loadFile( $code ); + return $this->mNamespaceNames[$code]; } /** * Get namespace aliases for a specific language. * - * @param $code string The language code. + * @param string $code The language code. * * @return array Namespace aliases. */ public function getNamespaceAliases( $code ) { $this->loadFile( $code ); + return $this->mNamespaceAliases[$code]; } /** * Get magic words for a specific language. * - * @param $code string The language code. + * @param string $code The language code. * * @return array Magic words. */ public function getMagicWords( $code ) { $this->loadFile( $code ); + return $this->mMagicWords[$code]; } /** * Get special page aliases for a specific language. * - * @param $code string The language code. + * @param string $code The language code. * * @return array Special page aliases. */ public function getSpecialPageAliases( $code ) { $this->loadFile( $code ); + return $this->mSpecialPageAliases[$code]; } /** * Get the untranslated messages for a specific language. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The untranslated messages for this language. */ public function getUntranslatedMessages( $code ) { $this->loadGeneralMessages(); $this->loadMessages( $code ); + return array_diff_key( $this->mGeneralMessages['required'], $this->mMessages[$code]['required'] ); } /** * Get the duplicate messages for a specific language. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The duplicate messages for this language. */ @@ -316,26 +354,28 @@ class languages { $duplicateMessages[$key] = $value; } } + return $duplicateMessages; } /** * Get the obsolete messages for a specific language. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The obsolete messages for this language. */ public function getObsoleteMessages( $code ) { $this->loadGeneralMessages(); $this->loadMessages( $code ); + return $this->mMessages[$code]['obsolete']; } /** * Get the messages whose variables do not match the original ones. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The messages whose variables do not match the original ones. */ @@ -348,11 +388,13 @@ class languages { $missing = false; foreach ( $variables as $var ) { if ( preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) && - !preg_match( "/$var/sU", $value ) ) { + !preg_match( "/$var/sU", $value ) + ) { $missing = true; } if ( !preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) && - preg_match( "/$var/sU", $value ) ) { + preg_match( "/$var/sU", $value ) + ) { $missing = true; } } @@ -360,13 +402,14 @@ class languages { $mismatchMessages[$key] = $value; } } + return $mismatchMessages; } /** * Get the messages which do not use plural. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The messages which do not use plural in this language. */ @@ -375,17 +418,20 @@ class languages { $this->loadMessages( $code ); $messagesWithoutPlural = array(); foreach ( $this->mMessages[$code]['translated'] as $key => $value ) { - if ( stripos( $this->mGeneralMessages['translatable'][$key], '{{plural:' ) !== false && stripos( $value, '{{plural:' ) === false ) { + if ( stripos( $this->mGeneralMessages['translatable'][$key], '{{plural:' ) !== false && + stripos( $value, '{{plural:' ) === false + ) { $messagesWithoutPlural[$key] = $value; } } + return $messagesWithoutPlural; } /** * Get the empty messages. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The empty messages for this language. */ @@ -398,13 +444,14 @@ class languages { $emptyMessages[$key] = $value; } } + return $emptyMessages; } /** * Get the messages with trailing whitespace. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The messages with trailing whitespace in this language. */ @@ -417,13 +464,14 @@ class languages { $messagesWithWhitespace[$key] = $value; } } + return $messagesWithWhitespace; } /** * Get the non-XHTML messages. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The non-XHTML messages for this language. */ @@ -445,13 +493,14 @@ class languages { $nonXHTMLMessages[$key] = $value; } } + return $nonXHTMLMessages; } /** * Get the messages which include wrong characters. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The messages which include wrong characters in this language. */ @@ -482,13 +531,14 @@ class languages { $wrongCharsMessages[$key] = $value; } } + return $wrongCharsMessages; } /** * Get the messages which include dubious links. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The messages which include dubious links in this language. */ @@ -500,24 +550,25 @@ class languages { foreach ( $this->mMessages[$code]['translated'] as $key => $value ) { $matches = array(); preg_match_all( "/\[\[([{$tc}]+)(?:\\|(.+?))?]]/sDu", $value, $matches ); - for ( $i = 0; $i < count( $matches[0] ); $i++ ) { + $numMatches = count( $matches[0] ); + for ( $i = 0; $i < $numMatches; $i++ ) { if ( preg_match( "/.*project.*/isDu", $matches[1][$i] ) ) { $messages[$key][] = $matches[0][$i]; } } - if ( isset( $messages[$key] ) ) { $messages[$key] = implode( $messages[$key], ", " ); } } + return $messages; } /** * Get the messages which include unbalanced brackets. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The messages which include unbalanced brackets in this language. */ @@ -547,15 +598,15 @@ class languages { if ( $a !== $b || $c !== $d ) { $messages[$key] = "$a, $b, $c, $d"; } - } + return $messages; } /** * Get the untranslated namespace names. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The untranslated namespace names in this language. */ @@ -566,13 +617,14 @@ class languages { if ( isset( $namespacesDiff[NS_MAIN] ) ) { unset( $namespacesDiff[NS_MAIN] ); } + return $namespacesDiff; } /** * Get the project talk namespace names with no $1. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The problematic project talk namespaces in this language. */ @@ -601,7 +653,7 @@ class languages { /** * Get the untranslated magic words. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The untranslated magic words in this language. */ @@ -614,13 +666,14 @@ class languages { $magicWords[$key] = $value[1]; } } + return $magicWords; } /** * Get the obsolete magic words. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The obsolete magic words in this language. */ @@ -633,13 +686,14 @@ class languages { $magicWords[$key] = $value[1]; } } + return $magicWords; } /** * Get the magic words that override the original English magic word. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The overriding magic words in this language. */ @@ -662,13 +716,14 @@ class languages { } } } + return $magicWords; } /** * Get the magic words which do not match the case-sensitivity of the original words. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The magic words whose case does not match in this language. */ @@ -685,13 +740,14 @@ class languages { $magicWords[$key] = $local[0]; } } + return $magicWords; } /** * Get the untranslated special page names. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The untranslated special page names in this language. */ @@ -704,13 +760,14 @@ class languages { $specialPageAliases[$key] = $value[0]; } } + return $specialPageAliases; } /** * Get the obsolete special page names. * - * @param $code string The language code. + * @param string $code The language code. * * @return array The obsolete special page names in this language. */ @@ -723,12 +780,12 @@ class languages { $specialPageAliases[$key] = $value[0]; } } + return $specialPageAliases; } } -class extensionLanguages extends languages { - +class ExtensionLanguages extends Languages { /** * @var MessageGroup */ @@ -736,7 +793,7 @@ class extensionLanguages extends languages { /** * Load the messages group. - * @param $group MessageGroup The messages group. + * @param MessageGroup $group The messages group. */ function __construct( MessageGroup $group ) { $this->mMessageGroup = $group; @@ -757,7 +814,7 @@ class extensionLanguages extends languages { /** * Load the language file. * - * @param $code string The language code. + * @param string $code The language code. */ protected function loadFile( $code ) { if ( !isset( $this->mRawMessages[$code] ) ) { diff --git a/maintenance/language/listVariants.php b/maintenance/language/listVariants.php new file mode 100644 index 00000000..4bff8916 --- /dev/null +++ b/maintenance/language/listVariants.php @@ -0,0 +1,73 @@ +<?php +/** + * Lists all language variants + * + * Copyright © 2014 MediaWiki developers + * https://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + */ +require_once dirname( __DIR__ ) . '/Maintenance.php'; + +/** + * @since 1.24 + */ +class ListVariants extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = 'Outputs a list of language variants'; + $this->addOption( 'flat', 'Output variants in a flat list' ); + $this->addOption( 'json', 'Output variants as JSON' ); + } + + public function execute() { + $variantLangs = array(); + $variants = array(); + foreach ( LanguageConverter::$languagesWithVariants as $langCode ) { + $lang = Language::factory( $langCode ); + if ( count( $lang->getVariants() ) > 1 ) { + $variants += array_flip( $lang->getVariants() ); + $variantLangs[$langCode] = $lang->getVariants(); + } + } + $variants = array_keys( $variants ); + sort( $variants ); + $result = $this->hasOption( 'flat' ) ? $variants : $variantLangs; + + // Not using $this->output() because muting makes no sense here + if ( $this->hasOption( 'json' ) ) { + echo FormatJson::encode( $result, true ) . "\n"; + } else { + foreach ( $result as $key => $value ) { + if ( is_array( $value ) ) { + echo "$key\n"; + foreach ( $value as $variant ) { + echo " $variant\n"; + } + } else { + echo "$value\n"; + } + } + } + } +} + +$maintClass = 'ListVariants'; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/language/messageTypes.inc b/maintenance/language/messageTypes.inc deleted file mode 100644 index f9239b1c..00000000 --- a/maintenance/language/messageTypes.inc +++ /dev/null @@ -1,872 +0,0 @@ -<?php -/** - * Several types of messages. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup MaintenanceLanguage - */ - -/** Ignored messages, which should exist only in the English messages file. */ -$wgIgnoredMessages = array( - 'sidebar', - 'accesskey-pt-userpage', - 'accesskey-pt-anonuserpage', - 'accesskey-pt-mytalk', - 'accesskey-pt-anontalk', - 'accesskey-pt-preferences', - 'accesskey-pt-watchlist', - 'accesskey-pt-mycontris', - 'accesskey-pt-login', - 'accesskey-pt-anonlogin', - 'accesskey-pt-logout', - 'accesskey-ca-talk', - 'accesskey-ca-edit', - 'accesskey-ca-addsection', - 'accesskey-ca-viewsource', - 'accesskey-ca-history', - 'accesskey-ca-protect', - 'accesskey-ca-unprotect', - 'accesskey-ca-delete', - 'accesskey-ca-undelete', - 'accesskey-ca-move', - 'accesskey-ca-watch', - 'accesskey-ca-unwatch', - 'accesskey-search', - 'accesskey-search-go', - 'accesskey-search-fulltext', - 'accesskey-p-logo', - 'accesskey-n-mainpage', - 'accesskey-n-mainpage-description', - 'accesskey-n-portal', - 'accesskey-n-currentevents', - 'accesskey-n-recentchanges', - 'accesskey-n-randompage', - 'accesskey-n-help', - 'accesskey-t-whatlinkshere', - 'accesskey-t-recentchangeslinked', - 'accesskey-feed-rss', - 'accesskey-feed-atom', - 'accesskey-t-contributions', - 'accesskey-t-emailuser', - 'accesskey-t-permalink', - 'accesskey-t-print', - 'accesskey-t-upload', - 'accesskey-t-specialpages', - 'accesskey-ca-nstab-main', - 'accesskey-ca-nstab-user', - 'accesskey-ca-nstab-media', - 'accesskey-ca-nstab-special', - 'accesskey-ca-nstab-project', - 'accesskey-ca-nstab-image', - 'accesskey-ca-nstab-mediawiki', - 'accesskey-ca-nstab-template', - 'accesskey-ca-nstab-help', - 'accesskey-ca-nstab-category', - 'accesskey-minoredit', - 'accesskey-save', - 'accesskey-preview', - 'accesskey-diff', - 'accesskey-compareselectedversions', - 'accesskey-watch', - 'accesskey-upload', - 'accesskey-preferences-save', - 'accesskey-summary', - 'accesskey-userrights-set', - 'accesskey-blockip-block', - 'accesskey-export', - 'accesskey-import', - 'accesskey-watchlistedit-normal-submit', - 'accesskey-watchlistedit-raw-submit', - 'addsection', - 'talkpageheader', - 'anonnotice', - 'autoblock_whitelist', - 'searchmenu-new-nocreate', - 'googlesearch', - 'opensearch-desc', - 'exif-make-value', - 'exif-model-value', - 'exif-software-value', - 'exif-software-version-value', - 'history_copyright', - 'licenses', - 'loginstart', - 'loginend-https', - 'loginend', - 'loginlanguagelinks', - 'pear-mail-error', - 'php-mail-error', - 'markaspatrolledlink', - 'newarticletextanon', - 'newsectionheaderdefaultlevel', - 'mainpage-nstab', - 'newtalkseparator', - 'noarticletextanon', - 'number_of_watching_users_RCview', - 'pagecategorieslink', - 'pubmedurl', - 'randompage-url', - 'recentchanges-url', - 'recentchangestext', - 'revision-info-current', - 'revision-nav', - 'rfcurl', - 'shareddescriptionfollows', - 'signature-anon', - 'signupstart', - 'signupend', - 'signupend-https', - 'sitenotice', - 'sitesubtitle', - 'sitetitle', - 'sp-contributions-footer', - 'sp-contributions-footer-anon', - 'sp-contributions-footer-newbies', - 'statistics-summary', - 'statistics-footer', - 'talkpagetext', - 'uploadfooter', - 'upload-default-description', - 'listgrouprights-link', - 'search-interwiki-custom', - 'allpages-summary', - 'booksources-summary', - 'categories-summary', - 'blocklist-summary', - 'protectedtitles-summary', - 'listusers-summary', - 'longpages-summary', - 'preferences-summary', - 'specialpages-summary', - 'whatlinkshere-summary', - 'listredirects-summary', - 'uncategorizedpages-summary', - 'uncategorizedcategories-summary', - 'uncategorizedimages-summary', - 'uncategorizedtemplates-summary', - 'popularpages-summary', - 'wantedcategories-summary', - 'wantedfiles-summary', - 'wantedpages-summary', - 'watchlist-summary', - 'mostlinked-summary', - 'mostlinkedcategories-summary', - 'mostlinkedtemplates-summary', - 'mostcategories-summary', - 'mostimages-summary', - 'mostinterwikis-summary', - 'mostrevisions-summary', - 'prefixindex-summary', - 'shortpages-summary', - 'newpages-summary', - 'ancientpages-summary', - 'unwatchedpages-summary', - 'userrights-summary', - 'brokenredirects-summary', - 'deadendpages-summary', - 'protectedpages-summary', - 'disambiguations-summary', - 'pageswithprop-summary', - 'doubleredirects-summary', - 'lonelypages-summary', - 'unusedtemplates-summary', - 'fewestrevisions-summary', - 'upload-summary', - 'wantedtemplates-summary', - 'activeusers-summary', - 'search-summary', - 'editpage-head-copy-warn', - 'editpage-tos-summary', - 'addsection-preload', - 'addsection-editintro', - 'longpage-hint', - 'javascripttest-backlink', - 'javascripttest-qunit-name', - 'revdelete-logentry', - 'logdelete-logentry', - 'revdelete-content', - 'revdelete-summary', - 'revdelete-uname', - 'revdelete-hid', - 'revdelete-unhid', - 'revdelete-log-message', - 'logdelete-log-message', - 'deletedarticle', - 'suppressedarticle', - 'undeletedarticle', - 'patrol-log-line', - 'patrol-log-auto', - 'patrol-log-diff', - '1movedto2', - '1movedto2_redir', - 'move-redirect-suppressed', - 'newuserlog-create-entry', - 'newuserlog-create2-entry', - 'newuserlog-autocreate-entry', - 'rightslogentry', - 'rightslogentry-autopromote', - 'suppressedarticle', - 'deletedarticle', - // 'uploadedimage', - // 'overwroteimage', - 'createacct-helpusername', - 'createacct-imgcaptcha-help', - 'userlogout-summary', - 'changeemail-summary', - 'changepassword-summary', - 'unusedcategories-summary', - 'unusedimages-summary', - 'deletedcontributions-summary', - 'linksearch-summary', - 'emailuser-summary', - 'undelete-summary', - 'contributions-summary', - 'unblock-summary', - 'movepage-summary', - 'export-summary', - 'import-summary', - 'editwatchlist-summary', - 'version-summary', - 'tags-summary', - 'comparepages-summary', - 'resettokens-summary', - 'version-db-mysql-url', - 'version-db-mariadb-url', - 'version-db-percona-url', - 'version-db-postgres-url', - 'version-db-oracle-url', - 'version-db-sqlite-url', - 'version-db-mssql-url', - 'version-entrypoints-index-php', - 'version-entrypoints-api-php', - 'version-entrypoints-load-php', - 'ipb-default-expiry', - 'pageinfo-header', - 'pageinfo-footer', - 'createacct-benefit-head1', - 'createacct-benefit-icon1', - 'createacct-benefit-head2', - 'createacct-benefit-icon2', - 'createacct-benefit-head3', - 'createacct-benefit-icon3', - 'today-at', - 'redirect-text', - 'helppage', - 'edithelppage', - 'helplogin-url', - 'autocomment-prefix', - 'move-redirect-text', -); - -/** Optional messages, which may be translated only if changed in the target language. */ -$wgOptionalMessages = array( - 'linkprefix', - 'feed-atom', - 'feed-rss', - 'unit-pixel', - 'userrights-irreversible-marker', - 'tog-noconvertlink', - 'variantname-zh-hans', - 'variantname-zh-hant', - 'variantname-zh-cn', - 'variantname-zh-tw', - 'variantname-zh-hk', - 'variantname-zh-mo', - 'variantname-zh-my', - 'variantname-zh-sg', - 'variantname-zh', - 'variantname-gan-hans', - 'variantname-gan-hant', - 'variantname-gan', - 'variantname-sr-ec', - 'variantname-sr-el', - 'variantname-sr', - 'variantname-kk-arab', - 'variantname-kk-cyrl', - 'variantname-kk-latn', - 'variantname-kk-tr', - 'variantname-kk-kz', - 'variantname-kk-cn', - 'variantname-kk', - 'variantname-ku-latn', - 'variantname-ku-arab', - 'variantname-ku', - 'variantname-tg-cyrl', - 'variantname-tg-latn', - 'variantname-tg', - 'variantname-ike-cans', - 'variantname-ike-latn', - 'variantname-iu', - 'variantname-shi-tfng', - 'variantname-shi-latn', - 'variantname-shi', - 'rc-change-size', - 'resetpass_text', - 'image_sample', - 'media_sample', - 'skinname-cologneblue', - 'skinname-monobook', - 'skinname-modern', - 'skinname-vector', - 'common.css', - 'cologneblue.css', - 'monobook.css', - 'modern.css', - 'vector.css', - 'print.css', - 'noscript.css', - 'group-autoconfirmed.css', - 'group-bot.css', - 'group-sysop.css', - 'group-bureaucrat.css', - 'common.js', - 'cologneblue.js', - 'monobook.js', - 'modern.js', - 'vector.js', - 'group-autoconfirmed.js', - 'group-bot.js', - 'group-sysop.js', - 'group-bureaucrat.js', - 'widthheight', - 'exif-fnumber-format', - 'exif-focallength-format', - 'exif-compression-5', - 'exif-compression-6', - 'exif-compression-7', - 'exif-compression-8', - 'exif-compression-32773', - 'exif-compression-32946', - 'exif-compression-34712', - 'exif-photometricinterpretation-2', - 'exif-photometricinterpretation-6', - 'exif-xyresolution-i', - 'exif-xyresolution-c', - 'exif-colorspace-1', - 'exif-componentsconfiguration-1', - 'exif-componentsconfiguration-2', - 'exif-componentsconfiguration-3', - 'exif-componentsconfiguration-4', - 'exif-componentsconfiguration-5', - 'exif-componentsconfiguration-6', - 'exif-contact-value', - 'exif-coordinate-format', - 'exif-lightsource-20', - 'exif-lightsource-21', - 'exif-lightsource-22', - 'exif-lightsource-23', - 'exif-maxaperturevalue-value', - 'exif-subjectnewscode-value', - 'booksources-isbn', - 'protect-summary-desc', - 'sp-contributions-explain', - 'sorbs', - 'video-dims', - 'seconds-abbrev', - 'minutes-abbrev', - 'hours-abbrev', - 'days-abbrev', - 'pagetitle', - 'filename-prefix-blacklist', - 'edittools', - 'edittools-upload', - 'size-bytes', - 'size-kilobytes', - 'size-megabytes', - 'size-gigabytes', - 'size-terabytes', - 'size-petabytes', - 'size-exabytes', - 'size-zetabytes', - 'size-yottabytes', - 'bitrate-bits', - 'bitrate-kilobits', - 'bitrate-megabits', - 'bitrate-gigabits', - 'bitrate-terabits', - 'bitrate-petabits', - 'bitrate-exabits', - 'bitrate-zetabits', - 'bitrate-yottabits', - 'iranian-calendar-m1', - 'iranian-calendar-m2', - 'iranian-calendar-m3', - 'iranian-calendar-m4', - 'iranian-calendar-m5', - 'iranian-calendar-m6', - 'iranian-calendar-m7', - 'iranian-calendar-m8', - 'iranian-calendar-m9', - 'iranian-calendar-m10', - 'iranian-calendar-m11', - 'iranian-calendar-m12', - 'hijri-calendar-m1', - 'hijri-calendar-m2', - 'hijri-calendar-m3', - 'hijri-calendar-m4', - 'hijri-calendar-m5', - 'hijri-calendar-m6', - 'hijri-calendar-m7', - 'hijri-calendar-m8', - 'hijri-calendar-m9', - 'hijri-calendar-m10', - 'hijri-calendar-m11', - 'hijri-calendar-m12', - 'hebrew-calendar-m1', - 'hebrew-calendar-m2', - 'hebrew-calendar-m3', - 'hebrew-calendar-m4', - 'hebrew-calendar-m5', - 'hebrew-calendar-m6', - 'hebrew-calendar-m6a', - 'hebrew-calendar-m6b', - 'hebrew-calendar-m7', - 'hebrew-calendar-m8', - 'hebrew-calendar-m9', - 'hebrew-calendar-m10', - 'hebrew-calendar-m11', - 'hebrew-calendar-m12', - 'hebrew-calendar-m1-gen', - 'hebrew-calendar-m2-gen', - 'hebrew-calendar-m3-gen', - 'hebrew-calendar-m4-gen', - 'hebrew-calendar-m5-gen', - 'hebrew-calendar-m6-gen', - 'hebrew-calendar-m6a-gen', - 'hebrew-calendar-m6b-gen', - 'hebrew-calendar-m7-gen', - 'hebrew-calendar-m8-gen', - 'hebrew-calendar-m9-gen', - 'hebrew-calendar-m10-gen', - 'hebrew-calendar-m11-gen', - 'hebrew-calendar-m12-gen', - 'version-api', - 'version-svn-revision', - 'semicolon-separator', - 'comma-separator', - 'colon-separator', - 'pipe-separator', - 'word-separator', - 'ellipsis', - 'percent', - 'parentheses', - 'brackets', - 'listgrouprights-right-display', - 'listgrouprights-right-revoked', - 'timezone-utc', - 'unpatrolledletter', - 'diff-with-additional', - 'pagetitle-view-mainpage', - 'backlinksubtitle', - 'prefs-registration-date-time', - 'prefs-memberingroups-type', - 'userrights-groupsmember-type', - 'shared-repo-name-wikimediacommons', - 'usermessage-template', - 'filepage.css', - 'metadata-langitem', - 'metadata-langitem-default', - 'nocookiesforlogin', - 'version-entrypoints-articlepath', - 'version-entrypoints-scriptpath', - 'mergehistory-revisionrow', - 'categoryviewer-pagedlinks', - 'undelete-revisionrow', - 'pageinfo-redirects-value', - 'created', // @deprecated. Remove in MediaWiki 1.23. - 'changed', // @deprecated. Remove in MediaWiki 1.23. - 'limitreport-ppvisitednodes-value', - 'limitreport-ppgeneratednodes-value', - 'limitreport-expansiondepth-value', - 'limitreport-expensivefunctioncount-value', - 'tooltip-iwiki', -); - -/** Exif messages, which may be set as optional in several checks, but are generally mandatory */ -$wgEXIFMessages = array( - 'exif-imagewidth', - 'exif-imagelength', - 'exif-bitspersample', - 'exif-compression', - 'exif-photometricinterpretation', - 'exif-orientation', - 'exif-samplesperpixel', - 'exif-planarconfiguration', - 'exif-ycbcrsubsampling', - 'exif-ycbcrpositioning', - 'exif-xresolution', - 'exif-yresolution', - 'exif-stripoffsets', - 'exif-rowsperstrip', - 'exif-stripbytecounts', - 'exif-jpeginterchangeformat', - 'exif-jpeginterchangeformatlength', - 'exif-whitepoint', - 'exif-primarychromaticities', - 'exif-ycbcrcoefficients', - 'exif-referenceblackwhite', - 'exif-datetime', - 'exif-imagedescription', - 'exif-make', - 'exif-model', - 'exif-software', - 'exif-artist', - 'exif-copyright', - 'exif-exifversion', - 'exif-flashpixversion', - 'exif-colorspace', - 'exif-componentsconfiguration', - 'exif-compressedbitsperpixel', - 'exif-pixelydimension', - 'exif-pixelxdimension', - 'exif-usercomment', - 'exif-relatedsoundfile', - 'exif-datetimeoriginal', - 'exif-datetimedigitized', - 'exif-subsectime', - 'exif-subsectimeoriginal', - 'exif-subsectimedigitized', - 'exif-exposuretime', - 'exif-exposuretime-format', - 'exif-fnumber', - 'exif-fnumber-format', - 'exif-exposureprogram', - 'exif-spectralsensitivity', - 'exif-isospeedratings', - 'exif-shutterspeedvalue', - 'exif-aperturevalue', - 'exif-brightnessvalue', - 'exif-exposurebiasvalue', - 'exif-maxaperturevalue', - 'exif-subjectdistance', - 'exif-meteringmode', - 'exif-lightsource', - 'exif-flash', - 'exif-focallength', - 'exif-focallength-format', - 'exif-subjectarea', - 'exif-flashenergy', - 'exif-focalplanexresolution', - 'exif-focalplaneyresolution', - 'exif-focalplaneresolutionunit', - 'exif-subjectlocation', - 'exif-exposureindex', - 'exif-sensingmethod', - 'exif-filesource', - 'exif-scenetype', - 'exif-customrendered', - 'exif-exposuremode', - 'exif-whitebalance', - 'exif-digitalzoomratio', - 'exif-focallengthin35mmfilm', - 'exif-scenecapturetype', - 'exif-gaincontrol', - 'exif-contrast', - 'exif-saturation', - 'exif-sharpness', - 'exif-devicesettingdescription', - 'exif-subjectdistancerange', - 'exif-imageuniqueid', - 'exif-gpsversionid', - 'exif-gpslatituderef', - 'exif-gpslatitude', - 'exif-gpslongituderef', - 'exif-gpslongitude', - 'exif-gpsaltituderef', - 'exif-gpsaltitude', - 'exif-gpstimestamp', - 'exif-gpssatellites', - 'exif-gpsstatus', - 'exif-gpsmeasuremode', - 'exif-gpsdop', - 'exif-gpsspeedref', - 'exif-gpsspeed', - 'exif-gpstrackref', - 'exif-gpstrack', - 'exif-gpsimgdirectionref', - 'exif-gpsimgdirection', - 'exif-gpsmapdatum', - 'exif-gpsdestlatituderef', - 'exif-gpsdestlatitude', - 'exif-gpsdestlongituderef', - 'exif-gpsdestlongitude', - 'exif-gpsdestbearingref', - 'exif-gpsdestbearing', - 'exif-gpsdestdistanceref', - 'exif-gpsdestdistance', - 'exif-gpsprocessingmethod', - 'exif-gpsareainformation', - 'exif-gpsdatestamp', - 'exif-gpsdifferential', - 'exif-coordinate-format', - 'exif-jpegfilecomment', - 'exif-keywords', - 'exif-worldregioncreated', - 'exif-countrycreated', - 'exif-countrycodecreated', - 'exif-provinceorstatecreated', - 'exif-citycreated', - 'exif-sublocationcreated', - 'exif-worldregiondest', - 'exif-countrydest', - 'exif-countrycodedest', - 'exif-provinceorstatedest', - 'exif-citydest', - 'exif-sublocationdest', - 'exif-objectname', - 'exif-specialinstructions', - 'exif-headline', - 'exif-credit', - 'exif-source', - 'exif-editstatus', - 'exif-urgency', - 'exif-fixtureidentifier', - 'exif-locationdest', - 'exif-locationdestcode', - 'exif-objectcycle', - 'exif-contact', - 'exif-writer', - 'exif-languagecode', - 'exif-iimversion', - 'exif-iimcategory', - 'exif-iimsupplementalcategory', - 'exif-datetimeexpires', - 'exif-datetimereleased', - 'exif-originaltransmissionref', - 'exif-identifier', - 'exif-lens', - 'exif-serialnumber', - 'exif-cameraownername', - 'exif-label', - 'exif-datetimemetadata', - 'exif-nickname', - 'exif-rating', - 'exif-rightscertificate', - 'exif-copyrighted', - 'exif-copyrightowner', - 'exif-usageterms', - 'exif-webstatement', - 'exif-originaldocumentid', - 'exif-licenseurl', - 'exif-morepermissionsurl', - 'exif-attributionurl', - 'exif-preferredattributionname', - 'exif-pngfilecomment', - 'exif-disclaimer', - 'exif-contentwarning', - 'exif-giffilecomment', - 'exif-intellectualgenre', - 'exif-subjectnewscode', - 'exif-scenecode', - 'exif-event', - 'exif-organisationinimage', - 'exif-personinimage', - 'exif-originalimageheight', - 'exif-originalimagewidth', - 'exif-make-value', - 'exif-model-value', - 'exif-software-value', - 'exif-software-version-value', - 'exif-contact-value', - 'exif-subjectnewscode-value', - 'exif-compression-1', - 'exif-compression-2', - 'exif-compression-3', - 'exif-compression-4', - 'exif-compression-5', - 'exif-compression-6', - 'exif-compression-7', - 'exif-compression-8', - 'exif-compression-32773', - 'exif-compression-32946', - 'exif-compression-34712', - 'exif-copyrighted-true', - 'exif-copyrighted-false', - 'exif-photometricinterpretation-2', - 'exif-photometricinterpretation-6', - 'exif-unknowndate', - 'exif-orientation-1', - 'exif-orientation-2', - 'exif-orientation-3', - 'exif-orientation-4', - 'exif-orientation-5', - 'exif-orientation-6', - 'exif-orientation-7', - 'exif-orientation-8', - 'exif-planarconfiguration-1', - 'exif-planarconfiguration-2', - 'exif-xyresolution-i', - 'exif-xyresolution-c', - 'exif-colorspace-1', - 'exif-colorspace-65535', - 'exif-componentsconfiguration-0', - 'exif-componentsconfiguration-1', - 'exif-componentsconfiguration-2', - 'exif-componentsconfiguration-3', - 'exif-componentsconfiguration-4', - 'exif-componentsconfiguration-5', - 'exif-componentsconfiguration-6', - 'exif-exposureprogram-0', - 'exif-exposureprogram-1', - 'exif-exposureprogram-2', - 'exif-exposureprogram-3', - 'exif-exposureprogram-4', - 'exif-exposureprogram-5', - 'exif-exposureprogram-6', - 'exif-exposureprogram-7', - 'exif-exposureprogram-8', - 'exif-subjectdistance-value', - 'exif-meteringmode-0', - 'exif-meteringmode-1', - 'exif-meteringmode-2', - 'exif-meteringmode-3', - 'exif-meteringmode-4', - 'exif-meteringmode-5', - 'exif-meteringmode-6', - 'exif-meteringmode-255', - 'exif-lightsource-0', - 'exif-lightsource-1', - 'exif-lightsource-2', - 'exif-lightsource-3', - 'exif-lightsource-4', - 'exif-lightsource-9', - 'exif-lightsource-10', - 'exif-lightsource-11', - 'exif-lightsource-12', - 'exif-lightsource-13', - 'exif-lightsource-14', - 'exif-lightsource-15', - 'exif-lightsource-17', - 'exif-lightsource-18', - 'exif-lightsource-19', - 'exif-lightsource-20', - 'exif-lightsource-21', - 'exif-lightsource-22', - 'exif-lightsource-23', - 'exif-lightsource-24', - 'exif-lightsource-255', - 'exif-flash-fired-0', - 'exif-flash-fired-1', - 'exif-flash-return-0', - 'exif-flash-return-2', - 'exif-flash-return-3', - 'exif-flash-mode-1', - 'exif-flash-mode-2', - 'exif-flash-mode-3', - 'exif-flash-function-1', - 'exif-flash-redeye-1', - 'exif-focalplaneresolutionunit-2', - 'exif-sensingmethod-1', - 'exif-sensingmethod-2', - 'exif-sensingmethod-3', - 'exif-sensingmethod-4', - 'exif-sensingmethod-5', - 'exif-sensingmethod-7', - 'exif-sensingmethod-8', - 'exif-filesource-3', - 'exif-scenetype-1', - 'exif-customrendered-0', - 'exif-customrendered-1', - 'exif-exposuremode-0', - 'exif-exposuremode-1', - 'exif-exposuremode-2', - 'exif-whitebalance-0', - 'exif-whitebalance-1', - 'exif-scenecapturetype-0', - 'exif-scenecapturetype-1', - 'exif-scenecapturetype-2', - 'exif-scenecapturetype-3', - 'exif-gaincontrol-0', - 'exif-gaincontrol-1', - 'exif-gaincontrol-2', - 'exif-gaincontrol-3', - 'exif-gaincontrol-4', - 'exif-contrast-0', - 'exif-contrast-1', - 'exif-contrast-2', - 'exif-saturation-0', - 'exif-saturation-1', - 'exif-saturation-2', - 'exif-sharpness-0', - 'exif-sharpness-1', - 'exif-sharpness-2', - 'exif-subjectdistancerange-0', - 'exif-subjectdistancerange-1', - 'exif-subjectdistancerange-2', - 'exif-subjectdistancerange-3', - 'exif-gpslatitude-n', - 'exif-gpslatitude-s', - 'exif-gpslongitude-e', - 'exif-gpslongitude-w', - 'exif-gpsaltitude-above-sealevel', - 'exif-gpsaltitude-below-sealevel', - 'exif-gpsstatus-a', - 'exif-gpsstatus-v', - 'exif-gpsmeasuremode-2', - 'exif-gpsmeasuremode-3', - 'exif-gpsspeed-k', - 'exif-gpsspeed-m', - 'exif-gpsspeed-n', - 'exif-gpsdestdistance-k', - 'exif-gpsdestdistance-m', - 'exif-gpsdestdistance-n', - 'exif-gpsdop-excellent', - 'exif-gpsdop-good', - 'exif-gpsdop-moderate', - 'exif-gpsdop-fair', - 'exif-gpsdop-poor', - 'exif-objectcycle-a', - 'exif-objectcycle-p', - 'exif-objectcycle-b', - 'exif-gpsdirection-t', - 'exif-gpsdirection-m', - 'exif-ycbcrpositioning-1', - 'exif-ycbcrpositioning-2', - 'exif-dc-contributor', - 'exif-dc-coverage', - 'exif-dc-date', - 'exif-dc-publisher', - 'exif-dc-relation', - 'exif-dc-rights', - 'exif-dc-source', - 'exif-dc-type', - 'exif-rating-rejected', - 'exif-isospeedratings-overflow', - 'exif-maxaperturevalue-value', - 'exif-iimcategory-ace', - 'exif-iimcategory-clj', - 'exif-iimcategory-dis', - 'exif-iimcategory-fin', - 'exif-iimcategory-edu', - 'exif-iimcategory-evn', - 'exif-iimcategory-hth', - 'exif-iimcategory-hum', - 'exif-iimcategory-lab', - 'exif-iimcategory-lif', - 'exif-iimcategory-pol', - 'exif-iimcategory-rel', - 'exif-iimcategory-sci', - 'exif-iimcategory-soi', - 'exif-iimcategory-spo', - 'exif-iimcategory-war', - 'exif-iimcategory-wea', - 'exif-urgency-normal', - 'exif-urgency-low', - 'exif-urgency-high', - 'exif-urgency-other', -); diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc deleted file mode 100644 index e351549b..00000000 --- a/maintenance/language/messages.inc +++ /dev/null @@ -1,4239 +0,0 @@ -<?php -/** - * Define the messages structure in the messages file, for an automated rewriting. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup MaintenanceLanguage - */ - -/** The structure of the messages, divided to blocks */ -$wgMessageStructure = array( - 'sidebar' => array( - 'sidebar', - ), - 'toggles' => array( - 'tog-underline', - 'tog-justify', - 'tog-hideminor', - 'tog-hidepatrolled', - 'tog-newpageshidepatrolled', - 'tog-extendwatchlist', - 'tog-usenewrc', - 'tog-numberheadings', - 'tog-showtoolbar', - 'tog-editondblclick', - 'tog-editsection', - 'tog-editsectiononrightclick', - 'tog-showtoc', - 'tog-rememberpassword', - 'tog-watchcreations', - 'tog-watchdefault', - 'tog-watchmoves', - 'tog-watchdeletion', - 'tog-minordefault', - 'tog-previewontop', - 'tog-previewonfirst', - 'tog-nocache', - 'tog-enotifwatchlistpages', - 'tog-enotifusertalkpages', - 'tog-enotifminoredits', - 'tog-enotifrevealaddr', - 'tog-shownumberswatching', - 'tog-oldsig', - 'tog-fancysig', - 'tog-uselivepreview', - 'tog-forceeditsummary', - 'tog-watchlisthideown', - 'tog-watchlisthidebots', - 'tog-watchlisthideminor', - 'tog-watchlisthideliu', - 'tog-watchlisthideanons', - 'tog-watchlisthidepatrolled', - 'tog-ccmeonemails', - 'tog-diffonly', - 'tog-showhiddencats', - 'tog-noconvertlink', - 'tog-norollbackdiff', - 'tog-useeditwarning', - 'tog-prefershttps' - ), - 'underline' => array( - 'underline-always', - 'underline-never', - 'underline-default', - ), - 'editfont' => array( - 'editfont-style', - 'editfont-default', - 'editfont-monospace', - 'editfont-sansserif', - 'editfont-serif', - ), - 'dates' => array( - 'sunday', - 'monday', - 'tuesday', - 'wednesday', - 'thursday', - 'friday', - 'saturday', - 'sun', - 'mon', - 'tue', - 'wed', - 'thu', - 'fri', - 'sat', - 'january', - 'february', - 'march', - 'april', - 'may_long', - 'june', - 'july', - 'august', - 'september', - 'october', - 'november', - 'december', - 'january-gen', - 'february-gen', - 'march-gen', - 'april-gen', - 'may-gen', - 'june-gen', - 'july-gen', - 'august-gen', - 'september-gen', - 'october-gen', - 'november-gen', - 'december-gen', - 'jan', - 'feb', - 'mar', - 'apr', - 'may', - 'jun', - 'jul', - 'aug', - 'sep', - 'oct', - 'nov', - 'dec', - 'january-date', - 'february-date', - 'march-date', - 'april-date', - 'may-date', - 'june-date', - 'july-date', - 'august-date', - 'september-date', - 'october-date', - 'november-date', - 'december-date', - ), - 'categorypages' => array( - 'pagecategories', - 'pagecategorieslink', - 'category_header', - 'subcategories', - 'category-media-header', - 'category-empty', - 'hidden-categories', - 'hidden-category-category', - 'category-subcat-count', - 'category-subcat-count-limited', - 'category-article-count', - 'category-article-count-limited', - 'category-file-count', - 'category-file-count-limited', - 'listingcontinuesabbrev', - 'index-category', - 'noindex-category', - 'broken-file-category', - 'categoryviewer-pagedlinks', - ), - 'mainpage' => array( - 'linkprefix', - ), - 'miscellaneous1' => array( - 'about', - 'article', - 'newwindow', - 'cancel', - 'moredotdotdot', - 'morenotlisted', - 'mypage', - 'mytalk', - 'anontalk', - 'navigation', - 'and', - ), - 'cologneblue' => array( - 'qbfind', - 'qbbrowse', - 'qbedit', - 'qbpageoptions', - 'qbmyoptions', - 'qbspecialpages', - 'faq', - 'faqpage', - 'sitetitle', - 'sitesubtitle', - ), - 'vector' => array( - 'vector-action-addsection', - 'vector-action-delete', - 'vector-action-move', - 'vector-action-protect', - 'vector-action-undelete', - 'vector-action-unprotect', - 'vector-simplesearch-preference', - 'vector-view-create', - 'vector-view-edit', - 'vector-view-history', - 'vector-view-view', - 'vector-view-viewsource', - 'actions', - 'namespaces', - 'variants', - ), - 'miscellaneous2' => array( - 'navigation-heading', - 'errorpagetitle', - 'returnto', - 'tagline', - 'help', - 'search', - 'searchbutton', - 'go', - 'searcharticle', - 'history', - 'history_short', - 'updatedmarker', - 'printableversion', - 'permalink', - 'print', - 'view', - 'edit', - 'create', - 'editthispage', - 'create-this-page', - 'delete', - 'deletethispage', - 'undeletethispage', - 'undelete_short', - 'viewdeleted_short', - 'protect', - 'protect_change', - 'protectthispage', - 'unprotect', - 'unprotectthispage', - 'newpage', - 'talkpage', - 'talkpagelinktext', - 'specialpage', - 'personaltools', - 'postcomment', - 'addsection', - 'articlepage', - 'talk', - 'views', - 'toolbox', - 'userpage', - 'projectpage', - 'imagepage', - 'mediawikipage', - 'templatepage', - 'viewhelppage', - 'categorypage', - 'viewtalkpage', - 'otherlanguages', - 'redirectedfrom', - 'redirectpagesub', - 'talkpageheader', - 'lastmodifiedat', - 'viewcount', - 'protectedpage', - 'jumpto', - 'jumptonavigation', - 'jumptosearch', - 'view-pool-error', - 'pool-timeout', - 'pool-queuefull', - 'pool-errorunknown', - ), - 'links' => array( - 'aboutsite', - 'aboutpage', - 'copyright', - 'copyrightpage', - 'currentevents', - 'currentevents-url', - 'disclaimers', - 'disclaimerpage', - 'edithelp', - 'edithelppage', - 'help', - 'helppage', - 'mainpage', - 'mainpage-description', - 'policy-url', - 'portal', - 'portal-url', - 'privacy', - 'privacypage', - ), - 'badaccess' => array( - 'badaccess', - 'badaccess-group0', - 'badaccess-groups', - ), - 'versionrequired' => array( - 'versionrequired', - 'versionrequiredtext', - ), - 'miscellaneous3' => array( - 'ok', - 'pagetitle', - 'pagetitle-view-mainpage', - 'backlinksubtitle', - 'retrievedfrom', - 'youhavenewmessages', - 'newmessageslink', - 'newmessagesdifflink', - 'youhavenewmessagesfromusers', - 'youhavenewmessagesmanyusers', - 'newmessageslinkplural', - 'newmessagesdifflinkplural', - 'youhavenewmessagesmulti', - 'newtalkseparator', - 'editsection', - 'editold', - 'viewsourceold', - 'editlink', - 'viewsourcelink', - 'editsectionhint', - 'toc', - 'showtoc', - 'hidetoc', - 'collapsible-collapse', - 'collapsible-expand', - 'thisisdeleted', - 'viewdeleted', - 'restorelink', - 'feedlinks', - 'feed-invalid', - 'feed-unavailable', - 'site-rss-feed', - 'site-atom-feed', - 'page-rss-feed', - 'page-atom-feed', - 'feed-atom', - 'feed-rss', - 'sitenotice', - 'anonnotice', - 'newsectionheaderdefaultlevel', - 'red-link-title', - 'sort-descending', - 'sort-ascending', - - ), - 'nstab' => array( - 'nstab-main', - 'nstab-user', - 'nstab-media', - 'nstab-special', - 'nstab-project', - 'nstab-image', - 'nstab-mediawiki', - 'nstab-template', - 'nstab-help', - 'nstab-category', - 'mainpage-nstab', - ), - 'main' => array( - 'nosuchaction', - 'nosuchactiontext', - 'nosuchspecialpage', - 'nospecialpagetext', - ), - 'errors' => array( - 'error', - 'databaseerror', - 'databaseerror-text', - 'databaseerror-textcl', - 'databaseerror-query', - 'databaseerror-function', - 'databaseerror-error', - 'laggedslavemode', - 'readonly', - 'enterlockreason', - 'readonlytext', - 'missing-article', // not used anymore in core, but kept for extensions - 'missingarticle-rev', // not used anymore in core, but kept for extensions - 'missingarticle-diff', // not used anymore in core, but kept for extensions - 'readonly_lag', - 'internalerror', - 'internalerror_info', - 'fileappenderrorread', - 'fileappenderror', - 'filecopyerror', - 'filerenameerror', - 'filedeleteerror', - 'directorycreateerror', - 'filenotfound', - 'fileexistserror', - 'unexpected', - 'formerror', - 'badarticleerror', - 'cannotdelete', - 'cannotdelete-title', - 'delete-hook-aborted', - 'no-null-revision', - 'badtitle', - 'badtitletext', - 'perfcached', - 'perfcachedts', - 'querypage-no-updates', - 'wrong_wfQuery_params', - 'viewsource', - 'viewsource-title', - 'actionthrottled', - 'actionthrottledtext', - 'protectedpagetext', - 'viewsourcetext', - 'viewyourtext', - 'protectedinterface', - 'editinginterface', - 'cascadeprotected', - 'namespaceprotected', - 'customcssprotected', - 'customjsprotected', - 'mycustomcssprotected', - 'mycustomjsprotected', - 'myprivateinfoprotected', - 'mypreferencesprotected', - 'ns-specialprotected', - 'titleprotected', - 'filereadonlyerror', - 'invalidtitle-knownnamespace', - 'invalidtitle-unknownnamespace', - 'exception-nologin', - 'exception-nologin-text', - ), - 'virus' => array( - 'virus-badscanner', - 'virus-scanfailed', - 'virus-unknownscanner', - ), - 'login' => array( - 'logouttext', - 'welcomeuser', - 'welcomecreation-msg', - 'yourname', - 'userlogin-yourname', - 'userlogin-yourname-ph', - 'createacct-another-username-ph', - 'createacct-helpusername', - 'yourpassword', - 'userlogin-yourpassword', - 'userlogin-yourpassword-ph', - 'createacct-yourpassword-ph', - 'yourpasswordagain', - 'createacct-yourpasswordagain', - 'createacct-yourpasswordagain-ph', - 'remembermypassword', - 'userlogin-remembermypassword', - 'userlogin-signwithsecure', - 'yourdomainname', - 'password-change-forbidden', - 'externaldberror', - 'login', - 'nav-login-createaccount', - 'loginprompt', - 'userlogin', - 'userloginnocreate', - 'logout', - 'userlogout', - 'userlogout-summary', - 'notloggedin', - 'userlogin-noaccount', - 'userlogin-joinproject', - 'nologin', - 'nologinlink', - 'createaccount', - 'gotaccount', - 'gotaccountlink', - 'userlogin-resetlink', - 'userlogin-resetpassword-link', - 'helplogin-url', - 'userlogin-helplink2', - 'userlogin-loggedin', - 'userlogin-createanother', - 'createacct-join', - 'createacct-another-join', - 'createacct-emailrequired', - 'createacct-emailoptional', - 'createacct-email-ph', - 'createacct-another-email-ph', - 'createaccountmail', - 'createacct-realname', - 'createaccountreason', - 'createacct-reason', - 'createacct-reason-ph', - 'createacct-captcha', - 'createacct-imgcaptcha-help', - 'createacct-imgcaptcha-ph', - 'createacct-submit', - 'createacct-another-submit', - 'createacct-benefit-heading', - 'createacct-benefit-icon1', - 'createacct-benefit-head1', - 'createacct-benefit-body1', - 'createacct-benefit-icon2', - 'createacct-benefit-head2', - 'createacct-benefit-body2', - 'createacct-benefit-icon3', - 'createacct-benefit-head3', - 'createacct-benefit-body3', - 'badretype', - 'userexists', - 'loginerror', - 'createacct-error', - 'createaccounterror', - 'nocookiesnew', - 'nocookieslogin', - 'nocookiesfornew', - 'nocookiesforlogin', - 'noname', - 'loginsuccesstitle', - 'loginsuccess', - 'nosuchuser', - 'nosuchusershort', - 'nouserspecified', - 'login-userblocked', - 'wrongpassword', - 'wrongpasswordempty', - 'passwordtooshort', - 'password-name-match', - 'password-login-forbidden', - 'mailmypassword', - 'passwordremindertitle', - 'passwordremindertext', - 'noemail', - 'noemailcreate', - 'passwordsent', - 'blocked-mailpassword', - 'eauthentsent', - 'throttled-mailpassword', - 'loginstart', - 'loginend', - 'loginend-https', - 'signupstart', - 'signupend', - 'signupend-https', - 'mailerror', - 'acct_creation_throttle_hit', - 'emailauthenticated', - 'emailnotauthenticated', - 'noemailprefs', - 'emailconfirmlink', - 'invalidemailaddress', - 'cannotchangeemail', - 'emaildisabled', - 'accountcreated', - 'accountcreatedtext', - 'createaccount-title', - 'createaccount-text', - 'usernamehasherror', - 'login-throttled', - 'login-abort-generic', - 'loginlanguagelabel', - 'loginlanguagelinks', - 'suspicious-userlogout', - 'createacct-another-realname-tip', - ), - 'mail' => array( - 'pear-mail-error', - 'php-mail-error', - 'php-mail-error-unknown', - 'user-mail-no-addy', - 'user-mail-no-body', - ), - 'resetpass' => array( - 'resetpass', - 'resetpass_announce', - 'resetpass_text', - 'resetpass_header', - 'oldpassword', - 'newpassword', - 'retypenew', - 'resetpass_submit', - 'changepassword-success', - 'resetpass_forbidden', - 'resetpass-no-info', - 'resetpass-submit-loggedin', - 'resetpass-submit-cancel', - 'resetpass-wrong-oldpass', - 'resetpass-temp-password', - 'resetpass-abort-generic', - ), - 'passwordreset' => array( - 'passwordreset', - 'passwordreset-text-one', - 'passwordreset-text-many', - 'passwordreset-legend', - 'passwordreset-disabled', - 'passwordreset-emaildisabled', - 'passwordreset-username', - 'passwordreset-domain', - 'passwordreset-capture', - 'passwordreset-capture-help', - 'passwordreset-email', - 'passwordreset-emailtitle', - 'passwordreset-emailtext-ip', - 'passwordreset-emailtext-user', - 'passwordreset-emailelement', - 'passwordreset-emailsent', - 'passwordreset-emailsent-capture', - 'passwordreset-emailerror-capture', - ), - 'changeemail' => array( - 'changeemail', - 'changeemail-summary', - 'changeemail-header', - 'changeemail-text', - 'changeemail-no-info', - 'changeemail-oldemail', - 'changeemail-newemail', - 'changeemail-none', - 'changeemail-password', - 'changeemail-submit', - 'changeemail-cancel', - ), - 'resettokens' => array( - 'resettokens', - 'resettokens-summary', - 'resettokens-text', - 'resettokens-no-tokens', - 'resettokens-legend', - 'resettokens-tokens', - 'resettokens-token-label', - 'resettokens-watchlist-token', - 'resettokens-done', - 'resettokens-resetbutton', - ), - 'toolbar' => array( - 'bold_sample', - 'bold_tip', - 'italic_sample', - 'italic_tip', - 'link_sample', - 'link_tip', - 'extlink_sample', - 'extlink_tip', - 'headline_sample', - 'headline_tip', - 'nowiki_sample', - 'nowiki_tip', - 'image_sample', - 'image_tip', - 'media_sample', - 'media_tip', - 'sig_tip', - 'hr_tip', - ), - 'edit' => array( - 'summary', - 'subject', - 'minoredit', - 'watchthis', - 'savearticle', - 'preview', - 'showpreview', - 'showlivepreview', - 'showdiff', - 'anoneditwarning', - 'anonpreviewwarning', - 'missingsummary', - 'missingcommenttext', - 'missingcommentheader', - 'summary-preview', - 'subject-preview', - 'blockedtitle', - 'blockedtext', - 'autoblockedtext', - 'blockednoreason', - 'whitelistedittext', - 'confirmedittext', - 'nosuchsectiontitle', - 'nosuchsectiontext', - 'loginreqtitle', - 'loginreqlink', - 'loginreqpagetext', - 'accmailtitle', - 'accmailtext', - 'newarticle', - 'newarticletext', - 'newarticletextanon', - 'talkpagetext', - 'anontalkpagetext', - 'noarticletext', - 'noarticletext-nopermission', - 'noarticletextanon', - 'missing-revision', - 'userpage-userdoesnotexist', - 'userpage-userdoesnotexist-view', - 'blocked-notice-logextract', - 'clearyourcache', - 'usercssyoucanpreview', - 'userjsyoucanpreview', - 'usercsspreview', - 'userjspreview', - 'sitecsspreview', - 'sitejspreview', - 'userinvalidcssjstitle', - 'updated', - 'note', - 'previewnote', - 'continue-editing', - 'previewconflict', - 'session_fail_preview', - 'session_fail_preview_html', - 'token_suffix_mismatch', - 'edit_form_incomplete', - 'editing', - 'creating', - 'editingsection', - 'editingcomment', - 'editconflict', - 'explainconflict', - 'yourtext', - 'storedversion', - 'nonunicodebrowser', - 'editingold', - 'yourdiff', - 'copyrightwarning', - 'copyrightwarning2', - 'editpage-head-copy-warn', - 'editpage-tos-summary', - 'longpage-hint', - 'longpageerror', - 'readonlywarning', - 'protectedpagewarning', - 'semiprotectedpagewarning', - 'cascadeprotectedwarning', - 'titleprotectedwarning', - 'templatesused', - 'templatesusedpreview', - 'templatesusedsection', - 'template-protected', - 'template-semiprotected', - 'hiddencategories', - 'edittools', - 'edittools-upload', - 'nocreatetext', - 'nocreate-loggedin', - 'sectioneditnotsupported-title', - 'sectioneditnotsupported-text', - 'permissionserrors', - 'permissionserrorstext', - 'permissionserrorstext-withaction', - 'recreate-moveddeleted-warn', - 'moveddeleted-notice', - 'log-fulllog', - 'edit-hook-aborted', - 'edit-gone-missing', - 'edit-conflict', - 'edit-no-change', - 'postedit-confirmation', - 'edit-already-exists', - 'addsection-preload', - 'addsection-editintro', - 'defaultmessagetext', - 'content-failed-to-parse', - 'invalid-content-data', - 'content-not-allowed-here', - 'editwarning-warning', - ), - 'contentmodels' => array( - 'content-model-wikitext', - 'content-model-text', - 'content-model-javascript', - 'content-model-css', - ), - 'parserwarnings' => array( - 'expensive-parserfunction-warning', - 'expensive-parserfunction-category', - 'post-expand-template-inclusion-warning', - 'post-expand-template-inclusion-category', - 'post-expand-template-argument-warning', - 'post-expand-template-argument-category', - 'parser-template-loop-warning', - 'parser-template-recursion-depth-warning', - 'language-converter-depth-warning', - 'node-count-exceeded-category', - 'node-count-exceeded-warning', - 'expansion-depth-exceeded-category', - 'expansion-depth-exceeded-warning', - 'parser-unstrip-loop-warning', - 'parser-unstrip-recursion-limit', - 'converter-manual-rule-error', - ), - 'undo' => array( - 'undo-success', - 'undo-failure', - 'undo-norev', - 'undo-summary', - 'undo-summary-username-hidden', - ), - 'cantcreateaccount' => array( - 'cantcreateaccounttitle', - 'cantcreateaccount-text', - ), - 'history' => array( - 'viewpagelogs', - 'nohistory', - 'currentrev', - 'currentrev-asof', - 'revisionasof', - 'revision-info', - 'revision-info-current', - 'revision-nav', - 'previousrevision', - 'nextrevision', - 'currentrevisionlink', - 'cur', - 'next', - 'last', - 'page_first', - 'page_last', - 'histlegend', - 'history-fieldset-title', - 'history-show-deleted', - 'history_copyright', - 'histfirst', - 'histlast', - 'historysize', - 'historyempty', - ), - 'history-feed' => array( - 'history-feed-title', - 'history-feed-description', - 'history-feed-item-nocomment', - 'history-feed-empty', - ), - 'revdelete' => array( - 'rev-deleted-comment', - 'rev-deleted-user', - 'rev-deleted-event', - 'rev-deleted-user-contribs', - 'rev-deleted-text-permission', - 'rev-deleted-text-unhide', - 'rev-suppressed-text-unhide', - 'rev-deleted-text-view', - 'rev-suppressed-text-view', - 'rev-deleted-no-diff', - 'rev-suppressed-no-diff', - 'rev-deleted-unhide-diff', - 'rev-suppressed-unhide-diff', - 'rev-deleted-diff-view', - 'rev-suppressed-diff-view', - 'rev-delundel', - 'rev-showdeleted', - 'revisiondelete', - 'revdelete-nooldid-title', - 'revdelete-nooldid-text', - 'revdelete-nologtype-title', - 'revdelete-nologtype-text', - 'revdelete-nologid-title', - 'revdelete-nologid-text', - 'revdelete-no-file', - 'revdelete-show-file-confirm', - 'revdelete-show-file-submit', - 'revdelete-selected', - 'logdelete-selected', - 'revdelete-text', - 'revdelete-confirm', - 'revdelete-suppress-text', - 'revdelete-legend', - 'revdelete-hide-text', - 'revdelete-hide-image', - 'revdelete-hide-name', - 'revdelete-hide-comment', - 'revdelete-hide-user', - 'revdelete-hide-restricted', - 'revdelete-radio-same', - 'revdelete-radio-set', - 'revdelete-radio-unset', - 'revdelete-suppress', - 'revdelete-unsuppress', - 'revdelete-log', - 'revdelete-submit', - 'revdelete-success', - 'revdelete-failure', - 'logdelete-success', - 'logdelete-failure', - 'revdel-restore', - 'revdel-restore-deleted', - 'revdel-restore-visible', - 'pagehist', - 'deletedhist', - 'revdelete-hide-current', - 'revdelete-show-no-access', - 'revdelete-modify-no-access', - 'revdelete-modify-missing', - 'revdelete-no-change', - 'revdelete-concurrent-change', - 'revdelete-only-restricted', - 'revdelete-reason-dropdown', - 'revdelete-otherreason', - 'revdelete-reasonotherlist', - 'revdelete-edit-reasonlist', - 'revdelete-offender', - ), - 'suppression' => array( - 'suppressionlog', - 'suppressionlogtext', - ), - 'mergehistory' => array( - 'mergehistory', - 'mergehistory-header', - 'mergehistory-box', - 'mergehistory-from', - 'mergehistory-into', - 'mergehistory-list', - 'mergehistory-merge', - 'mergehistory-go', - 'mergehistory-submit', - 'mergehistory-empty', - 'mergehistory-success', - 'mergehistory-fail', - 'mergehistory-no-source', - 'mergehistory-no-destination', - 'mergehistory-invalid-source', - 'mergehistory-invalid-destination', - 'mergehistory-autocomment', - 'mergehistory-comment', - 'mergehistory-same-destination', - 'mergehistory-reason', - 'mergehistory-revisionrow' - ), - 'mergelog' => array( - 'mergelog', - 'pagemerge-logentry', - 'revertmerge', - 'mergelogpagetext', - ), - 'diffs' => array( - 'history-title', - 'difference-title', - 'difference-title-multipage', - 'difference-multipage', - 'lineno', - 'compareselectedversions', - 'showhideselectedversions', - 'editundo', - 'diff-empty', - 'diff-multi', - 'diff-multi-manyusers', - 'difference-missing-revision', - ), - 'search' => array( - 'search-summary', - 'searchresults', - 'searchresults-title', - 'searchresulttext', - 'searchsubtitle', - 'searchsubtitleinvalid', - 'toomanymatches', - 'titlematches', - 'notitlematches', - 'textmatches', - 'notextmatches', - 'prevn', - 'nextn', - 'prevn-title', - 'nextn-title', - 'shown-title', - 'viewprevnext', - 'searchmenu-legend', - 'searchmenu-exists', - 'searchmenu-new', - 'searchmenu-new-nocreate', - 'searchmenu-prefix', - 'searchprofile-articles', - 'searchprofile-project', - 'searchprofile-images', - 'searchprofile-everything', - 'searchprofile-advanced', - 'searchprofile-articles-tooltip', - 'searchprofile-project-tooltip', - 'searchprofile-images-tooltip', - 'searchprofile-everything-tooltip', - 'searchprofile-advanced-tooltip', - 'search-result-size', - 'search-result-category-size', - 'search-result-score', - 'search-redirect', - 'search-section', - 'search-suggest', - 'search-interwiki-caption', - 'search-interwiki-default', - 'search-interwiki-custom', - 'search-interwiki-more', - 'search-relatedarticle', - 'mwsuggest-disable', - 'searcheverything-enable', - 'searchrelated', - 'searchall', - 'showingresults', - 'showingresultsnum', - 'showingresultsheader', - 'nonefound', - 'search-nonefound', - 'powersearch', - 'powersearch-legend', - 'powersearch-ns', - 'powersearch-redir', - 'powersearch-field', - 'powersearch-togglelabel', - 'powersearch-toggleall', - 'powersearch-togglenone', - 'search-external', - 'searchdisabled', - 'googlesearch', - 'search-error', - ), - 'opensearch' => array( - 'opensearch-desc', - ), - 'preferences' => array( - 'preferences', - 'preferences-summary', - 'mypreferences', - 'prefs-edits', - 'prefsnologin', - 'prefsnologintext', - 'changepassword', - 'changepassword-summary', - 'prefs-skin', - 'skin-preview', - 'datedefault', - 'prefs-beta', - 'prefs-datetime', - 'prefs-labs', - 'prefs-user-pages', - 'prefs-personal', - 'prefs-rc', - 'prefs-watchlist', - 'prefs-watchlist-days', - 'prefs-watchlist-days-max', - 'prefs-watchlist-edits', - 'prefs-watchlist-edits-max', - 'prefs-watchlist-token', - 'prefs-misc', // continue checking if used from here on (r49916) - 'prefs-resetpass', - 'prefs-changeemail', - 'prefs-setemail', - 'prefs-email', - 'prefs-rendering', - 'saveprefs', - 'resetprefs', - 'restoreprefs', - 'prefs-editing', - 'rows', - 'columns', - 'searchresultshead', - 'resultsperpage', - 'stub-threshold', - 'stub-threshold-disabled', - 'recentchangesdays', - 'recentchangesdays-max', - 'recentchangescount', - 'prefs-help-recentchangescount', - 'prefs-help-watchlist-token2', - 'savedprefs', - 'timezonelegend', - 'localtime', - 'timezoneuseserverdefault', - 'timezoneuseoffset', - 'timezoneoffset', - 'servertime', - 'guesstimezone', - 'timezoneregion-africa', - 'timezoneregion-america', - 'timezoneregion-antarctica', - 'timezoneregion-arctic', - 'timezoneregion-asia', - 'timezoneregion-atlantic', - 'timezoneregion-australia', - 'timezoneregion-europe', - 'timezoneregion-indian', - 'timezoneregion-pacific', - 'allowemail', - 'prefs-searchoptions', - 'prefs-namespaces', - 'defaultns', - 'default', - 'defaultns', - 'prefs-files', - 'prefs-custom-css', - 'prefs-custom-js', - 'prefs-common-css-js', - 'prefs-reset-intro', - 'prefs-emailconfirm-label', - 'youremail', - 'username', - 'uid', - 'prefs-memberingroups', - 'prefs-memberingroups-type', - 'prefs-registration', - 'prefs-registration-date-time', - 'yourrealname', - 'yourlanguage', - 'yourvariant', - 'prefs-help-variant', - 'yournick', - 'prefs-help-signature', - 'badsig', - 'badsiglength', - 'yourgender', - 'gender-unknown', - 'gender-male', - 'gender-female', - 'prefs-help-gender', - 'email', - 'prefs-help-realname', - - # 3 messages depending upon $wgEmailConfirmToEdit and $wgEnableUserEmail - 'prefs-help-email', - 'prefs-help-email-others', - 'prefs-help-email-required', - - 'prefs-info', - 'prefs-i18n', - 'prefs-signature', - 'prefs-dateformat', - 'prefs-timeoffset', - 'prefs-advancedediting', - 'prefs-editor', - 'prefs-preview', - 'prefs-advancedrc', - 'prefs-advancedrendering', - 'prefs-advancedsearchoptions', - 'prefs-advancedwatchlist', - 'prefs-displayrc', - 'prefs-displaysearchoptions', - 'prefs-displaywatchlist', - 'prefs-tokenwatchlist', - 'prefs-diffs', - 'prefs-help-prefershttps', - ), - 'preferences-email' => array( - 'email-address-validity-valid', - 'email-address-validity-invalid', - ), - 'userrights' => array( - 'userrights', - 'userrights-summary', - 'userrights-lookup-user', - 'userrights-user-editname', - 'editusergroup', - 'editinguser', - 'userrights-editusergroup', - 'saveusergroups', - 'userrights-groupsmember', - 'userrights-groupsmember-auto', - 'userrights-groupsmember-type', - 'userrights-groups-help', - 'userrights-reason', - 'userrights-no-interwiki', - 'userrights-nodatabase', - 'userrights-nologin', - 'userrights-notallowed', - 'userrights-changeable-col', - 'userrights-unchangeable-col', - 'userrights-irreversible-marker', - 'userrights-conflict', - 'userrights-removed-self', - ), - 'group' => array( - 'group', - 'group-user', - 'group-autoconfirmed', - 'group-bot', - 'group-sysop', - 'group-bureaucrat', - 'group-suppress', - 'group-all', - ), - 'group-member' => array( - 'group-user-member', - 'group-autoconfirmed-member', - 'group-bot-member', - 'group-sysop-member', - 'group-bureaucrat-member', - 'group-suppress-member', - ), - 'grouppage' => array( - 'grouppage-user', - 'grouppage-autoconfirmed', - 'grouppage-bot', - 'grouppage-sysop', - 'grouppage-bureaucrat', - 'grouppage-suppress', - ), - 'right' => array( - 'right-read', - 'right-edit', - 'right-createpage', - 'right-createtalk', - 'right-createaccount', - 'right-minoredit', - 'right-move', - 'right-move-subpages', - 'right-move-rootuserpages', - 'right-movefile', - 'right-suppressredirect', - 'right-upload', - 'right-reupload', - 'right-reupload-own', - 'right-reupload-shared', - 'right-upload_by_url', - 'right-purge', - 'right-autoconfirmed', - 'right-bot', - 'right-nominornewtalk', - 'right-apihighlimits', - 'right-writeapi', - 'right-delete', - 'right-bigdelete', - 'right-deletelogentry', - 'right-deleterevision', - 'right-deletedhistory', - 'right-deletedtext', - 'right-browsearchive', - 'right-undelete', - 'right-suppressrevision', - 'right-suppressionlog', - 'right-block', - 'right-blockemail', - 'right-hideuser', - 'right-ipblock-exempt', - 'right-proxyunbannable', - 'right-unblockself', - 'right-protect', - 'right-editprotected', - 'right-editsemiprotected', - 'right-editinterface', - 'right-editusercssjs', - 'right-editusercss', - 'right-edituserjs', - 'right-editmyusercss', - 'right-editmyuserjs', - 'right-viewmywatchlist', - 'right-editmywatchlist', - 'right-viewmyprivateinfo', - 'right-editmyprivateinfo', - 'right-editmyoptions', - 'right-rollback', - 'right-markbotedits', - 'right-noratelimit', - 'right-import', - 'right-importupload', - 'right-patrol', - 'right-autopatrol', - 'right-patrolmarks', - 'right-unwatchedpages', - 'right-mergehistory', - 'right-userrights', - 'right-userrights-interwiki', - 'right-siteadmin', - 'right-override-export-depth', - 'right-sendemail', - 'right-passwordreset', - ), - 'newuserlog' => array( - 'newuserlogpage', - 'newuserlogpagetext', - ), - 'rightslog' => array( - 'rightslog', - 'rightslogtext', - ), - 'action' => array( - 'action-read', - 'action-edit', - 'action-createpage', - 'action-createtalk', - 'action-createaccount', - 'action-minoredit', - 'action-move', - 'action-move-subpages', - 'action-move-rootuserpages', - 'action-movefile', - 'action-upload', - 'action-reupload', - 'action-reupload-shared', - 'action-upload_by_url', - 'action-writeapi', - 'action-delete', - 'action-deleterevision', - 'action-deletedhistory', - 'action-browsearchive', - 'action-undelete', - 'action-suppressrevision', - 'action-suppressionlog', - 'action-block', - 'action-protect', - 'action-rollback', - 'action-import', - 'action-importupload', - 'action-patrol', - 'action-autopatrol', - 'action-unwatchedpages', - 'action-mergehistory', - 'action-userrights', - 'action-userrights-interwiki', - 'action-siteadmin', - 'action-sendemail', - 'action-editmywatchlist', - 'action-viewmywatchlist', - 'action-viewmyprivateinfo', - 'action-editmyprivateinfo', - ), - 'recentchanges' => array( - 'nchanges', - 'enhancedrc-since-last-visit', - 'enhancedrc-history', - 'recentchanges', - 'recentchanges-url', - 'recentchanges-legend', - 'recentchanges-summary', - 'recentchangestext', - 'recentchanges-noresult', - 'recentchanges-feed-description', - 'recentchanges-label-newpage', - 'recentchanges-label-minor', - 'recentchanges-label-bot', - 'recentchanges-label-unpatrolled', - 'rcnote', - 'rcnotefrom', - 'rclistfrom', - 'rcshowhideminor', - 'rcshowhidebots', - 'rcshowhideliu', - 'rcshowhideanons', - 'rcshowhidepatr', - 'rcshowhidemine', - 'rclinks', - 'diff', - 'hist', - 'hide', - 'show', - 'minoreditletter', - 'newpageletter', - 'boteditletter', - 'unpatrolledletter', - 'number_of_watching_users_RCview', - 'number_of_watching_users_pageview', - 'rc_categories', - 'rc_categories_any', - 'rc-change-size', - 'rc-change-size-new', - 'newsectionsummary', - 'rc-enhanced-expand', - 'rc-enhanced-hide', - 'rc-old-title', - ), - 'recentchangeslinked' => array( - 'recentchangeslinked', - 'recentchangeslinked-feed', - 'recentchangeslinked-toolbox', - 'recentchangeslinked-title', - 'recentchangeslinked-summary', - 'recentchangeslinked-page', - 'recentchangeslinked-to', - ), - 'upload' => array( - 'upload', - 'uploadbtn', - 'reuploaddesc', - 'upload-tryagain', - 'uploadnologin', - 'uploadnologintext', - 'upload_directory_missing', - 'upload_directory_read_only', - 'uploaderror', - 'upload-summary', - 'upload-recreate-warning', - 'uploadtext', - 'upload-permitted', - 'upload-preferred', - 'upload-prohibited', - 'uploadfooter', - 'upload-default-description', - 'uploadlog', - 'uploadlogpage', - 'uploadlogpagetext', - 'filename', - 'filedesc', - 'fileuploadsummary', - 'filereuploadsummary', - 'filestatus', - 'filesource', - 'uploadedfiles', - 'ignorewarning', - 'ignorewarnings', - 'minlength1', - 'illegalfilename', - 'filename-toolong', - 'badfilename', - 'filetype-mime-mismatch', - 'filetype-badmime', - 'filetype-bad-ie-mime', - 'filetype-unwanted-type', - 'filetype-banned-type', - 'filetype-missing', - 'empty-file', - 'file-too-large', - 'filename-tooshort', - 'filetype-banned', - 'verification-error', - 'hookaborted', - 'illegal-filename', - 'overwrite', - 'unknown-error', - 'tmp-create-error', - 'tmp-write-error', - 'large-file', - 'largefileserver', - 'emptyfile', - 'windows-nonascii-filename', - 'fileexists', - 'filepageexists', - 'fileexists-extension', - 'fileexists-thumbnail-yes', - 'file-thumbnail-no', - 'fileexists-forbidden', - 'fileexists-shared-forbidden', - 'file-exists-duplicate', - 'file-deleted-duplicate', - 'uploadwarning', - 'uploadwarning-text', - 'savefile', - 'uploadedimage', - 'overwroteimage', - 'uploaddisabled', - 'copyuploaddisabled', - 'uploadfromurl-queued', - 'uploaddisabledtext', - 'php-uploaddisabledtext', - 'uploadscripted', - 'uploadvirus', - 'uploadjava', - 'upload-source', - 'sourcefilename', - 'sourceurl', - 'destfilename', - 'upload-maxfilesize', - 'upload-description', - 'upload-options', - 'watchthisupload', - 'filewasdeleted', - 'filename-bad-prefix', - 'filename-prefix-blacklist', - 'upload-success-subj', - 'upload-success-msg', - 'upload-failure-subj', - 'upload-failure-msg', - 'upload-warning-subj', - 'upload-warning-msg', - ), - 'upload-errors' => array( - 'upload-proto-error', - 'upload-proto-error-text', - 'upload-file-error', - 'upload-file-error-text', - 'upload-misc-error', - 'upload-misc-error-text', - 'upload-too-many-redirects', - 'upload-unknown-size', - 'upload-http-error', - 'upload-copy-upload-invalid-domain', - ), - - 'filebackend-errors' => array( - 'backend-fail-stream', - 'backend-fail-backup', - 'backend-fail-notexists', - 'backend-fail-hashes', - 'backend-fail-notsame', - 'backend-fail-invalidpath', - 'backend-fail-delete', - 'backend-fail-describe', - 'backend-fail-alreadyexists', - 'backend-fail-store', - 'backend-fail-copy', - 'backend-fail-move', - 'backend-fail-opentemp', - 'backend-fail-writetemp', - 'backend-fail-closetemp', - 'backend-fail-read', - 'backend-fail-create', - 'backend-fail-maxsize', - 'backend-fail-readonly', - 'backend-fail-synced', - 'backend-fail-connect', - 'backend-fail-internal', - 'backend-fail-contenttype', - 'backend-fail-batchsize', - 'backend-fail-usable' - ), - - 'filejournal-errors' => array( - 'filejournal-fail-dbconnect', - 'filejournal-fail-dbquery' - ), - - 'lockmanager-errors' => array( - 'lockmanager-notlocked', - 'lockmanager-fail-closelock', - 'lockmanager-fail-deletelock', - 'lockmanager-fail-acquirelock', - 'lockmanager-fail-openlock', - 'lockmanager-fail-acquirelock', - 'lockmanager-fail-releaselock', - 'lockmanager-fail-db-bucket', - 'lockmanager-fail-db-release', - 'lockmanager-fail-svr-acquire', - 'lockmanager-fail-svr-release' - ), - - 'zip' => array( - 'zip-file-open-error', - 'zip-wrong-format', - 'zip-bad', - 'zip-unsupported' - ), - - 'uploadstash' => array( - 'uploadstash', - 'uploadstash-summary', - 'uploadstash-clear', - 'uploadstash-nofiles', - 'uploadstash-badtoken', - 'uploadstash-errclear', - 'uploadstash-refresh', - 'invalid-chunk-offset', - ), - - 'img-auth' => array( - 'img-auth-accessdenied', - 'img-auth-desc', - 'img-auth-nopathinfo', - 'img-auth-notindir', - 'img-auth-badtitle', - 'img-auth-nologinnWL', - 'img-auth-nofile', - 'img-auth-isdir', - 'img-auth-streaming', - 'img-auth-public', - 'img-auth-noread', - 'img-auth-bad-query-string', - ), - - 'http-errors' => array( - 'http-invalid-url', - 'http-invalid-scheme', - 'http-request-error', - 'http-read-error', - 'http-timed-out', - 'http-curl-error', - 'http-bad-status', - ), - - 'upload-curl-errors' => array( - 'upload-curl-error6', - 'upload-curl-error6-text', - 'upload-curl-error28', - 'upload-curl-error28-text', - ), - 'licenses' => array( - 'license', - 'license-header', - 'nolicense', - 'licenses', - 'license-nopreview', - 'upload_source_url', - 'upload_source_file', - ), - 'filelist' => array( - 'listfiles-summary', - 'listfiles_search_for', - 'imgfile', - 'listfiles', - 'listfiles_thumb', - 'listfiles_date', - 'listfiles_name', - 'listfiles_user', - 'listfiles_size', - 'listfiles_description', - 'listfiles_count', - 'listfiles-show-all', - 'listfiles-latestversion', - 'listfiles-latestversion-yes', - 'listfiles-latestversion-no', - ), - 'filedescription' => array( - 'file-anchor-link', - 'filehist', - 'filehist-help', - 'filehist-deleteall', - 'filehist-deleteone', - 'filehist-revert', - 'filehist-current', - 'filehist-datetime', - 'filehist-thumb', - 'filehist-thumbtext', - 'filehist-nothumb', - 'filehist-user', - 'filehist-dimensions', - 'filehist-filesize', - 'filehist-comment', - 'filehist-missing', - 'imagelinks', - 'linkstoimage', - 'linkstoimage-more', - 'nolinkstoimage', - 'morelinkstoimage', - 'linkstoimage-redirect', - 'duplicatesoffile', - 'sharedupload', - 'sharedupload-desc-there', - 'sharedupload-desc-here', - 'sharedupload-desc-edit', - 'sharedupload-desc-create', - 'shareddescriptionfollows', - 'filepage-nofile', - 'filepage-nofile-link', - 'uploadnewversion-linktext', - 'shared-repo-from', - 'shared-repo', - 'shared-repo-name-wikimediacommons', - 'filepage.css', - 'upload-disallowed-here', - ), - 'filerevert' => array( - 'filerevert', - 'filerevert-legend', - 'filerevert-intro', - 'filerevert-comment', - 'filerevert-defaultcomment', - 'filerevert-submit', - 'filerevert-success', - 'filerevert-badversion', - ), - 'filedelete' => array( - 'filedelete', - 'filedelete-legend', - 'filedelete-intro', - 'filedelete-intro-old', - 'filedelete-comment', - 'filedelete-submit', - 'filedelete-success', - 'filedelete-success-old', - 'filedelete-nofile', - 'filedelete-nofile-old', - 'filedelete-otherreason', - 'filedelete-reason-otherlist', - 'filedelete-reason-dropdown', - 'filedelete-edit-reasonlist', - 'filedelete-maintenance', - 'filedelete-maintenance-title', - ), - 'mimesearch' => array( - 'mimesearch', - 'mimesearch-summary', - 'mimetype', - 'download', - ), - 'unwatchedpages' => array( - 'unwatchedpages', - 'unwatchedpages-summary', - ), - 'listredirects' => array( - 'listredirects', - 'listredirects-summary', - ), - 'unusedtemplates' => array( - 'unusedtemplates', - 'unusedtemplates-summary', - 'unusedtemplatestext', - 'unusedtemplateswlh', - ), - 'randompage' => array( - 'randompage', - 'randompage-nopages', - 'randompage-url', - ), - 'randomincategory' => array( - 'randomincategory', - 'randomincategory-invalidcategory', - 'randomincategory-nopages', - 'randomincategory-selectcategory', - 'randomincategory-selectcategory-submit', - ), - 'randomredirect' => array( - 'randomredirect', - 'randomredirect-nopages', - ), - 'statistics' => array( - 'statistics', - 'statistics-summary', - 'statistics-header-pages', - 'statistics-header-edits', - 'statistics-header-views', - 'statistics-header-users', - 'statistics-header-hooks', - 'statistics-articles', - 'statistics-pages', - 'statistics-pages-desc', - 'statistics-files', - 'statistics-edits', - 'statistics-edits-average', - 'statistics-views-total', - 'statistics-views-total-desc', - 'statistics-views-peredit', - 'statistics-users', - 'statistics-users-active', - 'statistics-users-active-desc', - 'statistics-mostpopular', - 'statistics-footer', - ), - 'pageswithprop' => array( - 'pageswithprop', - 'pageswithprop-summary', - 'pageswithprop-legend', - 'pageswithprop-text', - 'pageswithprop-prop', - 'pageswithprop-submit', - 'pageswithprop-prophidden-long', - 'pageswithprop-prophidden-binary', - ), - 'doubleredirects' => array( - 'doubleredirects', - 'doubleredirects-summary', - 'doubleredirectstext', - 'double-redirect-fixed-move', - 'double-redirect-fixed-maintenance', - 'double-redirect-fixer', - ), - 'brokenredirects' => array( - 'brokenredirects', - 'brokenredirects-summary', - 'brokenredirectstext', - 'brokenredirects-edit', - 'brokenredirects-delete', - ), - 'withoutinterwiki' => array( - 'withoutinterwiki', - 'withoutinterwiki-summary', - 'withoutinterwiki-legend', - 'withoutinterwiki-submit', - ), - 'fewestrevisions' => array( - 'fewestrevisions', - 'fewestrevisions-summary', - ), - 'specialpages' => array( - 'nbytes', - 'ncategories', - 'ninterwikis', - 'nlinks', - 'nmembers', - 'nrevisions', - 'nviews', - 'nchanges', - 'nimagelinks', - 'ntransclusions', - 'specialpage-empty', - 'lonelypages', - 'lonelypages-summary', - 'lonelypagestext', - 'uncategorizedpages', - 'uncategorizedpages-summary', - 'uncategorizedcategories', - 'uncategorizedcategories-summary', - 'uncategorizedimages', - 'uncategorizedimages-summary', - 'uncategorizedtemplates', - 'uncategorizedtemplates-summary', - 'unusedcategories', - 'unusedcategories-summary', - 'unusedimages', - 'unusedimages-summary', - 'popularpages', - 'popularpages-summary', - 'wantedcategories', - 'wantedcategories-summary', - 'wantedpages', - 'wantedpages-summary', - 'wantedpages-badtitle', - 'wantedfiles', - 'wantedfiles-summary', - 'wantedfiletext-cat', - 'wantedfiletext-nocat', - 'wantedtemplates', - 'wantedtemplates-summary', - 'mostlinked', - 'mostlinked-summary', - 'mostlinkedcategories', - 'mostlinkedcategories-summary', - 'mostlinkedtemplates', - 'mostlinkedtemplates-summary', - 'mostcategories', - 'mostcategories-summary', - 'mostimages', - 'mostimages-summary', - 'mostinterwikis', - 'mostinterwikis-summary', - 'mostrevisions', - 'mostrevisions-summary', - 'prefixindex', - 'prefixindex-namespace', - 'prefixindex-summary', - 'prefixindex-strip', - 'shortpages', - 'shortpages-summary', - 'longpages', - 'longpages-summary', - 'deadendpages', - 'deadendpages-summary', - 'deadendpagestext', - 'protectedpages', - 'protectedpages-indef', - 'protectedpages-summary', - 'protectedpages-cascade', - 'protectedpagestext', - 'protectedpagesempty', - 'protectedtitles', - 'protectedtitles-summary', - 'protectedtitlestext', - 'protectedtitlesempty', - 'listusers', - 'listusers-summary', - 'listusers-editsonly', - 'listusers-creationsort', - 'listusers-desc', - 'usereditcount', - 'usercreated', - 'newpages', - 'newpages-summary', - 'newpages-username', - 'ancientpages', - 'ancientpages-summary', - 'move', - 'movethispage', - 'unusedimagestext', - 'unusedcategoriestext', - 'notargettitle', - 'notargettext', - 'nopagetitle', - 'nopagetext', - 'pager-newer-n', - 'pager-older-n', - 'suppress', - 'querypage-disabled', - ), - 'booksources' => array( - 'booksources', - 'booksources-summary', - 'booksources-search-legend', - 'booksources-isbn', - 'booksources-go', - 'booksources-text', - 'booksources-invalid-isbn', - ), - 'magicwords' => array( - 'rfcurl', - 'pubmedurl', - ), - 'logpages' => array( - 'specialloguserlabel', - 'speciallogtitlelabel', - 'log', - 'all-logs-page', - 'alllogstext', - 'logempty', - 'log-title-wildcard', - 'showhideselectedlogentries', - ), - 'allpages' => array( - 'allpages', - 'allpages-summary', - 'alphaindexline', - 'nextpage', - 'prevpage', - 'allpagesfrom', - 'allpagesto', - 'allarticles', - 'allinnamespace', - 'allnotinnamespace', - 'allpagesprev', - 'allpagesnext', - 'allpagessubmit', - 'allpagesprefix', - 'allpagesbadtitle', - 'allpages-bad-ns', - 'allpages-hide-redirects', - ), - 'cachedspecial' => array( - 'cachedspecial-viewing-cached-ttl', - 'cachedspecial-viewing-cached-ts', - 'cachedspecial-refresh-now', - ), - 'categories' => array( - 'categories', - 'categories-summary', - 'categoriespagetext', - 'categoriesfrom', - 'special-categories-sort-count', - 'special-categories-sort-abc', - ), - 'deletedcontribs' => array( - 'deletedcontributions', - 'deletedcontributions-summary', - 'deletedcontributions-title', - 'sp-deletedcontributions-contribs', - ), - 'linksearch' => array( - 'linksearch', - 'linksearch-summary', - 'linksearch-pat', - 'linksearch-ns', - 'linksearch-ok', - 'linksearch-text', - 'linksearch-line', - 'linksearch-error', - ), - 'listusers' => array( - 'listusersfrom', - 'listusers-submit', - 'listusers-noresult', - 'listusers-blocked', - ), - 'activeusers' => array( - 'activeusers', - 'activeusers-summary', - 'activeusers-intro', - 'activeusers-count', - 'activeusers-from', - 'activeusers-hidebots', - 'activeusers-hidesysops', - 'activeusers-submit', - 'activeusers-noresult', - ), - 'listgrouprights' => array( - 'listgrouprights', - 'listgrouprights-summary', - 'listgrouprights-key', - 'listgrouprights-group', - 'listgrouprights-rights', - 'listgrouprights-helppage', - 'listgrouprights-members', - 'listgrouprights-right-display', - 'listgrouprights-right-revoked', - 'listgrouprights-addgroup', - 'listgrouprights-removegroup', - 'listgrouprights-addgroup-all', - 'listgrouprights-removegroup-all', - 'listgrouprights-addgroup-self', - 'listgrouprights-removegroup-self', - 'listgrouprights-addgroup-self-all', - 'listgrouprights-removegroup-self-all', - - ), - 'emailuser' => array( - 'mailnologin', - 'mailnologintext', - 'emailuser', - 'emailuser-title-target', - 'emailuser-title-notarget', - 'emailuser-summary', - 'emailpage', - 'emailpagetext', - 'usermailererror', - 'defemailsubject', - 'usermaildisabled', - 'usermaildisabledtext', - 'noemailtitle', - 'noemailtext', - 'nowikiemailtitle', - 'nowikiemailtext', - 'emailnotarget', - 'emailtarget', - 'emailusername', - 'emailusernamesubmit', - 'email-legend', - 'emailfrom', - 'emailto', - 'emailsubject', - 'emailmessage', - 'emailsend', - 'emailccme', - 'emailccsubject', - 'emailsent', - 'emailsenttext', - 'emailuserfooter', - ), - 'usermessage' => array( - 'usermessage-summary', - 'usermessage-editor', - 'usermessage-template', - ), - 'watchlist' => array( - 'watchlist', - 'watchlist-summary', - 'mywatchlist', - 'watchlistfor2', - 'nowatchlist', - 'watchlistanontext', - 'watchnologin', - 'watchnologintext', - 'addwatch', - 'addedwatchtext', - 'removewatch', - 'removedwatchtext', - 'watch', - 'watchthispage', - 'unwatch', - 'unwatchthispage', - 'notanarticle', - 'notvisiblerev', - 'watchlist-details', - 'wlheader-enotif', - 'wlheader-showupdated', - 'watchmethod-recent', - 'watchmethod-list', - 'watchlistcontains', - 'iteminvalidname', - 'wlnote', - 'wlshowlast', - 'watchlist-options', - ), - 'watching' => array( - 'watching', - 'unwatching', - 'watcherrortext', - ), - 'enotif' => array( - 'enotif_mailer', - 'enotif_reset', - 'enotif_impersonal_salutation', - 'enotif_subject_deleted', - 'enotif_subject_created', - 'enotif_subject_moved', - 'enotif_subject_restored', - 'enotif_subject_changed', - 'enotif_body_intro_deleted', - 'enotif_body_intro_created', - 'enotif_body_intro_moved', - 'enotif_body_intro_restored', - 'enotif_body_intro_changed', - 'enotif_lastvisited', - 'enotif_lastdiff', - 'enotif_anon_editor', - 'enotif_body', - 'created', - 'changed', - ), - 'delete' => array( - 'deletepage', - 'confirm', - 'excontent', - 'excontentauthor', - 'exbeforeblank', - 'exblank', - 'delete-confirm', - 'delete-legend', - 'historywarning', - 'confirmdeletetext', - 'actioncomplete', - 'actionfailed', - 'deletedtext', - 'dellogpage', - 'dellogpagetext', - 'deletionlog', - 'reverted', - 'deletecomment', - 'deleteotherreason', - 'deletereasonotherlist', - 'deletereason-dropdown', - 'delete-edit-reasonlist', - 'delete-toobig', - 'delete-warning-toobig', - ), - 'rollback' => array( - 'rollback', - 'rollback_short', - 'rollbacklink', - 'rollbacklinkcount', - 'rollbacklinkcount-morethan', - 'rollbackfailed', - 'cantrollback', - 'alreadyrolled', - 'editcomment', - 'revertpage', - 'revertpage-nouser', - 'rollback-success', - ), - 'edittokens' => array( - 'sessionfailure-title', - 'sessionfailure', - ), - 'protect' => array( - 'protectlogpage', - 'protectlogtext', - 'protectedarticle', - 'modifiedarticleprotection', - 'unprotectedarticle', - 'movedarticleprotection', - 'protect-title', - 'protect-title-notallowed', - 'prot_1movedto2', - 'protect-badnamespace-title', - 'protect-badnamespace-text', - 'protect-norestrictiontypes-text', - 'protect-norestrictiontypes-title', - 'protect-legend', - 'protectcomment', - 'protectexpiry', - 'protect_expiry_invalid', - 'protect_expiry_old', - 'protect-unchain-permissions', - 'protect-text', - 'protect-locked-blocked', - 'protect-locked-dblock', - 'protect-locked-access', - 'protect-cascadeon', - 'protect-default', - 'protect-fallback', - 'protect-level-autoconfirmed', - 'protect-level-sysop', - 'protect-summary-desc', - 'protect-summary-cascade', - 'protect-expiring', - 'protect-expiring-local', - 'protect-expiry-indefinite', - 'protect-cascade', - 'protect-cantedit', - 'protect-othertime', - 'protect-othertime-op', - 'protect-existing-expiry', - 'protect-otherreason', - 'protect-otherreason-op', - 'protect-dropdown', - 'protect-edit-reasonlist', - 'protect-expiry-options', - 'restriction-type', - 'restriction-level', - 'minimum-size', - 'maximum-size', - 'pagesize', - ), - 'restrictions' => array( - 'restriction-edit', - 'restriction-move', - 'restriction-create', - 'restriction-upload', - ), - 'restriction-levels' => array( - 'restriction-level-sysop', - 'restriction-level-autoconfirmed', - 'restriction-level-all', - ), - 'undelete' => array( - 'undelete', - 'undelete-summary', - 'undeletepage', - 'undeletepagetitle', - 'viewdeletedpage', - 'undeletepagetext', - 'undelete-fieldset-title', - 'undeleteextrahelp', - 'undeleterevisions', - 'undeletehistory', - 'undeleterevdel', - 'undeletehistorynoadmin', - 'undelete-revision', - 'undeleterevision-missing', - 'undelete-nodiff', - 'undeletebtn', - 'undeletelink', - 'undeleteviewlink', - 'undeletereset', - 'undeleteinvert', - 'undeletecomment', - 'undeletedrevisions', - 'undeletedrevisions-files', - 'undeletedfiles', - 'cannotundelete', - 'undeletedpage', - 'undelete-header', - 'undelete-search-title', - 'undelete-search-box', - 'undelete-search-prefix', - 'undelete-search-submit', - 'undelete-no-results', - 'undelete-filename-mismatch', - 'undelete-bad-store-key', - 'undelete-cleanup-error', - 'undelete-missing-filearchive', - 'undelete-error', - 'undelete-error-short', - 'undelete-error-long', - 'undelete-show-file-confirm', - 'undelete-show-file-submit', - 'undelete-revisionrow', - ), - 'nsform' => array( - 'namespace', - 'invert', - 'tooltip-invert', - 'namespace_association', - 'tooltip-namespace_association', - 'blanknamespace', - ), - 'contributions' => array( - 'contributions', - 'contributions-summary', - 'contributions-title', - 'mycontris', - 'contribsub2', - 'nocontribs', - 'uctop', - 'month', - 'year', - ), - 'sp-contributions' => array( - 'sp-contributions-newbies', - 'sp-contributions-newbies-sub', - 'sp-contributions-newbies-title', - 'sp-contributions-blocklog', - 'sp-contributions-deleted', - 'sp-contributions-uploads', - 'sp-contributions-logs', - 'sp-contributions-talk', - 'sp-contributions-userrights', - 'sp-contributions-blocked-notice', - 'sp-contributions-blocked-notice-anon', - 'sp-contributions-search', - 'sp-contributions-username', - 'sp-contributions-toponly', - 'sp-contributions-submit', - 'sp-contributions-explain', - 'sp-contributions-footer', - 'sp-contributions-footer-anon', - 'sp-contributions-footer-newbies', - ), - 'whatlinkshere' => array( - 'whatlinkshere', - 'whatlinkshere-title', - 'whatlinkshere-summary', - 'whatlinkshere-page', - 'linkshere', - 'nolinkshere', - 'nolinkshere-ns', - 'isredirect', - 'istemplate', - 'isimage', - 'whatlinkshere-prev', - 'whatlinkshere-next', - 'whatlinkshere-links', - 'whatlinkshere-hideredirs', - 'whatlinkshere-hidetrans', - 'whatlinkshere-hidelinks', - 'whatlinkshere-hideimages', - 'whatlinkshere-filters', - ), - 'block' => array( - 'autoblockid', - 'block', - 'unblock', - 'unblock-summary', - 'blockip', - 'blockip-title', - 'blockip-legend', - 'blockiptext', - 'ipadressorusername', - 'ipbexpiry', - 'ipbreason', - 'ipbreasonotherlist', - 'ipbreason-dropdown', - 'ipb-hardblock', - 'ipbcreateaccount', - 'ipbemailban', - 'ipbenableautoblock', - 'ipbsubmit', - 'ipbother', - 'ipboptions', - 'ipbotheroption', - 'ipbotherreason', - 'ipbhidename', - 'ipbwatchuser', - 'ipb-disableusertalk', - 'ipb-change-block', - 'ipb-confirm', - 'badipaddress', - 'blockipsuccesssub', - 'blockipsuccesstext', - 'ipb-blockingself', - 'ipb-confirmhideuser', - 'ipb-edit-dropdown', - 'ipb-unblock-addr', - 'ipb-unblock', - 'ipb-blocklist', - 'ipb-blocklist-contribs', - 'unblockip', - 'unblockiptext', - 'ipusubmit', - 'unblocked', - 'unblocked-range', - 'unblocked-id', - 'blocklist', - 'ipblocklist', - 'ipblocklist-legend', - 'blocklist-userblocks', - 'blocklist-tempblocks', - 'blocklist-addressblocks', - 'blocklist-rangeblocks', - 'blocklist-timestamp', - 'blocklist-target', - 'blocklist-expiry', - 'blocklist-by', - 'blocklist-params', - 'blocklist-reason', - 'blocklist-summary', - 'ipblocklist-submit', - 'ipblocklist-localblock', - 'ipblocklist-otherblocks', - - 'infiniteblock', - 'expiringblock', - 'anononlyblock', - 'noautoblockblock', - 'createaccountblock', - 'emailblock', - 'blocklist-nousertalk', - 'ipblocklist-empty', - 'ipblocklist-no-results', - 'blocklink', - 'unblocklink', - 'change-blocklink', - 'contribslink', - 'emaillink', - 'autoblocker', - 'blocklogpage', - 'blocklog-showlog', - 'blocklog-showsuppresslog', - 'blocklogentry', - 'reblock-logentry', - 'blocklogtext', - 'unblocklogentry', - 'block-log-flags-anononly', - 'block-log-flags-nocreate', - 'block-log-flags-noautoblock', - 'block-log-flags-noemail', - 'block-log-flags-nousertalk', - 'block-log-flags-angry-autoblock', - 'block-log-flags-hiddenname', - 'range_block_disabled', - 'ipb_expiry_invalid', - 'ipb_expiry_temp', - 'ipb_hide_invalid', - 'ipb_already_blocked', - 'ipb-needreblock', - 'ipb-otherblocks-header', - 'unblock-hideuser', - 'ipb_cant_unblock', - 'ipb_blocked_as_range', - 'ip_range_invalid', - 'ip_range_toolarge', - 'proxyblocker', - 'proxyblockreason', - 'sorbs', - 'sorbsreason', - 'sorbs_create_account_reason', - 'xffblockreason', - 'cant-block-while-blocked', - 'cant-see-hidden-user', - 'ipbblocked', - 'ipbnounblockself', - 'ipb-default-expiry', - ), - 'developertools' => array( - 'lockdb', - 'unlockdb', - 'lockdbtext', - 'unlockdbtext', - 'lockconfirm', - 'unlockconfirm', - 'lockbtn', - 'unlockbtn', - 'locknoconfirm', - 'lockdbsuccesssub', - 'unlockdbsuccesssub', - 'lockdbsuccesstext', - 'unlockdbsuccesstext', - 'lockfilenotwritable', - 'databasenotlocked', - 'lockedbyandtime', - ), - 'movepage' => array( - 'move-page', - 'movepage-summary', - 'move-page-legend', - 'movepagetext', - 'movepagetext-noredirectfixer', - 'movepagetalktext', - 'movearticle', - 'moveuserpage-warning', - 'movenologin', - 'movenologintext', - 'movenotallowed', - 'movenotallowedfile', - 'cant-move-user-page', - 'cant-move-to-user-page', - 'newtitle', - 'move-watch', - 'movepagebtn', - 'pagemovedsub', - 'movepage-moved', - 'movepage-moved-redirect', - 'movepage-moved-noredirect', - 'articleexists', - 'cantmove-titleprotected', - 'talkexists', - 'movedto', - 'movetalk', - 'move-subpages', - 'move-talk-subpages', - 'movepage-page-exists', - 'movepage-page-moved', - 'movepage-page-unmoved', - 'movepage-max-pages', - 'movelogpage', - 'movelogpagetext', - 'movesubpage', - 'movesubpagetext', - 'movenosubpage', - 'movereason', - 'move-redirect-text', - 'revertmove', - 'delete_and_move', - 'delete_and_move_text', - 'delete_and_move_confirm', - 'delete_and_move_reason', - 'selfmove', - 'immobile-source-namespace', - 'immobile-target-namespace', - 'immobile-target-namespace-iw', - 'immobile-source-page', - 'immobile-target-page', - 'bad-target-model', - 'immobile_namespace', - 'imagenocrossnamespace', - 'nonfile-cannot-move-to-file', - 'imagetypemismatch', - 'imageinvalidfilename', - 'fix-double-redirects', - 'move-leave-redirect', - 'protectedpagemovewarning', - 'semiprotectedpagemovewarning', - 'move-over-sharedrepo', - 'file-exists-sharedrepo', - ), - 'export' => array( - 'export', - 'export-summary', - 'exporttext', - 'exportall', - 'exportcuronly', - 'exportnohistory', - 'exportlistauthors', - 'export-submit', - 'export-addcattext', - 'export-addcat', - 'export-addnstext', - 'export-addns', - 'export-download', - 'export-templates', - 'export-pagelinks', - ), - 'allmessages' => array( - 'allmessages', - 'allmessagesname', - 'allmessagesdefault', - 'allmessagescurrent', - 'allmessagestext', - 'allmessagesnotsupportedDB', - 'allmessages-filter-legend', - 'allmessages-filter', - 'allmessages-filter-unmodified', - 'allmessages-filter-all', - 'allmessages-filter-modified', - 'allmessages-prefix', - 'allmessages-language', - 'allmessages-filter-submit', - ), - 'thumbnails' => array( - 'thumbnail-more', - 'filemissing', - 'thumbnail_error', - 'thumbnail_error_remote', - 'djvu_page_error', - 'djvu_no_xml', - 'thumbnail-temp-create', - 'thumbnail-dest-create', - 'thumbnail_invalid_params', - 'thumbnail_dest_directory', - 'thumbnail_image-type', - 'thumbnail_gd-library', - 'thumbnail_image-missing', - ), - 'import' => array( - 'import', - 'import-summary', - 'importinterwiki', - 'import-interwiki-text', - 'import-interwiki-source', - 'import-interwiki-history', - 'import-interwiki-templates', - 'import-interwiki-submit', - 'import-interwiki-namespace', - 'import-interwiki-rootpage', - 'import-upload-filename', - 'import-comment', - 'importtext', - 'importstart', - 'import-revision-count', - 'importnopages', - 'imported-log-entries', - 'importfailed', - 'importunknownsource', - 'importcantopen', - 'importbadinterwiki', - 'importnotext', - 'importsuccess', - 'importhistoryconflict', - 'importnosources', - 'importnofile', - 'importuploaderrorsize', - 'importuploaderrorpartial', - 'importuploaderrortemp', - 'import-parse-failure', - 'import-noarticle', - 'import-nonewrevisions', - 'xml-error-string', - 'import-upload', - 'import-token-mismatch', - 'import-invalid-interwiki', - 'import-error-edit', - 'import-error-create', - 'import-error-interwiki', - 'import-error-special', - 'import-error-invalid', - 'import-error-unserialize', - 'import-options-wrong', - 'import-rootpage-invalid', - 'import-rootpage-nosubpage', - ), - 'importlog' => array( - 'importlogpage', - 'importlogpagetext', - 'import-logentry-upload', - 'import-logentry-upload-detail', - 'import-logentry-interwiki', - 'import-logentry-interwiki-detail', - ), - 'javaccripttest' => array( - 'javascripttest', - 'javascripttest-backlink', - 'javascripttest-title', - 'javascripttest-pagetext-noframework', - 'javascripttest-pagetext-unknownframework', - 'javascripttest-pagetext-frameworks', - 'javascripttest-pagetext-skins', - 'javascripttest-qunit-name', - 'javascripttest-qunit-intro', - 'javascripttest-qunit-heading', - ), - 'accesskeys' => array( - 'accesskey-pt-userpage', - 'accesskey-pt-anonuserpage', - 'accesskey-pt-mytalk', - 'accesskey-pt-anontalk', - 'accesskey-pt-preferences', - 'accesskey-pt-watchlist', - 'accesskey-pt-mycontris', - 'accesskey-pt-login', - 'accesskey-pt-anonlogin', - 'accesskey-pt-logout', - 'accesskey-ca-talk', - 'accesskey-ca-edit', - 'accesskey-ca-addsection', - 'accesskey-ca-viewsource', - 'accesskey-ca-history', - 'accesskey-ca-protect', - 'accesskey-ca-unprotect', - 'accesskey-ca-delete', - 'accesskey-ca-undelete', - 'accesskey-ca-move', - 'accesskey-ca-watch', - 'accesskey-ca-unwatch', - 'accesskey-search', - 'accesskey-search-go', - 'accesskey-search-fulltext', - 'accesskey-p-logo', - 'accesskey-n-mainpage', - 'accesskey-n-mainpage-description', - 'accesskey-n-portal', - 'accesskey-n-currentevents', - 'accesskey-n-recentchanges', - 'accesskey-n-randompage', - 'accesskey-n-help', - 'accesskey-t-whatlinkshere', - 'accesskey-t-recentchangeslinked', - 'accesskey-t-random', - 'accesskey-feed-rss', - 'accesskey-feed-atom', - 'accesskey-t-contributions', - 'accesskey-t-emailuser', - 'accesskey-t-permalink', - 'accesskey-t-print', - 'accesskey-t-upload', - 'accesskey-t-specialpages', - 'accesskey-ca-nstab-main', - 'accesskey-ca-nstab-user', - 'accesskey-ca-nstab-media', - 'accesskey-ca-nstab-special', - 'accesskey-ca-nstab-project', - 'accesskey-ca-nstab-image', - 'accesskey-ca-nstab-mediawiki', - 'accesskey-ca-nstab-template', - 'accesskey-ca-nstab-help', - 'accesskey-ca-nstab-category', - 'accesskey-minoredit', - 'accesskey-save', - 'accesskey-preview', - 'accesskey-diff', - 'accesskey-compareselectedversions', - 'accesskey-watch', - 'accesskey-upload', - 'accesskey-preferences-save', - 'accesskey-summary', - 'accesskey-userrights-set', - 'accesskey-blockip-block', - 'accesskey-export', - 'accesskey-import', - 'accesskey-watchlistedit-normal-submit', - 'accesskey-watchlistedit-raw-submit', - ), - 'tooltips' => array( - 'tooltip-pt-userpage', - 'tooltip-pt-anonuserpage', - 'tooltip-pt-mytalk', - 'tooltip-pt-anontalk', - 'tooltip-pt-preferences', - 'tooltip-pt-watchlist', - 'tooltip-pt-mycontris', - 'tooltip-pt-login', - 'tooltip-pt-anonlogin', - 'tooltip-pt-logout', - 'tooltip-ca-talk', - 'tooltip-ca-edit', - 'tooltip-ca-addsection', - 'tooltip-ca-viewsource', - 'tooltip-ca-history', - 'tooltip-ca-protect', - 'tooltip-ca-unprotect', - 'tooltip-ca-delete', - 'tooltip-ca-undelete', - 'tooltip-ca-move', - 'tooltip-ca-watch', - 'tooltip-ca-unwatch', - 'tooltip-search', - 'tooltip-search-go', - 'tooltip-search-fulltext', - 'tooltip-p-logo', - 'tooltip-n-mainpage', - 'tooltip-n-mainpage-description', - 'tooltip-n-portal', - 'tooltip-n-currentevents', - 'tooltip-n-recentchanges', - 'tooltip-n-randompage', - 'tooltip-n-help', - 'tooltip-t-whatlinkshere', - 'tooltip-t-recentchangeslinked', - 'tooltip-t-random', - 'tooltip-feed-rss', - 'tooltip-feed-atom', - 'tooltip-t-contributions', - 'tooltip-t-emailuser', - 'tooltip-t-upload', - 'tooltip-t-specialpages', - 'tooltip-t-print', - 'tooltip-t-permalink', - 'tooltip-ca-nstab-main', - 'tooltip-ca-nstab-user', - 'tooltip-ca-nstab-media', - 'tooltip-ca-nstab-special', - 'tooltip-ca-nstab-project', - 'tooltip-ca-nstab-image', - 'tooltip-ca-nstab-mediawiki', - 'tooltip-ca-nstab-template', - 'tooltip-ca-nstab-help', - 'tooltip-ca-nstab-category', - 'tooltip-minoredit', - 'tooltip-save', - 'tooltip-preview', - 'tooltip-diff', - 'tooltip-compareselectedversions', - 'tooltip-watch', - 'tooltip-watchlistedit-normal-submit', - 'tooltip-watchlistedit-raw-submit', - 'tooltip-recreate', - 'tooltip-upload', - 'tooltip-rollback', - 'tooltip-undo', - 'tooltip-preferences-save', - 'tooltip-summary', - 'interlanguage-link-title', - ), - 'stylesheets' => array( - 'common.css', - 'cologneblue.css', - 'monobook.css', - 'modern.css', - 'vector.css', - 'print.css', - 'noscript.css', - 'group-autoconfirmed.css', - 'group-bot.css', - 'group-sysop.css', - 'group-bureaucrat.css', - ), - 'scripts' => array( - 'common.js', - 'cologneblue.js', - 'monobook.js', - 'modern.js', - 'vector.js', - 'group-autoconfirmed.js', - 'group-bot.js', - 'group-sysop.js', - 'group-bureaucrat.js', - ), - 'metadata_cc' => array( - 'notacceptable', - ), - 'attribution' => array( - 'anonymous', - 'siteuser', - 'anonuser', - 'lastmodifiedatby', - 'othercontribs', - 'others', - 'siteusers', - 'anonusers', - 'creditspage', - 'nocredits', - ), - 'spamprotection' => array( - 'spamprotectiontitle', - 'spamprotectiontext', - 'spamprotectionmatch', - 'spambot_username', - 'spam_reverting', - 'spam_blanking', - 'spam_deleting', - 'simpleantispam-label', - ), - 'info' => array( - 'pageinfo-header', - 'pageinfo-title', - 'pageinfo-not-current', - 'pageinfo-header-basic', - 'pageinfo-header-edits', - 'pageinfo-header-restrictions', - 'pageinfo-header-properties', - 'pageinfo-display-title', - 'pageinfo-default-sort', - 'pageinfo-length', - 'pageinfo-article-id', - 'pageinfo-language', - 'pageinfo-robot-policy', - 'pageinfo-robot-index', - 'pageinfo-robot-noindex', - 'pageinfo-views', - 'pageinfo-watchers', - 'pageinfo-few-watchers', - 'pageinfo-redirects-name', - 'pageinfo-redirects-value', - 'pageinfo-subpages-name', - 'pageinfo-subpages-value', - 'pageinfo-firstuser', - 'pageinfo-firsttime', - 'pageinfo-lastuser', - 'pageinfo-lasttime', - 'pageinfo-edits', - 'pageinfo-authors', - 'pageinfo-recent-edits', - 'pageinfo-recent-authors', - 'pageinfo-magic-words', - 'pageinfo-hidden-categories', - 'pageinfo-templates', - 'pageinfo-transclusions', - 'pageinfo-footer', - 'pageinfo-toolboxlink', - 'pageinfo-redirectsto', - 'pageinfo-redirectsto-info', - 'pageinfo-contentpage', - 'pageinfo-contentpage-yes', - 'pageinfo-protect-cascading', - 'pageinfo-protect-cascading-yes', - 'pageinfo-protect-cascading-from', - 'pageinfo-category-info', - 'pageinfo-category-pages', - 'pageinfo-category-subcats', - 'pageinfo-category-files' - ), - 'skin' => array( - 'skinname-cologneblue', - 'skinname-monobook', - 'skinname-modern', - 'skinname-vector', - ), - 'patrolling' => array( - 'markaspatrolleddiff', - 'markaspatrolledlink', - 'markaspatrolledtext', - 'markedaspatrolled', - 'markedaspatrolledtext', - 'rcpatroldisabled', - 'rcpatroldisabledtext', - 'markedaspatrollederror', - 'markedaspatrollederrortext', - 'markedaspatrollederror-noautopatrol', - 'markedaspatrollednotify', - 'markedaspatrollederrornotify', - ), - 'patrol-log' => array( - 'patrol-log-page', - 'patrol-log-header', - 'log-show-hide-patrol', - ), - 'imagedeletion' => array( - 'deletedrevision', - 'filedeleteerror-short', - 'filedeleteerror-long', - 'filedelete-missing', - 'filedelete-old-unregistered', - 'filedelete-current-unregistered', - 'filedelete-archive-read-only', - ), - 'browsediffs' => array( - 'previousdiff', - 'nextdiff', - ), - 'media-info' => array( - 'mediawarning', - 'imagemaxsize', - 'thumbsize', - 'widthheight', - 'widthheightpage', - 'file-info', - 'file-info-size', - 'file-info-size-pages', - 'file-nohires', - 'svg-long-desc', - 'svg-long-desc-animated', - 'svg-long-error', - 'show-big-image', - 'show-big-image-preview', - 'show-big-image-other', - 'show-big-image-size', - 'file-info-gif-looped', - 'file-info-gif-frames', - 'file-info-png-looped', - 'file-info-png-repeat', - 'file-info-png-frames', - 'file-no-thumb-animation', - 'file-no-thumb-animation-gif', - ), - 'newfiles' => array( - 'newimages', - 'imagelisttext', - 'newimages-summary', - 'newimages-legend', - 'newimages-label', - 'showhidebots', - 'noimages', - 'ilsubmit', - 'bydate', - 'sp-newimages-showfrom', - ), - 'video-info' => array( - 'video-dims', - 'seconds-abbrev', - 'minutes-abbrev', - 'hours-abbrev', - 'days-abbrev', - 'seconds', - 'minutes', - 'hours', - 'days', - 'weeks', - 'months', - 'years', - 'ago', - 'just-now', - ), - 'human-timestamps' => array( - 'hours-ago', - 'minutes-ago', - 'seconds-ago', - 'monday-at', - 'tuesday-at', - 'wednesday-at', - 'thursday-at', - 'friday-at', - 'saturday-at', - 'sunday-at', - 'today-at', - 'yesterday-at', - ), - 'badimagelist' => array( - 'bad_image_list', - ), - 'variantname-zh' => array( - 'variantname-zh-hans', - 'variantname-zh-hant', - 'variantname-zh-cn', - 'variantname-zh-tw', - 'variantname-zh-hk', - 'variantname-zh-mo', - 'variantname-zh-sg', - 'variantname-zh-my', - 'variantname-zh', - ), - 'variantname-gan' => array( - 'variantname-gan-hans', - 'variantname-gan-hant', - 'variantname-gan', - ), - 'variantname-sr' => array( - 'variantname-sr-ec', - 'variantname-sr-el', - 'variantname-sr', - ), - 'variantname-kk' => array( - 'variantname-kk-kz', - 'variantname-kk-tr', - 'variantname-kk-cn', - 'variantname-kk-cyrl', - 'variantname-kk-latn', - 'variantname-kk-arab', - 'variantname-kk', - ), - 'variantname-ku' => array( - 'variantname-ku-arab', - 'variantname-ku-latn', - 'variantname-ku', - ), - 'variantname-tg' => array( - 'variantname-tg-cyrl', - 'variantname-tg-latn', - 'variantname-tg', - ), - 'variantname-iu' => array( - 'variantname-ike-cans', - 'variantname-ike-latn', - 'variantname-iu', - ), - 'variantname-shi' => array( - 'variantname-shi-tfng', - 'variantname-shi-latn', - 'variantname-shi', - ), - 'metadata' => array( - 'metadata', - 'metadata-help', - 'metadata-expand', - 'metadata-collapse', - 'metadata-fields', - 'metadata-langitem', - 'metadata-langitem-default', - ), - 'exif' => array( - 'exif-imagewidth', - 'exif-imagelength', - 'exif-bitspersample', - 'exif-compression', - 'exif-photometricinterpretation', - 'exif-orientation', - 'exif-samplesperpixel', - 'exif-planarconfiguration', - 'exif-ycbcrsubsampling', - 'exif-ycbcrpositioning', - 'exif-xresolution', - 'exif-yresolution', - 'exif-resolutionunit', - 'exif-stripoffsets', - 'exif-rowsperstrip', - 'exif-stripbytecounts', - 'exif-jpeginterchangeformat', - 'exif-jpeginterchangeformatlength', - 'exif-whitepoint', - 'exif-primarychromaticities', - 'exif-ycbcrcoefficients', - 'exif-referenceblackwhite', - 'exif-datetime', - 'exif-imagedescription', - 'exif-make', - 'exif-model', - 'exif-software', - 'exif-artist', - 'exif-copyright', - 'exif-exifversion', - 'exif-flashpixversion', - 'exif-colorspace', - 'exif-componentsconfiguration', - 'exif-compressedbitsperpixel', - 'exif-pixelydimension', - 'exif-pixelxdimension', - 'exif-usercomment', - 'exif-relatedsoundfile', - 'exif-datetimeoriginal', - 'exif-datetimedigitized', - 'exif-subsectime', - 'exif-subsectimeoriginal', - 'exif-subsectimedigitized', - 'exif-exposuretime', - 'exif-exposuretime-format', - 'exif-fnumber', - 'exif-fnumber-format', - 'exif-exposureprogram', - 'exif-spectralsensitivity', - 'exif-isospeedratings', - 'exif-shutterspeedvalue', - 'exif-aperturevalue', - 'exif-brightnessvalue', - 'exif-exposurebiasvalue', - 'exif-maxaperturevalue', - 'exif-subjectdistance', - 'exif-meteringmode', - 'exif-lightsource', - 'exif-flash', - 'exif-focallength', - 'exif-focallength-format', - 'exif-subjectarea', - 'exif-flashenergy', - 'exif-focalplanexresolution', - 'exif-focalplaneyresolution', - 'exif-focalplaneresolutionunit', - 'exif-subjectlocation', - 'exif-exposureindex', - 'exif-sensingmethod', - 'exif-filesource', - 'exif-scenetype', - 'exif-customrendered', - 'exif-exposuremode', - 'exif-whitebalance', - 'exif-digitalzoomratio', - 'exif-focallengthin35mmfilm', - 'exif-scenecapturetype', - 'exif-gaincontrol', - 'exif-contrast', - 'exif-saturation', - 'exif-sharpness', - 'exif-devicesettingdescription', - 'exif-subjectdistancerange', - 'exif-imageuniqueid', - 'exif-gpsversionid', - 'exif-gpslatituderef', - 'exif-gpslatitude', - 'exif-gpslongituderef', - 'exif-gpslongitude', - 'exif-gpsaltituderef', - 'exif-gpsaltitude', - 'exif-gpstimestamp', - 'exif-gpssatellites', - 'exif-gpsstatus', - 'exif-gpsmeasuremode', - 'exif-gpsdop', - 'exif-gpsspeedref', - 'exif-gpsspeed', - 'exif-gpstrackref', - 'exif-gpstrack', - 'exif-gpsimgdirectionref', - 'exif-gpsimgdirection', - 'exif-gpsmapdatum', - 'exif-gpsdestlatituderef', - 'exif-gpsdestlatitude', - 'exif-gpsdestlongituderef', - 'exif-gpsdestlongitude', - 'exif-gpsdestbearingref', - 'exif-gpsdestbearing', - 'exif-gpsdestdistanceref', - 'exif-gpsdestdistance', - 'exif-gpsprocessingmethod', - 'exif-gpsareainformation', - 'exif-gpsdatestamp', - 'exif-gpsdifferential', - 'exif-coordinate-format', - 'exif-jpegfilecomment', - 'exif-keywords', - 'exif-worldregioncreated', - 'exif-countrycreated', - 'exif-countrycodecreated', - 'exif-provinceorstatecreated', - 'exif-citycreated', - 'exif-sublocationcreated', - 'exif-worldregiondest', - 'exif-countrydest', - 'exif-countrycodedest', - 'exif-provinceorstatedest', - 'exif-citydest', - 'exif-sublocationdest', - 'exif-objectname', - 'exif-specialinstructions', - 'exif-headline', - 'exif-credit', - 'exif-source', - 'exif-editstatus', - 'exif-urgency', - 'exif-fixtureidentifier', - 'exif-locationdest', - 'exif-locationdestcode', - 'exif-objectcycle', - 'exif-contact', - 'exif-writer', - 'exif-languagecode', - 'exif-iimversion', - 'exif-iimcategory', - 'exif-iimsupplementalcategory', - 'exif-datetimeexpires', - 'exif-datetimereleased', - 'exif-originaltransmissionref', - 'exif-identifier', - 'exif-lens', - 'exif-serialnumber', - 'exif-cameraownername', - 'exif-label', - 'exif-datetimemetadata', - 'exif-nickname', - 'exif-rating', - 'exif-rightscertificate', - 'exif-copyrighted', - 'exif-copyrightowner', - 'exif-usageterms', - 'exif-webstatement', - 'exif-originaldocumentid', - 'exif-licenseurl', - 'exif-morepermissionsurl', - 'exif-attributionurl', - 'exif-preferredattributionname', - 'exif-pngfilecomment', - 'exif-disclaimer', - 'exif-contentwarning', - 'exif-giffilecomment', - 'exif-intellectualgenre', - 'exif-subjectnewscode', - 'exif-scenecode', - 'exif-event', - 'exif-organisationinimage', - 'exif-personinimage', - 'exif-originalimageheight', - 'exif-originalimagewidth', - ), - 'exif-values' => array( - 'exif-make-value', - 'exif-model-value', - 'exif-software-value', - 'exif-software-version-value', - 'exif-contact-value', - 'exif-subjectnewscode-value', - ), - 'exif-compression' => array( - 'exif-compression-1', - 'exif-compression-2', - 'exif-compression-3', - 'exif-compression-4', - 'exif-compression-5', - 'exif-compression-6', - 'exif-compression-7', - 'exif-compression-8', - 'exif-compression-32773', - 'exif-compression-32946', - 'exif-compression-34712', - ), - 'exif-copyrighted' => array( - 'exif-copyrighted-true', - 'exif-copyrighted-false', - ), - 'exif-photometricinterpretation' => array( - 'exif-photometricinterpretation-2', - 'exif-photometricinterpretation-6', - ), - 'exif-unknowndate' => array( - 'exif-unknowndate', - ), - 'exif-orientation' => array( - 'exif-orientation-1', - 'exif-orientation-2', - 'exif-orientation-3', - 'exif-orientation-4', - 'exif-orientation-5', - 'exif-orientation-6', - 'exif-orientation-7', - 'exif-orientation-8', - ), - 'exif-planarconfiguration' => array( - 'exif-planarconfiguration-1', - 'exif-planarconfiguration-2', - ), - 'exif-xyresolution' => array( - 'exif-xyresolution-i', - 'exif-xyresolution-c', - ), - 'exif-colorspace' => array( - 'exif-colorspace-1', - 'exif-colorspace-65535', - ), - 'exif-componentsconfiguration' => array( - 'exif-componentsconfiguration-0', - 'exif-componentsconfiguration-1', - 'exif-componentsconfiguration-2', - 'exif-componentsconfiguration-3', - 'exif-componentsconfiguration-4', - 'exif-componentsconfiguration-5', - 'exif-componentsconfiguration-6', - ), - 'exif-exposureprogram' => array( - 'exif-exposureprogram-0', - 'exif-exposureprogram-1', - 'exif-exposureprogram-2', - 'exif-exposureprogram-3', - 'exif-exposureprogram-4', - 'exif-exposureprogram-5', - 'exif-exposureprogram-6', - 'exif-exposureprogram-7', - 'exif-exposureprogram-8', - ), - 'exif-subjectdistance-value' => array( - 'exif-subjectdistance-value', - ), - 'exif-meteringmode' => array( - 'exif-meteringmode-0', - 'exif-meteringmode-1', - 'exif-meteringmode-2', - 'exif-meteringmode-3', - 'exif-meteringmode-4', - 'exif-meteringmode-5', - 'exif-meteringmode-6', - 'exif-meteringmode-255', - ), - 'exif-lightsource' => array( - 'exif-lightsource-0', - 'exif-lightsource-1', - 'exif-lightsource-2', - 'exif-lightsource-3', - 'exif-lightsource-4', - 'exif-lightsource-9', - 'exif-lightsource-10', - 'exif-lightsource-11', - 'exif-lightsource-12', - 'exif-lightsource-13', - 'exif-lightsource-14', - 'exif-lightsource-15', - 'exif-lightsource-17', - 'exif-lightsource-18', - 'exif-lightsource-19', - 'exif-lightsource-20', - 'exif-lightsource-21', - 'exif-lightsource-22', - 'exif-lightsource-23', - 'exif-lightsource-24', - 'exif-lightsource-255', - ), - 'exif-flash' => array( - 'exif-flash-fired-0', - 'exif-flash-fired-1', - 'exif-flash-return-0', - 'exif-flash-return-2', - 'exif-flash-return-3', - 'exif-flash-mode-1', - 'exif-flash-mode-2', - 'exif-flash-mode-3', - 'exif-flash-function-1', - 'exif-flash-redeye-1', - ), - 'exif-focalplaneresolutionunit' => array( - 'exif-focalplaneresolutionunit-2', - ), - 'exif-sensingmethod' => array( - 'exif-sensingmethod-1', - 'exif-sensingmethod-2', - 'exif-sensingmethod-3', - 'exif-sensingmethod-4', - 'exif-sensingmethod-5', - 'exif-sensingmethod-7', - 'exif-sensingmethod-8', - ), - 'exif-filesource' => array( - 'exif-filesource-3', - ), - 'exif-scenetype' => array( - 'exif-scenetype-1', - ), - 'exif-customrendered' => array( - 'exif-customrendered-0', - 'exif-customrendered-1', - ), - 'exif-exposuremode' => array( - 'exif-exposuremode-0', - 'exif-exposuremode-1', - 'exif-exposuremode-2', - ), - 'exif-whitebalance' => array( - 'exif-whitebalance-0', - 'exif-whitebalance-1', - ), - 'exif-scenecapturetype' => array( - 'exif-scenecapturetype-0', - 'exif-scenecapturetype-1', - 'exif-scenecapturetype-2', - 'exif-scenecapturetype-3', - ), - 'exif-gaincontrol' => array( - 'exif-gaincontrol-0', - 'exif-gaincontrol-1', - 'exif-gaincontrol-2', - 'exif-gaincontrol-3', - 'exif-gaincontrol-4', - ), - 'exif-contrast' => array( - 'exif-contrast-0', - 'exif-contrast-1', - 'exif-contrast-2', - ), - 'exif-saturation' => array( - 'exif-saturation-0', - 'exif-saturation-1', - 'exif-saturation-2', - ), - 'exif-sharpness' => array( - 'exif-sharpness-0', - 'exif-sharpness-1', - 'exif-sharpness-2', - ), - 'exif-subjectdistancerange' => array( - 'exif-subjectdistancerange-0', - 'exif-subjectdistancerange-1', - 'exif-subjectdistancerange-2', - 'exif-subjectdistancerange-3', - ), - 'exif-gpslatitude' => array( - 'exif-gpslatitude-n', - 'exif-gpslatitude-s', - ), - 'exif-gpslongitude' => array( - 'exif-gpslongitude-e', - 'exif-gpslongitude-w', - ), - 'exif-altituderef' => array( - 'exif-gpsaltitude-above-sealevel', - 'exif-gpsaltitude-below-sealevel', - ), - 'exif-gpsstatus' => array( - 'exif-gpsstatus-a', - 'exif-gpsstatus-v', - ), - 'exif-gpsmeasuremode' => array( - 'exif-gpsmeasuremode-2', - 'exif-gpsmeasuremode-3', - ), - 'exif-gpsspeed' => array( - 'exif-gpsspeed-k', - 'exif-gpsspeed-m', - 'exif-gpsspeed-n', - ), - 'exif-gpsdestdistanceref' => array( - 'exif-gpsdestdistance-k', - 'exif-gpsdestdistance-m', - 'exif-gpsdestdistance-n', - ), - 'exif-gdop' => array( - 'exif-gpsdop-excellent', - 'exif-gpsdop-good', - 'exif-gpsdop-moderate', - 'exif-gpsdop-fair', - 'exif-gpsdop-poor', - ), - 'exif-objectcycle' => array( - 'exif-objectcycle-a', - 'exif-objectcycle-p', - 'exif-objectcycle-b', - ), - 'exif-gpsdirection' => array( - 'exif-gpsdirection-t', - 'exif-gpsdirection-m', - ), - 'exif-ycbcrpositioning' => array( - 'exif-ycbcrpositioning-1', - 'exif-ycbcrpositioning-2', - ), - 'exif-dc' => array( - 'exif-dc-contributor', - 'exif-dc-coverage', - 'exif-dc-date', - 'exif-dc-publisher', - 'exif-dc-relation', - 'exif-dc-rights', - 'exif-dc-source', - 'exif-dc-type', - ), - 'exif-rating' => array( - 'exif-rating-rejected', - ), - 'exif-isospeedratings' => array( - 'exif-isospeedratings-overflow', - ), - 'exif-maxaperturevalue' => array( - 'exif-maxaperturevalue-value', - ), - 'exif-iimcategory' => array( - 'exif-iimcategory-ace', - 'exif-iimcategory-clj', - 'exif-iimcategory-dis', - 'exif-iimcategory-fin', - 'exif-iimcategory-edu', - 'exif-iimcategory-evn', - 'exif-iimcategory-hth', - 'exif-iimcategory-hum', - 'exif-iimcategory-lab', - 'exif-iimcategory-lif', - 'exif-iimcategory-pol', - 'exif-iimcategory-rel', - 'exif-iimcategory-sci', - 'exif-iimcategory-soi', - 'exif-iimcategory-spo', - 'exif-iimcategory-war', - 'exif-iimcategory-wea', - ), - 'exif-urgency' => array( - 'exif-urgency-normal', - 'exif-urgency-low', - 'exif-urgency-high', - 'exif-urgency-other', - ), - 'edit-externally' => array( - 'edit-externally', - 'edit-externally-help', - ), - 'all' => array( - 'watchlistall2', - 'namespacesall', - 'monthsall', - 'limitall', - ), - 'confirmemail' => array( - 'confirmemail', - 'confirmemail_noemail', - 'confirmemail_text', - 'confirmemail_pending', - 'confirmemail_send', - 'confirmemail_sent', - 'confirmemail_oncreate', - 'confirmemail_sendfailed', - 'confirmemail_invalid', - 'confirmemail_needlogin', - 'confirmemail_success', - 'confirmemail_loggedin', - 'confirmemail_error', - 'confirmemail_subject', - 'confirmemail_body', - 'confirmemail_body_changed', - 'confirmemail_body_set', - 'confirmemail_invalidated', - 'invalidateemail', - ), - 'scarytransclusion' => array( - 'scarytranscludedisabled', - 'scarytranscludefailed', - 'scarytranscludefailed-httpstatus', - 'scarytranscludetoolong', - ), - 'deleteconflict' => array( - 'deletedwhileediting', - 'confirmrecreate', - 'confirmrecreate-noreason', - 'recreate', - ), - 'unit-pixel' => array( - 'unit-pixel', - ), - 'purge' => array( - 'confirm_purge_button', - 'confirm-purge-top', - 'confirm-purge-bottom', - ), - 'watch-unwatch' => array( - 'confirm-watch-button', - 'confirm-watch-top', - 'confirm-unwatch-button', - 'confirm-unwatch-top', - ), - 'separators' => array( - 'semicolon-separator', - 'comma-separator', - 'colon-separator', - 'autocomment-prefix', - 'pipe-separator', - 'word-separator', - 'ellipsis', - 'percent', - 'parentheses', - 'brackets', - ), - 'imgmulti' => array( - 'imgmultipageprev', - 'imgmultipagenext', - 'imgmultigo', - 'imgmultigoto', - ), - 'tablepager' => array( - 'ascending_abbrev', - 'descending_abbrev', - 'table_pager_next', - 'table_pager_prev', - 'table_pager_first', - 'table_pager_last', - 'table_pager_limit', - 'table_pager_limit_label', - 'table_pager_limit_submit', - 'table_pager_empty', - ), - 'autosumm' => array( - 'autosumm-blank', - 'autosumm-replace', - 'autoredircomment', - 'autosumm-new', - ), - 'autoblock_whitelist' => array( - 'autoblock_whitelist', - ), - 'sizeunits' => array( - 'size-bytes', - 'size-kilobytes', - 'size-megabytes', - 'size-gigabytes', - 'size-terabytes', - 'size-petabytes', - 'size-exabytes', - 'size-zetabytes', - 'size-yottabytes', - ), - 'bitrateunits' => array( - 'bitrate-bits', - 'bitrate-kilobits', - 'bitrate-megabits', - 'bitrate-gigabits', - 'bitrate-terabits', - 'bitrate-petabits', - 'bitrate-exabits', - 'bitrate-zetabits', - 'bitrate-yottabits', - ), - 'livepreview' => array( - 'livepreview-loading', - 'livepreview-ready', - 'livepreview-failed', - 'livepreview-error', - ), - 'lagwarning' => array( - 'lag-warn-normal', - 'lag-warn-high', - ), - 'watch' => array( - 'confirm-watch-button', - ), - 'watchlisteditor' => array( - 'editwatchlist-summary', - 'watchlistedit-numitems', - 'watchlistedit-noitems', - 'watchlistedit-normal-title', - 'watchlistedit-normal-legend', - 'watchlistedit-normal-explain', - 'watchlistedit-normal-submit', - 'watchlistedit-normal-done', - 'watchlistedit-raw-title', - 'watchlistedit-raw-legend', - 'watchlistedit-raw-explain', - 'watchlistedit-raw-titles', - 'watchlistedit-raw-submit', - 'watchlistedit-raw-done', - 'watchlistedit-raw-added', - 'watchlistedit-raw-removed', - ), - 'watchlisttools' => array( - 'watchlisttools-view', - 'watchlisttools-edit', - 'watchlisttools-raw', - ), - 'iranian-dates' => array( - 'iranian-calendar-m1', - 'iranian-calendar-m2', - 'iranian-calendar-m3', - 'iranian-calendar-m4', - 'iranian-calendar-m5', - 'iranian-calendar-m6', - 'iranian-calendar-m7', - 'iranian-calendar-m8', - 'iranian-calendar-m9', - 'iranian-calendar-m10', - 'iranian-calendar-m11', - 'iranian-calendar-m12', - ), - 'hijri-dates' => array( - 'hijri-calendar-m1', - 'hijri-calendar-m2', - 'hijri-calendar-m3', - 'hijri-calendar-m4', - 'hijri-calendar-m5', - 'hijri-calendar-m6', - 'hijri-calendar-m7', - 'hijri-calendar-m8', - 'hijri-calendar-m9', - 'hijri-calendar-m10', - 'hijri-calendar-m11', - 'hijri-calendar-m12', - ), - 'hebrew-dates' => array( - 'hebrew-calendar-m1', - 'hebrew-calendar-m2', - 'hebrew-calendar-m3', - 'hebrew-calendar-m4', - 'hebrew-calendar-m5', - 'hebrew-calendar-m6', - 'hebrew-calendar-m6a', - 'hebrew-calendar-m6b', - 'hebrew-calendar-m7', - 'hebrew-calendar-m8', - 'hebrew-calendar-m9', - 'hebrew-calendar-m10', - 'hebrew-calendar-m11', - 'hebrew-calendar-m12', - 'hebrew-calendar-m1-gen', - 'hebrew-calendar-m2-gen', - 'hebrew-calendar-m3-gen', - 'hebrew-calendar-m4-gen', - 'hebrew-calendar-m5-gen', - 'hebrew-calendar-m6-gen', - 'hebrew-calendar-m6a-gen', - 'hebrew-calendar-m6b-gen', - 'hebrew-calendar-m7-gen', - 'hebrew-calendar-m8-gen', - 'hebrew-calendar-m9-gen', - 'hebrew-calendar-m10-gen', - 'hebrew-calendar-m11-gen', - 'hebrew-calendar-m12-gen', - ), - 'signatures' => array( - 'signature', - 'signature-anon', - 'timezone-utc', - ), - 'CoreParserFunctions' => array( - 'unknown_extension_tag', - 'duplicate-defaultsort', - ), - 'version' => array( - 'version', - 'version-summary', - 'version-extensions', - 'version-specialpages', - 'version-parserhooks', - 'version-variables', - 'version-antispam', - 'version-skins', - 'version-api', - 'version-other', - 'version-mediahandlers', - 'version-hooks', - 'version-parser-extensiontags', - 'version-parser-function-hooks', - 'version-hook-name', - 'version-hook-subscribedby', - 'version-version', - 'version-svn-revision', - 'version-license', - 'version-poweredby-credits', - 'version-poweredby-others', - 'version-poweredby-translators', - 'version-credits-summary', - 'version-license-info', - 'version-software', - 'version-software-product', - 'version-software-version', - 'version-db-mysql-url', - 'version-db-mariadb-url', - 'version-db-percona-url', - 'version-db-postgres-url', - 'version-db-oracle-url', - 'version-db-sqlite-url', - 'version-db-mssql-url', - 'version-entrypoints', - 'version-entrypoints-header-entrypoint', - 'version-entrypoints-header-url', - 'version-entrypoints-articlepath', - 'version-entrypoints-scriptpath', - 'version-entrypoints-index-php', - 'version-entrypoints-api-php', - 'version-entrypoints-load-php', - ), - 'redirect' => array( - 'redirect', - 'redirect-legend', - 'redirect-text', - 'redirect-summary', - 'redirect-submit', - 'redirect-lookup', - 'redirect-value', - 'redirect-user', - 'redirect-revision', - 'redirect-file', - 'redirect-not-exists', - ), - 'fileduplicatesearch' => array( - 'fileduplicatesearch', - 'fileduplicatesearch-summary', - 'fileduplicatesearch-legend', - 'fileduplicatesearch-filename', - 'fileduplicatesearch-submit', - 'fileduplicatesearch-info', - 'fileduplicatesearch-result-1', - 'fileduplicatesearch-result-n', - 'fileduplicatesearch-noresults', - ), - 'special-specialpages' => array( - 'specialpages', - 'specialpages-summary', - 'specialpages-note', - 'specialpages-group-maintenance', - 'specialpages-group-other', - 'specialpages-group-login', - 'specialpages-group-changes', - 'specialpages-group-media', - 'specialpages-group-users', - 'specialpages-group-highuse', - 'specialpages-group-pages', - 'specialpages-group-pagetools', - 'specialpages-group-wiki', - 'specialpages-group-redirects', - 'specialpages-group-spam', - ), - 'special-blank' => array( - 'blankpage', - 'intentionallyblankpage', - ), - 'external_images' => array( - 'external_image_whitelist', - ), - 'special-tags' => array( - 'tags', - 'tags-summary', - 'tag-filter', - 'tag-filter-submit', - 'tag-list-wrapper', - 'tags-title', - 'tags-intro', - 'tags-tag', - 'tags-display-header', - 'tags-description-header', - 'tags-active-header', - 'tags-hitcount-header', - 'tags-active-yes', - 'tags-active-no', - 'tags-edit', - 'tags-hitcount', - ), - 'comparepages' => array( - 'comparepages', - 'comparepages-summary', - 'compare-selector', - 'compare-page1', - 'compare-page2', - 'compare-rev1', - 'compare-rev2', - 'compare-submit', - 'compare-invalid-title', - 'compare-title-not-exists', - 'compare-revision-not-exists', - ), - 'db-error-messages' => array( - 'dberr-header', - 'dberr-problems', - 'dberr-again', - 'dberr-info', - 'dberr-info-hidden', - 'dberr-usegoogle', - 'dberr-outofdate', - 'dberr-cachederror', - ), - 'html-forms' => array( - 'htmlform-invalid-input', - 'htmlform-select-badoption', - 'htmlform-int-invalid', - 'htmlform-float-invalid', - 'htmlform-int-toolow', - 'htmlform-int-toohigh', - 'htmlform-required', - 'htmlform-submit', - 'htmlform-reset', - 'htmlform-selectorother-other', - 'htmlform-no', - 'htmlform-yes', - 'htmlform-chosen-placeholder', - ), - 'sqlite' => array( - 'sqlite-has-fts', - 'sqlite-no-fts', - ), - 'unwatch' => array( - 'confirm-unwatch-button', - ), - 'logging' => array( - 'logentry-delete-delete', - 'logentry-delete-restore', - 'logentry-delete-event', - 'logentry-delete-revision', - 'logentry-delete-event-legacy', - 'logentry-delete-revision-legacy', - 'logentry-suppress-delete', - 'logentry-suppress-event', - 'logentry-suppress-revision', - 'logentry-suppress-event-legacy', - 'logentry-suppress-revision-legacy', - 'revdelete-content-hid', - 'revdelete-summary-hid', - 'revdelete-uname-hid', - 'revdelete-content-unhid', - 'revdelete-summary-unhid', - 'revdelete-uname-unhid', - 'revdelete-restricted', - 'revdelete-unrestricted', - 'logentry-move-move', - 'logentry-move-move-noredirect', - 'logentry-move-move_redir', - 'logentry-move-move_redir-noredirect', - 'logentry-patrol-patrol', - 'logentry-patrol-patrol-auto', - 'logentry-newusers-newusers', - 'logentry-newusers-create', - 'logentry-newusers-create2', - 'logentry-newusers-byemail', - 'logentry-newusers-autocreate', - 'logentry-rights-rights', - 'logentry-rights-rights-legacy', - 'logentry-rights-autopromote', - 'rightsnone', - ), - 'logging-irc' => array( - 'revdelete-logentry', - 'logdelete-logentry', - 'revdelete-content', - 'revdelete-summary', - 'revdelete-uname', - 'revdelete-hid', - 'revdelete-unhid', - 'revdelete-log-message', - 'logdelete-log-message', - 'deletedarticle', - 'suppressedarticle', - 'undeletedarticle', - 'patrol-log-line', - 'patrol-log-auto', - 'patrol-log-diff', - '1movedto2', - '1movedto2_redir', - 'move-redirect-suppressed', - 'newuserlog-create-entry', - 'newuserlog-create2-entry', - 'newuserlog-autocreate-entry', - 'suppressedarticle', - 'deletedarticle', - // 'uploadedimage', - // 'overwroteimage', - 'rightslogentry', - 'rightslogentry-autopromote', - ), - 'feedback' => array( - 'feedback-bugornote', - 'feedback-subject', - 'feedback-message', - 'feedback-cancel', - 'feedback-submit', - 'feedback-adding', - 'feedback-error1', - 'feedback-error2', - 'feedback-error3', - 'feedback-thanks', - 'feedback-close', - 'feedback-bugcheck', - 'feedback-bugnew', - ), - 'searchsuggestions' => array( - 'searchsuggest-search', - 'searchsuggest-containing', - ), - 'apierrors' => array( - 'api-error-badaccess-groups', - 'api-error-badtoken', - 'api-error-copyuploaddisabled', - 'api-error-duplicate', - 'api-error-duplicate-archive', - 'api-error-duplicate-archive-popup-title', - 'api-error-duplicate-popup-title', - 'api-error-empty-file', - 'api-error-emptypage', - 'api-error-fetchfileerror', - 'api-error-fileexists-forbidden', - 'api-error-fileexists-shared-forbidden', - 'api-error-file-too-large', - 'api-error-filename-tooshort', - 'api-error-filetype-banned', - 'api-error-filetype-banned-type', - 'api-error-filetype-missing', - 'api-error-hookaborted', - 'api-error-http', - 'api-error-illegal-filename', - 'api-error-internal-error', - 'api-error-invalid-file-key', - 'api-error-missingparam', - 'api-error-missingresult', - 'api-error-mustbeloggedin', - 'api-error-mustbeposted', - 'api-error-noimageinfo', - 'api-error-nomodule', - 'api-error-ok-but-empty', - 'api-error-overwrite', - 'api-error-stashfailed', - 'api-error-publishfailed', - 'api-error-timeout', - 'api-error-unclassified', - 'api-error-unknown-code', - 'api-error-unknown-error', - 'api-error-unknown-warning', - 'api-error-unknownerror', - 'api-error-uploaddisabled', - 'api-error-verification-error', - ), - 'duration' => array( - 'duration-seconds', - 'duration-minutes', - 'duration-hours', - 'duration-days', - 'duration-weeks', - 'duration-years', - 'duration-decades', - 'duration-centuries', - 'duration-millennia' - ), - 'rotation' => array( - 'rotate-comment', - ), - 'limitreport' => array( - 'limitreport-title', - 'limitreport-cputime', - 'limitreport-cputime-value', - 'limitreport-walltime', - 'limitreport-walltime-value', - 'limitreport-ppvisitednodes', - 'limitreport-ppvisitednodes-value', - 'limitreport-ppgeneratednodes', - 'limitreport-ppgeneratednodes-value', - 'limitreport-postexpandincludesize', - 'limitreport-postexpandincludesize-value', - 'limitreport-templateargumentsize', - 'limitreport-templateargumentsize-value', - 'limitreport-expansiondepth', - 'limitreport-expansiondepth-value', - 'limitreport-expensivefunctioncount', - 'limitreport-expensivefunctioncount-value', - ), -); - -/** Comments for each block */ -$wgBlockComments = array( - 'sidebar' => "The sidebar for MonoBook is generated from this message, lines that do not -begin with * or ** are discarded, furthermore lines that do begin with ** and -do not contain | are also discarded, but do not depend on this behavior for -future releases. Also note that since each list value is wrapped in a unique -(X)HTML id it should only appear once and include characters that are legal -(X)HTML id names.", - 'toggles' => 'User preference toggles', - 'underline' => '', - 'editfont' => 'Font style option in Special:Preferences', - 'dates' => 'Dates', - 'categorypages' => 'Categories related messages', - 'mainpage' => '', - 'miscellaneous1' => '', - 'cologneblue' => 'Cologne Blue skin', - 'vector' => 'Vector skin', - 'miscellaneous2' => '', - 'links' => 'All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).', - 'badaccess' => '', - 'versionrequired' => '', - 'miscellaneous3' => '', - 'nstab' => "Short words for each namespace, by default used in the namespace tab in monobook", - 'main' => 'Main script and global functions', - 'errors' => 'General errors', - 'virus' => 'Virus scanner', - 'login' => 'Login and logout pages', - 'mail' => 'Email sending', - 'passwordstrength' => 'JavaScript password checks', - 'resetpass' => 'Change password dialog', - 'passwordreset' => 'Special:PasswordReset', - 'changeemail' => 'Special:ChangeEmail', - 'resettokens' => 'Special:ResetTokens', - 'toolbar' => 'Edit page toolbar', - 'edit' => 'Edit pages', - 'parserwarnings' => 'Parser/template warnings', - 'contentmodels' => 'Content models', - 'undo' => '"Undo" feature', - 'cantcreateaccount' => 'Account creation failure', - 'history' => 'History pages', - 'history-feed' => 'Revision feed', - 'revdelete' => 'Revision deletion', - 'suppression' => 'Suppression log', - 'mergehistory' => 'History merging', - 'mergelog' => 'Merge log', - 'diffs' => 'Diffs', - 'search' => 'Search results', - 'opensearch' => 'OpenSearch description', - 'preferences' => 'Preferences page', - 'preferences-email' => 'User preference: email validation using jQuery', - 'userrights' => 'User rights', - 'group' => 'Groups', - 'group-member' => '', - 'grouppage' => '', - 'right' => 'Rights', - 'action' => 'Associated actions - in the sentence "You do not have permission to X"', - 'rightslog' => 'User rights log', - 'recentchanges' => 'Recent changes', - 'recentchangeslinked' => 'Recent changes linked', - 'upload' => 'Upload', - 'zip' => 'ZipDirectoryReader', - 'upload-errors' => '', - 'filebackend-errors' => 'File backend', - 'filejournal-errors' => 'File journal errors', - 'lockmanager-errors' => 'Lock manager', - 'uploadstash' => 'Special:UploadStash', - 'img-auth' => 'img_auth script messages', - 'http-errors' => 'HTTP errors', - 'upload-curl-errors' => 'Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html>', - 'licenses' => '', - 'filelist' => 'Special:ListFiles', - 'filedescription' => 'File description page', - 'filerevert' => 'File reversion', - 'filedelete' => 'File deletion', - 'mimesearch' => 'MIME search', - 'unwatchedpages' => 'Unwatched pages', - 'listredirects' => 'List redirects', - 'unusedtemplates' => 'Unused templates', - 'randompage' => 'Random page', - 'randomincategory' => 'Random page in category', - 'randomredirect' => 'Random redirect', - 'statistics' => 'Statistics', - 'pageswithprop' => '', - 'doubleredirects' => '', - 'brokenredirects' => '', - 'withoutinterwiki' => '', - 'fewestrevisions' => '', - 'specialpages' => 'Miscellaneous special pages', - 'booksources' => 'Book sources', - 'magicwords' => 'Magic words', - 'logpages' => 'Special:Log', - 'allpages' => 'Special:AllPages', - 'categories' => 'Special:Categories', - 'deletedcontribs' => 'Special:DeletedContributions', - 'linksearch' => 'Special:LinkSearch', - 'listusers' => 'Special:ListUsers', - 'activeusers' => 'Special:ActiveUsers', - 'newuserlog' => 'Special:Log/newusers', - 'listgrouprights' => 'Special:ListGroupRights', - 'emailuser' => 'Email user', - 'usermessage' => 'User Messenger', - 'watchlist' => 'Watchlist', - 'watching' => 'Displayed when you click the "watch" button and it is in the process of watching', - 'enotif' => '', - 'delete' => 'Delete', - 'rollback' => 'Rollback', - 'edittokens' => 'Edit tokens', - 'protect' => 'Protect', - 'restrictions' => 'Restrictions (nouns)', - 'restriction-levels' => 'Restriction levels', - 'undelete' => 'Undelete', - 'nsform' => 'Namespace form on various pages', - 'contributions' => 'Contributions', - 'sp-contributions' => '', - 'whatlinkshere' => 'What links here', - 'block' => 'Block/unblock', - 'developertools' => 'Developer tools', - 'movepage' => 'Move page', - 'export' => 'Export', - 'allmessages' => 'Namespace 8 related', - 'thumbnails' => 'Thumbnails', - 'import' => 'Special:Import', - 'importlog' => 'Import log', - 'javaccripttest' => 'JavaScriptTest', - 'accesskeys' => 'Keyboard access keys for power users', - 'tooltips' => 'Tooltip help for the actions', - 'stylesheets' => 'Stylesheets', - 'scripts' => 'Scripts', - 'metadata_cc' => 'Metadata', - 'attribution' => 'Attribution', - 'spamprotection' => 'Spam protection', - 'info' => 'Info page', - 'skin' => 'Skin names', - 'patrolling' => 'Patrolling', - 'patrol-log' => 'Patrol log', - 'imagedeletion' => 'Image deletion', - 'browsediffs' => 'Browsing diffs', - 'newfiles' => 'Special:NewFiles', - 'video-info' => 'Video information, used by Language::formatTimePeriod() to format lengths in the above messages', - 'human-timestamps' => 'Human-readable timestamps', - 'badimagelist' => 'Bad image list', - 'variantname-zh' => "Short names for language variants used for language conversion links. -Variants for Chinese language", - 'variantname-gan' => 'Variants for Gan language', - 'variantname-sr' => 'Variants for Serbian language', - 'variantname-kk' => 'Variants for Kazakh language', - 'variantname-ku' => 'Variants for Kurdish language', - 'variantname-tg' => 'Variants for Tajiki language', - 'variantname-iu' => 'Variants for Inuktitut language', - 'variantname-shi' => 'Variants for Tachelhit language', - 'media-info' => 'Media information', - 'metadata' => 'Metadata', - 'exif' => 'Exif tags', - 'exif-values' => 'Make & model, can be wikified in order to link to the camera and model name', - 'exif-compression' => 'Exif attributes', - 'exif-copyrighted' => '', - 'exif-unknowndate' => '', - 'exif-photometricinterpretation' => '', - 'exif-orientation' => '', - 'exif-planarconfiguration' => '', - 'exif-xyresolution' => '', - 'exif-colorspace' => '', - 'exif-componentsconfiguration' => '', - 'exif-exposureprogram' => '', - 'exif-subjectdistance-value' => '', - 'exif-meteringmode' => '', - 'exif-lightsource' => '', - 'exif-flash' => 'Flash modes', - 'exif-focalplaneresolutionunit' => '', - 'exif-sensingmethod' => '', - 'exif-filesource' => '', - 'exif-scenetype' => '', - 'exif-customrendered' => '', - 'exif-exposuremode' => '', - 'exif-whitebalance' => '', - 'exif-scenecapturetype' => '', - 'exif-gaincontrol' => '', - 'exif-contrast' => '', - 'exif-saturation' => '', - 'exif-sharpness' => '', - 'exif-subjectdistancerange' => '', - 'exif-gpslatitude' => 'Pseudotags used for GPSLatitudeRef and GPSDestLatitudeRef', - 'exif-gpslongitude' => 'Pseudotags used for GPSLongitudeRef and GPSDestLongitudeRef', - 'exif-altituderef' => 'Pseudotags used for GPSAltitudeRef', - 'exif-gpsstatus' => '', - 'exif-gpsmeasuremode' => '', - 'exif-gpsspeed' => 'Pseudotags used for GPSSpeedRef', - 'exif-gpsdestdistanceref' => 'Pseudotags used for GPSDestDistanceRef', - 'exif-gdop' => '', - 'exif-objectcycle' => '', - 'exif-gpsdirection' => 'Pseudotags used for GPSTrackRef, GPSImgDirectionRef and GPSDestBearingRef', - 'exif-ycbcrpositioning' => '', - 'exif-dc' => '', - 'exif-rating' => '', - 'exif-isospeedratings' => '', - 'exif-maxaperturevalue' => '', - 'exif-iimcategory' => '', - 'exif-urgency' => '', - 'edit-externally' => 'External editor support', - 'all' => "'all' in various places, this might be different for inflected languages", - 'confirmemail' => 'Email address confirmation', - 'scarytransclusion' => 'Scary transclusion', - 'deleteconflict' => 'Delete conflict', - 'unit-pixel' => '', - 'purge' => 'action=purge', - 'watch-unwatch' => 'action=watch/unwatch', - 'separators' => 'Separators for various lists, etc.', - 'imgmulti' => 'Multipage image navigation', - 'tablepager' => 'Table pager', - 'autosumm' => 'Auto-summaries', - 'autoblock_whitelist' => 'Autoblock whitelist', - 'sizeunits' => 'Size units', - 'bitrateunits' => 'Bitrate units', - 'livepreview' => 'Live preview', - 'lagwarning' => 'Friendlier slave lag warnings', - 'watchlisteditor' => 'Watchlist editor', - 'watchlisttools' => 'Watchlist editing tools', - 'iranian-dates' => 'Iranian month names', - 'hijri-dates' => 'Hijri month names', - 'hebrew-dates' => 'Hebrew month names', - 'signatures' => 'Signatures', - 'CoreParserFunctions' => 'Core parser functions', - 'version' => 'Special:Version', - 'redirect' => 'Special:Redirect', - 'fileduplicatesearch' => 'Special:FileDuplicateSearch', - 'special-specialpages' => 'Special:SpecialPages', - 'special-blank' => 'Special:BlankPage', - 'external_images' => 'External image whitelist', - 'special-tags' => 'Special:Tags', - 'comparepages' => 'Special:ComparePages', - 'db-error-messages' => 'Database error messages', - 'html-forms' => 'HTML forms', - 'sqlite' => 'SQLite database support', - 'logging' => 'New logging system', - 'logging-irc' => 'For IRC, see bug 34508. Do not change', - 'feedback' => 'Feedback', - 'searchsuggestions' => 'Search suggestions', - 'apierrors' => 'API errors', - 'duration' => 'Durations', - 'cachedspecial' => 'SpecialCachedPage', - 'rotation' => 'Image rotation', - 'limitreport' => 'Limit report', -); diff --git a/maintenance/language/rebuildLanguage.php b/maintenance/language/rebuildLanguage.php deleted file mode 100644 index 66948aeb..00000000 --- a/maintenance/language/rebuildLanguage.php +++ /dev/null @@ -1,126 +0,0 @@ -<?php -/** - * Rewrite the messages array in the files languages/messages/MessagesXx.php. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup MaintenanceLanguage - * @defgroup MaintenanceLanguage MaintenanceLanguage - */ - -require_once __DIR__ . '/../commandLine.inc'; -require_once 'languages.inc'; -require_once 'writeMessagesArray.inc'; - -/** - * Rewrite a messages array. - * - * @param $languages - * @param $code string The language code. - * @param bool $write Write to the messages file? - * @param bool $listUnknown List the unknown messages? - * @param bool $removeUnknown Remove the unknown messages? - * @param bool $removeDupes Remove the duplicated messages? - * @param $dupeMsgSource string The source file intended to remove from the array. - * @param $messagesFolder String: path to a folder to store the MediaWiki messages. - */ -function rebuildLanguage( $languages, $code, $write, $listUnknown, $removeUnknown, $removeDupes, $dupeMsgSource, $messagesFolder ) { - $messages = $languages->getMessages( $code ); - $messages = $messages['all']; - if ( $removeDupes ) { - $messages = removeDupes( $messages, $dupeMsgSource ); - } - MessageWriter::writeMessagesToFile( $messages, $code, $write, $listUnknown, $removeUnknown, $messagesFolder ); -} - -/** - * Remove duplicates from a message array. - * - * @param $oldMsgArray array The input message array. - * @param $dupeMsgSource string The source file path for duplicates. - * @return Array $newMsgArray The output message array, with duplicates removed. - */ -function removeDupes( $oldMsgArray, $dupeMsgSource ) { - if ( file_exists( $dupeMsgSource ) ) { - include $dupeMsgSource; - if ( !isset( $dupeMessages ) ) { - echo "There are no duplicated messages in the source file provided."; - exit( 1 ); - } - } else { - echo "The specified file $dupeMsgSource cannot be found."; - exit( 1 ); - } - $newMsgArray = $oldMsgArray; - foreach ( $oldMsgArray as $key => $value ) { - if ( array_key_exists( $key, $dupeMessages ) ) { - unset( $newMsgArray[$key] ); - } - } - return $newMsgArray; -} - -# Show help -if ( isset( $options['help'] ) ) { - echo <<<TEXT -Run this script to rewrite the messages array in the files languages/messages/MessagesXX.php. -Parameters: - * lang: Language code (default: the installation default language). You can also specify "all" to check all the languages. - * help: Show this help. -Options: - * dry-run: Do not write the array to the file. - * no-unknown: Do not list the unknown messages. - * remove-unknown: Remove unknown messages. - * remove-duplicates: Remove duplicated messages based on a PHP source file. - * messages-folder: An alternative folder with MediaWiki messages. - -TEXT; - exit( 1 ); -} - -# Get the language code -if ( isset( $options['lang'] ) ) { - $wgCode = $options['lang']; -} else { - $wgCode = $wgContLang->getCode(); -} - -# Get the duplicate message source -if ( isset( $options['remove-duplicates'] ) && ( strcmp( $options['remove-duplicates'], '' ) ) ) { - $wgDupeMessageSource = $options['remove-duplicates']; -} else { - $wgDupeMessageSource = ''; -} - -# Get the options -$wgWriteToFile = !isset( $options['dry-run'] ); -$wgListUnknownMessages = !isset( $options['no-unknown'] ); -$wgRemoveUnknownMessages = isset( $options['remove-unknown'] ); -$wgRemoveDuplicateMessages = isset( $options['remove-duplicates'] ); -$messagesFolder = isset( $options['messages-folder'] ) ? $options['messages-folder'] : false; - -# Get language objects -$languages = new languages(); - -# Write all the language -if ( $wgCode == 'all' ) { - foreach ( $languages->getLanguages() as $languageCode ) { - rebuildLanguage( $languages, $languageCode, $wgWriteToFile, $wgListUnknownMessages, $wgRemoveUnknownMessages, $wgRemoveDuplicateMessages, $wgDupeMessageSource, $messagesFolder ); - } -} else { - rebuildLanguage( $languages, $wgCode, $wgWriteToFile, $wgListUnknownMessages, $wgRemoveUnknownMessages, $wgRemoveDuplicateMessages, $wgDupeMessageSource, $messagesFolder ); -} diff --git a/maintenance/language/transstat.php b/maintenance/language/transstat.php index 61b84a07..4a853b0f 100644 --- a/maintenance/language/transstat.php +++ b/maintenance/language/transstat.php @@ -24,7 +24,7 @@ * @author Antoine Musso <hashar at free dot fr> * * Output is posted from time to time on: - * http://www.mediawiki.org/wiki/Localisation_statistics + * https://www.mediawiki.org/wiki/Localisation_statistics */ $optionsWithArgs = array( 'output' ); @@ -32,7 +32,6 @@ require_once __DIR__ . '/../commandLine.inc'; require_once 'languages.inc'; require_once __DIR__ . '/StatOutputs.php'; - if ( isset( $options['help'] ) ) { showUsage(); } @@ -57,25 +56,23 @@ TEXT; exit( 1 ); } - - # Select an output engine switch ( $options['output'] ) { case 'wiki': - $output = new wikiStatsOutput(); + $output = new WikiStatsOutput(); break; case 'text': - $output = new textStatsOutput(); + $output = new TextStatsOutput(); break; case 'csv': - $output = new csvStatsOutput(); + $output = new CsvStatsOutput(); break; default: showUsage(); } # Languages -$wgLanguages = new languages(); +$wgLanguages = new Languages(); # Header $output->heading(); @@ -97,7 +94,8 @@ $wgRequiredMessagesNumber = count( $wgGeneralMessages['required'] ); foreach ( $wgLanguages->getLanguages() as $code ) { # Don't check English, RTL English or dummy language codes if ( $code == 'en' || $code == 'enRTL' || ( is_array( $wgDummyLanguageCodes ) && - isset( $wgDummyLanguageCodes[$code] ) ) ) { + isset( $wgDummyLanguageCodes[$code] ) ) + ) { continue; } @@ -107,16 +105,33 @@ foreach ( $wgLanguages->getLanguages() as $code ) { $messages = $wgLanguages->getMessages( $code ); $messagesNumber = count( $messages['translated'] ); $requiredMessagesNumber = count( $messages['required'] ); - $requiredMessagesPercent = $output->formatPercent( $requiredMessagesNumber, $wgRequiredMessagesNumber ); + $requiredMessagesPercent = $output->formatPercent( + $requiredMessagesNumber, + $wgRequiredMessagesNumber + ); $obsoleteMessagesNumber = count( $messages['obsolete'] ); - $obsoleteMessagesPercent = $output->formatPercent( $obsoleteMessagesNumber, $messagesNumber, true ); + $obsoleteMessagesPercent = $output->formatPercent( + $obsoleteMessagesNumber, + $messagesNumber, + true + ); $messagesWithMismatchVariables = $wgLanguages->getMessagesWithMismatchVariables( $code ); $emptyMessages = $wgLanguages->getEmptyMessages( $code ); $messagesWithWhitespace = $wgLanguages->getMessagesWithWhitespace( $code ); $nonXHTMLMessages = $wgLanguages->getNonXHTMLMessages( $code ); $messagesWithWrongChars = $wgLanguages->getMessagesWithWrongChars( $code ); - $problematicMessagesNumber = count( array_unique( array_merge( $messagesWithMismatchVariables, $emptyMessages, $messagesWithWhitespace, $nonXHTMLMessages, $messagesWithWrongChars ) ) ); - $problematicMessagesPercent = $output->formatPercent( $problematicMessagesNumber, $messagesNumber, true ); + $problematicMessagesNumber = count( array_unique( array_merge( + $messagesWithMismatchVariables, + $emptyMessages, + $messagesWithWhitespace, + $nonXHTMLMessages, + $messagesWithWrongChars + ) ) ); + $problematicMessagesPercent = $output->formatPercent( + $problematicMessagesNumber, + $messagesNumber, + true + ); # Output them $output->blockstart(); diff --git a/maintenance/language/validate.php b/maintenance/language/validate.php deleted file mode 100644 index 63d9b847..00000000 --- a/maintenance/language/validate.php +++ /dev/null @@ -1,64 +0,0 @@ -<?php -/** - * Check language files for unrecognised variables. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup MaintenanceLanguage - */ - -if ( PHP_SAPI != 'cli' ) { - die( "Run me from the command line please.\n" ); -} - -if ( !isset( $argv[1] ) ) { - print "Usage: php {$argv[0]} <filename>\n"; - exit( 1 ); -} -array_shift( $argv ); - -define( 'MEDIAWIKI', 1 ); -define( 'NOT_REALLY_MEDIAWIKI', 1 ); - -$IP = __DIR__ . '/../..'; - -require_once "$IP/includes/Defines.php"; -require_once "$IP/languages/Language.php"; - -$files = array(); -foreach ( $argv as $arg ) { - $files = array_merge( $files, glob( $arg ) ); -} - -foreach ( $files as $filename ) { - print "$filename..."; - $vars = getVars( $filename ); - $keys = array_keys( $vars ); - $diff = array_diff( $keys, Language::$mLocalisationKeys ); - if ( $diff ) { - print "\nWarning: unrecognised variable(s): " . implode( ', ', $diff ) . "\n"; - } else { - print " ok\n"; - } -} - -function getVars( $filename ) { - require $filename; - $vars = get_defined_vars(); - unset( $vars['filename'] ); - return $vars; -} diff --git a/maintenance/language/writeMessagesArray.inc b/maintenance/language/writeMessagesArray.inc deleted file mode 100644 index fc0da3f5..00000000 --- a/maintenance/language/writeMessagesArray.inc +++ /dev/null @@ -1,283 +0,0 @@ -<?php -/** - * Write a messages array as a PHP text. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup MaintenanceLanguage - */ - -/** - * @ingroup MaintenanceLanguage - */ -class MessageWriter { - static $optionalComment = 'only translate this message to other languages if you have to change it'; - static $ignoredComment = "do not translate or duplicate this message to other languages"; - - static $messageStructure; - static $blockComments; - static $ignoredMessages; - static $optionalMessages; - - /** - * Write a messages array as a PHP text and write it to the messages file. - * - * @param $messages Array: the messages array. - * @param $code String: the language code. - * @param $write Boolean: write to the messages file? - * @param $listUnknown Boolean: list the unknown messages? - * @param $removeUnknown Boolean: whether to remove unkown messages - * @param $messagesFolder String: path to a folder to store the MediaWiki messages. Defaults to the current install. - */ - public static function writeMessagesToFile( $messages, $code, $write, $listUnknown, $removeUnknown, $messagesFolder = false ) { - # Rewrite the messages array - $messages = self::writeMessagesArray( $messages, $code == 'en', false, $removeUnknown ); - $messagesText = $messages[0]; - $sortedMessages = $messages[1]; - - # Write to the file - if ( $messagesFolder ) { - $filename = Language::getFileName( "$messagesFolder/Messages", $code ); - } else { - $filename = Language::getMessagesFileName( $code ); - } - - if ( file_exists( $filename ) ) { - $contents = file_get_contents( $filename ); - } else { - $contents = '<?php -$messages = array( -); -'; - } - - if ( strpos( $contents, '$messages' ) !== false ) { - $contents = explode( '$messages', $contents ); - if ( $messagesText == '$messages' . $contents[1] ) { - echo "Generated messages for language $code. Same as the current file.\n"; - } else { - if ( $write ) { - $new = $contents[0]; - $new .= $messagesText; - file_put_contents( $filename, $new ); - echo "Generated and wrote messages for language $code.\n"; - } else { - echo "Generated messages for language $code. Please run the script again (without the parameter \"dry-run\") to write the array to the file.\n"; - } - } - if ( $listUnknown && isset( $sortedMessages['unknown'] ) && !empty( $sortedMessages['unknown'] ) ) { - if ( $removeUnknown ) { - echo "\nThe following " . count( $sortedMessages['unknown'] ) . " unknown messages have been removed:\n"; - } else { - echo "\nThere are " . count( $sortedMessages['unknown'] ) . " unknown messages, please check them:\n"; - } - foreach ( $sortedMessages['unknown'] as $key => $value ) { - echo "* " . $key . "\n"; - } - } - } else { - echo "Generated messages for language $code. There seem to be no messages array in the file.\n"; - } - } - - /** - * Write a messages array as a PHP text. - * - * @param $messages Array: the messages array. - * @param $ignoredComments Boolean: show comments about ignored and optional - * messages? (For English.) - * @param $prefix String: base path for messages.inc and messageTypes.inc files - * or false for default path (this directory) - * @param $removeUnknown Boolean: whether to remove unkown messages - * - * @return Array of the PHP text and the sorted messages array. - */ - public static function writeMessagesArray( $messages, $ignoredComments = false, $prefix = false, $removeUnknown = false ) { - # Load messages - $dir = $prefix ? $prefix : __DIR__; - - require $dir . '/messages.inc'; - self::$messageStructure = $wgMessageStructure; - self::$blockComments = $wgBlockComments; - - require $dir . '/messageTypes.inc'; - self::$ignoredMessages = $wgIgnoredMessages; - self::$optionalMessages = $wgOptionalMessages; - - # Sort messages to blocks - $sortedMessages['unknown'] = $messages; - foreach ( self::$messageStructure as $blockName => $block ) { - /** - * @var $block array - */ - foreach ( $block as $key ) { - if ( array_key_exists( $key, $sortedMessages['unknown'] ) ) { - $sortedMessages[$blockName][$key] = $sortedMessages['unknown'][$key]; - unset( $sortedMessages['unknown'][$key] ); - } - } - } - - # Write all the messages - $messagesText = "\$messages = array( -"; - foreach ( $sortedMessages as $block => $messages ) { - # Skip if it's the block of unknown messages - handle that in the end of file - if ( $block == 'unknown' ) { - continue; - } - - if ( $ignoredComments ) { - $ignored = self::$ignoredMessages; - $optional = self::$optionalMessages; - } else { - $ignored = array(); - $optional = array(); - } - $comments = self::makeComments( array_keys( $messages ), $ignored, $optional ); - - # Write the block - $messagesText .= self::writeMessagesBlock( self::$blockComments[$block], $messages, $comments ); - } - - # Write the unknown messages, alphabetically sorted. - # Of course, we don't have any comments for them, because they are unknown. - if ( !$removeUnknown ) { - ksort( $sortedMessages['unknown'] ); - $messagesText .= self::writeMessagesBlock( 'Unknown messages', $sortedMessages['unknown'] ); - } - $messagesText .= "); -"; - return array( $messagesText, $sortedMessages ); - } - - /** - * Generates an array of comments for messages. - * - * @param $messages Array: key of messages. - * @param $ignored Array: list of ingored message keys. - * @param $optional Array: list of optional message keys. - * @return array - */ - public static function makeComments( $messages, $ignored, $optional ) { - # Comment collector - $commentArray = array(); - - # List of keys only - foreach ( $messages as $key ) { - if ( in_array( $key, $ignored ) ) { - $commentArray[$key] = ' # ' . self::$ignoredComment; - } elseif ( in_array( $key, $optional ) ) { - $commentArray[$key] = ' # ' . self::$optionalComment; - } - } - - return $commentArray; - } - - /** - * Write a block of messages to PHP. - * - * @param $blockComment String: the comment of whole block. - * @param $messages Array: the block messages. - * @param $messageComments Array: optional comments for messages in this block. - * @param $prefix String: prefix for every line, for indenting purposes. - * - * @return string The block, formatted in PHP. - */ - public static function writeMessagesBlock( $blockComment, $messages, - $messageComments = array(), $prefix = '' ) { - - $blockText = ''; - - # Skip the block if it includes no messages - if ( empty( $messages ) ) { - return ''; - } - - # Format the block comment (if exists); check for multiple lines comments - if ( !empty( $blockComment ) ) { - if ( strpos( $blockComment, "\n" ) === false ) { - $blockText .= "$prefix# $blockComment -"; - } else { - $blockText .= "$prefix/* -$blockComment -*/ -"; - } - } - - # Get max key length - $maxKeyLength = max( array_map( 'strlen', array_keys( $messages ) ) ); - - # Format the messages - foreach ( $messages as $key => $value ) { - # Add the key name - $blockText .= "$prefix'$key'"; - - # Add the appropriate block whitespace - $blockText .= str_repeat( ' ', $maxKeyLength - strlen( $key ) ); - - # Refer to the value - $blockText .= ' => '; - - # Check for the appropriate apostrophe and add the value - # Quote \ here, because it needs always escaping - $value = addcslashes( $value, '\\' ); - - # For readability - $single = "'"; - $double = '"'; - - if ( strpos( $value, $single ) === false ) { - # Nothing ugly, just use ' - $blockText .= $single . $value . $single; - } elseif ( strpos( $value, $double ) === false && !preg_match( '/\$[a-zA-Z_\x7f-\xff]/', $value ) ) { - # No "-quotes, no variables that need quoting, use " - $blockText .= $double . $value . $double; - } else { - # Something needs quoting, pick the quote which causes less quoting - $quote = substr_count( $value, $double ) + substr_count( $value, '$' ) >= substr_count( $value, $single ) ? $single : $double; - if ( $quote === $double ) { - $extra = '$'; - } else { - $extra = ''; - } - $blockText .= $quote . addcslashes( $value, $quote . $extra ) . $quote; - } - - # Comma - $blockText .= ','; - - # Add comments, if there is any - if ( array_key_exists( $key, $messageComments ) ) { - $blockText .= $messageComments[$key]; - } - - # Newline - $blockText .= " -"; - } - - # Newline to end the block - $blockText .= " -"; - - return $blockText; - } -} diff --git a/maintenance/language/zhtable/Makefile.py b/maintenance/language/zhtable/Makefile.py index 7e197945..f902e581 100644 --- a/maintenance/language/zhtable/Makefile.py +++ b/maintenance/language/zhtable/Makefile.py @@ -30,9 +30,9 @@ def unichr3( *args ): return [unichr( int( i[2:7], 16 ) ) for i in args if i[2:7]] # DEFINE -UNIHAN_VER = '6.2.0' +UNIHAN_VER = '6.3.0' SF_MIRROR = 'dfn' -SCIM_TABLES_VER = '0.5.11' +SCIM_TABLES_VER = '0.5.13' SCIM_PINYIN_VER = '0.5.92' LIBTABE_VER = '0.2.3' # END OF DEFINE @@ -59,7 +59,11 @@ def uncompress( fp, member, encoding = 'U8' ): shutil.move( member, name ) if '/' in member: shutil.rmtree( member.split( '/', 1 )[0] ) - return open( name, 'rb', encoding, 'ignore' ) + if pyversion[:1] in ['2']: + fc = open( name, 'rb', encoding, 'ignore' ) + else: + fc = open( name, 'r', encoding = encoding, errors = 'ignore' ) + return fc unzip = lambda path, member, encoding = 'U8': \ uncompress( zf.ZipFile( path ), member, encoding ) @@ -136,7 +140,10 @@ def unihanParser( path ): def applyExcludes( mlist, path ): """ Apply exclude rules from path to mlist. """ - excludes = open( path, 'rb', 'U8' ).read().split() + if pyversion[:1] in ['2']: + excludes = open( path, 'rb', 'U8' ).read().split() + else: + excludes = open( path, 'r', encoding = 'U8' ).read().split() excludes = [word.split( '#' )[0].strip() for word in excludes] excludes = '|'.join( excludes ) excptn = re.compile( '.*(?:%s).*' % excludes ) @@ -145,7 +152,7 @@ def applyExcludes( mlist, path ): return mlist def charManualTable( path ): - fp = open( path, 'rb', 'U8' ) + fp = open( path, 'r', encoding = 'U8' ) ret = {} for line in fp: elems = line.split( '#' )[0].split( '|' ) @@ -156,13 +163,18 @@ def charManualTable( path ): def toManyRules( src_table ): tomany = set() - for ( f, t ) in src_table.iteritems(): - for i in range( 1, len( t ) ): - tomany.add( t[i] ) + if pyversion[:1] in ['2']: + for ( f, t ) in src_table.iteritems(): + for i in range( 1, len( t ) ): + tomany.add( t[i] ) + else: + for ( f, t ) in src_table.items(): + for i in range( 1, len( t ) ): + tomany.add( t[i] ) return tomany def removeRules( path, table ): - fp = open( path, 'rb', 'U8' ) + fp = open( path, 'r', encoding = 'U8' ) texc = list() for line in fp: elems = line.split( '=>' ) @@ -179,13 +191,18 @@ def removeRules( path, table ): if t: texc.append( t ) texcptn = re.compile( '^(?:%s)$' % '|'.join( texc ) ) - for (tmp_f, tmp_t) in table.copy().iteritems(): - if texcptn.match( tmp_t ): - table.pop( tmp_f ) + if pyversion[:1] in ['2']: + for (tmp_f, tmp_t) in table.copy().iteritems(): + if texcptn.match( tmp_t ): + table.pop( tmp_f ) + else: + for (tmp_f, tmp_t) in table.copy().items(): + if texcptn.match( tmp_t ): + table.pop( tmp_f ) return table def customRules( path ): - fp = open( path, 'rb', 'U8' ) + fp = open( path, 'r', encoding = 'U8' ) ret = dict() for line in fp: elems = line.split( '#' )[0].split() @@ -210,7 +227,7 @@ def translate( text, conv_table ): return text def manualWordsTable( path, conv_table, reconv_table ): - fp = open( path, 'rb', 'U8' ) + fp = open( path, 'r', encoding = 'U8' ) reconv_table = {} wordlist = [line.split( '#' )[0].strip() for line in fp] wordlist = list( set( wordlist ) ) @@ -285,8 +302,12 @@ def main(): t2s_1tomany.update( charManualTable( 'trad2simp.manual' ) ) s2t_1tomany.update( charManualTable( 'simp2trad.manual' ) ) - t2s_1to1 = dict( [( f, t[0] ) for ( f, t ) in t2s_1tomany.iteritems()] ) - s2t_1to1 = dict( [( f, t[0] ) for ( f, t ) in s2t_1tomany.iteritems()] ) + if pyversion[:1] in ['2']: + t2s_1to1 = dict( [( f, t[0] ) for ( f, t ) in t2s_1tomany.iteritems()] ) + s2t_1to1 = dict( [( f, t[0] ) for ( f, t ) in s2t_1tomany.iteritems()] ) + else: + t2s_1to1 = dict( [( f, t[0] ) for ( f, t ) in t2s_1tomany.items()] ) + s2t_1to1 = dict( [( f, t[0] ) for ( f, t ) in s2t_1tomany.items()] ) s_tomany = toManyRules( t2s_1tomany ) t_tomany = toManyRules( s2t_1tomany ) @@ -333,10 +354,16 @@ def main(): # Final tables # sorted list toHans - t2s_1to1 = dict( [( f, t ) for ( f, t ) in t2s_1to1.iteritems() if f != t] ) + if pyversion[:1] in ['2']: + t2s_1to1 = dict( [( f, t ) for ( f, t ) in t2s_1to1.iteritems() if f != t] ) + else: + t2s_1to1 = dict( [( f, t ) for ( f, t ) in t2s_1to1.items() if f != t] ) toHans = dictToSortedList( t2s_1to1, 0 ) + dictToSortedList( t2s_word2word, 1 ) # sorted list toHant - s2t_1to1 = dict( [( f, t ) for ( f, t ) in s2t_1to1.iteritems() if f != t] ) + if pyversion[:1] in ['2']: + s2t_1to1 = dict( [( f, t ) for ( f, t ) in s2t_1to1.iteritems() if f != t] ) + else: + s2t_1to1 = dict( [( f, t ) for ( f, t ) in s2t_1to1.items() if f != t] ) toHant = dictToSortedList( s2t_1to1, 0 ) + dictToSortedList( s2t_word2word, 1 ) # sorted list toCN toCN = dictToSortedList( customRules( 'toCN.manual' ), 1 ) @@ -352,7 +379,7 @@ def main(): /** * Simplified / Traditional Chinese conversion tables * - * Automatically generated using code and data in includes/zhtable/ + * Automatically generated using code and data in maintenance/language/zhtable/ * Do not modify directly! * * @file @@ -372,7 +399,10 @@ $zh2Hant = array(\n''' + PHPArray( toSG ) \ + '\n);\n' - f = open( os.path.join( '..', '..', '..', 'includes', 'ZhConversion.php' ), 'wb', encoding = 'utf8' ) + if pyversion[:1] in ['2']: + f = open( os.path.join( '..', '..', '..', 'includes', 'ZhConversion.php' ), 'wb', encoding = 'utf8' ) + else: + f = open( os.path.join( '..', '..', '..', 'includes', 'ZhConversion.php' ), 'w', buffering = 4096, encoding = 'utf8' ) print ('Writing ZhConversion.php ... ') f.write( php ) f.close() diff --git a/maintenance/language/zhtable/trad2simp_supp_unset.manual b/maintenance/language/zhtable/trad2simp_supp_unset.manual deleted file mode 100644 index e69de29b..00000000 --- a/maintenance/language/zhtable/trad2simp_supp_unset.manual +++ /dev/null diff --git a/maintenance/language/zhtable/tradphrases.manual b/maintenance/language/zhtable/tradphrases.manual index e20ca05b..69b2c832 100644 --- a/maintenance/language/zhtable/tradphrases.manual +++ b/maintenance/language/zhtable/tradphrases.manual @@ -3991,6 +3991,7 @@ 學裡 獄裡 館裡 +箱裡 系列裡 村子裡 艷后 diff --git a/maintenance/locking/LockServerDaemon.php b/maintenance/locking/LockServerDaemon.php deleted file mode 100644 index 01fbac72..00000000 --- a/maintenance/locking/LockServerDaemon.php +++ /dev/null @@ -1,640 +0,0 @@ -<?php -/** - * Simple lock server daemon that accepts lock/unlock requests. - * - * This code should not require MediaWiki setup or PHP files. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup LockManager Maintenance - */ - -if ( PHP_SAPI !== 'cli' ) { - die( "This is not a valid entry point.\n" ); -} -error_reporting( E_ALL ); - -// Run the server... -set_time_limit( 0 ); -LockServerDaemon::init( - getopt( '', array( - 'address:', 'port:', 'authKey:', - 'lockTimeout::', 'maxClients::', 'maxBacklog::', 'maxLocks::', - ) ) -)->main(); - -/** - * Simple lock server daemon that accepts lock/unlock requests - * - * @ingroup LockManager Maintenance - */ -class LockServerDaemon { - /** @var resource */ - protected $sock; // socket to listen/accept on - /** @var Array */ - protected $sessions = array(); // (session => resource) - /** @var Array */ - protected $deadSessions = array(); // (session => UNIX timestamp) - - /** @var LockHolder */ - protected $lockHolder; - - protected $address; // string IP address - protected $port; // integer - protected $authKey; // string key - protected $lockTimeout; // integer number of seconds - protected $maxBacklog; // integer - protected $maxClients; // integer - - protected $startTime; // integer UNIX timestamp - protected $ticks = 0; // integer counter - - /* @var LockServerDaemon */ - protected static $instance = null; - - /** - * @params $config Array - * @param array $config - * @throws Exception - * @return LockServerDaemon - */ - public static function init( array $config ) { - if ( self::$instance ) { - throw new Exception( 'LockServer already initialized.' ); - } - foreach ( array( 'address', 'port', 'authKey' ) as $par ) { - if ( !isset( $config[$par] ) ) { - die( "Usage: php LockServerDaemon.php " . - "--address <address> --port <port> --authKey <key> " . - "[--lockTimeout <seconds>] " . - "[--maxLocks <integer>] [--maxClients <integer>] [--maxBacklog <integer>]\n" - ); - } - } - self::$instance = new self( $config ); - return self::$instance; - } - - /** - * @params $config Array - */ - protected function __construct( array $config ) { - // Required parameters... - $this->address = $config['address']; - $this->port = $config['port']; - $this->authKey = $config['authKey']; - // Parameters with defaults... - $this->lockTimeout = isset( $config['lockTimeout'] ) - ? (int)$config['lockTimeout'] - : 60; - $this->maxClients = isset( $config['maxClients'] ) - ? (int)$config['maxClients'] - : 1000; // less than default FD_SETSIZE - $this->maxBacklog = isset( $config['maxBacklog'] ) - ? (int)$config['maxBacklog'] - : 100; - $maxLocks = isset( $config['maxLocks'] ) - ? (int)$config['maxLocks'] - : 10000; - - $this->lockHolder = new LockHolder( $maxLocks ); - } - - /** - * @throws Exception - * @return void - */ - protected function setupServerSocket() { - if ( !function_exists( 'socket_create' ) ) { - throw new Exception( "PHP sockets extension missing from PHP CLI mode." ); - } - $sock = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ); - if ( $sock === false ) { - throw new Exception( "socket_create(): " . socket_strerror( socket_last_error() ) ); - } - socket_set_option( $sock, SOL_SOCKET, SO_REUSEADDR, 1 ); // bypass 2MLS - socket_set_nonblock( $sock ); // don't block on accept() - if ( socket_bind( $sock, $this->address, $this->port ) === false ) { - throw new Exception( "socket_bind(): " . - socket_strerror( socket_last_error( $sock ) ) ); - } elseif ( socket_listen( $sock, $this->maxBacklog ) === false ) { - throw new Exception( "socket_listen(): " . - socket_strerror( socket_last_error( $sock ) ) ); - } - $this->sock = $sock; - $this->startTime = time(); - } - - /** - * Entry-point function that listens to the server socket, accepts - * new clients, and recieves/responds to requests to lock resources. - */ - public function main() { - $this->setupServerSocket(); // setup listening socket - $socketArray = new SocketArray(); // sockets being serviced - $socketArray->addSocket( $this->sock ); // add listening socket - do { - list( $read, $write ) = $socketArray->socketsForSelect(); - if ( socket_select( $read, $write, $except = NULL, NULL ) < 1 ) { - continue; // wait - } - // Check if there is a client trying to connect... - if ( in_array( $this->sock, $read ) && $socketArray->size() < $this->maxClients ) { - $newSock = socket_accept( $this->sock ); - if ( $newSock ) { - socket_set_option( $newSock, SOL_SOCKET, SO_KEEPALIVE, 1 ); - socket_set_nonblock( $newSock ); // don't block on read()/write() - $socketArray->addSocket( $newSock ); - } - } - // Loop through all the clients that have data to read... - foreach ( $read as $read_sock ) { - if ( $read_sock === $this->sock ) { - continue; // skip listening socket - } - // Avoids PHP_NORMAL_READ per https://bugs.php.net/bug.php?id=33471 - $data = socket_read( $read_sock, 65535 ); - // Check if the client is disconnected - if ( $data === false || $data === '' ) { - $socketArray->closeSocket( $read_sock ); - $this->recordDeadSocket( $read_sock ); // remove session - // Check if we reached the end of a message - } elseif ( substr( $data, -1 ) === "\n" ) { - // Newline is the last char (given ping-pong message usage) - $cmd = $socketArray->readRcvBuffer( $read_sock ) . $data; - // Perform the requested command... - $response = $this->doCommand( rtrim( $cmd ), $read_sock ); - // Send the response to the client... - $socketArray->appendSndBuffer( $read_sock, $response . "\n" ); - // Otherwise, we just have more message data to append - } elseif ( !$socketArray->appendRcvBuffer( $read_sock, $data ) ) { - $socketArray->closeSocket( $read_sock ); // too big - $this->recordDeadSocket( $read_sock ); // remove session - } - } - // Loop through all the clients that have data to write... - foreach ( $write as $write_sock ) { - $bytes = socket_write( $write_sock, $socketArray->readSndBuffer( $write_sock ) ); - // Check if the client is disconnected - if ( $bytes === false ) { - $socketArray->closeSocket( $write_sock ); - $this->recordDeadSocket( $write_sock ); // remove session - // Otherwise, truncate these bytes from the start of the write buffer - } else { - $socketArray->consumeSndBuffer( $write_sock, $bytes ); - } - } - // Prune dead locks every few socket events... - if ( ++$this->ticks >= 9 ) { - $this->ticks = 0; - $this->purgeExpiredLocks(); - } - } while ( true ); - } - - /** - * @param $data string - * @param $sourceSock resource - * @return string - */ - protected function doCommand( $data, $sourceSock ) { - $cmdArr = $this->getCommand( $data ); - if ( is_string( $cmdArr ) ) { - return $cmdArr; // error - } - list( $function, $session, $type, $resources ) = $cmdArr; - // On first command, track the session => sock correspondence - if ( !isset( $this->sessions[$session] ) ) { - $this->sessions[$session] = $sourceSock; - unset( $this->deadSessions[$session] ); // renew if dead - } - if ( $function === 'ACQUIRE' ) { - return $this->lockHolder->lock( $session, $type, $resources ); - } elseif ( $function === 'RELEASE' ) { - return $this->lockHolder->unlock( $session, $type, $resources ); - } elseif ( $function === 'RELEASE_ALL' ) { - return $this->lockHolder->release( $session ); - } elseif ( $function === 'STAT' ) { - return $this->stat(); - } - return 'INTERNAL_ERROR'; - } - - /** - * @param $data string - * @return Array - */ - protected function getCommand( $data ) { - $m = explode( ':', $data ); // <session, key, command, type, values> - if ( count( $m ) == 5 ) { - list( $session, $key, $command, $type, $values ) = $m; - $goodKey = hash_hmac( 'sha1', - "{$session}\n{$command}\n{$type}\n{$values}", $this->authKey ); - if ( $goodKey !== $key ) { - return 'BAD_KEY'; - } elseif ( strlen( $session ) !== 32 ) { - return 'BAD_SESSION'; - } - $values = explode( '|', $values ); - if ( $command === 'ACQUIRE' ) { - $needsLockArgs = true; - } elseif ( $command === 'RELEASE' ) { - $needsLockArgs = true; - } elseif ( $command === 'RELEASE_ALL' ) { - $needsLockArgs = false; - } elseif ( $command === 'STAT' ) { - $needsLockArgs = false; - } else { - return 'BAD_COMMAND'; - } - if ( $needsLockArgs ) { - if ( $type !== 'SH' && $type !== 'EX' ) { - return 'BAD_TYPE'; - } - foreach ( $values as $value ) { - if ( strlen( $value ) !== 31 ) { - return 'BAD_FORMAT'; - } - } - } - return array( $command, $session, $type, $values ); - } - return 'BAD_FORMAT'; - } - - /** - * Remove a socket's corresponding session from tracking and - * store it in the dead session tracking if it still has locks. - * - * @param $socket resource - * @return bool - */ - protected function recordDeadSocket( $socket ) { - $session = array_search( $socket, $this->sessions ); - if ( $session !== false ) { - unset( $this->sessions[$session] ); - // Record recently killed sessions that still have locks - if ( $this->lockHolder->sessionHasLocks( $session ) ) { - $this->deadSessions[$session] = time(); - } - return true; - } - return false; - } - - /** - * Clear locks for sessions that have been dead for a while - * - * @return integer Number of sessions purged - */ - protected function purgeExpiredLocks() { - $count = 0; - $now = time(); - foreach ( $this->deadSessions as $session => $timestamp ) { - if ( ( $now - $timestamp ) > $this->lockTimeout ) { - $this->lockHolder->release( $session ); - unset( $this->deadSessions[$session] ); - ++$count; - } - } - return $count; - } - - /** - * Get the current timestamp and memory usage - * - * @return string - */ - protected function stat() { - return ( time() - $this->startTime ) . ':' . memory_get_usage(); - } -} - -/** - * LockServerDaemon helper class that keeps track socket states - */ -class SocketArray { - /* @var Array */ - protected $clients = array(); // array of client sockets - /* @var Array */ - protected $rBuffers = array(); // corresponding socket read buffers - /* @var Array */ - protected $wBuffers = array(); // corresponding socket write buffers - - const BUFFER_SIZE = 65535; - - /** - * @return Array (list of sockets to read, list of sockets to write) - */ - public function socketsForSelect() { - $rSockets = array(); - $wSockets = array(); - foreach ( $this->clients as $key => $socket ) { - if ( $this->wBuffers[$key] !== '' ) { - $wSockets[] = $socket; // wait for writing to unblock - } else { - $rSockets[] = $socket; // wait for reading to unblock - } - } - return array( $rSockets, $wSockets ); - } - - /** - * @return integer Number of client sockets - */ - public function size() { - return count( $this->clients ); - } - - /** - * @param $sock resource - * @return bool - */ - public function addSocket( $sock ) { - $this->clients[] = $sock; - $this->rBuffers[] = ''; - $this->wBuffers[] = ''; - return true; - } - - /** - * @param $sock resource - * @return bool - */ - public function closeSocket( $sock ) { - $key = array_search( $sock, $this->clients ); - if ( $key === false ) { - return false; - } - socket_close( $sock ); - unset( $this->clients[$key] ); - unset( $this->rBuffers[$key] ); - unset( $this->wBuffers[$key] ); - return true; - } - - /** - * @param $sock resource - * @param $data string - * @return bool - */ - public function appendRcvBuffer( $sock, $data ) { - $key = array_search( $sock, $this->clients ); - if ( $key === false ) { - return false; - } elseif ( ( strlen( $this->rBuffers[$key] ) + strlen( $data ) ) > self::BUFFER_SIZE ) { - return false; - } - $this->rBuffers[$key] .= $data; - return true; - } - - /** - * @param $sock resource - * @return string|bool - */ - public function readRcvBuffer( $sock ) { - $key = array_search( $sock, $this->clients ); - if ( $key === false ) { - return false; - } - $data = $this->rBuffers[$key]; - $this->rBuffers[$key] = ''; // consume data - return $data; - } - - /** - * @param $sock resource - * @param $data string - * @return bool - */ - public function appendSndBuffer( $sock, $data ) { - $key = array_search( $sock, $this->clients ); - if ( $key === false ) { - return false; - } elseif ( ( strlen( $this->wBuffers[$key] ) + strlen( $data ) ) > self::BUFFER_SIZE ) { - return false; - } - $this->wBuffers[$key] .= $data; - return true; - } - - /** - * @param $sock resource - * @return bool - */ - public function readSndBuffer( $sock ) { - $key = array_search( $sock, $this->clients ); - if ( $key === false ) { - return false; - } - return $this->wBuffers[$key]; - } - - /** - * @param $sock resource - * @param $bytes integer - * @return bool - */ - public function consumeSndBuffer( $sock, $bytes ) { - $key = array_search( $sock, $this->clients ); - if ( $key === false ) { - return false; - } - $this->wBuffers[$key] = (string)substr( $this->wBuffers[$key], $bytes ); - return true; - } -} - -/** - * LockServerDaemon helper class that keeps track of the locks - */ -class LockHolder { - /** @var Array */ - protected $shLocks = array(); // (key => session => 1) - /** @var Array */ - protected $exLocks = array(); // (key => session) - - /** @var Array */ - protected $sessionIndexSh = array(); // (session => key => 1) - /** @var Array */ - protected $sessionIndexEx = array(); // (session => key => 1) - protected $lockCount = 0; // integer - - protected $maxLocks; // integer - - /** - * @params $maxLocks integer Maximum number of locks to allow - */ - public function __construct( $maxLocks ) { - $this->maxLocks = $maxLocks; - } - - /** - * @param $session string - * @return bool - */ - public function sessionHasLocks( $session ) { - return isset( $this->sessionIndexSh[$session] ) - || isset( $this->sessionIndexEx[$session] ); - } - - /** - * @param $session string - * @param $type string - * @param $keys Array - * @return string - */ - public function lock( $session, $type, array $keys ) { - if ( ( $this->lockCount + count( $keys ) ) > $this->maxLocks ) { - return 'TOO_MANY_LOCKS'; - } - if ( $type === 'SH' ) { - // Check if any keys are already write-locked... - foreach ( $keys as $key ) { - if ( isset( $this->exLocks[$key] ) && $this->exLocks[$key] !== $session ) { - return 'CANT_ACQUIRE'; - } - } - // Acquire the read-locks... - foreach ( $keys as $key ) { - $this->set_sh_lock( $key, $session ); - } - return 'ACQUIRED'; - } elseif ( $type === 'EX' ) { - // Check if any keys are already read-locked or write-locked... - foreach ( $keys as $key ) { - if ( isset( $this->exLocks[$key] ) && $this->exLocks[$key] !== $session ) { - return 'CANT_ACQUIRE'; - } - if ( isset( $this->shLocks[$key] ) ) { - foreach ( $this->shLocks[$key] as $otherSession => $x ) { - if ( $otherSession !== $session ) { - return 'CANT_ACQUIRE'; - } - } - } - } - // Acquire the write-locks... - foreach ( $keys as $key ) { - $this->set_ex_lock( $key, $session ); - } - return 'ACQUIRED'; - } - return 'INTERNAL_ERROR'; - } - - /** - * @param $session string - * @param $type string - * @param $keys Array - * @return string - */ - public function unlock( $session, $type, array $keys ) { - if ( $type === 'SH' ) { - foreach ( $keys as $key ) { - $this->unset_sh_lock( $key, $session ); - } - return 'RELEASED'; - } elseif ( $type === 'EX' ) { - foreach ( $keys as $key ) { - $this->unset_ex_lock( $key, $session ); - } - return 'RELEASED'; - } - return 'INTERNAL_ERROR'; - } - - /** - * @param $session string - * @return string - */ - public function release( $session ) { - if ( isset( $this->sessionIndexSh[$session] ) ) { - foreach ( $this->sessionIndexSh[$session] as $key => $x ) { - $this->unset_sh_lock( $key, $session ); - } - } - if ( isset( $this->sessionIndexEx[$session] ) ) { - foreach ( $this->sessionIndexEx[$session] as $key => $x ) { - $this->unset_ex_lock( $key, $session ); - } - } - return 'RELEASED_ALL'; - } - - /** - * @param $key string - * @param $session string - * @return void - */ - protected function set_sh_lock( $key, $session ) { - if ( !isset( $this->shLocks[$key][$session] ) ) { - $this->shLocks[$key][$session] = 1; - $this->sessionIndexSh[$session][$key] = 1; - ++$this->lockCount; // we are adding a lock - } - } - - /** - * @param $key string - * @param $session string - * @return void - */ - protected function set_ex_lock( $key, $session ) { - if ( !isset( $this->exLocks[$key][$session] ) ) { - $this->exLocks[$key] = $session; - $this->sessionIndexEx[$session][$key] = 1; - ++$this->lockCount; // we are adding a lock - } - } - - /** - * @param $key string - * @param $session string - * @return void - */ - protected function unset_sh_lock( $key, $session ) { - if ( isset( $this->shLocks[$key][$session] ) ) { - unset( $this->shLocks[$key][$session] ); - if ( !count( $this->shLocks[$key] ) ) { - unset( $this->shLocks[$key] ); - } - unset( $this->sessionIndexSh[$session][$key] ); - if ( !count( $this->sessionIndexSh[$session] ) ) { - unset( $this->sessionIndexSh[$session] ); - } - --$this->lockCount; - } - } - - /** - * @param $key string - * @param $session string - * @return void - */ - protected function unset_ex_lock( $key, $session ) { - if ( isset( $this->exLocks[$key] ) && $this->exLocks[$key] === $session ) { - unset( $this->exLocks[$key] ); - unset( $this->sessionIndexEx[$session][$key] ); - if ( !count( $this->sessionIndexEx[$session] ) ) { - unset( $this->sessionIndexEx[$session] ); - } - --$this->lockCount; - } - } -} diff --git a/maintenance/mctest.php b/maintenance/mctest.php index eda101e7..a97d2e12 100644 --- a/maintenance/mctest.php +++ b/maintenance/mctest.php @@ -30,7 +30,7 @@ require_once __DIR__ . '/Maintenance.php'; * * @ingroup Maintenance */ -class mcTest extends Maintenance { +class McTest extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Makes several 'set', 'incr' and 'get' requests on every" @@ -67,7 +67,7 @@ class mcTest extends Maintenance { foreach ( $servers as $server ) { $this->output( str_pad( $server, $maxSrvLen ), - $server # output channel + $server # output channel ); $mcc = new MemCachedClientforWiki( array( @@ -78,7 +78,7 @@ class mcTest extends Maintenance { $set = 0; $incr = 0; $get = 0; - $time_start = $this->microtime_float(); + $time_start = microtime( true ); for ( $i = 1; $i <= $iterations; $i++ ) { if ( $mcc->set( "test$i", $i ) ) { $set++; @@ -95,21 +95,12 @@ class mcTest extends Maintenance { $get++; } } - $exectime = $this->microtime_float() - $time_start; + $exectime = microtime( true ) - $time_start; $this->output( " set: $set incr: $incr get: $get time: $exectime", $server ); } } - - /** - * Return microtime() as a float - * @return float - */ - private function microtime_float() { - list( $usec, $sec ) = explode( " ", microtime() ); - return ( (float)$usec + (float)$sec ); - } } -$maintClass = "mcTest"; +$maintClass = "McTest"; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/mergeMessageFileList.php b/maintenance/mergeMessageFileList.php index e9183377..2a6f8a8b 100644 --- a/maintenance/mergeMessageFileList.php +++ b/maintenance/mergeMessageFileList.php @@ -43,15 +43,23 @@ class MergeMessageFileList extends Maintenance { function __construct() { parent::__construct(); - $this->addOption( 'list-file', 'A file containing a list of extension setup files, one per line.', false, true ); + $this->addOption( + 'list-file', + 'A file containing a list of extension setup files, one per line.', + false, + true + ); $this->addOption( 'extensions-dir', 'Path where extensions can be found.', false, true ); $this->addOption( 'output', 'Send output to this file (omit for stdout)', false, true ); - $this->mDescription = 'Merge $wgExtensionMessagesFiles from various extensions to produce a ' . - 'single array containing all message files.'; + $this->mDescription = 'Merge $wgExtensionMessagesFiles and $wgMessagesDirs from ' . + ' various extensions to produce a single file listing all message files and dirs.'; } public function execute() { - global $mmfl, $wgExtensionEntryPointListFiles; + // @codingStandardsIgnoreStart Ignore error: Global variable "$mmfl" is lacking 'wg' prefix + global $mmfl; + // @codingStandardsIgnoreEnd + global $wgExtensionEntryPointListFiles; if ( !count( $wgExtensionEntryPointListFiles ) && !$this->hasOption( 'list-file' ) @@ -117,6 +125,7 @@ class MergeMessageFileList extends Maintenance { if ( $fileLines === false ) { $this->hasError = true; $this->error( "Unable to open list file $fileName." ); + return $files; } # Strip comments, discard empty lines, and trim leading and trailing @@ -134,6 +143,7 @@ class MergeMessageFileList extends Maintenance { } } } + return $files; } } @@ -148,7 +158,7 @@ foreach ( $mmfl['setupFiles'] as $fileName ) { fwrite( STDERR, "Loading data from $fileName\n" ); } // Include the extension to update $wgExtensionMessagesFiles - if ( !( include_once( $fileName ) ) ) { + if ( !( include_once $fileName ) ) { fwrite( STDERR, "Unable to read $fileName\n" ); exit( 1 ); } @@ -158,7 +168,8 @@ $s = "<" . "?php\n" . "## This file is generated by mergeMessageFileList.php. Do not edit it directly.\n\n" . "if ( defined( 'MW_NO_EXTENSION_MESSAGES' ) ) return;\n\n" . - '$wgExtensionMessagesFiles = ' . var_export( $wgExtensionMessagesFiles, true ) . ";\n\n"; + '$wgExtensionMessagesFiles = ' . var_export( $wgExtensionMessagesFiles, true ) . ";\n\n" . + '$wgMessagesDirs = ' . var_export( $wgMessagesDirs, true ) . ";\n\n"; $dirs = array( $IP, diff --git a/maintenance/minify.php b/maintenance/minify.php index ec936c83..efecaad0 100644 --- a/maintenance/minify.php +++ b/maintenance/minify.php @@ -49,7 +49,6 @@ class MinifyScript extends Maintenance { $this->mDescription = "Minify a file or set of files.\n\n" . "If --outfile is not specified, then the output file names will have a .min extension\n" . "added, e.g. jquery.js -> jquery.min.js."; - } public function execute() { @@ -66,6 +65,7 @@ class MinifyScript extends Maintenance { // Minify one file $this->minify( $this->getArg( 0 ), $this->getOption( 'outfile' ) ); + return; } @@ -103,6 +103,7 @@ class MinifyScript extends Maintenance { $this->error( "No file extension, cannot determine type: $fileName" ); exit( 1 ); } + return substr( $fileName, $dotPos + 1 ); } diff --git a/maintenance/moveBatch.php b/maintenance/moveBatch.php index 34e64282..713753f2 100644 --- a/maintenance/moveBatch.php +++ b/maintenance/moveBatch.php @@ -21,7 +21,7 @@ * @ingroup Maintenance * @author Tim Starling * - * USAGE: php moveBatch.php [-u <user>] [-r <reason>] [-i <interval>] [listfile] + * USAGE: php moveBatch.php [-u <user>] [-r <reason>] [-i <interval>] [-noredirects] [listfile] * * [listfile] - file with two titles per line, separated with pipe characters; * the first title is the source, the second is the destination. @@ -29,6 +29,7 @@ * <user> - username to perform moves as * <reason> - reason to be given for moves * <interval> - number of seconds to sleep after each move + * <noredirects> - suppress creation of redirects * * This will print out error codes from Title::moveTo() if something goes wrong, * e.g. immobile_namespace for namespaces which can't be moved @@ -48,6 +49,7 @@ class MoveBatch extends Maintenance { $this->addOption( 'u', "User to perform move", false, true ); $this->addOption( 'r', "Reason to move page", false, true ); $this->addOption( 'i', "Interval to sleep between moves" ); + $this->addOption( 'noredirects', "Suppress creation of redirects" ); $this->addArg( 'listfile', 'List of pages to move, newline delimited', false ); } @@ -62,6 +64,7 @@ class MoveBatch extends Maintenance { $user = $this->getOption( 'u', 'Move page script' ); $reason = $this->getOption( 'r', '' ); $interval = $this->getOption( 'i', 0 ); + $noredirects = $this->getOption( 'noredirects', false ); if ( $this->hasArg() ) { $file = fopen( $this->getArg(), 'r' ); } else { @@ -79,7 +82,9 @@ class MoveBatch extends Maintenance { # Setup complete, now start $dbw = wfGetDB( DB_MASTER ); + // @codingStandardsIgnoreStart Ignore avoid function calls in a FOR loop test part warning for ( $linenum = 1; !feof( $file ); $linenum++ ) { + // @codingStandardsIgnoreEnd $line = fgets( $file ); if ( $line === false ) { break; @@ -96,10 +101,9 @@ class MoveBatch extends Maintenance { continue; } - $this->output( $source->getPrefixedText() . ' --> ' . $dest->getPrefixedText() ); $dbw->begin( __METHOD__ ); - $err = $source->moveTo( $dest, false, $reason ); + $err = $source->moveTo( $dest, false, $reason, !$noredirects ); if ( $err !== true ) { $msg = array_shift( $err[0] ); $this->output( "\nFAILED: " . wfMessage( $msg, $err[0] )->text() ); diff --git a/maintenance/mssql/archives/named_constraints.sql b/maintenance/mssql/archives/named_constraints.sql new file mode 100644 index 00000000..94b77ea7 --- /dev/null +++ b/maintenance/mssql/archives/named_constraints.sql @@ -0,0 +1,38 @@ +DECLARE @fullyQualifiedTableName nvarchar(max), +@tableName sysname, +@fieldName sysname, +@constr sysname, +@constrNew sysname, +@sqlcmd nvarchar(max), +@sqlcreate nvarchar(max) + +SET @fullyQualifiedTableName = '/*_*//*$tableName*/' +SET @tableName = '/*$tableName*/' +SET @fieldName = '/*$fieldName*/' + +SELECT @constr = CONSTRAINT_NAME +FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS +WHERE TABLE_NAME = @tableName +AND CONSTRAINT_CATALOG = '/*$wgDBname*/' +AND CONSTRAINT_SCHEMA = '/*$wgDBmwschema*/' +AND CONSTRAINT_TYPE = 'CHECK' +AND CONSTRAINT_NAME LIKE ('CK__' + left(@tableName,9) + '__' + left(@fieldName,5) + '%') + +SELECT @constrNew = CONSTRAINT_NAME +FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS +WHERE TABLE_NAME = @tableName +AND CONSTRAINT_CATALOG = '/*$wgDBname*/' +AND CONSTRAINT_SCHEMA = '/*$wgDBmwschema*/' +AND CONSTRAINT_TYPE = 'CHECK' +AND CONSTRAINT_NAME = (@fieldName + '_ckc') + +IF @constr IS NOT NULL +BEGIN + SET @sqlcmd = 'ALTER TABLE ' + @fullyQualifiedTableName + ' DROP CONSTRAINT [' + @constr + ']' + EXECUTE sp_executesql @sqlcmd +END +IF @constrNew IS NULL +BEGIN + SET @sqlcreate = 'ALTER TABLE ' + @fullyQualifiedTableName + ' WITH NOCHECK ADD CONSTRAINT ' + @fieldName + '_ckc CHECK /*$checkConstraint*/;' + EXECUTE sp_executesql @sqlcreate +END
\ No newline at end of file diff --git a/maintenance/mssql/archives/patch-fa_major_mime-chemical.sql b/maintenance/mssql/archives/patch-fa_major_mime-chemical.sql new file mode 100644 index 00000000..18368087 --- /dev/null +++ b/maintenance/mssql/archives/patch-fa_major_mime-chemical.sql @@ -0,0 +1,4 @@ +ALTER TABLE /*_*/filearchive +DROP CONSTRAINT fa_major_mime_ckc; +ALTER TABLE /*_*/filearchive +WITH NOCHECK ADD CONSTRAINT fa_major_mime_ckc CHECK (fa_major_mime IN('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart', 'chemical'));
\ No newline at end of file diff --git a/maintenance/mssql/archives/patch-img_major_mime-chemical.sql b/maintenance/mssql/archives/patch-img_major_mime-chemical.sql new file mode 100644 index 00000000..eed07869 --- /dev/null +++ b/maintenance/mssql/archives/patch-img_major_mime-chemical.sql @@ -0,0 +1,4 @@ +ALTER TABLE /*_*/image +DROP CONSTRAINT img_major_mime_ckc; +ALTER TABLE /*_*/image +WITH NOCHECK ADD CONSTRAINT img_major_mime_ckc CHECK (img_major_mime IN('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart', 'chemical'));
\ No newline at end of file diff --git a/maintenance/mssql/archives/patch-oi_major_mime-chemical.sql b/maintenance/mssql/archives/patch-oi_major_mime-chemical.sql new file mode 100644 index 00000000..35482edc --- /dev/null +++ b/maintenance/mssql/archives/patch-oi_major_mime-chemical.sql @@ -0,0 +1,4 @@ +ALTER TABLE /*_*/oldimage +DROP CONSTRAINT oi_major_mime_ckc; +ALTER TABLE /*_*/oldimage +WITH NOCHECK ADD CONSTRAINT oi_major_mime_ckc CHECK (oi_major_mime IN('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart', 'chemical'));
\ No newline at end of file diff --git a/maintenance/mssql/archives/patch-page_page_lang.sql b/maintenance/mssql/archives/patch-page_page_lang.sql new file mode 100644 index 00000000..d2f537b0 --- /dev/null +++ b/maintenance/mssql/archives/patch-page_page_lang.sql @@ -0,0 +1 @@ +ALTER TABLE /*_*/page ADD page_lang VARBINARY(35) DEFAULT NULL diff --git a/maintenance/mssql/archives/patch-user_password_expires.sql b/maintenance/mssql/archives/patch-user_password_expires.sql new file mode 100644 index 00000000..c22b10c7 --- /dev/null +++ b/maintenance/mssql/archives/patch-user_password_expires.sql @@ -0,0 +1 @@ +ALTER TABLE /*_*/mwuser ADD user_password_expires VARCHAR(14) DEFAULT NULL
\ No newline at end of file diff --git a/maintenance/mssql/tables.sql b/maintenance/mssql/tables.sql index 7356c38f..b9cd7159 100644 --- a/maintenance/mssql/tables.sql +++ b/maintenance/mssql/tables.sql @@ -30,26 +30,30 @@ -- tables. -- LINE:53 -CREATE TABLE /*$wgDBprefix*/user ( +CREATE TABLE /*_*/mwuser ( user_id INT NOT NULL PRIMARY KEY IDENTITY(0,1), user_name NVARCHAR(255) NOT NULL UNIQUE DEFAULT '', user_real_name NVARCHAR(255) NOT NULL DEFAULT '', user_password NVARCHAR(255) NOT NULL DEFAULT '', user_newpassword NVARCHAR(255) NOT NULL DEFAULT '', - user_newpass_time DATETIME NULL, + user_newpass_time varchar(14) NULL DEFAULT NULL, user_email NVARCHAR(255) NOT NULL DEFAULT '', user_options NVARCHAR(MAX) NOT NULL DEFAULT '', - user_touched DATETIME NOT NULL DEFAULT GETDATE(), + user_touched varchar(14) NOT NULL DEFAULT '', user_token NCHAR(32) NOT NULL DEFAULT '', - user_email_authenticated DATETIME DEFAULT NULL, + user_email_authenticated varchar(14) DEFAULT NULL, user_email_token NCHAR(32) DEFAULT '', - user_email_token_expires DATETIME DEFAULT NULL, - user_registration DATETIME DEFAULT NULL, - user_editcount INT NULL + user_email_token_expires varchar(14) DEFAULT NULL, + user_registration varchar(14) DEFAULT NULL, + user_editcount INT NULL DEFAULT NULL, + user_password_expires varchar(14) DEFAULT NULL ); -CREATE INDEX /*$wgDBprefix*/user_email_token ON /*$wgDBprefix*/[user](user_email_token); -CREATE UNIQUE INDEX /*$wgDBprefix*/[user_name] ON /*$wgDBprefix*/[user]([user_name]); -; +CREATE UNIQUE INDEX /*i*/user_name ON /*_*/mwuser (user_name); +CREATE INDEX /*i*/user_email_token ON /*_*/mwuser (user_email_token); +CREATE INDEX /*i*/user_email ON /*_*/mwuser (user_email); + +-- Insert a dummy user to represent anons +INSERT INTO /*_*/mwuser (user_name) VALUES ('##Anonymous##'); -- -- User permissions have been broken out to a separate table; @@ -57,86 +61,107 @@ CREATE UNIQUE INDEX /*$wgDBprefix*/[user_name] ON /*$wgDBprefix*/[user]([ -- permissions assigned to a user in each project. -- -- This table replaces the old user_rights field which used a --- comma-separated blob. -CREATE TABLE /*$wgDBprefix*/user_groups ( - ug_user INT NOT NULL REFERENCES /*$wgDBprefix*/[user](user_id) ON DELETE CASCADE, - ug_group NVARCHAR(16) NOT NULL DEFAULT '', +-- comma-separated nvarchar(max). +CREATE TABLE /*_*/user_groups ( + ug_user INT NOT NULL REFERENCES /*_*/mwuser(user_id) ON DELETE CASCADE, + ug_group NVARCHAR(255) NOT NULL DEFAULT '', +); +CREATE UNIQUE clustered INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user, ug_group); +CREATE INDEX /*i*/ug_group ON /*_*/user_groups(ug_group); + +-- Stores the groups the user has once belonged to. +-- The user may still belong to these groups (check user_groups). +-- Users are not autopromoted to groups from which they were removed. +CREATE TABLE /*_*/user_former_groups ( + ufg_user INT NOT NULL REFERENCES /*_*/mwuser(user_id) ON DELETE CASCADE, + ufg_group nvarchar(255) NOT NULL default '' ); -CREATE UNIQUE clustered INDEX /*$wgDBprefix*/user_groups_unique ON /*$wgDBprefix*/user_groups(ug_user, ug_group); -CREATE INDEX /*$wgDBprefix*/user_group ON /*$wgDBprefix*/user_groups(ug_group); +CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group); -- Stores notifications of user talk page changes, for the display -- of the "you have new messages" box --- Changed user_id column to mwuser_id to avoid clashing with user_id function -CREATE TABLE /*$wgDBprefix*/user_newtalk ( - user_id INT NOT NULL DEFAULT 0 REFERENCES /*$wgDBprefix*/[user](user_id) ON DELETE CASCADE, +-- Changed user_id column to user_id to avoid clashing with user_id function +CREATE TABLE /*_*/user_newtalk ( + user_id INT NOT NULL REFERENCES /*_*/mwuser(user_id) ON DELETE CASCADE, user_ip NVARCHAR(40) NOT NULL DEFAULT '', - user_last_timestamp DATETIME NOT NULL DEFAULT '', + user_last_timestamp varchar(14) DEFAULT NULL, ); -CREATE INDEX /*$wgDBprefix*/user_group_id ON /*$wgDBprefix*/user_newtalk([user_id]); -CREATE INDEX /*$wgDBprefix*/user_ip ON /*$wgDBprefix*/user_newtalk(user_ip); +CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id); +CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip); -- -- User preferences and other fun stuff --- replaces old user.user_options BLOB +-- replaces old user.user_options nvarchar(max) -- -CREATE TABLE /*$wgDBprefix*/user_properties ( - up_user INT NOT NULL, - up_property NVARCHAR(32) NOT NULL, +CREATE TABLE /*_*/user_properties ( + up_user INT NOT NULL REFERENCES /*_*/mwuser(user_id) ON DELETE CASCADE, + up_property NVARCHAR(255) NOT NULL, up_value NVARCHAR(MAX), ); -CREATE UNIQUE clustered INDEX /*$wgDBprefix*/user_props_user_prop ON /*$wgDBprefix*/user_properties(up_user, up_property); -CREATE INDEX /*$wgDBprefix*/user_props_prop ON /*$wgDBprefix*/user_properties(up_property); +CREATE UNIQUE CLUSTERED INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property); +CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property); -- -- Core of the wiki: each page has an entry here which identifies -- it by title and contains some essential metadata. -- -CREATE TABLE /*$wgDBprefix*/page ( - page_id INT NOT NULL PRIMARY KEY clustered IDENTITY, +CREATE TABLE /*_*/page ( + page_id INT NOT NULL PRIMARY KEY IDENTITY(0,1), page_namespace INT NOT NULL, page_title NVARCHAR(255) NOT NULL, - page_restrictions NVARCHAR(255) NULL, + page_restrictions NVARCHAR(255) NOT NULL, page_counter BIGINT NOT NULL DEFAULT 0, page_is_redirect BIT NOT NULL DEFAULT 0, page_is_new BIT NOT NULL DEFAULT 0, - page_random NUMERIC(15,14) NOT NULL DEFAULT RAND(), - page_touched DATETIME NOT NULL DEFAULT GETDATE(), - page_latest INT NOT NULL, + page_random real NOT NULL DEFAULT RAND(), + page_touched varchar(14) NOT NULL default '', + page_links_updated varchar(14) DEFAULT NULL, + page_latest INT, -- FK inserted later page_len INT NOT NULL, + page_content_model nvarchar(32) default null, + page_lang VARBINARY(35) DEFAULT NULL ); -CREATE UNIQUE INDEX /*$wgDBprefix*/page_unique_name ON /*$wgDBprefix*/page(page_namespace, page_title); -CREATE INDEX /*$wgDBprefix*/page_random_idx ON /*$wgDBprefix*/page(page_random); -CREATE INDEX /*$wgDBprefix*/page_len_idx ON /*$wgDBprefix*/page(page_len); -; +CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title); +CREATE INDEX /*i*/page_random ON /*_*/page (page_random); +CREATE INDEX /*i*/page_len ON /*_*/page (page_len); +CREATE INDEX /*i*/page_redirect_namespace_len ON /*_*/page (page_is_redirect, page_namespace, page_len); + +-- insert a dummy page +INSERT INTO /*_*/page (page_namespace, page_title, page_restrictions, page_latest, page_len) VALUES (-1,'','',0,0); -- -- Every edit of a page creates also a revision row. -- This stores metadata about the revision, and a reference -- to the TEXT storage backend. -- -CREATE TABLE /*$wgDBprefix*/revision ( - rev_id INT NOT NULL UNIQUE IDENTITY, - rev_page INT NOT NULL, - rev_text_id INT NOT NULL, - rev_comment NVARCHAR(max) NOT NULL, - rev_user INT NOT NULL DEFAULT 0 /*REFERENCES [user](user_id)*/, +CREATE TABLE /*_*/revision ( + rev_id INT NOT NULL UNIQUE IDENTITY(0,1), + rev_page INT NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + rev_text_id INT NOT NULL, -- FK added later + rev_comment NVARCHAR(255) NOT NULL, + rev_user INT REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL, rev_user_text NVARCHAR(255) NOT NULL DEFAULT '', - rev_timestamp DATETIME NOT NULL DEFAULT GETDATE(), + rev_timestamp varchar(14) NOT NULL default '', rev_minor_edit BIT NOT NULL DEFAULT 0, - rev_deleted BIT NOT NULL DEFAULT 0, + rev_deleted TINYINT NOT NULL DEFAULT 0, rev_len INT, - rev_parent_id INT DEFAULT NULL, - + rev_parent_id INT DEFAULT NULL REFERENCES /*_*/revision(rev_id), + rev_sha1 nvarchar(32) not null default '', + rev_content_model nvarchar(32) default null, + rev_content_format nvarchar(64) default null ); -CREATE UNIQUE clustered INDEX /*$wgDBprefix*/revision_unique ON /*$wgDBprefix*/revision(rev_page, rev_id); -CREATE UNIQUE INDEX /*$wgDBprefix*/rev_id ON /*$wgDBprefix*/revision(rev_id); -CREATE INDEX /*$wgDBprefix*/rev_timestamp ON /*$wgDBprefix*/revision(rev_timestamp); -CREATE INDEX /*$wgDBprefix*/page_timestamp ON /*$wgDBprefix*/revision(rev_page, rev_timestamp); -CREATE INDEX /*$wgDBprefix*/user_timestamp ON /*$wgDBprefix*/revision(rev_user, rev_timestamp); -CREATE INDEX /*$wgDBprefix*/usertext_timestamp ON /*$wgDBprefix*/revision(rev_user_text, rev_timestamp); -; +CREATE UNIQUE CLUSTERED INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id); +CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp); +CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp); +CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp); +CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp); +CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp); + +-- insert a dummy revision +INSERT INTO /*_*/revision (rev_page,rev_text_id,rev_comment,rev_user,rev_len) VALUES (0,0,'',0,0); + +ALTER TABLE /*_*/page ADD CONSTRAINT FK_page_latest_page_id FOREIGN KEY (page_latest) REFERENCES /*_*/revision(rev_id); -- -- Holds TEXT of individual page revisions. @@ -145,12 +170,17 @@ CREATE INDEX /*$wgDBprefix*/usertext_timestamp ON /*$wgDBprefix*/revision -- MediaWiki 1.4 and earlier: an upgrade will transform that -- table INTo the 'text' table to minimize unnecessary churning -- and downtime. If upgrading, the other fields will be left unused. -CREATE TABLE /*$wgDBprefix*/text ( - old_id INT NOT NULL PRIMARY KEY clustered IDENTITY, - old_text TEXT NOT NULL, +CREATE TABLE /*_*/text ( + old_id INT NOT NULL PRIMARY KEY IDENTITY(0,1), + old_text nvarchar(max) NOT NULL, old_flags NVARCHAR(255) NOT NULL, ); +-- insert a dummy text +INSERT INTO /*_*/text (old_text,old_flags) VALUES ('',''); + +ALTER TABLE /*_*/revision ADD CONSTRAINT FK_rev_text_id_old_id FOREIGN KEY (rev_text_id) REFERENCES /*_*/text(old_id) ON DELETE CASCADE; + -- -- Holding area for deleted articles, which may be viewed -- or restored by admins through the Special:Undelete interface. @@ -158,575 +188,1153 @@ CREATE TABLE /*$wgDBprefix*/text ( -- fields, with several caveats. -- Cannot reasonably create views on this table, due to the presence of TEXT -- columns. -CREATE TABLE /*$wgDBprefix*/archive ( - ar_id NOT NULL PRIMARY KEY clustered IDENTITY, +CREATE TABLE /*_*/archive ( + ar_id int NOT NULL PRIMARY KEY IDENTITY, ar_namespace SMALLINT NOT NULL DEFAULT 0, ar_title NVARCHAR(255) NOT NULL DEFAULT '', ar_text NVARCHAR(MAX) NOT NULL, ar_comment NVARCHAR(255) NOT NULL, - ar_user INT NULL REFERENCES /*$wgDBprefix*/[user](user_id) ON DELETE SET NULL, + ar_user INT REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL, ar_user_text NVARCHAR(255) NOT NULL, - ar_timestamp DATETIME NOT NULL DEFAULT GETDATE(), + ar_timestamp varchar(14) NOT NULL default '', ar_minor_edit BIT NOT NULL DEFAULT 0, ar_flags NVARCHAR(255) NOT NULL, - ar_rev_id INT, - ar_text_id INT, - ar_deleted BIT NOT NULL DEFAULT 0, - ar_len INT DEFAULT NULL, - ar_page_id INT NULL, - ar_parent_id INT NULL, + ar_rev_id INT NULL, -- NOT a FK, the row gets deleted from revision and moved here + ar_text_id INT REFERENCES /*_*/text(old_id) ON DELETE CASCADE, + ar_deleted TINYINT NOT NULL DEFAULT 0, + ar_len INT, + ar_page_id INT NULL, -- NOT a FK, the row gets deleted from page and moved here + ar_parent_id INT NULL REFERENCES /*_*/revision(rev_id), + ar_sha1 nvarchar(32) default null, + ar_content_model nvarchar(32) DEFAULT NULL, + ar_content_format nvarchar(64) DEFAULT NULL ); -CREATE INDEX /*$wgDBprefix*/ar_name_title_timestamp ON /*$wgDBprefix*/archive(ar_namespace,ar_title,ar_timestamp); -CREATE INDEX /*$wgDBprefix*/ar_usertext_timestamp ON /*$wgDBprefix*/archive(ar_user_text,ar_timestamp); -CREATE INDEX /*$wgDBprefix*/ar_user_text ON /*$wgDBprefix*/archive(ar_user_text); +CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp); +CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp); +CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id); -- -- Track page-to-page hyperlinks within the wiki. -- -CREATE TABLE /*$wgDBprefix*/pagelinks ( - pl_from INT NOT NULL DEFAULT 0 REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, - pl_namespace SMALLINT NOT NULL DEFAULT 0, +CREATE TABLE /*_*/pagelinks ( + pl_from INT NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + pl_namespace INT NOT NULL DEFAULT 0, pl_title NVARCHAR(255) NOT NULL DEFAULT '', ); -CREATE UNIQUE INDEX /*$wgDBprefix*/pl_from ON /*$wgDBprefix*/pagelinks(pl_from,pl_namespace,pl_title); -CREATE UNIQUE INDEX /*$wgDBprefix*/pl_namespace ON /*$wgDBprefix*/pagelinks(pl_namespace,pl_title,pl_from); +CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title); +CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from); + -- -- Track template inclusions. -- -CREATE TABLE /*$wgDBprefix*/templatelinks ( - tl_from INT NOT NULL DEFAULT 0 REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, - tl_namespace SMALLINT NOT NULL DEFAULT 0, - tl_title NVARCHAR(255) NOT NULL DEFAULT '', +CREATE TABLE /*_*/templatelinks ( + tl_from int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + tl_namespace int NOT NULL default 0, + tl_title nvarchar(255) NOT NULL default '' ); -CREATE UNIQUE INDEX /*$wgDBprefix*/tl_from ON /*$wgDBprefix*/templatelinks(tl_from,tl_namespace,tl_title); -CREATE UNIQUE INDEX /*$wgDBprefix*/tl_namespace ON /*$wgDBprefix*/templatelinks(tl_namespace,tl_title,tl_from); + +CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title); +CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from); + -- -- Track links to images *used inline* -- We don't distinguish live from broken links here, so --- they do not need to be changed ON upload/removal. +-- they do not need to be changed on upload/removal. -- -CREATE TABLE /*$wgDBprefix*/imagelinks ( - il_from INT NOT NULL DEFAULT 0 REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, - il_to NVARCHAR(255) NOT NULL DEFAULT '', - CONSTRAINT /*$wgDBprefix*/il_from PRIMARY KEY(il_from,il_to), +CREATE TABLE /*_*/imagelinks ( + -- Key to page_id of the page containing the image / media link. + il_from int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + + -- Filename of target image. + -- This is also the page_title of the file's description page; + -- all such pages are in namespace 6 (NS_FILE). + il_to nvarchar(255) NOT NULL default '' ); -CREATE UNIQUE INDEX /*$wgDBprefix*/il_from_to ON /*$wgDBprefix*/imagelinks(il_from,il_to); -CREATE UNIQUE INDEX /*$wgDBprefix*/il_to_from ON /*$wgDBprefix*/imagelinks(il_to,il_from); + +CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to); +CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from); -- -- Track category inclusions *used inline* -- This tracks a single level of category membership --- (folksonomic tagging, really). -- -CREATE TABLE /*$wgDBprefix*/categorylinks ( - cl_from INT NOT NULL DEFAULT 0, - cl_to NVARCHAR(255) NOT NULL DEFAULT '', - cl_sortkey NVARCHAR(150) NOT NULL DEFAULT '', - cl_timestamp DATETIME NOT NULL DEFAULT GETDATE(), - CONSTRAINT /*$wgDBprefix*/cl_from PRIMARY KEY(cl_from, cl_to), +CREATE TABLE /*_*/categorylinks ( + -- Key to page_id of the page defined as a category member. + cl_from int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + + -- Name of the category. + -- This is also the page_title of the category's description page; + -- all such pages are in namespace 14 (NS_CATEGORY). + cl_to nvarchar(255) NOT NULL default '', + + -- A binary string obtained by applying a sortkey generation algorithm + -- (Collation::getSortKey()) to page_title, or cl_sortkey_prefix . "\n" + -- . page_title if cl_sortkey_prefix is nonempty. + cl_sortkey varbinary(230) NOT NULL default 0x, + + -- A prefix for the raw sortkey manually specified by the user, either via + -- [[Category:Foo|prefix]] or {{defaultsort:prefix}}. If nonempty, it's + -- concatenated with a line break followed by the page title before the sortkey + -- conversion algorithm is run. We store this so that we can update + -- collations without reparsing all pages. + -- Note: If you change the length of this field, you also need to change + -- code in LinksUpdate.php. See bug 25254. + cl_sortkey_prefix varbinary(255) NOT NULL default 0x, + + -- This isn't really used at present. Provided for an optional + -- sorting method by approximate addition time. + cl_timestamp varchar(14) NOT NULL, + + -- Stores $wgCategoryCollation at the time cl_sortkey was generated. This + -- can be used to install new collation versions, tracking which rows are not + -- yet updated. '' means no collation, this is a legacy row that needs to be + -- updated by updateCollation.php. In the future, it might be possible to + -- specify different collations per category. + cl_collation nvarchar(32) NOT NULL default '', + + -- Stores whether cl_from is a category, file, or other page, so we can + -- paginate the three categories separately. This never has to be updated + -- after the page is created, since none of these page types can be moved to + -- any other. + cl_type varchar(10) NOT NULL default 'page', + -- SQL server doesn't have enums, so we approximate with this + CONSTRAINT cl_type_ckc CHECK (cl_type IN('page', 'subcat', 'file')) ); -CREATE UNIQUE INDEX /*$wgDBprefix*/cl_from_to ON /*$wgDBprefix*/categorylinks(cl_from,cl_to); --- We always sort within a given category... -CREATE INDEX /*$wgDBprefix*/cl_sortkey ON /*$wgDBprefix*/categorylinks(cl_to,cl_sortkey); --- Not really used? -CREATE INDEX /*$wgDBprefix*/cl_timestamp ON /*$wgDBprefix*/categorylinks(cl_to,cl_timestamp); ---; + +CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to); + +-- We always sort within a given category, and within a given type. FIXME: +-- Formerly this index didn't cover cl_type (since that didn't exist), so old +-- callers won't be using an index: fix this? +CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from); + +-- Used by the API (and some extensions) +CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp); + +-- FIXME: Not used, delete this +CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation); -- -- Track all existing categories. Something is a category if 1) it has an en- -- try somewhere in categorylinks, or 2) it once did. Categories might not -- have corresponding pages, so they need to be tracked separately. -- -CREATE TABLE /*$wgDBprefix*/category ( - cat_id int NOT NULL IDENTITY(1,1), - cat_title nvarchar(255) NOT NULL, +CREATE TABLE /*_*/category ( + -- Primary key + cat_id int NOT NULL PRIMARY KEY IDENTITY, + + -- Name of the category, in the same form as page_title (with underscores). + -- If there is a category page corresponding to this category, by definition, + -- it has this name (in the Category namespace). + cat_title nvarchar(255) NOT NULL, + + -- The numbers of member pages (including categories and media), subcatego- + -- ries, and Image: namespace members, respectively. These are signed to + -- make underflow more obvious. We make the first number include the second + -- two for better sorting: subtracting for display is easy, adding for order- + -- ing is not. cat_pages int NOT NULL default 0, cat_subcats int NOT NULL default 0, - cat_files int NOT NULL default 0, - cat_hidden tinyint NOT NULL default 0, + cat_files int NOT NULL default 0 ); -CREATE UNIQUE INDEX /*$wgDBprefix*/cat_title ON /*$wgDBprefix*/category(cat_title); --- For Special:Mostlinkedcategories -CREATE INDEX /*$wgDBprefix*/cat_pages ON /*$wgDBprefix*/category(cat_pages); +CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title); +-- For Special:Mostlinkedcategories +CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages); -CREATE TABLE /*$wgDBprefix*/change_tag ( - ct_rc_id int NOT NULL default 0, - ct_log_id int NOT NULL default 0, - ct_rev_id int NOT NULL default 0, - ct_tag varchar(255) NOT NULL, - ct_params varchar(255) NOT NULL, -); -CREATE UNIQUE INDEX /*$wgDBprefix*/change_tag_rc_tag ON /*$wgDBprefix*/change_tag(ct_rc_id,ct_tag); -CREATE UNIQUE INDEX /*$wgDBprefix*/change_tag_log_tag ON /*$wgDBprefix*/change_tag(ct_log_id,ct_tag); -CREATE UNIQUE INDEX /*$wgDBprefix*/change_tag_rev_tag ON /*$wgDBprefix*/change_tag(ct_rev_id,ct_tag); -CREATE INDEX /*$wgDBprefix*/change_tag_tag_id ON /*$wgDBprefix*/change_tag(ct_tag,ct_rc_id,ct_rev_id,ct_log_id); -CREATE TABLE /*$wgDBprefix*/tag_summary ( - ts_rc_id INT NOT NULL default 0, - ts_log_id INT NOT NULL default 0, - ts_rev_id INT NOT NULL default 0, - ts_tags varchar(255) NOT NULL +-- +-- Track links to external URLs +-- +CREATE TABLE /*_*/externallinks ( + -- Primary key + el_id int NOT NULL PRIMARY KEY IDENTITY, + + -- page_id of the referring page + el_from int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + + -- The URL + el_to nvarchar(max) NOT NULL, + + -- In the case of HTTP URLs, this is the URL with any username or password + -- removed, and with the labels in the hostname reversed and converted to + -- lower case. An extra dot is added to allow for matching of either + -- example.com or *.example.com in a single scan. + -- Example: + -- http://user:password@sub.example.com/page.html + -- becomes + -- http://com.example.sub./page.html + -- which allows for fast searching for all pages under example.com with the + -- clause: + -- WHERE el_index LIKE 'http://com.example.%' + el_index nvarchar(450) NOT NULL ); -CREATE UNIQUE INDEX /*$wgDBprefix*/tag_summary_rc_id ON /*$wgDBprefix*/tag_summary(ts_rc_id); -CREATE UNIQUE INDEX /*$wgDBprefix*/tag_summary_log_id ON /*$wgDBprefix*/tag_summary(ts_log_id); -CREATE UNIQUE INDEX /*$wgDBprefix*/tag_summary_rev_id ON /*$wgDBprefix*/tag_summary(ts_rev_id); -CREATE TABLE /*$wgDBprefix*/valid_tag ( - vt_tag varchar(255) NOT NULL PRIMARY KEY -); +CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from); +CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index); -- --- Table for storing localisation data +-- Track interlanguage links -- -CREATE TABLE /*$wgDBprefix*/l10n_cache ( - -- language code - lc_lang NVARCHAR(32) NOT NULL, +CREATE TABLE /*_*/langlinks ( + -- page_id of the referring page + ll_from int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, - -- cache key - lc_key NVARCHAR(255) NOT NULL, + -- Language code of the target + ll_lang nvarchar(20) NOT NULL default '', - -- Value - lc_value TEXT NOT NULL DEFAULT '', + -- Title of the target, including namespace + ll_title nvarchar(255) NOT NULL default '' ); -CREATE INDEX /*$wgDBprefix*/lc_lang_key ON /*$wgDBprefix*/l10n_cache (lc_lang, lc_key); --- --- Track links to external URLs --- IE >= 4 supports no more than 2083 characters in a URL -CREATE TABLE /*$wgDBprefix*/externallinks ( - el_id INT NOT NULL PRIMARY KEY clustered IDENTITY, - el_from INT NOT NULL DEFAULT '0', - el_to VARCHAR(2083) NOT NULL, - el_index VARCHAR(896) NOT NULL, -); --- Maximum key length ON SQL Server is 900 bytes -CREATE INDEX /*$wgDBprefix*/externallinks_index ON /*$wgDBprefix*/externallinks(el_index); +CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang); +CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title); --- --- Track INTerlanguage links --- -CREATE TABLE /*$wgDBprefix*/langlinks ( - ll_from INT NOT NULL DEFAULT 0, - ll_lang NVARCHAR(20) NOT NULL DEFAULT '', - ll_title NVARCHAR(255) NOT NULL DEFAULT '', - CONSTRAINT /*$wgDBprefix*/langlinks_pk PRIMARY KEY(ll_from, ll_lang), -); -CREATE UNIQUE INDEX /*$wgDBprefix*/langlinks_reverse_key ON /*$wgDBprefix*/langlinks(ll_lang,ll_title); -- -- Track inline interwiki links -- -CREATE TABLE /*$wgDBprefix*/iwlinks ( - -- page_id of the referring page - iwl_from INT NOT NULL DEFAULT 0, +CREATE TABLE /*_*/iwlinks ( + -- page_id of the referring page + iwl_from int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, - -- Interwiki prefix code of the target - iwl_prefix NVARCHAR(20) NOT NULL DEFAULT '', + -- Interwiki prefix code of the target + iwl_prefix nvarchar(20) NOT NULL default '', - -- Title of the target, including namespace - iwl_title NVARCHAR(255) NOT NULL DEFAULT '', + -- Title of the target, including namespace + iwl_title nvarchar(255) NOT NULL default '' ); -CREATE UNIQUE INDEX /*$wgDBprefix*/iwl_from ON /*$wgDBprefix*/iwlinks(iwl_from,iwl_prefix,iwl_title); -CREATE UNIQUE INDEX /*$wgDBprefix*/iwl_prefix ON /*$wgDBprefix*/iwlinks(iwl_prefix,iwl_title); +CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title); +CREATE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from); +CREATE INDEX /*i*/iwl_prefix_from_title ON /*_*/iwlinks (iwl_prefix, iwl_from, iwl_title); -- -- Contains a single row with some aggregate info --- ON the state of the site. +-- on the state of the site. -- -CREATE TABLE /*$wgDBprefix*/site_stats ( - ss_row_id INT NOT NULL DEFAULT 1 PRIMARY KEY, - ss_total_views BIGINT DEFAULT 0, - ss_total_edits BIGINT DEFAULT 0, - ss_good_articles BIGINT DEFAULT 0, - ss_total_pages BIGINT DEFAULT -1, - ss_users BIGINT DEFAULT -1, - ss_active_users BIGINT DEFAULT -1, - ss_admins INT DEFAULT -1, - ss_images INT DEFAULT 0, +CREATE TABLE /*_*/site_stats ( + -- The single row should contain 1 here. + ss_row_id int NOT NULL, + + -- Total number of page views, if hit counters are enabled. + ss_total_views bigint default 0, + + -- Total number of edits performed. + ss_total_edits bigint default 0, + + -- An approximate count of pages matching the following criteria: + -- * in namespace 0 + -- * not a redirect + -- * contains the text '[[' + -- See Article::isCountable() in includes/Article.php + ss_good_articles bigint default 0, + + -- Total pages, theoretically equal to SELECT COUNT(*) FROM page; except faster + ss_total_pages bigint default '-1', + + -- Number of users, theoretically equal to SELECT COUNT(*) FROM user; + ss_users bigint default '-1', + + -- Number of users that still edit + ss_active_users bigint default '-1', + + -- Number of images, equivalent to SELECT COUNT(*) FROM image + ss_images int default 0 ); --- INSERT INTO site_stats DEFAULT VALUES; +-- Pointless index to assuage developer superstitions +CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id); + -- -- Stores an ID for every time any article is visited; --- depending ON $wgHitcounterUpdateFreq, it is +-- depending on $wgHitcounterUpdateFreq, it is -- periodically cleared and the page_counter column --- in the page table updated for the all articles +-- in the page table updated for all the articles -- that have been visited.) -- -CREATE TABLE /*$wgDBprefix*/hitcounter ( - hc_id BIGINT NOT NULL +CREATE TABLE /*_*/hitcounter ( + hc_id int NOT NULL ); + -- --- The Internet is full of jerks, alas. Sometimes it's handy +-- The internet is full of jerks, alas. Sometimes it's handy -- to block a vandal or troll account. -- -CREATE TABLE /*$wgDBprefix*/ipblocks ( - ipb_id INT NOT NULL PRIMARY KEY, - ipb_address NVARCHAR(255) NOT NULL, - ipb_user INT NOT NULL DEFAULT 0, - ipb_by INT NOT NULL DEFAULT 0, - ipb_by_text NVARCHAR(255) NOT NULL DEFAULT '', - ipb_reason NVARCHAR(255) NOT NULL, - ipb_timestamp DATETIME NOT NULL DEFAULT GETDATE(), - ipb_auto BIT NOT NULL DEFAULT 0, - ipb_anon_only BIT NOT NULL DEFAULT 0, - ipb_create_account BIT NOT NULL DEFAULT 1, - ipb_enable_autoblock BIT NOT NULL DEFAULT 1, - ipb_expiry DATETIME NOT NULL DEFAULT GETDATE(), - ipb_range_start NVARCHAR(32) NOT NULL DEFAULT '', - ipb_range_end NVARCHAR(32) NOT NULL DEFAULT '', - ipb_deleted BIT NOT NULL DEFAULT 0, - ipb_block_email BIT NOT NULL DEFAULT 0, - ipb_allow_usertalk BIT NOT NULL DEFAULT 0, - ipb_parent_block_id INT DEFAULT NULL, +CREATE TABLE /*_*/ipblocks ( + -- Primary key, introduced for privacy. + ipb_id int NOT NULL PRIMARY KEY IDENTITY, + + -- Blocked IP address in dotted-quad form or user name. + ipb_address nvarchar(255) NOT NULL, + + -- Blocked user ID or 0 for IP blocks. + ipb_user int REFERENCES /*_*/mwuser(user_id), + + -- User ID who made the block. + ipb_by int REFERENCES /*_*/mwuser(user_id) ON DELETE CASCADE, + + -- User name of blocker + ipb_by_text nvarchar(255) NOT NULL default '', + + -- Text comment made by blocker. + ipb_reason nvarchar(255) NOT NULL, + + -- Creation (or refresh) date in standard YMDHMS form. + -- IP blocks expire automatically. + ipb_timestamp varchar(14) NOT NULL default '', + + -- Indicates that the IP address was banned because a banned + -- user accessed a page through it. If this is 1, ipb_address + -- will be hidden, and the block identified by block ID number. + ipb_auto bit NOT NULL default 0, + + -- If set to 1, block applies only to logged-out users + ipb_anon_only bit NOT NULL default 0, + + -- Block prevents account creation from matching IP addresses + ipb_create_account bit NOT NULL default 1, + + -- Block triggers autoblocks + ipb_enable_autoblock bit NOT NULL default 1, + + -- Time at which the block will expire. + -- May be "infinity" + ipb_expiry varchar(14) NOT NULL, + + -- Start and end of an address range, in hexadecimal + -- Size chosen to allow IPv6 + -- FIXME: these fields were originally blank for single-IP blocks, + -- but now they are populated. No migration was ever done. They + -- should be fixed to be blank again for such blocks (bug 49504). + ipb_range_start varchar(255) NOT NULL, + ipb_range_end varchar(255) NOT NULL, + + -- Flag for entries hidden from users and Sysops + ipb_deleted bit NOT NULL default 0, + + -- Block prevents user from accessing Special:Emailuser + ipb_block_email bit NOT NULL default 0, + + -- Block allows user to edit their own talk page + ipb_allow_usertalk bit NOT NULL default 0, + + -- ID of the block that caused this block to exist + -- Autoblocks set this to the original block + -- so that the original block being deleted also + -- deletes the autoblocks + ipb_parent_block_id int default NULL REFERENCES /*_*/ipblocks(ipb_id) + ); + -- Unique index to support "user already blocked" messages -- Any new options which prevent collisions should be included ---UNIQUE INDEX ipb_address (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only), -CREATE UNIQUE INDEX /*$wgDBprefix*/ipb_address ON /*$wgDBprefix*/ipblocks(ipb_address, ipb_user, ipb_auto, ipb_anon_only); -CREATE INDEX /*$wgDBprefix*/ipb_user ON /*$wgDBprefix*/ipblocks(ipb_user); -CREATE INDEX /*$wgDBprefix*/ipb_range ON /*$wgDBprefix*/ipblocks(ipb_range_start, ipb_range_end); -CREATE INDEX /*$wgDBprefix*/ipb_timestamp ON /*$wgDBprefix*/ipblocks(ipb_timestamp); -CREATE INDEX /*$wgDBprefix*/ipb_expiry ON /*$wgDBprefix*/ipblocks(ipb_expiry); -; +CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address, ipb_user, ipb_auto, ipb_anon_only); + +CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user); +CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start, ipb_range_end); +CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp); +CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry); +CREATE INDEX /*i*/ipb_parent_block_id ON /*_*/ipblocks (ipb_parent_block_id); + -- -- Uploaded images and other files. -CREATE TABLE /*$wgDBprefix*/image ( - img_name varchar(255) NOT NULL default '', - img_size INT NOT NULL DEFAULT 0, - img_width INT NOT NULL DEFAULT 0, - img_height INT NOT NULL DEFAULT 0, - img_metadata TEXT NOT NULL, -- was MEDIUMBLOB - img_bits SMALLINT NOT NULL DEFAULT 0, - img_media_type NVARCHAR(MAX) DEFAULT 'UNKNOWN', - img_major_mime NVARCHAR(MAX) DEFAULT 'UNKNOWN', - img_minor_mime NVARCHAR(MAX) NOT NULL DEFAULT 'unknown', - img_description NVARCHAR(MAX) NOT NULL, - img_user INT NOT NULL DEFAULT 0, - img_user_text VARCHAR(255) NOT NULL DEFAULT '', - img_timestamp DATETIME NOT NULL DEFAULT GETDATE(), - img_sha1 VARCHAR(255) NOT NULL default '', -); --- Used by Special:Imagelist for sort-by-size -CREATE INDEX /*$wgDBprefix*/img_size ON /*$wgDBprefix*/[image](img_size); --- Used by Special:Newimages and Special:Imagelist -CREATE INDEX /*$wgDBprefix*/img_timestamp ON /*$wgDBprefix*/[image](img_timestamp) -CREATE INDEX /*$wgDBprefix*/[img_sha1] ON /*wgDBprefix*/[image](img_sha1) +-- +CREATE TABLE /*_*/image ( + -- Filename. + -- This is also the title of the associated description page, + -- which will be in namespace 6 (NS_FILE). + img_name varbinary(255) NOT NULL default 0x PRIMARY KEY, + + -- File size in bytes. + img_size int NOT NULL default 0, + + -- For images, size in pixels. + img_width int NOT NULL default 0, + img_height int NOT NULL default 0, + + -- Extracted Exif metadata stored as a serialized PHP array. + img_metadata varbinary(max) NOT NULL, + + -- For images, bits per pixel if known. + img_bits int NOT NULL default 0, + + -- Media type as defined by the MEDIATYPE_xxx constants + img_media_type varchar(16) default null, + + -- major part of a MIME media type as defined by IANA + -- see http://www.iana.org/assignments/media-types/ + img_major_mime varchar(16) not null default 'unknown', + + -- minor part of a MIME media type as defined by IANA + -- the minor parts are not required to adher to any standard + -- but should be consistent throughout the database + -- see http://www.iana.org/assignments/media-types/ + img_minor_mime nvarchar(100) NOT NULL default 'unknown', + + -- Description field as entered by the uploader. + -- This is displayed in image upload history and logs. + img_description nvarchar(255) NOT NULL, + + -- user_id and user_name of uploader. + img_user int REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL, + img_user_text nvarchar(255) NOT NULL, + + -- Time of the upload. + img_timestamp nvarchar(14) NOT NULL default '', + + -- SHA-1 content hash in base-36 + img_sha1 nvarchar(32) NOT NULL default '', + + CONSTRAINT img_major_mime_ckc check (img_major_mime IN('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart', 'chemical')), + CONSTRAINT img_media_type_ckc check (img_media_type in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE')) +); + +CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp); +-- Used by Special:ListFiles for sort-by-size +CREATE INDEX /*i*/img_size ON /*_*/image (img_size); +-- Used by Special:Newimages and Special:ListFiles +CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp); +-- Used in API and duplicate search +CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1); +-- Used to get media of one type +CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,img_minor_mime); + -- -- Previous revisions of uploaded files. -- Awkwardly, image rows have to be moved into -- this table at re-upload time. -- -CREATE TABLE /*$wgDBprefix*/oldimage ( - oi_name VARCHAR(255) NOT NULL DEFAULT '', - oi_archive_name VARCHAR(255) NOT NULL DEFAULT '', - oi_size INT NOT NULL DEFAULT 0, - oi_width INT NOT NULL DEFAULT 0, - oi_height INT NOT NULL DEFAULT 0, - oi_bits SMALLINT NOT NULL DEFAULT 0, - oi_description NVARCHAR(MAX) NOT NULL, - oi_user INT NOT NULL DEFAULT 0, - oi_user_text VARCHAR(255) NOT NULL DEFAULT '', - oi_timestamp DATETIME NOT NULL DEFAULT GETDATE(), - oi_metadata TEXT NOT NULL, - oi_media_type NVARCHAR(MAX) DEFAULT 'UNKNOWN', - oi_major_mime NVARCHAR(MAX) NOT NULL DEFAULT 'UNKNOWN', - oi_minor_mime NVARCHAR(MAX) NOT NULL DEFAULT 'unknown', - oi_deleted BIT NOT NULL default 0, - oi_sha1 VARCHAR(255) NOT NULL default '', -); -CREATE INDEX /*$wgDBprefix*/oi_usertext_timestamp ON /*$wgDBprefix*/oldimage(oi_user_text,oi_timestamp); -CREATE INDEX /*$wgDBprefix*/oi_name_timestamp ON /*$wgDBprefix*/oldimage(oi_name, oi_timestamp); -CREATE INDEX /*$wgDBprefix*/oi_name_archive_name ON /*$wgDBprefix*/oldimage(oi_name,oi_archive_name); -CREATE INDEX /*$wgDBprefix*/[oi_sha1] ON /*$wgDBprefix*/oldimage(oi_sha1); +CREATE TABLE /*_*/oldimage ( + -- Base filename: key to image.img_name + oi_name varbinary(255) NOT NULL default 0x REFERENCES /*_*/image(img_name) ON DELETE CASCADE ON UPDATE CASCADE, + + -- Filename of the archived file. + -- This is generally a timestamp and '!' prepended to the base name. + oi_archive_name varbinary(255) NOT NULL default 0x, + + -- Other fields as in image... + oi_size int NOT NULL default 0, + oi_width int NOT NULL default 0, + oi_height int NOT NULL default 0, + oi_bits int NOT NULL default 0, + oi_description nvarchar(255) NOT NULL, + oi_user int REFERENCES /*_*/mwuser(user_id), + oi_user_text nvarchar(255) NOT NULL, + oi_timestamp varchar(14) NOT NULL default '', + + oi_metadata nvarchar(max) NOT NULL, + oi_media_type varchar(16) default null, + oi_major_mime varchar(16) not null default 'unknown', + oi_minor_mime nvarchar(100) NOT NULL default 'unknown', + oi_deleted tinyint NOT NULL default 0, + oi_sha1 nvarchar(32) NOT NULL default '', + + CONSTRAINT oi_major_mime_ckc check (oi_major_mime IN('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart', 'chemical')), + CONSTRAINT oi_media_type_ckc check (oi_media_type IN('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE')) +); + +CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp); +CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp); +-- oi_archive_name truncated to 14 to avoid key length overflow +CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name); +CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1); + -- -- Record of deleted file data -- -CREATE TABLE /*$wgDBprefix*/filearchive ( - fa_id INT NOT NULL PRIMARY KEY, - fa_name NVARCHAR(255) NOT NULL DEFAULT '', - fa_archive_name NVARCHAR(255) DEFAULT '', - fa_storage_group NVARCHAR(16), - fa_storage_key NVARCHAR(64) DEFAULT '', - fa_deleted_user INT, - fa_deleted_timestamp NVARCHAR(14) DEFAULT NULL, - fa_deleted_reason NVARCHAR(255), - fa_size SMALLINT DEFAULT 0, - fa_width SMALLINT DEFAULT 0, - fa_height SMALLINT DEFAULT 0, - fa_metadata NVARCHAR(MAX), -- was mediumblob - fa_bits SMALLINT DEFAULT 0, - fa_media_type NVARCHAR(11) DEFAULT NULL, - fa_major_mime NVARCHAR(11) DEFAULT 'unknown', - fa_minor_mime NVARCHAR(32) DEFAULT 'unknown', - fa_description NVARCHAR(255), - fa_user INT DEFAULT 0, - fa_user_text NVARCHAR(255) DEFAULT '', - fa_timestamp DATETIME DEFAULT GETDATE(), - fa_deleted BIT NOT NULL DEFAULT 0, -); --- Pick by image name -CREATE INDEX /*$wgDBprefix*/filearchive_name ON /*$wgDBprefix*/filearchive(fa_name,fa_timestamp); --- Pick by dupe files -CREATE INDEX /*$wgDBprefix*/filearchive_dupe ON /*$wgDBprefix*/filearchive(fa_storage_group,fa_storage_key); --- Pick by deletion time -CREATE INDEX /*$wgDBprefix*/filearchive_time ON /*$wgDBprefix*/filearchive(fa_deleted_timestamp); --- Pick by deleter -CREATE INDEX /*$wgDBprefix*/filearchive_user ON /*$wgDBprefix*/filearchive(fa_deleted_user); +CREATE TABLE /*_*/filearchive ( + -- Unique row id + fa_id int NOT NULL PRIMARY KEY IDENTITY, + + -- Original base filename; key to image.img_name, page.page_title, etc + fa_name nvarchar(255) NOT NULL default '', + + -- Filename of archived file, if an old revision + fa_archive_name nvarchar(255) default '', + + -- Which storage bin (directory tree or object store) the file data + -- is stored in. Should be 'deleted' for files that have been deleted; + -- any other bin is not yet in use. + fa_storage_group nvarchar(16), + + -- SHA-1 of the file contents plus extension, used as a key for storage. + -- eg 8f8a562add37052a1848ff7771a2c515db94baa9.jpg + -- + -- If NULL, the file was missing at deletion time or has been purged + -- from the archival storage. + fa_storage_key nvarchar(64) default '', + + -- Deletion information, if this file is deleted. + fa_deleted_user int, + fa_deleted_timestamp varchar(14) default '', + fa_deleted_reason nvarchar(max), + + -- Duped fields from image + fa_size int default 0, + fa_width int default 0, + fa_height int default 0, + fa_metadata nvarchar(max), + fa_bits int default 0, + fa_media_type varchar(16) default null, + fa_major_mime varchar(16) not null default 'unknown', + fa_minor_mime nvarchar(100) default 'unknown', + fa_description nvarchar(255), + fa_user int default 0 REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL, + fa_user_text nvarchar(255), + fa_timestamp varchar(14) default '', + + -- Visibility of deleted revisions, bitfield + fa_deleted tinyint NOT NULL default 0, + + -- sha1 hash of file content + fa_sha1 nvarchar(32) NOT NULL default '', + + CONSTRAINT fa_major_mime_ckc check (fa_major_mime in('unknown', 'application', 'audio', 'image', 'text', 'video', 'message', 'model', 'multipart', 'chemical')), + CONSTRAINT fa_media_type_ckc check (fa_media_type in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE')) +); + +-- pick out by image name +CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp); +-- pick out dupe files +CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key); +-- sort by deletion time +CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp); +-- sort by uploader +CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp); +-- find file by sha1, 10 bytes will be enough for hashes to be indexed +CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1); + + +-- +-- Store information about newly uploaded files before they're +-- moved into the actual filestore +-- +CREATE TABLE /*_*/uploadstash ( + us_id int NOT NULL PRIMARY KEY IDENTITY, + + -- the user who uploaded the file. + us_user int REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL, + + -- file key. this is how applications actually search for the file. + -- this might go away, or become the primary key. + us_key nvarchar(255) NOT NULL, + + -- the original path + us_orig_path nvarchar(255) NOT NULL, + + -- the temporary path at which the file is actually stored + us_path nvarchar(255) NOT NULL, + + -- which type of upload the file came from (sometimes) + us_source_type nvarchar(50), + + -- the date/time on which the file was added + us_timestamp varchar(14) NOT NULL, + + us_status nvarchar(50) NOT NULL, + + -- chunk counter starts at 0, current offset is stored in us_size + us_chunk_inx int NULL, + + -- Serialized file properties from FSFile::getProps() + us_props nvarchar(max), + + -- file size in bytes + us_size int NOT NULL, + -- this hash comes from FSFile::getSha1Base36(), and is 31 characters + us_sha1 nvarchar(31) NOT NULL, + us_mime nvarchar(255), + -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table + us_media_type varchar(16) default null, + -- image-specific properties + us_image_width int, + us_image_height int, + us_image_bits smallint, + + CONSTRAINT us_media_type_ckc check (us_media_type in('UNKNOWN', 'BITMAP', 'DRAWING', 'AUDIO', 'VIDEO', 'MULTIMEDIA', 'OFFICE', 'TEXT', 'EXECUTABLE', 'ARCHIVE')) +); + +-- sometimes there's a delete for all of a user's stuff. +CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user); +-- pick out files by key, enforce key uniqueness +CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key); +-- the abandoned upload cleanup script needs this +CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp); + -- -- Primarily a summary table for Special:Recentchanges, -- this table contains some additional info on edits from -- the last few days, see Article::editUpdates() -- -CREATE TABLE /*$wgDBprefix*/recentchanges ( - rc_id INT NOT NULL, - rc_timestamp DATETIME DEFAULT GETDATE(), - rc_cur_time DATETIME DEFAULT GETDATE(), - rc_user INT DEFAULT 0, - rc_user_text NVARCHAR(255) DEFAULT '', - rc_namespace SMALLINT DEFAULT 0, - rc_title NVARCHAR(255) DEFAULT '', - rc_comment NVARCHAR(255) DEFAULT '', - rc_minor BIT DEFAULT 0, - rc_bot BIT DEFAULT 0, - rc_new BIT DEFAULT 0, - rc_cur_id INT DEFAULT 0, - rc_this_oldid INT DEFAULT 0, - rc_last_oldid INT DEFAULT 0, - rc_type tinyint DEFAULT 0, - rc_patrolled BIT DEFAULT 0, - rc_ip NCHAR(40) DEFAULT '', - rc_old_len INT DEFAULT 0, - rc_new_len INT DEFAULT 0, - rc_deleted BIT DEFAULT 0, - rc_logid INT DEFAULT 0, - rc_log_type NVARCHAR(255) NULL DEFAULT NULL, - rc_log_action NVARCHAR(255) NULL DEFAULT NULL, - rc_params NVARCHAR(MAX) DEFAULT '', -); -CREATE INDEX /*$wgDBprefix*/rc_timestamp ON /*$wgDBprefix*/recentchanges(rc_timestamp); -CREATE INDEX /*$wgDBprefix*/rc_namespace_title ON /*$wgDBprefix*/recentchanges(rc_namespace, rc_title); -CREATE INDEX /*$wgDBprefix*/rc_cur_id ON /*$wgDBprefix*/recentchanges(rc_cur_id); -CREATE INDEX /*$wgDBprefix*/new_name_timestamp ON /*$wgDBprefix*/recentchanges(rc_new,rc_namespace,rc_timestamp); -CREATE INDEX /*$wgDBprefix*/rc_ip ON /*$wgDBprefix*/recentchanges(rc_ip); -CREATE INDEX /*$wgDBprefix*/rc_ns_usertext ON /*$wgDBprefix*/recentchanges(rc_namespace, rc_user_text); -CREATE INDEX /*$wgDBprefix*/rc_user_text ON /*$wgDBprefix*/recentchanges(rc_user_text, rc_timestamp); -; - -CREATE TABLE /*$wgDBprefix*/watchlist ( - wl_user INT NOT NULL, - wl_namespace SMALLINT NOT NULL DEFAULT 0, - wl_title NVARCHAR(255) NOT NULL DEFAULT '', - wl_notificationtimestamp NVARCHAR(14) DEFAULT NULL, - -); -CREATE UNIQUE INDEX /*$wgDBprefix*/namespace_title ON /*$wgDBprefix*/watchlist(wl_namespace,wl_title); - --- Needs fulltext index. -CREATE TABLE /*$wgDBprefix*/searchindex ( - si_page INT NOT NULL unique REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, - si_title varbinary(max) NOT NULL, - si_text varbinary(max) NOT NULL, - si_ext CHAR(4) NOT NULL DEFAULT '.txt', -); -CREATE FULLTEXT CATALOG wikidb AS DEFAULT; -CREATE UNIQUE CLUSTERED INDEX searchindex_page ON searchindex (si_page); -CREATE FULLTEXT INDEX on searchindex (si_title TYPE COLUMN si_ext, si_text TYPE COLUMN si_ext) -KEY INDEX searchindex_page -; - --- This table is not used unless profiling is turned on -CREATE TABLE profiling ( - pf_count INTEGER NOT NULL DEFAULT 0, - pf_time NUMERIC(18,10) NOT NULL DEFAULT 0, - pf_name NVARCHAR(200) NOT NULL, - pf_server NVARCHAR(200) NULL -); -CREATE UNIQUE INDEX pf_name_server ON profiling (pf_name, pf_server); - --- --- Recognized INTerwiki link prefixes --- -CREATE TABLE /*$wgDBprefix*/interwiki ( - iw_prefix NCHAR(32) NOT NULL PRIMARY KEY, - iw_url NCHAR(127) NOT NULL, - iw_api TEXT NOT NULL DEFAULT '', - iw_wikiid NVARCHAR(64) NOT NULL DEFAULT '', - iw_local BIT NOT NULL, - iw_trans BIT NOT NULL DEFAULT 0, +CREATE TABLE /*_*/recentchanges ( + rc_id int NOT NULL PRIMARY KEY IDENTITY, + rc_timestamp varchar(14) not null default '', + + -- This is no longer used + -- Field kept in database for downgrades + -- @todo: add drop patch with 1.24 + rc_cur_time varchar(14) NOT NULL default '', + + -- As in revision + rc_user int NOT NULL default 0 REFERENCES /*_*/mwuser(user_id), + rc_user_text nvarchar(255) NOT NULL, + + -- When pages are renamed, their RC entries do _not_ change. + rc_namespace int NOT NULL default 0, + rc_title nvarchar(255) NOT NULL default '', + + -- as in revision... + rc_comment nvarchar(255) NOT NULL default '', + rc_minor bit NOT NULL default 0, + + -- Edits by user accounts with the 'bot' rights key are + -- marked with a 1 here, and will be hidden from the + -- default view. + rc_bot bit NOT NULL default 0, + + -- Set if this change corresponds to a page creation + rc_new bit NOT NULL default 0, + + -- Key to page_id (was cur_id prior to 1.5). + -- This will keep links working after moves while + -- retaining the at-the-time name in the changes list. + rc_cur_id int REFERENCES /*_*/page(page_id), + + -- rev_id of the given revision + rc_this_oldid int REFERENCES /*_*/revision(rev_id), + + -- rev_id of the prior revision, for generating diff links. + rc_last_oldid int REFERENCES /*_*/revision(rev_id), + + -- The type of change entry (RC_EDIT,RC_NEW,RC_LOG,RC_EXTERNAL) + rc_type tinyint NOT NULL default 0, + + -- The source of the change entry (replaces rc_type) + -- default of '' is temporary, needed for initial migration + rc_source nvarchar(16) not null default '', + + -- If the Recent Changes Patrol option is enabled, + -- users may mark edits as having been reviewed to + -- remove a warning flag on the RC list. + -- A value of 1 indicates the page has been reviewed. + rc_patrolled bit NOT NULL default 0, + + -- Recorded IP address the edit was made from, if the + -- $wgPutIPinRC option is enabled. + rc_ip nvarchar(40) NOT NULL default '', + + -- Text length in characters before + -- and after the edit + rc_old_len int, + rc_new_len int, + + -- Visibility of recent changes items, bitfield + rc_deleted tinyint NOT NULL default 0, + + -- Value corresponding to log_id, specific log entries + rc_logid int, -- FK added later + -- Store log type info here, or null + rc_log_type nvarchar(255) NULL default NULL, + -- Store log action or null + rc_log_action nvarchar(255) NULL default NULL, + -- Log params + rc_params nvarchar(max) NULL ); +CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp); +CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title); +CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id); +CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp); +CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip); +CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text); +CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp); + + +CREATE TABLE /*_*/watchlist ( + -- Key to user.user_id + wl_user int NOT NULL REFERENCES /*_*/mwuser(user_id) ON DELETE CASCADE, + + -- Key to page_namespace/page_title + -- Note that users may watch pages which do not exist yet, + -- or existed in the past but have been deleted. + wl_namespace int NOT NULL default 0, + wl_title nvarchar(255) NOT NULL default '', + + -- Timestamp used to send notification e-mails and show "updated since last visit" markers on + -- history and recent changes / watchlist. Set to NULL when the user visits the latest revision + -- of the page, which means that they should be sent an e-mail on the next change. + wl_notificationtimestamp varchar(14) + +); + +CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title); +CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title); + + +-- +-- Our search index for the builtin MediaWiki search +-- +CREATE TABLE /*_*/searchindex ( + -- Key to page_id + si_page int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + + -- Munged version of title + si_title nvarchar(255) NOT NULL default '', + + -- Munged version of body text + si_text nvarchar(max) NOT NULL +); + +CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page); +-- Fulltext index is defined in MssqlInstaller.php + +-- +-- Recognized interwiki link prefixes +-- +CREATE TABLE /*_*/interwiki ( + -- The interwiki prefix, (e.g. "Meatball", or the language prefix "de") + iw_prefix nvarchar(32) NOT NULL, + + -- The URL of the wiki, with "$1" as a placeholder for an article name. + -- Any spaces in the name will be transformed to underscores before + -- insertion. + iw_url nvarchar(max) NOT NULL, + + -- The URL of the file api.php + iw_api nvarchar(max) NOT NULL, + + -- The name of the database (for a connection to be established with wfGetLB( 'wikiid' )) + iw_wikiid nvarchar(64) NOT NULL, + + -- A boolean value indicating whether the wiki is in this project + -- (used, for example, to detect redirect loops) + iw_local bit NOT NULL, + + -- Boolean value indicating whether interwiki transclusions are allowed. + iw_trans bit NOT NULL default 0 +); + +CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix); + + -- -- Used for caching expensive grouped queries -- -CREATE TABLE /*$wgDBprefix*/querycache ( - qc_type NCHAR(32) NOT NULL, - qc_value INT NOT NULL DEFAULT '0', - qc_namespace SMALLINT NOT NULL DEFAULT 0, - qc_title NCHAR(255) NOT NULL DEFAULT '', - CONSTRAINT /*$wgDBprefix*/qc_pk PRIMARY KEY (qc_type,qc_value) +CREATE TABLE /*_*/querycache ( + -- A key name, generally the base name of of the special page. + qc_type nvarchar(32) NOT NULL, + + -- Some sort of stored value. Sizes, counts... + qc_value int NOT NULL default 0, + + -- Target namespace+title + qc_namespace int NOT NULL default 0, + qc_title nvarchar(255) NOT NULL default '' ); +CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value); + + -- -- For a few generic cache operations if not using Memcached -- -CREATE TABLE /*$wgDBprefix*/objectcache ( - keyname NCHAR(255) NOT NULL DEFAULT '', - [value] NVARCHAR(MAX), -- IMAGE, - exptime DATETIME, -- This is treated as a DATETIME -); -CREATE CLUSTERED INDEX /*$wgDBprefix*/[objectcache_time] ON /*$wgDBprefix*/objectcache(exptime); -CREATE UNIQUE INDEX /*$wgDBprefix*/[objectcache_PK] ON /*wgDBprefix*/objectcache(keyname); --- --- Cache of INTerwiki transclusion --- -CREATE TABLE /*$wgDBprefix*/transcache ( - tc_url NVARCHAR(255) NOT NULL PRIMARY KEY, - tc_contents NVARCHAR(MAX), - tc_time INT NOT NULL, -); - -CREATE TABLE /*$wgDBprefix*/logging ( - log_id INT PRIMARY KEY IDENTITY, - log_type NCHAR(10) NOT NULL DEFAULT '', - log_action NCHAR(10) NOT NULL DEFAULT '', - log_timestamp DATETIME NOT NULL DEFAULT GETDATE(), - log_user INT NOT NULL DEFAULT 0, - log_user_text NVARCHAR(255) NOT NULL DEFAULT '', - log_namespace INT NOT NULL DEFAULT 0, - log_title NVARCHAR(255) NOT NULL DEFAULT '', - log_page INT NULL DEFAULT NULL, - log_comment NVARCHAR(255) NOT NULL DEFAULT '', - log_params NVARCHAR(MAX) NOT NULL, - log_deleted BIT NOT NULL DEFAULT 0, -); -CREATE INDEX /*$wgDBprefix*/type_time ON /*$wgDBprefix*/logging (log_type, log_timestamp); -CREATE INDEX /*$wgDBprefix*/user_time ON /*$wgDBprefix*/logging (log_user, log_timestamp); -CREATE INDEX /*$wgDBprefix*/page_time ON /*$wgDBprefix*/logging (log_namespace, log_title, log_timestamp); -CREATE INDEX /*$wgDBprefix*/times ON /*$wgDBprefix*/logging (log_timestamp); -CREATE INDEX /*$wgDBprefix*/log_user_type_time ON /*$wgDBprefix*/logging (log_user, log_type, log_timestamp); -CREATE INDEX /*$wgDBprefix*/log_page_id_time ON /*$wgDBprefix*/logging (log_page,log_timestamp); - -CREATE TABLE /*$wgDBprefix*/log_search ( - -- The type of ID (rev ID, log ID, rev timestamp, username) - ls_field NVARCHAR(32) NOT NULL, - -- The value of the ID - ls_value NVARCHAR(255) NOT NULL, - -- Key to log_id - ls_log_id INT NOT NULL default 0, -); -CREATE UNIQUE INDEX /*$wgDBprefix*/ls_field_val ON /*$wgDBprefix*/log_search (ls_field,ls_value,ls_log_id); -CREATE INDEX /*$wgDBprefix*/ls_log_id ON /*$wgDBprefix*/log_search (ls_log_id); +CREATE TABLE /*_*/objectcache ( + keyname nvarchar(255) NOT NULL default '' PRIMARY KEY, + value varbinary(max), + exptime varchar(14) +); +CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime); + + +-- +-- Cache of interwiki transclusion +-- +CREATE TABLE /*_*/transcache ( + tc_url nvarchar(255) NOT NULL, + tc_contents nvarchar(max), + tc_time varchar(14) NOT NULL +); + +CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url); + + +CREATE TABLE /*_*/logging ( + -- Log ID, for referring to this specific log entry, probably for deletion and such. + log_id int NOT NULL PRIMARY KEY IDENTITY(0,1), + + -- Symbolic keys for the general log type and the action type + -- within the log. The output format will be controlled by the + -- action field, but only the type controls categorization. + log_type nvarchar(32) NOT NULL default '', + log_action nvarchar(32) NOT NULL default '', + + -- Timestamp. Duh. + log_timestamp varchar(14) NOT NULL default '', + + -- The user who performed this action; key to user_id + log_user int REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL, + + -- Name of the user who performed this action + log_user_text nvarchar(255) NOT NULL default '', + + -- Key to the page affected. Where a user is the target, + -- this will point to the user page. + log_namespace int NOT NULL default 0, + log_title nvarchar(255) NOT NULL default '', + log_page int NULL REFERENCES /*_*/page(page_id) ON DELETE SET NULL, + + -- Freeform text. Interpreted as edit history comments. + log_comment nvarchar(255) NOT NULL default '', + + -- miscellaneous parameters: + -- LF separated list (old system) or serialized PHP array (new system) + log_params nvarchar(max) NOT NULL, + + -- rev_deleted for logs + log_deleted tinyint NOT NULL default 0 +); + +CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp); +CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp); +CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp); +CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp); +CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp); +CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp); +CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp); +CREATE INDEX /*i*/log_user_text_type_time ON /*_*/logging (log_user_text, log_type, log_timestamp); +CREATE INDEX /*i*/log_user_text_time ON /*_*/logging (log_user_text, log_timestamp); + +INSERT INTO /*_*/logging (log_user,log_page,log_params) VALUES(0,0,''); + +ALTER TABLE /*_*/recentchanges ADD CONSTRAINT FK_rc_logid_log_id FOREIGN KEY (rc_logid) REFERENCES /*_*/logging(log_id) ON DELETE CASCADE; + +CREATE TABLE /*_*/log_search ( + -- The type of ID (rev ID, log ID, rev timestamp, username) + ls_field nvarchar(32) NOT NULL, + -- The value of the ID + ls_value nvarchar(255) NOT NULL, + -- Key to log_id + ls_log_id int REFERENCES /*_*/logging(log_id) ON DELETE CASCADE +); +CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id); +CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id); -- Jobs performed by parallel apache threads or a command-line daemon -CREATE TABLE /*$wgDBprefix*/job ( - job_id INT NOT NULL PRIMARY KEY, - job_cmd NVARCHAR(200) NOT NULL DEFAULT '', - job_namespace INT NOT NULL, - job_title NVARCHAR(200) NOT NULL, - job_params NVARCHAR(255) NOT NULL, +CREATE TABLE /*_*/job ( + job_id int NOT NULL PRIMARY KEY IDENTITY, + + -- Command name + -- Limited to 60 to prevent key length overflow + job_cmd nvarchar(60) NOT NULL default '', + + -- Namespace and title to act on + -- Should be 0 and '' if the command does not operate on a title + job_namespace int NOT NULL, + job_title nvarchar(255) NOT NULL, + + -- Timestamp of when the job was inserted + -- NULL for jobs added before addition of the timestamp + job_timestamp nvarchar(14) NULL default NULL, + + -- Any other parameters to the command + -- Stored as a PHP serialized array, or an empty string if there are no parameters + job_params nvarchar(max) NOT NULL, + + -- Random, non-unique, number used for job acquisition (for lock concurrency) + job_random int NOT NULL default 0, + + -- The number of times this job has been locked + job_attempts int NOT NULL default 0, + + -- Field that conveys process locks on rows via process UUIDs + job_token nvarchar(32) NOT NULL default '', + + -- Timestamp when the job was locked + job_token_timestamp varchar(14) NULL default NULL, + + -- Base 36 SHA1 of the job parameters relevant to detecting duplicates + job_sha1 nvarchar(32) NOT NULL default '' ); -CREATE INDEX /*$wgDBprefix*/job_idx ON /*$wgDBprefix*/job(job_cmd,job_namespace,job_title); + +CREATE INDEX /*i*/job_sha1 ON /*_*/job (job_sha1); +CREATE INDEX /*i*/job_cmd_token ON /*_*/job (job_cmd,job_token,job_random); +CREATE INDEX /*i*/job_cmd_token_id ON /*_*/job (job_cmd,job_token,job_id); +CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title); +CREATE INDEX /*i*/job_timestamp ON /*_*/job (job_timestamp); + -- Details of updates to cached special pages -CREATE TABLE /*$wgDBprefix*/querycache_info ( - qci_type NVARCHAR(32) NOT NULL DEFAULT '' PRIMARY KEY, - qci_timestamp NVARCHAR(14) NOT NULL DEFAULT '19700101000000', +CREATE TABLE /*_*/querycache_info ( + -- Special page name + -- Corresponds to a qc_type value + qci_type nvarchar(32) NOT NULL default '', + + -- Timestamp of last update + qci_timestamp varchar(14) NOT NULL default '' ); +CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type); + + -- For each redirect, this table contains exactly one row defining its target -CREATE TABLE /*$wgDBprefix*/redirect ( - rd_from INT NOT NULL DEFAULT 0 REFERENCES /*$wgDBprefix*/[page](page_id) ON DELETE CASCADE, - rd_namespace SMALLINT NOT NULL DEFAULT '0', - rd_title NVARCHAR(255) NOT NULL DEFAULT '', - rd_interwiki NVARCHAR(32) DEFAULT NULL, - rd_fragment NVARCHAR(255) DEFAULT NULL, +CREATE TABLE /*_*/redirect ( + -- Key to the page_id of the redirect page + rd_from int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + + -- Key to page_namespace/page_title of the target page. + -- The target page may or may not exist, and due to renames + -- and deletions may refer to different page records as time + -- goes by. + rd_namespace int NOT NULL default 0, + rd_title nvarchar(255) NOT NULL default '', + rd_interwiki nvarchar(32) default NULL, + rd_fragment nvarchar(255) default NULL ); -CREATE UNIQUE INDEX /*$wgDBprefix*/rd_ns_title ON /*$wgDBprefix*/redirect(rd_namespace,rd_title,rd_from); + +CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from); + -- Used for caching expensive grouped queries that need two links (for example double-redirects) -CREATE TABLE /*$wgDBprefix*/querycachetwo ( - qcc_type NCHAR(32) NOT NULL, - qcc_value INT NOT NULL DEFAULT 0, - qcc_namespace INT NOT NULL DEFAULT 0, - qcc_title NCHAR(255) NOT NULL DEFAULT '', - qcc_namespacetwo INT NOT NULL DEFAULT 0, - qcc_titletwo NCHAR(255) NOT NULL DEFAULT '', - CONSTRAINT /*$wgDBprefix*/qcc_type PRIMARY KEY(qcc_type,qcc_value), -); -CREATE UNIQUE INDEX /*$wgDBprefix*/qcc_title ON /*$wgDBprefix*/querycachetwo(qcc_type,qcc_namespace,qcc_title); -CREATE UNIQUE INDEX /*$wgDBprefix*/qcc_titletwo ON /*$wgDBprefix*/querycachetwo(qcc_type,qcc_namespacetwo,qcc_titletwo); - - ---- Used for storing page restrictions (i.e. protection levels) -CREATE TABLE /*$wgDBprefix*/page_restrictions ( - pr_page INT NOT NULL REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, - pr_type NVARCHAR(200) NOT NULL, - pr_level NVARCHAR(200) NOT NULL, - pr_cascade SMALLINT NOT NULL, - pr_user INT NULL, - pr_expiry DATETIME NULL, - pr_id INT UNIQUE IDENTITY, - CONSTRAINT /*$wgDBprefix*/pr_pagetype PRIMARY KEY(pr_page,pr_type), -); -CREATE INDEX /*$wgDBprefix*/pr_page ON /*$wgDBprefix*/page_restrictions(pr_page); -CREATE INDEX /*$wgDBprefix*/pr_typelevel ON /*$wgDBprefix*/page_restrictions(pr_type,pr_level); -CREATE INDEX /*$wgDBprefix*/pr_pagelevel ON /*$wgDBprefix*/page_restrictions(pr_level); -CREATE INDEX /*$wgDBprefix*/pr_cascade ON /*$wgDBprefix*/page_restrictions(pr_cascade); -; +CREATE TABLE /*_*/querycachetwo ( + -- A key name, generally the base name of of the special page. + qcc_type nvarchar(32) NOT NULL, + + -- Some sort of stored value. Sizes, counts... + qcc_value int NOT NULL default 0, + + -- Target namespace+title + qcc_namespace int NOT NULL default 0, + qcc_title nvarchar(255) NOT NULL default '', + + -- Target namespace+title2 + qcc_namespacetwo int NOT NULL default 0, + qcc_titletwo nvarchar(255) NOT NULL default '' +); + +CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value); +CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title); +CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo); + + +-- Used for storing page restrictions (i.e. protection levels) +CREATE TABLE /*_*/page_restrictions ( + -- Field for an ID for this restrictions row (sort-key for Special:ProtectedPages) + pr_id int NOT NULL PRIMARY KEY IDENTITY, + -- Page to apply restrictions to (Foreign Key to page). + pr_page int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + -- The protection type (edit, move, etc) + pr_type nvarchar(60) NOT NULL, + -- The protection level (Sysop, autoconfirmed, etc) + pr_level nvarchar(60) NOT NULL, + -- Whether or not to cascade the protection down to pages transcluded. + pr_cascade bit NOT NULL, + -- Field for future support of per-user restriction. + pr_user int NULL, + -- Field for time-limited protection. + pr_expiry varchar(14) NULL +); + +CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type); +CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level); +CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level); +CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade); + -- Protected titles - nonexistent pages that have been protected -CREATE TABLE /*$wgDBprefix*/protected_titles ( +CREATE TABLE /*_*/protected_titles ( pt_namespace int NOT NULL, - pt_title NVARCHAR(255) NOT NULL, - pt_user int NOT NULL, - pt_reason NVARCHAR(3555), - pt_timestamp DATETIME NOT NULL, - pt_expiry DATETIME NOT NULL default '', - pt_create_perm NVARCHAR(60) NOT NULL, - PRIMARY KEY (pt_namespace,pt_title), + pt_title nvarchar(255) NOT NULL, + pt_user int REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL, + pt_reason nvarchar(255), + pt_timestamp varchar(14) NOT NULL, + pt_expiry varchar(14) NOT NULL, + pt_create_perm nvarchar(60) NOT NULL ); -CREATE INDEX /*$wgDBprefix*/pt_timestamp ON /*$wgDBprefix*/protected_titles(pt_timestamp); -; + +CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title); +CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp); + -- Name/value pairs indexed by page_id -CREATE TABLE /*$wgDBprefix*/page_props ( - pp_page int NOT NULL, - pp_propname NVARCHAR(60) NOT NULL, - pp_value NVARCHAR(MAX) NOT NULL, - PRIMARY KEY (pp_page,pp_propname) +CREATE TABLE /*_*/page_props ( + pp_page int NOT NULL REFERENCES /*_*/page(page_id) ON DELETE CASCADE, + pp_propname nvarchar(60) NOT NULL, + pp_value nvarchar(max) NOT NULL ); +CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname); +CREATE UNIQUE INDEX /*i*/pp_propname_page ON /*_*/page_props (pp_propname,pp_page); + + -- A table to log updates, one text key row per update. -CREATE TABLE /*$wgDBprefix*/updatelog ( - ul_key NVARCHAR(255) NOT NULL, - PRIMARY KEY (ul_key) +CREATE TABLE /*_*/updatelog ( + ul_key nvarchar(255) NOT NULL PRIMARY KEY, + ul_value nvarchar(max) ); --- NOTE To enable full text indexing on SQL 2008 you need to create an account FDH$MSSQLSERVER --- AND assign a password for the FDHOST process to run under --- Once you have assigned a password to that account, you need to run the following stored procedure --- replacing XXXXX with the password you used. --- EXEC sp_fulltext_resetfdhostaccount @username = 'FDH$MSSQLSERVER', @password = 'XXXXXX' ; +-- A table to track tags for revisions, logs and recent changes. +CREATE TABLE /*_*/change_tag ( + -- RCID for the change + ct_rc_id int NULL REFERENCES /*_*/recentchanges(rc_id), + -- LOGID for the change + ct_log_id int NULL REFERENCES /*_*/logging(log_id), + -- REVID for the change + ct_rev_id int NULL REFERENCES /*_*/revision(rev_id), + -- Tag applied + ct_tag nvarchar(255) NOT NULL, + -- Parameters for the tag, presently unused + ct_params nvarchar(max) NULL +); + +CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag); +CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag); +CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag); +-- Covering index, so we can pull all the info only out of the index. +CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id); + + +-- Rollup table to pull a LIST of tags simply without ugly GROUP_CONCAT +-- that only works on MySQL 4.1+ +CREATE TABLE /*_*/tag_summary ( + -- RCID for the change + ts_rc_id int NULL REFERENCES /*_*/recentchanges(rc_id), + -- LOGID for the change + ts_log_id int NULL REFERENCES /*_*/logging(log_id), + -- REVID for the change + ts_rev_id int NULL REFERENCES /*_*/revision(rev_id), + -- Comma-separated list of tags + ts_tags nvarchar(max) NOT NULL +); + +CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id); +CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id); +CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id); + + +CREATE TABLE /*_*/valid_tag ( + vt_tag nvarchar(255) NOT NULL PRIMARY KEY +); + +-- Table for storing localisation data +CREATE TABLE /*_*/l10n_cache ( + -- Language code + lc_lang nvarchar(32) NOT NULL, + -- Cache key + lc_key nvarchar(255) NOT NULL, + -- Value + lc_value varbinary(max) NOT NULL +); +CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key); + +-- Table for caching JSON message texts for the resource loader +CREATE TABLE /*_*/msg_resource ( + -- Resource name + mr_resource nvarchar(255) NOT NULL, + -- Language code + mr_lang nvarchar(32) NOT NULL, + -- JSON blob + mr_blob varbinary(max) NOT NULL, + -- Timestamp of last update + mr_timestamp varchar(14) NOT NULL +); +CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang); + +-- Table for administering which message is contained in which resource +CREATE TABLE /*_*/msg_resource_links ( + mrl_resource varbinary(255) NOT NULL, + -- Message key + mrl_message varbinary(255) NOT NULL +); +CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource); + +-- Table caching which local files a module depends on that aren't +-- registered directly, used for fast retrieval of file dependency. +-- Currently only used for tracking images that CSS depends on +CREATE TABLE /*_*/module_deps ( + -- Module name + md_module nvarchar(255) NOT NULL, + -- Skin name + md_skin nvarchar(32) NOT NULL, + -- JSON nvarchar(max) with file dependencies + md_deps nvarchar(max) NOT NULL +); +CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin); + +-- Holds all the sites known to the wiki. +CREATE TABLE /*_*/sites ( + -- Numeric id of the site + site_id int NOT NULL PRIMARY KEY IDENTITY, + + -- Global identifier for the site, ie 'enwiktionary' + site_global_key nvarchar(32) NOT NULL, + + -- Type of the site, ie 'mediawiki' + site_type nvarchar(32) NOT NULL, + + -- Group of the site, ie 'wikipedia' + site_group nvarchar(32) NOT NULL, + + -- Source of the site data, ie 'local', 'wikidata', 'my-magical-repo' + site_source nvarchar(32) NOT NULL, + + -- Language code of the sites primary language. + site_language nvarchar(32) NOT NULL, + + -- Protocol of the site, ie 'http://', 'irc://', '//' + -- This field is an index for lookups and is build from type specific data in site_data. + site_protocol nvarchar(32) NOT NULL, + + -- Domain of the site in reverse order, ie 'org.mediawiki.www.' + -- This field is an index for lookups and is build from type specific data in site_data. + site_domain NVARCHAR(255) NOT NULL, + + -- Type dependent site data. + site_data nvarchar(max) NOT NULL, + + -- If site.tld/path/key:pageTitle should forward users to the page on + -- the actual site, where "key" is the local identifier. + site_forward bit NOT NULL, + + -- Type dependent site config. + -- For instance if template transclusion should be allowed if it's a MediaWiki. + site_config nvarchar(max) NOT NULL +); + +CREATE UNIQUE INDEX /*i*/sites_global_key ON /*_*/sites (site_global_key); +CREATE INDEX /*i*/sites_type ON /*_*/sites (site_type); +CREATE INDEX /*i*/sites_group ON /*_*/sites (site_group); +CREATE INDEX /*i*/sites_source ON /*_*/sites (site_source); +CREATE INDEX /*i*/sites_language ON /*_*/sites (site_language); +CREATE INDEX /*i*/sites_protocol ON /*_*/sites (site_protocol); +CREATE INDEX /*i*/sites_domain ON /*_*/sites (site_domain); +CREATE INDEX /*i*/sites_forward ON /*_*/sites (site_forward); + +-- Links local site identifiers to their corresponding site. +CREATE TABLE /*_*/site_identifiers ( + -- Key on site.site_id + si_site int NOT NULL REFERENCES /*_*/sites(site_id) ON DELETE CASCADE, + + -- local key type, ie 'interwiki' or 'langlink' + si_type nvarchar(32) NOT NULL, + + -- local key value, ie 'en' or 'wiktionary' + si_key nvarchar(32) NOT NULL +); ---- Add the full-text capabilities, depricated in SQL Server 2005, FTS is enabled on all user created tables by default unless you are using SQL Server 2005 Express ---sp_fulltext_database 'enable'; ---sp_fulltext_catalog 'WikiCatalog', 'create' ---sp_fulltext_table ---sp_fulltext_column ---sp_fulltext_table 'Articles', 'activate' +CREATE UNIQUE INDEX /*i*/site_ids_type ON /*_*/site_identifiers (si_type, si_key); +CREATE INDEX /*i*/site_ids_site ON /*_*/site_identifiers (si_site); +CREATE INDEX /*i*/site_ids_key ON /*_*/site_identifiers (si_key); diff --git a/maintenance/mssql/update-keys.sql b/maintenance/mssql/update-keys.sql new file mode 100644 index 00000000..4d2c1c12 --- /dev/null +++ b/maintenance/mssql/update-keys.sql @@ -0,0 +1,31 @@ +-- Update keys for Microsoft SQL Server +-- SQL to insert update keys into the initial tables after a +-- fresh installation of MediaWiki's database. +-- This is read and executed by the install script; you should +-- not have to run it by itself unless doing a manual install. +-- Insert keys here if either the unnecessary would cause heavy +-- processing or could potentially cause trouble by lowering field +-- sizes, adding constraints, etc. +-- When adjusting field sizes, it is recommended removing old +-- patches but to play safe, update keys should also inserted here. + +-- +-- The /*_*/ comments in this and other files are +-- replaced with the defined table prefix by the installer +-- and updater scripts. If you are installing or running +-- updates manually, you will need to manually insert the +-- table prefix if any when running these scripts. +-- + +INSERT INTO /*_*/updatelog + SELECT 'filearchive-fa_major_mime-patch-fa_major_mime-chemical.sql' AS ul_key, null as ul_value + UNION SELECT 'image-img_major_mime-patch-img_major_mime-chemical.sql', null + UNION SELECT 'oldimage-oi_major_mime-patch-oi_major_mime-chemical.sql', null + UNION SELECT 'cl_type-category_types-ck', null + UNION SELECT 'fa_major_mime-major_mime-ck', null + UNION SELECT 'fa_media_type-media_type-ck', null + UNION SELECT 'img_major_mime-major_mime-ck', null + UNION SELECT 'img_media_type-media_type-ck', null + UNION SELECT 'oi_major_mime-major_mime-ck', null + UNION SELECT 'oi_media_type-media_type-ck', null + UNION SELECT 'us_media_type-media_type-ck', null;
\ No newline at end of file diff --git a/maintenance/mwdocgen.php b/maintenance/mwdocgen.php index b22dd885..ee0ff017 100644 --- a/maintenance/mwdocgen.php +++ b/maintenance/mwdocgen.php @@ -152,15 +152,13 @@ You might want to delete the temporary file: --------------------------------------------------- TEXT - ); + ); if ( $exitcode !== 0 ) { $this->error( "Something went wrong (exit: $exitcode)\n", $exitcode ); } - } - } $maintClass = 'MWDocGen'; diff --git a/maintenance/mwjsduck-gen b/maintenance/mwjsduck-gen index bc10bc2c..5247637b 100644 --- a/maintenance/mwjsduck-gen +++ b/maintenance/mwjsduck-gen @@ -1,4 +1,5 @@ #!/usr/bin/env bash +set -e JSDUCK_MWVERSION=master if [[ "$1" == "--version" && "$2" != "" ]] @@ -6,7 +7,8 @@ then JSDUCK_MWVERSION="$2" elif [[ "$*" != "" ]] then - echo "Usage $0: [--version <mediawiki version>]" + FILENAME=$(basename $0) + echo "Usage: $FILENAME [--version <mediawiki version>]" echo exit 1 fi @@ -15,7 +17,9 @@ MWCORE_DIR=$(cd $(dirname $0)/..; pwd) jsduck \ --config=$MWCORE_DIR/maintenance/jsduck/config.json \ ---footer="Documentation for MediaWiki core ($JSDUCK_MWVERSION). Generated on {DATE} by {JSDUCK} {VERSION}." \ -&& echo 'JSDuck execution finished.' +--footer="Documentation for branch ($JSDUCK_MWVERSION) on {DATE} by {JSDUCK} {VERSION}." \ +--processes 0 + +echo 'JSDuck execution finished.' ln -s ../../resources $MWCORE_DIR/docs/js/modules diff --git a/maintenance/namespaceDupes.php b/maintenance/namespaceDupes.php index ff024682..cbc389be 100644 --- a/maintenance/namespaceDupes.php +++ b/maintenance/namespaceDupes.php @@ -3,7 +3,7 @@ * Check for articles to fix after adding/deleting namespaces * * Copyright © 2005-2007 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,16 +44,13 @@ class NamespaceConflictChecker extends Maintenance { $this->mDescription = ""; $this->addOption( 'fix', 'Attempt to automatically fix errors' ); $this->addOption( 'suffix', "Dupes will be renamed with correct namespace with " . - "<text> appended after the article name", false, true ); + "<text> appended after the article name", false, true ); $this->addOption( 'prefix', "Do an explicit check for the given title prefix " . - "appended after the article name", false, true ); + "appended after the article name", false, true ); } public function execute() { - global $wgTitle; - $this->db = wfGetDB( DB_MASTER ); - $wgTitle = Title::newFromText( 'Namespace title conflict cleanup script' ); $fix = $this->hasOption( 'fix' ); $suffix = $this->getOption( 'suffix', '' ); @@ -75,8 +72,8 @@ class NamespaceConflictChecker extends Maintenance { /** * @todo Document - * @param $fix Boolean: whether or not to fix broken entries - * @param $suffix String: suffix to append to renamed articles + * @param bool $fix Whether or not to fix broken entries + * @param string $suffix Suffix to append to renamed articles * * @return bool */ @@ -141,13 +138,14 @@ class NamespaceConflictChecker extends Maintenance { foreach ( $spaces as $name => $ns ) { $ok = $this->checkNamespace( $ns, $name, $fix, $suffix ) && $ok; } + return $ok; } /** * Get the interwiki list * - * @return Array + * @return array */ private function getInterwikiList() { $result = Interwiki::getAllPrefixes(); @@ -155,15 +153,16 @@ class NamespaceConflictChecker extends Maintenance { foreach ( $result as $row ) { $prefixes[] = $row['iw_prefix']; } + return $prefixes; } /** * @todo Document - * @param $ns Integer: a namespace id - * @param $name String - * @param $fix Boolean: whether to fix broken entries - * @param $suffix String: suffix to append to renamed articles + * @param int $ns A namespace id + * @param string $name + * @param bool $fix Whether to fix broken entries + * @param string $suffix Suffix to append to renamed articles * @return bool */ private function checkNamespace( $ns, $name, $fix, $suffix = '' ) { @@ -181,19 +180,21 @@ class NamespaceConflictChecker extends Maintenance { $ok = $this->resolveConflict( $row, $resolvable, $suffix ) && $ok; } } + return $ok; } /** * @todo Do this for real - * @param $key - * @param $prefix - * @param $fix - * @param $suffix string + * @param int $key + * @param string $prefix + * @param bool $fix + * @param string $suffix * @return bool */ private function checkPrefix( $key, $prefix, $fix, $suffix = '' ) { $this->output( "Checking prefix \"$prefix\" vs namespace $key\n" ); + return $this->checkNamespace( $key, $prefix, $fix, $suffix ); } @@ -201,45 +202,42 @@ class NamespaceConflictChecker extends Maintenance { * Find pages in mainspace that have a prefix of the new namespace * so we know titles that will need migrating * - * @param $ns Integer: namespace id (id for new namespace?) - * @param $name String: prefix that is being made a namespace + * @param int $ns Namespace id (id for new namespace?) + * @param string $name Prefix that is being made a namespace * * @return array */ private function getConflicts( $ns, $name ) { - $page = 'page'; - $table = $this->db->tableName( $page ); - - $prefix = $this->db->strencode( $name ); - $encNamespace = $this->db->addQuotes( $ns ); - - $titleSql = "TRIM(LEADING '$prefix:' FROM {$page}_title)"; + $titleSql = "TRIM(LEADING {$this->db->addQuotes( "$name:" )} FROM page_title)"; if ( $ns == 0 ) { // An interwiki; try an alternate encoding with '-' for ':' - $titleSql = $this->db->buildConcat( array( "'$prefix-'", $titleSql ) ); + $titleSql = $this->db->buildConcat( array( + $this->db->addQuotes( "$name-" ), + $titleSql, + ) ); } - $sql = "SELECT {$page}_id AS id, - {$page}_title AS oldtitle, - $encNamespace + {$page}_namespace AS namespace, - $titleSql AS title, - {$page}_namespace AS oldnamespace - FROM {$table} - WHERE ( {$page}_namespace=0 OR {$page}_namespace=1 ) - AND {$page}_title " . $this->db->buildLike( $name . ':', $this->db->anyString() ); - - $result = $this->db->query( $sql, __METHOD__ ); - - $set = array(); - foreach ( $result as $row ) { - $set[] = $row; - } - return $set; + return iterator_to_array( $this->db->select( 'page', + array( + 'id' => 'page_id', + 'oldtitle' => 'page_title', + 'namespace' => $this->db->addQuotes( $ns ) . ' + page_namespace', + 'title' => $titleSql, + 'oldnamespace' => 'page_namespace', + ), + array( + 'page_namespace' => array( 0, 1 ), + 'page_title' . $this->db->buildLike( "$name:", $this->db->anyString() ), + ), + __METHOD__ + ) ); } /** * Report any conflicts we find * + * @param stdClass $row + * @param string $suffix * @return bool */ private function reportConflict( $row, $suffix ) { @@ -252,6 +250,7 @@ class NamespaceConflictChecker extends Maintenance { $row->oldnamespace, $row->oldtitle ) ); $this->output( "... *** cannot resolve automatically; illegal title ***\n" ); + return false; } @@ -266,6 +265,7 @@ class NamespaceConflictChecker extends Maintenance { $id = $newTitle->getArticleID(); if ( $id ) { $this->output( "... *** cannot resolve automatically; page exists with ID $id ***\n" ); + return false; } else { return true; @@ -275,9 +275,9 @@ class NamespaceConflictChecker extends Maintenance { /** * Resolve any conflicts * - * @param $row Object: row from the page table to fix - * @param $resolvable Boolean - * @param $suffix String: suffix to append to the fixed page + * @param stClass $row Row from the page table to fix + * @param bool $resolvable + * @param string $suffix Suffix to append to the fixed page * @return bool */ private function resolveConflict( $row, $resolvable, $suffix ) { @@ -289,6 +289,7 @@ class NamespaceConflictChecker extends Maintenance { $title = Title::makeTitleSafe( $row->namespace, $row->title ); if ( !$title ) { $this->output( "... !!! invalid title\n" ); + return false; } $id = $title->getArticleID(); @@ -301,15 +302,16 @@ class NamespaceConflictChecker extends Maintenance { $this->output( "... *** using suffixed form [[" . $title->getPrefixedText() . "]] ***\n" ); } $this->resolveConflictOn( $row, 'page', 'page' ); + return true; } /** * Resolve a given conflict * - * @param $row Object: row from the old broken entry - * @param $table String: table to update - * @param $prefix String: prefix for column name, like page or ar + * @param stdClass $row Row from the old broken entry + * @param string $table Table to update + * @param string $prefix Prefix for column name, like page or ar * @return bool */ private function resolveConflictOn( $row, $table, $prefix ) { @@ -327,6 +329,7 @@ class NamespaceConflictChecker extends Maintenance { ), __METHOD__ ); $this->output( "ok.\n" ); + return true; } } diff --git a/maintenance/nextJobDB.php b/maintenance/nextJobDB.php deleted file mode 100644 index 219b5d8e..00000000 --- a/maintenance/nextJobDB.php +++ /dev/null @@ -1,119 +0,0 @@ -<?php -/** - * Pick a database that has pending jobs - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup Maintenance - */ - -require_once __DIR__ . '/Maintenance.php'; - -/** - * Maintenance script that picks a database that has pending jobs. - * - * @ingroup Maintenance - */ -class nextJobDB extends Maintenance { - public function __construct() { - parent::__construct(); - $this->mDescription = "Pick a database that has pending jobs"; - $this->addOption( 'type', "Search by job type", false, true ); - $this->addOption( 'types', "Space separated list of job types to search for", false, true ); - } - - public function execute() { - global $wgJobTypesExcludedFromDefaultQueue; - - // job type required/picked - if ( $this->hasOption( 'types' ) ) { - $types = explode( ' ', $this->getOption( 'types' ) ); - } elseif ( $this->hasOption( 'type' ) ) { - $types = array( $this->getOption( 'type' ) ); - } else { - $types = false; - } - - // Handle any required periodic queue maintenance - $this->executeReadyPeriodicTasks(); - - // Get all the queues with jobs in them - $pendingDBs = JobQueueAggregator::singleton()->getAllReadyWikiQueues(); - if ( !count( $pendingDBs ) ) { - return; // no DBs with jobs or cache is both empty and locked - } - - do { - $again = false; - - $candidates = array(); // list of (type, db) - // Flatten the tree of candidates into a flat list so that a random - // item can be selected, weighing each queue (type/db tuple) equally. - foreach ( $pendingDBs as $type => $dbs ) { - if ( - ( is_array( $types ) && in_array( $type, $types ) ) || - ( $types === false && !in_array( $type, $wgJobTypesExcludedFromDefaultQueue ) ) - ) { - foreach ( $dbs as $db ) { - $candidates[] = array( $type, $db ); - } - } - } - if ( !count( $candidates ) ) { - return; // no jobs for this type - } - - list( $type, $db ) = $candidates[mt_rand( 0, count( $candidates ) - 1 )]; - if ( JobQueueGroup::singleton( $db )->isQueueDeprioritized( $type ) ) { - $pendingDBs[$type] = array_diff( $pendingDBs[$type], array( $db ) ); - $again = true; - } - } while ( $again ); - - if ( $this->hasOption( 'types' ) ) { - $this->output( $db . " " . $type . "\n" ); - } else { - $this->output( $db . "\n" ); - } - } - - /** - * Do all ready periodic jobs for all databases every 5 minutes (and .1% of the time) - * @return integer - */ - private function executeReadyPeriodicTasks() { - global $wgLocalDatabases, $wgMemc; - - $count = 0; - $memcKey = 'jobqueue:periodic:lasttime'; - $timestamp = (int)$wgMemc->get( $memcKey ); // UNIX timestamp or 0 - if ( ( time() - $timestamp ) > 300 || mt_rand( 0, 999 ) == 0 ) { // 5 minutes - if ( $wgMemc->add( "$memcKey:rebuild", 1, 1800 ) ) { // lock - foreach ( $wgLocalDatabases as $db ) { - $count += JobQueueGroup::singleton( $db )->executeReadyPeriodicTasks(); - } - $wgMemc->set( $memcKey, time() ); - $wgMemc->delete( "$memcKey:rebuild" ); // unlock - } - } - - return $count; - } -} - -$maintClass = "nextJobDb"; -require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/nukeNS.php b/maintenance/nukeNS.php index 479dcf76..64bf1b6f 100644 --- a/maintenance/nukeNS.php +++ b/maintenance/nukeNS.php @@ -91,7 +91,7 @@ class NukeNS extends Maintenance { $child = $this->runChild( 'NukePage', 'nukePage.php' ); $child->deleteRevisions( $revs ); $this->purgeRedundantText( true ); - $n_deleted ++; + $n_deleted++; } } else { $this->output( "skip: " . $title->getPrefixedText() . "\n" ); diff --git a/maintenance/oracle/alterSharedConstraints.php b/maintenance/oracle/alterSharedConstraints.php index 435625d5..eea6f7b1 100644 --- a/maintenance/oracle/alterSharedConstraints.php +++ b/maintenance/oracle/alterSharedConstraints.php @@ -44,6 +44,7 @@ class AlterSharedConstraints extends Maintenance { if ( $wgSharedDB == null ) { $this->output( "Database sharing is not enabled\n" ); + return; } @@ -56,35 +57,38 @@ class AlterSharedConstraints extends Maintenance { $ltable = "{$wgDBprefix}{$stable}"; } - $result = $dbw->query( "SELECT uc.constraint_name, uc.table_name, ucc.column_name, uccpk.table_name pk_table_name, uccpk.column_name pk_column_name, uc.delete_rule, uc.deferrable, uc.deferred - FROM user_constraints uc, user_cons_columns ucc, user_cons_columns uccpk - WHERE uc.constraint_type = 'R' - AND ucc.constraint_name = uc.constraint_name - AND uccpk.constraint_name = uc.r_constraint_name - AND uccpk.table_name = '$ltable'" ); + $result = $dbw->query( "SELECT uc.constraint_name, uc.table_name, ucc.column_name, + uccpk.table_name pk_table_name, uccpk.column_name pk_column_name, + uc.delete_rule, uc.deferrable, uc.deferred + FROM user_constraints uc, user_cons_columns ucc, user_cons_columns uccpk + WHERE uc.constraint_type = 'R' + AND ucc.constraint_name = uc.constraint_name + AND uccpk.constraint_name = uc.r_constraint_name + AND uccpk.table_name = '$ltable'" ); while ( ( $row = $result->fetchRow() ) !== false ) { - $this->output( "Altering {$row['constraint_name']} ..." ); + $this->output( "Altering {$row['constraint_name']} ..." ); - try { - $dbw->query( "ALTER TABLE {$row['table_name']} DROP CONSTRAINT {$wgDBprefix}{$row['constraint_name']}" ); - } catch ( DBQueryError $exdb ) { - if ( $exdb->errno != 2443 ) { - throw $exdb; - } + try { + $dbw->query( "ALTER TABLE {$row['table_name']} + DROP CONSTRAINT {$wgDBprefix}{$row['constraint_name']}" ); + } catch ( DBQueryError $exdb ) { + if ( $exdb->errno != 2443 ) { + throw $exdb; } + } - $deleteRule = $row['delete_rule'] == 'NO ACTION' ? '' : "ON DELETE {$row['delete_rule']}"; - $dbw->query( "ALTER TABLE {$row['table_name']} ADD CONSTRAINT {$wgDBprefix}{$row['constraint_name']} + $deleteRule = $row['delete_rule'] == 'NO ACTION' ? '' : "ON DELETE {$row['delete_rule']}"; + $dbw->query( "ALTER TABLE {$row['table_name']} + ADD CONSTRAINT {$wgDBprefix}{$row['constraint_name']} FOREIGN KEY ({$row['column_name']}) REFERENCES {$wgSharedDB}.$stable({$row['pk_column_name']}) {$deleteRule} {$row['deferrable']} INITIALLY {$row['deferred']}" ); - $this->output( "DONE\n" ); + $this->output( "DONE\n" ); } } } - } $maintClass = "AlterSharedConstraints"; diff --git a/maintenance/oracle/archives/patch-logging_user_text_time_index.sql b/maintenance/oracle/archives/patch-logging_user_text_time_index.sql new file mode 100644 index 00000000..e04abf5f --- /dev/null +++ b/maintenance/oracle/archives/patch-logging_user_text_time_index.sql @@ -0,0 +1,4 @@ +define mw_prefix='{$wgDBprefix}'; + +CREATE INDEX &mw_prefix.logging_i07 ON &mw_prefix.logging (log_user_text, log_timestamp); + diff --git a/maintenance/oracle/archives/patch-logging_user_text_type_time_index.sql b/maintenance/oracle/archives/patch-logging_user_text_type_time_index.sql new file mode 100644 index 00000000..c1c0d4f2 --- /dev/null +++ b/maintenance/oracle/archives/patch-logging_user_text_type_time_index.sql @@ -0,0 +1,4 @@ +define mw_prefix='{$wgDBprefix}'; + +CREATE INDEX &mw_prefix.logging_i06 ON &mw_prefix.logging (log_user_text, log_type, log_timestamp); + diff --git a/maintenance/oracle/archives/patch-page-page_lang.sql b/maintenance/oracle/archives/patch-page-page_lang.sql new file mode 100644 index 00000000..cae7cf90 --- /dev/null +++ b/maintenance/oracle/archives/patch-page-page_lang.sql @@ -0,0 +1,3 @@ +define mw_prefix='{$wgDBprefix}'; + +ALTER TABLE &mw_prefix.page ADD page_lang VARCHAR2(35); diff --git a/maintenance/oracle/archives/patch-page_links_updated.sql b/maintenance/oracle/archives/patch-page_links_updated.sql new file mode 100644 index 00000000..53603294 --- /dev/null +++ b/maintenance/oracle/archives/patch-page_links_updated.sql @@ -0,0 +1,4 @@ +define mw_prefix='{$wgDBprefix}'; + +ALTER TABLE &mw_prefix.page ADD page_links_updated TIMESTAMP(6) WITH TIME ZONE; + diff --git a/maintenance/oracle/archives/patch-rc_source.sql b/maintenance/oracle/archives/patch-rc_source.sql new file mode 100644 index 00000000..0c80afab --- /dev/null +++ b/maintenance/oracle/archives/patch-rc_source.sql @@ -0,0 +1,3 @@ +define mw_prefix='{$wgDBprefix}'; + +ALTER TABLE &mw_prefix.recentchanges ADD rc_source VARCHAR2(16); diff --git a/maintenance/oracle/archives/patch-user_password_expire.sql b/maintenance/oracle/archives/patch-user_password_expire.sql new file mode 100644 index 00000000..824cc820 --- /dev/null +++ b/maintenance/oracle/archives/patch-user_password_expire.sql @@ -0,0 +1,3 @@ +define mw_prefix='{$wgDBprefix}'; + +ALTER TABLE &mw_prefix.mwuser ADD user_password_expires TIMESTAMP(6) WITH TIME ZONE; diff --git a/maintenance/oracle/archives/patch_16_17_schema_changes.sql b/maintenance/oracle/archives/patch_16_17_schema_changes.sql index 64c28481..cd99f7cc 100644 --- a/maintenance/oracle/archives/patch_16_17_schema_changes.sql +++ b/maintenance/oracle/archives/patch_16_17_schema_changes.sql @@ -67,7 +67,7 @@ CREATE TABLE &mw_prefix.msg_resource ( mr_lang varchar2(32) NOT NULL, mr_blob BLOB NOT NULL, mr_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL -) ; +); CREATE UNIQUE INDEX &mw_prefix.msg_resource_u01 ON &mw_prefix.msg_resource (mr_resource, mr_lang); ALTER TABLE &mw_prefix.oldimage MODIFY oi_name DEFAULT 0; diff --git a/maintenance/oracle/tables.sql b/maintenance/oracle/tables.sql index acfabc33..36be16e4 100644 --- a/maintenance/oracle/tables.sql +++ b/maintenance/oracle/tables.sql @@ -2,7 +2,7 @@ define mw_prefix='{$wgDBprefix}'; -CREATE SEQUENCE user_user_id_seq MINVALUE 0 START WITH 0; +CREATE SEQUENCE user_user_id_seq; CREATE TABLE &mw_prefix.mwuser ( -- replace reserved word 'user' user_id NUMBER NOT NULL, user_name VARCHAR2(255) NOT NULL, @@ -18,7 +18,8 @@ CREATE TABLE &mw_prefix.mwuser ( -- replace reserved word 'user' user_options CLOB, user_touched TIMESTAMP(6) WITH TIME ZONE, user_registration TIMESTAMP(6) WITH TIME ZONE, - user_editcount NUMBER + user_editcount NUMBER, + user_password_expires TIMESTAMP(6) WITH TIME ZONE ); ALTER TABLE &mw_prefix.mwuser ADD CONSTRAINT &mw_prefix.mwuser_pk PRIMARY KEY (user_id); CREATE UNIQUE INDEX &mw_prefix.mwuser_u01 ON &mw_prefix.mwuser (user_name); @@ -27,7 +28,8 @@ CREATE INDEX &mw_prefix.mwuser_i02 ON &mw_prefix.mwuser (user_email, user_name); -- Create a dummy user to satisfy fk contraints especially with revisions INSERT INTO &mw_prefix.mwuser - VALUES (user_user_id_seq.nextval,'Anonymous',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, '', current_timestamp, current_timestamp, 0); + (user_id, user_name, user_options, user_touched, user_registration, user_editcount) + VALUES (0,'Anonymous','', current_timestamp, current_timestamp,0); CREATE TABLE &mw_prefix.user_groups ( ug_user NUMBER DEFAULT 0 NOT NULL, @@ -72,9 +74,11 @@ CREATE TABLE &mw_prefix.page ( page_is_new CHAR(1) DEFAULT '0' NOT NULL, page_random NUMBER(15,14) NOT NULL, page_touched TIMESTAMP(6) WITH TIME ZONE, + page_links_updated TIMESTAMP(6) WITH TIME ZONE, page_latest NUMBER DEFAULT 0 NOT NULL, -- FK? page_len NUMBER DEFAULT 0 NOT NULL, - page_content_model VARCHAR2(32) + page_content_model VARCHAR2(32), + page_lang VARCHAR2(35) DEFAULT NULL ); ALTER TABLE &mw_prefix.page ADD CONSTRAINT &mw_prefix.page_pk PRIMARY KEY (page_id); CREATE UNIQUE INDEX &mw_prefix.page_u01 ON &mw_prefix.page (page_namespace,page_title); @@ -84,7 +88,7 @@ CREATE INDEX &mw_prefix.page_i03 ON &mw_prefix.page (page_is_redirect, page_name -- Create a dummy page to satisfy fk contraints especially with revisions INSERT INTO &mw_prefix.page - VALUES (0, 0, ' ', NULL, 0, 0, 0, 0, current_timestamp, 0, 0, NULL); + VALUES (0, 0, ' ', NULL, 0, 0, 0, 0, current_timestamp, NULL, 0, 0, NULL, NULL); /*$mw$*/ CREATE TRIGGER &mw_prefix.page_set_random BEFORE INSERT ON &mw_prefix.page @@ -402,7 +406,7 @@ CREATE SEQUENCE recentchanges_rc_id_seq; CREATE TABLE &mw_prefix.recentchanges ( rc_id NUMBER NOT NULL, rc_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL, - rc_cur_time TIMESTAMP(6) WITH TIME ZONE NOT NULL, + rc_cur_time TIMESTAMP(6) WITH TIME ZONE, rc_user NUMBER DEFAULT 0 NOT NULL, rc_user_text VARCHAR2(255) NOT NULL, rc_namespace NUMBER DEFAULT 0 NOT NULL, @@ -415,6 +419,7 @@ CREATE TABLE &mw_prefix.recentchanges ( rc_this_oldid NUMBER DEFAULT 0 NOT NULL, rc_last_oldid NUMBER DEFAULT 0 NOT NULL, rc_type CHAR(1) DEFAULT '0' NOT NULL, + rc_source VARCHAR2(16), rc_patrolled CHAR(1) DEFAULT '0' NOT NULL, rc_ip VARCHAR2(15), rc_old_len NUMBER, @@ -509,6 +514,8 @@ CREATE INDEX &mw_prefix.logging_i02 ON &mw_prefix.logging (log_user, log_timesta CREATE INDEX &mw_prefix.logging_i03 ON &mw_prefix.logging (log_namespace, log_title, log_timestamp); CREATE INDEX &mw_prefix.logging_i04 ON &mw_prefix.logging (log_timestamp); CREATE INDEX &mw_prefix.logging_i05 ON &mw_prefix.logging (log_type, log_action, log_timestamp); +CREATE INDEX &mw_prefix.logging_i06 ON &mw_prefix.logging (log_user_text, log_type, log_timestamp); +CREATE INDEX &mw_prefix.logging_i07 ON &mw_prefix.logging (log_user_text, log_timestamp); CREATE TABLE &mw_prefix.log_search ( ls_field VARCHAR2(32) NOT NULL, @@ -663,7 +670,7 @@ CREATE TABLE &mw_prefix.msg_resource ( mr_lang varchar2(32) NOT NULL, mr_blob BLOB NOT NULL, mr_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL -) ; +); CREATE UNIQUE INDEX &mw_prefix.msg_resource_u01 ON &mw_prefix.msg_resource (mr_resource, mr_lang); CREATE TABLE &mw_prefix.msg_resource_links ( diff --git a/maintenance/oracle/update-keys.sql b/maintenance/oracle/update-keys.sql new file mode 100644 index 00000000..7761d0c5 --- /dev/null +++ b/maintenance/oracle/update-keys.sql @@ -0,0 +1,29 @@ +-- SQL to insert update keys into the initial tables after a +-- fresh installation of MediaWiki's database. +-- This is read and executed by the install script; you should +-- not have to run it by itself unless doing a manual install. +-- Insert keys here if either the unnecessary would cause heavy +-- processing or could potentially cause trouble by lowering field +-- sizes, adding constraints, etc. +-- When adjusting field sizes, it is recommended removing old +-- patches but to play safe, update keys should also inserted here. + +-- The /*_*/ comments in this and other files are +-- replaced with the defined table prefix by the installer +-- and updater scripts. If you are installing or running +-- updates manually, you will need to manually insert the +-- table prefix if any when running these scripts. +-- + +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'filearchive-fa_major_mime-patch-fa_major_mime-chemical.sql', null ); +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'image-img_major_mime-patch-img_major_mime-chemical.sql', null ); +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'oldimage-oi_major_mime-patch-oi_major_mime-chemical.sql', null ); +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'user_groups-ug_group-patch-ug_group-length-increase-255.sql', null ); +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'user_former_groups-ufg_group-patch-ufg_group-length-increase-255.sql', null ); +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'user_properties-up_property-patch-up_property.sql', null ); diff --git a/maintenance/orphans.php b/maintenance/orphans.php index b4d255ab..7e27107a 100644 --- a/maintenance/orphans.php +++ b/maintenance/orphans.php @@ -6,7 +6,7 @@ * Man this is depressing. * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,15 +40,13 @@ class Orphans extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Look for 'orphan' revisions hooked to pages which don't exist\n" . - "and 'childless' pages with no revisions\n" . - "Then, kill the poor widows and orphans\n" . - "Man this is depressing"; + "and 'childless' pages with no revisions\n" . + "Then, kill the poor widows and orphans\n" . + "Man this is depressing"; $this->addOption( 'fix', 'Actually fix broken entries' ); } public function execute() { - global $wgTitle; - $wgTitle = Title::newFromText( 'Orphan revision cleanup script' ); $this->checkOrphans( $this->hasOption( 'fix' ) ); $this->checkSeparation( $this->hasOption( 'fix' ) ); # Does not work yet, do not use @@ -57,8 +55,8 @@ class Orphans extends Maintenance { /** * Lock the appropriate tables for the script - * @param $db DatabaseBase object - * @param $extraTable String The name of any extra tables to lock (eg: text) + * @param DatabaseBase $db + * @param string $extraTable The name of any extra tables to lock (eg: text) */ private function lockTables( $db, $extraTable = array() ) { $tbls = array( 'page', 'revision', 'redirect' ); @@ -70,7 +68,7 @@ class Orphans extends Maintenance { /** * Check for orphan revisions - * @param $fix bool Whether to fix broken revisions when found + * @param bool $fix Whether to fix broken revisions when found */ private function checkOrphans( $fix ) { $dbw = wfGetDB( DB_MASTER ); @@ -81,7 +79,8 @@ class Orphans extends Maintenance { $this->lockTables( $dbw ); } - $this->output( "Checking for orphan revision table entries... (this may take a while on a large wiki)\n" ); + $this->output( "Checking for orphan revision table entries... " + . "(this may take a while on a large wiki)\n" ); $result = $dbw->query( " SELECT * FROM $revision LEFT OUTER JOIN $page ON rev_page=page_id @@ -90,8 +89,13 @@ class Orphans extends Maintenance { $orphans = $result->numRows(); if ( $orphans > 0 ) { global $wgContLang; + $this->output( "$orphans orphan revisions...\n" ); - $this->output( sprintf( "%10s %10s %14s %20s %s\n", 'rev_id', 'rev_page', 'rev_timestamp', 'rev_user_text', 'rev_comment' ) ); + $this->output( sprintf( + "%10s %10s %14s %20s %s\n", + 'rev_id', 'rev_page', 'rev_timestamp', 'rev_user_text', 'rev_comment' + ) ); + foreach ( $result as $row ) { $comment = ( $row->rev_comment == '' ) ? '' @@ -119,7 +123,7 @@ class Orphans extends Maintenance { } /** - * @param $fix bool + * @param bool $fix * @todo DON'T USE THIS YET! It will remove entries which have children, * but which aren't properly attached (eg if page_latest is bogus * but valid revisions do exist) @@ -133,7 +137,8 @@ class Orphans extends Maintenance { $this->lockTables( $dbw ); } - $this->output( "\nChecking for childless page table entries... (this may take a while on a large wiki)\n" ); + $this->output( "\nChecking for childless page table entries... " + . "(this may take a while on a large wiki)\n" ); $result = $dbw->query( " SELECT * FROM $page LEFT OUTER JOIN $revision ON page_latest=rev_id @@ -167,7 +172,7 @@ class Orphans extends Maintenance { /** * Check for pages where page_latest is wrong - * @param $fix bool Whether to fix broken entries + * @param bool $fix Whether to fix broken entries */ private function checkSeparation( $fix ) { $dbw = wfGetDB( DB_MASTER ); @@ -178,7 +183,8 @@ class Orphans extends Maintenance { $this->lockTables( $dbw, array( 'user', 'text' ) ); } - $this->output( "\nChecking for pages whose page_latest links are incorrect... (this may take a while on a large wiki)\n" ); + $this->output( "\nChecking for pages whose page_latest links are incorrect... " + . "(this may take a while on a large wiki)\n" ); $result = $dbw->query( " SELECT * FROM $page LEFT OUTER JOIN $revision ON page_latest=rev_id diff --git a/maintenance/pageExists.php b/maintenance/pageExists.php new file mode 100644 index 00000000..3bde81ef --- /dev/null +++ b/maintenance/pageExists.php @@ -0,0 +1,54 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + */ + +require_once __DIR__ . '/Maintenance.php'; + +/** + * @ingroup Maintenance + */ +class PageExists extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Report whether a specific page exists"; + $this->addArg( 'title', 'Page title to check whether it exists' ); + } + + public function execute() { + $titleArg = $this->getArg(); + $title = Title::newFromText( $titleArg ); + $pageExists = $title && $title->exists(); + + $text = ''; + $code = 0; + if ( $pageExists ) { + $text = "{$title} exists."; + } else { + $text = "{$titleArg} doesn't exist."; + $code = 1; + } + $this->output( $text ); + $this->error( '', $code ); + } +} + +$maintClass = "PageExists"; +require_once RUN_MAINTENANCE_IF_MAIN; + diff --git a/maintenance/parse.php b/maintenance/parse.php index 3ac7a281..638d7c5b 100644 --- a/maintenance/parse.php +++ b/maintenance/parse.php @@ -62,7 +62,12 @@ class CLIParser extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Parse a given wikitext"; - $this->addOption( 'title', 'Title name for the given wikitext (Default: \'CLIParser\')', false, true ); + $this->addOption( + 'title', + 'Title name for the given wikitext (Default: \'CLIParser\')', + false, + true + ); $this->addArg( 'file', 'File containing wikitext (Default: stdin)', false ); } @@ -90,7 +95,8 @@ class CLIParser extends Maintenance { if ( $input_file === $php_stdin ) { $ctrl = wfIsWindows() ? 'CTRL+Z' : 'CTRL+D'; - $this->error( basename( __FILE__ ) . ": warning: reading wikitext from STDIN. Press $ctrl to parse.\n" ); + $this->error( basename( __FILE__ ) + . ": warning: reading wikitext from STDIN. Press $ctrl to parse.\n" ); } return file_get_contents( $input_file ); @@ -107,13 +113,13 @@ class CLIParser extends Maintenance { * Default title is 'CLIParser', it can be overriden with the option * --title <Your:Title> * - * @return Title object + * @return Title */ protected function getTitle() { - $title = - $this->getOption( 'title' ) + $title = $this->getOption( 'title' ) ? $this->getOption( 'title' ) : 'CLIParser'; + return Title::newFromText( $title ); } @@ -123,9 +129,9 @@ class CLIParser extends Maintenance { */ protected function parse( $wikitext ) { return $this->parser->parse( - $wikitext - , $this->getTitle() - , new ParserOptions() + $wikitext, + $this->getTitle(), + new ParserOptions() ); } } diff --git a/maintenance/patchSql.php b/maintenance/patchSql.php index 31ce1566..5d9fc1b4 100644 --- a/maintenance/patchSql.php +++ b/maintenance/patchSql.php @@ -33,7 +33,10 @@ class PatchSql extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Run an SQL file into the DB, replacing prefix and charset vars"; - $this->addArg( 'patch-name', 'Name of the patch file, either full path or in maintenance/archives' ); + $this->addArg( + 'patch-name', + 'Name of the patch file, either full path or in maintenance/archives' + ); } public function getDbType() { diff --git a/maintenance/populateBacklinkNamespace.php b/maintenance/populateBacklinkNamespace.php new file mode 100644 index 00000000..054f7921 --- /dev/null +++ b/maintenance/populateBacklinkNamespace.php @@ -0,0 +1,97 @@ +<?php +/** + * Optional upgrade script to populate *_from_namespace fields + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + */ + +require_once __DIR__ . '/Maintenance.php'; + +/** + * Maintenance script to populate *_from_namespace fields + * + * @ingroup Maintenance + */ +class PopulateBacklinkNamespace extends LoggedUpdateMaintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Populate the *_from_namespace fields"; + $this->addOption( 'lastUpdatedId', "Highest page_id with updated links", false, true ); + } + + protected function getUpdateKey() { + return 'populate *_from_namespace'; + } + + protected function updateSkippedMessage() { + return '*_from_namespace column of backlink tables already populated.'; + } + + public function doDBUpdates() { + $force = $this->getOption( 'force' ); + + $db = $this->getDB( DB_MASTER ); + + $this->output( "Updating *_from_namespace fields in links tables.\n" ); + + $start = $this->getOption( 'lastUpdatedId' ); + if ( !$start ) { + $start = $db->selectField( 'page', 'MIN(page_id)', false, __METHOD__ ); + } + if ( !$start ) { + $this->output( "Nothing to do." ); + return false; + } + $end = $db->selectField( 'page', 'MAX(page_id)', false, __METHOD__ ); + + # Do remaining chunk + $end += $this->mBatchSize - 1; + $blockStart = $start; + $blockEnd = $start + $this->mBatchSize - 1; + while ( $blockEnd <= $end ) { + $this->output( "...doing page_id from $blockStart to $blockEnd\n" ); + $cond = "page_id BETWEEN $blockStart AND $blockEnd"; + $res = $db->select( 'page', array( 'page_id', 'page_namespace' ), $cond, __METHOD__ ); + foreach ( $res as $row ) { + $db->update( 'pagelinks', + array( 'pl_from_namespace' => $row->page_namespace ), + array( 'pl_from' => $row->page_id ), + __METHOD__ + ); + $db->update( 'templatelinks', + array( 'tl_from_namespace' => $row->page_namespace ), + array( 'tl_from' => $row->page_id ), + __METHOD__ + ); + $db->update( 'imagelinks', + array( 'il_from_namespace' => $row->page_namespace ), + array( 'il_from' => $row->page_id ), + __METHOD__ + ); + } + $blockStart += $this->mBatchSize - 1; + $blockEnd += $this->mBatchSize - 1; + wfWaitForSlaves(); + } + return true; + } +} + +$maintClass = "PopulateBacklinkNamespace"; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/populateBloomCache.php b/maintenance/populateBloomCache.php new file mode 100644 index 00000000..40ad5fc6 --- /dev/null +++ b/maintenance/populateBloomCache.php @@ -0,0 +1,78 @@ +<?php +/** + * Script to populate a bloom filter with a BloomFilter* class + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + */ + +require_once __DIR__ . '/Maintenance.php'; + +/** + * Script to populate a bloom filter with a BloomFilter* class + * + * @ingroup Maintenance + */ +class PopulateBloomFilter extends Maintenance { + public function __construct() { + parent::__construct(); + $this->addOption( 'cache', 'Bloom cache store name', true, true ); + $this->addOption( 'filter', 'Bloom filter name', true, true ); + $this->addOption( 'domain', 'Bloom filter domain', true, true ); + $this->addOption( 'delay', 'Sleep delay between batches (us)', false, true ); + $this->mDescription = "Populate the specified bloom filter"; + } + + public function execute() { + $type = $this->getOption( 'filter' ); + $domain = $this->getOption( 'domain' ); + $bcache = BloomCache::get( $this->getOption( 'cache' ) ); + $delay = $this->getOption( 'delay', 1e5 ); + + if ( !method_exists( "BloomFilter{$type}", 'merge' ) ) { + $this->error( "No \"BloomFilter{$type}::merge\" method found.", 1 ); + } + + $virtualKey = "$domain:$type"; + $status = $bcache->getStatus( $virtualKey ); + if ( $status == false ) { + $this->error( "Could not query virtual bloom filter '$virtualKey'.", 1 ); + } + + $startTime = microtime( true ); + $this->output( "Current timestamp is '$startTime'.\n" ); + $this->output( "Current filter timestamp is '{$status['asOfTime']}'.\n" ); + + do { + $status = call_user_func_array( + array( "BloomFilter{$type}", 'merge' ), + array( $bcache, $domain, $virtualKey, $status ) + ); + if ( $status == false ) { + $this->error( "Could not query virtual bloom filter '$virtualKey'.", 1 ); + } + $this->output( "Filter updated to timestamp '{$status['asOfTime']}'.\n" ); + usleep( $delay ); + } while ( $status['asOfTime'] && $status['asOfTime'] < $startTime ); + + $this->output( "Done, filter $type of domain $domain reached time '$startTime'.\n" ); + } +} + +$maintClass = "PopulateBloomFilter"; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/populateCategory.php b/maintenance/populateCategory.php index 4c8cdaa1..ab0ca1ed 100644 --- a/maintenance/populateCategory.php +++ b/maintenance/populateCategory.php @@ -51,9 +51,24 @@ When the script has finished, it will make a note of this in the database, and will not run again without the --force option. TEXT; # ' - $this->addOption( 'begin', 'Only do categories whose names are alphabetically after the provided name', false, true ); - $this->addOption( 'max-slave-lag', 'If slave lag exceeds this many seconds, wait until it drops before continuing. Default: 10', false, true ); - $this->addOption( 'throttle', 'Wait this many milliseconds after each category. Default: 0', false, true ); + $this->addOption( + 'begin', + 'Only do categories whose names are alphabetically after the provided name', + false, + true + ); + $this->addOption( + 'max-slave-lag', + 'If slave lag exceeds this many seconds, wait until it drops before continuing. Default: 10', + false, + true + ); + $this->addOption( + 'throttle', + 'Wait this many milliseconds after each category. Default: 0', + false, + true + ); $this->addOption( 'force', 'Run regardless of whether the database says it\'s been run already' ); } @@ -77,8 +92,9 @@ TEXT; ); if ( $row ) { $this->output( "Category table already populated. Use php " . - "maintenance/populateCategory.php\n--force from the command line " . - "to override.\n" ); + "maintenance/populateCategory.php\n--force from the command line " . + "to override.\n" ); + return true; } } @@ -126,16 +142,17 @@ TEXT; } if ( $dbw->insert( - 'updatelog', - array( 'ul_key' => 'populate category' ), - __METHOD__, - 'IGNORE' - ) - ) { + 'updatelog', + array( 'ul_key' => 'populate category' ), + __METHOD__, + 'IGNORE' + ) ) { $this->output( "Category population complete.\n" ); + return true; } else { $this->output( "Could not insert category population row.\n" ); + return false; } } diff --git a/maintenance/populateFilearchiveSha1.php b/maintenance/populateFilearchiveSha1.php index c579d4fc..850a5a5a 100644 --- a/maintenance/populateFilearchiveSha1.php +++ b/maintenance/populateFilearchiveSha1.php @@ -51,6 +51,7 @@ class PopulateFilearchiveSha1 extends LoggedUpdateMaintenance { if ( !$dbw->fieldExists( $table, 'fa_sha1', __METHOD__ ) ) { $this->output( "fa_sha1 column does not exist\n\n", true ); + return false; } diff --git a/maintenance/populateImageSha1.php b/maintenance/populateImageSha1.php index 126d22d9..e9123aa3 100644 --- a/maintenance/populateImageSha1.php +++ b/maintenance/populateImageSha1.php @@ -33,9 +33,15 @@ class PopulateImageSha1 extends LoggedUpdateMaintenance { parent::__construct(); $this->mDescription = "Populate the img_sha1 field"; $this->addOption( 'force', "Recalculate sha1 for rows that already have a value" ); + $this->addOption( 'multiversiononly', "Calculate only for files with several versions" ); $this->addOption( 'method', "Use 'pipe' to pipe to mysql command line,\n" . "\t\tdefault uses Database class", false, true ); - $this->addOption( 'file', 'Fix for a specific file, without File: namespace prefixed', false, true ); + $this->addOption( + 'file', + 'Fix for a specific file, without File: namespace prefixed', + false, + true + ); } protected function getUpdateKey() { @@ -47,7 +53,7 @@ class PopulateImageSha1 extends LoggedUpdateMaintenance { } public function execute() { - if ( $this->getOption( 'file' ) ) { + if ( $this->getOption( 'file' ) || $this->hasOption( 'multiversiononly' ) ) { $this->doDBUpdates(); // skip update log checks/saves } else { parent::execute(); @@ -71,18 +77,27 @@ class PopulateImageSha1 extends LoggedUpdateMaintenance { ); if ( !$res ) { $this->error( "No such file: $file", true ); + return false; } $this->output( "Populating img_sha1 field for specified files\n" ); } else { - if ( $force ) { + if ( $this->hasOption( 'multiversiononly' ) ) { + $conds = array(); + $this->output( "Populating and recalculating img_sha1 field for versioned files\n" ); + } elseif ( $force ) { $conds = array(); $this->output( "Populating and recalculating img_sha1 field\n" ); } else { $conds = array( 'img_sha1' => '' ); $this->output( "Populating img_sha1 field\n" ); } - $res = $dbw->select( 'image', array( 'img_name' ), $conds, __METHOD__ ); + if ( $this->hasOption( 'multiversiononly' ) ) { + $res = $dbw->select( 'oldimage', + array( 'img_name' => 'DISTINCT(oi_name)' ), $conds, __METHOD__ ); + } else { + $res = $dbw->select( 'image', array( 'img_name' ), $conds, __METHOD__ ); + } } $imageTable = $dbw->tableName( 'image' ); @@ -109,10 +124,12 @@ class PopulateImageSha1 extends LoggedUpdateMaintenance { "Done %d of %d, %5.3f%% \r", $i, $numRows, $i / $numRows * 100 ) ); wfWaitForSlaves(); } + $file = wfLocalFile( $row->img_name ); if ( !$file ) { continue; } + // Upgrade the current file version... $sha1 = $file->getRepo()->getFileSha1( $file->getPath() ); if ( strval( $sha1 ) !== '' ) { // file on disk and hashed properly diff --git a/maintenance/populateLogSearch.php b/maintenance/populateLogSearch.php index d65635e5..4c1a72e8 100644 --- a/maintenance/populateLogSearch.php +++ b/maintenance/populateLogSearch.php @@ -31,7 +31,12 @@ require_once __DIR__ . '/Maintenance.php'; * @ingroup Maintenance */ class PopulateLogSearch extends LoggedUpdateMaintenance { - static $tableMap = array( 'rev' => 'revision', 'fa' => 'filearchive', 'oi' => 'oldimage', 'ar' => 'archive' ); + private static $tableMap = array( + 'rev' => 'revision', + 'fa' => 'filearchive', + 'oi' => 'oldimage', + 'ar' => 'archive' + ); public function __construct() { parent::__construct(); @@ -51,11 +56,13 @@ class PopulateLogSearch extends LoggedUpdateMaintenance { $db = $this->getDB( DB_MASTER ); if ( !$db->tableExists( 'log_search' ) ) { $this->error( "log_search does not exist" ); + return false; } $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__ ); @@ -121,8 +128,8 @@ class PopulateLogSearch extends LoggedUpdateMaintenance { // Add item author relations... $log->addRelations( 'target_author_id', $userIds, $row->log_id ); $log->addRelations( 'target_author_ip', $userIPs, $row->log_id ); - // RevisionDelete logs - log events } elseif ( LogEventsList::typeAction( $row, $delTypes, 'event' ) ) { + // RevisionDelete logs - log events $params = LogPage::extractParams( $row->log_params ); // Param format: <item CSV> [<ofield> <nfield>] if ( count( $params ) < 1 ) { @@ -154,6 +161,7 @@ class PopulateLogSearch extends LoggedUpdateMaintenance { wfWaitForSlaves(); } $this->output( "Done populating log_search table.\n" ); + return true; } } diff --git a/maintenance/populateLogUsertext.php b/maintenance/populateLogUsertext.php index e579e522..96cb1ec9 100644 --- a/maintenance/populateLogUsertext.php +++ b/maintenance/populateLogUsertext.php @@ -52,6 +52,7 @@ class PopulateLogUsertext extends LoggedUpdateMaintenance { $start = $db->selectField( 'logging', 'MIN(log_id)', false, __METHOD__ ); if ( !$start ) { $this->output( "Nothing to do.\n" ); + return true; } $end = $db->selectField( 'logging', 'MAX(log_id)', false, __METHOD__ ); @@ -77,6 +78,7 @@ class PopulateLogUsertext extends LoggedUpdateMaintenance { wfWaitForSlaves(); } $this->output( "Done populating log_user_text field.\n" ); + return true; } } diff --git a/maintenance/populateParentId.php b/maintenance/populateParentId.php index e29fa5f1..f77978fc 100644 --- a/maintenance/populateParentId.php +++ b/maintenance/populateParentId.php @@ -49,6 +49,7 @@ class PopulateParentId extends LoggedUpdateMaintenance { $db = wfGetDB( DB_MASTER ); if ( !$db->tableExists( 'revision' ) ) { $this->error( "revision table does not exist" ); + return false; } $this->output( "Populating rev_parent_id column\n" ); @@ -56,6 +57,7 @@ class PopulateParentId extends LoggedUpdateMaintenance { $end = $db->selectField( 'revision', 'MAX(rev_id)', false, __FUNCTION__ ); if ( is_null( $start ) || is_null( $end ) ) { $this->output( "...revision table seems to be empty, nothing to do.\n" ); + return true; } # Do remaining chunk @@ -85,10 +87,16 @@ class PopulateParentId extends LoggedUpdateMaintenance { # 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 ) ), + $lastTimestamp = $db->selectField( + 'revision', + 'rev_timestamp', + array( + 'rev_page' => $row->rev_page, + "rev_timestamp < " . $db->addQuotes( $row->rev_timestamp ) + ), __METHOD__, - array( 'ORDER BY' => 'rev_timestamp DESC' ) ); + array( 'ORDER BY' => 'rev_timestamp DESC' ) + ); # If there is one, let the highest rev ID win if ( $lastTimestamp ) { $previousID = $db->selectField( 'revision', 'rev_id', @@ -113,6 +121,7 @@ class PopulateParentId extends LoggedUpdateMaintenance { wfWaitForSlaves(); } $this->output( "rev_parent_id population complete ... {$count} rows [{$changed} changed]\n" ); + return true; } } diff --git a/maintenance/populateRecentChangesSource.php b/maintenance/populateRecentChangesSource.php new file mode 100644 index 00000000..25a51d72 --- /dev/null +++ b/maintenance/populateRecentChangesSource.php @@ -0,0 +1,107 @@ +<?php +/** + * Upgrade script to populate the rc_source field + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + */ + +require_once __DIR__ . '/Maintenance.php'; + +/** + * Maintenance script to populate the rc_source field. + * + * @ingroup Maintenance + * @since 1.22 + */ +class PopulateRecentChangesSource extends LoggedUpdateMaintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = + "Populates rc_source field of the recentchanges table with the data in rc_type."; + $this->setBatchSize( 100 ); + } + + protected function doDBUpdates() { + $dbw = $this->getDB( DB_MASTER ); + if ( !$dbw->fieldExists( 'recentchanges', 'rc_source' ) ) { + $this->error( 'rc_source field in recentchanges table does not exist.' ); + } + + $start = $dbw->selectField( 'recentchanges', 'MIN(rc_id)', false, __METHOD__ ); + if ( !$start ) { + $this->output( "Nothing to do.\n" ); + + return true; + } + $end = $dbw->selectField( 'recentchanges', 'MAX(rc_id)', false, __METHOD__ ); + $end += $this->mBatchSize - 1; + $blockStart = $start; + $blockEnd = $start + $this->mBatchSize - 1; + + $updatedValues = $this->buildUpdateCondition( $dbw ); + + while ( $blockEnd <= $end ) { + $cond = "rc_id BETWEEN $blockStart AND $blockEnd"; + + $dbw->update( + 'recentchanges', + array( $updatedValues ), + array( + "rc_source = ''", + "rc_id BETWEEN $blockStart AND $blockEnd" + ), + __METHOD__ + ); + + $this->output( "." ); + wfWaitForSlaves(); + + $blockStart += $this->mBatchSize; + $blockEnd += $this->mBatchSize; + } + + $this->output( "\nDone.\n" ); + } + + protected function getUpdateKey() { + return __CLASS__; + } + + protected function buildUpdateCondition( DatabaseBase $dbw ) { + $rcNew = $dbw->addQuotes( RC_NEW ); + $rcSrcNew = $dbw->addQuotes( RecentChange::SRC_NEW ); + $rcEdit = $dbw->addQuotes( RC_EDIT ); + $rcSrcEdit = $dbw->addQuotes( RecentChange::SRC_EDIT ); + $rcLog = $dbw->addQuotes( RC_LOG ); + $rcSrcLog = $dbw->addQuotes( RecentChange::SRC_LOG ); + $rcExternal = $dbw->addQuotes( RC_EXTERNAL ); + $rcSrcExternal = $dbw->addQuotes( RecentChange::SRC_EXTERNAL ); + + return "rc_source = CASE + WHEN rc_type = $rcNew THEN $rcSrcNew + WHEN rc_type = $rcEdit THEN $rcSrcEdit + WHEN rc_type = $rcLog THEN $rcSrcLog + WHEN rc_type = $rcExternal THEN $rcSrcExternal + ELSE '' + END"; + } +} + +$maintClass = "PopulateRecentChangesSource"; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/populateRevisionLength.php b/maintenance/populateRevisionLength.php index 3c69125a..b73ac7f2 100644 --- a/maintenance/populateRevisionLength.php +++ b/maintenance/populateRevisionLength.php @@ -1,6 +1,7 @@ <?php /** - * Populates the rev_len field for old revisions created before MW 1.10. + * Populates the rev_len and ar_len fields for old revisions created + * before MW 1.10. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,86 +25,128 @@ require_once __DIR__ . '/Maintenance.php'; /** - * Maintenance script that populates the rev_len field for old revisions - * created before MW 1.10. + * Maintenance script that populates the rev_len and ar_len fields + * for old revisions created before MW 1.10. * * @ingroup Maintenance */ class PopulateRevisionLength extends LoggedUpdateMaintenance { public function __construct() { parent::__construct(); - $this->mDescription = "Populates the rev_len field"; + $this->mDescription = "Populates the rev_len and ar_len fields"; $this->setBatchSize( 200 ); } protected function getUpdateKey() { - return 'populate rev_len'; - } - - protected function updateSkippedMessage() { - return 'rev_len column of revision table already populated.'; + return 'populate rev_len and ar_len'; } public function doDBUpdates() { $db = $this->getDB( DB_MASTER ); if ( !$db->tableExists( 'revision' ) ) { $this->error( "revision table does not exist", true ); + } elseif ( !$db->tableExists( 'archive' ) ) { + $this->error( "archive table does not exist", true ); } elseif ( !$db->fieldExists( 'revision', 'rev_len', __METHOD__ ) ) { $this->output( "rev_len column does not exist\n\n", true ); + return false; } $this->output( "Populating rev_len column\n" ); + $rev = $this->doLenUpdates( 'revision', 'rev_id', 'rev', Revision::selectFields() ); + + $this->output( "Populating ar_len column\n" ); + $ar = $this->doLenUpdates( 'archive', 'ar_id', 'ar', Revision::selectArchiveFields() ); + + $this->output( "rev_len and ar_len population complete " + . "[$rev revision rows, $ar archive rows].\n" ); + + return true; + } - $start = $db->selectField( 'revision', 'MIN(rev_id)', false, __METHOD__ ); - $end = $db->selectField( 'revision', 'MAX(rev_id)', false, __METHOD__ ); + /** + * @param string $table + * @param string $idCol + * @param string $prefix + * @param array $fields + * @return int + */ + protected function doLenUpdates( $table, $idCol, $prefix, $fields ) { + $db = $this->getDB( DB_MASTER ); + $start = $db->selectField( $table, "MIN($idCol)", false, __METHOD__ ); + $end = $db->selectField( $table, "MAX($idCol)", false, __METHOD__ ); if ( !$start || !$end ) { - $this->output( "...revision table seems to be empty.\n" ); - return true; + $this->output( "...$table table seems to be empty.\n" ); + + return 0; } # Do remaining chunks $blockStart = intval( $start ); $blockEnd = intval( $start ) + $this->mBatchSize - 1; $count = 0; - $missing = 0; - $fields = Revision::selectFields(); + while ( $blockStart <= $end ) { - $this->output( "...doing rev_id from $blockStart to $blockEnd\n" ); + $this->output( "...doing $idCol from $blockStart to $blockEnd\n" ); $res = $db->select( - 'revision', + $table, $fields, array( - "rev_id >= $blockStart", - "rev_id <= $blockEnd", - "rev_len IS NULL" + "$idCol >= $blockStart", + "$idCol <= $blockEnd", + "{$prefix}_len IS NULL" ), __METHOD__ ); + + $db->begin( __METHOD__ ); # Go through and update rev_len from these rows. foreach ( $res as $row ) { - $rev = new Revision( $row ); - $content = $rev->getContent(); - if ( !$content ) { - # This should not happen, but sometimes does (bug 20757) - $this->output( "Content of revision {$row->rev_id} unavailable!\n" ); - $missing++; - } - else { - # Update the row... - $db->update( 'revision', - array( 'rev_len' => $content->getSize() ), - array( 'rev_id' => $row->rev_id ), - __METHOD__ ); + if ( $this->upgradeRow( $row, $table, $idCol, $prefix ) ) { $count++; } } + $db->commit( __METHOD__ ); + $blockStart += $this->mBatchSize; $blockEnd += $this->mBatchSize; wfWaitForSlaves(); } - $this->output( "rev_len population complete ... {$count} rows changed ({$missing} missing)\n" ); + return $count; + } + + /** + * @param stdClass $row + * @param string $table + * @param string $idCol + * @param string $prefix + * @return bool + */ + protected function upgradeRow( $row, $table, $idCol, $prefix ) { + $db = $this->getDB( DB_MASTER ); + + $rev = ( $table === 'archive' ) + ? Revision::newFromArchiveRow( $row ) + : new Revision( $row ); + + $content = $rev->getContent(); + if ( !$content ) { + # This should not happen, but sometimes does (bug 20757) + $id = $row->$idCol; + $this->output( "Content of $table $id unavailable!\n" ); + + return false; + } + + # Update the row... + $db->update( $table, + array( "{$prefix}_len" => $content->getSize() ), + array( $idCol => $row->$idCol ), + __METHOD__ + ); + return true; } } diff --git a/maintenance/populateRevisionSha1.php b/maintenance/populateRevisionSha1.php index 89bfb85b..f06b56be 100644 --- a/maintenance/populateRevisionSha1.php +++ b/maintenance/populateRevisionSha1.php @@ -50,6 +50,7 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance { $this->error( "archive table does not exist", true ); } elseif ( !$db->fieldExists( 'revision', 'rev_sha1', __METHOD__ ) ) { $this->output( "rev_sha1 column does not exist\n\n", true ); + return false; } @@ -61,15 +62,17 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance { $this->output( "Populating ar_sha1 column legacy rows\n" ); $ac += $this->doSha1LegacyUpdates(); - $this->output( "rev_sha1 and ar_sha1 population complete [$rc revision rows, $ac archive rows].\n" ); + $this->output( "rev_sha1 and ar_sha1 population complete " + . "[$rc revision rows, $ac archive rows].\n" ); + return true; } /** - * @param $table string - * @param $idCol - * @param $prefix string - * @return Integer Rows changed + * @param string $table + * @param string $idCol + * @param string $prefix + * @return int Rows changed */ protected function doSha1Updates( $table, $idCol, $prefix ) { $db = $this->getDB( DB_MASTER ); @@ -77,6 +80,7 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance { $end = $db->selectField( $table, "MAX($idCol)", false, __METHOD__ ); if ( !$start || !$end ) { $this->output( "...$table table seems to be empty.\n" ); + return 0; } @@ -103,6 +107,7 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance { $blockEnd += $this->mBatchSize; wfWaitForSlaves(); } + return $count; } @@ -130,14 +135,15 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance { } } $db->commit( __METHOD__ ); + return $count; } /** - * @param $row - * @param $table - * @param $idCol - * @param $prefix + * @param stdClass $row + * @param string $table + * @param string $idCol + * @param string $prefix * @return bool */ protected function upgradeRow( $row, $table, $idCol, $prefix ) { @@ -149,11 +155,13 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance { $text = $rev->getSerializedData(); } catch ( MWException $e ) { $this->output( "Data of revision with {$idCol}={$row->$idCol} unavailable!\n" ); + return false; // bug 22624? } if ( !is_string( $text ) ) { # This should not happen, but sometimes does (bug 20757) $this->output( "Data of revision with {$idCol}={$row->$idCol} unavailable!\n" ); + return false; } else { $db->update( $table, @@ -161,12 +169,13 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance { array( $idCol => $row->$idCol ), __METHOD__ ); + return true; } } /** - * @param $row + * @param stdClass $row * @return bool */ protected function upgradeLegacyArchiveRow( $row ) { @@ -175,12 +184,14 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance { $rev = Revision::newFromArchiveRow( $row ); } catch ( MWException $e ) { $this->output( "Text of revision with timestamp {$row->ar_timestamp} unavailable!\n" ); + return false; // bug 22624? } $text = $rev->getSerializedData(); if ( !is_string( $text ) ) { # This should not happen, but sometimes does (bug 20757) $this->output( "Data of revision with timestamp {$row->ar_timestamp} unavailable!\n" ); + return false; } else { # Archive table as no PK, but (NS,title,time) should be near unique. @@ -195,6 +206,7 @@ class PopulateRevisionSha1 extends LoggedUpdateMaintenance { ), __METHOD__ ); + return true; } } diff --git a/maintenance/postgres/mediawiki_mysql2postgres.pl b/maintenance/postgres/mediawiki_mysql2postgres.pl index 8f170abc..34837e1b 100644 --- a/maintenance/postgres/mediawiki_mysql2postgres.pl +++ b/maintenance/postgres/mediawiki_mysql2postgres.pl @@ -1,7 +1,6 @@ #!/usr/bin/perl ## Convert data from a MySQL mediawiki database into a Postgres mediawiki database -## svn: $Id$ ## NOTE: It is probably easier to dump your wiki using maintenance/dumpBackup.php ## and then import it with maintenance/importDump.php @@ -181,7 +180,7 @@ $MYSQLSOCKET and $conninfo .= "\n-- socket $MYSQLSOCKET"; print qq{ -- Dump of MySQL Mediawiki tables for import into a Postgres Mediawiki schema -- Performed by the program: $0 --- Version: $VERSION (subversion }.q{$LastChangedRevision$}.qq{) +-- Version: $VERSION -- Author: Greg Sabino Mullane <greg\@turnstep.com> Comments welcome -- -- This file was created: $now diff --git a/maintenance/postgres/tables.sql b/maintenance/postgres/tables.sql index 5ed7de99..400050e7 100644 --- a/maintenance/postgres/tables.sql +++ b/maintenance/postgres/tables.sql @@ -12,18 +12,23 @@ SET client_min_messages = 'ERROR'; DROP SEQUENCE IF EXISTS user_user_id_seq CASCADE; DROP SEQUENCE IF EXISTS page_page_id_seq CASCADE; DROP SEQUENCE IF EXISTS revision_rev_id_seq CASCADE; -DROP SEQUENCE IF EXISTS page_restrictions_id_seq CASCADE; +DROP SEQUENCE IF EXISTS text_old_id_seq CASCADE; +DROP SEQUENCE IF EXISTS page_restrictions_pr_id_seq CASCADE; DROP SEQUENCE IF EXISTS ipblocks_ipb_id_seq CASCADE; +DROP SEQUENCE IF EXISTS filearchive_fa_id_seq CASCADE; +DROP SEQUENCE IF EXISTS uploadstash_us_id_seq CASCADE; DROP SEQUENCE IF EXISTS recentchanges_rc_id_seq CASCADE; DROP SEQUENCE IF EXISTS logging_log_id_seq CASCADE; DROP SEQUENCE IF EXISTS job_job_id_seq CASCADE; DROP SEQUENCE IF EXISTS category_cat_id_seq CASCADE; DROP SEQUENCE IF EXISTS archive_ar_id_seq CASCADE; DROP SEQUENCE IF EXISTS externallinks_el_id_seq CASCADE; +DROP SEQUENCE IF EXISTS sites_site_id_seq CASCADE; DROP FUNCTION IF EXISTS page_deleted() CASCADE; DROP FUNCTION IF EXISTS ts2_page_title() CASCADE; DROP FUNCTION IF EXISTS ts2_page_text() CASCADE; DROP FUNCTION IF EXISTS add_interwiki(TEXT,INT,SMALLINT) CASCADE; +DROP TYPE IF EXISTS media_type CASCADE; CREATE SEQUENCE user_user_id_seq MINVALUE 0 START WITH 0; CREATE TABLE mwuser ( -- replace reserved word 'user' @@ -40,7 +45,8 @@ CREATE TABLE mwuser ( -- replace reserved word 'user' user_email_authenticated TIMESTAMPTZ, user_touched TIMESTAMPTZ, user_registration TIMESTAMPTZ, - user_editcount INTEGER + user_editcount INTEGER, + user_password_expires TIMESTAMPTZ NULL ); CREATE INDEX user_email_token_idx ON mwuser (user_email_token); @@ -80,9 +86,11 @@ CREATE TABLE page ( page_is_new SMALLINT NOT NULL DEFAULT 0, page_random NUMERIC(15,14) NOT NULL DEFAULT RANDOM(), page_touched TIMESTAMPTZ, + page_links_updated TIMESTAMPTZ NULL, page_latest INTEGER NOT NULL, -- FK? page_len INTEGER NOT NULL, - page_content_model TEXT + page_content_model TEXT, + page_lang TEXT DEFAULT NULL ); CREATE UNIQUE INDEX page_unique_name ON page (page_namespace, page_title); CREATE INDEX page_main_title ON page (page_title text_pattern_ops) WHERE page_namespace = 0; @@ -152,11 +160,13 @@ ALTER TABLE page_restrictions ADD CONSTRAINT page_restrictions_pk PRIMARY KEY (p CREATE TABLE page_props ( pp_page INTEGER NOT NULL REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, pp_propname TEXT NOT NULL, - pp_value TEXT NOT NULL + pp_value TEXT NOT NULL, + pp_sortkey FLOAT ); ALTER TABLE page_props ADD CONSTRAINT page_props_pk PRIMARY KEY (pp_page,pp_propname); CREATE INDEX page_props_propname ON page_props (pp_propname); CREATE UNIQUE INDEX pp_propname_page ON page_props (pp_propname,pp_page); +CREATE INDEX pp_propname_sortkey_page ON page_props (pp_propname, pp_sortkey, pp_page) WHERE (pp_sortkey IS NOT NULL); CREATE SEQUENCE archive_ar_id_seq; CREATE TABLE archive ( @@ -196,6 +206,7 @@ CREATE INDEX redirect_ns_title ON redirect (rd_namespace,rd_title,rd_from); CREATE TABLE pagelinks ( pl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + pl_from_namespace INTEGER NOT NULL DEFAULT 0, pl_namespace SMALLINT NOT NULL, pl_title TEXT NOT NULL ); @@ -204,6 +215,7 @@ CREATE INDEX pagelinks_title ON pagelinks (pl_title); CREATE TABLE templatelinks ( tl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + tl_from_namespace INTEGER NOT NULL DEFAULT 0, tl_namespace SMALLINT NOT NULL, tl_title TEXT NOT NULL ); @@ -212,6 +224,7 @@ CREATE INDEX templatelinks_from ON templatelinks (tl_from); CREATE TABLE imagelinks ( il_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + il_from_namespace INTEGER NOT NULL DEFAULT 0, il_to TEXT NOT NULL ); CREATE UNIQUE INDEX il_from ON imagelinks (il_to,il_from); @@ -399,7 +412,7 @@ CREATE SEQUENCE recentchanges_rc_id_seq; CREATE TABLE recentchanges ( rc_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('recentchanges_rc_id_seq'), rc_timestamp TIMESTAMPTZ NOT NULL, - rc_cur_time TIMESTAMPTZ NOT NULL, + rc_cur_time TIMESTAMPTZ NULL, rc_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, rc_user_text TEXT NOT NULL, rc_namespace SMALLINT NOT NULL, @@ -412,6 +425,7 @@ CREATE TABLE recentchanges ( rc_this_oldid INTEGER NOT NULL, rc_last_oldid INTEGER NOT NULL, rc_type SMALLINT NOT NULL DEFAULT 0, + rc_source TEXT NOT NULL, rc_patrolled SMALLINT NOT NULL DEFAULT 0, rc_ip CIDR, rc_old_len INTEGER, @@ -438,6 +452,7 @@ CREATE TABLE watchlist ( ); CREATE UNIQUE INDEX wl_user_namespace_title ON watchlist (wl_namespace, wl_title, wl_user); CREATE INDEX wl_user ON watchlist (wl_user); +CREATE INDEX wl_user_notificationtimestamp ON watchlist (wl_user, wl_notificationtimestamp); CREATE TABLE interwiki ( @@ -510,6 +525,8 @@ CREATE INDEX logging_page_time ON logging (log_namespace, log_title, log_timesta CREATE INDEX logging_times ON logging (log_timestamp); CREATE INDEX logging_user_type_time ON logging (log_user, log_type, log_timestamp); CREATE INDEX logging_page_id_time ON logging (log_page, log_timestamp); +CREATE INDEX logging_user_text_type_time ON logging (log_user_text, log_type, log_timestamp); +CREATE INDEX logging_user_text_time ON logging (log_user_text, log_timestamp); CREATE TABLE log_search ( ls_field TEXT NOT NULL, @@ -666,7 +683,7 @@ CREATE INDEX user_properties_property ON user_properties (up_property); CREATE TABLE l10n_cache ( lc_lang TEXT NOT NULL, lc_key TEXT NOT NULL, - lc_value TEXT NOT NULL + lc_value BYTEA NOT NULL ); CREATE INDEX l10n_cache_lc_lang_key ON l10n_cache (lc_lang, lc_key); diff --git a/maintenance/postgres/update-keys.sql b/maintenance/postgres/update-keys.sql new file mode 100644 index 00000000..7761d0c5 --- /dev/null +++ b/maintenance/postgres/update-keys.sql @@ -0,0 +1,29 @@ +-- SQL to insert update keys into the initial tables after a +-- fresh installation of MediaWiki's database. +-- This is read and executed by the install script; you should +-- not have to run it by itself unless doing a manual install. +-- Insert keys here if either the unnecessary would cause heavy +-- processing or could potentially cause trouble by lowering field +-- sizes, adding constraints, etc. +-- When adjusting field sizes, it is recommended removing old +-- patches but to play safe, update keys should also inserted here. + +-- The /*_*/ comments in this and other files are +-- replaced with the defined table prefix by the installer +-- and updater scripts. If you are installing or running +-- updates manually, you will need to manually insert the +-- table prefix if any when running these scripts. +-- + +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'filearchive-fa_major_mime-patch-fa_major_mime-chemical.sql', null ); +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'image-img_major_mime-patch-img_major_mime-chemical.sql', null ); +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'oldimage-oi_major_mime-patch-oi_major_mime-chemical.sql', null ); +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'user_groups-ug_group-patch-ug_group-length-increase-255.sql', null ); +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'user_former_groups-ufg_group-patch-ufg_group-length-increase-255.sql', null ); +INSERT INTO /*_*/updatelog (ul_key, ul_value) + VALUES( 'user_properties-up_property-patch-up_property.sql', null ); diff --git a/maintenance/preprocessDump.php b/maintenance/preprocessDump.php index 0dc19e28..17d97b05 100644 --- a/maintenance/preprocessDump.php +++ b/maintenance/preprocessDump.php @@ -4,7 +4,7 @@ * It may be useful for getting preprocessor statistics or filling the * preprocessor cache. * - * Copyright © 2011 Platonides - http://www.mediawiki.org/ + * Copyright © 2011 Platonides - https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,6 +41,7 @@ class PreprocessDump extends DumpIterator { public function getStripList() { global $wgParser; + return $wgParser->getStripList(); } @@ -75,7 +76,7 @@ class PreprocessDump extends DumpIterator { /** * Callback function for each revision, preprocessToObj() - * @param $rev Revision + * @param Revision $rev */ public function processRevision( $rev ) { $content = $rev->getContent( Revision::RAW ); @@ -87,7 +88,8 @@ class PreprocessDump extends DumpIterator { try { $this->mPreprocessor->preprocessToObj( strval( $content->getNativeData() ), 0 ); } catch ( Exception $e ) { - $this->error( "Caught exception " . $e->getMessage() . " in " . $rev->getTitle()->getPrefixedText() ); + $this->error( "Caught exception " . $e->getMessage() . " in " + . $rev->getTitle()->getPrefixedText() ); } } } diff --git a/maintenance/preprocessorFuzzTest.php b/maintenance/preprocessorFuzzTest.php index 563ea459..cb55f0f2 100644 --- a/maintenance/preprocessorFuzzTest.php +++ b/maintenance/preprocessorFuzzTest.php @@ -45,7 +45,8 @@ class PPFuzzTester { // public $outputTypes = array( 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' ); public $entryPoints = array( 'testSrvus', 'testPst', 'testPreprocess' ); public $verbose = false; - static $currentTest = false; + + private static $currentTest = false; function execute() { if ( !file_exists( 'results' ) ) { @@ -120,6 +121,7 @@ class PPFuzzTester { // It's done by the MW UI, so it's a reasonably legitimate thing to do. global $wgContLang; $s = $wgContLang->normalize( $s ); + return $s; } @@ -135,7 +137,8 @@ class PPFuzzTester { function pickEntryPoint() { $count = count( $this->entryPoints ); - return $this->entryPoints[ mt_rand( 0, $count - 1 ) ]; + + return $this->entryPoints[mt_rand( 0, $count - 1 )]; } } @@ -155,7 +158,8 @@ class PPFuzzTest { } /** - * @param $title Title + * @param Title $title + * @return array */ function templateHook( $title ) { $titleText = $title->getPrefixedDBkey(); @@ -181,6 +185,7 @@ class PPFuzzTest { 'text' => $text, 'finalTitle' => $finalTitle ); } + return $this->templates[$titleText]; } @@ -195,7 +200,13 @@ class PPFuzzTest { $options = ParserOptions::newFromUser( $wgUser ); $options->setTemplateCallback( array( $this, 'templateHook' ) ); $options->setTimestamp( wfTimestampNow() ); - $this->output = call_user_func( array( $wgParser, $this->entryPoint ), $this->mainText, $this->title, $options ); + $this->output = call_user_func( + array( $wgParser, $this->entryPoint ), + $this->mainText, + $this->title, + $options + ); + return $this->output; } @@ -203,7 +214,8 @@ class PPFuzzTest { $s = "Title: " . $this->title->getPrefixedDBkey() . "\n" . // "Output type: {$this->outputType}\n" . "Entry point: {$this->entryPoint}\n" . - "User: " . ( $this->fancySig ? 'fancy' : 'no-fancy' ) . ' ' . var_export( $this->nickname, true ) . "\n" . + "User: " . ( $this->fancySig ? 'fancy' : 'no-fancy' ) . + ' ' . var_export( $this->nickname, true ) . "\n" . "Main text: " . var_export( $this->mainText, true ) . "\n"; foreach ( $this->templates as $titleText => $template ) { $finalTitle = $template['finalTitle']; @@ -214,6 +226,7 @@ class PPFuzzTest { } } $s .= "Output: " . var_export( $this->output, true ) . "\n"; + return $s; } } diff --git a/maintenance/pruneFileCache.php b/maintenance/pruneFileCache.php index 48d38977..455e9c07 100644 --- a/maintenance/pruneFileCache.php +++ b/maintenance/pruneFileCache.php @@ -75,8 +75,8 @@ class PruneFileCache extends Maintenance { } /** - * @param $dir string - * @param $report string|bool Use 'report' to report the directories being scanned + * @param string $dir + * @param string|bool $report Use 'report' to report the directories being scanned */ protected function prune_directory( $dir, $report = false ) { $tsNow = time(); @@ -95,8 +95,8 @@ class PruneFileCache extends Maintenance { // Sanity check the file extension against known cache types if ( $mts < $this->minSurviveTimestamp && preg_match( '/\.(?:html|cache)(?:\.gz)?$/', $file ) - && unlink( $path ) ) - { + && unlink( $path ) + ) { $daysOld = round( ( $tsNow - $mts ) / 86400, 2 ); $this->output( "Deleted `$path` [days=$daysOld]\n" ); } diff --git a/maintenance/purgeChangedFiles.php b/maintenance/purgeChangedFiles.php index 9f83ee7f..1e702dea 100644 --- a/maintenance/purgeChangedFiles.php +++ b/maintenance/purgeChangedFiles.php @@ -69,7 +69,9 @@ class PurgeChangedFiles extends Maintenance { implode( ',', array_keys( self::$typeMappings ) ) . ',all)', false, true ); $this->addOption( 'htcp-dest', 'HTCP announcement destination (IP:port)', false, true ); $this->addOption( 'dry-run', 'Do not send purge requests' ); + $this->addOption( 'sleep-per-batch', 'Milliseconds to sleep between batches', false, true ); $this->addOption( 'verbose', 'Show more output', false, false, 'v' ); + $this->setBatchSize( 100 ); } public function execute() { @@ -119,7 +121,7 @@ class PurgeChangedFiles extends Maintenance { $this->mOptions['verbose'] = 1; } - $this->verbose( 'Purging files that were: ' . implode( ', ', $typeList ) . "\n"); + $this->verbose( 'Purging files that were: ' . implode( ', ', $typeList ) . "\n" ); foreach ( $typeList as $type ) { $this->verbose( "Checking for {$type} files...\n" ); $this->purgeFromLogType( $type ); @@ -154,6 +156,7 @@ class PurgeChangedFiles extends Maintenance { __METHOD__ ); + $bSize = 0; foreach ( $res as $row ) { $file = $repo->newFile( Title::makeTitle( NS_FILE, $row->log_title ) ); @@ -174,7 +177,6 @@ class PurgeChangedFiles extends Maintenance { // Sanity check to avoid data loss $repo->getBackend()->delete( array( 'src' => $file->getPath() ) ); $this->verbose( "Deleted orphan file: {$file->getPath()}.\n" ); - } else { $this->error( "File was not deleted: {$file->getPath()}.\n" ); } @@ -182,8 +184,7 @@ class PurgeChangedFiles extends Maintenance { // Purge items from fileachive table (rows are likely here) $this->purgeFromArchiveTable( $repo, $file ); - - } else if ( $logType === 'move' ) { + } elseif ( $logType === 'move' ) { // Purge the target file as well $params = unserialize( $row->log_params ); @@ -197,6 +198,12 @@ class PurgeChangedFiles extends Maintenance { } $this->verbose( "Purged file {$row->log_title}; {$type} @{$row->log_timestamp}.\n" ); + + if ( $this->hasOption( 'sleep-per-batch' ) && ++$bSize > $this->mBatchSize ) { + $bSize = 0; + // sleep-per-batch is milliseconds, usleep wants micro seconds. + usleep( 1000 * (int)$this->getOption( 'sleep-per-batch' ) ); + } } } } @@ -223,7 +230,6 @@ class PurgeChangedFiles extends Maintenance { // Sanity check to avoid data loss $repo->getBackend()->delete( array( 'src' => $ofile->getPath() ) ); $this->output( "Deleted orphan file: {$ofile->getPath()}.\n" ); - } else { $this->error( "File was not deleted: {$ofile->getPath()}.\n" ); } @@ -235,6 +241,7 @@ class PurgeChangedFiles extends Maintenance { protected function getDeletedPath( LocalRepo $repo, LocalFile $file ) { $hash = $repo->getFileSha1( $file->getPath() ); $key = "{$hash}.{$file->getExtension()}"; + return $repo->getDeletedHashPath( $key ) . $key; } @@ -248,7 +255,6 @@ class PurgeChangedFiles extends Maintenance { $this->output( $msg ); } } - } $maintClass = "PurgeChangedFiles"; diff --git a/maintenance/purgeChangedPages.php b/maintenance/purgeChangedPages.php index 071ac09c..67022094 100644 --- a/maintenance/purgeChangedPages.php +++ b/maintenance/purgeChangedPages.php @@ -160,10 +160,11 @@ class PurgeChangedPages extends Maintenance { * If this returns an empty array for a non-empty query result, then all the rows * had the same column value and the query should be repeated with a higher LIMIT. * - * @TODO: move this elsewhere + * @todo move this elsewhere * * @param ResultWrapper $res Query result sorted by $column (ascending) * @param string $column + * @param int $limit * @return array (array of rows, string column value) */ protected function pageableSortedRows( ResultWrapper $res, $column, $limit ) { @@ -183,6 +184,7 @@ class PurgeChangedPages extends Maintenance { } } $lastValueLeft = count( $rows ) ? $rows[count( $rows ) - 1]->$column : null; + return array( $rows, $lastValueLeft ); } } diff --git a/maintenance/purgeList.php b/maintenance/purgeList.php index 2f895201..2e196309 100644 --- a/maintenance/purgeList.php +++ b/maintenance/purgeList.php @@ -51,7 +51,9 @@ class PurgeList extends Maintenance { $this->output( "Done!\n" ); } - /** Purge URL coming from stdin */ + /** + * Purge URL coming from stdin + */ private function doPurge() { $stdin = $this->getStdin(); $urls = array(); @@ -78,7 +80,11 @@ class PurgeList extends Maintenance { $this->sendPurgeRequest( $urls ); } - /** Purge a namespace or all pages */ + /** + * Purge a namespace or all pages + * + * @param int|bool $namespace + */ private function purgeNamespace( $namespace = false ) { $dbr = wfGetDB( DB_SLAVE ); $startId = 0; @@ -114,7 +120,7 @@ class PurgeList extends Maintenance { /** * Helper to purge an array of $urls - * @param $urls array List of URLS to purge from squids + * @param array $urls List of URLS to purge from squids */ private function sendPurgeRequest( $urls ) { if ( $this->hasOption( 'delay' ) ) { @@ -135,7 +141,6 @@ class PurgeList extends Maintenance { $u->doUpdate(); } } - } $maintClass = "PurgeList"; diff --git a/maintenance/purgeOldText.inc b/maintenance/purgeOldText.inc deleted file mode 100644 index db961d81..00000000 --- a/maintenance/purgeOldText.inc +++ /dev/null @@ -1,78 +0,0 @@ -<?php - -/** - * Support functions for cleaning up redundant text records - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup Maintenance - * @author Rob Church <robchur@gmail.com> - */ - -function PurgeRedundantText( $delete = false ) { - - # Data should come off the master, wrapped in a transaction - $dbw = wfGetDB( DB_MASTER ); - $dbw->begin( __METHOD__ ); - - $tbl_arc = $dbw->tableName( 'archive' ); - $tbl_rev = $dbw->tableName( 'revision' ); - $tbl_txt = $dbw->tableName( 'text' ); - - # Get "active" text records from the revisions table - echo "Searching for active text records in revisions table..."; - $res = $dbw->query( "SELECT DISTINCT rev_text_id FROM $tbl_rev" ); - foreach ( $res as $row ) { - $cur[] = $row->rev_text_id; - } - echo "done.\n"; - - # Get "active" text records from the archive table - echo "Searching for active text records in archive table..."; - $res = $dbw->query( "SELECT DISTINCT ar_text_id FROM $tbl_arc" ); - $cur = array(); - foreach ( $res as $row ) { - $cur[] = $row->ar_text_id; - } - echo "done.\n"; - - # Get the IDs of all text records not in these sets - echo "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(); - foreach ( $res as $row ) { - $old[] = $row->old_id; - } - echo "done.\n"; - - # Inform the user of what we're going to do - $count = count( $old ); - echo "$count inactive items found.\n"; - - # Delete as appropriate - if ( $delete && $count ) { - echo "Deleting..."; - $set = implode( ', ', $old ); - $dbw->query( "DELETE FROM $tbl_txt WHERE old_id IN ( $set )" ); - echo "done.\n"; - } - - # Done - $dbw->commit( __METHOD__ ); - -} diff --git a/maintenance/purgeParserCache.php b/maintenance/purgeParserCache.php index ca2a0414..9970c1fd 100644 --- a/maintenance/purgeParserCache.php +++ b/maintenance/purgeParserCache.php @@ -37,7 +37,8 @@ class PurgeParserCache extends Maintenance { $this->addDescription( "Remove old objects from the parser cache. " . "This only works when the parser cache is in an SQL database." ); $this->addOption( 'expiredate', 'Delete objects expiring before this date.', false, true ); - $this->addOption( 'age', + $this->addOption( + 'age', 'Delete objects created more than this many seconds ago, assuming $wgParserCacheExpireTime ' . 'has been consistent.', false, true ); @@ -77,8 +78,8 @@ class PurgeParserCache extends Maintenance { $stars = floor( $percent / 2 ); $this->output( '[' . str_repeat( '*', $stars ) . str_repeat( '.', 50 - $stars ) . '] ' . "$percentString%\r" ); - } } + $maintClass = 'PurgeParserCache'; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/reassignEdits.php b/maintenance/reassignEdits.php index 7e15c09e..679cadb9 100644 --- a/maintenance/reassignEdits.php +++ b/maintenance/reassignEdits.php @@ -67,11 +67,11 @@ class ReassignEdits extends Maintenance { /** * Reassign edits from one user to another * - * @param $from User to take edits from - * @param $to User to assign edits to - * @param $rc bool Update the recent changes table - * @param $report bool Don't change things; just echo numbers - * @return integer Number of entries changed, or that would be changed + * @param User $from User to take edits from + * @param User $to User to assign edits to + * @param bool $rc Update the recent changes table + * @param bool $report Don't change things; just echo numbers + * @return int Number of entries changed, or that would be changed */ private function doReassignEdits( &$from, &$to, $rc = false, $report = false ) { $dbw = wfGetDB( DB_MASTER ); @@ -79,13 +79,23 @@ class ReassignEdits extends Maintenance { # Count things $this->output( "Checking current edits..." ); - $res = $dbw->select( 'revision', 'COUNT(*) AS count', $this->userConditions( $from, 'rev_user', 'rev_user_text' ), __METHOD__ ); + $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__ ); + $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" ); @@ -93,7 +103,12 @@ class ReassignEdits extends Maintenance { # 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__ ); + $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" ); @@ -125,6 +140,7 @@ class ReassignEdits extends Maintenance { } $dbw->commit( __METHOD__ ); + return (int)$total; } @@ -132,22 +148,24 @@ class ReassignEdits extends Maintenance { * 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 string Field name containing the identifier - * @param $utfield string Field name containing the user text + * @param User $user User for the condition + * @param string $idfield Field name containing the identifier + * @param string $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->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 string Field name containing the identifier - * @param $utfield string Field name containing the user text + * @param User $user User for the spec + * @param string $idfield Field name containing the identifier + * @param string $utfield Field name containing the user text * @return array */ private function userSpecification( &$user, $idfield, $utfield ) { @@ -157,7 +175,7 @@ class ReassignEdits extends Maintenance { /** * Initialise the user object * - * @param $username string Username or IP address + * @param string $username Username or IP address * @return User */ private function initialiseUser( $username ) { @@ -172,10 +190,9 @@ class ReassignEdits extends Maintenance { } } $user->load(); + return $user; } - - } $maintClass = "ReassignEdits"; diff --git a/maintenance/rebuildFileCache.php b/maintenance/rebuildFileCache.php index 12ed9fac..6ce54b9f 100644 --- a/maintenance/rebuildFileCache.php +++ b/maintenance/rebuildFileCache.php @@ -49,7 +49,7 @@ class RebuildFileCache extends Maintenance { public function execute() { global $wgUseFileCache, $wgReadOnly, $wgContentNamespaces, $wgRequestTime; - global $wgTitle, $wgOut; + global $wgOut; if ( !$wgUseFileCache ) { $this->error( "Nothing to do -- \$wgUseFileCache is disabled.", true ); } @@ -104,22 +104,22 @@ class RebuildFileCache extends Maintenance { $rebuilt = false; $wgRequestTime = microtime( true ); # bug 22852 - $wgTitle = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); - if ( null == $wgTitle ) { + $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); + if ( null == $title ) { $this->output( "Page {$row->page_id} has bad title\n" ); continue; // broken title? } $context = new RequestContext; - $context->setTitle( $wgTitle ); - $article = Article::newFromTitle( $wgTitle, $context ); + $context->setTitle( $title ); + $article = Article::newFromTitle( $title, $context ); $context->setWikiPage( $article->getPage() ); $wgOut = $context->getOutput(); // set display title // If the article is cacheable, then load it if ( $article->isFileCacheable() ) { - $cache = HTMLFileCache::newFromTitle( $wgTitle, 'view' ); + $cache = HTMLFileCache::newFromTitle( $title, 'view' ); if ( $cache->isCacheGood() ) { if ( $overwrite ) { $rebuilt = true; @@ -151,11 +151,6 @@ class RebuildFileCache extends Maintenance { $blockEnd += $this->mBatchSize; } $this->output( "Done!\n" ); - - // Remove these to be safe - if ( isset( $wgTitle ) ) { - unset( $wgTitle ); - } } } diff --git a/maintenance/rebuildImages.php b/maintenance/rebuildImages.php index 53bf823f..5a149678 100644 --- a/maintenance/rebuildImages.php +++ b/maintenance/rebuildImages.php @@ -8,7 +8,7 @@ * add them only. * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -79,6 +79,7 @@ class ImageBuilder extends Maintenance { if ( !isset( $this->repo ) ) { $this->repo = RepoGroup::singleton()->getLocalRepo(); } + return $this->repo; } @@ -149,6 +150,7 @@ class ImageBuilder extends Maintenance { // Create a File object from the row // This will also upgrade it $file = $this->getRepo()->newFileFromRow( $row ); + return $file->getUpgraded(); } @@ -161,9 +163,11 @@ class ImageBuilder extends Maintenance { // This will also upgrade it if ( $row->oi_archive_name == '' ) { $this->output( "Empty oi_archive_name for oi_name={$row->oi_name}\n" ); + return false; } $file = $this->getRepo()->newFileFromRow( $row ); + return $file->getUpgraded(); } @@ -201,14 +205,22 @@ class ImageBuilder extends Maintenance { if ( $filename == '' ) { $this->output( "Empty filename for $fullpath\n" ); + return; } if ( !$this->dryrun ) { $file = wfLocalFile( $filename ); - if ( !$file->recordUpload( '', '(recovered file, missing upload log entry)', '', '', '', - false, $timestamp ) ) - { + if ( !$file->recordUpload( + '', + '(recovered file, missing upload log entry)', + '', + '', + '', + false, + $timestamp + ) ) { $this->output( "Error uploading file $fullpath\n" ); + return; } } diff --git a/maintenance/rebuildLocalisationCache.php b/maintenance/rebuildLocalisationCache.php index b7f306b1..b04639c0 100644 --- a/maintenance/rebuildLocalisationCache.php +++ b/maintenance/rebuildLocalisationCache.php @@ -52,6 +52,7 @@ class RebuildLocalisationCache extends Maintenance { if ( $this->hasOption( 'memory-limit' ) ) { return parent::memoryLimit(); } + return '1000M'; } @@ -90,7 +91,7 @@ class RebuildLocalisationCache extends Maintenance { if ( $this->hasOption( 'outdir' ) ) { $conf['storeDirectory'] = $this->getOption( 'outdir' ); } - $lc = new LocalisationCache_BulkLoad( $conf ); + $lc = new LocalisationCacheBulkLoad( $conf ); $allCodes = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) ); if ( $this->hasOption( 'lang' ) ) { @@ -148,9 +149,9 @@ class RebuildLocalisationCache extends Maintenance { /** * Helper function to rebuild list of languages codes. Prints the code * for each language which is rebuilt. - * @param $codes array List of language codes to rebuild. - * @param $lc LocalisationCache Instance of LocalisationCache_BulkLoad (?) - * @param $force bool Rebuild up-to-date languages + * @param array $codes List of language codes to rebuild. + * @param LocalisationCache $lc Instance of LocalisationCacheBulkLoad (?) + * @param bool $force Rebuild up-to-date languages * @return int Number of rebuilt languages */ private function doRebuild( $codes, $lc, $force ) { @@ -162,6 +163,7 @@ class RebuildLocalisationCache extends Maintenance { $numRebuilt++; } } + return $numRebuilt; } diff --git a/maintenance/rebuildall.php b/maintenance/rebuildall.php index 1268d209..eeee9c21 100644 --- a/maintenance/rebuildall.php +++ b/maintenance/rebuildall.php @@ -42,7 +42,8 @@ class RebuildAll extends Maintenance { public function execute() { // Rebuild the text index if ( wfGetDB( DB_SLAVE )->getType() != 'postgres' ) { - $this->output( "** Rebuilding fulltext search index (if you abort this will break searching; run this script again to fix):\n" ); + $this->output( "** Rebuilding fulltext search index (if you abort " + . "this will break searching; run this script again to fix):\n" ); $rebuildText = $this->runChild( 'RebuildTextIndex', 'rebuildtextindex.php' ); $rebuildText->execute(); } @@ -53,7 +54,8 @@ class RebuildAll extends Maintenance { $rebuildRC->execute(); // Rebuild link tables - $this->output( "\n\n** Rebuilding links tables -- this can take a long time. It should be safe to abort via ctrl+C if you get bored.\n" ); + $this->output( "\n\n** Rebuilding links tables -- this can take a long time. " + . "It should be safe to abort via ctrl+C if you get bored.\n" ); $rebuildLinks = $this->runChild( 'RefreshLinks', 'refreshLinks.php' ); $rebuildLinks->execute(); diff --git a/maintenance/rebuildrecentchanges.php b/maintenance/rebuildrecentchanges.php index 18348258..f4b0505e 100644 --- a/maintenance/rebuildrecentchanges.php +++ b/maintenance/rebuildrecentchanges.php @@ -69,25 +69,31 @@ class RebuildRecentchanges extends Maintenance { $cutoff = time() - $wgRCMaxAge; $dbw->insertSelect( 'recentchanges', array( 'page', 'revision' ), array( - 'rc_timestamp' => 'rev_timestamp', - 'rc_cur_time' => 'rev_timestamp', - 'rc_user' => 'rev_user', - 'rc_user_text' => 'rev_user_text', - 'rc_namespace' => 'page_namespace', - 'rc_title' => 'page_title', - 'rc_comment' => 'rev_comment', - 'rc_minor' => 'rev_minor_edit', - 'rc_bot' => 0, - 'rc_new' => 'page_is_new', - 'rc_cur_id' => 'page_id', + 'rc_timestamp' => 'rev_timestamp', + 'rc_user' => 'rev_user', + 'rc_user_text' => 'rev_user_text', + 'rc_namespace' => 'page_namespace', + 'rc_title' => 'page_title', + 'rc_comment' => 'rev_comment', + 'rc_minor' => 'rev_minor_edit', + 'rc_bot' => 0, + 'rc_new' => 'page_is_new', + 'rc_cur_id' => 'page_id', 'rc_this_oldid' => 'rev_id', 'rc_last_oldid' => 0, // is this ok? - 'rc_type' => $dbw->conditional( 'page_is_new != 0', RC_NEW, RC_EDIT ), - 'rc_deleted' => 'rev_deleted' - ), array( + 'rc_type' => $dbw->conditional( 'page_is_new != 0', RC_NEW, RC_EDIT ), + 'rc_source' => $dbw->conditional( + 'page_is_new != 0', + $dbw->addQuotes( RecentChange::SRC_NEW ), + $dbw->addQuotes( RecentChange::SRC_EDIT ) + ), + 'rc_deleted' => 'rev_deleted' + ), + array( 'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $cutoff ) ), 'rev_page=page_id' - ), __METHOD__, + ), + __METHOD__, array(), // INSERT options array( 'ORDER BY' => 'rev_timestamp DESC', 'LIMIT' => 5000 ) // SELECT options ); @@ -144,6 +150,7 @@ class RebuildRecentchanges extends Maintenance { 'rc_last_oldid' => $lastOldId, 'rc_new' => $new, 'rc_type' => $new, + 'rc_source' => $new === 1 ? RecentChange::SRC_NEW : RecentChange::SRC_EDIT, 'rc_old_len' => $lastSize, 'rc_new_len' => $size, ), array( @@ -172,42 +179,42 @@ class RebuildRecentchanges extends Maintenance { // Some logs don't go in RC. This should check for that $basicRCLogs = array_diff( $wgLogTypes, array_keys( $wgLogRestrictions ) ); - // Escape...blah blah - $selectLogs = array(); - foreach ( $basicRCLogs as $logtype ) { - $safetype = $dbw->strencode( $logtype ); - $selectLogs[] = "'$safetype'"; - } - $cutoff = time() - $wgRCMaxAge; list( $logging, $page ) = $dbw->tableNamesN( 'logging', 'page' ); - $dbw->insertSelect( 'recentchanges', array( 'user', "$logging LEFT JOIN $page ON (log_namespace=page_namespace AND log_title=page_title)" ), + $dbw->insertSelect( + 'recentchanges', array( - 'rc_timestamp' => 'log_timestamp', - 'rc_cur_time' => 'log_timestamp', - 'rc_user' => 'log_user', - 'rc_user_text' => 'user_name', - 'rc_namespace' => 'log_namespace', - 'rc_title' => 'log_title', - 'rc_comment' => 'log_comment', - 'rc_minor' => 0, - 'rc_bot' => 0, - 'rc_patrolled' => 1, - 'rc_new' => 0, + 'user', + "$logging LEFT JOIN $page ON (log_namespace=page_namespace AND log_title=page_title)" + ), + array( + 'rc_timestamp' => 'log_timestamp', + 'rc_user' => 'log_user', + 'rc_user_text' => 'user_name', + 'rc_namespace' => 'log_namespace', + 'rc_title' => 'log_title', + 'rc_comment' => 'log_comment', + 'rc_minor' => 0, + 'rc_bot' => 0, + 'rc_patrolled' => 1, + 'rc_new' => 0, 'rc_this_oldid' => 0, 'rc_last_oldid' => 0, - 'rc_type' => RC_LOG, - 'rc_cur_id' => $dbw->cascadingDeletes() ? 'page_id' : 'COALESCE(page_id, 0)', - 'rc_log_type' => 'log_type', + 'rc_type' => RC_LOG, + 'rc_source' => $dbw->addQuotes( RecentChange::SRC_LOG ), + 'rc_cur_id' => $dbw->cascadingDeletes() ? 'page_id' : 'COALESCE(page_id, 0)', + 'rc_log_type' => 'log_type', 'rc_log_action' => 'log_action', - 'rc_logid' => 'log_id', - 'rc_params' => 'log_params', - 'rc_deleted' => 'log_deleted' - ), array( + 'rc_logid' => 'log_id', + 'rc_params' => 'log_params', + 'rc_deleted' => 'log_deleted' + ), + array( 'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $cutoff ) ), 'log_user=user_id', - 'log_type IN(' . implode( ',', $selectLogs ) . ')' - ), __METHOD__, + 'log_type' => $basicRCLogs, + ), + __METHOD__, array(), // INSERT options array( 'ORDER BY' => 'log_timestamp DESC', 'LIMIT' => 5000 ) // SELECT options ); @@ -222,7 +229,8 @@ class RebuildRecentchanges extends Maintenance { $dbw = wfGetDB( DB_MASTER ); - list( $recentchanges, $usergroups, $user ) = $dbw->tableNamesN( 'recentchanges', 'user_groups', 'user' ); + list( $recentchanges, $usergroups, $user ) = + $dbw->tableNamesN( 'recentchanges', 'user_groups', 'user' ); $botgroups = User::getGroupsWithPermission( 'bot' ); $autopatrolgroups = $wgUseRCPatrol ? User::getGroupsWithPermission( 'autopatrol' ) : array(); @@ -288,7 +296,6 @@ class RebuildRecentchanges extends Maintenance { $messageMemc->delete( wfMemcKey( 'rcfeed', $feed, 'timestamp' ) ); # Good enough for now. } } - } $maintClass = "RebuildRecentchanges"; diff --git a/maintenance/rebuildtextindex.php b/maintenance/rebuildtextindex.php index c651f720..bc85c666 100644 --- a/maintenance/rebuildtextindex.php +++ b/maintenance/rebuildtextindex.php @@ -50,8 +50,6 @@ class RebuildTextIndex extends Maintenance { } public function execute() { - global $wgTitle; - // Shouldn't be needed for Postgres $this->db = wfGetDB( DB_MASTER ); if ( $this->db->getType() == 'postgres' ) { @@ -61,15 +59,15 @@ class RebuildTextIndex extends Maintenance { $this->db = wfGetDB( DB_MASTER ); if ( $this->db->getType() == 'sqlite' ) { if ( !DatabaseSqlite::getFulltextSearchModule() ) { - $this->error( "Your version of SQLite module for PHP doesn't support full-text search (FTS3).\n", true ); + $this->error( "Your version of SQLite module for PHP doesn't " + . "support full-text search (FTS3).\n", true ); } if ( !$this->db->checkForEnabledSearch() ) { - $this->error( "Your database schema is not configured for full-text search support. Run update.php.\n", true ); + $this->error( "Your database schema is not configured for " + . "full-text search support. Run update.php.\n", true ); } } - $wgTitle = Title::newFromText( "Rebuild text index script" ); - if ( $this->db->getType() == 'mysql' ) { $this->dropMysqlTextIndex(); $this->populateSearchIndex(); diff --git a/maintenance/refreshImageMetadata.php b/maintenance/refreshImageMetadata.php index 7fe5c4c1..831118ca 100644 --- a/maintenance/refreshImageMetadata.php +++ b/maintenance/refreshImageMetadata.php @@ -5,7 +5,7 @@ * Usage: php refreshImageMetadata.php * * Copyright © 2011 Brian Wolff - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,15 +47,41 @@ class RefreshImageMetadata extends Maintenance { $this->mDescription = 'Script to update image metadata records'; $this->setBatchSize( 200 ); - $this->addOption( 'force', 'Reload metadata from file even if the metadata looks ok', false, false, 'f' ); - $this->addOption( 'broken-only', 'Only fix really broken records, leave old but still compatible records alone.' ); - $this->addOption( 'verbose', 'Output extra information about each upgraded/non-upgraded file.', false, false, 'v' ); + $this->addOption( + 'force', + 'Reload metadata from file even if the metadata looks ok', + false, + false, + 'f' + ); + $this->addOption( + 'broken-only', + 'Only fix really broken records, leave old but still compatible records alone.' + ); + $this->addOption( + 'verbose', + 'Output extra information about each upgraded/non-upgraded file.', + false, + false, + 'v' + ); $this->addOption( 'start', 'Name of file to start with', false, true ); $this->addOption( 'end', 'Name of file to end with', false, true ); - $this->addOption( 'mime', '(Inefficient!) Only refresh files with this mime type. Can accept wild-card image/*', false, true ); - $this->addOption( 'metadata-contains', '(Inefficient!) Only refresh files where the img_metadata field contains this string. Can be used if its known a specific property was being extracted incorrectly.', false, true ); - + $this->addOption( + 'mime', + '(Inefficient!) Only refresh files with this MIME type. Can accept wild-card image/*', + false, + true + ); + $this->addOption( + 'metadata-contains', + '(Inefficient!) Only refresh files where the img_metadata field ' + . 'contains this string. Can be used if its known a specific ' + . 'property was being extracted incorrectly.', + false, + true + ); } public function execute() { @@ -121,7 +147,7 @@ class RefreshImageMetadata extends Maintenance { // to weed out any inconsequential changes. $error++; $this->output( "Warning: File:{$row->img_name} used to have " . - "$oldLength bytes of metadata but now has $newLength bytes.\n" ); + "$oldLength bytes of metadata but now has $newLength bytes.\n" ); } elseif ( $verbose ) { $this->output( "Refreshed File:{$row->img_name}.\n" ); } @@ -134,20 +160,17 @@ class RefreshImageMetadata extends Maintenance { if ( $newLength < $oldLength - 5 ) { $error++; $this->output( "Warning: File:{$row->img_name} used to have " . - "$oldLength bytes of metadata but now has $newLength bytes. (forced)\n" ); - + "$oldLength bytes of metadata but now has $newLength bytes. (forced)\n" ); } if ( $verbose ) { $this->output( "Forcibly refreshed File:{$row->img_name}.\n" ); } - } - else { + } else { if ( $verbose ) { $this->output( "Skipping File:{$row->img_name}.\n" ); } } } - } $conds2 = array( 'img_name > ' . $dbw->addQuotes( $row->img_name ) ); wfWaitForSlaves(); @@ -155,14 +178,18 @@ class RefreshImageMetadata extends Maintenance { $total = $upgraded + $leftAlone; if ( $force ) { - $this->output( "\nFinished refreshing file metadata for $total files. $upgraded needed to be refreshed, $leftAlone did not need to be but were refreshed anyways, and $error refreshes were suspicious.\n" ); + $this->output( "\nFinished refreshing file metadata for $total files. " + . "$upgraded needed to be refreshed, $leftAlone did not need to " + . "be but were refreshed anyways, and $error refreshes were suspicious.\n" ); } else { - $this->output( "\nFinished refreshing file metadata for $total files. $upgraded were refreshed, $leftAlone were already up to date, and $error refreshes were suspicious.\n" ); + $this->output( "\nFinished refreshing file metadata for $total files. " + . "$upgraded were refreshed, $leftAlone were already up to date, " + . "and $error refreshes were suspicious.\n" ); } } /** - * @param $dbw DatabaseBase + * @param DatabaseBase $dbw * @return array */ function getConditions( $dbw ) { @@ -185,12 +212,13 @@ class RefreshImageMetadata extends Maintenance { if ( $like ) { $conds[] = 'img_metadata ' . $dbw->buildLike( $dbw->anyString(), $like, $dbw->anyString() ); } + return $conds; } /** - * @param $force bool - * @param $brokenOnly bool + * @param bool $force + * @param bool $brokenOnly */ function setupParameters( $force, $brokenOnly ) { global $wgUpdateCompatibleMetadata; @@ -207,6 +235,5 @@ class RefreshImageMetadata extends Maintenance { } } - $maintClass = 'RefreshImageMetadata'; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/refreshLinks.php b/maintenance/refreshLinks.php index 98ea9301..0c2f722c 100644 --- a/maintenance/refreshLinks.php +++ b/maintenance/refreshLinks.php @@ -57,15 +57,16 @@ class RefreshLinks extends Maintenance { /** * Do the actual link refreshing. - * @param $start int Page_id to start from - * @param $newOnly bool Only do pages with 1 edit - * @param $maxLag int Max DB replication lag - * @param $end int Page_id to stop at - * @param $redirectsOnly bool Only fix redirects - * @param $oldRedirectsOnly bool Only fix redirects without redirect entries + * @param int $start Page_id to start from + * @param bool $newOnly Only do pages with 1 edit + * @param int $maxLag Max DB replication lag + * @param int $end Page_id to stop at + * @param bool $redirectsOnly Only fix redirects + * @param bool $oldRedirectsOnly Only fix redirects without redirect entries */ private function doRefreshLinks( $start, $newOnly = false, $maxLag = false, - $end = 0, $redirectsOnly = false, $oldRedirectsOnly = false ) { + $end = 0, $redirectsOnly = false, $oldRedirectsOnly = false + ) { global $wgParser, $wgUseTidy; $reportingInterval = 100; @@ -185,7 +186,7 @@ class RefreshLinks extends Maintenance { * entry in the "redirect" table points to the correct page and not to an * invalid one. * - * @param $id int The page ID to check + * @param int $id The page ID to check */ private function fixRedirect( $id ) { $page = WikiPage::newFromID( $id ); @@ -196,6 +197,7 @@ class RefreshLinks extends Maintenance { // Delete any redirect table entry for it $dbw->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ ); + return; } @@ -222,7 +224,7 @@ class RefreshLinks extends Maintenance { /** * Run LinksUpdate for all links on a given page_id - * @param $id int The page_id + * @param int $id The page_id */ public static function fixLinksFromArticle( $id ) { $page = WikiPage::newFromID( $id ); @@ -251,8 +253,8 @@ class RefreshLinks extends Maintenance { * Removes non-existing links from pages from pagelinks, imagelinks, * categorylinks, templatelinks, externallinks, interwikilinks, langlinks and redirect tables. * - * @param $maxLag int - * @param $batchSize int The size of deletion batches + * @param int $maxLag + * @param int $batchSize The size of deletion batches * * @author Merlijn van Deen <valhallasw@arctus.nl> */ diff --git a/maintenance/removeUnusedAccounts.php b/maintenance/removeUnusedAccounts.php index 16cb17ab..90dc6220 100644 --- a/maintenance/removeUnusedAccounts.php +++ b/maintenance/removeUnusedAccounts.php @@ -58,12 +58,13 @@ class RemoveUnusedAccounts extends Maintenance { } $touchedSeconds = 86400 * $touched; foreach ( $res as $row ) { - # Check the account, but ignore it if it's within a $excludedGroups group or if it's touched within the $touchedSeconds seconds. + # 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" ); @@ -85,7 +86,12 @@ class RemoveUnusedAccounts extends Maintenance { $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__ ); + $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" ); } @@ -96,8 +102,8 @@ class RemoveUnusedAccounts extends Maintenance { * 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 bool Perform checking on the master + * @param int $id User's ID + * @param bool $master Perform checking on the master * @return bool */ private function isInactiveAccount( $id, $master = false ) { diff --git a/maintenance/renderDump.php b/maintenance/renderDump.php index 0cde28c5..169f512c 100644 --- a/maintenance/renderDump.php +++ b/maintenance/renderDump.php @@ -7,7 +7,7 @@ * Templates etc are pulled from the local wiki database, not from the dump. * * Copyright (C) 2006 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -78,12 +78,13 @@ class DumpRenderer extends Maintenance { /** * Callback function for each revision, turn into HTML and save - * @param $rev Revision + * @param Revision $rev */ public function handleRevision( $rev ) { $title = $rev->getTitle(); if ( !$title ) { $this->error( "Got bogus revision with null title!" ); + return; } $display = $title->getPrefixedText(); diff --git a/maintenance/resetUserTokens.php b/maintenance/resetUserTokens.php index bfe04d79..08be5537 100644 --- a/maintenance/resetUserTokens.php +++ b/maintenance/resetUserTokens.php @@ -34,9 +34,15 @@ require_once __DIR__ . '/Maintenance.php'; class ResetUserTokens extends Maintenance { public function __construct() { parent::__construct(); - $this->mDescription = "Reset the user_token of all users on the wiki. Note that this may log some of them out."; + $this->mDescription = + "Reset the user_token of all users on the wiki. Note that this may log some of them out."; $this->addOption( 'nowarn', "Hides the 5 seconds warning", false, false ); - $this->addOption( 'nulls', 'Only reset tokens that are currently null (string of \x00\'s)', false, false ); + $this->addOption( + 'nulls', + 'Only reset tokens that are currently null (string of \x00\'s)', + false, + false + ); $this->setBatchSize( 1000 ); } @@ -45,14 +51,16 @@ class ResetUserTokens extends Maintenance { if ( !$this->getOption( 'nowarn' ) ) { if ( $this->nullsOnly ) { - $this->output( "The script is about to reset the user_token for USERS WITH NULL TOKENS in the database.\n" ); + $this->output( "The script is about to reset the user_token " + . "for USERS WITH NULL TOKENS in the database.\n" ); } else { $this->output( "The script is about to reset the user_token for ALL USERS in the database.\n" ); $this->output( "This may log some of them out and is not necessary unless you believe your\n" ); $this->output( "user table has been compromised.\n" ); } $this->output( "\n" ); - $this->output( "Abort with control-c in the next five seconds (skip this countdown with --nowarn) ... " ); + $this->output( "Abort with control-c in the next five seconds " + . "(skip this countdown with --nowarn) ... " ); wfCountDown( 5 ); } @@ -62,7 +70,7 @@ class ResetUserTokens extends Maintenance { $where = array(); if ( $this->nullsOnly ) { // Have to build this by hand, because \ is escaped in helper functions - $where = array( 'user_token = \'' . str_repeat( '\0', 32) . '\'' ); + $where = array( 'user_token = \'' . str_repeat( '\0', 32 ) . '\'' ); } $maxid = $dbr->selectField( 'user', 'MAX(user_id)', array(), __METHOD__ ); @@ -90,9 +98,7 @@ class ResetUserTokens extends Maintenance { $max = $min + $this->mBatchSize; wfWaitForSlaves(); - - } while ( $max <= $maxid ); - + } while ( $min <= $maxid ); } private function updateUser( $userid ) { diff --git a/maintenance/resources/update-oojs-ui.sh b/maintenance/resources/update-oojs-ui.sh new file mode 100644 index 00000000..1b352922 --- /dev/null +++ b/maintenance/resources/update-oojs-ui.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +# This script generates a commit that updates our distribution copy of OOjs UI + +if [ -z "$1" ] +then + # Missing required parameter + echo >&2 "Usage: $0 path/to/repo/for/oojs-ui" + exit 1 +fi + +TARGET_REPO=$(cd "$(dirname $0)/../.."; pwd) +TARGET_DIR=resources/lib/oojs-ui +UI_REPO=$1 + +function oojsuihash() { + grep "OOjs UI v" "$TARGET_REPO/$TARGET_DIR/oojs-ui.js" \ + | head -n 1 \ + | grep -Eo '\([a-z0-9]+\)' \ + | sed 's/^(//' \ + | sed 's/)$//' +} + +function oojsuitag() { + grep "OOjs UI v" "$TARGET_REPO/$TARGET_DIR/oojs-ui.js" \ + | head -n 1 \ + | grep -Eo '\bv[0-9a-z.-]+\b' +} + +function oojsuiversion() { + grep "OOjs UI v" "$TARGET_REPO/$TARGET_DIR/oojs-ui.js" \ + | head -n 1 \ + | grep -Eo '\bv[0-9a-z.-]+\b.*$' +} + +# Prepare working tree +cd "$TARGET_REPO" && +git reset $TARGET_DIR && git checkout $TARGET_DIR && git fetch origin && +git checkout -B upstream-oojsui origin/master || exit 1 + +cd $UI_REPO || exit 1 + +# Read the old version and check for changes +OLDHASH=$(oojsuihash) +if [ -z "$OLDHASH" ] +then + OLDTAG=$(oojsuitag) +fi +if [ "$OLDHASH" == "" ] +then + OLDHASH=$(git rev-parse "$OLDTAG") + if [ $? != 0 ] + then + echo "Could not find OOjs UI version" + cd - + exit 1 + fi +fi +if [ "$(git rev-parse $OLDHASH)" == "$(git rev-parse HEAD)" ] +then + echo "No changes (already at $OLDHASH)" + cd - + exit 0 +fi + +# Build the distribution +npm install && grunt git-build || exit 1 + +# Get the list of changes +NEWCHANGES=$(git log $OLDHASH.. --oneline --no-merges --reverse --color=never) +NEWCHANGESDISPLAY=$(git log $OLDHASH.. --oneline --no-merges --reverse --color=always) + +# Copy files +# - Exclude the default non-svg stylesheet +rsync --recursive --delete --force --exclude 'oojs-ui.css' --exclude 'oojs-ui*.rtl.css' ./dist/ "$TARGET_REPO/$TARGET_DIR" || exit 1 + +# Read the new version +NEWVERSION=$(oojsuiversion) + +# Generate commit +cd "$TARGET_REPO" +COMMITMSG=$(cat <<END +Update OOjs UI to $NEWVERSION + +New changes: +$NEWCHANGES +END +) +git add -u $TARGET_DIR && git add $TARGET_DIR && git commit -m "$COMMITMSG" +cat >&2 <<END + + +Created commit with changes: +$NEWCHANGESDISPLAY +END diff --git a/maintenance/resources/update-oojs.sh b/maintenance/resources/update-oojs.sh new file mode 100644 index 00000000..d9e6fb9d --- /dev/null +++ b/maintenance/resources/update-oojs.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +if [ -n "$2" ] +then + # Too many parameters + echo >&2 "Usage: $0 [<version>]" + exit 1 +fi + +REPO_DIR=$(cd "$(dirname $0)/../.."; pwd) # Root dir of the git repo working tree +TARGET_DIR="resources/lib/oojs" # Destination relative to the root of the repo +NPM_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'update-oojs') # e.g. /tmp/update-oojs.rI0I5Vir + +# Prepare working tree +cd "$REPO_DIR" && +git reset $TARGET_DIR && git checkout $TARGET_DIR && git fetch origin && +git checkout -B upstream-oojs origin/master || exit 1 + +# Fetch upstream version +cd $NPM_DIR +if [ -n "$1" ] +then + npm install "oojs@$1" || exit 1 +else + npm install oojs || exit 1 +fi + +OOJS_VERSION=$(node -e 'console.log(JSON.parse(require("fs").readFileSync("./node_modules/oojs/package.json")).version);') +if [ "$OOJS_VERSION" == "" ] +then + echo 'Could not find OOjs version' + exit 1 +fi + +# Copy file(s) +rsync --force ./node_modules/oojs/dist/oojs.jquery.js "$REPO_DIR/$TARGET_DIR" || exit 1 + +# Clean up temporary area +rm -rf "$NPM_DIR" + +# Generate commit +cd $REPO_DIR || exit 1 + +COMMITMSG=$(cat <<END +Update OOjs to v$OOJS_VERSION + +Release notes: + https://git.wikimedia.org/blob/oojs%2Fcore.git/v$OOJS_VERSION/History.md +END +) + +# Stage deletion, modification and creation of files. Then commit. +git add --update $TARGET_DIR && git add $TARGET_DIR && git commit -m "$COMMITMSG" || exit 1 diff --git a/maintenance/rollbackEdits.php b/maintenance/rollbackEdits.php index e5e33c02..967dda85 100644 --- a/maintenance/rollbackEdits.php +++ b/maintenance/rollbackEdits.php @@ -33,8 +33,14 @@ require_once __DIR__ . '/Maintenance.php'; class RollbackEdits extends Maintenance { public function __construct() { parent::__construct(); - $this->mDescription = "Rollback all edits by a given user or IP provided they're the most recent edit"; - $this->addOption( 'titles', 'A list of titles, none means all titles where the given user is the most recent', false, true ); + $this->mDescription = + "Rollback all edits by a given user or IP provided they're the most recent edit"; + $this->addOption( + 'titles', + 'A list of titles, none means all titles where the given user is the most recent', + false, + true + ); $this->addOption( 'user', 'A user or IP to rollback all edits for', true, true ); $this->addOption( 'summary', 'Edit summary to use', false, true ); $this->addOption( 'bot', 'Mark the edits as bot' ); @@ -66,6 +72,7 @@ class RollbackEdits extends Maintenance { if ( !$titles ) { $this->output( 'No suitable titles to be rolled back' ); + return; } @@ -84,7 +91,7 @@ class RollbackEdits extends Maintenance { /** * Get all pages that should be rolled back for a given user - * @param $user String a name to check against rev_user_text + * @param string $user A name to check against rev_user_text * @return array */ private function getRollbackTitles( $user ) { @@ -99,6 +106,7 @@ class RollbackEdits extends Maintenance { foreach ( $results as $row ) { $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title ); } + return $titles; } } diff --git a/maintenance/runBatchedQuery.php b/maintenance/runBatchedQuery.php index 93ba24a9..af889050 100644 --- a/maintenance/runBatchedQuery.php +++ b/maintenance/runBatchedQuery.php @@ -33,7 +33,8 @@ require_once __DIR__ . '/Maintenance.php'; class BatchedQueryRunner extends Maintenance { public function __construct() { parent::__construct(); - $this->mDescription = "Run a query repeatedly until it affects 0 rows, and wait for slaves in between.\n" . + $this->mDescription = + "Run a query repeatedly until it affects 0 rows, and wait for slaves in between.\n" . "NOTE: You need to set a LIMIT clause yourself."; } @@ -60,6 +61,5 @@ class BatchedQueryRunner extends Maintenance { } } - $maintClass = "BatchedQueryRunner"; require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/runJobs.php b/maintenance/runJobs.php index 429edf42..40605ceb 100644 --- a/maintenance/runJobs.php +++ b/maintenance/runJobs.php @@ -2,10 +2,6 @@ /** * Run pending jobs. * - * Options: - * --maxjobs <num> (default 10000) - * --type <job_cmd> - * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -40,19 +36,20 @@ class RunJobs extends Maintenance { $this->addOption( 'maxtime', 'Maximum amount of wall-clock time', false, true ); $this->addOption( 'type', 'Type of job to run', false, true ); $this->addOption( 'procs', 'Number of processes to use', false, true ); + $this->addOption( 'nothrottle', 'Ignore job throttling configuration', false, false ); + $this->addOption( 'result', 'Set to JSON to print only a JSON response', false, true ); } public function memoryLimit() { if ( $this->hasOption( 'memory-limit' ) ) { return parent::memoryLimit(); } + // Don't eat all memory on the machine if we get a bad job. return "150M"; } public function execute() { - global $wgTitle; - if ( wfReadOnly() ) { $this->error( "Unable to run jobs; the wiki is in read-only mode.", 1 ); // die } @@ -68,112 +65,29 @@ class RunJobs extends Maintenance { } } } - $maxJobs = $this->getOption( 'maxjobs', false ); - $maxTime = $this->getOption( 'maxtime', false ); - $startTime = time(); - $type = $this->getOption( 'type', false ); - $wgTitle = Title::newFromText( 'RunJobs.php' ); - $jobsRun = 0; // counter - - $group = JobQueueGroup::singleton(); - // Handle any required periodic queue maintenance - $count = $group->executeReadyPeriodicTasks(); - if ( $count > 0 ) { - $this->runJobsLog( "Executed $count periodic queue task(s)." ); - } - - $flags = JobQueueGroup::USE_CACHE | JobQueueGroup::USE_PRIORITY; - $lastTime = time(); // time since last slave check - do { - $job = ( $type === false ) - ? $group->pop( JobQueueGroup::TYPE_DEFAULT, $flags ) - : $group->pop( $type ); // job from a single queue - if ( $job ) { // found a job - ++$jobsRun; - $this->runJobsLog( $job->toString() . " STARTING" ); - - // Set timer to stop the job if too much CPU time is used - set_time_limit( $maxTime ?: 0 ); - // Run the job... - wfProfileIn( __METHOD__ . '-' . get_class( $job ) ); - $t = microtime( true ); - try { - $status = $job->run(); - $error = $job->getLastError(); - } catch ( MWException $e ) { - $status = false; - $error = get_class( $e ) . ': ' . $e->getMessage(); - $e->report(); // write error to STDERR and the log - } - $timeMs = intval( ( microtime( true ) - $t ) * 1000 ); - wfProfileOut( __METHOD__ . '-' . get_class( $job ) ); - // Disable the timer - set_time_limit( 0 ); - - // Mark the job as done on success or when the job cannot be retried - if ( $status !== false || !$job->allowRetries() ) { - $group->ack( $job ); // done - } - - if ( $status === false ) { - $this->runJobsLog( $job->toString() . " t=$timeMs error={$error}" ); - } else { - $this->runJobsLog( $job->toString() . " t=$timeMs good" ); - } - - // Break out if we hit the job count or wall time limits... - if ( $maxJobs && $jobsRun >= $maxJobs ) { - break; - } elseif ( $maxTime && ( time() - $startTime ) > $maxTime ) { - break; - } - // Don't let any of the main DB slaves get backed up - $timePassed = time() - $lastTime; - if ( $timePassed >= 5 || $timePassed < 0 ) { - wfWaitForSlaves(); - $lastTime = time(); - } - // Don't let any queue slaves/backups fall behind - if ( $jobsRun > 0 && ( $jobsRun % 100 ) == 0 ) { - $group->waitForBackups(); - } + $json = ( $this->getOption( 'result' ) === 'json' ); - // Bail if near-OOM instead of in a job - $this->assertMemoryOK(); - } - } while ( $job ); // stop when there are no jobs - } - - /** - * Make sure that this script is not too close to the memory usage limit - * @throws MWException - */ - private function assertMemoryOK() { - static $maxBytes = null; - if ( $maxBytes === null ) { - $m = array(); - if ( preg_match( '!^(\d+)(k|m|g|)$!i', ini_get( 'memory_limit' ), $m ) ) { - list( , $num, $unit ) = $m; - $conv = array( 'g' => 1024 * 1024 * 1024, 'm' => 1024 * 1024, 'k' => 1024, '' => 1 ); - $maxBytes = $num * $conv[strtolower( $unit )]; - } else { - $maxBytes = 0; - } + $runner = new JobRunner(); + if ( !$json ) { + $runner->setDebugHandler( array( $this, 'debugInternal' ) ); } - $usedBytes = memory_get_usage(); - if ( $maxBytes && $usedBytes >= 0.95 * $maxBytes ) { - throw new MWException( "Detected excessive memory usage ($usedBytes/$maxBytes)." ); + $response = $runner->run( array( + 'type' => $this->getOption( 'type', false ), + 'maxJobs' => $this->getOption( 'maxjobs', false ), + 'maxTime' => $this->getOption( 'maxtime', false ), + 'throttle' => $this->hasOption( 'nothrottle' ) ? false : true, + ) ); + if ( $json ) { + $this->output( FormatJson::encode( $response, true ) ); } } /** - * Log the job message - * @param $msg String The message to log + * @param string $s */ - private function runJobsLog( $msg ) { - $this->output( wfTimestamp( TS_DB ) . " $msg\n" ); - wfDebugLog( 'runJobs', $msg ); + public function debugInternal( $s ) { + $this->output( $s ); } } diff --git a/maintenance/runScript.php b/maintenance/runScript.php new file mode 100644 index 00000000..385db157 --- /dev/null +++ b/maintenance/runScript.php @@ -0,0 +1,64 @@ +<?php +/** + * Convenience maintenance script wrapper, useful for scripts + * or extensions located outside of standard locations. + * + * To use, give the maintenance script as a relative or full path. + * + * Example usage: + * + * If your pwd is mediawiki base folder: + * php maintenance/runScript.php extensions/Wikibase/lib/maintenance/dispatchChanges.php + * + * If your pwd is maintenance folder: + * php runScript.php ../extensions/Wikibase/lib/maintenance/dispatchChanges.php + * + * Or full path: + * php /var/www/mediawiki/maintenance/runScript.php maintenance/runJobs.php + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @author Katie Filbert < aude.wiki@gmail.com > + * @file + * @ingroup Maintenance + */ +$IP = getenv( 'MW_INSTALL_PATH' ); + +if ( $IP === false ) { + $IP = dirname( __DIR__ ); + + putenv( "MW_INSTALL_PATH=$IP" ); +} + +require_once "$IP/maintenance/Maintenance.php"; + +if ( !isset( $argv[1] ) ) { + fwrite( STDERR, "This script requires a maintainance script as an argument.\n" + . "Usage: runScript.php extensions/Wikibase/lib/maintenance/dispatchChanges\n" ); + exit( 1 ); +} + +$scriptFilename = $argv[1]; +array_shift( $argv ); + +$scriptFile = realpath( $scriptFilename ); + +if ( !$scriptFile ) { + fwrite( STDERR, "The MediaWiki script file \"{$scriptFilename}\" does not exist.\n" ); + exit( 1 ); +} + +require_once $scriptFile; diff --git a/maintenance/showCacheStats.php b/maintenance/showCacheStats.php index cd9768d4..3d16af14 100644 --- a/maintenance/showCacheStats.php +++ b/maintenance/showCacheStats.php @@ -46,17 +46,6 @@ class ShowCacheStats extends Maintenance { if ( get_class( $wgMemc ) == 'EmptyBagOStuff' ) { $this->error( "You are running EmptyBagOStuff, I can not provide any statistics.", 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 the cache isn't running. Aborting.", 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' ) ) ); @@ -66,8 +55,16 @@ class ShowCacheStats extends Maintenance { $total = $hits + $expired + $absent + $stub; if ( $total ) { $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits / $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( + "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 ) ); } else { @@ -81,7 +78,11 @@ class ShowCacheStats extends Maintenance { $total = $hits + $misses; if ( $total ) { $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( + "misses: %-10d %6.2f%%\n", + $misses, + $misses / $total * 100 + ) ); $this->output( sprintf( "updates: %-10d\n", $updates ) ); } else { $this->output( "no statistics available\n" ); @@ -94,8 +95,16 @@ class ShowCacheStats extends Maintenance { $total = $hits + $misses + $uncacheable; if ( $total ) { $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 ) ); + $this->output( sprintf( + "misses: %-10d %6.2f%%\n", + $misses, + $misses / $total * 100 + ) ); + $this->output( sprintf( + "uncacheable: %-10d %6.2f%%\n", + $uncacheable, + $uncacheable / $total * 100 + ) ); } else { $this->output( "no statistics available\n" ); } diff --git a/maintenance/showJobs.php b/maintenance/showJobs.php index afd7c745..b8dc5548 100644 --- a/maintenance/showJobs.php +++ b/maintenance/showJobs.php @@ -38,7 +38,10 @@ class ShowJobs extends Maintenance { parent::__construct(); $this->mDescription = "Show number of jobs waiting in master database"; $this->addOption( 'group', 'Show number of jobs per job type' ); - $this->addOption( 'list', 'Show a complete list of all jobs in a machine-readable format, instead of statistics' ); + $this->addOption( + 'list', + 'Show a complete list of all jobs in a machine-readable format, instead of statistics' + ); } public function execute() { @@ -56,14 +59,16 @@ class ShowJobs extends Maintenance { } elseif ( $this->hasOption( 'group' ) ) { foreach ( $group->getQueueTypes() as $type ) { $queue = $group->get( $type ); + $delayed = $queue->getDelayedCount(); $pending = $queue->getSize(); $claimed = $queue->getAcquiredCount(); $abandoned = $queue->getAbandonedCount(); $active = max( 0, $claimed - $abandoned ); - if ( ( $pending + $claimed ) > 0 ) { + if ( ( $pending + $claimed + $delayed + $abandoned ) > 0 ) { $this->output( "{$type}: $pending queued; " . - "$claimed claimed ($active active, $abandoned abandoned)\n" + "$claimed claimed ($active active, $abandoned abandoned); " . + "$delayed delayed\n" ); } } diff --git a/maintenance/showSiteStats.php b/maintenance/showSiteStats.php index 49148b33..374a66e9 100644 --- a/maintenance/showSiteStats.php +++ b/maintenance/showSiteStats.php @@ -41,6 +41,7 @@ class ShowSiteStats extends Maintenance { parent::__construct(); $this->mDescription = "Show the cached statistics"; } + public function execute() { $fields = array( 'ss_total_views' => 'Total views', @@ -65,7 +66,11 @@ class ShowSiteStats extends Maintenance { // Show them foreach ( $fields as $field => $desc ) { - $this->output( sprintf( "%-{$max_length_desc}s: %{$max_length_value}d\n", $desc, $stats->$field ) ); + $this->output( sprintf( + "%-{$max_length_desc}s: %{$max_length_value}d\n", + $desc, + $stats->$field + ) ); } } } diff --git a/maintenance/sql.php b/maintenance/sql.php index a628b0bc..afa3ef72 100644 --- a/maintenance/sql.php +++ b/maintenance/sql.php @@ -34,15 +34,17 @@ class MwSql extends Maintenance { parent::__construct(); $this->mDescription = "Send SQL queries to a MediaWiki database"; $this->addOption( 'cluster', 'Use an external cluster by name', false, true ); + $this->addOption( 'wikidb', 'The database wiki ID to use if not the current one', false, true ); $this->addOption( 'slave', 'Use a slave server (either "any" or by name)', false, true ); } public function execute() { + $wiki = $this->getOption( 'wikidb' ) ?: false; // Get the appropriate load balancer (for this wiki) if ( $this->hasOption( 'cluster' ) ) { - $lb = wfGetLBFactory()->getExternalLB( $this->getOption( 'cluster' ) ); + $lb = wfGetLBFactory()->getExternalLB( $this->getOption( 'cluster' ), $wiki ); } else { - $lb = wfGetLB(); + $lb = wfGetLB( $wiki ); } // Figure out which server to use if ( $this->hasOption( 'slave' ) ) { @@ -51,7 +53,8 @@ class MwSql extends Maintenance { $index = DB_SLAVE; } else { $index = null; - for ( $i = 0; $i < $lb->getServerCount(); ++$i ) { + $serverCount = $lb->getServerCount(); + for ( $i = 0; $i < $serverCount; ++$i ) { if ( $lb->getServerName( $i ) === $server ) { $index = $i; break; @@ -65,9 +68,9 @@ class MwSql extends Maintenance { $index = DB_MASTER; } // Get a DB handle (with this wiki's DB selected) from the appropriate load balancer - $dbw = $lb->getConnection( $index ); - if ( $this->hasOption( 'slave' ) && $dbw->getLBInfo( 'master' ) !== null ) { - $this->error( "The server selected ({$dbw->getServer()}) is not a slave.", 1 ); + $db = $lb->getConnection( $index, array(), $wiki ); + if ( $this->hasOption( 'slave' ) && $db->getLBInfo( 'master' ) !== null ) { + $this->error( "The server selected ({$db->getServer()}) is not a slave.", 1 ); } if ( $this->hasArg( 0 ) ) { @@ -76,7 +79,7 @@ class MwSql extends Maintenance { $this->error( "Unable to open input file", true ); } - $error = $dbw->sourceStream( $file, false, array( $this, 'sqlPrintResult' ) ); + $error = $db->sourceStream( $file, false, array( $this, 'sqlPrintResult' ) ); if ( $error !== true ) { $this->error( $error, true ); } else { @@ -85,12 +88,12 @@ class MwSql extends Maintenance { } $useReadline = function_exists( 'readline_add_history' ) - && Maintenance::posix_isatty( 0 /*STDIN*/ ); + && Maintenance::posix_isatty( 0 /*STDIN*/ ); if ( $useReadline ) { global $IP; $historyFile = isset( $_ENV['HOME'] ) ? - "{$_ENV['HOME']}/.mwsql_history" : "$IP/maintenance/.mwsql_history"; + "{$_ENV['HOME']}/.mwsql_history" : "$IP/maintenance/.mwsql_history"; readline_read_history( $historyFile ); } @@ -102,7 +105,7 @@ class MwSql extends Maintenance { # User simply pressed return key continue; } - $done = $dbw->streamStatementEnd( $wholeLine, $line ); + $done = $db->streamStatementEnd( $wholeLine, $line ); $wholeLine .= $line; @@ -114,16 +117,16 @@ class MwSql extends Maintenance { if ( $useReadline ) { # Delimiter is eated by streamStatementEnd, we add it # up in the history (bug 37020) - readline_add_history( $wholeLine . $dbw->getDelimiter() ); + readline_add_history( $wholeLine . $db->getDelimiter() ); readline_write_history( $historyFile ); } try { - $res = $dbw->query( $wholeLine ); - $this->sqlPrintResult( $res, $dbw ); + $res = $db->query( $wholeLine ); + $this->sqlPrintResult( $res, $db ); $prompt = $newPrompt; $wholeLine = ''; } catch ( DBQueryError $e ) { - $doDie = ! Maintenance::posix_isatty( 0 ); + $doDie = !Maintenance::posix_isatty( 0 ); $this->error( $e, $doDie ); } } @@ -132,8 +135,8 @@ class MwSql extends Maintenance { /** * Print the results, callback for $db->sourceStream() - * @param $res ResultWrapper The results object - * @param $db DatabaseBase object + * @param ResultWrapper $res The results object + * @param DatabaseBase $db */ public function sqlPrintResult( $res, $db ) { if ( !$res ) { diff --git a/maintenance/sqlite.inc b/maintenance/sqlite.inc index 08188cad..5c0fd07f 100644 --- a/maintenance/sqlite.inc +++ b/maintenance/sqlite.inc @@ -40,9 +40,9 @@ class Sqlite { * Checks given files for correctness of SQL syntax. MySQL DDL will be converted to * SQLite-compatible during processing. * Will throw exceptions on SQL errors - * @param $files + * @param array|string $files * @throws MWException - * @return mixed true if no error or error string in case of errors + * @return bool True if no error or error string in case of errors */ public static function checkSqlSyntax( $files ) { if ( !Sqlite::isPresent() ) { @@ -78,6 +78,7 @@ class Sqlite { foreach ( $columns as $col ) { if ( !isset( $allowedTypes[strtolower( $col->type )] ) ) { $db->close(); + return "Table {$table->name} has column {$col->name} with non-native type '{$col->type}'"; } } @@ -86,6 +87,7 @@ class Sqlite { return $e->getMessage(); } $db->close(); + return true; } -}; +} diff --git a/maintenance/sqlite.php b/maintenance/sqlite.php index 8a785245..edc9e145 100644 --- a/maintenance/sqlite.php +++ b/maintenance/sqlite.php @@ -32,7 +32,10 @@ class SqliteMaintenance extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Performs some operations specific to SQLite database backend"; - $this->addOption( 'vacuum', 'Clean up database by removing deleted pages. Decreases database file size' ); + $this->addOption( + 'vacuum', + 'Clean up database by removing deleted pages. Decreases database file size' + ); $this->addOption( 'integrity', 'Check database for integrity' ); $this->addOption( 'backup-to', 'Backup database to the given file', false, true ); $this->addOption( 'check-syntax', 'Check SQL file(s) for syntax errors', false, true ); @@ -52,6 +55,7 @@ class SqliteMaintenance extends Maintenance { // Should work even if we use a non-SQLite database if ( $this->hasOption( 'check-syntax' ) ) { $this->checkSyntax(); + return; } @@ -59,6 +63,7 @@ class SqliteMaintenance extends Maintenance { if ( $this->db->getType() != 'sqlite' ) { $this->error( "This maintenance script requires a SQLite database.\n" ); + return; } @@ -98,6 +103,7 @@ class SqliteMaintenance extends Maintenance { if ( !$res || $res->numRows() == 0 ) { $this->error( "Error: integrity check query returned nothing.\n" ); + return; } diff --git a/maintenance/sqlite/archives/initial-indexes.sql b/maintenance/sqlite/archives/initial-indexes.sql index 1a59be5a..954c85d3 100644 --- a/maintenance/sqlite/archives/initial-indexes.sql +++ b/maintenance/sqlite/archives/initial-indexes.sql @@ -237,13 +237,13 @@ CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki_tmp (iw_prefix); CREATE TABLE /*_*/page_restrictions_tmp ( + pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, pr_page int NOT NULL, pr_type varbinary(60) NOT NULL, pr_level varbinary(60) NOT NULL, pr_cascade tinyint NOT NULL, pr_user int NULL, - pr_expiry varbinary(14) NULL, - pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT + pr_expiry varbinary(14) NULL ); CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions_tmp (pr_page,pr_type); diff --git a/maintenance/sqlite/archives/patch-drop-rc_cur_time.sql b/maintenance/sqlite/archives/patch-drop-rc_cur_time.sql new file mode 100644 index 00000000..350479fb --- /dev/null +++ b/maintenance/sqlite/archives/patch-drop-rc_cur_time.sql @@ -0,0 +1,45 @@ +-- rc_cur_time is no longer used, delete the field +CREATE TABLE /*_*/recentchanges_tmp ( + rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT, + rc_timestamp varbinary(14) NOT NULL default '', + rc_user int unsigned NOT NULL default 0, + rc_user_text varchar(255) binary NOT NULL, + rc_namespace int NOT NULL default 0, + rc_title varchar(255) binary NOT NULL default '', + rc_comment varchar(255) binary NOT NULL default '', + rc_minor tinyint unsigned NOT NULL default 0, + rc_bot tinyint unsigned NOT NULL default 0, + rc_new tinyint unsigned NOT NULL default 0, + rc_cur_id int unsigned NOT NULL default 0, + rc_this_oldid int unsigned NOT NULL default 0, + rc_last_oldid int unsigned NOT NULL default 0, + rc_type tinyint unsigned NOT NULL default 0, + rc_source varchar(16) binary not null default '', + rc_patrolled tinyint unsigned NOT NULL default 0, + rc_ip varbinary(40) NOT NULL default '', + rc_old_len int, + rc_new_len int, + rc_deleted tinyint unsigned NOT NULL default 0, + rc_logid int unsigned NOT NULL default 0, + rc_log_type varbinary(255) NULL default NULL, + rc_log_action varbinary(255) NULL default NULL, + rc_params blob NULL +) /*$wgDBTableOptions*/; + +INSERT INTO /*_*/recentchanges_tmp + SELECT rc_id, rc_timestamp, rc_user, rc_user_text, rc_namespace, rc_title, rc_comment, rc_minor, + rc_bot, rc_new, rc_cur_id, rc_this_oldid, rc_last_oldid, rc_type, rc_source, rc_patrolled, + rc_ip, rc_old_len, rc_new_len, rc_deleted, rc_logid, rc_log_type, rc_log_action, rc_params + FROM /*_*/recentchanges; + +DROP TABLE /*_*/recentchanges; + +ALTER TABLE /*_*/recentchanges_tmp RENAME TO /*_*/recentchanges; + +CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp); +CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title); +CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id); +CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp); +CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip); +CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text); +CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
\ No newline at end of file diff --git a/maintenance/sqlite/archives/patch-page-page_lang.sql b/maintenance/sqlite/archives/patch-page-page_lang.sql new file mode 100644 index 00000000..8de2dc7b --- /dev/null +++ b/maintenance/sqlite/archives/patch-page-page_lang.sql @@ -0,0 +1,3 @@ +-- Add page_lang column + +ALTER TABLE /*$wgDBprefix*/page ADD COLUMN page_lang TEXT default NULL; diff --git a/maintenance/storage/checkStorage.php b/maintenance/storage/checkStorage.php index 03dc113a..0f996625 100644 --- a/maintenance/storage/checkStorage.php +++ b/maintenance/storage/checkStorage.php @@ -34,7 +34,6 @@ if ( !defined( 'MEDIAWIKI' ) ) { $cs->check( $fix, $xml ); } - // ---------------------------------------------------------------------------------- /** @@ -212,8 +211,12 @@ class CheckStorage { $curIds = array(); if ( count( $objectRevs ) ) { $headerLength = 300; - $res = $dbr->select( 'text', array( 'old_id', 'old_flags', "LEFT(old_text, $headerLength) AS header" ), - array( 'old_id IN (' . implode( ',', $objectRevs ) . ')' ), __METHOD__ ); + $res = $dbr->select( + 'text', + array( 'old_id', 'old_flags', "LEFT(old_text, $headerLength) AS header" ), + array( 'old_id IN (' . implode( ',', $objectRevs ) . ')' ), + __METHOD__ + ); foreach ( $res as $row ) { $oldId = $row->old_id; $matches = array(); @@ -224,7 +227,11 @@ class CheckStorage { $className = strtolower( $matches[2] ); if ( strlen( $className ) != $matches[1] ) { - $this->error( 'restore text', "Error: invalid object header, wrong class name length", $oldId ); + $this->error( + 'restore text', + "Error: invalid object header, wrong class name length", + $oldId + ); continue; } @@ -263,8 +270,12 @@ class CheckStorage { $externalConcatBlobs = array(); if ( count( $concatBlobs ) ) { $headerLength = 300; - $res = $dbr->select( 'text', array( 'old_id', 'old_flags', "LEFT(old_text, $headerLength) AS header" ), - array( 'old_id IN (' . implode( ',', array_keys( $concatBlobs ) ) . ')' ), __METHOD__ ); + $res = $dbr->select( + 'text', + array( 'old_id', 'old_flags', "LEFT(old_text, $headerLength) AS header" ), + array( 'old_id IN (' . implode( ',', array_keys( $concatBlobs ) ) . ')' ), + __METHOD__ + ); foreach ( $res as $row ) { $flags = explode( ',', $row->old_flags ); if ( in_array( 'external', $flags ) ) { @@ -272,7 +283,11 @@ class CheckStorage { if ( in_array( 'object', $flags ) ) { $urlParts = explode( '/', $row->header ); if ( $urlParts[0] != 'DB:' ) { - $this->error( 'unfixable', "Error: unrecognised external storage type \"{$urlParts[0]}", $row->old_id ); + $this->error( + 'unfixable', + "Error: unrecognised external storage type \"{$urlParts[0]}", + $row->old_id + ); } else { $cluster = $urlParts[2]; $id = $urlParts[3]; @@ -284,12 +299,20 @@ class CheckStorage { ); } } else { - $this->error( 'unfixable', "Error: invalid flags \"{$row->old_flags}\" on concat bulk row {$row->old_id}", + $this->error( + 'unfixable', + "Error: invalid flags \"{$row->old_flags}\" on concat bulk row {$row->old_id}", $concatBlobs[$row->old_id] ); } - } elseif ( strcasecmp( substr( $row->header, 0, strlen( self::CONCAT_HEADER ) ), self::CONCAT_HEADER ) ) { - $this->error( 'restore text', "Error: Incorrect object header for concat bulk row {$row->old_id}", - $concatBlobs[$row->old_id] ); + } elseif ( strcasecmp( + substr( $row->header, 0, strlen( self::CONCAT_HEADER ) ), + self::CONCAT_HEADER + ) ) { + $this->error( + 'restore text', + "Error: Incorrect object header for concat bulk row {$row->old_id}", + $concatBlobs[$row->old_id] + ); } # else good unset( $concatBlobs[$row->old_id] ); @@ -299,7 +322,6 @@ class CheckStorage { // Check targets of unresolved stubs $this->checkExternalConcatBlobs( $externalConcatBlobs ); - // next chunk } @@ -331,7 +353,6 @@ class CheckStorage { } } - function error( $type, $msg, $ids ) { if ( is_array( $ids ) && count( $ids ) == 1 ) { $ids = reset( $ids ); @@ -374,17 +395,23 @@ class CheckStorage { array( 'blob_id IN( ' . implode( ',', $blobIds ) . ')' ), __METHOD__ ); foreach ( $res as $row ) { if ( strcasecmp( $row->header, self::CONCAT_HEADER ) ) { - $this->error( 'restore text', "Error: invalid header on target $cluster/{$row->blob_id} of two-part ES URL", - $oldIds[$row->blob_id] ); + $this->error( + 'restore text', + "Error: invalid header on target $cluster/{$row->blob_id} of two-part ES URL", + $oldIds[$row->blob_id] + ); } unset( $oldIds[$row->blob_id] ); - } $extDb->freeResult( $res ); // Print errors for missing blobs rows foreach ( $oldIds as $blobId => $oldIds2 ) { - $this->error( 'restore text', "Error: missing target $cluster/$blobId for two-part ES URL", $oldIds2 ); + $this->error( + 'restore text', + "Error: missing target $cluster/$blobId for two-part ES URL", + $oldIds2 + ); } } } @@ -405,6 +432,7 @@ class CheckStorage { // Write revision list if ( !file_put_contents( $revFileName, implode( "\n", $revIds ) ) ) { echo "Error writing revision list, can't restore text\n"; + return; } @@ -421,12 +449,14 @@ class CheckStorage { if ( $exitStatus ) { echo "mwdumper died with exit status $exitStatus\n"; + return; } $file = fopen( $filteredXmlFileName, 'r' ); if ( !$file ) { echo "Unable to open filtered XML file\n"; + return; } @@ -448,6 +478,7 @@ class CheckStorage { if ( $content === null ) { echo "Revision $id is broken, we have no content available\n"; + return; } @@ -459,12 +490,14 @@ class CheckStorage { // be safe, we'll skip it and leave it broken echo "Revision $id is blank in the dump, may have been broken before export\n"; + return; } if ( !$id ) { // No ID, can't import echo "No id tag in revision, can't import\n"; + return; } @@ -473,6 +506,7 @@ class CheckStorage { $oldId = $dbr->selectField( 'revision', 'rev_text_id', array( 'rev_id' => $id ), __METHOD__ ); if ( !$oldId ) { echo "Missing revision row for rev_id $id\n"; + return; } diff --git a/maintenance/storage/compressOld.php b/maintenance/storage/compressOld.php index 8cb55487..cfffbbca 100644 --- a/maintenance/storage/compressOld.php +++ b/maintenance/storage/compressOld.php @@ -59,12 +59,41 @@ class CompressOld extends Maintenance { parent::__construct(); $this->mDescription = 'Compress the text of a wiki'; $this->addOption( 'type', 'Set compression type to either: gzip|concat', false, true, 't' ); - $this->addOption( 'chunksize', 'Maximum number of revisions in a concat chunk', false, true, 'c' ); - $this->addOption( 'begin-date', 'Earliest date to check for uncompressed revisions', false, true, 'b' ); + $this->addOption( + 'chunksize', + 'Maximum number of revisions in a concat chunk', + false, + true, + 'c' + ); + $this->addOption( + 'begin-date', + 'Earliest date to check for uncompressed revisions', + false, + true, + 'b' + ); $this->addOption( 'end-date', 'Latest revision date to compress', false, true, 'e' ); - $this->addOption( 'startid', 'The id to start from (gzip -> text table, concat -> page table)', false, true, 's' ); - $this->addOption( 'extdb', 'Store specified revisions in an external cluster (untested)', false, true ); - $this->addOption( 'endid', 'The page_id to stop at (only when using concat compression type)', false, true, 'n' ); + $this->addOption( + 'startid', + 'The id to start from (gzip -> text table, concat -> page table)', + false, + true, + 's' + ); + $this->addOption( + 'extdb', + 'Store specified revisions in an external cluster (untested)', + false, + true + ); + $this->addOption( + 'endid', + 'The page_id to stop at (only when using concat compression type)', + false, + true, + 'n' + ); } public function execute() { @@ -107,23 +136,36 @@ class CompressOld extends Maintenance { } } - /** @todo document */ + /** + * @todo document + * @param int $start + * @param string $extdb + */ private function compressOldPages( $start = 0, $extdb = '' ) { $chunksize = 50; $this->output( "Starting from old_id $start...\n" ); $dbw = wfGetDB( DB_MASTER ); do { - $res = $dbw->select( 'text', array( 'old_id', 'old_flags', 'old_text' ), - "old_id>=$start", __METHOD__, array( 'ORDER BY' => 'old_id', 'LIMIT' => $chunksize, 'FOR UPDATE' ) ); + $res = $dbw->select( + 'text', + array( 'old_id', 'old_flags', 'old_text' ), + "old_id>=$start", + __METHOD__, + array( 'ORDER BY' => 'old_id', 'LIMIT' => $chunksize, 'FOR UPDATE' ) + ); + if ( $res->numRows() == 0 ) { break; } + $last = $start; + foreach ( $res as $row ) { # print " {$row->old_id} - {$row->old_namespace}:{$row->old_title}\n"; $this->compressPage( $row, $extdb ); $last = $row->old_id; } + $start = $last + 1; # Deletion may leave long empty stretches $this->output( "$start...\n" ); } while ( true ); @@ -131,12 +173,14 @@ class CompressOld extends Maintenance { /** * @todo document - * @param $row - * @param $extdb + * @param stdClass $row + * @param string $extdb * @return bool */ private function compressPage( $row, $extdb ) { - if ( false !== strpos( $row->old_flags, 'gzip' ) || false !== strpos( $row->old_flags, 'object' ) ) { + if ( false !== strpos( $row->old_flags, 'gzip' ) + || false !== strpos( $row->old_flags, 'object' ) + ) { #print "Already compressed row {$row->old_id}\n"; return false; } @@ -150,6 +194,7 @@ class CompressOld extends Maintenance { $compress = $storeObj->store( $extdb, $compress ); if ( $compress === false ) { $this->error( "Unable to store object" ); + return false; } } @@ -164,21 +209,22 @@ class CompressOld extends Maintenance { ), __METHOD__, array( 'LIMIT' => 1 ) ); + return true; } /** - * @param $startId - * @param $maxChunkSize - * @param $beginDate - * @param $endDate - * @param $extdb string - * @param $maxPageId bool|int + * @param int $startId + * @param int $maxChunkSize + * @param string $beginDate + * @param string $endDate + * @param string $extdb + * @param bool|int $maxPageId * @return bool */ private function compressWithConcat( $startId, $maxChunkSize, $beginDate, - $endDate, $extdb = "", $maxPageId = false ) - { + $endDate, $extdb = "", $maxPageId = false + ) { $loadStyle = self::LS_CHUNKED; $dbr = wfGetDB( DB_SLAVE ); @@ -213,12 +259,15 @@ class CompressOld extends Maintenance { # overwriting bulk storage concat rows. Don't compress external references, because # the script doesn't yet delete rows from external storage. $conds = array( - 'old_flags NOT ' . $dbr->buildLike( $dbr->anyString(), 'object', $dbr->anyString() ) . ' AND old_flags NOT ' - . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ) ); + 'old_flags NOT ' . $dbr->buildLike( $dbr->anyString(), 'object', $dbr->anyString() ) + . ' AND old_flags NOT ' + . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ) + ); if ( $beginDate ) { if ( !preg_match( '/^\d{14}$/', $beginDate ) ) { $this->error( "Invalid begin date \"$beginDate\"\n" ); + return false; } $conds[] = "rev_timestamp>'" . $beginDate . "'"; @@ -226,6 +275,7 @@ class CompressOld extends Maintenance { if ( $endDate ) { if ( !preg_match( '/^\d{14}$/', $endDate ) ) { $this->error( "Invalid end date \"$endDate\"\n" ); + return false; } $conds[] = "rev_timestamp<'" . $endDate . "'"; @@ -303,8 +353,10 @@ class CompressOld extends Maintenance { $usedChunk = false; $primaryOldid = $revs[$i]->rev_text_id; + // @codingStandardsIgnoreStart Ignore avoid function calls in a FOR loop test part warning # Get the text of each revision and add it to the object for ( $j = 0; $j < $thisChunkSize && $chunk->isHappy(); $j++ ) { + // @codingStandardsIgnoreEnd $oldid = $revs[$i + $j]->rev_text_id; # Get text @@ -350,9 +402,10 @@ class CompressOld extends Maintenance { if ( $usedChunk ) { if ( $extdb != "" ) { # Move blob objects to External Storage - $stored = $storeObj->store( $extdb, serialize( $chunk )); + $stored = $storeObj->store( $extdb, serialize( $chunk ) ); if ( $stored === false ) { $this->error( "Unable to store object" ); + return false; } # Store External Storage URLs instead of Stub placeholders @@ -406,9 +459,9 @@ class CompressOld extends Maintenance { } $this->output( "\n" ); } + return true; } - } $maintClass = 'CompressOld'; diff --git a/maintenance/storage/fixBug20757.php b/maintenance/storage/fixBug20757.php index 101aa068..d2fe3b42 100644 --- a/maintenance/storage/fixBug20757.php +++ b/maintenance/storage/fixBug20757.php @@ -57,14 +57,9 @@ class FixBug20757 extends Maintenance { $totalRevs = $dbr->selectField( 'text', 'MAX(old_id)', false, __METHOD__ ); - if ( $dbr->getType() == 'mysql' - && version_compare( $dbr->getServerVersion(), '4.1.0', '>=' ) ) - { + if ( $dbr->getType() == 'mysql' ) { // In MySQL 4.1+, the binary field old_text has a non-working LOWER() function $lowerLeft = 'LOWER(CONVERT(LEFT(old_text,22) USING latin1))'; - } else { - // No CONVERT() in MySQL 4.0 - $lowerLeft = 'LOWER(LEFT(old_text,22))'; } while ( true ) { @@ -301,14 +296,15 @@ class FixBug20757 extends Maintenance { $this->mapCache[$pageId] = $map; $this->mapCacheSize += count( $map ); } + return $this->mapCache[$pageId]; } /** * This is based on part of HistoryBlobStub::getText(). * Determine if the text can be retrieved from the row in the normal way. - * @param $stub - * @param $secondaryRow + * @param array $stub + * @param stdClass $secondaryRow * @return bool */ function isUnbrokenStub( $stub, $secondaryRow ) { @@ -316,7 +312,10 @@ class FixBug20757 extends Maintenance { $text = $secondaryRow->old_text; if ( in_array( 'external', $flags ) ) { $url = $text; - @list( /* $proto */ , $path ) = explode( '://', $url, 2 ); + wfSuppressWarnings(); + list( /* $proto */, $path ) = explode( '://', $url, 2 ); + wfRestoreWarnings(); + if ( $path == "" ) { return false; } @@ -343,6 +342,7 @@ class FixBug20757 extends Maintenance { $obj->uncompress(); $text = $obj->getItem( $stub['hash'] ); + return $text !== false; } } diff --git a/maintenance/storage/orphanStats.php b/maintenance/storage/orphanStats.php index 1df1501e..c5213ad1 100644 --- a/maintenance/storage/orphanStats.php +++ b/maintenance/storage/orphanStats.php @@ -32,11 +32,13 @@ require_once __DIR__ . '/../Maintenance.php'; class OrphanStats extends Maintenance { public function __construct() { parent::__construct(); - $this->mDescription = "how some statistics on the blob_orphans table, created with trackBlobs.php"; + $this->mDescription = + "Show some statistics on the blob_orphans table, created with trackBlobs.php"; } protected function &getDB( $cluster, $groups = array(), $wiki = false ) { $lb = wfGetLBFactory()->getExternalLB( $cluster ); + return $lb->getConnection( DB_SLAVE ); } @@ -54,12 +56,17 @@ class OrphanStats extends Maintenance { foreach ( $res as $boRow ) { $extDB = $this->getDB( $boRow->bo_cluster ); - $blobRow = $extDB->selectRow( 'blobs', '*', array( 'blob_id' => $boRow->bo_blob_id ), __METHOD__ ); + $blobRow = $extDB->selectRow( + 'blobs', + '*', + array( 'blob_id' => $boRow->bo_blob_id ), + __METHOD__ + ); $num++; $size = strlen( $blobRow->blob_text ); $totalSize += $size; - $hashes[ sha1( $blobRow->blob_text ) ] = true; + $hashes[sha1( $blobRow->blob_text )] = true; $maxSize = max( $size, $maxSize ); } unset( $res ); @@ -67,8 +74,8 @@ class OrphanStats extends Maintenance { $this->output( "Number of orphans: $num\n" ); if ( $num > 0 ) { $this->output( "Average size: " . round( $totalSize / $num, 0 ) . " bytes\n" . - "Max size: $maxSize\n" . - "Number of unique texts: " . count( $hashes ) . "\n" ); + "Max size: $maxSize\n" . + "Number of unique texts: " . count( $hashes ) . "\n" ); } } } diff --git a/maintenance/storage/recompressTracked.php b/maintenance/storage/recompressTracked.php index b2663165..910f56bd 100644 --- a/maintenance/storage/recompressTracked.php +++ b/maintenance/storage/recompressTracked.php @@ -27,14 +27,16 @@ require __DIR__ . '/../commandLine.inc'; if ( count( $args ) < 1 ) { echo "Usage: php recompressTracked.php [options] <cluster> [... <cluster>...] -Moves blobs indexed by trackBlobs.php to a specified list of destination clusters, and recompresses them in the process. Restartable. +Moves blobs indexed by trackBlobs.php to a specified list of destination clusters, +and recompresses them in the process. Restartable. Options: - --procs <procs> Set the number of child processes (default 1) - --copy-only Copy only, do not update the text table. Restart without this option to complete. - --debug-log <file> Log debugging data to the specified file - --info-log <file> Log progress messages to the specified file - --critical-log <file> Log error messages to the specified file + --procs <procs> Set the number of child processes (default 1) + --copy-only Copy only, do not update the text table. Restart + without this option to complete. + --debug-log <file> Log debugging data to the specified file + --info-log <file> Log progress messages to the specified file + --critical-log <file> Log error messages to the specified file "; exit( 1 ); } @@ -63,8 +65,15 @@ class RecompressTracked { public $debugLog, $infoLog, $criticalLog; public $store; - static $optionsWithArgs = array( 'procs', 'slave-id', 'debug-log', 'info-log', 'critical-log' ); - static $cmdLineOptionMap = array( + private static $optionsWithArgs = array( + 'procs', + 'slave-id', + 'debug-log', + 'info-log', + 'critical-log' + ); + + private static $cmdLineOptionMap = array( 'no-count' => 'noCount', 'procs' => 'numProcs', 'copy-only' => 'copyOnly', @@ -86,6 +95,7 @@ class RecompressTracked { $jobOptions[$classOption] = $options[$cmdOption]; } } + return new self( $jobOptions ); } @@ -109,7 +119,6 @@ class RecompressTracked { if ( $this->debugLog ) { $this->logToFile( $msg, $this->debugLog ); } - } function info( $msg ) { @@ -181,13 +190,16 @@ class RecompressTracked { $dbr = wfGetDB( DB_SLAVE ); if ( !$dbr->tableExists( 'blob_tracking' ) ) { $this->critical( "Error: blob_tracking table does not exist" ); + return false; } $row = $dbr->selectRow( 'blob_tracking', '*', false, __METHOD__ ); if ( !$row ) { $this->info( "Warning: blob_tracking table contains no rows, skipping this wiki." ); + return false; } + return true; } @@ -267,6 +279,7 @@ class RecompressTracked { if ( isset( $pipes[$slaveId] ) ) { $this->prevSlaveId = $slaveId; $this->dispatchToSlave( $slaveId, $args ); + return; } } @@ -276,6 +289,8 @@ class RecompressTracked { /** * Dispatch a command to a specified slave + * @param int $slaveId + * @param array|string $args */ function dispatchToSlave( $slaveId, $args ) { $args = (array)$args; @@ -339,6 +354,9 @@ class RecompressTracked { /** * Display a progress report + * @param string $label + * @param int $current + * @param int $end */ function report( $label, $current, $end ) { $this->numBatches++; @@ -434,14 +452,14 @@ class RecompressTracked { $args = explode( ' ', $line ); $cmd = array_shift( $args ); switch ( $cmd ) { - case 'doPage': - $this->doPage( intval( $args[0] ) ); - break; - case 'doOrphanList': - $this->doOrphanList( array_map( 'intval', $args ) ); - break; - case 'quit': - return; + case 'doPage': + $this->doPage( intval( $args[0] ) ); + break; + case 'doOrphanList': + $this->doOrphanList( array_map( 'intval', $args ) ); + break; + case 'quit': + return; } $this->waitForSlaves(); } @@ -449,6 +467,8 @@ class RecompressTracked { /** * Move tracked text in a given page + * + * @param int $pageId */ function doPage( $pageId ) { $title = Title::newFromId( $pageId ); @@ -527,6 +547,9 @@ class RecompressTracked { * without data loss. * * The transaction is kept short to reduce locking. + * + * @param int $textId + * @param string $url */ function moveTextRow( $textId, $url ) { if ( $this->copyOnly ) { @@ -560,6 +583,8 @@ class RecompressTracked { * * This function completes any moves that only have done bt_new_url. This * can happen when the script is interrupted, or when --copy-only is used. + * + * @param array $conds */ function finishIncompleteMoves( $conds ) { $dbr = wfGetDB( DB_SLAVE ); @@ -602,21 +627,25 @@ class RecompressTracked { if ( $cluster === false ) { $cluster = reset( $this->destClusters ); } + return $cluster; } /** * Gets a DB master connection for the given external cluster name - * @param $cluster string + * @param string $cluster * @return DatabaseBase */ function getExtDB( $cluster ) { $lb = wfGetLBFactory()->getExternalLB( $cluster ); + return $lb->getConnection( DB_MASTER ); } /** * Move an orphan text_id to the new cluster + * + * @param array $textIds */ function doOrphanList( $textIds ) { // Finish incomplete moves @@ -683,6 +712,8 @@ class CgzCopyTransaction { /** * Create a transaction from a RecompressTracked object + * @param RecompressTracked $parent + * @param string $blobClass */ function __construct( $parent, $blobClass ) { $this->blobClass = $blobClass; @@ -694,8 +725,8 @@ class CgzCopyTransaction { /** * Add text. * Returns false if it's ready to commit. - * @param $text string - * @param $textId + * @param string $text + * @param int $textId * @return bool */ function addItem( $text, $textId ) { @@ -706,6 +737,7 @@ class CgzCopyTransaction { $hash = $this->cgz->addItem( $text ); $this->referrers[$textId] = $hash; $this->texts[$textId] = $text; + return $this->cgz->isHappy(); } @@ -769,6 +801,7 @@ class CgzCopyTransaction { $this->critical( "Warning: concurrent operation detected, are there two conflicting " . "processes running, doing the same job?" ); } + return; } $this->recompress(); diff --git a/maintenance/storage/resolveStubs.php b/maintenance/storage/resolveStubs.php index e47d6407..290f1649 100644 --- a/maintenance/storage/resolveStubs.php +++ b/maintenance/storage/resolveStubs.php @@ -65,6 +65,9 @@ function resolveStubs() { /** * Resolve a history stub + * @param int $id + * @param string $stubText + * @param string $flags */ function resolveStub( $id, $stubText, $flags ) { $fname = 'resolveStub'; @@ -77,12 +80,18 @@ function resolveStub( $id, $stubText, $flags ) { if ( strtolower( get_class( $stub ) ) !== 'historyblobstub' ) { print "Error found object of class " . get_class( $stub ) . ", expecting historyblobstub\n"; + return; } # Get the (maybe) external row - $externalRow = $dbr->selectRow( 'text', array( 'old_text' ), - array( 'old_id' => $stub->mOldId, 'old_flags' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ) ), + $externalRow = $dbr->selectRow( + 'text', + array( 'old_text' ), + array( + 'old_id' => $stub->mOldId, + 'old_flags' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ) + ), $fname ); diff --git a/maintenance/storage/testCompression.php b/maintenance/storage/testCompression.php index fdc28d9b..f7ec6624 100644 --- a/maintenance/storage/testCompression.php +++ b/maintenance/storage/testCompression.php @@ -24,8 +24,9 @@ $optionsWithArgs = array( 'start', 'limit', 'type' ); require __DIR__ . '/../commandLine.inc'; -if ( !isset( $args[0] ) ) { - echo "Usage: php testCompression.php [--type=<type>] [--start=<start-date>] [--limit=<num-revs>] <page-title>\n"; +if ( !isset( $args[0] ) ) { + echo "Usage: php testCompression.php [--type=<type>] [--start=<start-date>] " . + "[--limit=<num-revs>] <page-title>\n"; exit( 1 ); } @@ -45,7 +46,6 @@ if ( isset( $options['limit'] ) ) { } $type = isset( $options['type'] ) ? $options['type'] : 'ConcatenatedGzipHistoryBlob'; - $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( array( 'page', 'revision', 'text' ), diff --git a/maintenance/storage/trackBlobs.php b/maintenance/storage/trackBlobs.php index 7857dd95..a3f93862 100644 --- a/maintenance/storage/trackBlobs.php +++ b/maintenance/storage/trackBlobs.php @@ -24,7 +24,6 @@ require __DIR__ . '/../commandLine.inc'; - if ( count( $args ) < 1 ) { echo "Usage: php trackBlobs.php <cluster> [... <cluster>]\n"; echo "Adds blobs from a given ES cluster to the blob_tracking table\n"; @@ -127,6 +126,7 @@ class TrackBlobs { $this->textClause .= 'old_text' . $dbr->buildLike( "DB://$cluster/", $dbr->anyString() ); } } + return $this->textClause; } @@ -134,6 +134,7 @@ class TrackBlobs { if ( !preg_match( '!^DB://(\w+)/(\d+)(?:/([0-9a-fA-F]+)|)$!', $text, $m ) ) { return false; } + return array( 'cluster' => $m[1], 'id' => intval( $m[2] ), @@ -306,6 +307,7 @@ class TrackBlobs { function findOrphanBlobs() { if ( !extension_loaded( 'gmp' ) ) { echo "Can't find orphan blobs, need bitfield support provided by GMP.\n"; + return; } diff --git a/maintenance/syncFileBackend.php b/maintenance/syncFileBackend.php index f0be709f..14a1502f 100644 --- a/maintenance/syncFileBackend.php +++ b/maintenance/syncFileBackend.php @@ -71,6 +71,7 @@ class SyncFileBackend extends Maintenance { if ( $this->isQuiet() ) { print $id; // give a single machine-readable number } + return; } @@ -104,7 +105,7 @@ class SyncFileBackend extends Maintenance { } // Periodically update the position file - $callback = function( $pos ) use ( $startFromPosFile, $posFile, $start ) { + $callback = function ( $pos ) use ( $startFromPosFile, $posFile, $start ) { if ( $startFromPosFile && $pos >= $start ) { // successfully advanced file_put_contents( $posFile, $pos, LOCK_EX ); } @@ -141,12 +142,12 @@ class SyncFileBackend extends Maintenance { * Sync $dst backend to $src backend based on the $src logs given after $start. * Returns the journal entry ID this advanced to and handled (inclusive). * - * @param $src FileBackend - * @param $dst FileBackend - * @param $start integer Starting journal position - * @param $end integer Starting journal position - * @param $callback Closure Callback to update any position file - * @return integer|false Journal entry ID or false if there are none + * @param FileBackend $src + * @param FileBackend $dst + * @param int $start Starting journal position + * @param int $end Starting journal position + * @param Closure $callback Callback to update any position file + * @return int|bool Journal entry ID or false if there are none */ protected function syncBackends( FileBackend $src, FileBackend $dst, $start, $end, Closure $callback @@ -198,9 +199,9 @@ class SyncFileBackend extends Maintenance { /** * Sync particular files of backend $src to the corresponding $dst backend files * - * @param $paths Array - * @param $src FileBackend - * @param $dst FileBackend + * @param array $paths + * @param FileBackend $src + * @param FileBackend $dst * @return Status */ protected function syncFileBatch( array $paths, FileBackend $src, FileBackend $dst ) { @@ -221,6 +222,9 @@ class SyncFileBackend extends Maintenance { return $status; } + $src->preloadFileStat( array( 'srcs' => $sPaths, 'latest' => 1 ) ); + $dst->preloadFileStat( array( 'srcs' => $dPaths, 'latest' => 1 ) ); + $ops = array(); $fsFiles = array(); foreach ( $sPaths as $i => $sPath ) { @@ -235,6 +239,7 @@ class SyncFileBackend extends Maintenance { if ( !$fsFile ) { $this->error( "Unable to sync '$dPath': could not get local copy." ); $status->fatal( 'backend-fail-internal', $src->getName() ); + return $status; } $fsFiles[] = $fsFile; // keep TempFSFile objects alive as needed @@ -251,6 +256,7 @@ class SyncFileBackend extends Maintenance { } else { // error $this->error( "Unable to sync '$dPath': could not stat file." ); $status->fatal( 'backend-fail-internal', $src->getName() ); + return $status; } } @@ -273,8 +279,9 @@ class SyncFileBackend extends Maintenance { /** * Substitute the backend name of storage paths with that of a given one * - * @param $paths Array|string List of paths or single string path - * @return Array|string + * @param array|string $paths List of paths or single string path + * @param FileBackend $backend + * @return array|string */ protected function replaceNamePaths( $paths, FileBackend $backend ) { return preg_replace( diff --git a/maintenance/tables.sql b/maintenance/tables.sql index de92ef53..02286848 100644 --- a/maintenance/tables.sql +++ b/maintenance/tables.sql @@ -126,7 +126,12 @@ CREATE TABLE /*_*/user ( -- Meant primarily for heuristic checks to give an impression of whether -- the account has been used much. -- - user_editcount int + user_editcount int, + + -- Expiration date for user password. Use $user->expirePassword() + -- to force a password reset. + user_password_expires varbinary(14) DEFAULT NULL + ) /*$wgDBTableOptions*/; CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name); @@ -256,6 +261,11 @@ CREATE TABLE /*_*/page ( -- of contained templates. page_touched binary(14) NOT NULL default '', + -- This timestamp is updated whenever a page is re-parsed and + -- it has all the link tracking tables updated for it. This is + -- useful for de-duplicating expensive backlink update jobs. + page_links_updated varbinary(14) NULL default NULL, + -- Handy key to revision.rev_id of the current revision. -- This may be 0 during page creation, but that shouldn't -- happen outside of a transaction... hopefully. @@ -265,7 +275,10 @@ CREATE TABLE /*_*/page ( page_len int unsigned NOT NULL, -- content model, see CONTENT_MODEL_XXX constants - page_content_model varbinary(32) DEFAULT NULL + page_content_model varbinary(32) DEFAULT NULL, + + -- Page content language + page_lang varbinary(35) DEFAULT NULL ) /*$wgDBTableOptions*/; CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title); @@ -460,6 +473,8 @@ CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id); CREATE TABLE /*_*/pagelinks ( -- Key to the page_id of the page containing the link. pl_from int unsigned NOT NULL default 0, + -- Namespace for this page + pl_from_namespace int NOT NULL default 0, -- Key to page_namespace/page_title of the target page. -- The target page may or may not exist, and due to renames @@ -470,7 +485,8 @@ CREATE TABLE /*_*/pagelinks ( ) /*$wgDBTableOptions*/; CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title); -CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from); +CREATE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from); +CREATE INDEX /*i*/pl_backlinks_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from_namespace,pl_from); -- @@ -479,6 +495,8 @@ CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,p CREATE TABLE /*_*/templatelinks ( -- Key to the page_id of the page containing the link. tl_from int unsigned NOT NULL default 0, + -- Namespace for this page + tl_from_namespace int NOT NULL default 0, -- Key to page_namespace/page_title of the target page. -- The target page may or may not exist, and due to renames @@ -489,7 +507,8 @@ CREATE TABLE /*_*/templatelinks ( ) /*$wgDBTableOptions*/; CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title); -CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from); +CREATE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from); +CREATE INDEX /*i*/tl_backlinks_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from_namespace,tl_from); -- @@ -500,6 +519,8 @@ CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_tit CREATE TABLE /*_*/imagelinks ( -- Key to page_id of the page containing the image / media link. il_from int unsigned NOT NULL default 0, + -- Namespace for this page + il_from_namespace int NOT NULL default 0, -- Filename of target image. -- This is also the page_title of the file's description page; @@ -508,7 +529,8 @@ CREATE TABLE /*_*/imagelinks ( ) /*$wgDBTableOptions*/; CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to); -CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from); +CREATE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from); +CREATE INDEX /*i*/il_backlinks_namespace ON /*_*/imagelinks (il_to,il_from_namespace,il_from); -- @@ -714,7 +736,7 @@ CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id); -- CREATE TABLE /*_*/hitcounter ( hc_id int unsigned NOT NULL -) ENGINE=HEAP MAX_ROWS=25000; +) ENGINE=MEMORY MAX_ROWS=25000; -- @@ -825,7 +847,8 @@ CREATE TABLE /*_*/image ( -- major part of a MIME media type as defined by IANA -- see http://www.iana.org/assignments/media-types/ - img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown", + -- for "chemical" cf. http://dx.doi.org/10.1021/ci9803233 by the ACS + img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") NOT NULL default "unknown", -- minor part of a MIME media type as defined by IANA -- the minor parts are not required to adher to any standard @@ -884,7 +907,7 @@ CREATE TABLE /*_*/oldimage ( oi_metadata mediumblob NOT NULL, oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, - oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown", + oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") NOT NULL default "unknown", oi_minor_mime varbinary(100) NOT NULL default "unknown", oi_deleted tinyint unsigned NOT NULL default 0, oi_sha1 varbinary(32) NOT NULL default '' @@ -934,7 +957,7 @@ CREATE TABLE /*_*/filearchive ( fa_metadata mediumblob, fa_bits int default 0, fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, - fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown", + fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") default "unknown", fa_minor_mime varbinary(100) default "unknown", fa_description tinyblob, fa_user int unsigned default 0, @@ -991,12 +1014,12 @@ CREATE TABLE /*_*/uploadstash ( -- chunk counter starts at 0, current offset is stored in us_size us_chunk_inx int unsigned NULL, - -- Serialized file properties from File::getPropsFromPath + -- Serialized file properties from FSFile::getProps() us_props blob, -- file size in bytes us_size int unsigned NOT NULL, - -- this hash comes from File::sha1Base36(), and is 31 characters + -- this hash comes from FSFile::getSha1Base36(), and is 31 characters us_sha1 varchar(31) NOT NULL, us_mime varchar(255), -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table @@ -1025,9 +1048,6 @@ CREATE TABLE /*_*/recentchanges ( rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT, rc_timestamp varbinary(14) NOT NULL default '', - -- This is no longer used - rc_cur_time varbinary(14) NOT NULL default '', - -- As in revision rc_user int unsigned NOT NULL default 0, rc_user_text varchar(255) binary NOT NULL, @@ -1062,6 +1082,10 @@ CREATE TABLE /*_*/recentchanges ( -- The type of change entry (RC_EDIT,RC_NEW,RC_LOG,RC_EXTERNAL) rc_type tinyint unsigned NOT NULL default 0, + -- The source of the change entry (replaces rc_type) + -- default of '' is temporary, needed for initial migration + rc_source varchar(16) binary not null default '', + -- If the Recent Changes Patrol option is enabled, -- users may mark edits as having been reviewed to -- remove a warning flag on the RC list. @@ -1109,14 +1133,16 @@ CREATE TABLE /*_*/watchlist ( wl_namespace int NOT NULL default 0, wl_title varchar(255) binary NOT NULL default '', - -- Timestamp when user was last sent a notification e-mail; - -- cleared when the user visits the page. + -- Timestamp used to send notification e-mails and show "updated since last visit" markers on + -- history and recent changes / watchlist. Set to NULL when the user visits the latest revision + -- of the page, which means that they should be sent an e-mail on the next change. wl_notificationtimestamp varbinary(14) ) /*$wgDBTableOptions*/; CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title); CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title); +CREATE INDEX /*i*/wl_user_notificationtimestamp ON /*_*/watchlist (wl_user, wl_notificationtimestamp); -- @@ -1256,6 +1282,8 @@ CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp); CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp); CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp); CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp); +CREATE INDEX /*i*/log_user_text_type_time ON /*_*/logging (log_user_text, log_type, log_timestamp); +CREATE INDEX /*i*/log_user_text_time ON /*_*/logging (log_user_text, log_timestamp); CREATE TABLE /*_*/log_search ( @@ -1369,6 +1397,8 @@ CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo, -- Used for storing page restrictions (i.e. protection levels) CREATE TABLE /*_*/page_restrictions ( + -- Field for an ID for this restrictions row (sort-key for Special:ProtectedPages) + pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, -- Page to apply restrictions to (Foreign Key to page). pr_page int NOT NULL, -- The protection type (edit, move, etc) @@ -1380,9 +1410,7 @@ CREATE TABLE /*_*/page_restrictions ( -- Field for future support of per-user restriction. pr_user int NULL, -- Field for time-limited protection. - pr_expiry varbinary(14) NULL, - -- Field for an ID for this restrictions row (sort-key for Special:ProtectedPages) - pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT + pr_expiry varbinary(14) NULL ) /*$wgDBTableOptions*/; CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type); @@ -1410,12 +1438,13 @@ CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp); CREATE TABLE /*_*/page_props ( pp_page int NOT NULL, pp_propname varbinary(60) NOT NULL, - pp_value blob NOT NULL + pp_value blob NOT NULL, + pp_sortkey float DEFAULT NULL ) /*$wgDBTableOptions*/; CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname); CREATE UNIQUE INDEX /*i*/pp_propname_page ON /*_*/page_props (pp_propname,pp_page); - +CREATE UNIQUE INDEX /*i*/pp_propname_sortkey_page ON /*_*/page_props (pp_propname,pp_sortkey,pp_page); -- A table to log updates, one text key row per update. CREATE TABLE /*_*/updatelog ( @@ -1514,7 +1543,7 @@ CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin) -- Holds all the sites known to the wiki. CREATE TABLE /*_*/sites ( --- Numeric id of the site + -- Numeric id of the site site_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, -- Global identifier for the site, ie 'enwiktionary' diff --git a/maintenance/term/MWTerm.php b/maintenance/term/MWTerm.php index c52f07cc..d90d0695 100644 --- a/maintenance/term/MWTerm.php +++ b/maintenance/term/MWTerm.php @@ -35,8 +35,8 @@ class AnsiTermColorer { /** * Return ANSI terminal escape code for changing text attribs/color * - * @param $color String: semicolon-separated list of attribute/color codes - * @return String + * @param string $color Semicolon-separated list of attribute/color codes + * @return string */ public function color( $color ) { global $wgCommandLineDarkBg; @@ -49,7 +49,7 @@ class AnsiTermColorer { /** * Return ANSI terminal escape code for restoring default text attributes * - * @return String + * @return string */ public function reset() { return $this->color( 0 ); diff --git a/maintenance/undelete.php b/maintenance/undelete.php index c890c69b..adebd277 100644 --- a/maintenance/undelete.php +++ b/maintenance/undelete.php @@ -47,7 +47,7 @@ class Undelete extends Maintenance { if ( !$wgUser ) { $this->error( "Invalid username", true ); } - $archive = new PageArchive( $title ); + $archive = new PageArchive( $title, RequestContext::getMain()->getConfig() ); $this->output( "Undeleting " . $title->getPrefixedDBkey() . '...' ); $archive->undelete( array(), $reason ); $this->output( "done\n" ); diff --git a/maintenance/update-keys.sql b/maintenance/update-keys.sql new file mode 100644 index 00000000..dfbb67ea --- /dev/null +++ b/maintenance/update-keys.sql @@ -0,0 +1,29 @@ +-- SQL to insert update keys into the initial tables after a +-- fresh installation of MediaWiki's database. +-- This is read and executed by the install script; you should +-- not have to run it by itself unless doing a manual install. +-- Insert keys here if either the unnecessary would cause heavy +-- processing or could potentially cause trouble by lowering field +-- sizes, adding constraints, etc. +-- When adjusting field sizes, it is recommended removing old +-- patches but to play safe, update keys should also inserted here. + +-- This is a shared file used for both MySQL and SQLite installs. +-- Therefore inserting multiple values is not possible using the +-- INSERT INTO VALUES syntax. +-- +-- +-- The /*_*/ comments in this and other files are +-- replaced with the defined table prefix by the installer +-- and updater scripts. If you are installing or running +-- updates manually, you will need to manually insert the +-- table prefix if any when running these scripts. +-- + +INSERT IGNORE INTO /*_*/updatelog + SELECT 'filearchive-fa_major_mime-patch-fa_major_mime-chemical.sql' AS ul_key, null as ul_value + UNION SELECT 'image-img_major_mime-patch-img_major_mime-chemical.sql', null + UNION SELECT 'oldimage-oi_major_mime-patch-oi_major_mime-chemical.sql', null + UNION SELECT 'user_groups-ug_group-patch-ug_group-length-increase-255.sql', null + UNION SELECT 'user_former_groups-ufg_group-patch-ufg_group-length-increase-255.sql', null + UNION SELECT 'user_properties-up_property-patch-up_property.sql', null;
\ No newline at end of file diff --git a/maintenance/update.php b/maintenance/update.php index 19429716..046d73cd 100644 --- a/maintenance/update.php +++ b/maintenance/update.php @@ -1,3 +1,4 @@ +#!/usr/bin/env php <?php /** * Run all updaters. @@ -25,7 +26,7 @@ * @ingroup Maintenance */ -if ( !function_exists( 'version_compare' ) || ( version_compare( phpversion(), '5.3.2' ) < 0 ) ) { +if ( !function_exists( 'version_compare' ) || ( version_compare( PHP_VERSION, '5.3.2' ) < 0 ) ) { require dirname( __FILE__ ) . '/../includes/PHPVersionError.php'; wfPHPVersionError( 'cli' ); } @@ -47,13 +48,19 @@ class UpdateMediaWiki extends Maintenance { $this->addOption( 'doshared', 'Also update shared tables' ); $this->addOption( 'nopurge', 'Do not purge the objectcache table after updates' ); $this->addOption( 'noschema', 'Only do the updates that are not done during schema updates' ); - $this->addOption( 'schema', 'Output SQL to do the schema updates instead of doing them. Works even when $wgAllowSchemaUpdates is false', false, true ); + $this->addOption( + 'schema', + 'Output SQL to do the schema updates instead of doing them. Works ' + . 'even when $wgAllowSchemaUpdates is false', + false, + true + ); $this->addOption( 'force', 'Override when $wgAllowSchemaUpdates disables this script' ); } function getDbType() { /* If we used the class constant PHP4 would give a parser error here */ - return 2 /* Maintenance::DB_ADMIN */; + return 2; /* Maintenance::DB_ADMIN */ } function compatChecks() { @@ -74,30 +81,22 @@ class UpdateMediaWiki extends Maintenance { $test = new PhpXmlBugTester(); if ( !$test->ok ) { $this->error( - "Your system has a combination of PHP and libxml2 versions which is buggy\n" . + "Your system has a combination of PHP and libxml2 versions that is buggy\n" . "and can cause hidden data corruption in MediaWiki and other web apps.\n" . - "Upgrade to PHP 5.2.9 or later and libxml2 2.7.3 or later!\n" . - "ABORTING (see http://bugs.php.net/bug.php?id=45996).\n", - true ); - } - - $test = new PhpRefCallBugTester; - $test->execute(); - if ( !$test->ok ) { - $ver = phpversion(); - $this->error( - "PHP $ver is not compatible with MediaWiki due to a bug involving\n" . - "reference parameters to __call. Upgrade to PHP 5.3.2 or higher, or \n" . - "downgrade to PHP 5.3.0 to fix this.\n" . - "ABORTING (see http://bugs.php.net/bug.php?id=50394 for details)\n", + "Upgrade to libxml2 2.7.3 or later.\n" . + "ABORTING (see https://bugs.php.net/bug.php?id=45996).\n", true ); } } function execute() { - global $wgVersion, $wgTitle, $wgLang, $wgAllowSchemaUpdates; + global $wgVersion, $wgLang, $wgAllowSchemaUpdates; - if ( !$wgAllowSchemaUpdates && !( $this->hasOption( 'force' ) || $this->hasOption( 'schema' ) || $this->hasOption( 'noschema' ) ) ) { + if ( !$wgAllowSchemaUpdates + && !( $this->hasOption( 'force' ) + || $this->hasOption( 'schema' ) + || $this->hasOption( 'noschema' ) ) + ) { $this->error( "Do not run update.php on this wiki. If you're seeing this you should\n" . "probably ask for some help in performing your schema updates or use\n" . "the --noschema and --schema options to get an SQL file for someone\n" @@ -118,7 +117,8 @@ class UpdateMediaWiki extends Maintenance { } $wgLang = Language::factory( 'en' ); - $wgTitle = Title::newFromText( "MediaWiki database updater" ); + + define( 'MW_UPDATER', true ); $this->output( "MediaWiki {$wgVersion} Updater\n\n" ); @@ -142,10 +142,13 @@ class UpdateMediaWiki extends Maintenance { $this->output( "Depending on the size of your database this may take a while!\n" ); if ( !$this->hasOption( 'quick' ) ) { - $this->output( "Abort with control-c in the next five seconds (skip this countdown with --quick) ... " ); + $this->output( "Abort with control-c in the next five seconds " + . "(skip this countdown with --quick) ... " ); wfCountDown( 5 ); } + $time1 = new MWTimestamp(); + $shared = $this->hasOption( 'doshared' ); $updates = array( 'core', 'extensions' ); @@ -178,8 +181,10 @@ class UpdateMediaWiki extends Maintenance { if ( !$this->hasOption( 'nopurge' ) ) { $updater->purgeCache(); } + $time2 = new MWTimestamp(); - $this->output( "\nDone.\n" ); + $timeDiff = $time2->diff( $time1 ); + $this->output( "\nDone in " . $timeDiff->format( "%i:%S" ) . ".\n" ); } function afterFinalSetup() { @@ -190,7 +195,7 @@ class UpdateMediaWiki extends Maintenance { # cache from $wgExtensionFunctions (bug 20471) $wgLocalisationCacheConf = array( 'class' => 'LocalisationCache', - 'storeClass' => 'LCStore_Null', + 'storeClass' => 'LCStoreNull', 'storeDirectory' => false, 'manualRecache' => false, ); diff --git a/maintenance/updateArticleCount.php b/maintenance/updateArticleCount.php index 7964a21f..470647a4 100644 --- a/maintenance/updateArticleCount.php +++ b/maintenance/updateArticleCount.php @@ -49,10 +49,16 @@ class UpdateArticleCount extends Maintenance { if ( $this->hasOption( '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__ ); + $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" ); + $this->output( "To update the site statistics table, run the script " + . "with the --update option.\n" ); } } } diff --git a/maintenance/updateCollation.php b/maintenance/updateCollation.php index 964b3138..342ffbad 100644 --- a/maintenance/updateCollation.php +++ b/maintenance/updateCollation.php @@ -47,7 +47,7 @@ class UpdateCollation extends Maintenance { $this->mDescription = <<<TEXT This script will find all rows in the categorylinks table whose collation is out-of-date (cl_collation != '$wgCategoryCollation') and repopulate cl_sortkey -using the page title and cl_sortkey_prefix. If everything's collation is +using the page title and cl_sortkey_prefix. If all collations are up-to-date, it will do nothing. TEXT; @@ -120,6 +120,7 @@ TEXT; } if ( $count == 0 ) { $this->output( "Collations up-to-date.\n" ); + return; } $this->output( "Fixing collation for $count rows.\n" ); @@ -150,7 +151,8 @@ TEXT; # This is an old-style row, so the sortkey needs to be # converted. if ( $row->cl_sortkey == $title->getText() - || $row->cl_sortkey == $title->getPrefixedText() ) { + || $row->cl_sortkey == $title->getPrefixedText() + ) { $prefix = ''; } else { # Custom sortkey, use it as a prefix @@ -188,15 +190,14 @@ TEXT; __METHOD__ ); } + if ( $row ) { + $batchConds = array( $this->getBatchCondition( $row, $dbw ) ); + } } if ( !$dryRun ) { $dbw->commit( __METHOD__ ); } - if ( $row ) { - $batchConds = array( $this->getBatchCondition( $row ) ); - } - $count += $res->numRows(); $this->output( "$count done.\n" ); @@ -218,9 +219,11 @@ TEXT; /** * Return an SQL expression selecting rows which sort above the given row, * assuming an ordering of cl_to, cl_type, cl_from + * @param stdClass $row + * @param DatabaseBase $dbw + * @return string */ - function getBatchCondition( $row ) { - $dbw = $this->getDB( DB_MASTER ); + function getBatchCondition( $row, $dbw ) { $fields = array( 'cl_to', 'cl_type', 'cl_from' ); $first = true; $cond = false; @@ -238,6 +241,7 @@ TEXT; $prefix .= " AND $equality"; } } + return $cond; } diff --git a/maintenance/updateDoubleWidthSearch.php b/maintenance/updateDoubleWidthSearch.php index 41988d1b..796cedd9 100644 --- a/maintenance/updateDoubleWidthSearch.php +++ b/maintenance/updateDoubleWidthSearch.php @@ -36,7 +36,12 @@ class UpdateDoubleWidthSearch extends Maintenance { parent::__construct(); $this->mDescription = "Script to normalize double-byte latin UTF-8 characters"; $this->addOption( 'q', 'quiet', false, true ); - $this->addOption( 'l', 'How long the searchindex and revision tables will be locked for', false, true ); + $this->addOption( + 'l', + 'How long the searchindex and revision tables will be locked for', + false, + true + ); } public function getDbType() { @@ -67,6 +72,7 @@ class UpdateDoubleWidthSearch extends Maintenance { $sql = "SELECT si_page FROM $searchindex WHERE ( si_text RLIKE '$regexp' ) OR ( si_title RLIKE '$regexp' )"; + return $dbw->query( $sql, __METHOD__ ); } } diff --git a/maintenance/updateRestrictions.php b/maintenance/updateRestrictions.php index 175447e7..5b5cc048 100644 --- a/maintenance/updateRestrictions.php +++ b/maintenance/updateRestrictions.php @@ -59,7 +59,12 @@ class UpdateRestrictions extends Maintenance { while ( $blockEnd <= $end ) { $this->output( "...doing page_id from $blockStart to $blockEnd\n" ); $cond = "page_id BETWEEN $blockStart AND $blockEnd AND page_restrictions !=''"; - $res = $db->select( 'page', array( 'page_id', 'page_namespace', 'page_restrictions' ), $cond, __METHOD__ ); + $res = $db->select( + 'page', + array( 'page_id', 'page_namespace', 'page_restrictions' ), + $cond, + __METHOD__ + ); $batch = array(); foreach ( $res as $row ) { $oldRestrictions = array(); @@ -108,7 +113,13 @@ class UpdateRestrictions extends Maintenance { // Kill any broken rows from previous imports $db->delete( 'page_restrictions', array( 'pr_level' => '' ) ); // Kill other invalid rows - $db->deleteJoin( 'page_restrictions', 'page', 'pr_page', 'page_id', array( 'page_namespace' => NS_MEDIAWIKI ) ); + $db->deleteJoin( + 'page_restrictions', + 'page', + 'pr_page', + 'page_id', + array( 'page_namespace' => NS_MEDIAWIKI ) + ); $this->output( "...Done!\n" ); } } diff --git a/maintenance/updateSearchIndex.php b/maintenance/updateSearchIndex.php index 0691bee8..68a51bd9 100644 --- a/maintenance/updateSearchIndex.php +++ b/maintenance/updateSearchIndex.php @@ -42,8 +42,18 @@ class UpdateSearchIndex extends Maintenance { $this->mDescription = "Script for periodic off-peak updating of the search index"; $this->addOption( 's', 'starting timestamp', false, true ); $this->addOption( 'e', 'Ending timestamp', false, true ); - $this->addOption( 'p', 'File for saving/loading timestamps, searchUpdate.WIKI_ID.pos by default', false, true ); - $this->addOption( 'l', 'How long the searchindex and revision tables will be locked for', false, true ); + $this->addOption( + 'p', + 'File for saving/loading timestamps, searchUpdate.WIKI_ID.pos by default', + false, + true + ); + $this->addOption( + 'l', + 'How long the searchindex and revision tables will be locked for', + false, + true + ); } public function getDbType() { diff --git a/maintenance/updateSpecialPages.php b/maintenance/updateSpecialPages.php index 3432cb20..61642828 100644 --- a/maintenance/updateSpecialPages.php +++ b/maintenance/updateSpecialPages.php @@ -34,34 +34,32 @@ class UpdateSpecialPages extends Maintenance { parent::__construct(); $this->addOption( 'list', 'List special page names' ); $this->addOption( 'only', 'Only update "page"; case sensitive, ' . - 'check correct case by calling this script with --list or on ' . - 'includes/QueryPage.php. Ex: --only=BrokenRedirects', false, true ); + 'check correct case by calling this script with --list. ' . + 'Ex: --only=BrokenRedirects', false, true ); $this->addOption( 'override', 'Also update pages that have updates disabled' ); } public function execute() { - global $IP, $wgQueryPages, $wgQueryCacheLimit, $wgDisableQueryPageUpdate; + global $wgQueryCacheLimit, $wgDisableQueryPageUpdate; - if ( !$this->hasOption( 'list' ) && !$this->hasOption( 'only' ) ) { - $this->doSpecialPageCacheUpdates(); - } $dbw = wfGetDB( DB_MASTER ); - // This is needed to initialise $wgQueryPages - require_once "$IP/includes/QueryPage.php"; + $this->doSpecialPageCacheUpdates( $dbw ); - foreach ( $wgQueryPages as $page ) { + foreach ( QueryPage::getPages() as $page ) { list( $class, $special ) = $page; $limit = isset( $page[2] ) ? $page[2] : null; # --list : just show the name of pages if ( $this->hasOption( 'list' ) ) { - $this->output( "$special\n" ); + $this->output( "$special [QueryPage]\n" ); continue; } - if ( !$this->hasOption( 'override' ) && $wgDisableQueryPageUpdate && in_array( $special, $wgDisableQueryPageUpdate ) ) { - $this->output( sprintf( "%-30s disabled\n", $special ) ); + if ( !$this->hasOption( 'override' ) + && $wgDisableQueryPageUpdate && in_array( $special, $wgDisableQueryPageUpdate ) + ) { + $this->output( sprintf( "%-30s [QueryPage] disabled\n", $special ) ); continue; } @@ -81,7 +79,7 @@ class UpdateSpecialPages extends Maintenance { } if ( !$this->hasOption( 'only' ) || $this->getOption( 'only' ) == $queryPage->getName() ) { - $this->output( sprintf( '%-30s ', $special ) ); + $this->output( sprintf( '%-30s [QueryPage] ', $special ) ); if ( $queryPage->isExpensive() ) { $t1 = explode( ' ', microtime() ); # Do the query @@ -112,9 +110,6 @@ class UpdateSpecialPages extends Maintenance { sleep( 10 ); } while ( !wfGetLB()->pingAll() ); $this->output( "Reconnected\n\n" ); - } else { - # Commit the results - $dbw->commit( __METHOD__ ); } # Wait for the slave to catch up wfWaitForSlaves(); @@ -128,32 +123,41 @@ class UpdateSpecialPages extends Maintenance { } } - public function doSpecialPageCacheUpdates() { + public function doSpecialPageCacheUpdates( $dbw ) { global $wgSpecialPageCacheUpdates; - $dbw = wfGetDB( DB_MASTER ); foreach ( $wgSpecialPageCacheUpdates as $special => $call ) { - if ( !is_callable( $call ) ) { - $this->error( "Uncallable function $call!" ); + # --list : just show the name of pages + if ( $this->hasOption( 'list' ) ) { + $this->output( "$special [callback]\n" ); continue; } - $this->output( sprintf( '%-30s ', $special ) ); - $t1 = explode( ' ', microtime() ); - call_user_func( $call, $dbw ); - $t2 = explode( ' ', microtime() ); - $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 ' ); + + if ( !$this->hasOption( 'only' ) || $this->getOption( 'only' ) == $special ) { + if ( !is_callable( $call ) ) { + $this->error( "Uncallable function $call!" ); + continue; + } + $this->output( sprintf( '%-30s [callback] ', $special ) ); + $t1 = explode( ' ', microtime() ); + call_user_func( $call, $dbw ); + $t2 = explode( ' ', microtime() ); + + $this->output( "completed 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 ) ); + # Wait for the slave to catch up + wfWaitForSlaves(); } - $this->output( sprintf( "completed in %.2fs\n", $seconds ) ); - # Wait for the slave to catch up - wfWaitForSlaves(); } } } diff --git a/maintenance/userDupes.inc b/maintenance/userDupes.inc index 8bd80c97..15e1174f 100644 --- a/maintenance/userDupes.inc +++ b/maintenance/userDupes.inc @@ -3,7 +3,7 @@ * Helper class for update.php. * * Copyright © 2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,7 +46,7 @@ class UserDupes { /** * Output some text via the output callback provided - * @param $str String Text to print + * @param string $str Text to print */ private function out( $str ) { call_user_func( $this->outputCallback, $str ); @@ -61,6 +61,7 @@ class UserDupes { $info = $this->db->indexInfo( 'user', 'user_name', __METHOD__ ); if ( !$info ) { $this->out( "WARNING: doesn't seem to have user_name index at all!\n" ); + return false; } @@ -95,13 +96,14 @@ class UserDupes { * not requested. (If doing resolution, edits may be reassigned.) * Status information will be echo'd to stdout. * - * @param $doDelete bool: pass true to actually remove things - * from the database; false to just check. + * @param bool $doDelete Pass true to actually remove things + * from the database; false to just check. * @return bool */ function checkDupes( $doDelete = false ) { if ( $this->hasUniqueIndex() ) { echo wfWikiID() . " already has a unique index on its user table.\n"; + return true; } @@ -125,7 +127,8 @@ class UserDupes { if ( $this->reassigned > 0 ) { if ( $doDelete ) { - $this->out( "$this->reassigned duplicate accounts had edits reassigned to a canonical record id.\n" ); + $this->out( "$this->reassigned duplicate accounts had edits " + . "reassigned to a canonical record id.\n" ); } else { $this->out( "$this->reassigned duplicate accounts need to have edits reassigned.\n" ); } @@ -133,22 +136,27 @@ class UserDupes { if ( $this->trimmed > 0 ) { if ( $doDelete ) { - $this->out( "$this->trimmed duplicate user records were deleted from " . wfWikiID() . ".\n" ); + $this->out( "$this->trimmed duplicate user records were deleted from " + . wfWikiID() . ".\n" ); } else { - $this->out( "$this->trimmed duplicate user accounts were found on " . wfWikiID() . " which can be removed safely.\n" ); + $this->out( "$this->trimmed duplicate user accounts were found on " + . wfWikiID() . " which can be removed safely.\n" ); } } if ( $this->failed > 0 ) { $this->out( "Something terribly awry; $this->failed duplicate accounts were not removed.\n" ); + return false; } if ( $this->trimmed == 0 || $doDelete ) { $this->out( "It is now safe to apply the unique index on user_name.\n" ); + return true; } else { $this->out( "Run this script again with the --fix option to automatically delete them.\n" ); + return false; } } @@ -184,7 +192,7 @@ class UserDupes { function getDupes() { $user = $this->db->tableName( 'user' ); $result = $this->db->query( - "SELECT user_name,COUNT(*) AS n + "SELECT user_name,COUNT(*) AS n FROM $user GROUP BY user_name HAVING n > 1", __METHOD__ ); @@ -193,6 +201,7 @@ class UserDupes { foreach ( $result as $row ) { $list[] = $row->user_name; } + return $list; } @@ -200,8 +209,8 @@ class UserDupes { * Examine user records for the given name. Try to see which record * will be the one that actually gets used, then check remaining records * for edits. If the dupes have no edits, we can safely remove them. - * @param $name string - * @param $doDelete bool + * @param string $name + * @param bool $doDelete * @access private */ function examine( $name, $doDelete ) { @@ -249,7 +258,7 @@ class UserDupes { * Count the number of edits attributed to this user. * Does not currently check log table or other things * where it might show up... - * @param $userid int + * @param int $userid * @return int * @access private */ @@ -262,8 +271,8 @@ class UserDupes { } /** - * @param $from int - * @param $to int + * @param int $from + * @param int $to * @access private */ function reassignEdits( $from, $to ) { @@ -277,7 +286,7 @@ class UserDupes { /** * Remove a user account line. - * @param $userid int + * @param int $userid * @access private */ function trimAccount( $userid ) { @@ -285,5 +294,4 @@ class UserDupes { $this->db->delete( 'user', array( 'user_id' => $userid ), __METHOD__ ); $this->out( " ok" ); } - } diff --git a/maintenance/userOptions.inc b/maintenance/userOptions.inc index 51da80d3..99ba3b83 100644 --- a/maintenance/userOptions.inc +++ b/maintenance/userOptions.inc @@ -30,7 +30,7 @@ require_once __DIR__ . '/commandLine.inc'; /** * @ingroup Maintenance */ -class userOptions { +class UserOptions { public $mQuick; public $mQuiet; public $mDry; @@ -40,21 +40,24 @@ class userOptions { private $mMode, $mReady; - /** Constructor. Will show usage and exit if script options are not correct */ + /** + * Constructor. Will show usage and exit if script options are not correct + * @param array $opts + * @param array $args + */ function __construct( $opts, $args ) { if ( !$this->checkOpts( $opts, $args ) ) { - userOptions::showUsageAndExit(); + UserOptions::showUsageAndExit(); } else { $this->mReady = $this->initializeOpts( $opts, $args ); } } - /** * This is used to check options. Only needed on construction * - * @param $opts array - * @param $args array + * @param array $opts + * @param array $args * * @return bool */ @@ -73,8 +76,8 @@ class userOptions { /** * load script options in the object * - * @param $opts array - * @param $args array + * @param array $opts + * @param array $args * * @return bool */ @@ -108,7 +111,8 @@ class userOptions { return false; } - $this->{ $this->mMode } (); + $this->{$this->mMode}(); + return true; } @@ -140,7 +144,7 @@ class userOptions { array( 'user_id' ), array(), __METHOD__ - ); + ); foreach ( $result as $id ) { @@ -156,15 +160,18 @@ class userOptions { $userValue = $user->getOption( $this->mAnOption ); if ( $userValue <> $defaultOptions[$this->mAnOption] ) { + // @codingStandardsIgnoreStart Ignore silencing errors is discouraged warning @$ret[$this->mAnOption][$userValue]++; + // @codingStandardsIgnoreEnd } - } else { foreach ( $defaultOptions as $name => $defaultValue ) { $userValue = $user->getOption( $name ); if ( $userValue <> $defaultValue ) { + // @codingStandardsIgnoreStart Ignore silencing errors is discouraged warning @$ret[$name][$userValue]++; + // @codingStandardsIgnoreEnd } } } @@ -179,7 +186,6 @@ class userOptions { } } - /** Change our users options */ private function CHANGER() { $this->warn(); @@ -190,7 +196,7 @@ class userOptions { array( 'user_id' ), array(), __METHOD__ - ); + ); foreach ( $result as $id ) { @@ -202,7 +208,8 @@ class userOptions { if ( $curValue == $this->mOldValue ) { if ( !$this->mQuiet ) { - print "Setting {$this->mAnOption} for $username from '{$this->mOldValue}' to '{$this->mNewValue}'): "; + print "Setting {$this->mAnOption} for $username from '{$this->mOldValue}' " . + "to '{$this->mNewValue}'): "; } // Change value @@ -215,7 +222,6 @@ class userOptions { if ( !$this->mQuiet ) { print " OK\n"; } - } elseif ( !$this->mQuiet ) { print "Not changing '$username' using <{$this->mAnOption}> = '$curValue'\n"; } @@ -232,6 +238,7 @@ class userOptions { foreach ( $def as $optname => $defaultValue ) { array_push( $ret, $optname ); } + return $ret; } @@ -240,7 +247,7 @@ class userOptions { # public static function showUsageAndExit() { -print <<<USAGE + print <<<USAGE This script pass through all users and change one of their options. The new option is NOT validated. @@ -264,7 +271,7 @@ Options: --dry : do not save user settings back to database USAGE; - exit( 0 ); + exit( 0 ); } /** @@ -277,14 +284,14 @@ USAGE; return true; } -print <<<WARN + print <<<WARN The script is about to change the skin for ALL USERS in the database. Users with option <$this->mAnOption> = '$this->mOldValue' will be made to use '$this->mNewValue'. Abort with control-c in the next five seconds.... WARN; wfCountDown( 5 ); + return true; } - } diff --git a/maintenance/userOptions.php b/maintenance/userOptions.php index e0de3574..53db48cd 100644 --- a/maintenance/userOptions.php +++ b/maintenance/userOptions.php @@ -28,7 +28,7 @@ require_once 'userOptions.inc'; // Load up our tool system, exit with usage() if options are not fine -$uo = new userOptions( $options, $args ); +$uo = new UserOptions( $options, $args ); $uo->run(); diff --git a/maintenance/waitForSlave.php b/maintenance/waitForSlave.php index a62d1618..c9b1abba 100644 --- a/maintenance/waitForSlave.php +++ b/maintenance/waitForSlave.php @@ -34,6 +34,7 @@ class WaitForSlave extends Maintenance { parent::__construct(); $this->addArg( 'maxlag', 'How long to wait for the slaves, default 10 seconds', false ); } + public function execute() { wfWaitForSlaves( $this->getArg( 0, 10 ) ); } diff --git a/maintenance/wrapOldPasswords.php b/maintenance/wrapOldPasswords.php new file mode 100644 index 00000000..37272a01 --- /dev/null +++ b/maintenance/wrapOldPasswords.php @@ -0,0 +1,126 @@ +<?php +/** + * Maintenance script to wrap all old-style passwords in a layered type + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + */ +require_once __DIR__ . '/Maintenance.php'; + +/** + * Maintenance script to wrap all passwords of a certain type in a specified layered + * type that wraps around the old type. + * + * @since 1.24 + * @ingroup Maintenance + */ +class WrapOldPasswords extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Wrap all passwords of a certain type in a new layered type"; + $this->addOption( 'type', + 'Password type to wrap passwords in (must inherit LayeredParameterizedPassword)', true, true ); + $this->addOption( 'verbose', 'Enables verbose output', false, false, 'v' ); + $this->setBatchSize( 100 ); + } + + public function execute() { + global $wgAuth; + + if ( !$wgAuth->allowSetLocalPassword() ) { + $this->error( '$wgAuth does not allow local passwords. Aborting.', true ); + } + + $passwordFactory = new PasswordFactory(); + $passwordFactory->init( RequestContext::getMain()->getConfig() ); + + $typeInfo = $passwordFactory->getTypes(); + $layeredType = $this->getOption( 'type' ); + + // Check that type exists and is a layered type + if ( !isset( $typeInfo[$layeredType] ) ) { + $this->error( 'Undefined password type', true ); + } + + $passObj = $passwordFactory->newFromType( $layeredType ); + if ( !$passObj instanceof LayeredParameterizedPassword ) { + $this->error( 'Layered parameterized password type must be used.', true ); + } + + // Extract the first layer type + $typeConfig = $typeInfo[$layeredType]; + $firstType = $typeConfig['types'][0]; + + // Get a list of password types that are applicable + $dbw = $this->getDB( DB_MASTER ); + $typeCond = 'user_password' . $dbw->buildLike( ":$firstType:", $dbw->anyString() ); + + $minUserId = 0; + do { + $dbw->begin(); + + $res = $dbw->select( 'user', + array( 'user_id', 'user_name', 'user_password' ), + array( + 'user_id > ' . $dbw->addQuotes( $minUserId ), + $typeCond + ), + __METHOD__, + array( + 'ORDER BY' => 'user_id', + 'LIMIT' => $this->mBatchSize, + 'LOCK IN SHARE MODE', + ) + ); + + /** @var User[] $updateUsers */ + $updateUsers = array(); + foreach ( $res as $row ) { + if ( $this->hasOption( 'verbose' ) ) { + $this->output( "Updating password for user {$row->user_name} ({$row->user_id}).\n" ); + } + + $user = User::newFromId( $row->user_id ); + /** @var ParameterizedPassword $password */ + $password = $passwordFactory->newFromCiphertext( $row->user_password ); + /** @var LayeredParameterizedPassword $layeredPassword */ + $layeredPassword = $passwordFactory->newFromType( $layeredType ); + $layeredPassword->partialCrypt( $password ); + + $updateUsers[] = $user; + $dbw->update( 'user', + array( 'user_password' => $layeredPassword->toString() ), + array( 'user_id' => $row->user_id ), + __METHOD__ + ); + + $minUserId = $row->user_id; + } + + $dbw->commit(); + + // Clear memcached so old passwords are wiped out + foreach ( $updateUsers as $user ) { + $user->clearSharedCache(); + } + } while ( $res->numRows() ); + } +} + +$maintClass = "WrapOldPasswords"; +require_once RUN_MAINTENANCE_IF_MAIN; |