summaryrefslogtreecommitdiff
path: root/maintenance
diff options
context:
space:
mode:
Diffstat (limited to 'maintenance')
-rw-r--r--maintenance/7zip.inc69
-rw-r--r--maintenance/Doxyfile2
-rw-r--r--maintenance/FiveUpgrade.inc2
-rw-r--r--maintenance/Maintenance.php860
-rw-r--r--maintenance/README6
-rw-r--r--maintenance/addwiki.php412
-rw-r--r--maintenance/apache-ampersand.diff53
-rw-r--r--maintenance/archives/patch-change_tag-indexes.sql21
-rw-r--r--maintenance/archives/patch-eu_local_id.sql3
-rw-r--r--maintenance/archives/patch-external_user.sql9
-rw-r--r--maintenance/archives/patch-filearchive-user-index.sql (renamed from maintenance/archives/patch-filearhive-user-index.sql)0
-rw-r--r--maintenance/archives/patch-job.sql18
-rw-r--r--maintenance/archives/patch-l10n_cache.sql8
-rw-r--r--maintenance/archives/patch-log_search-rename-index.sql7
-rw-r--r--maintenance/archives/patch-log_search.sql10
-rw-r--r--maintenance/archives/patch-log_user_text.sql5
-rw-r--r--maintenance/archives/patch-mime_minor_length.sql10
-rw-r--r--maintenance/archives/patch-rd_interwiki.sql6
-rw-r--r--maintenance/archives/patch-tc-timestamp.sql4
-rw-r--r--maintenance/archives/patch-transcache.sql2
-rw-r--r--maintenance/archives/patch-user_properties.sql22
-rw-r--r--maintenance/archives/populateSha1.php59
-rw-r--r--maintenance/archives/rebuildRecentchanges.inc123
-rw-r--r--maintenance/archives/upgradeWatchlist.php67
-rw-r--r--maintenance/attachLatest.php96
-rw-r--r--maintenance/attribute.php106
-rw-r--r--maintenance/backup.inc18
-rw-r--r--maintenance/benchmarkPurge.php157
-rw-r--r--maintenance/changePassword.php78
-rw-r--r--maintenance/checkAutoLoader.php70
-rw-r--r--maintenance/checkBadRedirects.php75
-rw-r--r--maintenance/checkImages.php121
-rw-r--r--maintenance/checkSyntax.php296
-rw-r--r--maintenance/checkUsernames.php44
-rw-r--r--maintenance/cleanupCaps.php57
-rw-r--r--maintenance/cleanupImages.php88
-rw-r--r--maintenance/cleanupSpam.php211
-rw-r--r--maintenance/cleanupTable.inc161
-rw-r--r--maintenance/cleanupTitles.php67
-rw-r--r--maintenance/cleanupWatchlist.php60
-rw-r--r--maintenance/clear_interwiki_cache.php56
-rw-r--r--maintenance/clear_stats.php71
-rw-r--r--maintenance/commandLine.inc269
-rw-r--r--maintenance/convertLinks.inc6
-rw-r--r--maintenance/convertLinks.php239
-rw-r--r--maintenance/convertUserOptions.php72
-rw-r--r--maintenance/counter.php12
-rw-r--r--maintenance/createAndPromote.php111
-rw-r--r--maintenance/deleteArchivedFiles.inc56
-rw-r--r--maintenance/deleteArchivedFiles.php85
-rw-r--r--maintenance/deleteArchivedRevisions.inc34
-rw-r--r--maintenance/deleteArchivedRevisions.php71
-rw-r--r--maintenance/deleteBatch.php172
-rw-r--r--maintenance/deleteDefaultMessages.php90
-rw-r--r--maintenance/deleteImageMemcached.php73
-rw-r--r--maintenance/deleteOldRevisions.inc68
-rw-r--r--maintenance/deleteOldRevisions.php102
-rw-r--r--maintenance/deleteOrphanedRevisions.inc.php32
-rw-r--r--maintenance/deleteOrphanedRevisions.php118
-rw-r--r--maintenance/deleteRevision.php104
-rw-r--r--maintenance/deleteSelfExternals.php54
-rw-r--r--maintenance/doMaintenance.php102
-rw-r--r--maintenance/dumpBackup.php20
-rw-r--r--maintenance/dumpInterwiki.inc3
-rw-r--r--maintenance/dumpInterwiki.php7
-rw-r--r--maintenance/dumpLinks.php60
-rw-r--r--maintenance/dumpSisterSites.php47
-rw-r--r--maintenance/dumpTextPass.php80
-rw-r--r--maintenance/dumpUploads.php87
-rw-r--r--maintenance/edit.php144
-rw-r--r--maintenance/fetchInterwiki.pl102
-rw-r--r--maintenance/fetchText.php82
-rw-r--r--maintenance/findhooks.php285
-rw-r--r--maintenance/fixSlaveDesync.php335
-rw-r--r--maintenance/fixTimestamps.php200
-rw-r--r--maintenance/fixUserRegistration.php61
-rw-r--r--maintenance/fuzz-tester.php8
-rw-r--r--maintenance/gearman/gearman.inc2
-rw-r--r--maintenance/gearman/gearmanWorker.php2
-rw-r--r--maintenance/generateSitemap.php158
-rw-r--r--maintenance/getLagTimes.php59
-rw-r--r--maintenance/getSlaveServer.php60
-rw-r--r--maintenance/getText.php58
-rw-r--r--maintenance/httpSessionDownload.php57
-rw-r--r--maintenance/ibm_db2/README40
-rw-r--r--maintenance/ibm_db2/tables.sql551
-rw-r--r--maintenance/importDump.php13
-rw-r--r--maintenance/importImages.inc (renamed from maintenance/importImages.inc.php)30
-rw-r--r--maintenance/importImages.php168
-rw-r--r--maintenance/importLogs.inc144
-rw-r--r--maintenance/importLogs.php27
-rw-r--r--maintenance/importTextFile.php4
-rw-r--r--maintenance/importUseModWiki.php16
-rw-r--r--maintenance/initEditCount.php161
-rw-r--r--maintenance/initStats.inc57
-rw-r--r--maintenance/initStats.php87
-rw-r--r--maintenance/install-utils.inc219
-rw-r--r--maintenance/installExtension.php2
-rw-r--r--maintenance/interwiki.sql18
-rw-r--r--maintenance/lag.php51
-rw-r--r--maintenance/language/StatOutputs.php14
-rw-r--r--maintenance/language/alltrans.php40
-rw-r--r--maintenance/language/checkDupeMessages.php118
-rw-r--r--maintenance/language/checkExtensions.php4
-rw-r--r--maintenance/language/checkLanguage.inc39
-rw-r--r--maintenance/language/countMessages.php87
-rw-r--r--maintenance/language/date-formats.php102
-rw-r--r--maintenance/language/diffLanguage.php6
-rw-r--r--maintenance/language/digit2html.php68
-rw-r--r--maintenance/language/dumpMessages.php48
-rw-r--r--maintenance/language/generateNormalizerData.php137
-rw-r--r--maintenance/language/lang2po.php225
-rw-r--r--maintenance/language/langmemusage.php58
-rw-r--r--maintenance/language/languages.inc18
-rw-r--r--maintenance/language/makeMessageDB.php45
-rw-r--r--maintenance/language/messageTypes.inc25
-rw-r--r--maintenance/language/messages.inc520
-rw-r--r--maintenance/language/rebuildLanguage.php55
-rw-r--r--maintenance/language/transstat.php71
-rw-r--r--maintenance/language/writeMessagesArray.inc25
-rw-r--r--maintenance/mcc.php84
-rw-r--r--maintenance/mctest.php112
-rw-r--r--maintenance/mergeMessageFileList.php72
-rw-r--r--maintenance/migrateUserGroup.php70
-rw-r--r--maintenance/minify.php111
-rw-r--r--maintenance/moveBatch.php157
-rw-r--r--maintenance/mwdocgen.php15
-rw-r--r--maintenance/namespace2sql.php18
-rw-r--r--maintenance/namespaceDupes.php244
-rw-r--r--maintenance/nextJobDB.php127
-rw-r--r--maintenance/nukeNS.php158
-rw-r--r--maintenance/nukePage.inc91
-rw-r--r--maintenance/nukePage.php114
-rw-r--r--maintenance/ora/patch_seq_names_pre1.16.sql8
-rw-r--r--maintenance/ora/tables.sql1129
-rw-r--r--maintenance/ora/user.sql16
-rw-r--r--maintenance/orphans.php371
-rw-r--r--maintenance/ourusers.php1
-rw-r--r--maintenance/parserTests.inc731
-rw-r--r--maintenance/parserTests.php15
-rw-r--r--maintenance/parserTests.txt972
-rw-r--r--maintenance/patchSql.php69
-rw-r--r--maintenance/populateCategory.php128
-rw-r--r--maintenance/populateLogSearch.inc80
-rw-r--r--maintenance/populateLogSearch.php153
-rw-r--r--maintenance/populateLogUsertext.php82
-rw-r--r--maintenance/populateParentId.php119
-rw-r--r--maintenance/populateSha1.php101
-rw-r--r--maintenance/postgres/archives/patch-l10n_cache.sql8
-rw-r--r--maintenance/postgres/archives/patch-log_search.sql9
-rw-r--r--maintenance/postgres/archives/patch-update_sequences.sql20
-rw-r--r--maintenance/postgres/archives/patch-user_properties.sql8
-rw-r--r--maintenance/postgres/compare_schemas.pl15
-rw-r--r--maintenance/postgres/mediawiki_mysql2postgres.pl6
-rw-r--r--maintenance/postgres/tables.sql71
-rw-r--r--maintenance/preprocessorFuzzTest.php5
-rw-r--r--maintenance/protect.php68
-rw-r--r--maintenance/purgeList.php74
-rw-r--r--maintenance/purgeOldText.inc4
-rw-r--r--maintenance/purgeOldText.php46
-rw-r--r--maintenance/reassignEdits.inc.php143
-rw-r--r--maintenance/reassignEdits.php197
-rw-r--r--maintenance/rebuildFileCache.php179
-rw-r--r--maintenance/rebuildImages.php8
-rw-r--r--maintenance/rebuildInterwiki.inc1
-rw-r--r--maintenance/rebuildInterwiki.php3
-rw-r--r--maintenance/rebuildLocalisationCache.php133
-rw-r--r--maintenance/rebuildall.php78
-rw-r--r--maintenance/rebuildmessages.php56
-rw-r--r--maintenance/rebuildrecentchanges.inc246
-rw-r--r--maintenance/rebuildrecentchanges.php292
-rw-r--r--maintenance/rebuildtextindex.inc66
-rw-r--r--maintenance/rebuildtextindex.php132
-rw-r--r--maintenance/refreshImageCount.php65
-rw-r--r--maintenance/refreshLinks.inc202
-rw-r--r--maintenance/refreshLinks.php313
-rw-r--r--maintenance/removeUnusedAccounts.inc46
-rw-r--r--maintenance/removeUnusedAccounts.php150
-rw-r--r--maintenance/renameDbPrefix.php129
-rw-r--r--maintenance/renamewiki.php115
-rw-r--r--maintenance/renderDump.php75
-rw-r--r--maintenance/rollbackEdits.php97
-rw-r--r--maintenance/runBatchedQuery.php60
-rw-r--r--maintenance/runJobs.php141
-rw-r--r--maintenance/showJobs.php49
-rw-r--r--maintenance/showStats.php78
-rw-r--r--maintenance/sql.php108
-rw-r--r--maintenance/sqlite.php113
-rw-r--r--maintenance/sqlite/archives/initial-indexes.sql39
-rw-r--r--maintenance/sqlite/archives/patch-log_user_text.sql5
-rw-r--r--maintenance/sqlite/archives/patch-rd_interwiki.sql5
-rw-r--r--maintenance/sqlite/archives/patch-tc-timestamp.sql3
-rw-r--r--maintenance/sqlite/archives/searchindex-fts3.sql18
-rw-r--r--maintenance/sqlite/archives/searchindex-no-fts.sql25
-rw-r--r--maintenance/stats.php127
-rw-r--r--maintenance/storage/compressOld.inc6
-rw-r--r--maintenance/storage/compressOld.php2
-rw-r--r--maintenance/storage/dumpRev.php111
-rw-r--r--maintenance/storage/fixBug20757.php314
-rwxr-xr-xmaintenance/storage/make-blobs11
-rw-r--r--maintenance/storage/moveToExternal.php2
-rw-r--r--maintenance/storage/orphanStats.php43
-rw-r--r--maintenance/storage/recompressTracked.php57
-rw-r--r--maintenance/storage/resolveStubs.php10
-rw-r--r--maintenance/storage/storageTypeStats.php98
-rw-r--r--maintenance/storage/trackBlobs.php28
-rw-r--r--maintenance/tables.sql99
-rw-r--r--maintenance/testRunner.ora.sql37
-rw-r--r--maintenance/tests/.svnignore6
-rw-r--r--maintenance/tests/ApiSetup.php39
-rw-r--r--maintenance/tests/ApiTest.php164
-rw-r--r--maintenance/tests/CdbTest.php79
-rw-r--r--maintenance/tests/DatabaseSqliteTest.php57
-rw-r--r--maintenance/tests/DatabaseTest.php92
-rw-r--r--maintenance/tests/GlobalTest.php212
-rw-r--r--maintenance/tests/HttpTest.php567
-rw-r--r--maintenance/tests/IPTest.php52
-rw-r--r--maintenance/tests/ImageFunctionsTest.php48
-rw-r--r--maintenance/tests/LanguageConverterTest.php148
-rw-r--r--maintenance/tests/LicensesTest.php17
-rw-r--r--maintenance/tests/LocalFileTest.php97
-rw-r--r--maintenance/tests/Makefile23
-rw-r--r--maintenance/tests/MediaWikiParserTest.php283
-rw-r--r--maintenance/tests/MediaWiki_Setup.php28
-rw-r--r--maintenance/tests/README24
-rw-r--r--maintenance/tests/RevisionTest.php114
-rw-r--r--maintenance/tests/SanitizerTest.php73
-rw-r--r--maintenance/tests/SearchEngineTest.php138
-rw-r--r--maintenance/tests/SearchMySQLTest.php26
-rw-r--r--maintenance/tests/SearchUpdateTest.php103
-rw-r--r--maintenance/tests/SiteConfigurationTest.php311
-rw-r--r--maintenance/tests/TimeAdjustTest.php40
-rw-r--r--maintenance/tests/TitleTest.php17
-rw-r--r--maintenance/tests/XmlTest.php115
-rw-r--r--maintenance/tests/bootstrap.php15
-rw-r--r--maintenance/tests/phpunit.xml17
-rw-r--r--maintenance/tests/test-prefetch-current.xml75
-rw-r--r--maintenance/tests/test-prefetch-previous.xml57
-rw-r--r--maintenance/tests/test-prefetch-stub.xml75
-rw-r--r--maintenance/undelete.php51
-rw-r--r--maintenance/update.php56
-rw-r--r--maintenance/updateArticleCount.inc.php61
-rw-r--r--maintenance/updateArticleCount.php113
-rw-r--r--maintenance/updateRestrictions.php160
-rw-r--r--maintenance/updateSearchIndex.inc115
-rw-r--r--maintenance/updateSearchIndex.php186
-rw-r--r--maintenance/updateSpecialPages.php215
-rw-r--r--maintenance/updaters.inc532
-rw-r--r--maintenance/upgrade1_5.php2
-rw-r--r--maintenance/userOptions.inc10
-rw-r--r--maintenance/users.sql6
-rw-r--r--maintenance/waitForSlave.php35
-rw-r--r--maintenance/wikipedia-interwiki.sql7
253 files changed, 17566 insertions, 7782 deletions
diff --git a/maintenance/7zip.inc b/maintenance/7zip.inc
new file mode 100644
index 00000000..617083bf
--- /dev/null
+++ b/maintenance/7zip.inc
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Stream wrapper around 7za filter program.
+ * Required since we can't pass an open file resource to XMLReader->open()
+ * which is used for the text prefetch.
+ *
+ * @ingroup Maintenance
+ */
+class SevenZipStream {
+ var $stream;
+
+ private function stripPath( $path ) {
+ $prefix = 'mediawiki.compress.7z://';
+ return substr( $path, strlen( $prefix ) );
+ }
+
+ function stream_open( $path, $mode, $options, &$opened_path ) {
+ if( $mode[0] == 'r' ) {
+ $options = 'e -bd -so';
+ } elseif( $mode[0] == 'w' ) {
+ $options = 'a -bd -si';
+ } else {
+ return false;
+ }
+ $arg = wfEscapeShellArg( $this->stripPath( $path ) );
+ $command = "7za $options $arg";
+ if( !wfIsWindows() ) {
+ // Suppress the stupid messages on stderr
+ $command .= ' 2>/dev/null';
+ }
+ $this->stream = popen( $command, $mode );
+ return ($this->stream !== false);
+ }
+
+ function url_stat( $path, $flags ) {
+ return stat( $this->stripPath( $path ) );
+ }
+
+ // This is all so lame; there should be a default class we can extend
+
+ function stream_close() {
+ return fclose( $this->stream );
+ }
+
+ function stream_flush() {
+ return fflush( $this->stream );
+ }
+
+ function stream_read( $count ) {
+ return fread( $this->stream, $count );
+ }
+
+ function stream_write( $data ) {
+ return fwrite( $this->stream, $data );
+ }
+
+ function stream_tell() {
+ return ftell( $this->stream );
+ }
+
+ function stream_eof() {
+ return feof( $this->stream );
+ }
+
+ function stream_seek( $offset, $whence ) {
+ return fseek( $this->stream, $offset, $whence );
+ }
+}
+stream_wrapper_register( 'mediawiki.compress.7z', 'SevenZipStream' ); \ No newline at end of file
diff --git a/maintenance/Doxyfile b/maintenance/Doxyfile
index cdc748d8..db737bff 100644
--- a/maintenance/Doxyfile
+++ b/maintenance/Doxyfile
@@ -135,7 +135,7 @@ FILE_PATTERNS = *.c \
RECURSIVE = YES
EXCLUDE =
EXCLUDE_SYMLINKS = YES
-EXCLUDE_PATTERNS = LocalSettings.php AdminSettings.php
+EXCLUDE_PATTERNS = LocalSettings.php AdminSettings.php .svn {{EXCLUDE}}
EXAMPLE_PATH =
EXAMPLE_PATTERNS = *
EXAMPLE_RECURSIVE = NO
diff --git a/maintenance/FiveUpgrade.inc b/maintenance/FiveUpgrade.inc
index b041a436..be0112e9 100644
--- a/maintenance/FiveUpgrade.inc
+++ b/maintenance/FiveUpgrade.inc
@@ -526,7 +526,7 @@ class FiveUpgrade {
} else {
global $IP;
$this->log( 'adding iw_trans...' );
- dbsource( $IP . '/maintenance/archives/patch-interwiki-trans.sql', $this->dbw );
+ $this->dbw->sourceFile( $IP . '/maintenance/archives/patch-interwiki-trans.sql' );
$this->log( 'added iw_trans.' );
}
diff --git a/maintenance/Maintenance.php b/maintenance/Maintenance.php
new file mode 100644
index 00000000..277278c0
--- /dev/null
+++ b/maintenance/Maintenance.php
@@ -0,0 +1,860 @@
+<?php
+/**
+ * @file
+ * @ingroup Maintenance
+ * @defgroup Maintenance Maintenance
+ */
+
+// Define this so scripts can easily find doMaintenance.php
+define( 'DO_MAINTENANCE', dirname( __FILE__ ) . '/doMaintenance.php' );
+$maintClass = false;
+
+// Make sure we're on PHP5 or better
+if( version_compare( PHP_VERSION, '5.0.0' ) < 0 ) {
+ echo( "Sorry! This version of MediaWiki requires PHP 5; you are running " .
+ PHP_VERSION . ".\n\n" .
+ "If you are sure you already have PHP 5 installed, it may be installed\n" .
+ "in a different path from PHP 4. Check with your system administrator.\n" );
+ die();
+}
+
+/**
+ * Abstract maintenance class for quickly writing and churning out
+ * maintenance scripts with minimal effort. All that _must_ be defined
+ * is the execute() method. See docs/maintenance.txt for more info
+ * and a quick demo of how to use it.
+ *
+ * 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 Chad Horohoe <chad@anyonecanedit.org>
+ * @since 1.16
+ * @ingroup Maintenance
+ */
+abstract class Maintenance {
+
+ /**
+ * Constants for DB access type
+ * @see Maintenance::getDbType()
+ */
+ const DB_NONE = 0;
+ const DB_STD = 1;
+ const DB_ADMIN = 2;
+
+ // Const for getStdin()
+ const STDIN_ALL = 'all';
+
+ // This is the desired params
+ protected $mParams = array();
+
+ // Array of desired args
+ protected $mArgList = array();
+
+ // This is the list of options that were actually passed
+ protected $mOptions = array();
+
+ // This is the list of arguments that were actually passed
+ protected $mArgs = array();
+
+ // Name of the script currently running
+ protected $mSelf;
+
+ // Special vars for params that are always used
+ protected $mQuiet = false;
+ protected $mDbUser, $mDbPass;
+
+ // A description of the script, children should change this
+ protected $mDescription = '';
+
+ // Have we already loaded our user input?
+ protected $mInputLoaded = false;
+
+ // Batch size. If a script supports this, they should set
+ // a default with setBatchSize()
+ protected $mBatchSize = null;
+
+ /**
+ * List of all the core maintenance scripts. This is added
+ * to scripts added by extensions in $wgMaintenanceScripts
+ * and returned by getMaintenanceScripts()
+ */
+ protected static $mCoreScripts = null;
+
+ /**
+ * Default constructor. Children should call this if implementing
+ * their own constructors
+ */
+ public function __construct() {
+ $this->addDefaultParams();
+ register_shutdown_function( array( $this, 'outputChanneled' ), false );
+ }
+
+ /**
+ * Do the actual work. All child classes will need to implement this
+ */
+ abstract public function execute();
+
+ /**
+ * Add a parameter to the script. Will be displayed on --help
+ * with the associated description
+ *
+ * @param $name String The name of the param (help, version, etc)
+ * @param $description String The description of the param to show on --help
+ * @param $required boolean Is the param required?
+ * @param $withArg Boolean Is an argument required with this option?
+ */
+ protected function addOption( $name, $description, $required = false, $withArg = false ) {
+ $this->mParams[$name] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg );
+ }
+
+ /**
+ * Checks to see if a particular param exists.
+ * @param $name String The name of the param
+ * @return boolean
+ */
+ protected function hasOption( $name ) {
+ return isset( $this->mOptions[$name] );
+ }
+
+ /**
+ * Get an option, or return the default
+ * @param $name String The name of the param
+ * @param $default mixed Anything you want, default null
+ * @return mixed
+ */
+ protected function getOption( $name, $default = null ) {
+ if( $this->hasOption( $name ) ) {
+ return $this->mOptions[$name];
+ } else {
+ // Set it so we don't have to provide the default again
+ $this->mOptions[$name] = $default;
+ return $this->mOptions[$name];
+ }
+ }
+
+ /**
+ * Add some args that are needed
+ * @param $arg String Name of the arg, like 'start'
+ * @param $description String Short description of the arg
+ * @param $required Boolean Is this required?
+ */
+ protected function addArg( $arg, $description, $required = true ) {
+ $this->mArgList[] = array(
+ 'name' => $arg,
+ 'desc' => $description,
+ 'require' => $required
+ );
+ }
+
+ /**
+ * Does a given argument exist?
+ * @param $argId int The integer value (from zero) for the arg
+ * @return boolean
+ */
+ protected function hasArg( $argId = 0 ) {
+ return isset( $this->mArgs[$argId] );
+ }
+
+ /**
+ * Get an argument.
+ * @param $argId int The integer value (from zero) for the arg
+ * @param $default mixed The default if it doesn't exist
+ * @return mixed
+ */
+ protected function getArg( $argId = 0, $default = null ) {
+ return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default;
+ }
+
+ /**
+ * Set the batch size.
+ * @param $s int The number of operations to do in a batch
+ */
+ protected function setBatchSize( $s = 0 ) {
+ $this->mBatchSize = $s;
+ }
+
+ /**
+ * Get the script's name
+ * @return String
+ */
+ public function getName() {
+ return $this->mSelf;
+ }
+
+ /**
+ * Return input from stdin.
+ * @param $length int 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 )
+ return file_get_contents( 'php://stdin' );
+ $f = fopen( 'php://stdin', 'rt' );
+ if( !$len )
+ return $f;
+ $input = fgets( $f, $len );
+ fclose( $f );
+ return rtrim( $input );
+ }
+
+ /**
+ * Throw some output to the user. Scripts can call this with no fears,
+ * as we handle all --quiet stuff here
+ * @param $out String The text to show to the user
+ * @param $channel Mixed Unique identifier for the channel. See function outputChanneled.
+ */
+ protected function output( $out, $channel = null ) {
+ if( $this->mQuiet ) {
+ return;
+ }
+ if ( $channel === null ) {
+ $f = fopen( 'php://stdout', 'w' );
+ fwrite( $f, $out );
+ fclose( $f );
+ }
+ else {
+ $out = preg_replace( '/\n\z/', '', $out );
+ $this->outputChanneled( $out, $channel );
+ }
+ }
+
+ /**
+ * Throw an error to the user. Doesn't respect --quiet, so don't use
+ * this for non-error output
+ * @param $err String The error to display
+ * @param $die boolean If true, go ahead and die out.
+ */
+ protected function error( $err, $die = false ) {
+ $this->outputChanneled( false );
+ if ( php_sapi_name() == 'cli' ) {
+ fwrite( STDERR, $err . "\n" );
+ } else {
+ $f = fopen( 'php://stderr', 'w' );
+ fwrite( $f, $err . "\n" );
+ fclose( $f );
+ }
+ if( $die ) die();
+ }
+
+ private $atLineStart = true;
+ private $lastChannel = null;
+
+ /**
+ * Message outputter with channeled message support. Messages on the
+ * same channel are concatenated, but any intervening messages in another
+ * channel start a new line.
+ * @param $msg String The message without trailing newline
+ * @param $channel Channel identifier or null for no channel. Channel comparison uses ===.
+ */
+ public function outputChanneled( $msg, $channel = null ) {
+ $handle = fopen( 'php://stdout', 'w' );
+
+ if ( $msg === false ) {
+ // For cleanup
+ if ( !$this->atLineStart ) fwrite( $handle, "\n" );
+ fclose( $handle );
+ return;
+ }
+
+ // End the current line if necessary
+ if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
+ fwrite( $handle, "\n" );
+ }
+
+ fwrite( $handle, $msg );
+
+ $this->atLineStart = false;
+ if ( $channel === null ) {
+ // For unchanneled messages, output trailing newline immediately
+ fwrite( $handle, "\n" );
+ $this->atLineStart = true;
+ }
+ $this->lastChannel = $channel;
+
+ // Cleanup handle
+ fclose( $handle );
+ }
+
+ /**
+ * Does the script need different DB access? By default, we give Maintenance
+ * scripts normal rights to the DB. Sometimes, a script needs admin rights
+ * access for a reason and sometimes they want no access. Subclasses should
+ * override and return one of the following values, as needed:
+ * 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 int
+ */
+ public function getDbType() {
+ return Maintenance::DB_STD;
+ }
+
+ /**
+ * Add the default parameters to the scripts
+ */
+ protected function addDefaultParams() {
+ $this->addOption( 'help', "Display this help message" );
+ $this->addOption( 'quiet', "Whether to supress non-error output" );
+ $this->addOption( 'conf', "Location of LocalSettings.php, if not default", false, true );
+ $this->addOption( 'wiki', "For specifying the wiki ID", false, true );
+ $this->addOption( 'globals', "Output globals at the end of processing for debugging" );
+ // If we support a DB, show the options
+ if( $this->getDbType() > 0 ) {
+ $this->addOption( 'dbuser', "The DB user to use for this script", false, true );
+ $this->addOption( 'dbpass', "The password to use for this script", false, true );
+ }
+ // If we support $mBatchSize, show the option
+ if( $this->mBatchSize ) {
+ $this->addOption( 'batch-size', 'Run this many operations ' .
+ 'per batch, default: ' . $this->mBatchSize , false, true );
+ }
+ }
+
+ /**
+ * Run a child maintenance script. Pass all of the current arguments
+ * to it.
+ * @param $maintClass String A name of a child maintenance class
+ * @param $classFile String Full path of where the child is
+ * @return Maintenance child
+ */
+ protected function runChild( $maintClass, $classFile = null ) {
+ // If we haven't already specified, kill setup procedures
+ // for child scripts, we've already got a sane environment
+ self::disableSetup();
+
+ // Make sure the class is loaded first
+ if( !class_exists( $maintClass ) ) {
+ if( $classFile ) {
+ require_once( $classFile );
+ }
+ if( !class_exists( $maintClass ) ) {
+ $this->error( "Cannot spawn child: $maintClass" );
+ }
+ }
+
+ $child = new $maintClass();
+ $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs );
+ return $child;
+ }
+
+ /**
+ * Disable Setup.php mostly
+ */
+ protected static function disableSetup() {
+ if( !defined( 'MW_NO_SETUP' ) )
+ define( 'MW_NO_SETUP', true );
+ }
+
+ /**
+ * Do some sanity checking and basic setup
+ */
+ public function setup() {
+ global $IP, $wgCommandLineMode, $wgRequestTime;
+
+ # Abort if called from a web server
+ if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) {
+ $this->error( "This script must be run from the command line", true );
+ }
+
+ # Make sure we can handle script parameters
+ if( !ini_get( 'register_argc_argv' ) ) {
+ $this->error( "Cannot get command line arguments, register_argc_argv is set to false", true );
+ }
+
+ if( version_compare( phpversion(), '5.2.4' ) >= 0 ) {
+ // Send PHP warnings and errors to stderr instead of stdout.
+ // This aids in diagnosing problems, while keeping messages
+ // out of redirected output.
+ if( ini_get( 'display_errors' ) ) {
+ ini_set( 'display_errors', 'stderr' );
+ }
+
+ // Don't touch the setting on earlier versions of PHP,
+ // as setting it would disable output if you'd wanted it.
+
+ // Note that exceptions are also sent to stderr when
+ // command-line mode is on, regardless of PHP version.
+ }
+
+ # Set the memory limit
+ # Note we need to set it again later in cache LocalSettings changed it
+ ini_set( 'memory_limit', $this->memoryLimit() );
+
+ # Set max execution time to 0 (no limit). PHP.net says that
+ # "When running PHP from the command line the default setting is 0."
+ # But sometimes this doesn't seem to be the case.
+ ini_set( 'max_execution_time', 0 );
+
+ $wgRequestTime = microtime( true );
+
+ # Define us as being in MediaWiki
+ define( 'MEDIAWIKI', true );
+
+ # Setup $IP, using MW_INSTALL_PATH if it exists
+ $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== ''
+ ? getenv( 'MW_INSTALL_PATH' )
+ : realpath( dirname( __FILE__ ) . '/..' );
+
+ $wgCommandLineMode = true;
+ # Turn off output buffering if it's on
+ @ob_end_flush();
+
+ $this->loadParamsAndArgs();
+ $this->maybeHelp();
+ $this->validateParamsAndArgs();
+ }
+
+ /**
+ * Normally we disable the memory_limit when running admin scripts.
+ * Some scripts may wish to actually set a limit, however, to avoid
+ * blowing up unexpectedly.
+ */
+ public function memoryLimit() {
+ return -1;
+ }
+
+ /**
+ * Clear all params and arguments.
+ */
+ public function clearParamsAndArgs() {
+ $this->mOptions = array();
+ $this->mArgs = array();
+ $this->mInputLoaded = false;
+ }
+
+ /**
+ * Process command line arguments
+ * $mOptions becomes an array with keys set to the option names
+ * $mArgs becomes a zero-based array containing the non-option arguments
+ *
+ * @param $self String The name of the script, if any
+ * @param $opts Array An array of options, in form of key=>value
+ * @param $args Array An array of command line arguments
+ */
+ public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
+ # If we were given opts or args, set those and return early
+ if( $self ) {
+ $this->mSelf = $self;
+ $this->mInputLoaded = true;
+ }
+ if( $opts ) {
+ $this->mOptions = $opts;
+ $this->mInputLoaded = true;
+ }
+ if( $args ) {
+ $this->mArgs = $args;
+ $this->mInputLoaded = true;
+ }
+
+ # If we've already loaded input (either by user values or from $argv)
+ # skip on loading it again. The array_shift() will corrupt values if
+ # it's run again and again
+ if( $this->mInputLoaded ) {
+ $this->loadSpecialVars();
+ return;
+ }
+
+ global $argv;
+ $this->mSelf = array_shift( $argv );
+
+ $options = array();
+ $args = array();
+
+ # Parse arguments
+ for( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
+ if ( $arg == '--' ) {
+ # End of options, remainder should be considered arguments
+ $arg = next( $argv );
+ while( $arg !== false ) {
+ $args[] = $arg;
+ $arg = next( $argv );
+ }
+ break;
+ } elseif ( substr( $arg, 0, 2 ) == '--' ) {
+ # Long options
+ $option = substr( $arg, 2 );
+ if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) {
+ $param = next( $argv );
+ if ( $param === false ) {
+ $this->error( "\nERROR: $option needs a value after it\n" );
+ $this->maybeHelp( true );
+ }
+ $options[$option] = $param;
+ } else {
+ $bits = explode( '=', $option, 2 );
+ if( count( $bits ) > 1 ) {
+ $option = $bits[0];
+ $param = $bits[1];
+ } else {
+ $param = 1;
+ }
+ $options[$option] = $param;
+ }
+ } elseif ( substr( $arg, 0, 1 ) == '-' ) {
+ # Short options
+ for ( $p=1; $p<strlen( $arg ); $p++ ) {
+ $option = $arg{$p};
+ if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) {
+ $param = next( $argv );
+ if ( $param === false ) {
+ $this->error( "\nERROR: $option needs a value after it\n" );
+ $this->maybeHelp( true );
+ }
+ $options[$option] = $param;
+ } else {
+ $options[$option] = 1;
+ }
+ }
+ } else {
+ $args[] = $arg;
+ }
+ }
+
+ $this->mOptions = $options;
+ $this->mArgs = $args;
+ $this->loadSpecialVars();
+ $this->mInputLoaded = true;
+ }
+
+ /**
+ * Run some validation checks on the params, etc
+ */
+ protected function validateParamsAndArgs() {
+ $die = false;
+ # Check to make sure we've got all the required options
+ foreach( $this->mParams as $opt => $info ) {
+ if( $info['require'] && !$this->hasOption( $opt ) ) {
+ $this->error( "Param $opt required!" );
+ $die = true;
+ }
+ }
+ # Check arg list too
+ foreach( $this->mArgList as $k => $info ) {
+ if( $info['require'] && !$this->hasArg($k) ) {
+ $this->error( "Argument <" . $info['name'] . "> required!" );
+ $die = true;
+ }
+ }
+
+ if( $die ) $this->maybeHelp( true );
+ }
+
+ /**
+ * Handle the special variables that are global to all scripts
+ */
+ protected function loadSpecialVars() {
+ if( $this->hasOption( 'dbuser' ) )
+ $this->mDbUser = $this->getOption( 'dbuser' );
+ if( $this->hasOption( 'dbpass' ) )
+ $this->mDbPass = $this->getOption( 'dbpass' );
+ if( $this->hasOption( 'quiet' ) )
+ $this->mQuiet = true;
+ if( $this->hasOption( 'batch-size' ) )
+ $this->mBatchSize = $this->getOption( 'batch-size' );
+ }
+
+ /**
+ * Maybe show the help.
+ * @param $force boolean Whether to force the help to show, default false
+ */
+ protected function maybeHelp( $force = false ) {
+ $screenWidth = 80; // TODO: Caculate this!
+ $tab = " ";
+ $descWidth = $screenWidth - ( 2 * strlen( $tab ) );
+
+ ksort( $this->mParams );
+ if( $this->hasOption( 'help' ) || $force ) {
+ $this->mQuiet = false;
+
+ if( $this->mDescription ) {
+ $this->output( "\n" . $this->mDescription . "\n" );
+ }
+ $output = "\nUsage: php " . basename( $this->mSelf );
+ if( $this->mParams ) {
+ $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]";
+ }
+ if( $this->mArgList ) {
+ $output .= " <";
+ foreach( $this->mArgList as $k => $arg ) {
+ $output .= $arg['name'] . ">";
+ if( $k < count( $this->mArgList ) - 1 )
+ $output .= " <";
+ }
+ }
+ $this->output( "$output\n" );
+ foreach( $this->mParams as $par => $info ) {
+ $this->output( wordwrap( "$tab$par : " . $info['desc'], $descWidth,
+ "\n$tab$tab" ) . "\n" );
+ }
+ foreach( $this->mArgList as $info ) {
+ $this->output( wordwrap( "$tab<" . $info['name'] . "> : " .
+ $info['desc'], $descWidth, "\n$tab$tab" ) . "\n" );
+ }
+ die( 1 );
+ }
+ }
+
+ /**
+ * Handle some last-minute setup here.
+ */
+ public function finalSetup() {
+ global $wgCommandLineMode, $wgShowSQLErrors;
+ global $wgTitle, $wgProfiling, $IP, $wgDBadminuser, $wgDBadminpassword;
+ global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf;
+
+ # Turn off output buffering again, it might have been turned on in the settings files
+ if( ob_get_level() ) {
+ ob_end_flush();
+ }
+ # Same with these
+ $wgCommandLineMode = true;
+
+ # If these were passed, use them
+ if( $this->mDbUser )
+ $wgDBadminuser = $this->mDbUser;
+ if( $this->mDbPass )
+ $wgDBadminpassword = $this->mDbPass;
+
+ if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) {
+ $wgDBuser = $wgDBadminuser;
+ $wgDBpassword = $wgDBadminpassword;
+
+ if( $wgDBservers ) {
+ foreach ( $wgDBservers as $i => $server ) {
+ $wgDBservers[$i]['user'] = $wgDBuser;
+ $wgDBservers[$i]['password'] = $wgDBpassword;
+ }
+ }
+ if( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
+ $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
+ $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
+ }
+ }
+
+ if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
+ $fn = MW_CMDLINE_CALLBACK;
+ $fn();
+ }
+
+ $wgShowSQLErrors = true;
+ @set_time_limit( 0 );
+ ini_set( 'memory_limit', $this->memoryLimit() );
+
+ $wgProfiling = false; // only for Profiler.php mode; avoids OOM errors
+ }
+
+ /**
+ * Potentially debug globals. Originally a feature only
+ * for refreshLinks
+ */
+ public function globals() {
+ if( $this->hasOption( 'globals' ) ) {
+ print_r( $GLOBALS );
+ }
+ }
+
+ /**
+ * Do setup specific to WMF
+ */
+ public function loadWikimediaSettings() {
+ global $IP, $wgNoDBParam, $wgUseNormalUser, $wgConf, $site, $lang;
+
+ if ( empty( $wgNoDBParam ) ) {
+ # Check if we were passed a db name
+ if ( isset( $this->mOptions['wiki'] ) ) {
+ $db = $this->mOptions['wiki'];
+ } else {
+ $db = array_shift( $this->mArgs );
+ }
+ list( $site, $lang ) = $wgConf->siteFromDB( $db );
+
+ # If not, work out the language and site the old way
+ if ( is_null( $site ) || is_null( $lang ) ) {
+ if ( !$db ) {
+ $lang = 'aa';
+ } else {
+ $lang = $db;
+ }
+ if ( isset( $this->mArgs[0] ) ) {
+ $site = array_shift( $this->mArgs );
+ } else {
+ $site = 'wikipedia';
+ }
+ }
+ } else {
+ $lang = 'aa';
+ $site = 'wikipedia';
+ }
+
+ # This is for the IRC scripts, which now run as the apache user
+ # The apache user doesn't have access to the wikiadmin_pass command
+ if ( $_ENV['USER'] == 'apache' ) {
+ #if ( posix_geteuid() == 48 ) {
+ $wgUseNormalUser = true;
+ }
+
+ putenv( 'wikilang=' . $lang );
+
+ $DP = $IP;
+ ini_set( 'include_path', ".:$IP:$IP/includes:$IP/languages:$IP/maintenance" );
+
+ if ( $lang == 'test' && $site == 'wikipedia' ) {
+ define( 'TESTWIKI', 1 );
+ }
+ }
+
+ /**
+ * Generic setup for most installs. Returns the location of LocalSettings
+ * @return String
+ */
+ public function loadSettings() {
+ global $wgWikiFarm, $wgCommandLineMode, $IP, $DP;
+
+ $wgWikiFarm = false;
+ if ( isset( $this->mOptions['conf'] ) ) {
+ $settingsFile = $this->mOptions['conf'];
+ } else {
+ $settingsFile = "$IP/LocalSettings.php";
+ }
+ if ( isset( $this->mOptions['wiki'] ) ) {
+ $bits = explode( '-', $this->mOptions['wiki'] );
+ if ( count( $bits ) == 1 ) {
+ $bits[] = '';
+ }
+ define( 'MW_DB', $bits[0] );
+ define( 'MW_PREFIX', $bits[1] );
+ }
+
+ if ( !is_readable( $settingsFile ) ) {
+ $this->error( "A copy of your installation's LocalSettings.php\n" .
+ "must exist and be readable in the source directory.", true );
+ }
+ $wgCommandLineMode = true;
+ $DP = $IP;
+ return $settingsFile;
+ }
+
+ /**
+ * Support function for cleaning up redundant text records
+ * @param $delete boolean Whether or not to actually delete the records
+ * @author Rob Church <robchur@gmail.com>
+ */
+ protected function purgeRedundantText( $delete = true ) {
+ # Data should come off the master, wrapped in a transaction
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+
+ $tbl_arc = $dbw->tableName( 'archive' );
+ $tbl_rev = $dbw->tableName( 'revision' );
+ $tbl_txt = $dbw->tableName( 'text' );
+
+ # Get "active" text records from the revisions table
+ $this->output( "Searching for active text records in revisions table..." );
+ $res = $dbw->query( "SELECT DISTINCT rev_text_id FROM $tbl_rev" );
+ foreach( $res as $row ) {
+ $cur[] = $row->rev_text_id;
+ }
+ $this->output( "done.\n" );
+
+ # Get "active" text records from the archive table
+ $this->output( "Searching for active text records in archive table..." );
+ $res = $dbw->query( "SELECT DISTINCT ar_text_id FROM $tbl_arc" );
+ foreach( $res as $row ) {
+ $cur[] = $row->ar_text_id;
+ }
+ $this->output( "done.\n" );
+
+ # Get the IDs of all text records not in these sets
+ $this->output( "Searching for inactive text records..." );
+ $set = implode( ', ', $cur );
+ $res = $dbw->query( "SELECT old_id FROM $tbl_txt WHERE old_id NOT IN ( $set )" );
+ $old = array();
+ foreach( $res as $row ) {
+ $old[] = $row->old_id;
+ }
+ $this->output( "done.\n" );
+
+ # Inform the user of what we're going to do
+ $count = count( $old );
+ $this->output( "$count inactive items found.\n" );
+
+ # Delete as appropriate
+ if( $delete && $count ) {
+ $this->output( "Deleting..." );
+ $set = implode( ', ', $old );
+ $dbw->query( "DELETE FROM $tbl_txt WHERE old_id IN ( $set )" );
+ $this->output( "done.\n" );
+ }
+
+ # Done
+ $dbw->commit();
+ }
+
+ /**
+ * Get the maintenance directory.
+ */
+ protected function getDir() {
+ return dirname( __FILE__ );
+ }
+
+ /**
+ * 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 ) {
+ self::disableSetup();
+ $paths = array(
+ dirname( __FILE__ ),
+ dirname( __FILE__ ) . '/gearman',
+ dirname( __FILE__ ) . '/language',
+ dirname( __FILE__ ) . '/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;
+ }
+}
diff --git a/maintenance/README b/maintenance/README
index e2215c1d..d6e76917 100644
--- a/maintenance/README
+++ b/maintenance/README
@@ -10,8 +10,8 @@ proper installation.
Certain scripts will require elevated access to the database. In order to
provide this, first create a MySQL user with "all" permissions on the wiki
-database, and then place their username and password in an AdminSettings.php
-file in the directory above. See AdminSettings.sample for specifics on this.
+database, and then set $wgDBadminuser and $wgDBadminpassword in your
+LocalSettings.php
=== Brief explanation of files ===
@@ -94,7 +94,7 @@ installations.
Immediately complete all jobs in the job queue
stats.php
- Show all statistics stored in memcached
+ Show all statistics stored in the cache
undelete.php
Undelete all revisions of a page
diff --git a/maintenance/addwiki.php b/maintenance/addwiki.php
index ebe52f2e..0cb4d74a 100644
--- a/maintenance/addwiki.php
+++ b/maintenance/addwiki.php
@@ -1,104 +1,163 @@
<?php
/**
+ * @defgroup Wikimedia Wikimedia
+ */
+
+/**
* Add a new wiki
* Wikimedia specific!
*
+ * 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
+ * @ingroup Wikimedia
*/
-$wgNoDBParam = true;
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-require_once( "commandLine.inc" );
-require_once( "rebuildInterwiki.inc" );
-require_once( "languages/Names.php" );
-if ( count( $args ) != 3 ) {
- wfDie( "Usage: php addwiki.php <language> <site> <dbname>\nThe site for Wikipedia is 'wikipedia'.\n" );
-}
+class AddWiki extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Add a new wiki to the family. Wikimedia specific!";
+ $this->addArg( 'language', 'Language code of new site' );
+ $this->addArg( 'site', 'Type of site' );
+ $this->addArg( 'dbname', 'Name of database to create' );
+ }
-addWiki( $args[0], $args[1], $args[2] );
+ public function getDbType() {
+ return Maintenance::DB_ADMIN;
+ }
-# -----------------------------------------------------------------
+ public function execute() {
+ global $IP, $wgLanguageNames, $wgDefaultExternalStore, $wgNoDBParam;
-function addWiki( $lang, $site, $dbName )
-{
- global $IP, $wgLanguageNames, $wgDefaultExternalStore;
+ $wgNoDBParam = true;
+ $lang = $this->getArg(0);
+ $site = $this->getArg(1);
+ $dbName = $this->getArg(2);
- if ( !isset( $wgLanguageNames[$lang] ) ) {
- print "Language $lang not found in \$wgLanguageNames\n";
- return;
- }
- $name = $wgLanguageNames[$lang];
+ if ( !isset( $wgLanguageNames[$lang] ) ) {
+ $this->error( "Language $lang not found in \$wgLanguageNames", true );
+ }
+ $name = $wgLanguageNames[$lang];
- $dbw = wfGetDB( DB_MASTER );
- $common = "/home/wikipedia/common";
- $maintenance = "$IP/maintenance";
+ $dbw = wfGetDB( DB_MASTER );
+ $common = "/home/wikipedia/common";
- print "Creating database $dbName for $lang.$site ($name)\n";
-
- # Set up the database
- $dbw->query( "SET table_type=Innodb" );
- $dbw->query( "CREATE DATABASE $dbName" );
- $dbw->selectDB( $dbName );
-
- print "Initialising tables\n";
- dbsource( "$maintenance/tables.sql", $dbw );
- dbsource( "$IP/extensions/OAI/update_table.sql", $dbw );
- dbsource( "$IP/extensions/AntiSpoof/sql/patch-antispoof.mysql.sql", $dbw );
- dbsource( "$IP/extensions/CheckUser/cu_changes.sql", $dbw );
- dbsource( "$IP/extensions/CheckUser/cu_log.sql", $dbw );
- dbsource( "$IP/extensions/TitleKey/titlekey.sql", $dbw );
- dbsource( "$IP/extensions/Oversight/hidden.sql", $dbw );
- dbsource( "$IP/extensions/GlobalBlocking/localdb_patches/setup-global_block_whitelist.sql", $dbw );
-
- $dbw->query( "INSERT INTO site_stats(ss_row_id) VALUES (1)" );
-
- # Initialise external storage
- if ( is_array( $wgDefaultExternalStore ) ) {
- $stores = $wgDefaultExternalStore;
- } elseif ( $stores ) {
- $stores = array( $wgDefaultExternalStore );
- } else {
- $stores = array();
- }
- if ( count( $stores ) ) {
- require_once( 'ExternalStoreDB.php' );
- print "Initialising external storage $store...\n";
- global $wgDBuser, $wgDBpassword, $wgExternalServers;
- foreach ( $stores as $storeURL ) {
- $m = array();
- if ( !preg_match( '!^DB://(.*)$!', $storeURL, $m ) ) {
- continue;
+ $this->output( "Creating database $dbName for $lang.$site ($name)\n" );
+
+ # Set up the database
+ $dbw->query( "SET table_type=Innodb" );
+ $dbw->query( "CREATE DATABASE $dbName" );
+ $dbw->selectDB( $dbName );
+
+ $this->output( "Initialising tables\n" );
+ $dbw->sourceFile( $this->getDir() . '/tables.sql' );
+ $dbw->sourceFile( "$IP/extensions/OAI/update_table.sql" );
+ $dbw->sourceFile( "$IP/extensions/AntiSpoof/sql/patch-antispoof.mysql.sql" );
+ $dbw->sourceFile( "$IP/extensions/CheckUser/cu_changes.sql" );
+ $dbw->sourceFile( "$IP/extensions/CheckUser/cu_log.sql" );
+ $dbw->sourceFile( "$IP/extensions/TitleKey/titlekey.sql" );
+ $dbw->sourceFile( "$IP/extensions/Oversight/hidden.sql" );
+ $dbw->sourceFile( "$IP/extensions/GlobalBlocking/localdb_patches/setup-global_block_whitelist.sql" );
+ $dbw->sourceFile( "$IP/extensions/AbuseFilter/abusefilter.tables.sql" );
+ $dbw->sourceFile( "$IP/extensions/UsabilityInitiative/PrefStats/PrefStats.sql" );
+ $dbw->sourceFile( "$IP/extensions/ProofreadPage/ProofreadPage.sql" );
+ $dbw->sourceFile( "$IP/extensions/UsabilityInitiative/ClickTracking/ClickTrackingEvents.sql" );
+ $dbw->sourceFile( "$IP/extensions/UsabilityInitiative/ClickTracking/ClickTracking.sql" );
+ $dbw->sourceFile( "$IP/extensions/UsabilityInitiative/UserDailyContribs/UserDailyContribs.sql" );
+
+ $dbw->query( "INSERT INTO site_stats(ss_row_id) VALUES (1)" );
+
+ # Initialise external storage
+ if ( is_array( $wgDefaultExternalStore ) ) {
+ $stores = $wgDefaultExternalStore;
+ } elseif ( $stores ) {
+ $stores = array( $wgDefaultExternalStore );
+ } else {
+ $stores = array();
+ }
+ if ( count( $stores ) ) {
+ global $wgDBuser, $wgDBpassword, $wgExternalServers;
+ foreach ( $stores as $storeURL ) {
+ $m = array();
+ if ( !preg_match( '!^DB://(.*)$!', $storeURL, $m ) ) {
+ continue;
+ }
+
+ $cluster = $m[1];
+ $this->output( "Initialising external storage $cluster...\n" );
+
+ # Hack
+ $wgExternalServers[$cluster][0]['user'] = $wgDBuser;
+ $wgExternalServers[$cluster][0]['password'] = $wgDBpassword;
+
+ $store = new ExternalStoreDB;
+ $extdb = $store->getMaster( $cluster );
+ $extdb->query( "SET table_type=InnoDB" );
+ $extdb->query( "CREATE DATABASE $dbName" );
+ $extdb->selectDB( $dbName );
+
+ # Hack x2
+ $blobsTable = $store->getTable( $extdb );
+ $sedCmd = "sed s/blobs\\\\\\>/$blobsTable/ " . $this->getDir() . "/storage/blobs.sql";
+ $blobsFile = popen( $sedCmd, 'r' );
+ $extdb->sourceStream( $blobsFile );
+ pclose( $blobsFile );
+ $extdb->commit();
}
-
- $cluster = $m[1];
-
- # Hack
- $wgExternalServers[$cluster][0]['user'] = $wgDBuser;
- $wgExternalServers[$cluster][0]['password'] = $wgDBpassword;
-
- $store = new ExternalStoreDB;
- $extdb =& $store->getMaster( $cluster );
- $extdb->query( "SET table_type=InnoDB" );
- $extdb->query( "CREATE DATABASE $dbName" );
- $extdb->selectDB( $dbName );
- dbsource( "$maintenance/storage/blobs.sql", $extdb );
- $extdb->immediateCommit();
}
- }
- global $wgTitle, $wgArticle;
- $wgTitle = Title::newFromText( wfMsgWeirdKey( "mainpage/$lang" ) );
- print "Writing main page to " . $wgTitle->getPrefixedDBkey() . "\n";
- $wgArticle = new Article( $wgTitle );
- $ucsite = ucfirst( $site );
+ global $wgTitle, $wgArticle;
+ $wgTitle = Title::newFromText( wfMsgWeirdKey( "mainpage/$lang" ) );
+ $this->output( "Writing main page to " . $wgTitle->getPrefixedDBkey() . "\n" );
+ $wgArticle = new Article( $wgTitle );
+ $ucsite = ucfirst( $site );
+
+ $wgArticle->insertNewArticle( $this->getFirstArticle( $ucsite, $name ), '', false, false );
+
+ $this->output( "Adding to dblists\n" );
+
+ # Add to dblist
+ $file = fopen( "$common/all.dblist", "a" );
+ fwrite( $file, "$dbName\n" );
+ fclose( $file );
+
+ # Update the sublists
+ shell_exec("cd $common && ./refresh-dblist");
+
+ #print "Constructing interwiki SQL\n";
+ # Rebuild interwiki tables
+ #passthru( '/home/wikipedia/conf/interwiki/update' );
- $wgArticle->insertNewArticle( <<<EOT
+ $this->output( "Script ended. You still have to:
+ * Add any required settings in InitialiseSettings.php
+ * Run sync-common-all
+ * Run /home/wikipedia/conf/interwiki/update
+ " );
+ }
+
+ private function getFirstArticle( $ucsite, $name ) {
+ return <<<EOT
==This subdomain is reserved for the creation of a [[wikimedia:Our projects|$ucsite]] in '''[[w:en:{$name}|{$name}]]''' language==
* Please '''do not start editing''' this new site. This site has a test project on the [[incubator:|Wikimedia Incubator]] (or on the [[betawikiversity:|BetaWikiversity]] or on the [[oldwikisource:|Old Wikisource]]) and it will be imported to here.
-* If you would like to help translating the interface to this language, please do not translate here, but go to [[betawiki:|Betawiki]], a special wiki for translating the interface. That way everyone can use it on every wiki using the [[mw:|same software]].
+* If you would like to help translating the interface to this language, please do not translate here, but go to [[translatewiki:|translatewiki]], a special wiki for translating the interface. That way everyone can use it on every wiki using the [[mw:|same software]].
* For information about how to edit and for other general help, see [[m:Help:Contents|Help on Wikimedia's Meta-Wiki]] or [[mw:Help:Contents|Help on MediaWiki.org]].
@@ -116,135 +175,280 @@ function addWiki( $lang, $site, $dbName )
See Wikimedia's [[m:|Meta-Wiki]] for the coordination of these projects.
[[aa:]]
+[[ab:]]
+[[ace:]]
[[af:]]
+[[ak:]]
[[als:]]
+[[am:]]
+[[an:]]
+[[ang:]]
[[ar:]]
-[[de:]]
-[[en:]]
+[[arc:]]
+[[arz:]]
[[as:]]
[[ast:]]
+[[av:]]
[[ay:]]
[[az:]]
+[[ba:]]
+[[bar:]]
+[[bat-smg:]]
[[bcl:]]
[[be:]]
+[[be-x-old:]]
[[bg:]]
+[[bh:]]
+[[bi:]]
+[[bm:]]
[[bn:]]
[[bo:]]
+[[bpy:]]
+[[br:]]
[[bs:]]
-[[cs:]]
+[[bug:]]
+[[bxr:]]
+[[ca:]]
+[[cbk-zam:]]
+[[cdo:]]
+[[ce:]]
+[[ceb:]]
+[[ch:]]
+[[cho:]]
+[[chr:]]
+[[chy:]]
+[[ckb:]]
[[co:]]
+[[cr:]]
+[[crh:]]
[[cs:]]
+[[csb:]]
+[[cu:]]
+[[cv:]]
[[cy:]]
[[da:]]
+[[de:]]
+[[diq:]]
+[[dk:]]
+[[dsb:]]
+[[dv:]]
+[[dz:]]
+[[ee:]]
[[el:]]
+[[eml:]]
+[[en:]]
[[eo:]]
[[es:]]
[[et:]]
[[eu:]]
+[[ext:]]
[[fa:]]
+[[ff:]]
[[fi:]]
+[[fiu-vro:]]
+[[fj:]]
+[[fo:]]
[[fr:]]
+[[frp:]]
+[[fur:]]
[[fy:]]
[[ga:]]
+[[gan:]]
+[[gd:]]
[[gl:]]
+[[glk:]]
[[gn:]]
+[[got:]]
[[gu:]]
+[[gv:]]
+[[ha:]]
+[[hak:]]
+[[haw:]]
[[he:]]
[[hi:]]
+[[hif:]]
+[[ho:]]
[[hr:]]
[[hsb:]]
+[[ht:]]
+[[hu:]]
[[hy:]]
+[[hz:]]
[[ia:]]
[[id:]]
+[[ie:]]
+[[ig:]]
+[[ii:]]
+[[ik:]]
+[[ilo:]]
+[[io:]]
[[is:]]
[[it:]]
+[[iu:]]
[[ja:]]
+[[jbo:]]
+[[jv:]]
[[ka:]]
+[[kaa:]]
+[[kab:]]
+[[kg:]]
+[[ki:]]
+[[kj:]]
[[kk:]]
+[[kl:]]
[[km:]]
[[kn:]]
[[ko:]]
+[[kr:]]
[[ks:]]
+[[ksh:]]
[[ku:]]
+[[kv:]]
+[[kw:]]
[[ky:]]
[[la:]]
+[[lad:]]
+[[lb:]]
+[[lbe:]]
+[[lg:]]
+[[li:]]
+[[lij:]]
+[[lmo:]]
[[ln:]]
[[lo:]]
[[lt:]]
[[lv:]]
-[[hu:]]
+[[map-bms:]]
+[[mdf:]]
+[[mg:]]
+[[mh:]]
+[[mhr:]]
[[mi:]]
[[mk:]]
[[ml:]]
[[mn:]]
+[[mo:]]
[[mr:]]
[[ms:]]
[[mt:]]
+[[mus:]]
+[[mwl:]]
[[my:]]
+[[myv:]]
+[[mzn:]]
[[na:]]
-[[nah:]]
+[[nan:]]
+[[nap:]]
[[nds:]]
+[[nds-nl:]]
[[ne:]]
+[[new:]]
+[[ng:]]
[[nl:]]
+[[nn:]]
[[no:]]
+[[nov:]]
+[[nrm:]]
+[[nv:]]
+[[ny:]]
[[oc:]]
[[om:]]
+[[or:]]
+[[os:]]
[[pa:]]
+[[pag:]]
+[[pam:]]
+[[pap:]]
+[[pdc:]]
+[[pi:]]
+[[pih:]]
[[pl:]]
+[[pms:]]
+[[pnt:]]
+[[pnb:]]
[[ps:]]
[[pt:]]
[[qu:]]
+[[rm:]]
+[[rmy:]]
+[[rn:]]
[[ro:]]
+[[roa-rup:]]
+[[roa-tara:]]
[[ru:]]
+[[rw:]]
[[sa:]]
+[[sah:]]
+[[sc:]]
+[[scn:]]
+[[sco:]]
+[[sd:]]
+[[se:]]
+[[sg:]]
+[[sh:]]
[[si:]]
+[[simple:]]
[[sk:]]
[[sl:]]
+[[sm:]]
+[[sn:]]
+[[so:]]
[[sq:]]
[[sr:]]
+[[srn:]]
+[[ss:]]
+[[st:]]
+[[stq:]]
+[[su:]]
[[sv:]]
[[sw:]]
+[[szl:]]
[[ta:]]
[[te:]]
+[[tet:]]
[[tg:]]
[[th:]]
+[[ti:]]
[[tk:]]
[[tl:]]
+[[tn:]]
+[[to:]]
+[[tpi:]]
[[tr:]]
+[[ts:]]
[[tt:]]
+[[tum:]]
+[[tw:]]
+[[ty:]]
+[[udm:]]
[[ug:]]
[[uk:]]
[[ur:]]
[[uz:]]
+[[ve:]]
+[[vec:]]
[[vi:]]
+[[vls:]]
[[vo:]]
+[[wa:]]
+[[war:]]
+[[wo:]]
+[[wuu:]]
+[[xal:]]
[[xh:]]
+[[yi:]]
[[yo:]]
[[za:]]
+[[zea:]]
[[zh:]]
+[[zh-classical:]]
+[[zh-min-nan:]]
+[[zh-yue:]]
[[zu:]]
-EOT
-, '', false, false );
-
- print "Adding to dblists\n";
-
- # Add to dblist
- $file = fopen( "$common/all.dblist", "a" );
- fwrite( $file, "$dbName\n" );
- fclose( $file );
-
- # Update the sublists
- shell_exec("cd $common && ./refresh-dblist");
-
- #print "Constructing interwiki SQL\n";
- # Rebuild interwiki tables
- #passthru( '/home/wikipedia/conf/interwiki/update' );
-
- print "Script ended. You still have to:
-* Add any required settings in InitialiseSettings.php
-* Run sync-common-all
-* Run /home/wikipedia/conf/interwiki/update
-";
+EOT;
+ }
}
+$maintClass = "AddWiki";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/apache-ampersand.diff b/maintenance/apache-ampersand.diff
deleted file mode 100644
index f281ce15..00000000
--- a/maintenance/apache-ampersand.diff
+++ /dev/null
@@ -1,53 +0,0 @@
---- orig/apache_1.3.26/src/modules/standard/mod_rewrite.h Wed Mar 13 13:05:34 2002
-+++ apache_1.3.26/src/modules/standard/mod_rewrite.h Tue Oct 15 14:07:21 2002
-@@ -447,6 +447,7 @@
- static char *rewrite_mapfunc_toupper(request_rec *r, char *key);
- static char *rewrite_mapfunc_tolower(request_rec *r, char *key);
- static char *rewrite_mapfunc_escape(request_rec *r, char *key);
-+static char *rewrite_mapfunc_ampescape(request_rec *r, char *key);
- static char *rewrite_mapfunc_unescape(request_rec *r, char *key);
- static char *select_random_value_part(request_rec *r, char *value);
- static void rewrite_rand_init(void);
---- orig/apache_1.3.26/src/modules/standard/mod_rewrite.c Wed May 29 10:39:23 2002
-+++ apache_1.3.26/src/modules/standard/mod_rewrite.c Tue Oct 15 14:07:49 2002
-@@ -502,6 +502,9 @@
- else if (strcmp(a2+4, "unescape") == 0) {
- new->func = rewrite_mapfunc_unescape;
- }
-+ else if (strcmp(a2+4, "ampescape") == 0) {
-+ new->func = rewrite_mapfunc_ampescape;
-+ }
- else if (sconf->state == ENGINE_ENABLED) {
- return ap_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
- a2+4, NULL);
-@@ -2982,6 +2985,30 @@
-
- value = ap_escape_uri(r->pool, key);
- return value;
-+}
-+
-+static char *rewrite_mapfunc_ampescape(request_rec *r, char *key)
-+{
-+ /* We only need to escape the ampersand */
-+ char *copy = ap_palloc(r->pool, 3 * strlen(key) + 3);
-+ const unsigned char *s = (const unsigned char *)key;
-+ unsigned char *d = (unsigned char *)copy;
-+ unsigned c;
-+
-+ while ((c = *s)) {
-+ if (c == '&') {
-+ *d++ = '%';
-+ *d++ = '2';
-+ *d++ = '6';
-+ }
-+ else {
-+ *d++ = c;
-+ }
-+ ++s;
-+ }
-+ *d = '\0';
-+
-+ return copy;
- }
-
- static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
diff --git a/maintenance/archives/patch-change_tag-indexes.sql b/maintenance/archives/patch-change_tag-indexes.sql
new file mode 100644
index 00000000..9f8d8f80
--- /dev/null
+++ b/maintenance/archives/patch-change_tag-indexes.sql
@@ -0,0 +1,21 @@
+--
+-- Rename indexes on change_tag from implicit to explicit names
+--
+
+DROP INDEX ct_rc_id ON /*_*/change_tag;
+DROP INDEX ct_log_id ON /*_*/change_tag;
+DROP INDEX ct_rev_id ON /*_*/change_tag;
+DROP INDEX ct_tag ON /*_*/change_tag;
+
+DROP INDEX ts_rc_id ON /*_*/tag_summary;
+DROP INDEX ts_log_id ON /*_*/tag_summary;
+DROP INDEX ts_rev_id ON /*_*/tag_summary;
+
+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);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+
+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);
diff --git a/maintenance/archives/patch-eu_local_id.sql b/maintenance/archives/patch-eu_local_id.sql
new file mode 100644
index 00000000..bb59d067
--- /dev/null
+++ b/maintenance/archives/patch-eu_local_id.sql
@@ -0,0 +1,3 @@
+ALTER TABLE /*_*/external_user
+CHANGE COLUMN eu_wiki_id
+eu_local_id int unsigned NOT NULL;
diff --git a/maintenance/archives/patch-external_user.sql b/maintenance/archives/patch-external_user.sql
new file mode 100644
index 00000000..176b46d4
--- /dev/null
+++ b/maintenance/archives/patch-external_user.sql
@@ -0,0 +1,9 @@
+CREATE TABLE /*_*/external_user (
+ -- Foreign key to user_id
+ eu_local_id int unsigned NOT NULL PRIMARY KEY,
+
+ -- Some opaque identifier provided by the external database
+ eu_external_id varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id);
diff --git a/maintenance/archives/patch-filearhive-user-index.sql b/maintenance/archives/patch-filearchive-user-index.sql
index c79000ad..c79000ad 100644
--- a/maintenance/archives/patch-filearhive-user-index.sql
+++ b/maintenance/archives/patch-filearchive-user-index.sql
diff --git a/maintenance/archives/patch-job.sql b/maintenance/archives/patch-job.sql
index a1f9094f..c9199efb 100644
--- a/maintenance/archives/patch-job.sql
+++ b/maintenance/archives/patch-job.sql
@@ -1,9 +1,9 @@
-
-- Jobs performed by parallel apache threads or a command-line daemon
-CREATE TABLE /*$wgDBprefix*/job (
- job_id int unsigned NOT NULL auto_increment,
+CREATE TABLE /*_*/job (
+ job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
- -- Command name, currently only refreshLinks is defined
+ -- Command name
+ -- Limited to 60 to prevent key length overflow
job_cmd varbinary(60) NOT NULL default '',
-- Namespace and title to act on
@@ -12,9 +12,9 @@ CREATE TABLE /*$wgDBprefix*/job (
job_title varchar(255) binary NOT NULL,
-- Any other parameters to the command
- -- Presently unused, format undefined
- job_params blob NOT NULL,
-
- PRIMARY KEY job_id (job_id),
- KEY (job_cmd, job_namespace, job_title)
+ -- Stored as a PHP serialized array, or an empty string if there are no parameters
+ job_params blob NOT NULL
) /*$wgDBTableOptions*/;
+
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+
diff --git a/maintenance/archives/patch-l10n_cache.sql b/maintenance/archives/patch-l10n_cache.sql
new file mode 100644
index 00000000..4c865d71
--- /dev/null
+++ b/maintenance/archives/patch-l10n_cache.sql
@@ -0,0 +1,8 @@
+-- Table for storing localisation data
+CREATE TABLE /*_*/l10n_cache (
+ lc_lang varbinary(32) NOT NULL,
+ lc_key varchar(255) NOT NULL,
+ lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
+
diff --git a/maintenance/archives/patch-log_search-rename-index.sql b/maintenance/archives/patch-log_search-rename-index.sql
new file mode 100644
index 00000000..41e051d8
--- /dev/null
+++ b/maintenance/archives/patch-log_search-rename-index.sql
@@ -0,0 +1,7 @@
+-- Rename the primary unique index from PRIMARY to ls_field_val
+-- This is for MySQL only and is necessary only for databases which were updated
+-- between MW 1.16 development revisions r50567 and r51465.
+ALTER TABLE /*_*/log_search
+ DROP PRIMARY KEY,
+ ADD UNIQUE INDEX ls_field_val (ls_field,ls_value,ls_log_id);
+
diff --git a/maintenance/archives/patch-log_search.sql b/maintenance/archives/patch-log_search.sql
new file mode 100644
index 00000000..8d92030b
--- /dev/null
+++ b/maintenance/archives/patch-log_search.sql
@@ -0,0 +1,10 @@
+CREATE TABLE /*_*/log_search (
+ -- The type of ID (rev ID, log ID, rev timestamp, username)
+ ls_field varbinary(32) NOT NULL,
+ -- The value of the ID
+ ls_value varchar(255) NOT NULL,
+ -- Key to log_id
+ ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+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);
diff --git a/maintenance/archives/patch-log_user_text.sql b/maintenance/archives/patch-log_user_text.sql
index dffbe8be..9a783d87 100644
--- a/maintenance/archives/patch-log_user_text.sql
+++ b/maintenance/archives/patch-log_user_text.sql
@@ -1,7 +1,8 @@
ALTER TABLE /*$wgDBprefix*/logging
ADD log_user_text varchar(255) binary NOT NULL default '',
- ADD log_target_id int unsigned NULL,
+ ADD log_page int unsigned NULL,
CHANGE log_type log_type varbinary(32) NOT NULL,
CHANGE log_action log_action varbinary(32) NOT NULL;
-CREATE INDEX /*i*/user_type_time ON /*_*/logging (log_user, log_type, 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);
diff --git a/maintenance/archives/patch-mime_minor_length.sql b/maintenance/archives/patch-mime_minor_length.sql
new file mode 100644
index 00000000..3a3c5c4f
--- /dev/null
+++ b/maintenance/archives/patch-mime_minor_length.sql
@@ -0,0 +1,10 @@
+ALTER TABLE /*_*/filearchive
+ MODIFY COLUMN fa_minor_mime varbinary(100) default "unknown";
+
+ALTER TABLE /*_*/image
+ MODIFY COLUMN img_minor_mime varbinary(100) NOT NULL default "unknown";
+
+ALTER TABLE /*_*/oldimage
+ MODIFY COLUMN oi_minor_mime varbinary(100) NOT NULL default "unknown";
+
+INSERT INTO /*_*/updatelog VALUES ('mime_minor_length'); \ No newline at end of file
diff --git a/maintenance/archives/patch-rd_interwiki.sql b/maintenance/archives/patch-rd_interwiki.sql
new file mode 100644
index 00000000..a12f1a7d
--- /dev/null
+++ b/maintenance/archives/patch-rd_interwiki.sql
@@ -0,0 +1,6 @@
+-- Add interwiki and fragment columns to redirect table
+
+ALTER TABLE /*$wgDBprefix*/redirect
+ ADD rd_interwiki varchar(32) default NULL,
+ ADD rd_fragment varchar(255) binary default NULL;
+
diff --git a/maintenance/archives/patch-tc-timestamp.sql b/maintenance/archives/patch-tc-timestamp.sql
new file mode 100644
index 00000000..4d90cf34
--- /dev/null
+++ b/maintenance/archives/patch-tc-timestamp.sql
@@ -0,0 +1,4 @@
+ALTER TABLE /*_*/transcache MODIFY tc_time binary(14);
+UPDATE /*_*/transcache SET tc_time = DATE_FORMAT(FROM_UNIXTIME(tc_time), "%Y%c%d%H%i%s");
+
+INSERT INTO /*_*/updatelog VALUES ('convert transcache field');
diff --git a/maintenance/archives/patch-transcache.sql b/maintenance/archives/patch-transcache.sql
index d0731697..70870efa 100644
--- a/maintenance/archives/patch-transcache.sql
+++ b/maintenance/archives/patch-transcache.sql
@@ -1,7 +1,7 @@
CREATE TABLE /*$wgDBprefix*/transcache (
tc_url varbinary(255) NOT NULL,
tc_contents TEXT,
- tc_time INT NOT NULL,
+ tc_time binary(14) NOT NULL,
UNIQUE INDEX tc_url_idx(tc_url)
) /*$wgDBTableOptions*/;
diff --git a/maintenance/archives/patch-user_properties.sql b/maintenance/archives/patch-user_properties.sql
new file mode 100644
index 00000000..e30e00dc
--- /dev/null
+++ b/maintenance/archives/patch-user_properties.sql
@@ -0,0 +1,22 @@
+--
+-- User preferences and perhaps other fun stuff. :)
+-- Replaces the old user.user_options blob, with a couple nice properties:
+--
+-- 1) We only store non-default settings, so changes to the defauls
+-- are now reflected for everybody, not just new accounts.
+-- 2) We can more easily do bulk lookups, statistics, or modifications of
+-- saved options since it's a sane table structure.
+--
+CREATE TABLE /*_*/user_properties(
+ -- Foreign key to user.user_id
+ up_user int not null,
+
+ -- Name of the option being saved. This is indexed for bulk lookup.
+ up_property varbinary(32) not null,
+
+ -- Property value as a string.
+ up_value blob
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/user_properties_user_property on /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property on /*_*/user_properties (up_property);
diff --git a/maintenance/archives/populateSha1.php b/maintenance/archives/populateSha1.php
deleted file mode 100644
index 487d3bad..00000000
--- a/maintenance/archives/populateSha1.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-/**
- * Optional upgrade script to populate the img_sha1 field
- *
- * @file
- * @ingroup MaintenanceArchive
- */
-
-$optionsWithArgs = array( 'method' );
-require_once( dirname(__FILE__).'/../commandLine.inc' );
-$method = isset( $options['method'] ) ? $options['method'] : 'normal';
-
-$t = -microtime( true );
-$fname = 'populateSha1.php';
-$dbw = wfGetDB( DB_MASTER );
-$res = $dbw->select( 'image', array( 'img_name' ), array( 'img_sha1' => '' ), $fname );
-$imageTable = $dbw->tableName( 'image' );
-$oldimageTable = $dbw->tableName( 'oldimage' );
-$batch = array();
-
-$cmd = 'mysql -u' . wfEscapeShellArg( $wgDBuser ) .
- ' -h' . wfEscapeShellArg( $wgDBserver ) .
- ' -p' . wfEscapeShellArg( $wgDBpassword, $wgDBname );
-if ( $method == 'pipe' ) {
- echo "Using pipe method\n";
- $pipe = popen( $cmd, 'w' );
-}
-
-$numRows = $res->numRows();
-$i = 0;
-foreach ( $res as $row ) {
- if ( $i % 100 == 0 ) {
- printf( "Done %d of %d, %5.3f%% \r", $i, $numRows, $i / $numRows * 100 );
- wfWaitForSlaves( 5 );
- }
- $file = wfLocalFile( $row->img_name );
- if ( !$file ) {
- continue;
- }
- $sha1 = File::sha1Base36( $file->getPath() );
- if ( strval( $sha1 ) !== '' ) {
- $sql = "UPDATE $imageTable SET img_sha1=" . $dbw->addQuotes( $sha1 ) .
- " WHERE img_name=" . $dbw->addQuotes( $row->img_name );
- if ( $method == 'pipe' ) {
- fwrite( $pipe, "$sql;\n" );
- } else {
- $dbw->query( $sql, $fname );
- }
- }
- $i++;
-}
-if ( $method == 'pipe' ) {
- fflush( $pipe );
- pclose( $pipe );
-}
-$t += microtime( true );
-printf( "\nDone %d files in %.1f seconds\n", $numRows, $t );
-
-?>
diff --git a/maintenance/archives/rebuildRecentchanges.inc b/maintenance/archives/rebuildRecentchanges.inc
deleted file mode 100644
index 65ba560c..00000000
--- a/maintenance/archives/rebuildRecentchanges.inc
+++ /dev/null
@@ -1,123 +0,0 @@
-<?php
-/**
- * Rebuild recent changes table
- *
- * @file
- * @deprecated
- * @ingroup MaintenanceArchive
- * @defgroup MaintenanceArchive MaintenanceArchive
- */
-
-/** */
-function rebuildRecentChangesTable()
-{
- $sql = "DROP TABLE IF EXISTS recentchanges";
- wfQuery( $sql );
-
- $sql = "CREATE TABLE recentchanges (
- rc_timestamp varchar(14) binary NOT NULL default '',
- rc_cur_time varchar(14) binary NOT NULL default '',
- rc_user int(10) unsigned NOT NULL default '0',
- rc_user_text varchar(255) binary NOT NULL default '',
- rc_namespace tinyint(3) unsigned NOT NULL default '0',
- rc_title varchar(255) binary NOT NULL default '',
- rc_comment varchar(255) binary NOT NULL default '',
- rc_minor tinyint(3) unsigned NOT NULL default '0',
- rc_new tinyint(3) unsigned NOT NULL default '0',
- rc_cur_id int(10) unsigned NOT NULL default '0',
- rc_this_oldid int(10) unsigned NOT NULL default '0',
- rc_last_oldid int(10) unsigned NOT NULL default '0',
- INDEX rc_cur_id (rc_cur_id),
- INDEX rc_cur_time (rc_cur_time),
- INDEX rc_timestamp (rc_timestamp),
- INDEX rc_namespace (rc_namespace),
- INDEX rc_title (rc_title)
-) ENGINE=MyISAM PACK_KEYS=1;";
- wfQuery( $sql );
-
- print( "Loading from CUR table...\n" );
-
- $sql = "INSERT INTO recentchanges (rc_timestamp,rc_cur_time,rc_user," .
- "rc_user_text,rc_namespace,rc_title,rc_comment,rc_minor,rc_new," .
- "rc_cur_id,rc_this_oldid,rc_last_oldid) SELECT cur_timestamp," .
- "cur_timestamp,cur_user,cur_user_text,cur_namespace,cur_title," .
- "cur_comment,cur_minor_edit,cur_is_new,cur_id,0,0 FROM cur " .
- "ORDER BY cur_timestamp DESC LIMIT 5000";
- wfQuery( $sql );
-
- print( "Loading from OLD table...\n" );
-
- $sql = "INSERT INTO recentchanges (rc_timestamp,rc_cur_time,rc_user," .
- "rc_user_text,rc_namespace,rc_title,rc_comment,rc_minor,rc_new," .
- "rc_cur_id,rc_this_oldid,rc_last_oldid) SELECT old_timestamp,''," .
- "old_user,old_user_text,old_namespace,old_title,old_comment," .
- "old_minor_edit,0,0,old_id,0 FROM old ORDER BY old_timestamp " .
- "DESC LIMIT 5000";
- wfQuery( $sql );
-
- $sql = "SELECT rc_timestamp FROM recentchanges " .
- "ORDER BY rc_timestamp DESC LIMIT 5000,1";
- $res = wfQuery( $sql );
- $obj = wfFetchObject( $res );
- $ts = $obj->rc_timestamp;
-
- $sql = "DELETE FROM recentchanges WHERE rc_timestamp < '{$ts}'";
- wfQuery( $sql );
-
- rebuildRecentChangesTablePass2();
-}
-
-function rebuildRecentChangesTablePass2()
-{
- $ns = $id = $count = 0;
- $title = $ct = "";
-
- print( "Updating links...\n" );
-
- $sql = "SELECT rc_namespace,rc_title,rc_timestamp FROM recentchanges " .
- "ORDER BY rc_namespace,rc_title,rc_timestamp DESC";
- $res = wfQuery( $sql );
-
- while ( $obj = wfFetchObject( $res ) ) {
- if ( ! ( $ns == $obj->rc_namespace &&
- 0 == strcmp( $title, wfStrencode( $obj->rc_title ) ) ) ) {
-
- $ns = $obj->rc_namespace;
- $title = wfStrencode( $obj->rc_title );
-
- $sql = "SELECT cur_id,cur_timestamp FROM cur WHERE " .
- "cur_namespace={$ns} AND cur_title='{$title}'";
- $res2 = wfQuery( $sql );
- $obj2 = wfFetchObject( $res2 );
-
- $id = $obj2->cur_id;
- $ct = $obj2->cur_timestamp;
- }
- $sql = "SELECT old_id FROM old WHERE old_namespace={$ns} " .
- "AND old_title='{$title}' AND old_timestamp < '" .
- "{$obj->rc_timestamp}' ORDER BY old_timestamp DESC LIMIT 1";
- $res2 = wfQuery( $sql );
-
- if ( 0 != wfNumRows( $res2 ) ) {
- $obj2 = wfFetchObject( $res2 );
-
- $sql = "UPDATE recentchanges SET rc_cur_id={$id},rc_cur_time=" .
- "'{$ct}',rc_last_oldid={$obj2->old_id} WHERE " .
- "rc_namespace={$ns} AND rc_title='{$title}' AND " .
- "rc_timestamp='{$obj->rc_timestamp}'";
- wfQuery( $sql );
- } else {
- $sql = "UPDATE recentchanges SET rc_cur_id={$id},rc_cur_time=" .
- "'{$ct}' WHERE rc_namespace={$ns} AND rc_title='{$title}' " .
- "AND rc_timestamp='{$obj->rc_timestamp}'";
- wfQuery( $sql );
- }
-
- if ( 0 == ( ++$count % 500 ) ) {
- printf( "%d records processed.\n", $count );
- }
- }
-}
-
-
-?>
diff --git a/maintenance/archives/upgradeWatchlist.php b/maintenance/archives/upgradeWatchlist.php
deleted file mode 100644
index 9788aa56..00000000
--- a/maintenance/archives/upgradeWatchlist.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-/**
- * @file
- * @deprecated
- * @ingroup MaintenanceArchive
- */
-
-/** */
-print "This script is obsolete!";
-print "It is retained in the source here in case some of its
-code might be useful for ad-hoc conversion tasks, but it is
-not maintained and probably won't even work as is.";
-exit();
-
-# Convert watchlists to new format
-
-global $IP;
-require_once( "../LocalSettings.php" );
-require_once( "$IP/Setup.php" );
-
-$wgTitle = Title::newFromText( "Rebuild links script" );
-set_time_limit(0);
-
-$wgDBuser = "wikiadmin";
-$wgDBpassword = $wgDBadminpassword;
-
-$sql = "DROP TABLE IF EXISTS watchlist";
-wfQuery( $sql, DB_MASTER );
-$sql = "CREATE TABLE watchlist (
- wl_user int(5) unsigned NOT NULL,
- wl_page int(8) unsigned NOT NULL,
- UNIQUE KEY (wl_user, wl_page)
-) ENGINE=MyISAM PACK_KEYS=1";
-wfQuery( $sql, DB_MASTER );
-
-$lc = new LinkCache;
-
-# Now, convert!
-$sql = "SELECT user_id,user_watch FROM user";
-$res = wfQuery( $sql, DB_SLAVE );
-$nu = wfNumRows( $res );
-$sql = "INSERT into watchlist (wl_user,wl_page) VALUES ";
-$i = $n = 0;
-while( $row = wfFetchObject( $res ) ) {
- $list = explode( "\n", $row->user_watch );
- $bits = array();
- foreach( $list as $title ) {
- if( $id = $lc->addLink( $title ) and ! $bits[$id]++) {
- $sql .= ($i++ ? "," : "") . "({$row->user_id},{$id})";
- }
- }
- if( ($n++ % 100) == 0 ) echo "$n of $nu users done...\n";
-}
-echo "$n users done.\n";
-if( $i ) {
- wfQuery( $sql, DB_MASTER );
-}
-
-
-# Add index
-# is this necessary?
-$sql = "ALTER TABLE watchlist
- ADD INDEX wl_user (wl_user),
- ADD INDEX wl_page (wl_page)";
-#wfQuery( $sql, DB_MASTER );
-
-
diff --git a/maintenance/attachLatest.php b/maintenance/attachLatest.php
index 8d680afa..67f3088b 100644
--- a/maintenance/attachLatest.php
+++ b/maintenance/attachLatest.php
@@ -1,8 +1,8 @@
<?php
-// quick hackjob to fix damages imports on wikisource
-// page records have page_latest wrong
-
/**
+ * quick hackjob to fix damages imports on wikisource
+ * page records have page_latest wrong
+ *
* Copyright (C) 2005 Brion Vibber <brion@pobox.com>
* http://www.mediawiki.org/
*
@@ -21,53 +21,61 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @file
* @ingroup Maintenance
*/
-require_once( 'commandLine.inc' );
-
-$fixit = isset( $options['fix'] );
-$fname = 'attachLatest';
-
-echo "Looking for pages with page_latest set to 0...\n";
-$dbw = wfGetDB( DB_MASTER );
-$result = $dbw->select( 'page',
- array( 'page_id', 'page_namespace', 'page_title' ),
- array( 'page_latest' => 0 ),
- $fname );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$n = 0;
-while( $row = $dbw->fetchObject( $result ) ) {
- $pageId = intval( $row->page_id );
- $title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $name = $title->getPrefixedText();
- $latestTime = $dbw->selectField( 'revision',
- 'MAX(rev_timestamp)',
- array( 'rev_page' => $pageId ),
- $fname );
- if( !$latestTime ) {
- echo wfWikiID()." $pageId [[$name]] can't find latest rev time?!\n";
- continue;
+class AttachLatest extends Maintenance {
+
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( "fix", "Actually fix the entries, will dry run otherwise" );
+ $this->mDescription = "Fix page_latest entries in the page table";
}
+
+ public function execute() {
+ $this->output( "Looking for pages with page_latest set to 0...\n" );
+ $dbw = wfGetDB( DB_MASTER );
+ $result = $dbw->select( 'page',
+ array( 'page_id', 'page_namespace', 'page_title' ),
+ array( 'page_latest' => 0 ),
+ __METHOD__ );
- $revision = Revision::loadFromTimestamp( $dbw, $title, $latestTime );
- if( is_null( $revision ) ) {
- echo wfWikiID()." $pageId [[$name]] latest time $latestTime, can't find revision id\n";
- continue;
+ $n = 0;
+ foreach( $result as $row ) {
+ $pageId = intval( $row->page_id );
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $name = $title->getPrefixedText();
+ $latestTime = $dbw->selectField( 'revision',
+ 'MAX(rev_timestamp)',
+ array( 'rev_page' => $pageId ),
+ __METHOD__ );
+ if( !$latestTime ) {
+ $this->output( wfWikiID()." $pageId [[$name]] can't find latest rev time?!\n" );
+ continue;
+ }
+
+ $revision = Revision::loadFromTimestamp( $dbw, $title, $latestTime );
+ if( is_null( $revision ) ) {
+ $this->output( wfWikiID()." $pageId [[$name]] latest time $latestTime, can't find revision id\n" );
+ continue;
+ }
+ $id = $revision->getId();
+ $this->output( wfWikiID()." $pageId [[$name]] latest time $latestTime, rev id $id\n" );
+ if( $this->hasOption('fix') ) {
+ $article = new Article( $title );
+ $article->updateRevisionOn( $dbw, $revision );
+ }
+ $n++;
+ }
+ $dbw->freeResult( $result );
+ $this->output( "Done! Processed $n pages.\n" );
+ if( !$this->hasOption('fix') ) {
+ $this->output( "This was a dry run; rerun with --fix to update page_latest.\n" );
+ }
}
- $id = $revision->getId();
- echo wfWikiID()." $pageId [[$name]] latest time $latestTime, rev id $id\n";
- if( $fixit ) {
- $article = new Article( $title );
- $article->updateRevisionOn( $dbw, $revision );
- }
- $n++;
-}
-$dbw->freeResult( $result );
-echo "Done! Processed $n pages.\n";
-if( !$fixit ) {
- echo "This was a dry run; rerun with --fix to update page_latest.\n";
}
-
+$maintClass = "AttachLatest";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/attribute.php b/maintenance/attribute.php
deleted file mode 100644
index 63f19435..00000000
--- a/maintenance/attribute.php
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-/**
- * Script for re-attributing edits
- * Use reassingEdits.php
- *
- * @file
- * @ingroup Maintenance
- */
-
-/** */
-require_once( "commandLine.inc" );
-
-# Parameters
-if ( count( $args ) < 2 ) {
- print "Not enough parameters\n";
- if ( $wgWikiFarm ) {
- print "Usage: php attribute.php <language> <site> <source> <destination>\n";
- } else {
- print "Usage: php attribute.php <source> <destination>\n";
- }
- exit;
-}
-
-$source = $args[0];
-$dest = $args[1];
-
-$dbr = wfGetDB( DB_SLAVE );
-extract( $dbr->tableNames( 'page', 'revision','user' ));
-$eSource = $dbr->strencode( $source );
-$eDest = $dbr->strencode( $dest );
-
-# Get user id
-$res = $dbr->query( "SELECT user_id FROM $user WHERE user_name='$eDest'" );
-$row = $dbr->fetchObject( $res );
-if ( !$row ) {
- print "Warning: the target name \"$dest\" does not exist";
- $uid = 0;
-} else {
- $uid = $row->user_id;
-}
-
-# Initialise files
-$logfile = fopen( "attribute.log", "a" );
-$sqlfile = fopen( "attribute.sql", "a" );
-
-fwrite( $logfile, "* $source &rarr; $dest\n" );
-
-fwrite( $sqlfile,
-"-- Changing attribution SQL file
--- Generated with attribute.php
--- $source -> $dest ($uid)
-");
-
-$omitTitle = "Wikipedia:Changing_attribution_for_an_edit";
-
-# Get revisions
-print "\nPage revisions\n\n";
-
-$res = $dbr->query( "SELECT page_namespace, page_title, rev_id, rev_timestamp
-FROM $revision,$page
-WHERE rev_user_text='$eSource' and rev_page=page_id" );
-$row = $dbr->fetchObject( $res );
-
-if ( $row ) {
-/*
- if ( $row->old_title=='Votes_for_deletion' && $row->old_namespace == 4 ) {
- # We don't have that long
- break;
- }
-*/
- fwrite( $logfile, "**Revision IDs: " );
- fwrite( $sqlfile, "UPDATE $revision SET rev_user=$uid, rev_user_text='$eDest' WHERE rev_id IN (\n" );
-
- for ( $first=true; $row; $row = $dbr->fetchObject( $res ) ) {
- $title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $fullTitle = $title->getPrefixedDbKey();
- if ( $fullTitle == $omitTitle ) {
- continue;
- }
-
- print "$fullTitle\n";
- $url = $title->getFullUrl( "oldid={$row->rev_id}" );
-
- # Output
- fwrite( $sqlfile, " " );
- if ( $first ) {
- $first = false;
- } else {
- fwrite( $sqlfile, ", " );
- fwrite( $logfile, ", " );
- }
-
- fwrite( $sqlfile, "{$row->rev_id} -- $url\n" );
- fwrite( $logfile, "[$url {$row->rev_id}]" );
-
- }
- fwrite( $sqlfile, ");\n" );
- fwrite( $logfile, "\n" );
-}
-
-print "\n";
-
-fclose( $sqlfile );
-fclose( $logfile );
-
-
diff --git a/maintenance/backup.inc b/maintenance/backup.inc
index e2e5363e..30bd0d88 100644
--- a/maintenance/backup.inc
+++ b/maintenance/backup.inc
@@ -232,13 +232,13 @@ class BackupDumper {
$this->startTime = wfTime();
}
+ /**
+ * @fixme the --server parameter is currently not respected, as it doesn't seem
+ * terribly easy to ask the load balancer for a particular connection by name.
+ */
function backupDb() {
- global $wgDBadminuser, $wgDBadminpassword;
- global $wgDBname, $wgDebugDumpSql, $wgDBtype;
- $flags = ($wgDebugDumpSql ? DBO_DEBUG : 0) | DBO_DEFAULT; // god-damn hack
-
- $class = 'Database' . ucfirst($wgDBtype);
- $db = new $class( $this->backupServer(), $wgDBadminuser, $wgDBadminpassword, $wgDBname, false, $flags );
+ $this->lb = wfGetLBFactory()->newMainLB();
+ $db = $this->lb->getConnection( DB_SLAVE, 'backup' );
// Discourage the server from disconnecting us if it takes a long time
// to read out the big ol' batch query.
@@ -246,6 +246,12 @@ class BackupDumper {
return $db;
}
+
+ function __destruct() {
+ if( isset( $this->lb ) ) {
+ $this->lb->closeAll();
+ }
+ }
function backupServer() {
global $wgDBserver;
diff --git a/maintenance/benchmarkPurge.php b/maintenance/benchmarkPurge.php
index 796e1da2..d167cf92 100644
--- a/maintenance/benchmarkPurge.php
+++ b/maintenance/benchmarkPurge.php
@@ -2,78 +2,105 @@
/**
* Squid purge benchmark script
*
- * @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
+ *
* @ingroup Maintenance
*/
-/** */
-require_once( "commandLine.inc" );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-/**
- * Run a bunch of URLs through SquidUpdate::purge()
- * to benchmark Squid response times.
- * @param $urls array A bunch of URLs to purge
- * @param $trials int How many times to run the test?
- */
-function benchSquid( $urls, $trials = 1 ) {
- $start = wfTime();
- for( $i = 0; $i < $trials; $i++) {
- SquidUpdate::purge( $urls );
+class BenchmarkPurge extends Maintenance {
+
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( "count", "How many URLs to feed to Squid for purging", false, true );
+ $this->mDescription = "Benchmark the Squid purge functions.";
}
- $delta = wfTime() - $start;
- $pertrial = $delta / $trials;
- $pertitle = $pertrial / count( $urls );
- return sprintf( "%4d titles in %6.2fms (%6.2fms each)",
- count( $urls ), $pertrial * 1000.0, $pertitle * 1000.0 );
-}
-
-/**
- * Get an array of randomUrl()'s.
- * @param $length int How many urls to add to the array
- */
-function randomUrlList( $length ) {
- $list = array();
- for( $i = 0; $i < $length; $i++ ) {
- $list[] = randomUrl();
+
+ public function execute() {
+ global $wgUseSquid;
+ if( !$wgUseSquid ) {
+ $this->error( "Squid purge benchmark doesn't do much without squid support on.". true );
+ } else {
+ $this->output( "There are " . count( $wgSquidServers ) . " defined squid servers:\n" );
+ if( $this->hasOption( 'count' ) ) {
+ $lengths = array( intval( $this->getOption('count') ) );
+ } else {
+ $lengths = array( 1, 10, 100 );
+ }
+ foreach( $lengths as $length ) {
+ $urls = $this->randomUrlList( $length );
+ $trial = $this->benchSquid( $urls );
+ $this->output( $trial . "\n" );
+ }
+ }
}
- return $list;
-}
-
-/**
- * Return a random URL of the wiki. Not necessarily an actual title in the
- * database, but at least a URL that looks like one.
- */
-function randomUrl() {
- global $wgServer, $wgArticlePath;
- return $wgServer . str_replace( '$1', randomTitle(), $wgArticlePath );
-}
-
-/**
- * Create a random title string (not necessarily a Title object).
- * For use with randomUrl().
- */
-function randomTitle() {
- $str = '';
- $length = mt_rand( 1, 20 );
- for( $i = 0; $i < $length; $i++ ) {
- $str .= chr( mt_rand( ord('a'), ord('z') ) );
+
+ /**
+ * Run a bunch of URLs through SquidUpdate::purge()
+ * to benchmark Squid response times.
+ * @param $urls array A bunch of URLs to purge
+ * @param $trials int How many times to run the test?
+ */
+ private function benchSquid( $urls, $trials = 1 ) {
+ $start = wfTime();
+ for( $i = 0; $i < $trials; $i++) {
+ SquidUpdate::purge( $urls );
+ }
+ $delta = wfTime() - $start;
+ $pertrial = $delta / $trials;
+ $pertitle = $pertrial / count( $urls );
+ return sprintf( "%4d titles in %6.2fms (%6.2fms each)",
+ count( $urls ), $pertrial * 1000.0, $pertitle * 1000.0 );
}
- return ucfirst( $str );
-}
-
-if( !$wgUseSquid ) {
- wfDie( "Squid purge benchmark doesn't do much without squid support on.\n" );
-} else {
- printf( "There are %d defined squid servers:\n", count( $wgSquidServers ) );
- #echo implode( "\n", $wgSquidServers ) . "\n";
- if( isset( $options['count'] ) ) {
- $lengths = array( intval( $options['count'] ) );
- } else {
- $lengths = array( 1, 10, 100 );
+
+ /**
+ * Get an array of randomUrl()'s.
+ * @param $length int How many urls to add to the array
+ */
+ private function randomUrlList( $length ) {
+ $list = array();
+ for( $i = 0; $i < $length; $i++ ) {
+ $list[] = $this->randomUrl();
+ }
+ return $list;
}
- foreach( $lengths as $length ) {
- $urls = randomUrlList( $length );
- $trial = benchSquid( $urls );
- print "$trial\n";
+
+ /**
+ * Return a random URL of the wiki. Not necessarily an actual title in the
+ * database, but at least a URL that looks like one.
+ */
+ private function randomUrl() {
+ global $wgServer, $wgArticlePath;
+ return $wgServer . str_replace( '$1', $this->randomTitle(), $wgArticlePath );
+ }
+
+ /**
+ * Create a random title string (not necessarily a Title object).
+ * For use with randomUrl().
+ */
+ private function randomTitle() {
+ $str = '';
+ $length = mt_rand( 1, 20 );
+ for( $i = 0; $i < $length; $i++ ) {
+ $str .= chr( mt_rand( ord('a'), ord('z') ) );
+ }
+ return ucfirst( $str );
}
}
+
+$maintClass = "BenchmarkPurge";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/changePassword.php b/maintenance/changePassword.php
index 0fe8c0be..fbc3fa76 100644
--- a/maintenance/changePassword.php
+++ b/maintenance/changePassword.php
@@ -2,55 +2,51 @@
/**
* Change the password of a given user
*
- * @file
- * @ingroup Maintenance
+ * 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 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
* @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
- */
-
-$optionsWithArgs = array( 'user', 'password' );
-require_once 'commandLine.inc';
-
-$USAGE =
- "Usage: php changePassword.php [--user=user --password=password | --help]\n" .
- "\toptions:\n" .
- "\t\t--help show this message\n" .
- "\t\t--user the username to operate on\n" .
- "\t\t--password the password to use\n";
-
-if( in_array( '--help', $argv ) )
- wfDie( $USAGE );
-
-$cp = new ChangePassword( @$options['user'], @$options['password'] );
-$cp->main();
-
-/**
* @ingroup Maintenance
*/
-class ChangePassword {
- var $dbw;
- var $user, $password;
-
- function ChangePassword( $user, $password ) {
- global $USAGE;
- if( !strlen( $user ) or !strlen( $password ) ) {
- wfDie( $USAGE );
- }
-
- $this->user = User::newFromName( $user );
- if ( !$this->user->getId() ) {
- die ( "No such user: $user\n" );
- }
- $this->password = $password;
+require_once( dirname(__FILE__) . '/Maintenance.php' );
- $this->dbw = wfGetDB( DB_MASTER );
+class ChangePassword extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( "user", "The username to operate on", true, true );
+ $this->addOption( "password", "The password to use", true, true );
+ $this->mDescription = "Change a user's password";
}
-
- function main() {
- $this->user->setPassword( $this->password );
- $this->user->saveSettings();
+
+ public function execute() {
+ $user = User::newFromName( $this->getOption('user') );
+ if( !$user->getId() ) {
+ $this->error( "No such user: " . $this->getOption('user'), true );
+ }
+ try {
+ $user->setPassword( $this->getOption('password') );
+ $user->saveSettings();
+ $this->output( "Password set for " . $user->getName() . "\n" );
+ } catch( PasswordError $pwe ) {
+ $this->error( $pwe->getText(), true );
+ }
}
}
+
+$maintClass = "ChangePassword";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/checkAutoLoader.php b/maintenance/checkAutoLoader.php
index 554395ca..9c8f29e3 100644
--- a/maintenance/checkAutoLoader.php
+++ b/maintenance/checkAutoLoader.php
@@ -1,29 +1,57 @@
<?php
-if ( php_sapi_name() != 'cli' ) exit;
+/**
+ * Check the autoloader
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
-$IP = dirname(__FILE__) .'/..';
-require( "$IP/includes/AutoLoader.php" );
-$files = array_unique( $wgAutoloadLocalClasses );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-foreach ( $files as $file ) {
- if( function_exists( 'parsekit_compile_file' ) ){
- $parseInfo = parsekit_compile_file( "$IP/$file" );
- $classes = array_keys( $parseInfo['class_table'] );
- } else {
- $contents = file_get_contents( "$IP/$file" );
- $m = array();
- preg_match_all( '/\n\s*class\s+([a-zA-Z0-9_]+)/', $contents, $m, PREG_PATTERN_ORDER );
- $classes = $m[1];
+class CheckAutoLoader extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "AutoLoader sanity checks";
}
- foreach ( $classes as $class ) {
- if ( !isset( $wgAutoloadLocalClasses[$class] ) ) {
- //printf( "%-50s Unlisted, in %s\n", $class, $file );
- echo " '$class' => '$file',\n";
- } elseif ( $wgAutoloadLocalClasses[$class] !== $file ) {
- echo "$class: Wrong file: found in $file, listed in " . $wgAutoloadLocalClasses[$class] . "\n";
+ public function execute() {
+ global $wgAutoloadLocalClasses, $IP;
+ $files = array_unique( $wgAutoloadLocalClasses );
+
+ foreach( $files as $file ) {
+ if( function_exists( 'parsekit_compile_file' ) ){
+ $parseInfo = parsekit_compile_file( "$IP/$file" );
+ $classes = array_keys( $parseInfo['class_table'] );
+ } else {
+ $contents = file_get_contents( "$IP/$file" );
+ $m = array();
+ preg_match_all( '/\n\s*class\s+([a-zA-Z0-9_]+)/', $contents, $m, PREG_PATTERN_ORDER );
+ $classes = $m[1];
+ }
+ foreach ( $classes as $class ) {
+ if ( !isset( $wgAutoloadLocalClasses[$class] ) ) {
+ //printf( "%-50s Unlisted, in %s\n", $class, $file );
+ $this->output( "\t'$class' => '$file',\n" );
+ } elseif ( $wgAutoloadLocalClasses[$class] !== $file ) {
+ $this->output( "$class: Wrong file: found in $file, listed in " . $wgAutoloadLocalClasses[$class] . "\n" );
+ }
+ }
}
}
-
}
-
+$maintClass = "CheckAutoLoader";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/checkBadRedirects.php b/maintenance/checkBadRedirects.php
index 48a4b0e6..32f04f45 100644
--- a/maintenance/checkBadRedirects.php
+++ b/maintenance/checkBadRedirects.php
@@ -1,30 +1,59 @@
<?php
+/**
+ * CheckBadRedirects - See if pages marked as being redirects
+ * really are.
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-require "commandLine.inc";
-
-echo "Fetching redirects...\n";
-$dbr = wfGetDB( DB_SLAVE );
-$result = $dbr->select(
- array( 'page' ),
- array( 'page_namespace','page_title', 'page_latest' ),
- array( 'page_is_redirect' => 1 ) );
-
-$count = $result->numRows();
-echo "Found $count total redirects.\n";
-echo "Looking for bad redirects:\n";
-echo "\n";
+class CheckBadRedirects extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Look for bad redirects";
+ }
-foreach( $result as $row ) {
- $title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $rev = Revision::newFromId( $row->page_latest );
- if( $rev ) {
- $target = Title::newFromRedirect( $rev->getText() );
- if( !$target ) {
- echo $title->getPrefixedText();
- echo "\n";
+ public function execute() {
+ $this->output( "Fetching redirects...\n" );
+ $dbr = wfGetDB( DB_SLAVE );
+ $result = $dbr->select(
+ array( 'page' ),
+ array( 'page_namespace','page_title', 'page_latest' ),
+ array( 'page_is_redirect' => 1 ) );
+
+ $count = $result->numRows();
+ $this->output( "Found $count total redirects.\n" .
+ "Looking for bad redirects:\n\n" );
+
+ foreach( $result as $row ) {
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $rev = Revision::newFromId( $row->page_latest );
+ if( $rev ) {
+ $target = Title::newFromRedirect( $rev->getText() );
+ if( !$target ) {
+ $this->output( $title->getPrefixedText() . "\n" );
+ }
+ }
}
+ $this->output( "\ndone.\n" );
}
}
-echo "\n";
-echo "done.\n";
+$maintClass = "CheckBadRedirects";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/checkImages.php b/maintenance/checkImages.php
index 378caa34..5dcaac28 100644
--- a/maintenance/checkImages.php
+++ b/maintenance/checkImages.php
@@ -1,51 +1,82 @@
<?php
+/**
+ * Check images to see if they exist, are readable, etc etc
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-require( 'commandLine.inc' );
+class CheckImages extends Maintenance {
-$batchSize = 1000;
-$start = '';
-$dbr = wfGetDB( DB_SLAVE );
-$localRepo = RepoGroup::singleton()->getLocalRepo();
-
-$numImages = 0;
-$numGood = 0;
-
-do {
- $res = $dbr->select( 'image', '*', array( 'img_name > ' . $dbr->addQuotes( $start ) ),
- 'checkImages.php', array( 'LIMIT' => $batchSize ) );
- foreach ( $res as $row ) {
- $numImages++;
- $start = $row->img_name;
- $file = $localRepo->newFileFromRow( $row );
- $path = $file->getPath();
- if ( !$path ) {
- echo "{$row->img_name}: not locally accessible\n";
- continue;
- }
- $stat = @stat( $file->getPath() );
- if ( !$stat ) {
- echo "{$row->img_name}: missing\n";
- continue;
- }
-
- if ( $stat['mode'] & 040000 ) {
- echo "{$row->img_name}: is a directory\n";
- continue;
- }
-
- if ( $stat['size'] == 0 && $row->img_size != 0 ) {
- echo "{$row->img_name}: truncated, was {$row->img_size}\n";
- continue;
- }
-
- if ( $stat['size'] != $row->img_size ) {
- echo "{$row->img_name}: size mismatch DB={$row->img_size}, actual={$stat['size']}\n";
- continue;
- }
-
- $numGood++;
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Check images to see if they exist, are readable, etc";
+ $this->setBatchSize( 1000 );
}
+
+ public function execute() {
+ $start = '';
+ $dbr = wfGetDB( DB_SLAVE );
-} while ( $res->numRows() );
+ $numImages = 0;
+ $numGood = 0;
+
+ do {
+ $res = $dbr->select( 'image', '*', array( 'img_name > ' . $dbr->addQuotes( $start ) ),
+ __METHOD__, array( 'LIMIT' => $this->mBatchSize ) );
+ foreach ( $res as $row ) {
+ $numImages++;
+ $start = $row->img_name;
+ $file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
+ $path = $file->getPath();
+ if ( !$path ) {
+ $this->output( "{$row->img_name}: not locally accessible\n" );
+ continue;
+ }
+ $stat = @stat( $file->getPath() );
+ if ( !$stat ) {
+ $this->output( "{$row->img_name}: missing\n" );
+ continue;
+ }
+
+ if ( $stat['mode'] & 040000 ) {
+ $this->output( "{$row->img_name}: is a directory\n" );
+ continue;
+ }
+
+ if ( $stat['size'] == 0 && $row->img_size != 0 ) {
+ $this->output( "{$row->img_name}: truncated, was {$row->img_size}\n" );
+ continue;
+ }
+
+ if ( $stat['size'] != $row->img_size ) {
+ $this->output( "{$row->img_name}: size mismatch DB={$row->img_size}, actual={$stat['size']}\n" );
+ continue;
+ }
+
+ $numGood++;
+ }
+
+ } while ( $res->numRows() );
+
+ $this->output( "Good images: $numGood/$numImages\n" );
+ }
+}
-echo "Good images: $numGood/$numImages\n";
+$maintClass = "CheckImages";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/checkSyntax.php b/maintenance/checkSyntax.php
new file mode 100644
index 00000000..22832dce
--- /dev/null
+++ b/maintenance/checkSyntax.php
@@ -0,0 +1,296 @@
+<?php
+/**
+ * Check syntax of all PHP files in MediaWiki
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname( __FILE__ ) . '/Maintenance.php' );
+
+class CheckSyntax extends Maintenance {
+
+ // List of files we're going to check
+ private $mFiles = array(), $mFailures = array(), $mWarnings = array();
+ private $mIgnorePaths = array(), $mNoStyleCheckPaths = array();
+
+ public function __construct() {
+ 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 SVN command-line client)' );
+ $this->addOption( 'syntax-only', 'Check for syntax validity only, skip code style warnings' );
+ }
+
+ public function getDbType() {
+ return Maintenance::DB_NONE;
+ }
+
+ public function execute() {
+ $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', '<' );
+
+ $str = 'Checking syntax (using ' . ( $useParseKit ?
+ 'parsekit)' : ' php -l, this can take a long time)' );
+ $this->output( $str );
+ foreach( $this->mFiles as $f ) {
+ if( $useParseKit ) {
+ $this->checkFileWithParsekit( $f );
+ } else {
+ $this->checkFileWithCli( $f );
+ }
+ if( !$this->hasOption( 'syntax-only' ) ) {
+ $this->checkForMistakes( $f );
+ }
+ }
+ $this->output( "\nDone! " . count( $this->mFiles ) . " files checked, " .
+ count( $this->mFailures ) . " failures and " . count( $this->mWarnings ) .
+ " warnings found\n" );
+ }
+
+ /**
+ * Build the list of files we'll check for syntax errors
+ */
+ private function buildFileList() {
+ global $IP;
+
+ $this->mIgnorePaths = array(
+ // Compat stuff, explodes on PHP 5.3
+ "includes/NamespaceCompat.php$",
+ "DiscussionThreading/REV",
+ );
+
+ $this->mNoStyleCheckPaths = array(
+ // Third-party code we don't care about
+ "/activemq_stomp/",
+ "EmailPage/phpMailer",
+ "FCKeditor/fckeditor/",
+ '\bphplot-',
+ "/svggraph/",
+ "\bjsmin.php$",
+ "OggHandler/PEAR/",
+ "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' );
+ $f = @fopen( $file, 'r' );
+ if ( !$f ) {
+ $this->error( "Can't open file $file\n", true );
+ }
+ while( $path = trim( fgets( $f ) ) ) {
+ $this->addPath( $path );
+ }
+ fclose( $f );
+ return;
+ } elseif ( $this->hasOption( 'modified' ) ) {
+ $this->output( "Retrieving list from Subversion... " );
+ $parentDir = wfEscapeShellArg( dirname( __FILE__ ) . '/..' );
+ $output = wfShellExec( "svn status --ignore-externals $parentDir", $retval );
+ if ( $retval ) {
+ $this->error( "Error retrieving list from Subversion!\n", true );
+ } else {
+ $this->output( "done\n" );
+ }
+
+ preg_match_all( '/^\s*[AM].{7}(.*?)\r?$/m', $output, $matches );
+ foreach ( $matches[1] as $file ) {
+ if ( self::isSuitableFile( $file ) && !is_dir( $file ) ) {
+ $this->mFiles[] = $file;
+ }
+ }
+ return;
+ }
+
+ $this->output( 'Building file list...', 'listfiles' );
+
+ // Only check files in these directories.
+ // Don't just put $IP, because the recursive dir thingie goes into all subdirs
+ $dirs = array(
+ $IP . '/includes',
+ $IP . '/config',
+ $IP . '/languages',
+ $IP . '/maintenance',
+ $IP . '/skins',
+ );
+ if( $this->hasOption( 'with-extensions' ) ) {
+ $dirs[] = $IP . '/extensions';
+ }
+
+ foreach( $dirs as $d ) {
+ $this->addDirectoryContent( $d );
+ }
+
+ // Manually add two user-editable files that are usually sources of problems
+ 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 true if $file is of a type we can check
+ */
+ private function isSuitableFile( $file ) {
+ $ext = pathinfo( $file, PATHINFO_EXTENSION );
+ if ( $ext != 'php' && $ext != 'inc' && $ext != 'php5' )
+ return false;
+ foreach( $this->mIgnorePaths as $regex ) {
+ $m = array();
+ if ( preg_match( "~{$regex}~", $file, $m ) )
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Add given path to file list, searching it in include path if needed
+ */
+ 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
+ */
+ private function addFileOrDir( $path ) {
+ if ( is_dir( $path ) ) {
+ $this->addDirectoryContent( $path );
+ } elseif ( file_exists( $path ) ) {
+ $this->mFiles[] = $path;
+ } 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
+ */
+ private function addDirectoryContent( $dir ) {
+ $iterator = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator( $dir ),
+ RecursiveIteratorIterator::SELF_FIRST
+ );
+ foreach ( $iterator as $file ) {
+ if ( $this->isSuitableFile( $file->getRealPath() ) ) {
+ $this->mFiles[] = $file->getRealPath();
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ private function checkFileWithParsekit( $file ) {
+ static $okErrors = array(
+ 'Redefining already defined constructor',
+ 'Assigning the return value of new by reference is deprecated',
+ );
+ $errors = array();
+ parsekit_compile_file( $file, $errors, PARSEKIT_SIMPLE );
+ $ret = true;
+ if ( $errors ) {
+ foreach ( $errors as $error ) {
+ foreach ( $okErrors as $okError ) {
+ if ( substr( $error['errstr'], 0, strlen( $okError ) ) == $okError ) {
+ continue 2;
+ }
+ }
+ $ret = false;
+ $this->output( "Error in $file line {$error['lineno']}: {$error['errstr']}\n" );
+ $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
+ */
+ 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;
+ }
+
+ /**
+ * 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
+ */
+ private function checkForMistakes( $file ) {
+ foreach( $this->mNoStyleCheckPaths as $regex ) {
+ $m = array();
+ if ( preg_match( "~{$regex}~", $file, $m ) )
+ return;
+ }
+
+ $text = file_get_contents( $file );
+
+ $this->checkRegex( $file, $text, '/^[\s\r\n]+<\?/', 'leading whitespace' );
+ $this->checkRegex( $file, $text, '/\?>[\s\r\n]*$/', 'trailing ?>' );
+ $this->checkRegex( $file, $text, '/^[\xFF\xFE\xEF]/', 'byte-order mark' );
+ }
+
+ private function checkRegex( $file, $text, $regex, $desc ) {
+ if ( !preg_match( $regex, $text ) ) {
+ return;
+ }
+
+ if ( !isset( $this->mWarnings[$file] ) ) {
+ $this->mWarnings[$file] = array();
+ }
+ $this->mWarnings[$file][] = $desc;
+ $this->output( "Warning in file $file: $desc found.\n" );
+ }
+}
+
+$maintClass = "CheckSyntax";
+require_once( DO_MAINTENANCE );
+
diff --git a/maintenance/checkUsernames.php b/maintenance/checkUsernames.php
index 77565b99..85a3d157 100644
--- a/maintenance/checkUsernames.php
+++ b/maintenance/checkUsernames.php
@@ -3,40 +3,52 @@
* This script verifies that database usernames are actually valid.
* An existing usernames can become invalid if User::isValidUserName()
* is altered or if we change the $wgMaxNameChars
- * @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
+ *
* @ingroup Maintenance
*/
-error_reporting(E_ALL ^ E_NOTICE);
-require_once 'commandLine.inc';
-class checkUsernames {
- var $stderr, $log;
+require_once( dirname(__FILE__) . '/Maintenance.php' );
- function checkUsernames() {
- $this->stderr = fopen( 'php://stderr', 'wt' );
+class CheckUsernames extends Maintenance {
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Verify that database usernames are actually valid";
}
- function main() {
- $fname = 'checkUsernames::main';
+ function execute() {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'user',
array( 'user_id', 'user_name' ),
null,
- $fname
+ __METHOD__
);
- while ( $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( ! User::isValidUserName( $row->user_name ) ) {
- $out = sprintf( "%s: %6d: '%s'\n", wfWikiID(), $row->user_id, $row->user_name );
- fwrite( $this->stderr, $out );
+ $this->error( sprintf( "%s: %6d: '%s'\n", wfWikiID(), $row->user_id, $row->user_name ) );
wfDebugLog( 'checkUsernames', $out );
}
}
}
}
-$cun = new checkUsernames();
-$cun->main();
-
+$maintClass = "CheckUsernames";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/cleanupCaps.php b/maintenance/cleanupCaps.php
index 46ca18d6..6a48ea83 100644
--- a/maintenance/cleanupCaps.php
+++ b/maintenance/cleanupCaps.php
@@ -24,38 +24,35 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @file
* @author Brion Vibber <brion at pobox.com>
* @ingroup maintenance
*/
-$optionsWithArgs = array( 'namespace' );
+require_once( dirname(__FILE__) . '/cleanupTable.inc' );
-require_once( 'commandLine.inc' );
-require_once( 'cleanupTable.inc' );
-
-/**
- * @ingroup Maintenance
- */
class CapsCleanup extends TableCleanup {
- function __construct( $dryrun = false, $namespace = 0 ) {
- parent::__construct( 'page', $dryrun );
- $this->namespace = intval( $namespace );
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Script to cleanup capitalization";
+ $this->addOption( 'namespace', 'Namespace number to run caps cleanup on', false, true );
}
- function cleanup() {
- global $wgCapitalLinks;
- if( $wgCapitalLinks ) {
- echo "\$wgCapitalLinks is on -- no need for caps links cleanup.\n";
- return false;
- }
+ public function execute() {
+ global $wgCapitalLinks, $wgUser;
+ $this->namespace = intval( $this->getOption( 'namespace', 0 ) );
+ $this->dryrun = $this->hasOption( 'dry-run' );
+ $wgUser->setName( 'Conversion script' );
+ if( $wgCapitalLinks )
+ $this->error( "\$wgCapitalLinks is on -- no need for caps links cleanup.", true );
- $this->runTable( $this->targetTable,
- 'WHERE page_namespace=' . $this->namespace,
- array( &$this, 'processPage' ) );
+ $this->runTable( array(
+ 'table' => 'page',
+ 'conds' => array( 'page_namespace' => $this->namespace ),
+ 'index' => 'page_id',
+ 'callback' => 'processRow' ) );
}
- function processPage( $row ) {
+ protected function processRow( $row ) {
global $wgContLang;
$current = Title::makeTitle( $row->page_namespace, $row->page_title );
@@ -63,42 +60,38 @@ class CapsCleanup extends TableCleanup {
$upper = $row->page_title;
$lower = $wgContLang->lcfirst( $row->page_title );
if( $upper == $lower ) {
- $this->log( "\"$display\" already lowercase." );
+ $this->output( "\"$display\" already lowercase.\n" );
return $this->progress( 0 );
}
$target = Title::makeTitle( $row->page_namespace, $lower );
$targetDisplay = $target->getPrefixedText();
if( $target->exists() ) {
- $this->log( "\"$display\" skipped; \"$targetDisplay\" already exists" );
+ $this->output( "\"$display\" skipped; \"$targetDisplay\" already exists\n" );
return $this->progress( 0 );
}
if( $this->dryrun ) {
- $this->log( "\"$display\" -> \"$targetDisplay\": DRY RUN, NOT MOVED" );
+ $this->output( "\"$display\" -> \"$targetDisplay\": DRY RUN, NOT MOVED\n" );
$ok = true;
} else {
$ok = $current->moveTo( $target, false, 'Converting page titles to lowercase' );
- $this->log( "\"$display\" -> \"$targetDisplay\": $ok" );
+ $this->output( "\"$display\" -> \"$targetDisplay\": $ok\n" );
}
if( $ok === true ) {
$this->progress( 1 );
-
if( $row->page_namespace == $this->namespace ) {
$talk = $target->getTalkPage();
$row->page_namespace = $talk->getNamespace();
if( $talk->exists() ) {
- return $this->processPage( $row );
+ return $this->processRow( $row );
}
}
} else {
$this->progress( 0 );
}
}
-
}
-$wgUser->setName( 'Conversion script' );
-$ns = isset( $options['namespace'] ) ? $options['namespace'] : 0;
-$caps = new CapsCleanup( isset( $options['dry-run'] ), $ns );
-$caps->cleanup();
+$maintClass = "CapsCleanup";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/cleanupImages.php b/maintenance/cleanupImages.php
index 00903f22..db13f4c9 100644
--- a/maintenance/cleanupImages.php
+++ b/maintenance/cleanupImages.php
@@ -24,23 +24,26 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @file
* @author Brion Vibber <brion at pobox.com>
* @ingroup Maintenance
*/
-require_once( 'commandLine.inc' );
-require_once( 'cleanupTable.inc' );
+require_once( dirname(__FILE__) . '/cleanupTable.inc' );
-/**
- * @ingroup Maintenance
- */
class ImageCleanup extends TableCleanup {
- function __construct( $dryrun = false ) {
- parent::__construct( 'image', $dryrun );
+ protected $defaultParams = array(
+ 'table' => 'image',
+ 'conds' => array(),
+ 'index' => 'img_name',
+ 'callback' => 'processRow',
+ );
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Script to clean up broken, unparseable upload filenames";
}
- function processPage( $row ) {
+ protected function processRow( $row ) {
global $wgContLang;
$source = $row->img_name;
@@ -62,12 +65,12 @@ class ImageCleanup extends TableCleanup {
$cleaned = $wgContLang->checkTitleEncoding( $cleaned );
// Many of remainder look like non-normalized unicode
- $cleaned = UtfNormal::cleanUp( $cleaned );
+ $cleaned = $wgContLang->normalize( $cleaned );
$title = Title::makeTitleSafe( NS_FILE, $cleaned );
if( is_null( $title ) ) {
- $this->log( "page $source ($cleaned) is illegal." );
+ $this->output( "page $source ($cleaned) is illegal.\n" );
$safe = $this->buildSafeTitle( $cleaned );
if( $safe === false )
return $this->progress( 0 );
@@ -77,19 +80,19 @@ class ImageCleanup extends TableCleanup {
if( $title->getDBkey() !== $source ) {
$munged = $title->getDBkey();
- $this->log( "page $source ($munged) doesn't match self." );
+ $this->output( "page $source ($munged) doesn't match self.\n" );
$this->pokeFile( $source, $munged );
return $this->progress( 1 );
}
$this->progress( 0 );
}
-
- function killRow( $name ) {
+
+ private function killRow( $name ) {
if( $this->dryrun ) {
- $this->log( "DRY RUN: would delete bogus row '$name'" );
+ $this->output( "DRY RUN: would delete bogus row '$name'\n" );
} else {
- $this->log( "deleting bogus row '$name'" );
+ $this->output( "deleting bogus row '$name'\n" );
$db = wfGetDB( DB_MASTER );
$db->delete( 'image',
array( 'img_name' => $name ),
@@ -97,37 +100,55 @@ class ImageCleanup extends TableCleanup {
}
}
- function filePath( $name ) {
+ private function filePath( $name ) {
if ( !isset( $this->repo ) ) {
$this->repo = RepoGroup::singleton()->getLocalRepo();
}
return $this->repo->getRootDirectory() . '/' . $this->repo->getHashPath( $name ) . $name;
}
+
+ private function imageExists( $name, $db ) {
+ return $db->selectField( 'image', '1', array( 'img_name' => $name ), __METHOD__ );
+ }
+
+ private function pageExists( $name, $db ) {
+ return $db->selectField( 'page', '1', array( 'page_namespace' => NS_FILE, 'page_title' => $name ), __METHOD__ );
+ }
- function pokeFile( $orig, $new ) {
+ private function pokeFile( $orig, $new ) {
$path = $this->filePath( $orig );
if( !file_exists( $path ) ) {
- $this->log( "missing file: $path" );
+ $this->output( "missing file: $path\n" );
return $this->killRow( $orig );
}
$db = wfGetDB( DB_MASTER );
+
+ /*
+ * To prevent key collisions in the update() statements below,
+ * if the target title exists in the image table, or if both the
+ * original and target titles exist in the page table, append
+ * increasing version numbers until the target title exists in
+ * neither. (See also bug 16916.)
+ */
$version = 0;
$final = $new;
+ $conflict = ( $this->imageExists( $final, $db ) ||
+ ( $this->pageExists( $orig, $db ) && $this->pageExists( $final, $db ) ) );
- while( $db->selectField( 'image', 'img_name', array( 'img_name' => $final ), __METHOD__ ) ||
- Title::makeTitle( NS_FILE, $final )->exists() ) {
- $this->log( "Rename conflicts with '$final'..." );
+ while( $conflict ) {
+ $this->output( "Rename conflicts with '$final'...\n" );
$version++;
$final = $this->appendTitle( $new, "_$version" );
+ $conflict = ( $this->imageExists( $final, $db ) || $this->pageExists( $final, $db ) );
}
$finalPath = $this->filePath( $final );
if( $this->dryrun ) {
- $this->log( "DRY RUN: would rename $path to $finalPath" );
+ $this->output( "DRY RUN: would rename $path to $finalPath\n" );
} else {
- $this->log( "renaming $path to $finalPath" );
+ $this->output( "renaming $path to $finalPath\n" );
// XXX: should this use File::move()? FIXME?
$db->begin();
$db->update( 'image',
@@ -153,18 +174,18 @@ class ImageCleanup extends TableCleanup {
if( rename( $path, $finalPath ) ) {
$db->commit();
} else {
- $this->log( "RENAME FAILED" );
+ $this->error( "RENAME FAILED" );
$db->rollback();
}
}
}
-
- function appendTitle( $name, $suffix ) {
+
+ private function appendTitle( $name, $suffix ) {
return preg_replace( '/^(.*)(\..*?)$/',
"\\1$suffix\\2", $name );
}
-
- function buildSafeTitle( $name ) {
+
+ private function buildSafeTitle( $name ) {
global $wgLegalTitleChars;
$x = preg_replace_callback(
"/([^$wgLegalTitleChars]|~)/",
@@ -173,7 +194,7 @@ class ImageCleanup extends TableCleanup {
$test = Title::makeTitleSafe( NS_FILE, $x );
if( is_null( $test ) || $test->getDBkey() !== $x ) {
- $this->log( "Unable to generate safe title from '$name', got '$x'" );
+ $this->error( "Unable to generate safe title from '$name', got '$x'" );
return false;
}
@@ -181,8 +202,5 @@ class ImageCleanup extends TableCleanup {
}
}
-$wgUser->setName( 'Conversion script' );
-$caps = new ImageCleanup( !isset( $options['fix'] ) );
-$caps->cleanup();
-
-
+$maintClass = "ImageCleanup";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/cleanupSpam.php b/maintenance/cleanupSpam.php
index eb9bd914..e78ffe41 100644
--- a/maintenance/cleanupSpam.php
+++ b/maintenance/cleanupSpam.php
@@ -1,114 +1,133 @@
<?php
/**
- * @file
+ * Cleanup all spam from a given hostname
+ *
+ * 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
+ *
* @ingroup Maintenance
*/
-require_once( 'commandLine.inc' );
-require_once( "$IP/includes/LinkFilter.php" );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-function cleanupArticle( $id, $domain ) {
- $title = Title::newFromID( $id );
- if ( !$title ) {
- print "Internal error: no page for ID $id\n";
- return;
+class CleanupSpam extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Cleanup all spam from a given hostname";
+ $this->addOption( 'all', 'Check all wikis in $wgLocalDatabases' );
+ $this->addArg( 'hostname', 'Hostname that was spamming' );
}
- print $title->getPrefixedDBkey() . " ...";
- $rev = Revision::newFromTitle( $title );
- $revId = $rev->getId();
- $currentRevId = $revId;
-
- while ( $rev && LinkFilter::matchEntry( $rev->getText() , $domain ) ) {
- # Revision::getPrevious can't be used in this way before MW 1.6 (Revision.php 1.26)
- #$rev = $rev->getPrevious();
- $revId = $title->getPreviousRevisionID( $revId );
- if ( $revId ) {
- $rev = Revision::newFromTitle( $title, $revId );
- } else {
- $rev = false;
+ public function execute() {
+ global $wgLocalDatabases;
+
+ $username = wfMsg( 'spambot_username' );
+ $wgUser = User::newFromName( $username );
+ // Create the user if necessary
+ if ( !$wgUser->getId() ) {
+ $wgUser->addToDatabase();
}
- }
- if ( $revId == $currentRevId ) {
- // The regex didn't match the current article text
- // This happens e.g. when a link comes from a template rather than the page itself
- print "False match\n";
- } else {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->immediateBegin();
- if ( !$rev ) {
- // Didn't find a non-spammy revision, blank the page
- print "blanking\n";
- $article = new Article( $title );
- $article->updateArticle( '', wfMsg( 'spam_blanking', $domain ),
- false, false );
+ $spec = $this->getArg();
+ $like = LinkFilter::makeLikeArray( $spec );
+ if ( !$like ) {
+ $this->error( "Not a valid hostname specification: $spec", true );
+ }
+
+ if ( $this->hasOption('all') ) {
+ // Clean up spam on all wikis
+ $this->output( "Finding spam on " . count( $wgLocalDatabases ) . " wikis\n" );
+ $found = false;
+ foreach ( $wgLocalDatabases as $wikiID ) {
+ $dbr = wfGetDB( DB_SLAVE, array(), $wikiID );
+ $count = $dbr->selectField( 'externallinks', 'COUNT(*)',
+ array( 'el_index' . $dbr->buildLike( $like ) ), __METHOD__ );
+ if ( $count ) {
+ $found = true;
+ passthru( "php cleanupSpam.php --wiki='$wikiID' $spec | sed 's/^/$wikiID: /'" );
+ }
+ }
+ if ( $found ) {
+ $this->output( "All done\n" );
+ } else {
+ $this->output( "None found\n" );
+ }
} else {
- // Revert to this revision
- print "reverting\n";
- $article = new Article( $title );
- $article->updateArticle( $rev->getText(), wfMsg( 'spam_reverting', $domain ), false, false );
+ // Clean up spam on this wiki
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'externallinks', array( 'DISTINCT el_from' ),
+ array( 'el_index' . $dbr->buildLike( $like ) ), __METHOD__ );
+ $count = $dbr->numRows( $res );
+ $this->output( "Found $count articles containing $spec\n" );
+ foreach ( $res as $row ) {
+ $this->cleanupArticle( $row->el_from, $spec );
+ }
+ if ( $count ) {
+ $this->output( "Done\n" );
+ }
}
- $dbw->immediateCommit();
- wfDoUpdates();
}
-}
-//------------------------------------------------------------------------------
-
-
-
-$username = wfMsg( 'spambot_username' );
-$fname = $username;
-$wgUser = User::newFromName( $username );
-// Create the user if necessary
-if ( !$wgUser->getId() ) {
- $wgUser->addToDatabase();
-}
-
-if ( !isset( $args[0] ) ) {
- print "Usage: php cleanupSpam.php <hostname>\n";
- exit(1);
-}
-$spec = $args[0];
-$like = LinkFilter::makeLike( $spec );
-if ( !$like ) {
- print "Not a valid hostname specification: $spec\n";
- exit(1);
-}
-
-$dbr = wfGetDB( DB_SLAVE );
-
-if ( isset($options['all']) ) {
- // Clean up spam on all wikis
- $dbr = wfGetDB( DB_SLAVE );
- print "Finding spam on " . count($wgLocalDatabases) . " wikis\n";
- $found = false;
- foreach ( $wgLocalDatabases as $db ) {
- $count = $dbr->selectField( "`$db`.externallinks", 'COUNT(*)',
- array( 'el_index LIKE ' . $dbr->addQuotes( $like ) ), $fname );
- if ( $count ) {
- $found = true;
- passthru( "php cleanupSpam.php $db $spec | sed s/^/$db: /" );
+ private function cleanupArticle( $id, $domain ) {
+ $title = Title::newFromID( $id );
+ if ( !$title ) {
+ $this->error( "Internal error: no page for ID $id" );
+ return;
+ }
+
+ $this->output( $title->getPrefixedDBkey() . " ..." );
+ $rev = Revision::newFromTitle( $title );
+ $revId = $rev->getId();
+ $currentRevId = $revId;
+
+ while ( $rev && LinkFilter::matchEntry( $rev->getText() , $domain ) ) {
+ # Revision::getPrevious can't be used in this way before MW 1.6 (Revision.php 1.26)
+ #$rev = $rev->getPrevious();
+ $revId = $title->getPreviousRevisionID( $revId );
+ if ( $revId ) {
+ $rev = Revision::newFromTitle( $title, $revId );
+ } else {
+ $rev = false;
+ }
+ }
+ if ( $revId == $currentRevId ) {
+ // The regex didn't match the current article text
+ // This happens e.g. when a link comes from a template rather than the page itself
+ $this->output( "False match\n" );
+ } else {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+ if ( !$rev ) {
+ // Didn't find a non-spammy revision, blank the page
+ $this->output( "blanking\n" );
+ $article = new Article( $title );
+ $article->updateArticle( '', wfMsg( 'spam_blanking', $domain ),
+ false, false );
+
+ } else {
+ // Revert to this revision
+ $this->output( "reverting\n" );
+ $article = new Article( $title );
+ $article->updateArticle( $rev->getText(), wfMsg( 'spam_reverting', $domain ), false, false );
+ }
+ $dbw->commit();
+ wfDoUpdates();
}
- }
- if ( $found ) {
- print "All done\n";
- } else {
- print "None found\n";
- }
-} else {
- // Clean up spam on this wiki
- $res = $dbr->select( 'externallinks', array( 'DISTINCT el_from' ),
- array( 'el_index LIKE ' . $dbr->addQuotes( $like ) ), $fname );
- $count = $dbr->numRows( $res );
- print "Found $count articles containing $spec\n";
- while ( $row = $dbr->fetchObject( $res ) ) {
- cleanupArticle( $row->el_from, $spec );
- }
- if ( $count ) {
- print "Done\n";
}
}
-
+$maintClass = "CleanupSpam";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/cleanupTable.inc b/maintenance/cleanupTable.inc
index 75699c52..3549a9a1 100644
--- a/maintenance/cleanupTable.inc
+++ b/maintenance/cleanupTable.inc
@@ -1,31 +1,58 @@
<?php
-
-require_once( 'FiveUpgrade.inc' );
-
/**
+ * Generic table cleanup class. Already subclasses maintenance
+ *
+ * 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
+ *
* @ingroup Maintenance
*/
-abstract class TableCleanup extends FiveUpgrade {
- function __construct( $table, $dryrun = false ) {
- parent::__construct();
- $this->targetTable = $table;
- $this->maxLag = 10; # if slaves are lagged more than 10 secs, wait
- $this->dryrun = $dryrun;
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class TableCleanup extends Maintenance {
+ protected $defaultParams = array(
+ 'table' => 'page',
+ 'conds' => array(),
+ 'index' => 'page_id',
+ 'callback' => 'processRow',
+ );
+
+ protected $dryrun = false;
+ protected $maxLag = 10; # if slaves are lagged more than 10 secs, wait
+ public $batchSize = 100;
+ public $reportInterval = 100;
+
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( 'dry-run', 'Perform a dry run' );
}
- function cleanup() {
+ public function execute() {
+ global $wgUser;
+ $wgUser->setName( 'Conversion script' );
+ $this->dryrun = $this->hasOption( 'dry-run' );
if( $this->dryrun ) {
- echo "Checking for bad titles...\n";
+ $this->output( "Checking for bad titles...\n" );
} else {
- echo "Checking and fixing bad titles...\n";
+ $this->output( "Checking and fixing bad titles...\n" );
}
- $this->runTable( $this->targetTable,
- '', //'WHERE page_namespace=0',
- array( $this, 'processPage' ) );
+ $this->runTable( $this->defaultParams );
}
- function init( $count, $table ) {
+ protected function init( $count, $table ) {
$this->processed = 0;
$this->updated = 0;
$this->count = $count;
@@ -33,10 +60,10 @@ abstract class TableCleanup extends FiveUpgrade {
$this->table = $table;
}
- function progress( $updated ) {
+ protected function progress( $updated ) {
$this->updated += $updated;
$this->processed++;
- if( $this->processed % 100 != 0 ) {
+ if( $this->processed % $this->reportInterval != 0 ) {
return;
}
$portion = $this->processed / $this->count;
@@ -47,39 +74,89 @@ abstract class TableCleanup extends FiveUpgrade {
$estimatedTotalTime = $delta / $portion;
$eta = $this->startTime + $estimatedTotalTime;
- printf( "%s %s: %6.2f%% done on %s; ETA %s [%d/%d] %.2f/sec <%.2f%% updated>\n",
- wfWikiID(),
- wfTimestamp( TS_DB, intval( $now ) ),
- $portion * 100.0,
- $this->table,
- wfTimestamp( TS_DB, intval( $eta ) ),
- $this->processed,
- $this->count,
- $this->processed / $delta,
- $updateRate * 100.0 );
+ $this->output(
+ sprintf( "%s %s: %6.2f%% done on %s; ETA %s [%d/%d] %.2f/sec <%.2f%% updated>\n",
+ wfWikiID(),
+ wfTimestamp( TS_DB, intval( $now ) ),
+ $portion * 100.0,
+ $this->table,
+ wfTimestamp( TS_DB, intval( $eta ) ),
+ $this->processed,
+ $this->count,
+ $this->processed / $delta,
+ $updateRate * 100.0
+ )
+ );
flush();
}
- function runTable( $table, $where, $callback ) {
- $count = $this->dbw->selectField( $table, 'count(*)', '', __METHOD__ );
+ public function runTable( $params ) {
+ $dbr = wfGetDB( DB_SLAVE );
+
+ if ( array_diff( array_keys( $params ),
+ array( 'table', 'conds', 'index', 'callback' ) ) )
+ {
+ throw new MWException( __METHOD__.': Missing parameter ' . implode( ', ', $params ) );
+ }
+
+ $table = $params['table'];
+ $count = $dbr->selectField( $table, 'count(*)', $params['conds'], __METHOD__ );
$this->init( $count, $table );
- $this->log( "Processing $table..." );
+ $this->output( "Processing $table...\n" );
- $tableName = $this->dbr->tableName( $table );
- $sql = "SELECT * FROM $tableName $where";
- $result = $this->dbr->query( $sql, __METHOD__ );
- foreach( $result as $row ) {
- call_user_func( $callback, $row );
+ $index = (array)$params['index'];
+ $indexConds = array();
+ $options = array(
+ 'ORDER BY' => implode( ',', $index ),
+ 'LIMIT' => $this->batchSize
+ );
+ $callback = array( $this, $params['callback'] );
+
+ while ( true ) {
+ $conds = array_merge( $params['conds'], $indexConds );
+ $res = $dbr->select( $table, '*', $conds, __METHOD__, $options );
+ if ( !$res->numRows() ) {
+ // Done
+ break;
+ }
+
+ foreach ( $res as $row ) {
+ call_user_func( $callback, $row );
+ }
+
+ if ( $res->numRows() < $this->batchSize ) {
+ // Done
+ break;
+ }
+
+ // Update the conditions to select the next batch.
+ // Construct a condition string by starting with the least significant part
+ // of the index, and adding more significant parts progressively to the left
+ // of the string.
+ $nextCond = '';
+ foreach ( array_reverse( $index ) as $field ) {
+ $encValue = $dbr->addQuotes( $row->$field );
+ if ( $nextCond === '' ) {
+ $nextCond = "$field > $encValue";
+ } else {
+ $nextCond = "$field > $encValue OR ($field = $encValue AND ($nextCond))";
+ }
+ }
+ $indexConds = array( $nextCond );
}
- $this->log( "Finished $table... $this->updated of $this->processed rows updated" );
- $result->free();
+
+ $this->output( "Finished $table... $this->updated of $this->processed rows updated\n" );
}
- function hexChar( $matches ) {
+ protected function hexChar( $matches ) {
return sprintf( "\\x%02x", ord( $matches[1] ) );
}
-
- abstract function processPage( $row );
-
}
+
+class TableCleanupTest extends TableCleanup {
+ function processRow( $row ) {
+ $this->progress( mt_rand( 0, 1 ) );
+ }
+}
+
diff --git a/maintenance/cleanupTitles.php b/maintenance/cleanupTitles.php
index 4d76ac7a..ed714b2d 100644
--- a/maintenance/cleanupTitles.php
+++ b/maintenance/cleanupTitles.php
@@ -24,49 +24,47 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @file
* @author Brion Vibber <brion at pobox.com>
* @ingroup Maintenance
*/
-require_once( 'commandLine.inc' );
-require_once( 'cleanupTable.inc' );
+require_once( dirname(__FILE__) . '/cleanupTable.inc' );
-/**
- * @ingroup Maintenance
- */
class TitleCleanup extends TableCleanup {
- function __construct( $dryrun = false ) {
- parent::__construct( 'page', $dryrun );
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Script to clean up broken, unparseable titles";
}
- function processPage( $row ) {
- $current = Title::makeTitle( $row->page_namespace, $row->page_title );
- $display = $current->getPrefixedText();
-
- $verified = UtfNormal::cleanUp( $display );
-
+ protected function processRow( $row ) {
+ global $wgContLang;
+ $display = Title::makeName( $row->page_namespace, $row->page_title );
+ $verified = $wgContLang->normalize( $display );
$title = Title::newFromText( $verified );
- if( !is_null( $title ) && $title->equals( $current ) && $title->canExist() ) {
+ if( !is_null( $title )
+ && $title->canExist()
+ && $title->getNamespace() == $row->page_namespace
+ && $title->getDBkey() === $row->page_title )
+ {
return $this->progress( 0 ); // all is fine
}
if( $row->page_namespace == NS_FILE && $this->fileExists( $row->page_title ) ) {
- $this->log( "file $row->page_title needs cleanup, please run cleanupImages.php." );
+ $this->output( "file $row->page_title needs cleanup, please run cleanupImages.php.\n" );
return $this->progress( 0 );
} elseif( is_null( $title ) ) {
- $this->log( "page $row->page_id ($display) is illegal." );
+ $this->output( "page $row->page_id ($display) is illegal.\n" );
$this->moveIllegalPage( $row );
return $this->progress( 1 );
} else {
- $this->log( "page $row->page_id ($display) doesn't match self." );
+ $this->output( "page $row->page_id ($display) doesn't match self.\n" );
$this->moveInconsistentPage( $row, $title );
return $this->progress( 1 );
}
}
- function fileExists( $name ) {
+ protected function fileExists( $name ) {
// XXX: Doesn't actually check for file existence, just presence of image record.
// This is reasonable, since cleanupImages.php only iterates over the image table.
$dbr = wfGetDB( DB_SLAVE );
@@ -74,7 +72,7 @@ class TitleCleanup extends TableCleanup {
return $row !== false;
}
- function moveIllegalPage( $row ) {
+ protected function moveIllegalPage( $row ) {
$legal = 'A-Za-z0-9_/\\\\-';
$legalized = preg_replace_callback( "!([^$legal])!",
array( &$this, 'hexChar' ),
@@ -86,28 +84,28 @@ class TitleCleanup extends TableCleanup {
$title = Title::newFromText( $legalized );
if( is_null( $title ) ) {
$clean = 'Broken/id:' . $row->page_id;
- $this->log( "Couldn't legalize; form '$legalized' still invalid; using '$clean'" );
+ $this->output( "Couldn't legalize; form '$legalized' still invalid; using '$clean'\n" );
$title = Title::newFromText( $clean );
} elseif( $title->exists() ) {
$clean = 'Broken/id:' . $row->page_id;
- $this->log( "Legalized for '$legalized' exists; using '$clean'" );
+ $this->output( "Legalized for '$legalized' exists; using '$clean'\n" );
$title = Title::newFromText( $clean );
}
$dest = $title->getDBkey();
if( $this->dryrun ) {
- $this->log( "DRY RUN: would rename $row->page_id ($row->page_namespace,'$row->page_title') to ($row->page_namespace,'$dest')" );
+ $this->output( "DRY RUN: would rename $row->page_id ($row->page_namespace,'$row->page_title') to ($row->page_namespace,'$dest')\n" );
} else {
- $this->log( "renaming $row->page_id ($row->page_namespace,'$row->page_title') to ($row->page_namespace,'$dest')" );
+ $this->output( "renaming $row->page_id ($row->page_namespace,'$row->page_title') to ($row->page_namespace,'$dest')\n" );
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'page',
array( 'page_title' => $dest ),
array( 'page_id' => $row->page_id ),
- 'cleanupTitles::moveInconsistentPage' );
+ __METHOD__ );
}
}
- function moveInconsistentPage( $row, $title ) {
+ protected function moveInconsistentPage( $row, $title ) {
if( $title->exists() || $title->getInterwiki() ) {
if( $title->getInterwiki() ) {
$prior = $title->getPrefixedDbKey();
@@ -118,20 +116,20 @@ class TitleCleanup extends TableCleanup {
$verified = Title::makeTitleSafe( $row->page_namespace, $clean );
if( $verified->exists() ) {
$blah = "Broken/id:" . $row->page_id;
- $this->log( "Couldn't legalize; form '$clean' exists; using '$blah'" );
+ $this->output( "Couldn't legalize; form '$clean' exists; using '$blah'\n" );
$verified = Title::makeTitleSafe( $row->page_namespace, $blah );
}
$title = $verified;
}
if( is_null( $title ) ) {
- wfDie( "Something awry; empty title.\n" );
+ $this->error( "Something awry; empty title.", true );
}
$ns = $title->getNamespace();
$dest = $title->getDBkey();
if( $this->dryrun ) {
- $this->log( "DRY RUN: would rename $row->page_id ($row->page_namespace,'$row->page_title') to ($row->page_namespace,'$dest')" );
+ $this->output( "DRY RUN: would rename $row->page_id ($row->page_namespace,'$row->page_title') to ($row->page_namespace,'$dest')\n" );
} else {
- $this->log( "renaming $row->page_id ($row->page_namespace,'$row->page_title') to ($ns,'$dest')" );
+ $this->output( "renaming $row->page_id ($row->page_namespace,'$row->page_title') to ($ns,'$dest')\n" );
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'page',
array(
@@ -139,15 +137,12 @@ class TitleCleanup extends TableCleanup {
'page_title' => $dest
),
array( 'page_id' => $row->page_id ),
- 'cleanupTitles::moveInconsistentPage' );
+ __METHOD__ );
$linkCache = LinkCache::singleton();
$linkCache->clear();
}
}
}
-$wgUser->setName( 'Conversion script' );
-$caps = new TitleCleanup( !isset( $options['fix'] ) );
-$caps->cleanup();
-
-
+$maintClass = "TitleCleanup";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/cleanupWatchlist.php b/maintenance/cleanupWatchlist.php
index bfc3a1e2..ed84b268 100644
--- a/maintenance/cleanupWatchlist.php
+++ b/maintenance/cleanupWatchlist.php
@@ -24,54 +24,64 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @file
* @author Brion Vibber <brion at pobox.com>
* @ingroup Maintenance
*/
-require_once( 'commandLine.inc' );
-require_once( 'cleanupTable.inc' );
+require_once( dirname(__FILE__) . '/cleanupTable.inc' );
-/**
- * @ingroup Maintenance
- */
class WatchlistCleanup extends TableCleanup {
- function __construct( $dryrun = false ) {
- parent::__construct( 'watchlist', $dryrun );
+ protected $defaultParams = array(
+ 'table' => 'watchlist',
+ 'index' => array( 'wl_user', 'wl_namespace', 'wl_title' ),
+ 'conds' => array(),
+ 'callback' => 'processRow'
+ );
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Script to remove broken, unparseable titles in the Watchlist";
+ $this->addOption( 'fix', 'Actually remove entries; without will only report.' );
+ }
+
+ function execute() {
+ if ( !$this->hasOption( 'fix' ) ) {
+ $this->output( "Dry run only: use --fix to enable updates\n" );
+ }
+ parent::execute();
}
- function processPage( $row ) {
+ protected function processRow( $row ) {
+ global $wgContLang;
$current = Title::makeTitle( $row->wl_namespace, $row->wl_title );
$display = $current->getPrefixedText();
-
- $verified = UtfNormal::cleanUp( $display );
-
+ $verified = $wgContLang->normalize( $display );
$title = Title::newFromText( $verified );
if( $row->wl_user == 0 || is_null( $title ) || !$title->equals( $current ) ) {
- $this->log( "invalid watch by {$row->wl_user} for ({$row->wl_namespace}, \"{$row->wl_title}\")" );
- $this->removeWatch( $row );
- return $this->progress( 1 );
+ $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 );
}
-
- function removeWatch( $row ) {
- if( !$this->dryrun ) {
+
+ private function removeWatch( $row ) {
+ if( !$this->dryrun && $this->hasOption( 'fix' ) ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'watchlist', array(
'wl_user' => $row->wl_user,
'wl_namespace' => $row->wl_namespace,
'wl_title' => $row->wl_title ),
__METHOD__ );
- $this->log( '- removed' );
+ $this->output( "- removed\n" );
+ return 1;
+ } else {
+ return 0;
}
}
}
-$wgUser->setName( 'Conversion script' );
-$caps = new WatchlistCleanup( !isset( $options['fix'] ) );
-$caps->cleanup();
-
-
+$maintClass = "WatchlistCleanup";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/clear_interwiki_cache.php b/maintenance/clear_interwiki_cache.php
index ce154779..a3510a06 100644
--- a/maintenance/clear_interwiki_cache.php
+++ b/maintenance/clear_interwiki_cache.php
@@ -1,27 +1,53 @@
<?php
/**
* This script is used to clear the interwiki links for ALL languages in
- * memcached.
+ * the cache.
+ *
+ * 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('commandLine.inc');
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$dbr = wfGetDB( DB_SLAVE );
-$res = $dbr->select( 'interwiki', array( 'iw_prefix' ), false );
-$prefixes = array();
-while ( $row = $dbr->fetchObject( $res ) ) {
- $prefixes[] = $row->iw_prefix;
-}
+class ClearInterwikiCache extends Maintenance {
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Clear all interwiki links for all languages from the cache";
+ }
+
+ public function execute() {
+ global $wgLocalDatabases, $wgMemc;
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'interwiki', array( 'iw_prefix' ), false );
+ $prefixes = array();
+ foreach ( $res as $row ) {
+ $prefixes[] = $row->iw_prefix;
+ }
-foreach ( $wgLocalDatabases as $db ) {
- print "$db ";
- foreach ( $prefixes as $prefix ) {
- $wgMemc->delete("$db:interwiki:$prefix");
+ foreach ( $wgLocalDatabases as $db ) {
+ $this->output( "$db..." );
+ foreach ( $prefixes as $prefix ) {
+ $wgMemc->delete("$db:interwiki:$prefix");
+ }
+ $this->output( "done\n" );
+ }
}
}
-print "\n";
+$maintClass = "ClearInterwikiCache";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/clear_stats.php b/maintenance/clear_stats.php
index 4cacd74c..6a6a4981 100644
--- a/maintenance/clear_stats.php
+++ b/maintenance/clear_stats.php
@@ -1,38 +1,53 @@
<?php
/**
- * This script remove all statistics tracking from memcached
+ * This script remove all statistics tracking from the cache
*
- * @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
+ *
* @ingroup Maintenance
*/
-require_once('commandLine.inc');
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-foreach ( $wgLocalDatabases as $db ) {
- noisyDelete("$db:stats:request_with_session");
- noisyDelete("$db:stats:request_without_session");
- noisyDelete("$db:stats:pcache_hit");
- noisyDelete("$db:stats:pcache_miss_invalid");
- noisyDelete("$db:stats:pcache_miss_expired");
- noisyDelete("$db:stats:pcache_miss_absent");
- noisyDelete("$db:stats:pcache_miss_stub");
- noisyDelete("$db:stats:image_cache_hit");
- noisyDelete("$db:stats:image_cache_miss");
- noisyDelete("$db:stats:image_cache_update");
- noisyDelete("$db:stats:diff_cache_hit");
- noisyDelete("$db:stats:diff_cache_miss");
- noisyDelete("$db:stats:diff_uncacheable");
-}
+class clear_stats extends Maintenance {
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Remove all statistics tracking from the cache";
+ }
-function noisyDelete( $key ) {
- global $wgMemc;
- /*
- print "$key ";
- if ( $wgMemc->delete($key) ) {
- print "deleted\n";
- } else {
- print "FAILED\n";
- }*/
- $wgMemc->delete($key);
+ public function execute() {
+ global $wgLocalDatabases, $wgMemc;
+ foreach ( $wgLocalDatabases as $db ) {
+ $wgMemc->delete("$db:stats:request_with_session");
+ $wgMemc->delete("$db:stats:request_without_session");
+ $wgMemc->delete("$db:stats:pcache_hit");
+ $wgMemc->delete("$db:stats:pcache_miss_invalid");
+ $wgMemc->delete("$db:stats:pcache_miss_expired");
+ $wgMemc->delete("$db:stats:pcache_miss_absent");
+ $wgMemc->delete("$db:stats:pcache_miss_stub");
+ $wgMemc->delete("$db:stats:image_cache_hit");
+ $wgMemc->delete("$db:stats:image_cache_miss");
+ $wgMemc->delete("$db:stats:image_cache_update");
+ $wgMemc->delete("$db:stats:diff_cache_hit");
+ $wgMemc->delete("$db:stats:diff_cache_miss");
+ $wgMemc->delete("$db:stats:diff_uncacheable");
+ }
+ }
}
+$maintClass = "clear_stats";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/commandLine.inc b/maintenance/commandLine.inc
index e83a1916..332527ba 100644
--- a/maintenance/commandLine.inc
+++ b/maintenance/commandLine.inc
@@ -1,261 +1,46 @@
<?php
+
/**
- * @file
- * @todo document
- * @ingroup Maintenance
- * @defgroup Maintenance Maintenance
+ * Backwards-compatibility wrapper for old-style maintenance scripts
*/
-
-$wgRequestTime = microtime(true);
-
-/** */
-# Abort if called from a web server
-if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) {
- print "This script must be run from the command line\n";
- exit();
-}
-
-if( version_compare( PHP_VERSION, '5.0.0' ) < 0 ) {
- print "Sorry! This version of MediaWiki requires PHP 5; you are running " .
- PHP_VERSION . ".\n\n" .
- "If you are sure you already have PHP 5 installed, it may be " .
- "installed\n" .
- "in a different path from PHP 4. Check with your system administrator.\n";
- die( -1 );
-}
-
-define('MEDIAWIKI',true);
-
-# Process command line arguments
-# $options becomes an array with keys set to the option names
-# $optionsWithArgs is an array of GNU-style options that take an argument. The arguments are returned
-# in the values of $options.
-# $args becomes a zero-based array containing the non-option arguments
+require( dirname(__FILE__) . '/Maintenance.php' );
if ( !isset( $optionsWithArgs ) ) {
- $optionsWithArgs = array();
+ $optionsWithArgs = array();
}
-$optionsWithArgs[] = 'conf'; # For specifying the location of LocalSettings.php
-$optionsWithArgs[] = 'aconf'; # As above for AdminSettings.php
-$optionsWithArgs[] = 'wiki'; # For specifying the wiki ID
-
-$self = array_shift( $argv );
-$IP = strval( getenv('MW_INSTALL_PATH') ) !== ''
- ? getenv('MW_INSTALL_PATH')
- : realpath( dirname( __FILE__ ) . '/..' );
-#chdir( $IP );
-require_once( "$IP/StartProfiler.php" );
-$options = array();
-$args = array();
-
-
-# Parse arguments
-for( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
- if ( $arg == '--' ) {
- # End of options, remainder should be considered arguments
- $arg = next( $argv );
- while( $arg !== false ) {
- $args[] = $arg;
- $arg = next( $argv );
- }
- break;
- } elseif ( substr( $arg, 0, 2 ) == '--' ) {
- # Long options
- $option = substr( $arg, 2 );
- if ( in_array( $option, $optionsWithArgs ) ) {
- $param = next( $argv );
- if ( $param === false ) {
- echo "$arg needs a value after it\n";
- die( -1 );
- }
- $options[$option] = $param;
- } else {
- $bits = explode( '=', $option, 2 );
- if( count( $bits ) > 1 ) {
- $option = $bits[0];
- $param = $bits[1];
- } else {
- $param = 1;
- }
- $options[$option] = $param;
+class CommandLineInc extends Maintenance {
+ public function __construct() {
+ global $optionsWithArgs;
+ parent::__construct();
+ foreach ( $optionsWithArgs as $name ) {
+ $this->addOption( $name, '', false, true );
}
- } elseif ( substr( $arg, 0, 1 ) == '-' ) {
- # Short options
- for ( $p=1; $p<strlen( $arg ); $p++ ) {
- $option = $arg{$p};
- if ( in_array( $option, $optionsWithArgs ) ) {
- $param = next( $argv );
- if ( $param === false ) {
- echo "$arg needs a value after it\n";
- die( -1 );
- }
- $options[$option] = $param;
- } else {
- $options[$option] = 1;
- }
- }
- } else {
- $args[] = $arg;
}
-}
-
-
-# General initialisation
-
-$wgCommandLineMode = true;
-# Turn off output buffering if it's on
-@ob_end_flush();
-$sep = PATH_SEPARATOR;
-
-if (!isset( $wgUseNormalUser ) ) {
- $wgUseNormalUser = false;
-}
-
-if ( file_exists( dirname(__FILE__).'/wikimedia-mode' ) ) {
- $wgWikiFarm = true;
- $cluster = 'pmtpa';
- require_once( "$IP/includes/AutoLoader.php" );
- require_once( "$IP/includes/SiteConfiguration.php" );
-
- # Get $wgConf
- require( "$IP/wgConf.php" );
- if ( empty( $wgNoDBParam ) ) {
- # Check if we were passed a db name
- if ( isset( $options['wiki'] ) ) {
- $db = $options['wiki'];
- } else {
- $db = array_shift( $args );
- }
- list( $site, $lang ) = $wgConf->siteFromDB( $db );
+ public function getDbType() {
+ global $wgUseNormalUser;
- # If not, work out the language and site the old way
- if ( is_null( $site ) || is_null( $lang ) ) {
- if ( !$db ) {
- $lang = 'aa';
- } else {
- $lang = $db;
- }
- if ( isset( $args[0] ) ) {
- $site = array_shift( $args );
- } else {
- $site = 'wikipedia';
- }
- }
- } else {
- $lang = 'aa';
- $site = 'wikipedia';
+ return ( isset( $wgUseNormalUser ) && $wgUseNormalUser ) ?
+ Maintenance::DB_STD : Maintenance::DB_ADMIN;
}
- # This is for the IRC scripts, which now run as the apache user
- # The apache user doesn't have access to the wikiadmin_pass command
- if ( $_ENV['USER'] == 'apache' ) {
- #if ( posix_geteuid() == 48 ) {
- $wgUseNormalUser = true;
+ /**
+ * No help, it would just be misleading since it misses custom options
+ */
+ protected function maybeHelp( $force = false ) {
+ if ( !$force )
+ return;
+ parent::maybeHelp( true );
}
- putenv( 'wikilang='.$lang);
-
- $DP = $IP;
- ini_set( 'include_path', ".:$IP:$IP/includes:$IP/languages:$IP/maintenance" );
-
- if ( $lang == 'test' && $site == 'wikipedia' ) {
- define( 'TESTWIKI', 1 );
- }
-
- #require_once( $IP.'/includes/ProfilerStub.php' );
- require( $IP.'/includes/Defines.php' );
- require( $IP.'/CommonSettings.php' );
- if ( !$wgUseNormalUser ) {
- require( $IP.'/AdminSettings.php' );
- }
-} else {
- $wgWikiFarm = false;
- if ( isset( $options['conf'] ) ) {
- $settingsFile = $options['conf'];
- } else {
- $settingsFile = "$IP/LocalSettings.php";
- }
- if ( isset( $options['wiki'] ) ) {
- $bits = explode( '-', $options['wiki'] );
- if ( count( $bits ) == 1 ) {
- $bits[] = '';
- }
- define( 'MW_DB', $bits[0] );
- define( 'MW_PREFIX', $bits[1] );
- }
-
- if ( ! is_readable( $settingsFile ) ) {
- print "A copy of your installation's LocalSettings.php\n" .
- "must exist and be readable in the source directory.\n";
- exit( 1 );
- }
- $wgCommandLineMode = true;
- $DP = $IP;
- require_once( "$IP/includes/AutoLoader.php" );
- #require_once( $IP.'/includes/ProfilerStub.php' );
- require_once( $IP.'/includes/Defines.php' );
- require_once( $settingsFile );
- /* ini_set( 'include_path', ".$sep$IP$sep$IP/includes$sep$IP/languages$sep$IP/maintenance" ); */
-
- $adminSettings = isset( $options['aconf'] )
- ? $options['aconf']
- : "{$IP}/AdminSettings.php";
- if( is_readable( $adminSettings ) )
- require_once( $adminSettings );
-
-}
-
-# Turn off output buffering again, it might have been turned on in the settings files
-if( ob_get_level() ) {
- ob_end_flush();
-}
-# Same with these
-$wgCommandLineMode = true;
-
-if ( empty( $wgUseNormalUser ) && isset( $wgDBadminuser ) ) {
- $wgDBuser = $wgDBadminuser;
- $wgDBpassword = $wgDBadminpassword;
-
- if( $wgDBservers ) {
- foreach ( $wgDBservers as $i => $server ) {
- $wgDBservers[$i]['user'] = $wgDBuser;
- $wgDBservers[$i]['password'] = $wgDBpassword;
- }
- }
- if( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
- $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
- $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
- }
-}
-
-if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
- $fn = MW_CMDLINE_CALLBACK;
- $fn();
-}
-
-ini_set( 'memory_limit', -1 );
-
-if( version_compare( phpversion(), '5.2.4' ) >= 0 ) {
- // Send PHP warnings and errors to stderr instead of stdout.
- // This aids in diagnosing problems, while keeping messages
- // out of redirected output.
- if( ini_get( 'display_errors' ) ) {
- ini_set( 'display_errors', 'stderr' );
+ public function execute() {
+ global $args, $options;
+ $args = $this->mArgs;
+ $options = $this->mOptions;
}
-
- // Don't touch the setting on earlier versions of PHP,
- // as setting it would disable output if you'd wanted it.
-
- // Note that exceptions are also sent to stderr when
- // command-line mode is on, regardless of PHP version.
}
-$wgShowSQLErrors = true;
-require_once( "$IP/includes/Setup.php" );
-require_once( "$IP/install-utils.inc" );
-$wgTitle = null; # Much much faster startup than creating a title object
-@set_time_limit(0);
+$maintClass = 'CommandLineInc';
+require( DO_MAINTENANCE );
-$wgProfiling = false; // only for Profiler.php mode; avoids OOM errors
diff --git a/maintenance/convertLinks.inc b/maintenance/convertLinks.inc
index 4aff81ed..7c7b8aff 100644
--- a/maintenance/convertLinks.inc
+++ b/maintenance/convertLinks.inc
@@ -45,8 +45,10 @@ function convertLinks() {
$dbw = wfGetDB( DB_MASTER );
list ($cur, $links, $links_temp, $links_backup) = $dbw->tableNamesN( 'cur', 'links', 'links_temp', 'links_backup' );
-
- $res = $dbw->query( "SELECT l_from FROM $links LIMIT 1" );
+
+ // Get database-agnostic limit clause
+ $sql_limit = $dbw->limitResult( "SELECT l_from FROM $links", 1 );
+ $res = $dbw->query( $sql_limit );
if ( $dbw->fieldType( $res, 0 ) == "int" ) {
wfOut( "Schema already converted\n" );
return;
diff --git a/maintenance/convertLinks.php b/maintenance/convertLinks.php
index e86d1e7c..415662a0 100644
--- a/maintenance/convertLinks.php
+++ b/maintenance/convertLinks.php
@@ -3,12 +3,241 @@
* 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
*
- * @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
+ *
* @ingroup Maintenance
*/
-/** */
-require_once( "commandLine.inc" );
-require_once( "convertLinks.inc" );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+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";
+ }
+
+ public function execute() {
+ global $wgDBtype;
+ if( $wgDBtype == 'postgres' ) {
+ $this->output( "Links table already ok on Postgres.\n" );
+ return;
+ }
+
+ $this->output( "Converting links table to ID-ID...\n" );
+
+ global $wgLang, $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname;
+ global $noKeys, $logPerformance, $fh;
+
+ $tuplesAdded = $numBadLinks = $curRowsRead = 0; #counters etc
+ $totalTuplesInserted = 0; # total tuples INSERTed into links_temp
+
+ $reportCurReadProgress = true; #whether or not to give progress reports while reading IDs from cur table
+ $curReadReportInterval = 1000; #number of rows between progress reports
+
+ $reportLinksConvProgress = true; #whether or not to give progress reports during conversion
+ $linksConvInsertInterval = 1000; #number of rows per INSERT
+
+ $initialRowOffset = 0;
+ #$finalRowOffset = 0; # not used yet; highest row number from links table to process
+
+ # Overwrite the old links table with the new one. If this is set to false,
+ # the new table will be left at links_temp.
+ $overwriteLinksTable = true;
+
+ # Don't create keys, and so allow duplicates in the new links table.
+ # This gives a huge speed improvement for very large links tables which are MyISAM. (What about InnoDB?)
+ $noKeys = false;
+
+
+ $logPerformance = false; # output performance data to a file
+ $perfLogFilename = "convLinksPerf.txt";
+ #--------------------------------------------------------------------
+
+ $dbw = wfGetDB( DB_MASTER );
+ list ($cur, $links, $links_temp, $links_backup) = $dbw->tableNamesN( 'cur', 'links', 'links_temp', 'links_backup' );
+
+ $res = $dbw->query( "SELECT l_from FROM $links LIMIT 1" );
+ if ( $dbw->fieldType( $res, 0 ) == "int" ) {
+ $this->output( "Schema already converted\n" );
+ return;
+ }
+
+ $res = $dbw->query( "SELECT COUNT(*) AS count FROM $links" );
+ $row = $dbw->fetchObject($res);
+ $numRows = $row->count;
+ $dbw->freeResult( $res );
+
+ if ( $numRows == 0 ) {
+ $this->output( "Updating schema (no rows to convert)...\n" );
+ $this->createTempTable();
+ } else {
+ if ( $logPerformance ) { $fh = fopen ( $perfLogFilename, "w" ); }
+ $baseTime = $startTime = $this->getMicroTime();
+ # Create a title -> cur_id map
+ $this->output( "Loading IDs from $cur table...\n" );
+ $this->performanceLog ( "Reading $numRows rows from cur table...\n" );
+ $this->performanceLog ( "rows read vs seconds elapsed:\n" );
+
+ $dbw->bufferResults( false );
+ $res = $dbw->query( "SELECT cur_namespace,cur_title,cur_id FROM $cur" );
+ $ids = array();
+
+ while ( $row = $dbw->fetchObject( $res ) ) {
+ $title = $row->cur_title;
+ if ( $row->cur_namespace ) {
+ $title = $wgLang->getNsText( $row->cur_namespace ) . ":$title";
+ }
+ $ids[$title] = $row->cur_id;
+ $curRowsRead++;
+ if ($reportCurReadProgress) {
+ if (($curRowsRead % $curReadReportInterval) == 0) {
+ $this->performanceLog( $curRowsRead . " " . ($this->getMicroTime() - $baseTime) . "\n" );
+ $this->output( "\t$curRowsRead rows of $cur table read.\n" );
+ }
+ }
+ }
+ $dbw->freeResult( $res );
+ $dbw->bufferResults( true );
+ $this->output( "Finished loading IDs.\n\n" );
+ $this->performanceLog( "Took " . ($this->getMicroTime() - $baseTime) . " seconds to load IDs.\n\n" );
+ #--------------------------------------------------------------------
+
+ # Now, step through the links table (in chunks of $linksConvInsertInterval rows),
+ # convert, and write to the new table.
+ $this->createTempTable();
+ $this->performanceLog( "Resetting timer.\n\n" );
+ $baseTime = $this->getMicroTime();
+ $this->output( "Processing $numRows rows from $links table...\n" );
+ $this->performanceLog( "Processing $numRows rows from $links table...\n" );
+ $this->performanceLog( "rows inserted vs seconds elapsed:\n" );
+
+ for ($rowOffset = $initialRowOffset; $rowOffset < $numRows; $rowOffset += $linksConvInsertInterval) {
+ $sqlRead = "SELECT * FROM $links ";
+ $sqlRead = $dbw->limitResult($sqlRead, $linksConvInsertInterval,$rowOffset);
+ $res = $dbw->query($sqlRead);
+ if ( $noKeys ) {
+ $sqlWrite = array("INSERT INTO $links_temp (l_from,l_to) VALUES ");
+ } else {
+ $sqlWrite = array("INSERT IGNORE INTO $links_temp (l_from,l_to) VALUES ");
+ }
+
+ $tuplesAdded = 0; # no tuples added to INSERT yet
+ while ( $row = $dbw->fetchObject($res) ) {
+ $fromTitle = $row->l_from;
+ if ( array_key_exists( $fromTitle, $ids ) ) { # valid title
+ $from = $ids[$fromTitle];
+ $to = $row->l_to;
+ if ( $tuplesAdded != 0 ) {
+ $sqlWrite[] = ",";
+ }
+ $sqlWrite[] = "($from,$to)";
+ $tuplesAdded++;
+ } else { # invalid title
+ $numBadLinks++;
+ }
+ }
+ $dbw->freeResult($res);
+ #$this->output( "rowOffset: $rowOffset\ttuplesAdded: $tuplesAdded\tnumBadLinks: $numBadLinks\n" );
+ if ( $tuplesAdded != 0 ) {
+ if ($reportLinksConvProgress) {
+ $this->output( "Inserting $tuplesAdded tuples into $links_temp..." );
+ }
+ $dbw->query( implode("",$sqlWrite) );
+ $totalTuplesInserted += $tuplesAdded;
+ if ($reportLinksConvProgress)
+ $this->output( " done. Total $totalTuplesInserted tuples inserted.\n" );
+ $this->performanceLog( $totalTuplesInserted . " " . ($this->getMicroTime() - $baseTime) . "\n" );
+ }
+ }
+ $this->output( "$totalTuplesInserted valid titles and $numBadLinks invalid titles were processed.\n\n" );
+ $this->performanceLog( "$totalTuplesInserted valid titles and $numBadLinks invalid titles were processed.\n" );
+ $this->performanceLog( "Total execution time: " . ($this->getMicroTime() - $startTime) . " seconds.\n" );
+ if ( $logPerformance ) { fclose ( $fh ); }
+ }
+ #--------------------------------------------------------------------
+
+ if ( $overwriteLinksTable ) {
+ $dbConn = Database::newFromParams( $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname );
+ if (!($dbConn->isOpen())) {
+ $this->output( "Opening connection to database failed.\n" );
+ return;
+ }
+ # Check for existing links_backup, and delete it if it exists.
+ $this->output( "Dropping backup links table if it exists..." );
+ $dbConn->query( "DROP TABLE IF EXISTS $links_backup", DB_MASTER);
+ $this->output( " done.\n" );
+
+ # Swap in the new table, and move old links table to links_backup
+ $this->output( "Swapping tables '$links' to '$links_backup'; '$links_temp' to '$links'..." );
+ $dbConn->query( "RENAME TABLE links TO $links_backup, $links_temp TO $links", DB_MASTER );
+ $this->output( " done.\n\n" );
+
+ $dbConn->close();
+ $this->output( "Conversion complete. The old table remains at $links_backup;\n" );
+ $this->output( "delete at your leisure.\n" );
+ } else {
+ $this->output( "Conversion complete. The converted table is at $links_temp;\n" );
+ $this->output( "the original links table is unchanged.\n" );
+ }
+ }
+
+ private function createTempTable() {
+ global $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname;
+ global $noKeys;
+ $dbConn = Database::newFromParams( $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname );
+
+ if (!($dbConn->isOpen())) {
+ $this->output( "Opening connection to database failed.\n" );
+ return;
+ }
+ $links_temp = $dbConn->tableName( 'links_temp' );
+
+ $this->output( "Dropping temporary links table if it exists..." );
+ $dbConn->query( "DROP TABLE IF EXISTS $links_temp");
+ $this->output( " done.\n" );
+
+ $this->output( "Creating temporary links table..." );
+ if ( $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')");
+ } 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))");
+ }
+ $this->output( " done.\n\n" );
+ }
+
+ private function performanceLog( $text ) {
+ global $logPerformance, $fh;
+ if ( $logPerformance ) {
+ fwrite( $fh, $text );
+ }
+ }
+
+ private function getMicroTime() { # return time in seconds, with microsecond accuracy
+ list($usec, $sec) = explode(" ", microtime());
+ return ((float)$usec + (float)$sec);
+ }
+}
-convertLinks();
+$maintClass = "ConvertLinks";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/convertUserOptions.php b/maintenance/convertUserOptions.php
new file mode 100644
index 00000000..657a82c1
--- /dev/null
+++ b/maintenance/convertUserOptions.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Do each user sequentially, since accounts can't be deleted
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class ConvertUserOptions extends Maintenance {
+
+ private $mConversionCount = 0;
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Convert user options from old to new system";
+ }
+
+ public function execute() {
+ $this->output( "Beginning batch conversion of user options.\n" );
+ $id = 0;
+ $dbw = wfGetDB( DB_MASTER );
+
+ 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' ) );
+ $id = $this->convertOptionBatch( $res, $dbw );
+ $dbw->commit();
+
+ wfWaitForSlaves( 1 );
+
+ if ($id)
+ $this->output( "--Converted to ID $id\n" );
+ }
+ $this->output( "Conversion done. Converted " . $this->mConversionCount . " user records.\n" );
+ }
+
+ function convertOptionBatch( $res, $dbw ) {
+ $id = null;
+ foreach ( $res as $row ) {
+ $this->mConversionCount++;
+
+ $u = User::newFromRow( $row );
+
+ $u->saveSettings();
+ $id = $row->user_id;
+ }
+
+ return $id;
+ }
+}
+
+$maintClass = "ConvertUserOptions";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/counter.php b/maintenance/counter.php
deleted file mode 100644
index 67575ec1..00000000
--- a/maintenance/counter.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * Helper file for update.php
- *
- * @file
- * @ingroup Maintenance
- */
-
-function print_c($last, $current) {
- echo str_repeat( chr(8), strlen( $last ) ) . $current;
-}
-
diff --git a/maintenance/createAndPromote.php b/maintenance/createAndPromote.php
index a5a8f88d..391d1226 100644
--- a/maintenance/createAndPromote.php
+++ b/maintenance/createAndPromote.php
@@ -3,66 +3,73 @@
/**
* Maintenance script to create an account and grant it administrator rights
*
- * @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
+ *
* @ingroup Maintenance
* @author Rob Church <robchur@gmail.com>
*/
-$options = array( 'help', 'bureaucrat' );
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if( isset( $options['help'] ) ) {
- showHelp();
- exit( 1 );
-}
+class CreateAndPromote extends Maintenance {
-if( count( $args ) < 2 ) {
- echo( "Please provide a username and password for the new account.\n" );
- die( 1 );
-}
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Create a new user account with administrator rights";
+ $this->addOption( "bureaucrat", "Grant the account bureaucrat rights" );
+ $this->addArg( "username", "Username of new user" );
+ $this->addArg( "password", "Password to set" );
+ }
-$username = $args[0];
-$password = $args[1];
+ public function execute() {
+ $username = $this->getArg(0);
+ $password = $this->getArg(1);
+
+ $this->output( wfWikiID() . ": Creating and promoting User:{$username}..." );
+
+ $user = User::newFromName( $username );
+ if( !is_object( $user ) ) {
+ $this->error( "invalid username.", true );
+ } elseif( 0 != $user->idForName() ) {
+ $this->error( "account exists.", true );
+ }
-echo( wfWikiID() . ": Creating and promoting User:{$username}..." );
+ # Try to set the password
+ try {
+ $user->setPassword( $password );
+ } catch( PasswordError $pwe ) {
+ $this->error( $pwe->getText(), true );
+ }
-# Validate username and check it doesn't exist
-$user = User::newFromName( $username );
-if( !is_object( $user ) ) {
- echo( "invalid username.\n" );
- die( 1 );
-} elseif( 0 != $user->idForName() ) {
- echo( "account exists.\n" );
- die( 1 );
+ # Insert the account into the database
+ $user->addToDatabase();
+ $user->saveSettings();
+
+ # Promote user
+ $user->addGroup( 'sysop' );
+ if( $this->hasOption( 'bureaucrat' ) )
+ $user->addGroup( 'bureaucrat' );
+
+ # Increment site_stats.ss_users
+ $ssu = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
+ $ssu->doUpdate();
+
+ $this->output( "done.\n" );
+ }
}
-# Insert the account into the database
-$user->addToDatabase();
-$user->setPassword( $password );
-$user->saveSettings();
-
-# Promote user
-$user->addGroup( 'sysop' );
-if( isset( $option['bureaucrat'] ) )
- $user->addGroup( 'bureaucrat' );
-
-# Increment site_stats.ss_users
-$ssu = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
-$ssu->doUpdate();
-
-echo( "done.\n" );
-
-function showHelp() {
- echo( <<<EOT
-Create a new user account with administrator rights
-
-USAGE: php createAndPromote.php [--bureaucrat|--help] <username> <password>
-
- --bureaucrat
- Grant the account bureaucrat rights
- --help
- Show this help information
-
-EOT
- );
-} \ No newline at end of file
+$maintClass = "CreateAndPromote";
+require_once( DO_MAINTENANCE ); \ No newline at end of file
diff --git a/maintenance/deleteArchivedFiles.inc b/maintenance/deleteArchivedFiles.inc
deleted file mode 100644
index da1c14d5..00000000
--- a/maintenance/deleteArchivedFiles.inc
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-
-/**
- * Support functions for the deleteArchivedFiles script
- *
- * @file
- * @ingroup Maintenance
- * @author Aaron Schulz
- */
-
-require_once( "$IP/includes/FileStore.php" );
-require_once( "$IP/includes/filerepo/File.php" );
-
-function DeleteArchivedFiles( $delete = false ) {
-
- # Data should come off the master, wrapped in a transaction
- $dbw = wfGetDB( DB_MASTER );
-
- $transaction = new FSTransaction();
- if( !FileStore::lock() ) {
- wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
- return false;
- }
-
- $tbl_arch = $dbw->tableName( 'filearchive' );
-
- # Get "active" revisions from the filearchive table
- echo( "Searching for and deleting archived files...\n" );
- $res = $dbw->query( "SELECT fa_id,fa_storage_group,fa_storage_key FROM $tbl_arch" );
- while( $row = $dbw->fetchObject( $res ) ) {
- $key = $row->fa_storage_key;
- $group = $row->fa_storage_group;
- $id = $row->fa_id;
-
- $store = FileStore::get( $group );
- if( $store ) {
- $path = $store->filePath( $key );
- $sha1 = substr( $key, 0, strcspn( $key, '.' ) );
- $inuse = $dbw->selectField( 'oldimage', '1',
- array( 'oi_sha1' => $sha1,
- 'oi_deleted & '.File::DELETED_FILE => File::DELETED_FILE ),
- __METHOD__, array( 'FOR UPDATE' ) );
- if ( $path && file_exists($path) && !$inuse ) {
- $transaction->addCommit( FSTransaction::DELETE_FILE, $path );
- $dbw->query( "DELETE FROM $tbl_arch WHERE fa_id = $id" );
- } else {
- echo( "Notice - file '$key' not found in group '$group'\n" );
- }
- } else {
- echo( "Notice - invalid file storage group '$group' for file '$key'\n" );
- }
- }
- echo( "done.\n" );
-
- $transaction->commit();
-}
diff --git a/maintenance/deleteArchivedFiles.php b/maintenance/deleteArchivedFiles.php
index 97dc5824..af4bbb74 100644
--- a/maintenance/deleteArchivedFiles.php
+++ b/maintenance/deleteArchivedFiles.php
@@ -3,29 +3,80 @@
/**
* Delete archived (non-current) files from the database
*
- * @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
+ *
* @ingroup Maintenance
* @author Aaron Schulz
* Based on deleteOldRevisions.php by Rob Church
*/
-$options = array( 'delete', 'help' );
-require_once( 'commandLine.inc' );
-require_once( 'deleteArchivedFiles.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-echo( "Delete Archived Images\n\n" );
-
-if( @$options['help'] ) {
- ShowUsage();
-} else {
- DeleteArchivedFiles( @$options['delete'] );
-}
+class DeleteArchivedFiles extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Deletes all archived images.";
+ $this->addOption( 'delete', 'Perform the deletion' );
+ $this->addOption( 'force', 'Force deletion of rows from filearchive' );
+ }
-function ShowUsage() {
- echo( "Deletes all archived images.\n\n" );
- echo( "These images will no longer be restorable.\n\n" );
- echo( "Usage: php deleteArchivedRevisions.php [--delete|--help]\n\n" );
- echo( "delete : Performs the deletion\n" );
- echo( " help : Show this usage information\n" );
+ public function execute() {
+ if( !$this->hasOption('delete') ) {
+ $this->output( "Use --delete to actually confirm this script\n" );
+ return;
+ }
+ $force = $this->hasOption( 'force' );
+ # Data should come off the master, wrapped in a transaction
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+ $tbl_arch = $dbw->tableName( 'filearchive' );
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ # Get "active" revisions from the filearchive table
+ $this->output( "Searching for and deleting archived files...\n" );
+ $res = $dbw->query( "SELECT fa_id,fa_storage_group,fa_storage_key FROM $tbl_arch" );
+ $count = 0;
+ foreach( $res as $row ) {
+ $key = $row->fa_storage_key;
+ $group = $row->fa_storage_group;
+ $id = $row->fa_id;
+ $path = $repo->getZonePath( 'deleted' ).'/'.$repo->getDeletedHashPath($key).$key;
+ $sha1 = substr( $key, 0, strcspn( $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 ),
+ __METHOD__,
+ array( 'FOR UPDATE' )
+ );
+ if ( $path && file_exists($path) && !$inuse ) {
+ unlink($path); // delete
+ $count++;
+ $dbw->query( "DELETE FROM $tbl_arch WHERE fa_id = $id" );
+ } else {
+ $this->output( "Notice - file '$key' not found in group '$group'\n" );
+ if ( $force ) {
+ $this->output( "Got --force, deleting DB entry\n" );
+ $dbw->query( "DELETE FROM $tbl_arch WHERE fa_id = $id" );
+ }
+ }
+ }
+ $dbw->commit();
+ $this->output( "Done! [$count file(s)]\n" );
+ }
}
+$maintClass = "DeleteArchivedFiles";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/deleteArchivedRevisions.inc b/maintenance/deleteArchivedRevisions.inc
deleted file mode 100644
index 67e4c5a2..00000000
--- a/maintenance/deleteArchivedRevisions.inc
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-/**
- * Support functions for the deleteArchivedRevisions script
- *
- * @file
- * @ingroup Maintenance
- * @author Aaron Schulz
- */
-
-require_once( 'purgeOldText.inc' );
-
-function DeleteArchivedRevisions( $delete = false ) {
-
- # Data should come off the master, wrapped in a transaction
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
-
- $tbl_arch = $dbw->tableName( 'archive' );
- # Delete as appropriate
- echo( "Deleting archived revisions..." );
- $dbw->query( "TRUNCATE TABLE $tbl_arch" );
- echo( "done.\n" );
-
- $delete = $dbw->affectedRows() != 0;
-
- # This bit's done
- # Purge redundant text records
- $dbw->commit();
- if( $delete ) {
- PurgeRedundantText( true );
- }
-
-}
diff --git a/maintenance/deleteArchivedRevisions.php b/maintenance/deleteArchivedRevisions.php
index 87eebfad..c3f8bf11 100644
--- a/maintenance/deleteArchivedRevisions.php
+++ b/maintenance/deleteArchivedRevisions.php
@@ -3,29 +3,66 @@
/**
* Delete archived (deleted from public) revisions from the database
*
- * @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
+ *
* @ingroup Maintenance
* @author Aaron Schulz
* Shamelessly stolen from deleteOldRevisions.php by Rob Church :)
*/
-$options = array( 'delete', 'help' );
-require_once( 'commandLine.inc' );
-require_once( 'deleteArchivedRevisions.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-echo( "Delete Archived Revisions\n\n" );
-
-if( @$options['help'] ) {
- ShowUsage();
-} else {
- DeleteArchivedRevisions( @$options['delete'] );
-}
+class DeleteArchivedRevisions extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Deletes all archived revisions\nThese revisions will no longer be restorable";
+ $this->addOption( 'delete', 'Performs the deletion' );
+ }
-function ShowUsage() {
- echo( "Deletes all archived revisions.\n\n" );
- echo( "These revisions will no longer be restorable.\n\n" );
- echo( "Usage: php deleteArchivedRevisions.php [--delete|--help]\n\n" );
- echo( "delete : Performs the deletion\n" );
- echo( " help : Show this usage information\n" );
+ public function execute() {
+ $this->output( "Delete archived revisions\n\n" );
+ # Data should come off the master, wrapped in a transaction
+ $dbw = wfGetDB( DB_MASTER );
+ if( $this->hasOption('delete') ) {
+ $dbw->begin();
+
+ $tbl_arch = $dbw->tableName( 'archive' );
+
+ # Delete as appropriate
+ $this->output( "Deleting archived revisions... " );
+ $dbw->query( "TRUNCATE TABLE $tbl_arch" );
+
+ $count = $dbw->affectedRows();
+ $deletedRows = $count != 0;
+
+ $this->output( "done. $count revisions deleted.\n" );
+
+ # This bit's done
+ # Purge redundant text records
+ $dbw->commit();
+ if( $deletedRows ) {
+ $this->purgeRedundantText( true );
+ }
+ } else {
+ $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" );
+ }
+ }
}
+$maintClass = "DeleteArchivedRevisions";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/deleteBatch.php b/maintenance/deleteBatch.php
index 5aeea781..56afd86c 100644
--- a/maintenance/deleteBatch.php
+++ b/maintenance/deleteBatch.php
@@ -1,5 +1,4 @@
<?php
-
/**
* Deletes a batch of pages
* Usage: php deleteBatch.php [-u <user>] [-r <reason>] [-i <interval>] [listfile]
@@ -10,89 +9,104 @@
* <reason> is the delete reason
* <interval> is the number of seconds to sleep for after each delete
*
- * @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
+ *
* @ingroup Maintenance
*/
-
-$oldCwd = getcwd();
-$optionsWithArgs = array( 'u', 'r', 'i' );
-require_once( 'commandLine.inc' );
-
-chdir( $oldCwd );
-
-# Options processing
-
-$filename = 'php://stdin';
-$user = 'Delete page script';
-$reason = '';
-$interval = 0;
-
-if ( isset( $args[0] ) ) {
- $filename = $args[0];
-}
-if ( isset( $options['u'] ) ) {
- $user = $options['u'];
-}
-if ( isset( $options['r'] ) ) {
- $reason = $options['r'];
-}
-if ( isset( $options['i'] ) ) {
- $interval = $options['i'];
-}
-
-$wgUser = User::newFromName( $user );
-
-
-# Setup complete, now start
-
-$file = fopen( $filename, 'r' );
-if ( !$file ) {
- print "Unable to read file, exiting\n";
- exit;
-}
-
-$dbw = wfGetDB( DB_MASTER );
-
-for ( $linenum = 1; !feof( $file ); $linenum++ ) {
- $line = trim( fgets( $file ) );
- if ( $line == '' ) {
- continue;
- }
- $page = Title::newFromText( $line );
- if ( is_null( $page ) ) {
- print "Invalid title '$line' on line $linenum\n";
- continue;
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class DeleteBatch extends Maintenance {
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Deletes a batch of pages";
+ $this->addOption( 'u', "User to perform deletion", false, true );
+ $this->addOption( 'r', "Reason to delete page", false, true );
+ $this->addOption( 'i', "Interval to sleep between deletions" );
+ $this->addArg( 'listfile', 'File with titles to delete, separated by newlines', false );
}
- if( !$page->exists() ) {
- print "Skipping nonexistent page '$line'\n";
- continue;
- }
-
-
- print $page->getPrefixedText();
- $dbw->begin();
- if( $page->getNamespace() == NS_FILE ) {
- $art = new ImagePage( $page );
- $img = wfFindFile( $art->mTitle );
- if( !$img || !$img->delete( $reason ) ) {
- print "FAILED to delete image file... ";
+
+ public function execute() {
+ global $wgUser;
+
+ # Change to current working directory
+ $oldCwd = getcwd();
+ chdir( $oldCwd );
+
+ # Options processing
+ $user = $this->getOption( 'u', 'Delete page script' );
+ $reason = $this->getOption( 'r', '' );
+ $interval = $this->getOption( 'i', 0 );
+ if( $this->hasArg() ) {
+ $file = fopen( $this->getArg(), 'r' );
+ } else {
+ $file = $this->getStdin();
}
- } else {
- $art = new Article( $page );
- }
- $success = $art->doDeleteArticle( $reason );
- $dbw->immediateCommit();
- if ( $success ) {
- print "\n";
- } else {
- print " FAILED to delete image page\n";
- }
- if ( $interval ) {
- sleep( $interval );
+ # Setup
+ if( !$file ) {
+ $this->error( "Unable to read file, exiting", true );
+ }
+ $wgUser = User::newFromName( $user );
+ $dbw = wfGetDB( DB_MASTER );
+
+ # Handle each entry
+ for ( $linenum = 1; !feof( $file ); $linenum++ ) {
+ $line = trim( fgets( $file ) );
+ if ( $line == '' ) {
+ continue;
+ }
+ $page = Title::newFromText( $line );
+ if ( is_null( $page ) ) {
+ $this->output( "Invalid title '$line' on line $linenum\n" );
+ continue;
+ }
+ if( !$page->exists() ) {
+ $this->output( "Skipping nonexistent page '$line'\n" );
+ continue;
+ }
+
+
+ $this->output( $page->getPrefixedText() );
+ $dbw->begin();
+ if( $page->getNamespace() == NS_FILE ) {
+ $art = new ImagePage( $page );
+ $img = wfFindFile( $art->mTitle );
+ if( !$img || !$img->delete( $reason ) ) {
+ $this->output( "FAILED to delete image file... " );
+ }
+ } else {
+ $art = new Article( $page );
+ }
+ $success = $art->doDeleteArticle( $reason );
+ $dbw->commit();
+ if ( $success ) {
+ $this->output( "\n" );
+ } else {
+ $this->output( " FAILED to delete article\n" );
+ }
+
+ if ( $interval ) {
+ sleep( $interval );
+ }
+ wfWaitForSlaves( 5 );
+}
}
- wfWaitForSlaves( 5 );
}
-
-
+$maintClass = "DeleteBatch";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/deleteDefaultMessages.php b/maintenance/deleteDefaultMessages.php
index 77e85741..3f0e1b1c 100644
--- a/maintenance/deleteDefaultMessages.php
+++ b/maintenance/deleteDefaultMessages.php
@@ -1,48 +1,72 @@
<?php
-
/**
* Deletes all pages in the MediaWiki namespace which were last edited by
* "MediaWiki default".
*
- * @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
+ *
* @ingroup Maintenance
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- require_once( 'commandLine.inc' );
- deleteDefaultMessages();
-}
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-function deleteDefaultMessages() {
- $user = 'MediaWiki default';
- $reason = 'No longer required';
+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\"";
+ }
- global $wgUser;
- $wgUser = User::newFromName( $user );
- $wgUser->addGroup( 'bot' );
+ public function execute() {
+ self::reallyExecute();
+ }
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( array( 'page', 'revision' ),
- array( 'page_namespace', 'page_title' ),
- array(
- 'page_namespace' => NS_MEDIAWIKI,
- 'page_latest=rev_id',
- 'rev_user_text' => 'MediaWiki default',
- )
- );
-
- $dbw = wfGetDB( DB_MASTER );
-
- while ( $row = $dbr->fetchObject( $res ) ) {
- if ( function_exists( 'wfWaitForSlaves' ) ) {
- wfWaitForSlaves( 5 );
+ public static function reallyExecute() {
+ $user = 'MediaWiki default';
+ $reason = 'No longer required';
+
+ global $wgUser;
+ $wgUser = User::newFromName( $user );
+ $wgUser->addGroup( 'bot' );
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( array( 'page', 'revision' ),
+ array( 'page_namespace', 'page_title' ),
+ array(
+ 'page_namespace' => NS_MEDIAWIKI,
+ 'page_latest=rev_id',
+ 'rev_user_text' => 'MediaWiki default',
+ )
+ );
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ foreach ( $res as $row ) {
+ if ( function_exists( 'wfWaitForSlaves' ) ) {
+ wfWaitForSlaves( 5 );
+ }
+ $dbw->ping();
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $article = new Article( $title );
+ $dbw->begin();
+ $article->doDeleteArticle( $reason );
+ $dbw->commit();
}
- $dbw->ping();
- $title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $article = new Article( $title );
- $dbw->begin();
- $article->doDeleteArticle( $reason );
- $dbw->commit();
}
}
+$maintClass = "DeleteDefaultMessages";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/deleteImageMemcached.php b/maintenance/deleteImageMemcached.php
index 2c3afa86..9becddb8 100644
--- a/maintenance/deleteImageMemcached.php
+++ b/maintenance/deleteImageMemcached.php
@@ -1,33 +1,43 @@
<?php
/**
- * This script delete image information from memcached.
+ * This script delete image information from the cache.
*
* Usage example:
- * php deleteImageMemcached.php --until "2005-09-05 00:00:00" --sleep 0 --report 10
+ * php deleteImageMemcached.php --until "2005-09-05 00:00:00" --sleep 0
+ *
+ * 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
*/
-$optionsWithArgs = array( 'until', 'sleep', 'report' );
-
-require_once 'commandLine.inc';
-
-/**
- * @ingroup Maintenance
- */
-class DeleteImageCache {
- var $until, $sleep, $report;
+require_once( dirname(__FILE__) . '/Maintenance.php' );
- function DeleteImageCache( $until, $sleep, $report ) {
- $this->until = $until;
- $this->sleep = $sleep;
- $this->report = $report;
+class DeleteImageCache extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Delete image information from the cache";
+ $this->addOption( 'sleep', 'How many seconds to sleep between deletions', true, true );
+ $this->addOption( 'until', 'Timestamp to delete all entries prior to', true, true );
}
- function main() {
+ public function execute() {
global $wgMemc;
- $fname = 'DeleteImageCache::main';
+
+ $until = preg_replace( "/[^\d]/", '', $this->getOption('until') );
+ $sleep = (int)$this->getOption('sleep') * 1000; // milliseconds
ini_set( 'display_errors', false );
@@ -35,38 +45,31 @@ class DeleteImageCache {
$res = $dbr->select( 'image',
array( 'img_name' ),
- array( "img_timestamp < {$this->until}" ),
- $fname
+ array( "img_timestamp < {$until}" ),
+ __METHOD__
);
$i = 0;
$total = $this->getImageCount();
- while ( $row = $dbr->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ($i % $this->report == 0)
- printf("%s: %13s done (%s)\n", wfWikiID(), "$i/$total", wfPercent( $i / $total * 100 ));
+ $this->output( sprintf("%s: %13s done (%s)\n", wfWikiID(), "$i/$total", wfPercent( $i / $total * 100 ) ) );
$md5 = md5( $row->img_name );
$wgMemc->delete( wfMemcKey( 'Image', $md5 ) );
- if ($this->sleep != 0)
- usleep( $this->sleep );
+ if ($sleep != 0)
+ usleep( $sleep );
++$i;
}
}
- function getImageCount() {
- $fname = 'DeleteImageCache::getImageCount';
-
+ private function getImageCount() {
$dbr = wfGetDB( DB_SLAVE );
- return $dbr->selectField( 'image', 'COUNT(*)', array(), $fname );
+ return $dbr->selectField( 'image', 'COUNT(*)', array(), __METHOD__ );
}
}
-$until = preg_replace( "/[^\d]/", '', $options['until'] );
-$sleep = (int)$options['sleep'] * 1000; // milliseconds
-$report = (int)$options['report'];
-
-$dic = new DeleteImageCache( $until, $sleep, $report );
-$dic->main();
-
+$maintClass = "DeleteImageCache";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/deleteOldRevisions.inc b/maintenance/deleteOldRevisions.inc
deleted file mode 100644
index b681b9d0..00000000
--- a/maintenance/deleteOldRevisions.inc
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-/**
- * Support functions for the deleteOldRevisions script
- *
- * @file
- * @ingroup Maintenance
- * @author Rob Church <robchur@gmail.com>
- */
-
-require_once( 'purgeOldText.inc' );
-
-function DeleteOldRevisions( $delete = false, $args = array() ) {
-
- # Data should come off the master, wrapped in a transaction
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
-
- $tbl_pag = $dbw->tableName( 'page' );
- $tbl_rev = $dbw->tableName( 'revision' );
-
- $pageIdClause = '';
- $revPageClause = '';
-
- # If a list of page_ids was provided, limit results to that set of page_ids
- if ( sizeof( $args ) > 0 ) {
- $pageIdList = implode( ',', $args );
- $pageIdClause = " WHERE page_id IN ({$pageIdList})";
- $revPageClause = " AND rev_page IN ({$pageIdList})";
- echo( "Limiting to {$tbl_pag}.page_id IN ({$pageIdList})\n" );
- }
-
- # Get "active" revisions from the page table
- echo( "Searching for active revisions..." );
- $res = $dbw->query( "SELECT page_latest FROM $tbl_pag{$pageIdClause}" );
- while( $row = $dbw->fetchObject( $res ) ) {
- $cur[] = $row->page_latest;
- }
- echo( "done.\n" );
-
- # Get all revisions that aren't in this set
- echo( "Searching for inactive revisions..." );
- $set = implode( ', ', $cur );
- $res = $dbw->query( "SELECT rev_id FROM $tbl_rev WHERE rev_id NOT IN ( $set ){$revPageClause}" );
- while( $row = $dbw->fetchObject( $res ) ) {
- $old[] = $row->rev_id;
- }
- echo( "done.\n" );
-
- # Inform the user of what we're going to do
- $count = count( $old );
- echo( "$count old revisions found.\n" );
-
- # Delete as appropriate
- if( $delete && $count ) {
- echo( "Deleting..." );
- $set = implode( ', ', $old );
- $dbw->query( "DELETE FROM $tbl_rev WHERE rev_id IN ( $set )" );
- echo( "done.\n" );
- }
-
- # This bit's done
- # Purge redundant text records
- $dbw->commit();
- if( $delete ) {
- PurgeRedundantText( true );
- }
-
-}
diff --git a/maintenance/deleteOldRevisions.php b/maintenance/deleteOldRevisions.php
index c283c607..1f4dc4c9 100644
--- a/maintenance/deleteOldRevisions.php
+++ b/maintenance/deleteOldRevisions.php
@@ -3,27 +3,99 @@
/**
* Delete old (non-current) revisions from the database
*
- * @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
+ *
* @ingroup Maintenance
* @author Rob Church <robchur@gmail.com>
*/
-$options = array( 'delete', 'help' );
-require_once( 'commandLine.inc' );
-require_once( 'deleteOldRevisions.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-echo( "Delete Old Revisions\n\n" );
+class DeleteOldRevisions extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Delete old (non-current) revisions from the database";
+ $this->addOption( 'delete', 'Actually perform the deletion' );
+ $this->addOption( 'page_id', 'List of page ids to work on', false );
+ }
+
+ public function execute() {
+ $this->output( "Delete old revisions\n\n" );
+ $this->doDelete( $this->hasOption( 'delete' ), $this->mArgs );
+ }
+
+ function doDelete( $delete = false, $args = array() ) {
-if( @$options['help'] ) {
- ShowUsage();
-} else {
- DeleteOldRevisions( @$options['delete'], $args );
+ # Data should come off the master, wrapped in a transaction
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+
+ $tbl_pag = $dbw->tableName( 'page' );
+ $tbl_rev = $dbw->tableName( 'revision' );
+
+ $pageIdClause = '';
+ $revPageClause = '';
+
+ # If a list of page_ids was provided, limit results to that set of page_ids
+ if ( sizeof( $args ) > 0 ) {
+ $pageIdList = implode( ',', $args );
+ $pageIdClause = " WHERE page_id IN ({$pageIdList})";
+ $revPageClause = " AND rev_page IN ({$pageIdList})";
+ $this->output( "Limiting to {$tbl_pag}.page_id IN ({$pageIdList})\n" );
+ }
+
+ # Get "active" revisions from the page table
+ $this->output( "Searching for active revisions..." );
+ $res = $dbw->query( "SELECT page_latest FROM $tbl_pag{$pageIdClause}" );
+ foreach( $res as $row ) {
+ $cur[] = $row->page_latest;
+ }
+ $this->output( "done.\n" );
+
+ # Get all revisions that aren't in this set
+ $old = array();
+ $this->output( "Searching for inactive revisions..." );
+ $set = implode( ', ', $cur );
+ $res = $dbw->query( "SELECT rev_id FROM $tbl_rev WHERE rev_id NOT IN ( $set ){$revPageClause}" );
+ foreach( $res as $row ) {
+ $old[] = $row->rev_id;
+ }
+ $this->output( "done.\n" );
+
+ # Inform the user of what we're going to do
+ $count = count( $old );
+ $this->output( "$count old revisions found.\n" );
+
+ # Delete as appropriate
+ if( $delete && $count ) {
+ $this->output( "Deleting..." );
+ $set = implode( ', ', $old );
+ $dbw->query( "DELETE FROM $tbl_rev WHERE rev_id IN ( $set )" );
+ $this->output( "done.\n" );
+ }
+
+ # This bit's done
+ # Purge redundant text records
+ $dbw->commit();
+ if( $delete ) {
+ $this->purgeRedundantText( true );
+ }
+ }
}
-function ShowUsage() {
- echo( "Deletes non-current revisions from the database.\n\n" );
- echo( "Usage: php deleteOldRevisions.php [--delete|--help] [<page_id> ...]\n\n" );
- echo( "delete : Performs the deletion\n" );
- echo( " help : Show this usage information\n" );
-}
+$maintClass = "DeleteOldRevisions";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/deleteOrphanedRevisions.inc.php b/maintenance/deleteOrphanedRevisions.inc.php
deleted file mode 100644
index 6678d5b8..00000000
--- a/maintenance/deleteOrphanedRevisions.inc.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-/**
- * Support functions for the deleteOrphanedRevisions maintenance script
- *
- * @file
- * @ingroup Maintenance
- * @author Rob Church <robchur@gmail.com>
- */
-
-/**
- * Delete one or more revisions from the database
- * Do this inside a transaction
- *
- * @param $id Array of revision id values
- * @param $db Database class (needs to be a master)
- */
-function deleteRevisions( $id, &$dbw ) {
- if( !is_array( $id ) )
- $id = array( $id );
- $dbw->delete( 'revision', array( 'rev_id' => $id ), 'deleteRevision' );
-}
-
-/**
- * Spit out script usage information and exit
- */
-function showUsage() {
- echo( "Finds revisions which refer to nonexisting pages and deletes them from the database\n" );
- echo( "USAGE: php deleteOrphanedRevisions.php [--report]\n\n" );
- echo( " --report : Prints out a count of affected revisions but doesn't delete them\n\n" );
-}
-
diff --git a/maintenance/deleteOrphanedRevisions.php b/maintenance/deleteOrphanedRevisions.php
index 78441f8e..1146befb 100644
--- a/maintenance/deleteOrphanedRevisions.php
+++ b/maintenance/deleteOrphanedRevisions.php
@@ -4,51 +4,87 @@
* Maintenance script to delete revisions which refer to a nonexisting page
* Sometimes manual deletion done in a rush leaves crap in the database
*
- * @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
+ *
* @ingroup Maintenance
* @author Rob Church <robchur@gmail.com>
* @todo More efficient cleanup of text records
*/
-
-$options = array( 'report', 'help' );
-require_once( 'commandLine.inc' );
-require_once( 'deleteOrphanedRevisions.inc.php' );
-echo( "Delete Orphaned Revisions\n" );
-
-if( isset( $options['help'] ) )
- showUsage();
-
-$report = isset( $options['report'] );
-
-$dbw = wfGetDB( DB_MASTER );
-$dbw->immediateBegin();
-extract( $dbw->tableNames( 'page', 'revision' ) );
-
-# Find all the orphaned revisions
-echo( "Checking for orphaned revisions..." );
-$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)
-while( $row = $dbw->fetchObject( $res ) )
- $revisions[] = $row->rev_id;
-$dbw->freeResult( $res );
-$count = count( $revisions );
-echo( "found {$count}.\n" );
-
-# Nothing to do?
-if( $report || $count == 0 ) {
- $dbw->immediateCommit();
- exit();
-}
-# Delete each revision
-echo( "Deleting..." );
-deleteRevisions( $revisions, $dbw );
-echo( "done.\n" );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class DeleteOrphanedRevisions extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Maintenance script to delete revisions which refer to a nonexisting page";
+ $this->addOption( 'report', 'Prints out a count of affected revisions but doesn\'t delete them' );
+ }
+
+ public function execute() {
+ $this->output( "Delete Orphaned Revisions\n" );
+
+ $report = $this->hasOption('report');
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+ list( $page, $revision ) = $dbw->tableNamesN( 'page', 'revision' );
+
+ # 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";
+ $res = $dbw->query( $sql, 'deleteOrphanedRevisions' );
+
+ # Stash 'em all up for deletion (if needed)
+ $revisions = array();
+ foreach( $res as $row )
+ $revisions[] = $row->rev_id;
+ $dbw->freeResult( $res );
+ $count = count( $revisions );
+ $this->output( "found {$count}.\n" );
+
+ # Nothing to do?
+ if( $report || $count == 0 ) {
+ $dbw->commit();
+ exit(0);
+ }
+
+ # Delete each revision
+ $this->output( "Deleting..." );
+ $this->deleteRevs( $revisions, $dbw );
+ $this->output( "done.\n" );
+
+ # Close the transaction and call the script to purge unused text records
+ $dbw->commit();
+ $this->purgeRedundantText( true );
+ }
+
+ /**
+ * Delete one or more revisions from the database
+ * Do this inside a transaction
+ *
+ * @param $id Array of revision id values
+ * @param $db Database class (needs to be a master)
+ */
+ private function deleteRevs( $id, &$dbw ) {
+ if( !is_array( $id ) )
+ $id = array( $id );
+ $dbw->delete( 'revision', array( 'rev_id' => $id ), __METHOD__ );
+ }
+}
-# Close the transaction and call the script to purge unused text records
-$dbw->immediateCommit();
-require_once( 'purgeOldText.inc' );
-PurgeRedundantText( true );
+$maintClass = "DeleteOrphanedRevisions";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/deleteRevision.php b/maintenance/deleteRevision.php
index 0c203ab0..5dc0b59f 100644
--- a/maintenance/deleteRevision.php
+++ b/maintenance/deleteRevision.php
@@ -2,46 +2,80 @@
/**
* Delete one or more revisions by moving them to the archive table.
*
- * @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
+ *
* @ingroup Maintenance
*/
-require_once( 'commandLine.inc' );
-
-$dbw = wfGetDB( DB_MASTER );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if ( count( $args ) == 0 ) {
- echo "Usage: php deleteRevision.php <revid> [<revid> ...]\n";
- exit(1);
-}
-
-echo "Deleting revision(s) " . implode( ',', $args ) . " from ".wfWikiID()."...\n";
+class DeleteRevision extends Maintenance {
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Delete one or more revisions by moving them to the archive table";
+ }
+
+ public function execute() {
+ if( count( $this->mArgs ) == 0 ) {
+ $this->error( "No revisions specified", true );
+ }
-$affected = 0;
-foreach ( $args as $revID ) {
- $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
- array(
- 'ar_namespace' => 'page_namespace',
- 'ar_title' => 'page_title',
- 'ar_comment' => 'rev_comment',
- 'ar_user' => 'rev_user',
- 'ar_user_text' => 'rev_user_text',
- 'ar_timestamp' => 'rev_timestamp',
- 'ar_minor_edit' => 'rev_minor_edit',
- 'ar_rev_id' => 'rev_id',
- 'ar_text_id' => 'rev_text_id',
- ), array(
- 'rev_id' => $revID,
- 'page_id = rev_page'
- ), $fname
- );
- if ( !$dbw->affectedRows() ) {
- echo "Revision $revID not found\n";
- } else {
- $affected += $dbw->affectedRows();
- $dbw->delete( 'revision', array( 'rev_id' => $revID ) );
+ $this->output( "Deleting revision(s) " . implode( ',', $this->mArgs ) .
+ " from " . wfWikiID() . "...\n" );
+ $dbw = wfGetDB( DB_MASTER );
+
+ $affected = 0;
+ foreach ( $this->mArgs as $revID ) {
+ $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
+ array(
+ 'ar_namespace' => 'page_namespace',
+ 'ar_title' => 'page_title',
+ 'ar_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(
+ 'rev_id' => $revID,
+ 'page_id = rev_page'
+ ), __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__ );
+ $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__ );
+ }
+ }
+ }
+ $this->output( "Deleted $affected revisions\n" );
}
}
-print "Deleted $affected revisions\n";
-
+$maintClass = "DeleteRevision";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/deleteSelfExternals.php b/maintenance/deleteSelfExternals.php
new file mode 100644
index 00000000..1ab2839e
--- /dev/null
+++ b/maintenance/deleteSelfExternals.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * We want to make this whole thing as seamless as possible to the
+ * end-user. Unfortunately, we can't do _all_ of the work in the class
+ * because A) included files are not in global scope, but in the scope
+ * of their caller, and B) MediaWiki has way too many globals. So instead
+ * we'll kinda fake it, and do the requires() inline. <3 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( "Maintenance.php" );
+
+
+class DeleteSelfExternals extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = 'Delete self-references to $wgServer from externallinks';
+ $this->mBatchSize = 1000;
+ }
+
+ public function execute() {
+ global $wgServer;
+ $this->output( "Deleting self externals from $wgServer\n" );
+ $db = wfGetDB(DB_MASTER);
+ while (1) {
+ wfWaitForSlaves( 2 );
+ $db->commit();
+ $q = $db->limitResult( "DELETE /* deleteSelfExternals */ FROM externallinks WHERE el_to"
+ . $db->buildLike( $wgServer . '/', $db->anyString() ), $this->mBatchSize );
+ $this->output( "Deleting a batch\n" );
+ $db->query($q);
+ if (!$db->affectedRows()) exit(0);
+ }
+ }
+}
+
+$maintClass = "DeleteSelfExternals";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/doMaintenance.php b/maintenance/doMaintenance.php
new file mode 100644
index 00000000..008c5b87
--- /dev/null
+++ b/maintenance/doMaintenance.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * We want to make this whole thing as seamless as possible to the
+ * end-user. Unfortunately, we can't do _all_ of the work in the class
+ * because A) included files are not in global scope, but in the scope
+ * of their caller, and B) MediaWiki has way too many globals. So instead
+ * we'll kinda fake it, and do the requires() inline. <3 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 Chad Horohoe <chad@anyonecanedit.org>
+ * @file
+ * @ingroup Maintenance
+ */
+
+if ( !defined( 'DO_MAINTENANCE' ) ) {
+ echo "This file must be included after Maintenance.php\n";
+ exit( 1 );
+}
+
+if( !$maintClass || !class_exists( $maintClass ) ) {
+ echo "\$maintClass is not set or is set to a non-existent class.\n";
+ exit( 1 );
+}
+
+if( defined( 'MW_NO_SETUP' ) ) {
+ return;
+}
+
+// Get an object to start us off
+$maintenance = new $maintClass();
+
+// Basic sanity checks and such
+$maintenance->setup();
+
+// We used to call this variable $self, but it was moved
+// to $maintenance->mSelf. Keep that here for b/c
+$self = $maintenance->getName();
+
+# Setup the profiler
+if ( file_exists( "$IP/StartProfiler.php" ) ) {
+ require_once( "$IP/StartProfiler.php" );
+} else {
+ require_once( "$IP/includes/ProfilerStub.php" );
+}
+
+// Some other requires
+require_once( "$IP/includes/AutoLoader.php" );
+require_once( "$IP/includes/Defines.php" );
+
+// Load settings, using wikimedia-mode if needed
+// Fixme: replace this hack with general farm-friendly code
+if( file_exists( "$IP/wmf-config/wikimedia-mode" ) ) {
+ # TODO FIXME! Wikimedia-specific stuff needs to go away to an ext
+ # Maybe a hook?
+ global $cluster;
+ $wgWikiFarm = true;
+ $cluster = 'pmtpa';
+ require_once( "$IP/includes/SiteConfiguration.php" );
+ require( "$IP/wmf-config/wgConf.php" );
+ $maintenance->loadWikimediaSettings();
+ require( $IP.'/wmf-config/CommonSettings.php' );
+} else {
+ require_once( $maintenance->loadSettings() );
+}
+if ( $maintenance->getDbType() === Maintenance::DB_ADMIN &&
+ is_readable( "$IP/AdminSettings.php" ) )
+{
+ require( "$IP/AdminSettings.php" );
+}
+$maintenance->finalSetup();
+// Some last includes
+require_once( "$IP/includes/Setup.php" );
+require_once( "$IP/maintenance/install-utils.inc" );
+
+// Much much faster startup than creating a title object
+$wgTitle = null;
+
+// Do the work
+try {
+ $maintenance->execute();
+
+ // Potentially debug globals
+ $maintenance->globals();
+} catch( MWException $mwe ) {
+ echo( $mwe->getText() );
+ exit( 1 );
+}
+
diff --git a/maintenance/dumpBackup.php b/maintenance/dumpBackup.php
index 0e28734c..3f4530ed 100644
--- a/maintenance/dumpBackup.php
+++ b/maintenance/dumpBackup.php
@@ -26,7 +26,7 @@ $originalDir = getcwd();
$optionsWithArgs = array( 'pagelist', 'start', 'end' );
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
require_once( 'backup.inc' );
$dumper = new BackupDumper( $argv );
@@ -69,30 +69,30 @@ if( isset( $options['full'] ) ) {
$dumper->dump( WikiExporter::LOGS );
} else {
$dumper->progress( <<<ENDS
-This script dumps the wiki page database into an XML interchange wrapper
-format for export or backup.
+This script dumps the wiki page or logging database into an
+XML interchange wrapper format for export or backup.
XML output is sent to stdout; progress reports are sent to stderr.
Usage: php dumpBackup.php <action> [<options>]
Actions:
- --full Dump complete history of every page.
- --current Includes only the latest revision of each page.
- --logs Dump action logs for every page.
+ --full Dump all revisions of every page.
+ --current Dump only the latest revision of every page.
+ --logs Dump all log events.
Options:
--quiet Don't dump status reports to stderr.
--report=n Report position and speed after every n pages processed.
(Default: 100)
--server=h Force reading from MySQL server h
- --start=n Start from page_id n
- --end=n Stop before page_id n (exclusive)
+ --start=n Start from page_id or log_id n
+ --end=n Stop before page_id or log_id n (exclusive)
--skip-header Don't output the <mediawiki> header
--skip-footer Don't output the </mediawiki> footer
--stub Don't perform old_text lookups; for 2-pass dump
--uploads Include upload records (experimental)
-Fancy stuff:
+Fancy stuff: (Works? Add examples please.)
--plugin=<class>[:<file>] Load a dump plugin class
--output=<type>:<file> Begin a filtered output stream;
<type>s: file, gzip, bzip2, 7zip
@@ -101,5 +101,3 @@ Fancy stuff:
ENDS
);
}
-
-
diff --git a/maintenance/dumpInterwiki.inc b/maintenance/dumpInterwiki.inc
index 481e21cc..c366b08c 100644
--- a/maintenance/dumpInterwiki.inc
+++ b/maintenance/dumpInterwiki.inc
@@ -6,6 +6,7 @@
* @file
* @todo document
* @ingroup Maintenance
+ * @ingroup Wikimedia
*/
/**
@@ -201,7 +202,7 @@ function makeLink( $entry, $source ) {
array_key_exists($entry['iw_prefix'],$prefixRewrites[$source]))
$entry['iw_prefix'] = $prefixRewrites[$source][$entry['iw_prefix']];
if ($dbFile)
- dba_insert("{$source}:{$entry['iw_prefix']}", trim("{$entry['iw_local']} {$entry['iw_url']}"),$dbFile);
+ $dbFile->set( "{$source}:{$entry['iw_prefix']}", trim("{$entry['iw_local']} {$entry['iw_url']}") );
else
print "{$source}:{$entry['iw_prefix']} {$entry['iw_url']} {$entry['iw_local']}\n";
diff --git a/maintenance/dumpInterwiki.php b/maintenance/dumpInterwiki.php
index f5f22f3c..045e393b 100644
--- a/maintenance/dumpInterwiki.php
+++ b/maintenance/dumpInterwiki.php
@@ -6,20 +6,21 @@
* @file
* @todo document
* @ingroup Maintenance
+ * @ingroup Wikimedia
*/
/** */
$oldCwd = getcwd();
$optionsWithArgs = array( "o" );
-require( "commandLine.inc" );
-require( "dumpInterwiki.inc" );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
+require( dirname(__FILE__)."/dumpInterwiki.inc" );
chdir( $oldCwd );
# Output
if ( isset( $options['o'] ) ) {
# To database specified with -o
- $dbFile = dba_open( $options['o'], "n", "cdb_make" );
+ $dbFile = CdbWriter::open( $options['o'] );
}
getRebuildInterwikiDump();
diff --git a/maintenance/dumpLinks.php b/maintenance/dumpLinks.php
index 65dfac64..529cd1aa 100644
--- a/maintenance/dumpLinks.php
+++ b/maintenance/dumpLinks.php
@@ -26,38 +26,48 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @file
* @ingroup Mainatenance
*/
-require_once 'commandLine.inc';
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$dbr = wfGetDB( DB_SLAVE );
-$result = $dbr->select( array( 'pagelinks', 'page' ),
- array(
- 'page_id',
- 'page_namespace',
- 'page_title',
- 'pl_namespace',
- 'pl_title' ),
- array( 'page_id=pl_from' ),
- 'dumpLinks',
- array( 'ORDER BY' => 'page_id' ) );
+class DumpLinks extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Quick demo hack to generate a plaintext link dump";
+ }
-$lastPage = null;
-while( $row = $dbr->fetchObject( $result ) ) {
- if( $lastPage != $row->page_id ) {
- if( isset( $lastPage ) ) {
- print "\n";
+ public function execute() {
+ $dbr = wfGetDB( DB_SLAVE );
+ $result = $dbr->select( array( 'pagelinks', 'page' ),
+ array(
+ 'page_id',
+ 'page_namespace',
+ 'page_title',
+ 'pl_namespace',
+ 'pl_title' ),
+ array( 'page_id=pl_from' ),
+ __METHOD__,
+ array( 'ORDER BY' => 'page_id' ) );
+
+ $lastPage = null;
+ foreach( $result as $row ) {
+ if( $lastPage != $row->page_id ) {
+ if( isset( $lastPage ) ) {
+ $this->output( "\n" );
+ }
+ $page = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $this->output( $page->getPrefixedUrl() );
+ $lastPage = $row->page_id;
+ }
+ $link = Title::makeTitle( $row->pl_namespace, $row->pl_title );
+ $this->output( " " . $link->getPrefixedUrl() );
}
- $page = Title::makeTitle( $row->page_namespace, $row->page_title );
- print $page->getPrefixedUrl();
- $lastPage = $row->page_id;
+ if( isset( $lastPage ) )
+ $this->output( "\n" );
}
- $link = Title::makeTitle( $row->pl_namespace, $row->pl_title );
- print " " . $link->getPrefixedUrl();
}
-if( isset( $lastPage ) )
- print "\n";
+$maintClass = "DumpLinks";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/dumpSisterSites.php b/maintenance/dumpSisterSites.php
index 2a7369c0..d9fd28a6 100644
--- a/maintenance/dumpSisterSites.php
+++ b/maintenance/dumpSisterSites.php
@@ -21,29 +21,36 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @file
- * @ingroup SpecialPage
+ * @ingroup Maintenance
*/
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$dbr = wfGetDB( DB_SLAVE );
-$dbr->bufferResults( false );
-$result = $dbr->select( 'page',
- array( 'page_namespace', 'page_title' ),
- array(
- 'page_namespace' => NS_MAIN,
- 'page_is_redirect' => 0,
- ),
- 'dumpSisterSites' );
+class DumpSisterSites extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Quickie page name dump script for SisterSites usage";
+ }
+
+ public function execute() {
+ $dbr = wfGetDB( DB_SLAVE );
+ $dbr->bufferResults( false );
+ $result = $dbr->select( 'page',
+ array( 'page_namespace', 'page_title' ),
+ array( 'page_namespace' => NS_MAIN,
+ 'page_is_redirect' => 0,
+ ),
+ __METHOD__ );
-while( $row = $dbr->fetchObject( $result ) ) {
- $title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $url = $title->getFullUrl();
- $text = $title->getPrefixedText();
- echo "$url $text\n";
+ foreach( $result as $row ) {
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $url = $title->getFullUrl();
+ $text = $title->getPrefixedText();
+ $this->output( "$url $text\n" );
+ }
+ $dbr->freeResult( $result );
+ }
}
-$dbr->freeResult( $result );
-
-
+$maintClass = "DumpSisterSites";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/dumpTextPass.php b/maintenance/dumpTextPass.php
index e85fe421..2e639e68 100644
--- a/maintenance/dumpTextPass.php
+++ b/maintenance/dumpTextPass.php
@@ -24,79 +24,10 @@
$originalDir = getcwd();
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
require_once( 'backup.inc' );
/**
- * Stream wrapper around 7za filter program.
- * Required since we can't pass an open file resource to XMLReader->open()
- * which is used for the text prefetch.
- *
- * @ingroup Maintenance
- */
-class SevenZipStream {
- var $stream;
-
- private function stripPath( $path ) {
- $prefix = 'mediawiki.compress.7z://';
- return substr( $path, strlen( $prefix ) );
- }
-
- function stream_open( $path, $mode, $options, &$opened_path ) {
- if( $mode{0} == 'r' ) {
- $options = 'e -bd -so';
- } elseif( $mode{0} == 'w' ) {
- $options = 'a -bd -si';
- } else {
- return false;
- }
- $arg = wfEscapeShellArg( $this->stripPath( $path ) );
- $command = "7za $options $arg";
- if( !wfIsWindows() ) {
- // Suppress the stupid messages on stderr
- $command .= ' 2>/dev/null';
- }
- $this->stream = popen( $command, $mode );
- return ($this->stream !== false);
- }
-
- function url_stat( $path, $flags ) {
- return stat( $this->stripPath( $path ) );
- }
-
- // This is all so lame; there should be a default class we can extend
-
- function stream_close() {
- return fclose( $this->stream );
- }
-
- function stream_flush() {
- return fflush( $this->stream );
- }
-
- function stream_read( $count ) {
- return fread( $this->stream, $count );
- }
-
- function stream_write( $data ) {
- return fwrite( $this->stream, $data );
- }
-
- function stream_tell() {
- return ftell( $this->stream );
- }
-
- function stream_eof() {
- return feof( $this->stream );
- }
-
- function stream_seek( $offset, $whence ) {
- return fseek( $this->stream, $offset, $whence );
- }
-}
-stream_wrapper_register( 'mediawiki.compress.7z', 'SevenZipStream' );
-
-/**
* @ingroup Maintenance
*/
class TextPassDumper extends BackupDumper {
@@ -305,6 +236,7 @@ class TextPassDumper extends BackupDumper {
* May throw a database error if, say, the server dies during query.
*/
private function getTextDb( $id ) {
+ global $wgContLang;
$id = intval( $id );
$row = $this->db->selectRow( 'text',
array( 'old_text', 'old_flags' ),
@@ -315,7 +247,7 @@ class TextPassDumper extends BackupDumper {
return false;
}
$stripped = str_replace( "\r", "", $text );
- $normalized = UtfNormal::cleanUp( $stripped );
+ $normalized = $wgContLang->normalize( $stripped );
return $normalized;
}
@@ -390,6 +322,8 @@ class TextPassDumper extends BackupDumper {
}
private function getTextSpawnedOnce( $id ) {
+ global $wgContLang;
+
$ok = fwrite( $this->spawnWrite, "$id\n" );
//$this->progress( ">> $id" );
if( !$ok ) return false;
@@ -408,7 +342,7 @@ class TextPassDumper extends BackupDumper {
// Subprocess may not send everything at once, we have to loop.
while( $nbytes > strlen( $text ) ) {
$buffer = fread( $this->spawnRead, $nbytes - strlen( $text ) );
- if( $text === false ) break;
+ if( $buffer === false ) break;
$text .= $buffer;
}
@@ -420,7 +354,7 @@ class TextPassDumper extends BackupDumper {
// Do normalization in the dump thread...
$stripped = str_replace( "\r", "", $text );
- $normalized = UtfNormal::cleanUp( $stripped );
+ $normalized = $wgContLang->normalize( $stripped );
return $normalized;
}
diff --git a/maintenance/dumpUploads.php b/maintenance/dumpUploads.php
index c237feee..c8f1667b 100644
--- a/maintenance/dumpUploads.php
+++ b/maintenance/dumpUploads.php
@@ -1,37 +1,55 @@
<?php
/**
- * @file
+ * Dump a the list of files uploaded, for feeding to tar or similar
+ *
+ * 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
+ *
* @ingroup Maintenance
*/
-require_once 'commandLine.inc';
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-class UploadDumper {
- function __construct( $args ) {
+class UploadDumper extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Generates list of uploaded files which can be fed to tar or similar.
+By default, outputs relative paths against the parent directory of \$wgUploadDirectory.";
+ $this->addOption( 'base', 'Set base relative path instead of wiki include root', false, true );
+ $this->addOption( 'local', 'List all local files, used or not. No shared files included' );
+ $this->addOption( 'used', 'Skip local images that are not used' );
+ $this->addOption( 'shared', 'Include images used from shared repository' );
+ }
+
+ public function execute() {
global $IP, $wgUseSharedUploads;
$this->mAction = 'fetchLocal';
- $this->mBasePath = $IP;
+ $this->mBasePath = $this->getOption( 'base', $IP );
$this->mShared = false;
$this->mSharedSupplement = false;
-
- if( isset( $args['help'] ) ) {
- $this->mAction = 'help';
- }
-
- if( isset( $args['base'] ) ) {
- $this->mBasePath = $args['base'];
- }
-
- if( isset( $args['local'] ) ) {
+
+ if( $this->hasOption('local') ) {
$this->mAction = 'fetchLocal';
}
- if( isset( $args['used'] ) ) {
+ if( $this->hasOption('used') ) {
$this->mAction = 'fetchUsed';
}
- if( isset( $args['shared'] ) ) {
- if( isset( $args['used'] ) ) {
+ if( $this->hasOption('shared') ) {
+ if( $this->hasOption('used') ) {
// Include shared-repo files in the used check
$this->mShared = true;
} else {
@@ -39,34 +57,12 @@ class UploadDumper {
$this->mSharedSupplement = true;
}
}
- }
-
- function run() {
$this->{$this->mAction}( $this->mShared );
if( $this->mSharedSupplement ) {
$this->fetchUsed( true );
}
}
-
- function help() {
- echo <<<END
-Generates list of uploaded files which can be fed to tar or similar.
-By default, outputs relative paths against the parent directory of
-\$wgUploadDirectory.
-Usage:
-php dumpUploads.php [options] > list-o-files.txt
-
-Options:
---base=<path> Set base relative path instead of wiki include root
-
---local List all local files, used or not. No shared files included.
---used Skip local images that are not used
---shared Include images used from shared repository
-
-END;
- }
-
/**
* Fetch a list of all or used images from a particular image source.
* @param string $table
@@ -89,7 +85,7 @@ END;
}
$dbr->freeResult( $result );
}
-
+
function fetchLocal( $shared ) {
$dbr = wfGetDB( DB_SLAVE );
$result = $dbr->select( 'image',
@@ -108,17 +104,16 @@ END;
if( $file && $this->filterItem( $file, $shared ) ) {
$filename = $file->getFullPath();
$rel = wfRelativePath( $filename, $this->mBasePath );
- echo "$rel\n";
+ $this->output( "$rel\n" );
} else {
wfDebug( __METHOD__ . ": base file? $name\n" );
}
}
-
+
function filterItem( $file, $shared ) {
return $shared || $file->isLocal();
}
}
-$dumper = new UploadDumper( $options );
-$dumper->run();
-
+$maintClass = "UploadDumper";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/edit.php b/maintenance/edit.php
index 64178045..8d0068c3 100644
--- a/maintenance/edit.php
+++ b/maintenance/edit.php
@@ -1,77 +1,89 @@
<?php
/**
- * @file
+ * Make an edit
+ *
+ * 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
+ *
* @ingroup Maintenance
*/
-$optionsWithArgs = array( 'u', 's' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-require_once( 'commandLine.inc' );
+class EditCLI extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Edit an article from the command line, text is from stdin";
+ $this->addOption( 'u', 'Username', false, true );
+ $this->addOption( 's', 'Edit summary', false, true );
+ $this->addOption( 'm', 'Minor edit' );
+ $this->addOption( 'b', 'Bot edit' );
+ $this->addOption( 'a', 'Enable autosummary' );
+ $this->addOption( 'no-rc', 'Do not show the change in recent changes' );
+ $this->addArg( 'title', 'Title of article to edit' );
+ }
-if ( count( $args ) == 0 || isset( $options['help'] ) ) {
- print <<<EOT
-Edit an article from the command line
+ public function execute() {
+ global $wgUser, $wgTitle, $wgArticle;
-Usage: php edit.php [options...] <title>
-
-Options:
- -u <user> Username
- -s <summary> Edit summary
- -m Minor edit
- -b Bot (hidden) edit
- -a Enable autosummary
- --no-rc Do not show the change in recent changes
-
-If the specified user does not exist, it will be created.
-The text for the edit will be read from stdin.
-
-EOT;
- exit( 1 );
-}
-
-$userName = isset( $options['u'] ) ? $options['u'] : 'Maintenance script';
-$summary = isset( $options['s'] ) ? $options['s'] : '';
-$minor = isset( $options['m'] );
-$bot = isset( $options['b'] );
-$autoSummary = isset( $options['a'] );
-$noRC = isset( $options['no-rc'] );
-
-$wgUser = User::newFromName( $userName );
-if ( !$wgUser ) {
- print "Invalid username\n";
- exit( 1 );
-}
-if ( $wgUser->isAnon() ) {
- $wgUser->addToDatabase();
+ $userName = $this->getOption( 'u', 'Maintenance script' );
+ $summary = $this->getOption( 's', '' );
+ $minor = $this->hasOption( 'm' );
+ $bot = $this->hasOption( 'b' );
+ $autoSummary = $this->hasOption( 'a' );
+ $noRC = $this->hasOption( 'no-rc' );
+
+ $wgUser = User::newFromName( $userName );
+ if ( !$wgUser ) {
+ $this->error( "Invalid username", true );
+ }
+ if ( $wgUser->isAnon() ) {
+ $wgUser->addToDatabase();
+ }
+
+ $wgTitle = Title::newFromText( $this->getArg() );
+ if ( !$wgTitle ) {
+ $this->error( "Invalid title", true );
+ }
+
+ $wgArticle = new Article( $wgTitle );
+
+ # Read the text
+ $text = $this->getStdin( Maintenance::STDIN_ALL );
+
+ # Do the edit
+ $this->output( "Saving... " );
+ $status = $wgArticle->doEdit( $text, $summary,
+ ( $minor ? EDIT_MINOR : 0 ) |
+ ( $bot ? EDIT_FORCE_BOT : 0 ) |
+ ( $autoSummary ? EDIT_AUTOSUMMARY : 0 ) |
+ ( $noRC ? EDIT_SUPPRESS_RC : 0 ) );
+ if ( $status->isOK() ) {
+ $this->output( "done\n" );
+ $exit = 0;
+ } else {
+ $this->output( "failed\n" );
+ $exit = 1;
+ }
+ if ( !$status->isGood() ) {
+ $this->output( $status->getWikiText() . "\n" );
+ }
+ exit( $exit );
+ }
}
-$wgTitle = Title::newFromText( $args[0] );
-if ( !$wgTitle ) {
- print "Invalid title\n";
- exit( 1 );
-}
-
-$wgArticle = new Article( $wgTitle );
-
-# Read the text
-$text = file_get_contents( 'php://stdin' );
-
-# Do the edit
-print "Saving... ";
-$status = $wgArticle->doEdit( $text, $summary,
- ( $minor ? EDIT_MINOR : 0 ) |
- ( $bot ? EDIT_FORCE_BOT : 0 ) |
- ( $autoSummary ? EDIT_AUTOSUMMARY : 0 ) |
- ( $noRC ? EDIT_SUPPRESS_RC : 0 ) );
-if ( $status->isOK() ) {
- print "done\n";
- $exit = 0;
-} else {
- print "failed\n";
- $exit = 1;
-}
-if ( !$status->isGood() ) {
- print $status->getWikiText() . "\n";
-}
-exit( $exit );
+$maintClass = "EditCLI";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/fetchInterwiki.pl b/maintenance/fetchInterwiki.pl
deleted file mode 100644
index cb56a6df..00000000
--- a/maintenance/fetchInterwiki.pl
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/usr/bin/env perl
-# Copyright (C) 2005 Ævar Arnfjörð Bjarmason
-use strict;
-use warnings;
-use Socket;
-
-# Conf
-my $map = &get(&url('http://usemod.com/intermap.txt'));
-
-# --- #
-my $cont;
-my @map = split /\n/, $map;
-
-$cont .= '<?php
-# Note: this file is generated by maintenance/fetchInterwiki.pl
-# Edit and rerun that script rather than modifying this directly.
-
-/* private */ $wgValidInterwikis = array(
-';
-
-$cont .= "\t# The usemod interwiki map\n";
-for (my $i=0;$i<=$#map;++$i) {
- my ($name, $url) = $map[$i] =~ m#^([^ ]+) (.+)#i;
- $cont .= "\t'$name' => '$url\$1',\n";
-}
-
-my @iso = qw(
-aa ab af als am ar as ay az ba be bg bh bi bn bo bs ca chr co cs csb cy da de dk:da dz el en eo
-es et eu fa fi fj fo fr fy ga gd gl gn gu gv ha he hi hr hu hy ia id ik io is it iu ja jv ka kk
-kl km kn ko ks ku ky la lo lt lv mg mi mk ml mn mo mr ms my na nah nb nds ne nl no oc om or pa
-pl ps pt qu rm rn ro ru rw sa sd sg sh si sk sl sm sn so sq sr ss st su sv sw ta te tg th ti tk
-tl tn to tp tpi tr ts tt tw ug uk ur uz vi vo wa wo xh yi yo za zh zh-cn zh-tw zu);
-
-$cont .= '
- # Some custom additions:
- "ReVo" => "http://purl.org/NET/voko/revo/art/$1.html",
- # eg [[ReVo:cerami]], [[ReVo:astero]] - note X-sensitive!
- "EcheI" => "http://www.ikso.net/cgi-bin/wiki.pl?$1",
- "E\\xc4\\x89eI" => "http://www.ikso.net/cgi-bin/wiki.pl?$1",
- "UnuMondo" => "http://unumondo.com/cgi-bin/wiki.pl?$1", # X-sensitive!
- "JEFO" => "http://esperanto.jeunes.free.fr/vikio/index.php?$1",
- "PMEG" => "http://www.bertilow.com/pmeg/$1.php",
- # ekz [[PMEG:gramatiko/kunligaj vortetoj/au]]
- "EnciclopediaLibre" => "http://enciclopedia.us.es/wiki.phtml?title=$1",
-
- # Wikipedia-specific stuff:
- # Special cases
- "w" => "http://www.wikipedia.org/wiki/$1",
- "m" => "http://meta.wikipedia.org/wiki/$1",
- "meta" => "http://meta.wikipedia.org/wiki/$1",
- "sep11" => "http://sep11.wikipedia.org/wiki/$1",
- "simple"=> "http://simple.wikipedia.com/wiki.cgi?$1",
- "wiktionary" => "http://wiktionary.wikipedia.org/wiki/$1",
- "PageHistory" => "http://www.wikipedia.org/w/wiki.phtml?title=$1&action=history",
- "UserContributions" => "http://www.wikipedia.org/w/wiki.phtml?title=Special:Contributions&target=$1",
- "BackLinks" => "http://www.wikipedia.org/w/wiki.phtml?title=Special:Whatlinkshere&target=$1",
-
- # ISO 639 2-letter language codes
-';
-
-for(my $i=0; $i<=$#iso;++$i) {
- my @arr = split /:/, $iso[$i];
- $cont .= "\t";
- $cont .= "'$arr[0]' => 'http://";
-
- if ($arr[1]) {
- $cont .= $arr[1];
- } else {
- $cont .= $arr[0];
- }
- $cont .= ".wikipedia.org/wiki/\$1',\n";
-}
-
-$cont .= '
-);
-?>
-';
-
-open IW, ">Interwiki.php";
-print IW $cont;
-close IW;
-
-sub get {
- my ($host, $url) = @_;
- my $cont;
- my $eat;
-
- my $proto = getprotobyname('tcp');
- socket(Socket, AF_INET, SOCK_STREAM, $proto);
- my $iaddr = inet_aton("$host");
- my $port = getservbyname('http', 'tcp');
- my $sin = sockaddr_in($port, $iaddr);
- connect(Socket, $sin);
- send Socket, "GET $url HTTP/1.0\r\nHost: $host\r\n\r\n",0;
- while (<Socket>) {
- $cont .= $_ if $eat; # mmm, food
- ++$eat if ($_ =~ /^(\n|\r\n|)$/);
- }
- return $cont;
-}
-
-sub url {my ($server, $path) = $_[0] =~ m#.*(?=//)//([^/]*)(.*)#g;}
diff --git a/maintenance/fetchText.php b/maintenance/fetchText.php
index 91b78be3..746ef8ad 100644
--- a/maintenance/fetchText.php
+++ b/maintenance/fetchText.php
@@ -2,38 +2,66 @@
/**
* Communications protocol...
*
- * @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
+ *
* @ingroup Maintenance
*/
-require "commandLine.inc";
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$db = wfGetDB( DB_SLAVE );
-$stdin = fopen( "php://stdin", "rt" );
-while( !feof( $stdin ) ) {
- $line = fgets( $stdin );
- if( $line === false ) {
- // We appear to have lost contact...
- break;
+class FetchText extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Fetch the revision text from an old_id";
}
- $textId = intval( $line );
- $text = doGetText( $db, $textId );
- echo strlen( $text ) . "\n";
- echo $text;
-}
-/**
- * May throw a database error if, say, the server dies during query.
- */
-function doGetText( $db, $id ) {
- $id = intval( $id );
- $row = $db->selectRow( 'text',
- array( 'old_text', 'old_flags' ),
- array( 'old_id' => $id ),
- 'TextPassDumper::getText' );
- $text = Revision::getRevisionText( $row );
- if( $text === false ) {
- return false;
+ public function execute() {
+ $db = wfGetDB( DB_SLAVE );
+ $stdin = $this->getStdin();
+ while( !feof( $stdin ) ) {
+ $line = fgets( $stdin );
+ if( $line === false ) {
+ // We appear to have lost contact...
+ break;
+ }
+ $textId = intval( $line );
+ $text = $this->doGetText( $db, $textId );
+ $this->output( strlen( $text ) . "\n". $text );
+ }
+ }
+
+ /**
+ * May throw a database error if, say, the server dies during query.
+ * @param $db Database object
+ * @param $id int The old_id
+ * @return String
+ */
+ private function doGetText( $db, $id ) {
+ $id = intval( $id );
+ $row = $db->selectRow( 'text',
+ array( 'old_text', 'old_flags' ),
+ array( 'old_id' => $id ),
+ 'TextPassDumper::getText' );
+ $text = Revision::getRevisionText( $row );
+ if( $text === false ) {
+ return false;
+ }
+ return $text;
}
- return $text;
}
+
+$maintClass = "FetchText";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/findhooks.php b/maintenance/findhooks.php
index 64fe9a54..13236b6b 100644
--- a/maintenance/findhooks.php
+++ b/maintenance/findhooks.php
@@ -12,7 +12,21 @@
*
* Any instance of wfRunHooks that doesn't meet these parameters will be noted.
*
- * @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
+ *
* @ingroup Maintenance
*
* @author Ashar Voultoiz <hashar@altern.org>
@@ -20,139 +34,174 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public Licence 2.0 or later
*/
-/** This is a command line script*/
-require('commandLine.inc');
-# GLOBALS
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$doc = $IP . '/docs/hooks.txt';
-$pathinc = array(
- $IP.'/',
- $IP.'/includes/',
- $IP.'/includes/api/',
- $IP.'/includes/db/',
- $IP.'/includes/diff/',
- $IP.'/includes/filerepo/',
- $IP.'/includes/parser/',
- $IP.'/includes/specials/',
- $IP.'/languages/',
- $IP.'/maintenance/',
- $IP.'/skins/',
-);
-
-# FUNCTIONS
+class FindHooks extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Find hooks that are undocumented, missing, or just plain wrong";
+ $this->addOption( 'online', 'Check against mediawiki.org hook documentation' );
+ }
-/**
- * @return array of documented hooks
- */
-function getHooksFromDoc() {
- global $doc, $options;
- $m = array();
- if( isset( $options['online'] ) ){
- $content = Http::get( 'http://www.mediawiki.org/w/index.php?title=Manual:Hooks&action=raw' );
- preg_match_all( '/\[\[\/([a-zA-Z0-9-_:]+)\|/', $content, $m );
- } else {
- $content = file_get_contents( $doc );
- preg_match_all( "/\n'(.*?)'/", $content, $m );
+ public function getDbType() {
+ return Maintenance::DB_NONE;
}
- return array_unique( $m[1] );
-}
-/**
- * Get hooks from a PHP file
- * @param $file Full filename to the PHP file.
- * @return array of hooks found.
- */
-function getHooksFromFile( $file ) {
- $content = file_get_contents( $file );
- $m = array();
- preg_match_all( '/wfRunHooks\(\s*([\'"])(.*?)\1/', $content, $m);
- return $m[2];
-}
+ public function execute() {
+ global $IP;
-/**
- * Get hooks from the source code.
- * @param $path Directory where the include files can be found
- * @return array of hooks found.
- */
-function getHooksFromPath( $path ) {
- $hooks = array();
- if( $dh = opendir($path) ) {
- while(($file = readdir($dh)) !== false) {
- if( filetype($path.$file) == 'file' ) {
- $hooks = array_merge( $hooks, getHooksFromFile($path.$file) );
+ $documented = $this->getHooksFromDoc( $IP . '/docs/hooks.txt' );
+ $potential = array();
+ $bad = array();
+ $pathinc = array(
+ $IP.'/',
+ $IP.'/includes/',
+ $IP.'/includes/api/',
+ $IP.'/includes/db/',
+ $IP.'/includes/diff/',
+ $IP.'/includes/filerepo/',
+ $IP.'/includes/parser/',
+ $IP.'/includes/search/',
+ $IP.'/includes/specials/',
+ $IP.'/includes/upload/',
+ $IP.'/languages/',
+ $IP.'/maintenance/',
+ $IP.'/skins/',
+ );
+
+ foreach( $pathinc as $dir ) {
+ $potential = array_merge( $potential, $this->getHooksFromPath( $dir ) );
+ $bad = array_merge( $bad, $this->getBadHooksFromPath( $dir ) );
+ }
+
+ $potential = array_unique( $potential );
+ $bad = array_unique( $bad );
+ $todo = array_diff( $potential, $documented );
+ $deprecated = array_diff( $documented, $potential );
+
+ // let's show the results:
+ $this->printArray('Undocumented', $todo );
+ $this->printArray('Documented and not found', $deprecated );
+ $this->printArray('Unclear hook calls', $bad );
+
+ 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
+ */
+ private function getHooksFromDoc( $doc ) {
+ if( $this->hasOption( 'online' ) ){
+ // 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;
+ }
}
+ return array_diff( $allhooks, $removed );
+ } else {
+ $m = array();
+ $content = file_get_contents( $doc );
+ preg_match_all( "/\n'(.*?)'/", $content, $m );
+ return array_unique( $m[1] );
}
- closedir($dh);
}
- return $hooks;
-}
-/**
- * Get bad hooks (where the hook name could not be determined) from a PHP file
- * @param $file Full filename to the PHP file.
- * @return array of bad wfRunHooks() lines
- */
-function getBadHooksFromFile( $file ) {
- $content = file_get_contents( $file );
- $m = array();
- # We want to skip the "function wfRunHooks()" one. :)
- preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m);
- $list = array();
- foreach( $m[0] as $match ){
- $list[] = $match . "(" . $file . ")";
+ /**
+ * Get hooks from a PHP file
+ * @param $file Full filename to the PHP file.
+ * @return array of hooks found.
+ */
+ private function getHooksFromFile( $file ) {
+ $content = file_get_contents( $file );
+ $m = array();
+ preg_match_all( '/wfRunHooks\(\s*([\'"])(.*?)\1/', $content, $m);
+ return $m[2];
}
- 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
- */
-function getBadHooksFromPath( $path ) {
- $hooks = array();
- if( $dh = opendir($path) ) {
- while(($file = readdir($dh)) !== false) {
- # We don't want to read this file as it contains bad calls to wfRunHooks()
- if( filetype( $path.$file ) == 'file' && !$path.$file == __FILE__ ) {
- $hooks = array_merge( $hooks, getBadHooksFromFile($path.$file) );
+ /**
+ * Get hooks from the source code.
+ * @param $path Directory where the include files can be found
+ * @return array of hooks found.
+ */
+ private function getHooksFromPath( $path ) {
+ $hooks = array();
+ if( $dh = opendir($path) ) {
+ while(($file = readdir($dh)) !== false) {
+ if( filetype($path.$file) == 'file' ) {
+ $hooks = array_merge( $hooks, $this->getHooksFromFile($path.$file) );
+ }
}
+ closedir($dh);
}
- closedir($dh);
+ return $hooks;
}
- return $hooks;
-}
-/**
- * Nicely output the array
- * @param $msg A message to show before the value
- * @param $arr An array
- * @param $sort Boolean : wheter to sort the array (Default: true)
- */
-function printArray( $msg, $arr, $sort = true ) {
- if($sort) asort($arr);
- foreach($arr as $v) echo "$msg: $v\n";
-}
+ /**
+ * Get bad hooks (where the hook name could not be determined) from a PHP file
+ * @param $file Full filename to the PHP file.
+ * @return array of bad wfRunHooks() lines
+ */
+ private function getBadHooksFromFile( $file ) {
+ $content = file_get_contents( $file );
+ $m = array();
+ # We want to skip the "function wfRunHooks()" one. :)
+ preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m);
+ $list = array();
+ foreach( $m[0] as $match ){
+ $list[] = $match . "(" . $file . ")";
+ }
+ return $list;
+ }
-# MAIN
+ /**
+ * Get bad hooks from the source code.
+ * @param $path Directory where the include files can be found
+ * @return array of bad wfRunHooks() lines
+ */
+ private function getBadHooksFromPath( $path ) {
+ $hooks = array();
+ if( $dh = opendir($path) ) {
+ while(($file = readdir($dh)) !== false) {
+ # We don't want to read this file as it contains bad calls to wfRunHooks()
+ if( filetype( $path.$file ) == 'file' && !$path.$file == __FILE__ ) {
+ $hooks = array_merge( $hooks, $this->getBadHooksFromFile($path.$file) );
+ }
+ }
+ closedir($dh);
+ }
+ return $hooks;
+ }
-$documented = getHooksFromDoc($doc);
-$potential = array();
-$bad = array();
-foreach( $pathinc as $dir ) {
- $potential = array_merge( $potential, getHooksFromPath( $dir ) );
- $bad = array_merge( $bad, getBadHooksFromPath( $dir ) );
+ /**
+ * Nicely output the array
+ * @param $msg A message to show before the value
+ * @param $arr An array
+ * @param $sort Boolean : wheter 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" );
+ }
}
-$potential = array_unique( $potential );
-$bad = array_unique( $bad );
-$todo = array_diff( $potential, $documented );
-$deprecated = array_diff( $documented, $potential );
-
-// let's show the results:
-printArray('undocumented', $todo );
-printArray('not found', $deprecated );
-printArray('unclear hook calls', $bad );
-
-if ( count( $todo ) == 0 && count( $deprecated ) == 0 && count( $bad ) == 0 )
- echo "Looks good!\n";
+$maintClass = "FindHooks";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/fixSlaveDesync.php b/maintenance/fixSlaveDesync.php
index 7817bc56..c585beb1 100644
--- a/maintenance/fixSlaveDesync.php
+++ b/maintenance/fixSlaveDesync.php
@@ -1,196 +1,217 @@
<?php
/**
- * @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
+ *
* @ingroup Maintenance
*/
-$wgUseRootUser = true;
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-//$wgDebugLogFile = '/dev/stdout';
+class FixSlaveDesync extends Maintenance {
+ public function __construct() {
+ global $wgUseRootUser;
+ $wgUseRootUser = true;
-$slaveIndexes = array();
-for ( $i = 1; $i < count( $wgDBservers ); $i++ ) {
- if ( wfGetLB()->isNonZeroLoad( $i ) ) {
- $slaveIndexes[] = $i;
+ parent::__construct();
+ $this->mDescription = "";
+
}
-}
-/*
-foreach ( wfGetLB()->mServers as $i => $server ) {
- wfGetLB()->mServers[$i]['flags'] |= DBO_DEBUG;
-}*/
-$reportingInterval = 1000;
-
-if ( isset( $args[0] ) ) {
- desyncFixPage( $args[0] );
-} else {
- $dbw = wfGetDB( DB_MASTER );
- $maxPage = $dbw->selectField( 'page', 'MAX(page_id)', false, 'fixDesync.php' );
- $corrupt = findPageLatestCorruption();
- foreach ( $corrupt as $id => $dummy ) {
- desyncFixPage( $id );
- }
- /*
- for ( $i=1; $i <= $maxPage; $i++ ) {
- desyncFixPage( $i );
- if ( !($i % $reportingInterval) ) {
- print "$i\n";
+
+ public function execute() {
+ global $slaveIndexes, $wgDBservers;
+ $slaveIndexes = array();
+ for ( $i = 1; $i < count( $wgDBservers ); $i++ ) {
+ if ( wfGetLB()->isNonZeroLoad( $i ) ) {
+ $slaveIndexes[] = $i;
+ }
}
- }*/
-}
-function findPageLatestCorruption() {
- $desync = array();
- $n = 0;
- $dbw = wfGetDB( DB_MASTER );
- $masterIDs = array();
- $res = $dbw->select( 'page', array( 'page_id', 'page_latest' ), array( 'page_id<6054123' ), __METHOD__ );
- print "Number of pages: " . $dbw->numRows( $res ) . "\n";
- while ( $row = $dbw->fetchObject( $res ) ) {
- $masterIDs[$row->page_id] = $row->page_latest;
- if ( !( ++$n % 10000 ) ) {
- print "$n\r";
+ if ( $this->hasArg() ) {
+ $this->desyncFixPage( $this->getArg() );
+ } else {
+ $dbw = wfGetDB( DB_MASTER );
+ $maxPage = $dbw->selectField( 'page', 'MAX(page_id)', false, __METHOD__ );
+ $corrupt = $this->findPageLatestCorruption();
+ foreach ( $corrupt as $id => $dummy ) {
+ $this->desyncFixPage( $id );
+ }
}
}
- print "\n";
- $dbw->freeResult( $res );
-
- global $slaveIndexes;
- foreach ( $slaveIndexes as $i ) {
- $db = wfGetDB( $i );
- $res = $db->select( 'page', array( 'page_id', 'page_latest' ), array( 'page_id<6054123' ), __METHOD__ );
- while ( $row = $db->fetchObject( $res ) ) {
- if ( isset( $masterIDs[$row->page_id] ) && $masterIDs[$row->page_id] != $row->page_latest ) {
- $desync[$row->page_id] = true;
- print $row->page_id . "\t";
+
+ /**
+ * Find all pages that have a corrupted page_latest
+ * @return array
+ */
+ private function findPageLatestCorruption() {
+ $desync = array();
+ $n = 0;
+ $dbw = wfGetDB( DB_MASTER );
+ $masterIDs = array();
+ $res = $dbw->select( 'page', array( 'page_id', 'page_latest' ), array( 'page_id<6054123' ), __METHOD__ );
+ $this->output( "Number of pages: " . $dbw->numRows( $res ) . "\n" );
+ foreach ( $res as $row ) {
+ $masterIDs[$row->page_id] = $row->page_latest;
+ if ( !( ++$n % 10000 ) ) {
+ $this->output( "$n\r" );
}
}
- $db->freeResult( $res );
+ $this->output( "\n" );
+ $dbw->freeResult( $res );
+
+ global $slaveIndexes;
+ foreach ( $slaveIndexes as $i ) {
+ $db = wfGetDB( $i );
+ $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;
+ $this->output( $row->page_id . "\t" );
+ }
+ }
+ $db->freeResult( $res );
+ }
+ $this->output( "\n" );
+ return $desync;
}
- print "\n";
- return $desync;
-}
-function desyncFixPage( $pageID ) {
- global $slaveIndexes;
- $fname = 'desyncFixPage';
+ /**
+ * Fix a broken page entry
+ * @param $pageID int The page_id to fix
+ */
+ private function desyncFixPage( $pageID ) {
+ global $slaveIndexes;
- # Check for a corrupted page_latest
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
- $realLatest = $dbw->selectField( 'page', 'page_latest', array( 'page_id' => $pageID ),
- $fname, 'FOR UPDATE' );
- #list( $masterFile, $masterPos ) = $dbw->getMasterPos();
- $found = false;
- foreach ( $slaveIndexes as $i ) {
- $db = wfGetDB( $i );
- /*
- if ( !$db->masterPosWait( $masterFile, $masterPos, 10 ) ) {
- echo "Slave is too lagged, aborting\n";
- $dbw->commit();
- sleep(10);
- return;
- }*/
- $latest = $db->selectField( 'page', 'page_latest', array( 'page_id' => $pageID ), $fname );
- $max = $db->selectField( 'revision', 'MAX(rev_id)', false, $fname );
- if ( $latest != $realLatest && $realLatest < $max ) {
- print "page_latest corrupted in page $pageID, server $i\n";
- $found = true;
- break;
+ # Check for a corrupted page_latest
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+ $realLatest = $dbw->selectField( 'page', 'page_latest', array( 'page_id' => $pageID ),
+ __METHOD__, 'FOR UPDATE' );
+ #list( $masterFile, $masterPos ) = $dbw->getMasterPos();
+ $found = false;
+ foreach ( $slaveIndexes as $i ) {
+ $db = wfGetDB( $i );
+ /*
+ if ( !$db->masterPosWait( $masterFile, $masterPos, 10 ) ) {
+ $this->output( "Slave is too lagged, aborting\n" );
+ $dbw->commit();
+ sleep(10);
+ return;
+ }*/
+ $latest = $db->selectField( 'page', 'page_latest', array( 'page_id' => $pageID ), __METHOD__ );
+ $max = $db->selectField( 'revision', 'MAX(rev_id)', false, __METHOD__ );
+ if ( $latest != $realLatest && $realLatest < $max ) {
+ $this->output( "page_latest corrupted in page $pageID, server $i\n" );
+ $found = true;
+ break;
+ }
+ }
+ if ( !$found ) {
+ $this->output( "page_id $pageID seems fine\n" );
+ $dbw->commit();
+ return;
}
- }
- if ( !$found ) {
- print "page_id $pageID seems fine\n";
- $dbw->commit();
- return;
- }
- # Find the missing revisions
- $res = $dbw->select( 'revision', array( 'rev_id' ), array( 'rev_page' => $pageID ),
- $fname, 'FOR UPDATE' );
- $masterIDs = array();
- while ( $row = $dbw->fetchObject( $res ) ) {
- $masterIDs[] = $row->rev_id;
- }
- $dbw->freeResult( $res );
+ # Find the missing revisions
+ $res = $dbw->select( 'revision', array( 'rev_id' ), array( 'rev_page' => $pageID ),
+ __METHOD__, 'FOR UPDATE' );
+ $masterIDs = array();
+ foreach ( $res as $row ) {
+ $masterIDs[] = $row->rev_id;
+ }
+ $dbw->freeResult( $res );
- $res = $db->select( 'revision', array( 'rev_id' ), array( 'rev_page' => $pageID ), $fname );
- $slaveIDs = array();
- while ( $row = $db->fetchObject( $res ) ) {
- $slaveIDs[] = $row->rev_id;
- }
- $db->freeResult( $res );
- if ( count( $masterIDs ) < count( $slaveIDs ) ) {
- $missingIDs = array_diff( $slaveIDs, $masterIDs );
- if ( count( $missingIDs ) ) {
- print "Found " . count( $missingIDs ) . " lost in master, copying from slave... ";
- $dbFrom = $db;
- $found = true;
- $toMaster = true;
- } else {
- $found = false;
+ $res = $db->select( 'revision', array( 'rev_id' ), array( 'rev_page' => $pageID ), __METHOD__ );
+ $slaveIDs = array();
+ foreach ( $res as $row ) {
+ $slaveIDs[] = $row->rev_id;
}
- } else {
- $missingIDs = array_diff( $masterIDs, $slaveIDs );
- if ( count( $missingIDs ) ) {
- print "Found " . count( $missingIDs ) . " missing revision(s), copying from master... ";
- $dbFrom = $dbw;
- $found = true;
- $toMaster = false;
+ $db->freeResult( $res );
+ if ( count( $masterIDs ) < count( $slaveIDs ) ) {
+ $missingIDs = array_diff( $slaveIDs, $masterIDs );
+ if ( count( $missingIDs ) ) {
+ $this->output( "Found " . count( $missingIDs ) . " lost in master, copying from slave... " );
+ $dbFrom = $db;
+ $found = true;
+ $toMaster = true;
+ } else {
+ $found = false;
+ }
} else {
- $found = false;
+ $missingIDs = array_diff( $masterIDs, $slaveIDs );
+ if ( count( $missingIDs ) ) {
+ $this->output( "Found " . count( $missingIDs ) . " missing revision(s), copying from master... " );
+ $dbFrom = $dbw;
+ $found = true;
+ $toMaster = false;
+ } else {
+ $found = false;
+ }
}
- }
- if ( $found ) {
- foreach ( $missingIDs as $rid ) {
- print "$rid ";
- # Revision
- $row = $dbFrom->selectRow( 'revision', '*', array( 'rev_id' => $rid ), $fname );
- if ( $toMaster ) {
- $id = $dbw->selectField( 'revision', 'rev_id', array( 'rev_id' => $rid ),
- $fname, 'FOR UPDATE' );
- if ( $id ) {
- echo "Revision already exists\n";
- $found = false;
- break;
+ if ( $found ) {
+ foreach ( $missingIDs as $rid ) {
+ $this->output( "$rid " );
+ # Revision
+ $row = $dbFrom->selectRow( 'revision', '*', array( 'rev_id' => $rid ), __METHOD__ );
+ if ( $toMaster ) {
+ $id = $dbw->selectField( 'revision', 'rev_id', array( 'rev_id' => $rid ),
+ __METHOD__, 'FOR UPDATE' );
+ if ( $id ) {
+ $this->output( "Revision already exists\n" );
+ $found = false;
+ break;
+ } else {
+ $dbw->insert( 'revision', get_object_vars( $row ), __METHOD__, 'IGNORE' );
+ }
} else {
- $dbw->insert( 'revision', get_object_vars( $row ), $fname, 'IGNORE' );
+ foreach ( $slaveIndexes as $i ) {
+ $db = wfGetDB( $i );
+ $db->insert( 'revision', get_object_vars( $row ), __METHOD__, 'IGNORE' );
+ }
}
- } else {
- foreach ( $slaveIndexes as $i ) {
- $db = wfGetDB( $i );
- $db->insert( 'revision', get_object_vars( $row ), $fname, 'IGNORE' );
+
+ # Text
+ $row = $dbFrom->selectRow( 'text', '*', array( 'old_id' => $row->rev_text_id ), __METHOD__ );
+ if ( $toMaster ) {
+ $dbw->insert( 'text', get_object_vars( $row ), __METHOD__, 'IGNORE' );
+ } else {
+ foreach ( $slaveIndexes as $i ) {
+ $db = wfGetDB( $i );
+ $db->insert( 'text', get_object_vars( $row ), __METHOD__, 'IGNORE' );
+ }
}
}
+ $this->output( "done\n" );
+ }
- # Text
- $row = $dbFrom->selectRow( 'text', '*', array( 'old_id' => $row->rev_text_id ), $fname );
+ if ( $found ) {
+ $this->output( "Fixing page_latest... " );
if ( $toMaster ) {
- $dbw->insert( 'text', get_object_vars( $row ), $fname, 'IGNORE' );
+ #$dbw->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), __METHOD__ );
} else {
foreach ( $slaveIndexes as $i ) {
$db = wfGetDB( $i );
- $db->insert( 'text', get_object_vars( $row ), $fname, 'IGNORE' );
+ $db->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), __METHOD__ );
}
}
+ $this->output( "done\n" );
}
- print "done\n";
- }
-
- if ( $found ) {
- print "Fixing page_latest... ";
- if ( $toMaster ) {
- #$dbw->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), $fname );
- } else {
- foreach ( $slaveIndexes as $i ) {
- $db = wfGetDB( $i );
- $db->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), $fname );
- }
- }
- print "done\n";
+ $dbw->commit();
}
- $dbw->commit();
}
-
+$maintClass = "FixSlaveDesync";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/fixTimestamps.php b/maintenance/fixTimestamps.php
index f6794141..ea102fb8 100644
--- a/maintenance/fixTimestamps.php
+++ b/maintenance/fixTimestamps.php
@@ -6,101 +6,117 @@
* and must bracket the damage. There must be a majority of good timestamps in the
* search period.
*
- * @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
+ *
* @ingroup Maintenance
*/
-
-require_once( 'commandLine.inc' );
-
-if ( count( $args ) < 3 ) {
- echo "Usage: php fixTimestamps.php <offset in hours> <start time> <end time>\n";
- exit(1);
-}
-
-$offset = $args[0] * 3600;
-$start = $args[1];
-$end = $args[2];
-$fname = 'fixTimestamps.php';
-$grace = 60; // maximum normal clock offset
-
-# Find bounding revision IDs
-$dbw = wfGetDB( DB_MASTER );
-$revisionTable = $dbw->tableName( 'revision' );
-$res = $dbw->query( "SELECT MIN(rev_id) as minrev, MAX(rev_id) as maxrev FROM $revisionTable " .
- "WHERE rev_timestamp BETWEEN '{$start}' AND '{$end}'", $fname );
-$row = $dbw->fetchObject( $res );
-
-if ( is_null( $row->minrev ) ) {
- echo "No revisions in search period.\n";
- exit(0);
-}
-
-$minRev = $row->minrev;
-$maxRev = $row->maxrev;
-
-# Select all timestamps and IDs
-$sql = "SELECT rev_id, rev_timestamp FROM $revisionTable " .
- "WHERE rev_id BETWEEN $minRev AND $maxRev";
-if ( $offset > 0 ) {
- $sql .= " ORDER BY rev_id DESC";
- $expectedSign = -1;
-} else {
- $expectedSign = 1;
-}
-
-$res = $dbw->query( $sql, $fname );
-
-$lastNormal = 0;
-$badRevs = array();
-$numGoodRevs = 0;
-
-while ( $row = $dbw->fetchObject( $res ) ) {
- $timestamp = wfTimestamp( TS_UNIX, $row->rev_timestamp );
- $delta = $timestamp - $lastNormal;
- $sign = $delta == 0 ? 0 : $delta / abs( $delta );
- if ( $sign == 0 || $sign == $expectedSign ) {
- // Monotonic change
- $lastNormal = $timestamp;
- ++ $numGoodRevs;
- continue;
- } elseif ( abs( $delta ) <= $grace ) {
- // Non-monotonic change within grace interval
- ++ $numGoodRevs;
- continue;
- } else {
- // Non-monotonic change larger than grace interval
- $badRevs[] = $row->rev_id;
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class FixTimestamps extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "";
+ $this->addArg( 'offset', '' );
+ $this->addArg( 'start', 'Starting timestamp' );
+ $this->addArg( 'end', 'Ending timestamp' );
}
-}
-$dbw->freeResult( $res );
-
-$numBadRevs = count( $badRevs );
-if ( $numBadRevs > $numGoodRevs ) {
- echo
-"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.
-
-If the offset is right, then increase the search interval until there are enough
-good revisions to provide a majority reference.
-";
-
- exit(1);
-} elseif ( $numBadRevs == 0 ) {
- echo "No bad revisions found.\n";
- exit(0);
+ public function execute() {
+ $offset = $this->getArg(0) * 3600;
+ $start = $this->getArg(1);
+ $end = $this->getArg(2);
+ $grace = 60; // maximum normal clock offset
+
+ # Find bounding revision IDs
+ $dbw = wfGetDB( DB_MASTER );
+ $revisionTable = $dbw->tableName( 'revision' );
+ $res = $dbw->query( "SELECT MIN(rev_id) as minrev, MAX(rev_id) as maxrev FROM $revisionTable " .
+ "WHERE rev_timestamp BETWEEN '{$start}' AND '{$end}'", __METHOD__ );
+ $row = $dbw->fetchObject( $res );
+
+ if ( is_null( $row->minrev ) ) {
+ $this->error( "No revisions in search period.", true );
+ }
+
+ $minRev = $row->minrev;
+ $maxRev = $row->maxrev;
+
+ # Select all timestamps and IDs
+ $sql = "SELECT rev_id, rev_timestamp FROM $revisionTable " .
+ "WHERE rev_id BETWEEN $minRev AND $maxRev";
+ if ( $offset > 0 ) {
+ $sql .= " ORDER BY rev_id DESC";
+ $expectedSign = -1;
+ } else {
+ $expectedSign = 1;
+ }
+
+ $res = $dbw->query( $sql, __METHOD__ );
+
+ $lastNormal = 0;
+ $badRevs = array();
+ $numGoodRevs = 0;
+
+ foreach ( $res as $row ) {
+ $timestamp = wfTimestamp( TS_UNIX, $row->rev_timestamp );
+ $delta = $timestamp - $lastNormal;
+ $sign = $delta == 0 ? 0 : $delta / abs( $delta );
+ if ( $sign == 0 || $sign == $expectedSign ) {
+ // Monotonic change
+ $lastNormal = $timestamp;
+ ++ $numGoodRevs;
+ continue;
+ } elseif ( abs( $delta ) <= $grace ) {
+ // Non-monotonic change within grace interval
+ ++ $numGoodRevs;
+ continue;
+ } else {
+ // Non-monotonic change larger than grace interval
+ $badRevs[] = $row->rev_id;
+ }
+ }
+ $dbw->freeResult( $res );
+
+ $numBadRevs = count( $badRevs );
+ if ( $numBadRevs > $numGoodRevs ) {
+ $this->error(
+ "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.
+
+ If the offset is right, then increase the search interval until there are enough
+ good revisions to provide a majority reference.", true );
+ } elseif ( $numBadRevs == 0 ) {
+ $this->output( "No bad revisions found.\n" );
+ exit(0);
+ }
+
+ $this->output( sprintf( "Fixing %d revisions (%.2f%% of revisions in search interval)\n",
+ $numBadRevs, $numBadRevs / ($numGoodRevs + $numBadRevs) * 100 ) );
+
+ $fixup = -$offset;
+ $sql = "UPDATE $revisionTable " .
+ "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" );
+ }
}
-printf( "Fixing %d revisions (%.2f%% of revisions in search interval)\n",
- $numBadRevs, $numBadRevs / ($numGoodRevs + $numBadRevs) * 100 );
-
-$fixup = -$offset;
-$sql = "UPDATE $revisionTable " .
- "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 ) . ')';
-//echo "$sql\n";
-$dbw->query( $sql, $fname );
-echo "Done\n";
-
-
+$maintClass = "FixTimestamps";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/fixUserRegistration.php b/maintenance/fixUserRegistration.php
index eb5b7f7d..d3305358 100644
--- a/maintenance/fixUserRegistration.php
+++ b/maintenance/fixUserRegistration.php
@@ -3,32 +3,53 @@
* Fix the user_registration field.
* In particular, for values which are NULL, set them to the date of the first edit
*
- * @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
+ *
* @ingroup Maintenance
*/
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$fname = 'fixUserRegistration.php';
-
-$dbr = wfGetDB( DB_SLAVE );
-$dbw = wfGetDB( DB_MASTER );
+class FixUserRegistration extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Fix the user_registration field";
+ }
-// Get user IDs which need fixing
-$res = $dbr->select( 'user', 'user_id', 'user_registration IS NULL', $fname );
+ public function execute() {
+ $dbr = wfGetDB( DB_SLAVE );
+ $dbw = wfGetDB( DB_MASTER );
-while ( $row = $dbr->fetchObject( $res ) ) {
- $id = $row->user_id;
- // Get first edit time
- $timestamp = $dbr->selectField( 'revision', 'MIN(rev_timestamp)', array( 'rev_user' => $id ), $fname );
- // Update
- if ( !empty( $timestamp ) ) {
- $dbw->update( 'user', array( 'user_registration' => $timestamp ), array( 'user_id' => $id ), $fname );
- print "$id $timestamp\n";
- } else {
- print "$id NULL\n";
+ // Get user IDs which need fixing
+ $res = $dbr->select( 'user', 'user_id', 'user_registration IS NULL', __METHOD__ );
+ foreach ( $res as $row ) {
+ $id = $row->user_id;
+ // Get first edit time
+ $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__ );
+ $this->output( "$id $timestamp\n" );
+ } else {
+ $this->output( "$id NULL\n" );
+ }
+ }
+ $this->output( "\n" );
}
}
-print "\n";
-
+$maintClass = "FixUserRegistration";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/fuzz-tester.php b/maintenance/fuzz-tester.php
index 9c1ddaff..6d8c57f2 100644
--- a/maintenance/fuzz-tester.php
+++ b/maintenance/fuzz-tester.php
@@ -138,7 +138,7 @@ Wiki configuration for testing:
}
// --------- End ---------
- Also add/change this in AdminSettings.php:
+ Also add/change this in LocalSettings.php:
// --------- Start ---------
$wgEnableProfileInfo = true;
$wgDBserver = "localhost"; // replace with DB server hostname
@@ -169,7 +169,7 @@ TODO:
/////////////////////////// COMMAND LINE HELP ////////////////////////////////////
// This is a command line script, load MediaWiki env (gives command line options);
-require('commandLine.inc');
+require_once( dirname(__FILE__) . '/commandLine.inc' );
// if the user asked for an explanation of command line options.
if ( isset( $options["help"] ) ) {
@@ -1320,7 +1320,7 @@ class viewPageTest extends pageTest {
"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", "tlh", "tr", "tt", "ty", "tyv", "udm",
+ "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),
@@ -2447,7 +2447,7 @@ function validateHTML($text) {
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();
+ exit(1);
}
curl_close ($ch);
diff --git a/maintenance/gearman/gearman.inc b/maintenance/gearman/gearman.inc
index a2a4018a..514b9bac 100644
--- a/maintenance/gearman/gearman.inc
+++ b/maintenance/gearman/gearman.inc
@@ -12,7 +12,7 @@ class MWGearmanJob extends Net_Gearman_Job_Common {
socket_close( $this->conn );
# Close some more sockets
- wfGetLBFactory()->shutdown();
+ LBFactory::destroyInstance();
global $wgMemc;
$wgMemc->disconnect_all();
diff --git a/maintenance/gearman/gearmanWorker.php b/maintenance/gearman/gearmanWorker.php
index 0b26ff9f..d6f3949f 100644
--- a/maintenance/gearman/gearmanWorker.php
+++ b/maintenance/gearman/gearmanWorker.php
@@ -4,6 +4,8 @@ $optionsWithArgs = array( 'fake-job', 'procs' );
require( dirname(__FILE__).'/../commandLine.inc' );
require( dirname(__FILE__).'/gearman.inc' );
+ini_set('memory_limit', '150M' );
+
if ( isset( $options['procs'] ) ) {
$procs = $options['procs'];
if ( $procs < 1 || $procs > 1000 ) {
diff --git a/maintenance/generateSitemap.php b/maintenance/generateSitemap.php
index 737c4eba..8ca79341 100644
--- a/maintenance/generateSitemap.php
+++ b/maintenance/generateSitemap.php
@@ -4,6 +4,21 @@ define( 'GS_TALK', -1 );
/**
* Creates a sitemap for the site
*
+ * 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
+ *
* @ingroup Maintenance
*
* @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
@@ -16,7 +31,9 @@ define( 'GS_TALK', -1 );
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
-class GenerateSitemap {
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class GenerateSitemap extends Maintenance {
/**
* The maximum amount of urls in a sitemap file
*
@@ -68,29 +85,7 @@ class GenerateSitemap {
*
* @var array
*/
- var $priorities = array(
- // Custom main namespaces
- GS_MAIN => '0.5',
- // Custom talk namesspaces
- GS_TALK => '0.1',
- // MediaWiki standard namespaces
- NS_MAIN => '1.0',
- NS_TALK => '0.1',
- NS_USER => '0.5',
- NS_USER_TALK => '0.1',
- NS_PROJECT => '0.5',
- NS_PROJECT_TALK => '0.1',
- NS_FILE => '0.5',
- NS_FILE_TALK => '0.1',
- NS_MEDIAWIKI => '0.0',
- NS_MEDIAWIKI_TALK => '0.1',
- NS_TEMPLATE => '0.0',
- NS_TEMPLATE_TALK => '0.1',
- NS_HELP => '0.5',
- NS_HELP_TALK => '0.1',
- NS_CATEGORY => '0.5',
- NS_CATEGORY_TALK => '0.1',
- );
+ var $priorities = array();
/**
* A one-dimensional array of namespaces in the wiki
@@ -129,36 +124,58 @@ class GenerateSitemap {
var $file;
/**
- * A resource pointing to php://stderr
- *
- * @var resource
+ * Constructor
*/
- var $stderr;
+ 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' .
+ "\n\t\tdefaults to current directory", false, true );
+ $this->addOption( 'server', "The protocol and server name to use in URLs, e.g.\n" .
+ "\t\thttp://en.wikipedia.org. This is sometimes necessary because\n" .
+ "\t\tserver name detection may fail in command line scripts.", false, true );
+ $this->addOption( 'compress', 'Compress the sitemap files, can take value yes|no, default yes', false, true );
+ }
/**
- * Constructor
- *
- * @param string $fspath The path to prepend to the filenames, used to
- * save them somewhere else than in the root directory
- * @param string $path The path to append to the domain name
- * @param bool $compress Whether to compress the sitemap files
+ * Execute
*/
- function GenerateSitemap( $fspath, $compress ) {
+ public function execute() {
global $wgScriptPath;
-
+ $this->setNamespacePriorities();
$this->url_limit = 50000;
$this->size_limit = pow( 2, 20 ) * 10;
- $this->fspath = self::init_path( $fspath );
-
- $this->compress = $compress;
-
- $this->stderr = fopen( 'php://stderr', 'wt' );
+ $this->fspath = self::init_path( $this->getOption( 'fspath', getcwd() ) );
+ $this->compress = $this->getOption( 'compress', 'yes' ) !== 'no';
$this->dbr = wfGetDB( DB_SLAVE );
$this->generateNamespaces();
$this->timestamp = wfTimestamp( TS_ISO_8601, wfTimestampNow() );
-
-
$this->findex = fopen( "{$this->fspath}sitemap-index-" . wfWikiID() . ".xml", 'wb' );
+ $this->main();
+ }
+
+ private function setNamespacePriorities() {
+ // Custom main namespaces
+ $this->priorities[GS_MAIN] = '0.5';
+ // Custom talk namesspaces
+ $this->priorities[GS_TALK] = '0.1';
+ // MediaWiki standard namespaces
+ $this->priorities[NS_MAIN] = '1.0';
+ $this->priorities[NS_TALK] = '0.1';
+ $this->priorities[NS_USER] = '0.5';
+ $this->priorities[NS_USER_TALK] = '0.1';
+ $this->priorities[NS_PROJECT] = '0.5';
+ $this->priorities[NS_PROJECT_TALK] = '0.1';
+ $this->priorities[NS_FILE] = '0.5';
+ $this->priorities[NS_FILE_TALK] = '0.1';
+ $this->priorities[NS_MEDIAWIKI] = '0.0';
+ $this->priorities[NS_MEDIAWIKI_TALK] = '0.1';
+ $this->priorities[NS_TEMPLATE] = '0.0';
+ $this->priorities[NS_TEMPLATE_TALK] = '0.1';
+ $this->priorities[NS_HELP] = '0.5';
+ $this->priorities[NS_HELP_TALK] = '0.1';
+ $this->priorities[NS_CATEGORY] = '0.5';
+ $this->priorities[NS_CATEGORY_TALK] = '0.1';
}
/**
@@ -170,7 +187,7 @@ class GenerateSitemap {
}
# Create directory if needed
if( $fspath && !is_dir( $fspath ) ) {
- mkdir( $fspath, 0755 ) or die("Can not create directory $fspath.\n");
+ wfMkdirParents( $fspath ) or die("Can not create directory $fspath.\n");
}
return realpath( $fspath ). DIRECTORY_SEPARATOR ;
@@ -180,8 +197,6 @@ class GenerateSitemap {
* Generate a one-dimensional array of existing namespaces
*/
function generateNamespaces() {
- $fname = 'GenerateSitemap::generateNamespaces';
-
// Only generate for specific namespaces if $wgSitemapNamespaces is an array.
global $wgSitemapNamespaces;
if( is_array( $wgSitemapNamespaces ) ) {
@@ -192,14 +207,14 @@ class GenerateSitemap {
$res = $this->dbr->select( 'page',
array( 'page_namespace' ),
array(),
- $fname,
+ __METHOD__,
array(
'GROUP BY' => 'page_namespace',
'ORDER BY' => 'page_namespace',
)
);
- while ( $row = $this->dbr->fetchObject( $res ) )
+ foreach ( $res as $row )
$this->namespaces[] = $row->page_namespace;
}
@@ -236,8 +251,6 @@ class GenerateSitemap {
* @return resource
*/
function getPageRes( $namespace ) {
- $fname = 'GenerateSitemap::getPageRes';
-
return $this->dbr->select( 'page',
array(
'page_namespace',
@@ -245,7 +258,7 @@ class GenerateSitemap {
'page_touched',
),
array( 'page_namespace' => $namespace ),
- $fname
+ __METHOD__
);
}
@@ -267,8 +280,8 @@ class GenerateSitemap {
$i = $smcount = 0;
$fns = $wgContLang->getFormattedNsText( $namespace );
- $this->debug( "$namespace ($fns)" );
- while ( $row = $this->dbr->fetchObject( $res ) ) {
+ $this->output( "$namespace ($fns)" );
+ foreach ( $res as $row ) {
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() );
@@ -278,7 +291,7 @@ class GenerateSitemap {
$this->file = $this->open( $this->fspath . $filename, 'wb' );
$this->write( $this->file, $this->openFile() );
fwrite( $this->findex, $this->indexEntry( $filename ) );
- $this->debug( "\t$this->fspath$filename" );
+ $this->output( "\t$this->fspath$filename\n" );
$length = $this->limit[0];
$i = 1;
}
@@ -450,13 +463,6 @@ class GenerateSitemap {
}
/**
- * Write a string to stderr followed by a UNIX newline
- */
- function debug( $str ) {
- fwrite( $this->stderr, "$str\n" );
- }
-
- /**
* Populate $this->limit
*/
function generateLimit( $namespace ) {
@@ -470,31 +476,5 @@ class GenerateSitemap {
}
}
-if ( in_array( '--help', $argv ) ) {
- echo <<<EOT
-Usage: php generateSitemap.php [options]
- --help show this message
-
- --fspath=<path> The file system path to save to, e.g /tmp/sitemap
- Saves to current directory if not given.
-
- --server=<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.
-
- --compress=[yes|no] compress the sitemap files, default yes
-
-EOT;
- die( -1 );
-}
-
-$optionsWithArgs = array( 'fspath', 'server', 'compress' );
-require_once( dirname( __FILE__ ) . '/commandLine.inc' );
-
-if ( isset( $options['server'] ) ) {
- $wgServer = $options['server'];
-}
-
-$gs = new GenerateSitemap( @$options['fspath'], @$options['compress'] !== 'no' );
-$gs->main();
-
+$maintClass = "GenerateSitemap";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/getLagTimes.php b/maintenance/getLagTimes.php
index 0f750caf..bc14ae71 100644
--- a/maintenance/getLagTimes.php
+++ b/maintenance/getLagTimes.php
@@ -1,29 +1,54 @@
<?php
/**
- * @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
+ *
* @ingroup Maintenance
*/
-require 'commandLine.inc';
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$lb = wfGetLB();
+class GetLagTimes extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Dump replication lag times";
+ }
+
+ public function execute() {
+ $lb = wfGetLB();
-if( $lb->getServerCount() == 1 ) {
- echo "This script dumps replication lag times, but you don't seem to have\n";
- echo "a multi-host db server configuration.\n";
-} else {
- $lags = $lb->getLagTimes();
- foreach( $lags as $n => $lag ) {
- $host = $lb->getServerName( $n );
- if( IP::isValid( $host ) ) {
- $ip = $host;
- $host = gethostbyaddr( $host );
+ if( $lb->getServerCount() == 1 ) {
+ $this->error( "This script dumps replication lag times, but you don't seem to have\n"
+ . "a multi-host db server configuration." );
} else {
- $ip = gethostbyname( $host );
+ $lags = $lb->getLagTimes();
+ foreach( $lags as $n => $lag ) {
+ $host = $lb->getServerName( $n );
+ if( IP::isValid( $host ) ) {
+ $ip = $host;
+ $host = gethostbyaddr( $host );
+ } else {
+ $ip = gethostbyname( $host );
+ }
+ $starLen = min( intval( $lag ), 40 );
+ $stars = str_repeat( '*', $starLen );
+ $this->output( sprintf( "%10s %20s %3d %s\n", $ip, $host, $lag, $stars ) );
+ }
}
- $starLen = min( intval( $lag ), 40 );
- $stars = str_repeat( '*', $starLen );
- printf( "%10s %20s %3d %s\n", $ip, $host, $lag, $stars );
}
}
+$maintClass = "GetLagTimes";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/getSlaveServer.php b/maintenance/getSlaveServer.php
index 25258267..eac97a59 100644
--- a/maintenance/getSlaveServer.php
+++ b/maintenance/getSlaveServer.php
@@ -2,27 +2,49 @@
/**
* This script reports the hostname of a slave server.
*
- * @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
+ *
* @ingroup Maintenance
*/
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-require_once( dirname(__FILE__).'/commandLine.inc' );
-
-if ( $wgAllDBsAreLocalhost ) {
- # Can't fool the backup script
- print "localhost\n";
- exit;
+class GetSlaveServer extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( "group", "Query group to check specifically" );
+ $this->mDescription = "Report the hostname of a slave server";
+ }
+ public function execute() {
+ global $wgAllDBsAreLocalhost;
+ if( $wgAllDBsAreLocalhost ) {
+ $host = 'localhost';
+ } else {
+ if( $this->hasOption('group') ) {
+ $db = wfGetDB( DB_SLAVE, $this->getOption('group') );
+ $host = $db->getServer();
+ } else {
+ $lb = wfGetLB();
+ $i = $lb->getReaderIndex();
+ $host = $lb->getServerName( $i );
+ }
+ }
+ $this->output( "$host\n" );
+ }
}
-if( isset( $options['group'] ) ) {
- $db = wfGetDB( DB_SLAVE, $options['group'] );
- $host = $db->getServer();
-} else {
- $lb = wfGetLB();
- $i = $lb->getReaderIndex();
- $host = $lb->getServerName( $i );
-}
-
-print "$host\n";
-
-
+$maintClass = "GetSlaveServer";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/getText.php b/maintenance/getText.php
new file mode 100644
index 00000000..6326267d
--- /dev/null
+++ b/maintenance/getText.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Outputs page text to stdout, useful for command-line editing automation.
+ * Example: php getText.php "page title" | sed -e '...' | php edit.php "page title"
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class GetTextMaint extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = 'Outputs page text to stdout';
+ $this->addOption( 'show-private', 'Show the text even if it\'s not available to the public' );
+ $this->addArg( 'title', 'Page title' );
+ }
+
+ public function execute() {
+ $this->db = wfGetDB( DB_SLAVE );
+
+ $titleText = $this->getArg( 0 );
+ $title = Title::newFromText( $titleText );
+ if ( !$title ) {
+ $this->error( "$titleText is not a valid title.\n", true );
+ }
+
+ $rev = Revision::newFromTitle( $title );
+ if ( !$rev ) {
+ $titleText = $title->getPrefixedText();
+ $this->error( "Page $titleText does not exist.\n", true );
+ }
+ $text = $rev->getText( $this->hasOption('show-private') ? Revision::RAW : Revision::FOR_PUBLIC );
+ if ( $text === false ) {
+ $titleText = $title->getPrefixedText();
+ $this->error( "Couldn't extract the text from $titleText.\n", true );
+ }
+ $this->output( $text );
+ }
+}
+
+$maintClass = "GetTextMaint";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/httpSessionDownload.php b/maintenance/httpSessionDownload.php
new file mode 100644
index 00000000..cab6e872
--- /dev/null
+++ b/maintenance/httpSessionDownload.php
@@ -0,0 +1,57 @@
+<?php
+/*
+ * Simple entry point to initiate a background download
+ *
+ * arguments:
+ * --sid {$session_id} --usk {$upload_session_key} --wiki {wfWikiId()}
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class HttpSessionDownload extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Simple entry point to initiate a background download";
+ $this->addOption( 'sid', 'Session ID', true, true );
+ $this->addOption( 'usk', 'Upload session key', true, true );
+ }
+
+ public function execute() {
+ wfProfileIn(__METHOD__);
+
+ //run the download:
+ Http::doSessionIdDownload( $this->getOption('sid'), $this->getOption('usk') );
+
+ // close up shop:
+ // Execute any deferred updates
+ wfDoUpdates();
+
+ // Log what the user did, for book-keeping purposes.
+ wfLogProfilingData();
+
+ // Shut down the database before exit
+ wfGetLBFactory()->shutdown();
+
+ wfProfileOut(__METHOD__);
+ }
+}
+
+$maintClass = "HttpSessionDownload";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/ibm_db2/README b/maintenance/ibm_db2/README
index 4a2c0f60..3c3f381c 100644
--- a/maintenance/ibm_db2/README
+++ b/maintenance/ibm_db2/README
@@ -1,41 +1,3 @@
-== Syntax differences between other databases and IBM DB2 ==
-{| border cellspacing=0 cellpadding=4
-!MySQL!!IBM DB2
-|-
-
-|SELECT 1 FROM $table LIMIT 1
-|SELECT COUNT(*) FROM SYSIBM.SYSTABLES ST
-WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema'
-|-
-|MySQL code tries to read one row and interprets lack of error as proof of existence.
-|DB2 code counts the number of TABLES of that name in the database. There ought to be 1 for it to exist.
-|-
-|BEGIN
-|(implicit)
-|-
-|TEXT
-|VARCHAR(255) or CLOB
-|-
-|TIMESTAMPTZ
-|TIMESTAMP
-|-
-|BYTEA
-|VARGRAPHIC(255)
-|-
-|DEFAULT nextval('some_kind_of_sequence'),
-|GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1),
-|-
-|CIDR
-|VARCHAR(255)
-|-
-|LIMIT 10
-|FETCH FIRST 10 ROWS ONLY
-|-
-|ROLLBACK TO
-|ROLLBACK TO SAVEPOINT
-|-
-|RELEASE
-|RELEASE SAVEPOINT
-|}
== See also ==
+*[http://www.mediawiki.org/wiki/Manual:IBM_DB2 Installation instructions]
*[http://ca.php.net/manual/en/function.db2-connect.php PHP Manual for DB2 functions] \ No newline at end of file
diff --git a/maintenance/ibm_db2/tables.sql b/maintenance/ibm_db2/tables.sql
index 5e91102d..71c161c6 100644
--- a/maintenance/ibm_db2/tables.sql
+++ b/maintenance/ibm_db2/tables.sql
@@ -5,98 +5,110 @@
-- not have to run it by itself unless doing a manual install.
-- This is the IBM DB2 version.
-- For information about each table, please see the notes in maintenance/tables.sql
--- Please make sure all dollar-quoting uses $mw$ at the start of the line
--- TODO: Change CHAR/SMALLINT to BOOL (still used in a non-bool fashion in PHP code)
-
-
-CREATE SEQUENCE user_user_id_seq AS INTEGER START WITH 0 INCREMENT BY 1;
-CREATE TABLE mwuser ( -- replace reserved word 'user'
- user_id INTEGER NOT NULL PRIMARY KEY, -- DEFAULT nextval('user_user_id_seq'),
+CREATE TABLE user (
+ user_id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 0),
user_name VARCHAR(255) NOT NULL UNIQUE,
user_real_name VARCHAR(255),
- user_password clob(1K),
- user_newpassword clob(1K),
- user_newpass_time TIMESTAMP,
+ user_password VARCHAR(1024),
+ user_newpassword VARCHAR(1024),
+ user_newpass_time TIMESTAMP(3),
user_token VARCHAR(255),
- user_email VARCHAR(255),
+ user_email VARCHAR(1024),
user_email_token VARCHAR(255),
- user_email_token_expires TIMESTAMP,
- user_email_authenticated TIMESTAMP,
- user_options CLOB(64K),
- user_touched TIMESTAMP,
- user_registration TIMESTAMP,
+ user_email_token_expires TIMESTAMP(3),
+ user_email_authenticated TIMESTAMP(3),
+ -- obsolete, replace by user_properties table
+ user_options CLOB(64K) INLINE LENGTH 4096,
+ user_touched TIMESTAMP(3),
+ user_registration TIMESTAMP(3),
user_editcount INTEGER
);
-CREATE INDEX user_email_token_idx ON mwuser (user_email_token);
+CREATE INDEX user_email_token_idx ON user (user_email_token);
+--leonsp:
+CREATE UNIQUE INDEX user_include_idx
+ ON user(user_id)
+ INCLUDE (user_name, user_real_name, user_password, user_newpassword, user_newpass_time, user_token,
+ user_email, user_email_token, user_email_token_expires, user_email_authenticated,
+ user_touched, user_registration, user_editcount);
-- Create a dummy user to satisfy fk contraints especially with revisions
-INSERT INTO mwuser
- VALUES (NEXTVAL FOR user_user_id_seq,'Anonymous','', NULL,NULL,CURRENT_TIMESTAMP,NULL, NULL,NULL,NULL,NULL, NULL,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,0);
+INSERT INTO user(
+user_name, user_real_name, user_password, user_newpassword, user_newpass_time,
+user_email, user_email_authenticated, user_options, user_token, user_registration, user_editcount)
+VALUES (
+'Anonymous','', NULL, NULL, CURRENT_TIMESTAMP,
+NULL, NULL, NULL, NULL, CURRENT_timestamp, 0);
+
CREATE TABLE user_groups (
- ug_user INTEGER REFERENCES mwuser(user_id) ON DELETE CASCADE,
+ ug_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE CASCADE,
ug_group VARCHAR(255) NOT NULL
);
CREATE UNIQUE INDEX user_groups_unique ON user_groups (ug_user, ug_group);
+--leonsp:
+CREATE UNIQUE INDEX user_groups_include_idx
+ ON user_groups(ug_user)
+ INCLUDE (ug_group);
+
CREATE TABLE user_newtalk (
- user_id INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
- user_ip VARCHAR(255),
- user_last_timestamp TIMESTAMP
+ -- registered users key
+ user_id BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE CASCADE,
+ -- anonymous users key
+ user_ip VARCHAR(40),
+ user_last_timestamp TIMESTAMP(3)
);
CREATE INDEX user_newtalk_id_idx ON user_newtalk (user_id);
CREATE INDEX user_newtalk_ip_idx ON user_newtalk (user_ip);
+--leonsp:
+CREATE UNIQUE INDEX user_newtalk_include_idx
+ ON user_newtalk(user_id, user_ip)
+ INCLUDE (user_last_timestamp);
-CREATE SEQUENCE page_page_id_seq;
CREATE TABLE page (
- page_id INTEGER NOT NULL PRIMARY KEY, -- DEFAULT NEXT VALUE FOR user_user_id_seq,
+ page_id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 0),
page_namespace SMALLINT NOT NULL,
page_title VARCHAR(255) NOT NULL,
- page_restrictions clob(1K),
+ page_restrictions VARCHAR(1024),
page_counter BIGINT NOT NULL DEFAULT 0,
page_is_redirect SMALLINT NOT NULL DEFAULT 0,
page_is_new SMALLINT NOT NULL DEFAULT 0,
page_random NUMERIC(15,14) NOT NULL,
- page_touched TIMESTAMP,
- page_latest INTEGER NOT NULL, -- FK?
- page_len INTEGER NOT NULL
+ page_touched TIMESTAMP(3),
+ page_latest BIGINT NOT NULL, -- FK?
+ page_len BIGINT NOT NULL
);
CREATE UNIQUE INDEX page_unique_name ON page (page_namespace, page_title);
---CREATE INDEX page_main_title ON page (page_title) WHERE page_namespace = 0;
---CREATE INDEX page_talk_title ON page (page_title) WHERE page_namespace = 1;
---CREATE INDEX page_user_title ON page (page_title) WHERE page_namespace = 2;
---CREATE INDEX page_utalk_title ON page (page_title) WHERE page_namespace = 3;
---CREATE INDEX page_project_title ON page (page_title) WHERE page_namespace = 4;
CREATE INDEX page_random_idx ON page (page_random);
CREATE INDEX page_len_idx ON page (page_len);
+--leonsp:
+CREATE UNIQUE INDEX page_id_include
+ ON page (page_id)
+ INCLUDE (page_namespace, page_title, page_restrictions, page_counter, page_is_redirect, page_is_new, page_random, page_touched, page_latest, page_len);
+CREATE UNIQUE INDEX page_name_include
+ ON page (page_namespace, page_title)
+ INCLUDE (page_id, page_restrictions, page_counter, page_is_redirect, page_is_new, page_random, page_touched, page_latest, page_len);
---CREATE FUNCTION page_deleted() RETURNS TRIGGER LANGUAGE plpgsql AS
---$mw$
---BEGIN
---DELETE FROM recentchanges WHERE rc_namespace = OLD.page_namespace AND rc_title = OLD.page_title;
---RETURN NULL;
---END;
---$mw$;
---CREATE TRIGGER page_deleted AFTER DELETE ON page
--- FOR EACH ROW EXECUTE PROCEDURE page_deleted();
-
-CREATE SEQUENCE rev_rev_id_val;
CREATE TABLE revision (
- rev_id INTEGER NOT NULL UNIQUE, --DEFAULT nextval('rev_rev_id_val'),
- rev_page INTEGER REFERENCES page (page_id) ON DELETE CASCADE,
- rev_text_id INTEGER, -- FK
- rev_comment clob(1K), -- changed from VARCHAR(255)
- rev_user INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE RESTRICT,
- rev_user_text VARCHAR(255) NOT NULL,
- rev_timestamp TIMESTAMP NOT NULL,
- rev_minor_edit SMALLINT NOT NULL DEFAULT 0,
- rev_deleted SMALLINT NOT NULL DEFAULT 0,
- rev_len INTEGER,
- rev_parent_id INTEGER
+ rev_id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 0),
+ rev_page BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES page (page_id) ON DELETE CASCADE,
+ rev_text_id BIGINT, -- FK
+ rev_comment VARCHAR(1024),
+ rev_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE RESTRICT,
+ rev_user_text VARCHAR(255) NOT NULL,
+ rev_timestamp TIMESTAMP(3) NOT NULL,
+ rev_minor_edit SMALLINT NOT NULL DEFAULT 0,
+ rev_deleted SMALLINT NOT NULL DEFAULT 0,
+ rev_len BIGINT,
+ rev_parent_id BIGINT DEFAULT NULL
);
CREATE UNIQUE INDEX revision_unique ON revision (rev_page, rev_id);
CREATE INDEX rev_text_id_idx ON revision (rev_text_id);
@@ -105,34 +117,41 @@ CREATE INDEX rev_user_idx ON revision (rev_user);
CREATE INDEX rev_user_text_idx ON revision (rev_user_text);
-CREATE SEQUENCE text_old_id_val;
-CREATE TABLE pagecontent ( -- replaces reserved word 'text'
- old_id INTEGER NOT NULL,
+
+CREATE TABLE text ( -- replaces reserved word 'text'
+ --old_id INTEGER NOT NULL,
+ old_id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 0),
--PRIMARY KEY DEFAULT nextval('text_old_id_val'),
- old_text CLOB(16M),
- old_flags clob(1K)
+ old_text CLOB(16M) INLINE LENGTH 4096,
+ old_flags VARCHAR(1024)
);
-CREATE SEQUENCE pr_id_val;
+
CREATE TABLE page_restrictions (
- pr_id INTEGER NOT NULL UNIQUE,
- --DEFAULT nextval('pr_id_val'),
- pr_page INTEGER NOT NULL
+ --pr_id INTEGER NOT NULL UNIQUE, --DEFAULT nextval('pr_id_val'),
+ --pr_id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 0),
+ pr_id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 0),
+ pr_page INTEGER NOT NULL DEFAULT 0,
--(used to be nullable)
- REFERENCES page (page_id) ON DELETE CASCADE,
- pr_type VARCHAR(255) NOT NULL,
- pr_level VARCHAR(255) NOT NULL,
+ -- REFERENCES page (page_id) ON DELETE CASCADE,
+ pr_type VARCHAR(60) NOT NULL,
+ pr_level VARCHAR(60) NOT NULL,
pr_cascade SMALLINT NOT NULL,
pr_user INTEGER,
- pr_expiry TIMESTAMP,
- PRIMARY KEY (pr_page, pr_type)
+ pr_expiry TIMESTAMP(3)
+ --PRIMARY KEY (pr_page, pr_type)
);
--ALTER TABLE page_restrictions ADD CONSTRAINT page_restrictions_pk PRIMARY KEY (pr_page,pr_type);
+CREATE UNIQUE INDEX pr_pagetype ON page_restrictions (pr_page,pr_type);
+CREATE INDEX pr_typelevel ON page_restrictions (pr_type,pr_level);
+CREATE INDEX pr_level ON page_restrictions (pr_level);
+CREATE INDEX pr_cascade ON page_restrictions (pr_cascade);
CREATE TABLE page_props (
- pp_page INTEGER NOT NULL REFERENCES page (page_id) ON DELETE CASCADE,
+ pp_page INTEGER NOT NULL DEFAULT 0,
+ -- REFERENCES page (page_id) ON DELETE CASCADE,
pp_propname VARCHAR(255) NOT NULL,
- pp_value CLOB(64K) NOT NULL,
+ pp_value CLOB(64K) INLINE LENGTH 4096 NOT NULL,
PRIMARY KEY (pp_page,pp_propname)
);
--ALTER TABLE page_props ADD CONSTRAINT page_props_pk PRIMARY KEY (pp_page,pp_propname);
@@ -143,19 +162,21 @@ CREATE INDEX page_props_propname ON page_props (pp_propname);
CREATE TABLE archive (
ar_namespace SMALLINT NOT NULL,
ar_title VARCHAR(255) NOT NULL,
- ar_text CLOB(16M),
- ar_page_id INTEGER,
- ar_parent_id INTEGER,
- ar_comment clob(1K),
- ar_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
+ ar_text CLOB(16M) INLINE LENGTH 4096,
+ ar_comment VARCHAR(1024),
+ ar_user BIGINT NOT NULL,
+ -- no foreign keys in MySQL
+ -- REFERENCES user(user_id) ON DELETE SET NULL,
ar_user_text VARCHAR(255) NOT NULL,
- ar_timestamp TIMESTAMP NOT NULL,
+ ar_timestamp TIMESTAMP(3) NOT NULL,
ar_minor_edit SMALLINT NOT NULL DEFAULT 0,
- ar_flags clob(1K),
+ ar_flags VARCHAR(1024),
ar_rev_id INTEGER,
ar_text_id INTEGER,
ar_deleted SMALLINT NOT NULL DEFAULT 0,
- ar_len INTEGER
+ ar_len INTEGER,
+ ar_page_id INTEGER,
+ ar_parent_id INTEGER
);
CREATE INDEX archive_name_title_timestamp ON archive (ar_namespace,ar_title,ar_timestamp);
CREATE INDEX archive_user_text ON archive (ar_user_text);
@@ -163,38 +184,49 @@ CREATE INDEX archive_user_text ON archive (ar_user_text);
CREATE TABLE redirect (
- rd_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
- rd_namespace SMALLINT NOT NULL,
- rd_title VARCHAR(255) NOT NULL
+ rd_from BIGINT NOT NULL PRIMARY KEY,
+ --REFERENCES page(page_id) ON DELETE CASCADE,
+ rd_namespace SMALLINT NOT NULL DEFAULT 0,
+ rd_title VARCHAR(255) NOT NULL DEFAULT '',
+ rd_interwiki varchar(32),
+ rd_fragment VARCHAR(255)
);
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,
+ pl_from BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES page(page_id) ON DELETE CASCADE,
pl_namespace SMALLINT NOT NULL,
pl_title VARCHAR(255) NOT NULL
);
CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title);
CREATE TABLE templatelinks (
- tl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
+ tl_from BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES page(page_id) ON DELETE CASCADE,
tl_namespace SMALLINT NOT NULL,
tl_title VARCHAR(255) NOT NULL
);
CREATE UNIQUE INDEX templatelinks_unique ON templatelinks (tl_namespace,tl_title,tl_from);
+CREATE UNIQUE INDEX tl_from_idx ON templatelinks (tl_from,tl_namespace,tl_title);
CREATE TABLE imagelinks (
- il_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
+ il_from BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES page(page_id) ON DELETE CASCADE,
il_to VARCHAR(255) NOT NULL
);
-CREATE UNIQUE INDEX il_from ON imagelinks (il_to,il_from);
+CREATE UNIQUE INDEX il_from_idx ON imagelinks (il_to,il_from);
+CREATE UNIQUE INDEX il_to_idx ON imagelinks (il_from,il_to);
CREATE TABLE categorylinks (
- cl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
+ cl_from BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES page(page_id) ON DELETE CASCADE,
cl_to VARCHAR(255) NOT NULL,
- cl_sortkey VARCHAR(255),
- cl_timestamp TIMESTAMP NOT NULL
+ -- cl_sortkey has to be at least 86 wide
+ -- in order to be compatible with the old MySQL schema from MW 1.10
+ cl_sortkey VARCHAR(86),
+ cl_timestamp TIMESTAMP(3) NOT NULL
);
CREATE UNIQUE INDEX cl_from ON categorylinks (cl_from, cl_to);
CREATE INDEX cl_sortkey ON categorylinks (cl_to, cl_sortkey, cl_from);
@@ -202,16 +234,38 @@ CREATE INDEX cl_sortkey ON categorylinks (cl_to, cl_sortkey, cl_from);
CREATE TABLE externallinks (
- el_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
- el_to VARCHAR(255) NOT NULL,
- el_index VARCHAR(255) NOT NULL
+ el_from BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES page(page_id) ON DELETE CASCADE,
+ el_to VARCHAR(1024) NOT NULL,
+ el_index VARCHAR(1024) NOT NULL
);
CREATE INDEX externallinks_from_to ON externallinks (el_from,el_to);
CREATE INDEX externallinks_index ON externallinks (el_index);
+
+--
+-- Track external user accounts, if ExternalAuth is used
+--
+CREATE TABLE external_user (
+ -- Foreign key to user_id
+ eu_local_id BIGINT NOT NULL PRIMARY KEY,
+
+ -- Some opaque identifier provided by the external database
+ eu_external_id VARCHAR(255) NOT NULL
+);
+CREATE UNIQUE INDEX eu_external_id_idx
+ ON external_user (eu_external_id)
+ INCLUDE (eu_local_id);
+CREATE UNIQUE INDEX eu_local_id_idx
+ ON external_user (eu_local_id)
+ INCLUDE (eu_external_id);
+
+
+
CREATE TABLE langlinks (
- ll_from INTEGER NOT NULL REFERENCES page (page_id) ON DELETE CASCADE,
- ll_lang VARCHAR(255),
+ ll_from BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES page (page_id) ON DELETE CASCADE,
+ ll_lang VARCHAR(20),
ll_title VARCHAR(255)
);
CREATE UNIQUE INDEX langlinks_unique ON langlinks (ll_from,ll_lang);
@@ -219,12 +273,13 @@ CREATE INDEX langlinks_lang_title ON langlinks (ll_lang,ll_title);
CREATE TABLE site_stats (
- ss_row_id INTEGER NOT NULL UNIQUE,
- ss_total_views INTEGER DEFAULT 0,
- ss_total_edits INTEGER DEFAULT 0,
- ss_good_articles INTEGER DEFAULT 0,
+ ss_row_id BIGINT NOT NULL UNIQUE,
+ ss_total_views BIGINT DEFAULT 0,
+ ss_total_edits BIGINT DEFAULT 0,
+ ss_good_articles BIGINT DEFAULT 0,
ss_total_pages INTEGER DEFAULT -1,
ss_users INTEGER DEFAULT -1,
+ ss_active_users INTEGER DEFAULT -1,
ss_admins INTEGER DEFAULT -1,
ss_images INTEGER DEFAULT 0
);
@@ -233,25 +288,27 @@ CREATE TABLE hitcounter (
hc_id BIGINT NOT NULL
);
-CREATE SEQUENCE ipblocks_ipb_id_val;
CREATE TABLE ipblocks (
ipb_id INTEGER NOT NULL PRIMARY KEY,
--DEFAULT nextval('ipblocks_ipb_id_val'),
- ipb_address VARCHAR(255),
- ipb_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
- ipb_by INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
+ ipb_address VARCHAR(1024),
+ ipb_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE SET NULL,
+ ipb_by BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE CASCADE,
ipb_by_text VARCHAR(255) NOT NULL DEFAULT '',
- ipb_reason VARCHAR(255) NOT NULL,
- ipb_timestamp TIMESTAMP NOT NULL,
+ ipb_reason VARCHAR(1024) NOT NULL,
+ ipb_timestamp TIMESTAMP(3) NOT NULL,
ipb_auto SMALLINT NOT NULL DEFAULT 0,
ipb_anon_only SMALLINT NOT NULL DEFAULT 0,
ipb_create_account SMALLINT NOT NULL DEFAULT 1,
ipb_enable_autoblock SMALLINT NOT NULL DEFAULT 1,
- ipb_expiry TIMESTAMP NOT NULL,
- ipb_range_start VARCHAR(255),
- ipb_range_end VARCHAR(255),
+ ipb_expiry TIMESTAMP(3) NOT NULL,
+ ipb_range_start VARCHAR(1024),
+ ipb_range_end VARCHAR(1024),
ipb_deleted SMALLINT NOT NULL DEFAULT 0,
- ipb_block_email SMALLINT NOT NULL DEFAULT 0
+ ipb_block_email SMALLINT NOT NULL DEFAULT 0,
+ ipb_allow_usertalk SMALLINT NOT NULL DEFAULT 0
);
CREATE INDEX ipb_address ON ipblocks (ipb_address);
@@ -262,18 +319,19 @@ CREATE INDEX ipb_range ON ipblocks (ipb_range_start,ipb_range_end);
CREATE TABLE image (
img_name VARCHAR(255) NOT NULL PRIMARY KEY,
- img_size INTEGER NOT NULL,
+ img_size BIGINT NOT NULL,
img_width INTEGER NOT NULL,
img_height INTEGER NOT NULL,
- img_metadata CLOB(16M) NOT NULL DEFAULT '',
+ img_metadata CLOB(16M) INLINE LENGTH 4096 NOT NULL DEFAULT '',
img_bits SMALLINT,
img_media_type VARCHAR(255),
img_major_mime VARCHAR(255) DEFAULT 'unknown',
- img_minor_mime VARCHAR(255) DEFAULT 'unknown',
- img_description clob(1K) NOT NULL DEFAULT '',
- img_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
+ img_minor_mime VARCHAR(32) DEFAULT 'unknown',
+ img_description VARCHAR(1024) NOT NULL DEFAULT '',
+ img_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE SET NULL,
img_user_text VARCHAR(255) NOT NULL DEFAULT '',
- img_timestamp TIMESTAMP,
+ img_timestamp TIMESTAMP(3),
img_sha1 VARCHAR(255) NOT NULL DEFAULT ''
);
CREATE INDEX img_size_idx ON image (img_size);
@@ -281,23 +339,24 @@ CREATE INDEX img_timestamp_idx ON image (img_timestamp);
CREATE INDEX img_sha1 ON image (img_sha1);
CREATE TABLE oldimage (
- oi_name VARCHAR(255) NOT NULL,
+ oi_name VARCHAR(255) NOT NULL DEFAULT '',
oi_archive_name VARCHAR(255) NOT NULL,
- oi_size INTEGER NOT NULL,
+ oi_size BIGINT NOT NULL,
oi_width INTEGER NOT NULL,
oi_height INTEGER NOT NULL,
oi_bits SMALLINT NOT NULL,
- oi_description clob(1K),
- oi_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
+ oi_description VARCHAR(1024),
+ oi_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE SET NULL,
oi_user_text VARCHAR(255) NOT NULL,
- oi_timestamp TIMESTAMP NOT NULL,
- oi_metadata CLOB(16M) NOT NULL DEFAULT '',
+ oi_timestamp TIMESTAMP(3) NOT NULL,
+ oi_metadata CLOB(16M) INLINE LENGTH 4096 NOT NULL DEFAULT '',
oi_media_type VARCHAR(255) ,
oi_major_mime VARCHAR(255) NOT NULL DEFAULT 'unknown',
oi_minor_mime VARCHAR(255) NOT NULL DEFAULT 'unknown',
oi_deleted SMALLINT NOT NULL DEFAULT 0,
- oi_sha1 VARCHAR(255) NOT NULL DEFAULT '',
- FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE
+ oi_sha1 VARCHAR(255) NOT NULL DEFAULT ''
+ --FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE
);
--ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascade FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE;
CREATE INDEX oi_name_timestamp ON oldimage (oi_name,oi_timestamp);
@@ -305,29 +364,31 @@ CREATE INDEX oi_name_archive_name ON oldimage (oi_name,oi_archive_name);
CREATE INDEX oi_sha1 ON oldimage (oi_sha1);
-CREATE SEQUENCE filearchive_fa_id_seq;
+
CREATE TABLE filearchive (
fa_id INTEGER NOT NULL PRIMARY KEY,
--PRIMARY KEY DEFAULT nextval('filearchive_fa_id_seq'),
fa_name VARCHAR(255) NOT NULL,
fa_archive_name VARCHAR(255),
fa_storage_group VARCHAR(255),
- fa_storage_key VARCHAR(255),
- fa_deleted_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
- fa_deleted_timestamp TIMESTAMP NOT NULL,
+ fa_storage_key VARCHAR(64) DEFAULT '',
+ fa_deleted_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE SET NULL,
+ fa_deleted_timestamp TIMESTAMP(3) NOT NULL,
fa_deleted_reason VARCHAR(255),
- fa_size INTEGER NOT NULL,
+ fa_size BIGINT NOT NULL,
fa_width INTEGER NOT NULL,
fa_height INTEGER NOT NULL,
- fa_metadata CLOB(16M) NOT NULL DEFAULT '',
+ fa_metadata CLOB(16M) INLINE LENGTH 4096 NOT NULL DEFAULT '',
fa_bits SMALLINT,
fa_media_type VARCHAR(255),
fa_major_mime VARCHAR(255) DEFAULT 'unknown',
fa_minor_mime VARCHAR(255) DEFAULT 'unknown',
- fa_description clob(1K) NOT NULL,
- fa_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
+ fa_description VARCHAR(1024) NOT NULL,
+ fa_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE SET NULL,
fa_user_text VARCHAR(255) NOT NULL,
- fa_timestamp TIMESTAMP,
+ fa_timestamp TIMESTAMP(3),
fa_deleted SMALLINT NOT NULL DEFAULT 0
);
CREATE INDEX fa_name_time ON filearchive (fa_name, fa_timestamp);
@@ -335,13 +396,14 @@ CREATE INDEX fa_dupe ON filearchive (fa_storage_group, fa_storage_key);
CREATE INDEX fa_notime ON filearchive (fa_deleted_timestamp);
CREATE INDEX fa_nouser ON filearchive (fa_deleted_user);
-CREATE SEQUENCE rc_rc_id_seq;
+
CREATE TABLE recentchanges (
rc_id INTEGER NOT NULL PRIMARY KEY,
--PRIMARY KEY DEFAULT nextval('rc_rc_id_seq'),
- rc_timestamp TIMESTAMP NOT NULL,
- rc_cur_time TIMESTAMP NOT NULL,
- rc_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
+ rc_timestamp TIMESTAMP(3) NOT NULL,
+ rc_cur_time TIMESTAMP(3) NOT NULL,
+ rc_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE SET NULL,
rc_user_text VARCHAR(255) NOT NULL,
rc_namespace SMALLINT NOT NULL,
rc_title VARCHAR(255) NOT NULL,
@@ -349,21 +411,23 @@ CREATE TABLE recentchanges (
rc_minor SMALLINT NOT NULL DEFAULT 0,
rc_bot SMALLINT NOT NULL DEFAULT 0,
rc_new SMALLINT NOT NULL DEFAULT 0,
- rc_cur_id INTEGER REFERENCES page(page_id) ON DELETE SET NULL,
- rc_this_oldid INTEGER NOT NULL,
- rc_last_oldid INTEGER NOT NULL,
+ rc_cur_id BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES page(page_id) ON DELETE SET NULL,
+ rc_this_oldid BIGINT NOT NULL,
+ rc_last_oldid BIGINT NOT NULL,
rc_type SMALLINT NOT NULL DEFAULT 0,
rc_moved_to_ns SMALLINT,
rc_moved_to_title VARCHAR(255),
rc_patrolled SMALLINT NOT NULL DEFAULT 0,
- rc_ip VARCHAR(255), -- was CIDR type
+ rc_ip VARCHAR(40), -- was CIDR type
rc_old_len INTEGER,
rc_new_len INTEGER,
rc_deleted SMALLINT NOT NULL DEFAULT 0,
- rc_logid INTEGER NOT NULL DEFAULT 0,
+ rc_logid BIGINT NOT NULL DEFAULT 0,
rc_log_type VARCHAR(255),
rc_log_action VARCHAR(255),
- rc_params CLOB(64K)
+ rc_params CLOB(64K) INLINE LENGTH 4096
+
);
CREATE INDEX rc_timestamp ON recentchanges (rc_timestamp);
CREATE INDEX rc_namespace_title ON recentchanges (rc_namespace, rc_title);
@@ -374,26 +438,27 @@ CREATE INDEX rc_ip ON recentchanges (rc_ip);
CREATE TABLE watchlist (
- wl_user INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
+ wl_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE CASCADE,
wl_namespace SMALLINT NOT NULL DEFAULT 0,
wl_title VARCHAR(255) NOT NULL,
- wl_notificationtimestamp TIMESTAMP
+ wl_notificationtimestamp TIMESTAMP(3)
);
CREATE UNIQUE INDEX wl_user_namespace_title ON watchlist (wl_namespace, wl_title, wl_user);
CREATE TABLE math (
- math_inputhash VARGRAPHIC(255) NOT NULL UNIQUE,
- math_outputhash VARGRAPHIC(255) NOT NULL,
+ math_inputhash VARCHAR(16) FOR BIT DATA NOT NULL UNIQUE,
+ math_outputhash VARCHAR(16) FOR BIT DATA NOT NULL,
math_html_conservativeness SMALLINT NOT NULL,
- math_html VARCHAR(255),
- math_mathml VARCHAR(255)
+ math_html CLOB(64K) INLINE LENGTH 4096,
+ math_mathml CLOB(64K) INLINE LENGTH 4096
);
CREATE TABLE interwiki (
- iw_prefix VARCHAR(255) NOT NULL UNIQUE,
- iw_url CLOB(64K) NOT NULL,
+ iw_prefix VARCHAR(32) NOT NULL UNIQUE,
+ iw_url CLOB(64K) INLINE LENGTH 4096 NOT NULL,
iw_local SMALLINT NOT NULL,
iw_trans SMALLINT NOT NULL DEFAULT 0
);
@@ -401,8 +466,8 @@ CREATE TABLE interwiki (
CREATE TABLE querycache (
qc_type VARCHAR(255) NOT NULL,
- qc_value INTEGER NOT NULL,
- qc_namespace SMALLINT NOT NULL,
+ qc_value BIGINT NOT NULL,
+ qc_namespace INTEGER NOT NULL,
qc_title VARCHAR(255) NOT NULL
);
CREATE INDEX querycache_type_value ON querycache (qc_type, qc_value);
@@ -411,13 +476,13 @@ CREATE INDEX querycache_type_value ON querycache (qc_type, qc_value);
CREATE TABLE querycache_info (
qci_type VARCHAR(255) UNIQUE NOT NULL,
- qci_timestamp TIMESTAMP
+ qci_timestamp TIMESTAMP(3)
);
CREATE TABLE querycachetwo (
qcc_type VARCHAR(255) NOT NULL,
- qcc_value INTEGER NOT NULL DEFAULT 0,
+ qcc_value BIGINT NOT NULL DEFAULT 0,
qcc_namespace INTEGER NOT NULL DEFAULT 0,
qcc_title VARCHAR(255) NOT NULL DEFAULT '',
qcc_namespacetwo INTEGER NOT NULL DEFAULT 0,
@@ -429,8 +494,8 @@ CREATE INDEX querycachetwo_titletwo ON querycachetwo (qcc_type,qcc_namespacetw
CREATE TABLE objectcache (
keyname VARCHAR(255) NOT NULL UNIQUE, -- was nullable
- value CLOB(16M) NOT NULL DEFAULT '',
- exptime TIMESTAMP NOT NULL
+ value CLOB(16M) INLINE LENGTH 4096 NOT NULL DEFAULT '',
+ exptime TIMESTAMP(3) NOT NULL
);
CREATE INDEX objectcacache_exptime ON objectcache (exptime);
@@ -438,49 +503,57 @@ CREATE INDEX objectcacache_exptime ON objectcache (exptime);
CREATE TABLE transcache (
tc_url VARCHAR(255) NOT NULL UNIQUE,
- tc_contents VARCHAR(255) NOT NULL,
- tc_time TIMESTAMP NOT NULL
+ tc_contents CLOB(64K) INLINE LENGTH 4096 NOT NULL,
+ tc_time TIMESTAMP(3) NOT NULL
);
-CREATE SEQUENCE log_log_id_seq;
+
CREATE TABLE logging (
- log_id INTEGER NOT NULL PRIMARY KEY,
+ log_id BIGINT NOT NULL PRIMARY KEY,
--PRIMARY KEY DEFAULT nextval('log_log_id_seq'),
- log_type VARCHAR(255) NOT NULL,
- log_action VARCHAR(255) NOT NULL,
- log_timestamp TIMESTAMP NOT NULL,
- log_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
- log_namespace SMALLINT NOT NULL,
- log_title VARCHAR(255) NOT NULL,
- log_comment VARCHAR(255),
- log_params CLOB(64K),
- log_deleted SMALLINT NOT NULL DEFAULT 0
+ log_type VARCHAR(32) NOT NULL,
+ log_action VARCHAR(32) NOT NULL,
+ log_timestamp TIMESTAMP(3) NOT NULL,
+ log_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE SET NULL,
+ -- Name of the user who performed this action
+ log_user_text VARCHAR(255) NOT NULL default '',
+ log_namespace SMALLINT NOT NULL,
+ log_title VARCHAR(255) NOT NULL,
+ log_page BIGINT,
+ log_comment VARCHAR(255),
+ log_params CLOB(64K) INLINE LENGTH 4096,
+ log_deleted SMALLINT NOT NULL DEFAULT 0
);
CREATE INDEX logging_type_name ON logging (log_type, log_timestamp);
CREATE INDEX logging_user_time ON logging (log_timestamp, log_user);
CREATE INDEX logging_page_time ON logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX log_user_type_time ON logging (log_user, log_type, log_timestamp);
+CREATE INDEX log_page_id_time ON logging (log_page,log_timestamp);
+
+
-CREATE SEQUENCE trackbacks_tb_id_seq;
CREATE TABLE trackbacks (
tb_id INTEGER NOT NULL PRIMARY KEY,
--PRIMARY KEY DEFAULT nextval('trackbacks_tb_id_seq'),
- tb_page INTEGER REFERENCES page(page_id) ON DELETE CASCADE,
+ -- foreign key also in MySQL
+ tb_page INTEGER REFERENCES page(page_id) ON DELETE CASCADE,
tb_title VARCHAR(255) NOT NULL,
- tb_url CLOB(64K) NOT NULL,
- tb_ex VARCHAR(255),
+ tb_url CLOB(64K) INLINE LENGTH 4096 NOT NULL,
+ tb_ex CLOB(64K) INLINE LENGTH 4096,
tb_name VARCHAR(255)
);
CREATE INDEX trackback_page ON trackbacks (tb_page);
-CREATE SEQUENCE job_job_id_seq;
+
CREATE TABLE job (
- job_id INTEGER NOT NULL PRIMARY KEY,
+ job_id BIGINT NOT NULL PRIMARY KEY,
--PRIMARY KEY DEFAULT nextval('job_job_id_seq'),
job_cmd VARCHAR(255) NOT NULL,
job_namespace SMALLINT NOT NULL,
job_title VARCHAR(255) NOT NULL,
- job_params CLOB(64K) NOT NULL
+ job_params CLOB(64K) INLINE LENGTH 4096 NOT NULL
);
CREATE INDEX job_cmd_namespace_title ON job (job_cmd, job_namespace, job_title);
@@ -504,7 +577,7 @@ CREATE INDEX job_cmd_namespace_title ON job (job_cmd, job_namespace, job_title);
-- FOR EACH ROW EXECUTE PROCEDURE ts2_page_title();
---ALTER TABLE pagecontent ADD textvector tsvector;
+--ALTER TABLE text ADD textvector tsvector;
--CREATE FUNCTION ts2_page_text() RETURNS TRIGGER LANGUAGE plpgsql AS
--$mw$
--BEGIN
@@ -517,14 +590,14 @@ CREATE INDEX job_cmd_namespace_title ON job (job_cmd, job_namespace, job_title);
--END;
--$mw$;
---CREATE TRIGGER ts2_page_text BEFORE INSERT OR UPDATE ON pagecontent
+--CREATE TRIGGER ts2_page_text BEFORE INSERT OR UPDATE ON text
-- FOR EACH ROW EXECUTE PROCEDURE ts2_page_text();
-- These are added by the setup script due to version compatibility issues
-- If using 8.1, we switch from "gin" to "gist"
--CREATE INDEX ts2_page_title ON page USING gin(titlevector);
---CREATE INDEX ts2_page_text ON pagecontent USING gin(textvector);
+--CREATE INDEX ts2_page_text ON text USING gin(textvector);
--TODO
--CREATE FUNCTION add_interwiki (TEXT,INT,SMALLINT) RETURNS INT LANGUAGE SQL AS
@@ -536,7 +609,7 @@ CREATE INDEX job_cmd_namespace_title ON job (job_cmd, job_namespace, job_title);
-- hack implementation
-- should be replaced with OmniFind, Contains(), etc
CREATE TABLE searchindex (
- si_page int NOT NULL,
+ si_page BIGINT NOT NULL,
si_title varchar(255) NOT NULL default '',
si_text clob NOT NULL
);
@@ -552,13 +625,14 @@ CREATE TABLE profiling (
CREATE UNIQUE INDEX pf_name_server ON profiling (pf_name, pf_server);
CREATE TABLE protected_titles (
- pt_namespace SMALLINT NOT NULL,
+ pt_namespace INTEGER NOT NULL,
pt_title VARCHAR(255) NOT NULL,
- pt_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
- pt_reason clob(1K),
- pt_timestamp TIMESTAMP NOT NULL,
- pt_expiry TIMESTAMP ,
- pt_create_perm VARCHAR(255) NOT NULL DEFAULT ''
+ pt_user BIGINT NOT NULL DEFAULT 0,
+ -- REFERENCES user(user_id) ON DELETE SET NULL,
+ pt_reason VARCHAR(1024),
+ pt_timestamp TIMESTAMP(3) NOT NULL,
+ pt_expiry TIMESTAMP(3) ,
+ pt_create_perm VARCHAR(60) NOT NULL DEFAULT ''
);
CREATE UNIQUE INDEX protected_titles_unique ON protected_titles(pt_namespace, pt_title);
@@ -568,7 +642,7 @@ CREATE TABLE updatelog (
ul_key VARCHAR(255) NOT NULL PRIMARY KEY
);
-CREATE SEQUENCE category_id_seq;
+
CREATE TABLE category (
cat_id INTEGER NOT NULL PRIMARY KEY,
--PRIMARY KEY DEFAULT nextval('category_id_seq'),
@@ -581,24 +655,101 @@ CREATE TABLE category (
CREATE UNIQUE INDEX category_title ON category(cat_title);
CREATE INDEX category_pages ON category(cat_pages);
+-- added for 1.15
+
+-- A table to track tags for revisions, logs and recent changes.
+CREATE TABLE change_tag (
+ ct_rc_id INTEGER,
+ ct_log_id INTEGER,
+ ct_rev_id INTEGER,
+ ct_tag varchar(255) NOT NULL,
+ ct_params CLOB(64K) INLINE LENGTH 4096
+);
+CREATE UNIQUE INDEX change_tag_rc_tag ON change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX change_tag_log_tag ON change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX 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 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
+CREATE TABLE tag_summary (
+ ts_rc_id INTEGER,
+ ts_log_id INTEGER,
+ ts_rev_id INTEGER,
+ ts_tags CLOB(64K) INLINE LENGTH 4096 NOT NULL
+);
+CREATE UNIQUE INDEX tag_summary_rc_id ON tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX tag_summary_log_id ON tag_summary (ts_log_id);
+CREATE UNIQUE INDEX tag_summary_rev_id ON tag_summary (ts_rev_id);
+
+
+CREATE TABLE valid_tag (
+ vt_tag varchar(255) NOT NULL PRIMARY KEY
+);
+
+--
+-- User preferences and perhaps other fun stuff. :)
+-- Replaces the old user.user_options blob, with a couple nice properties:
+--
+-- 1) We only store non-default settings, so changes to the defaults
+-- are now reflected for everybody, not just new accounts.
+-- 2) We can more easily do bulk lookups, statistics, or modifications of
+-- saved options since it's a sane table structure.
+--
+CREATE TABLE user_properties (
+ -- Foreign key to user.user_id
+ up_user BIGINT NOT NULL,
+
+ -- Name of the option being saved. This is indexed for bulk lookup.
+ up_property VARCHAR(32) FOR BIT DATA NOT NULL,
+
+ -- Property value as a string.
+ up_value CLOB(64K) INLINE LENGTH 4096
+);
+CREATE UNIQUE INDEX user_properties_user_property ON user_properties (up_user,up_property);
+CREATE INDEX user_properties_property ON user_properties (up_property);
+
+CREATE TABLE log_search (
+ -- The type of ID (rev ID, log ID, rev TIMESTAMP(3), username)
+ ls_field VARCHAR(32) FOR BIT DATA NOT NULL,
+ -- The value of the ID
+ ls_value varchar(255) NOT NULL,
+ -- Key to log_id
+ ls_log_id BIGINT NOT NULL default 0
+);
+CREATE UNIQUE INDEX ls_field_val ON log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX ls_log_id ON log_search (ls_log_id);
+
CREATE TABLE mediawiki_version (
- type VARCHAR(255) NOT NULL,
- mw_version VARCHAR(255) NOT NULL,
- notes VARCHAR(255) ,
+ type VARCHAR(1024) NOT NULL,
+ mw_version VARCHAR(1024) NOT NULL,
+ notes VARCHAR(1024) ,
- pg_version VARCHAR(255) ,
- pg_dbname VARCHAR(255) ,
- pg_user VARCHAR(255) ,
- pg_port VARCHAR(255) ,
- mw_schema VARCHAR(255) ,
- ts2_schema VARCHAR(255) ,
- ctype VARCHAR(255) ,
+ pg_version VARCHAR(1024) ,
+ pg_dbname VARCHAR(1024) ,
+ pg_user VARCHAR(1024) ,
+ pg_port VARCHAR(1024) ,
+ mw_schema VARCHAR(1024) ,
+ ts2_schema VARCHAR(1024) ,
+ ctype VARCHAR(1024) ,
- sql_version VARCHAR(255) ,
- sql_date VARCHAR(255) ,
- cdate TIMESTAMP NOT NULL DEFAULT CURRENT TIMESTAMP
+ sql_version VARCHAR(1024) ,
+ sql_date VARCHAR(1024) ,
+ cdate TIMESTAMP(3) NOT NULL DEFAULT CURRENT TIMESTAMP
);
INSERT INTO mediawiki_version (type,mw_version,sql_version,sql_date)
VALUES ('Creation','??','$LastChangedRevision: 34049 $','$LastChangedDate: 2008-04-30 10:20:36 -0400 (Wed, 30 Apr 2008) $');
+-- Table for storing localisation data
+CREATE TABLE l10n_cache (
+ -- Language code
+ lc_lang VARCHAR(32) NOT NULL,
+ -- Cache key
+ lc_key VARCHAR(255) NOT NULL,
+ -- Value
+ lc_value CLOB(16M) INLINE LENGTH 4096 NOT NULL
+);
+CREATE INDEX lc_lang_key ON l10n_cache (lc_lang, lc_key);
+
diff --git a/maintenance/importDump.php b/maintenance/importDump.php
index eb51126a..714d76d8 100644
--- a/maintenance/importDump.php
+++ b/maintenance/importDump.php
@@ -24,7 +24,7 @@
$optionsWithArgs = array( 'report' );
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
/**
* @ingroup Maintenance
@@ -115,10 +115,19 @@ class BackupReader {
}
function importFromFile( $filename ) {
+ $t = true;
if( preg_match( '/\.gz$/', $filename ) ) {
$filename = 'compress.zlib://' . $filename;
}
- $file = fopen( $filename, 'rt' );
+ elseif( preg_match( '/\.bz2$/', $filename ) ) {
+ $filename = 'compress.bzip2://' . $filename;
+ }
+ elseif( preg_match( '/\.7z$/', $filename ) ) {
+ $filename = 'mediawiki.compress.7z://' . $filename;
+ $t = false;
+ }
+
+ $file = fopen( $filename, $t ? 'rt' : 't' ); //our 7zip wrapper uses popen, which seems not to like two-letter modes
return $this->importFromHandle( $file );
}
diff --git a/maintenance/importImages.inc.php b/maintenance/importImages.inc
index 290f3c07..7bb50eb8 100644
--- a/maintenance/importImages.inc.php
+++ b/maintenance/importImages.inc
@@ -6,6 +6,7 @@
* @file
* @ingroup Maintenance
* @author Rob Church <robchur@gmail.com>
+ * @author Mij <mij@bitchx.it>
*/
/**
@@ -18,6 +19,7 @@
function findFiles( $dir, $exts ) {
if( is_dir( $dir ) ) {
if( $dhl = opendir( $dir ) ) {
+ $files = array();
while( ( $file = readdir( $dhl ) ) !== false ) {
if( is_file( $dir . '/' . $file ) ) {
list( /* $name */, $ext ) = splitFilename( $dir . '/' . $file );
@@ -27,10 +29,10 @@ function findFiles( $dir, $exts ) {
}
return $files;
} else {
- return false;
+ return array();
}
} else {
- return false;
+ return array();
}
}
@@ -85,4 +87,26 @@ function findAuxFile( $file, $auxExtension, $maxStrip = 1 ) {
}
return false;
-} \ No newline at end of file
+}
+
+# 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';
+ $body = Http::get($url);
+ if (preg_match('#<ii comment="([^"]*)" />#', $body, $matches) == 0) {
+ return false;
+ }
+
+ return html_entity_decode( $matches[1] );
+}
+
+function getFileUserFromSourceWiki($wiki_host, $file) {
+ $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;
+ }
+
+ return html_entity_decode( $matches[1] );
+}
+
diff --git a/maintenance/importImages.php b/maintenance/importImages.php
index 7997b0d5..f0dd388a 100644
--- a/maintenance/importImages.php
+++ b/maintenance/importImages.php
@@ -2,17 +2,25 @@
/**
* Maintenance script to import one or more images from the local file system into
- * the wiki without using the web-based interface
+ * the wiki without using the web-based interface.
+ *
+ * "Smart import" additions:
+ * - aim: preserve the essential metadata (user, description) when importing medias 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.
+ * - commit the fetched metadata to the destination wiki while submitting.
*
* @file
* @ingroup Maintenance
* @author Rob Church <robchur@gmail.com>
+ * @author Mij <mij@bitchx.it>
*/
-$optionsWithArgs = array( 'extensions', 'comment', 'comment-file', 'comment-ext', 'user', 'license' );
-require_once( 'commandLine.inc' );
-require_once( 'importImages.inc.php' );
-$added = $skipped = $overwritten = 0;
+$optionsWithArgs = array( 'extensions', 'comment', 'comment-file', 'comment-ext', 'user', 'license', 'sleep', 'limit', 'from', 'source-wiki-url' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
+require_once( dirname(__FILE__) . '/importImages.inc' );
+$processed = $added = $ignored = $skipped = $overwritten = $failed = 0;
echo( "Import Images\n\n" );
@@ -25,7 +33,7 @@ if( count( $args ) > 0 ) {
if (isset($options['protect']) && isset($options['unprotect']))
die("Cannot specify both protect and unprotect. Only 1 is allowed.\n");
- if ($options['protect'] == 1)
+if (isset($options['protect']) && $options['protect'] == 1)
die("You must specify a protection option.\n");
# Prepare the list of allowed extensions
@@ -45,8 +53,27 @@ if( count( $args ) > 0 ) {
$user = User::newFromName( 'Maintenance script' );
$wgUser = $user;
+ # Get block check. If a value is given, this specified how often the check is performed
+ if ( isset( $options['check-userblock'] ) ) {
+ if ( !$options['check-userblock'] ) $checkUserBlock = 1;
+ else $checkUserBlock = (int)$options['check-userblock'];
+ } else {
+ $checkUserBlock = false;
+ }
+
+ # Get --from
+ $from = @$options['from'];
+
+ # Get sleep time.
+ $sleep = @$options['sleep'];
+ if ( $sleep ) $sleep = (int)$sleep;
+
+ # Get limit number
+ $limit = @$options['limit'];
+ if ( $limit ) $limit = (int)$limit;
+
# Get the upload comment
- $comment = 'Importing image file';
+ $comment = NULL;
if ( isset( $options['comment-file'] ) ) {
$comment = file_get_contents( $options['comment-file'] );
@@ -76,6 +103,23 @@ if( count( $args ) > 0 ) {
continue;
}
+ if ( $from ) {
+ if ( $from == $title->getDBkey() ) {
+ $from = NULL;
+ } else {
+ $ignored++;
+ continue;
+ }
+ }
+
+ if ( $checkUserBlock && ( ( $processed % $checkUserBlock ) == 0 ) ) {
+ $user->clearInstanceCache( 'name' ); //reload from DB!
+ if ( $user->isBlocked() ) {
+ echo( $user->getName() . " was blocked! Aborting.\n" );
+ break;
+ }
+ }
+
# Check existence
$image = wfLocalFile( $title );
if( $image->exists() ) {
@@ -88,36 +132,73 @@ if( count( $args ) > 0 ) {
continue;
}
} else {
- echo( "Importing {$base}..." );
- $svar = 'added';
- }
+ if ( isset( $options['skip-dupes'] ) ) {
+ $repo = $image->getRepo();
+ $sha1 = File::sha1Base36( $file ); #XXX: we end up calculating this again when actually uploading. that sucks.
- # Find comment text
- $commentText = false;
+ $dupes = $repo->findBySha1( $sha1 );
- if ( $commentExt ) {
- $f = findAuxFile( $file, $commentExt );
- if ( !$f ) {
- echo( " No comment file with extension {$commentExt} found for {$file}, using default comment. " );
- } else {
- $commentText = file_get_contents( $f );
- if ( !$f ) {
- echo( " Failed to load comment file {$f}, using default comment. " );
+ if ( $dupes ) {
+ echo( "{$base} already exists as " . $dupes[0]->getName() . ", skipping\n" );
+ $skipped++;
+ continue;
}
}
- }
- if ( !$commentText ) {
- $commentText = $comment;
+ echo( "Importing {$base}..." );
+ $svar = 'added';
}
+ if (isset( $options['source-wiki-url'])) {
+ /* find comment text directly from source wiki, through MW's API */
+ $real_comment = getFileCommentFromSourceWiki($options['source-wiki-url'], $base);
+ if ($real_comment === false)
+ $commentText = $comment;
+ else
+ $commentText = $real_comment;
+
+ /* find user directly from source wiki, through MW's API */
+ $real_user = getFileUserFromSourceWiki($options['source-wiki-url'], $base);
+ if ($real_user === false) {
+ $wgUser = $user;
+ } else {
+ $wgUser = User::newFromName($real_user);
+ if ($wgUser === false) {
+ # user does not exist in target wiki
+ echo ("failed: user '$real_user' does not exist in target wiki.");
+ continue;
+ }
+ }
+ } else {
+ # Find comment text
+ $commentText = false;
+
+ if ( $commentExt ) {
+ $f = findAuxFile( $file, $commentExt );
+ if ( !$f ) {
+ echo( " No comment file with extension {$commentExt} found for {$file}, using default comment. " );
+ } else {
+ $commentText = file_get_contents( $f );
+ if ( !$f ) {
+ echo( " Failed to load comment file {$f}, using default comment. " );
+ }
+ }
+ }
+
+ if ( !$commentText ) {
+ $commentText = $comment;
+ }
+ }
+
+
# Import the file
if ( isset( $options['dry'] ) ) {
- echo( " publishing {$file}... " );
+ echo( " publishing {$file} by '" . $wgUser->getName() . "', comment '$commentText'... " );
} else {
$archive = $image->publish( $file );
if( WikiError::isError( $archive ) || !$archive->isGood() ) {
echo( "failed.\n" );
+ $failed++;
continue;
}
}
@@ -141,7 +222,6 @@ if( count( $args ) > 0 ) {
}
- $$svar++;
if ( isset( $options['dry'] ) ) {
echo( "done.\n" );
} else if ( $image->recordUpload( $archive->value, $commentText, $license ) ) {
@@ -164,14 +244,24 @@ if( count( $args ) > 0 ) {
} else {
echo( "failed.\n" );
+ $svar = 'failed';
}
+ $$svar++;
+ $processed++;
+
+ if ( $limit && $processed >= $limit )
+ break;
+
+ if ( $sleep )
+ sleep( $sleep );
}
# Print out some statistics
echo( "\n" );
- foreach( array( 'count' => 'Found', 'added' => 'Added',
- 'skipped' => 'Skipped', 'overwritten' => 'Overwritten' ) 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" );
}
@@ -184,14 +274,14 @@ if( count( $args ) > 0 ) {
showUsage();
}
-exit();
+exit(0);
function showUsage( $reason = false ) {
if( $reason ) {
echo( $reason . "\n" );
}
- echo <<<END
+ echo <<<TEXT
Imports images and other media files into the wiki
USAGE: php importImages.php [options] <dir>
@@ -199,17 +289,25 @@ USAGE: php importImages.php [options] <dir>
Options:
--extensions=<exts> Comma-separated list of allowable extensions, defaults to \$wgFileExtensions
---overwrite Overwrite existing images if a conflicting-named image is found
+--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)
+--sleep=<sec> Sleep between files. Useful mostly for debugging.
--user=<username> Set username of uploader, default 'Maintenance script'
---comment=<text> Set upload summary comment, default 'Importing image file'
+--check-userblock Check if the user got blocked during import.
+--comment=<text> Set upload summary comment, default 'Importing image file'.
--comment-file=<file> Set upload summary comment the the content of <file>.
--comment-ext=<ext> Causes the comment for each file to be loaded from a file with the same name
- but the extension <ext>.
+ but the extension <ext>. If a global comment 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)
--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/"
-END;
- exit();
-} \ No newline at end of file
+TEXT;
+ exit(1);
+}
diff --git a/maintenance/importLogs.inc b/maintenance/importLogs.inc
deleted file mode 100644
index a008e6c7..00000000
--- a/maintenance/importLogs.inc
+++ /dev/null
@@ -1,144 +0,0 @@
-<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
-# http://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
-
-/**
- * Attempt to import existing log pages into the log tables.
- *
- * Not yet complete.
- *
- * @file
- * @todo document
- * @ingroup Maintenance
- */
-
-/** */
-require_once( 'GlobalFunctions.php' );
-require_once( 'Database.php' );
-require_once( 'Article.php' );
-require_once( 'LogPage.php' );
-
-/**
- * Log importer
- * @todo document
- * @ingroup Maintenance
- */
-class LogImporter {
- var $dummy = false;
-
- function LogImporter( $type ) {
- $this->type = $type;
- $this->db = wfGetDB( DB_MASTER );
- $this->actions = $this->setupActions();
- }
-
- function setupActions() {
- $actions = array();
- foreach( LogPage::validActions( $this->type ) as $action ) {
- $key = "{$this->type}/$action";
- $actions[$key] = $this->makeLineRegexp( $this->type, $action );
- }
- return $actions;
- }
-
- function makeLineRegexp( $type, $action ) {
- $linkRegexp = '(?:\[\[)?([^|\]]+?)(?:\|[^\]]+?)?(?:\]\])?';
- $linkRegexp2 = '\[\[([^|\]]+?)(?:\|[^\]]+?)?\]\]';
-
- $text = LogPage::actionText( $type, $action );
- $text = preg_quote( $text, '/' );
- $text = str_replace( '\$1', $linkRegexp, $text );
- $text = '^(.*?) ' . $linkRegexp2 . ' ' . $text;
- $text .= '(?: <em>\((.*)\)<\/em>)?';
- $text = "/$text/";
- return $text;
- }
-
- function importText( $text ) {
- if( $this->dummy ) {
- print $text;
- var_dump( $this->actions );
- }
- $lines = explode( '<li>', $text );
- foreach( $lines as $line ) {
- $matches = array();
- if( preg_match( '!^(.*)</li>!', $line, $matches ) ) {
- $this->importLine( $matches[1] );
- }
- }
- }
-
- function fixDate( $date ) {
- # Yuck! Parsing multilingual date formats??!!!!???!!??!
- # 01:55, 23 Aug 2004 - won't take in strtotimr
- # "Aug 23 2004 01:55" - seems ok
- # TODO: multilingual attempt to extract from the data in Language
- $matches = array();
- if( preg_match( '/^(\d+:\d+(?::\d+)?), (.*)$/', $date, $matches ) ) {
- $date = $matches[2] . ' ' . $matches[1];
- }
- $n = strtotime( $date ) + date("Z");
- # print gmdate( 'D, d M Y H:i:s T', $n ) . "\n";
- $timestamp = wfTimestamp( TS_MW, $n );
- return $timestamp;
- }
-
- function importLine( $line ) {
- foreach( $this->actions as $action => $regexp ) {
- $matches = array();
- if( preg_match( $regexp, $line, $matches ) ) {
- if( $this->dummy ) {
- #var_dump( $matches );
- }
- $date = $this->fixDate( $matches[1] );
- $user = Title::newFromText( $matches[2] );
- $target = Title::newFromText( $matches[3] );
- if( isset( $matches[4] ) ) {
- $comment = $matches[4];
- } else {
- $comment = '';
- }
-
- $insert = array(
- 'log_type' => $this->type,
- 'log_action' => preg_replace( '!^.*/!', '', $action ),
- 'log_timestamp' => $date,
- 'log_user' => intval( User::idFromName( $user->getText() ) ),
- 'log_namespace' => $target->getNamespace(),
- 'log_title' => $target->getDBkey(),
- 'log_comment' => wfUnescapeWikiText( $comment ),
- );
- if( $this->dummy ) {
- var_dump( $insert );
- } else {
- # FIXME: avoid duplicates!
- $this->db->insert( 'logging', $insert );
- }
- break;
- }
- }
- }
-}
-
-function wfUnescapeWikiText( $text ) {
- $text = str_replace(
- array( '&#91;', '&#124;', '&#39;', 'ISBN&#32;', '&#58;//' , "\n&#61;", '&#123;&#123;' ),
- array( '[', '|', "'", 'ISBN ' , '://' , "\n=", '{{' ),
- $text );
- return $text;
-}
diff --git a/maintenance/importLogs.php b/maintenance/importLogs.php
deleted file mode 100644
index 059c2f17..00000000
--- a/maintenance/importLogs.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-/**
- * @todo document
- * @file
- * @ingroup Maintenance
- */
-
-/** */
-require_once( "commandLine.inc" );
-require_once( "importLogs.inc" );
-
-#print $text;
-#exit();
-
-foreach( LogPage::validTypes() as $type ) {
- if( $type == '' ) continue;
-
- $page = LogPage::logName( $type );
- $log = new Article( Title::makeTitleSafe( NS_PROJECT, $page ) );
- $text = $log->fetchContent();
-
- $importer = new LogImporter( $type );
- $importer->dummy = true;
- $importer->importText( $text );
-}
-
-
diff --git a/maintenance/importTextFile.php b/maintenance/importTextFile.php
index bfb852e0..955d01f4 100644
--- a/maintenance/importTextFile.php
+++ b/maintenance/importTextFile.php
@@ -11,7 +11,7 @@
$options = array( 'help', 'nooverwrite', 'norc' );
$optionsWithArgs = array( 'title', 'user', 'comment' );
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
echo( "Import Text File\n\n" );
if( count( $args ) < 1 || isset( $options['help'] ) ) {
@@ -23,7 +23,7 @@ if( count( $args ) < 1 || isset( $options['help'] ) ) {
if( is_file( $filename ) ) {
$title = isset( $options['title'] ) ? $options['title'] : titleFromFilename( $filename );
- $title = Title::newFromUrl( $title );
+ $title = Title::newFromURL( $title );
if( is_object( $title ) ) {
diff --git a/maintenance/importUseModWiki.php b/maintenance/importUseModWiki.php
index 05a4c78c..0d014145 100644
--- a/maintenance/importUseModWiki.php
+++ b/maintenance/importUseModWiki.php
@@ -59,7 +59,7 @@ function importPages()
global $wgRootDirectory;
$gt = '>';
- echo <<<END
+ echo <<<XML
<?xml version="1.0" encoding="UTF-8" ?$gt
<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.1/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -69,7 +69,7 @@ function importPages()
xml:lang="en">
<!-- generated by importUseModWiki.php -->
-END;
+XML;
$letters = array(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
@@ -79,10 +79,10 @@ END;
if( is_dir( $dir ) )
importPageDirectory( $dir );
}
- echo <<<END
+ echo <<<XML
</mediawiki>
-END;
+XML;
}
function importPageDirectory( $dir, $prefix = "" )
@@ -227,11 +227,11 @@ function importPage( $title )
*/
$revisions = array( $page );
}
- $xml = <<<END
+ $xml = <<<XML
<page>
<title>$newtitle</title>
-END;
+XML;
# History
$revisions = array_merge( $revisions, fetchKeptPages( $title ) );
@@ -247,7 +247,7 @@ END;
$timestamp = xmlsafe( timestamp2ISO8601( $rev->ts ) );
$comment = xmlsafe( recodeText( $rev->summary ) );
- $xml .= <<<END
+ $xml .= <<<XML
<revision>
<timestamp>$timestamp</timestamp>
<contributor><username>$username</username></contributor>
@@ -256,7 +256,7 @@ END;
<text>$text</text>
</revision>
-END;
+XML;
}
$xml .= "</page>\n\n";
return $xml;
diff --git a/maintenance/initEditCount.php b/maintenance/initEditCount.php
index d26349bb..b7301643 100644
--- a/maintenance/initEditCount.php
+++ b/maintenance/initEditCount.php
@@ -1,89 +1,108 @@
<?php
/**
- * @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
+ *
* @ingroup Maintenance
*/
-require_once "commandLine.inc";
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if( isset( $options['help'] ) ) {
- die( "Batch-recalculate user_editcount fields from the revision table.
-Options:
- --quick Force the update to be done in a single query.
- --background Force replication-friendly mode; may be inefficient but
- avoids locking tables or lagging slaves with large updates;
- calculates counts on a slave if possible.
+class InitEditCount extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( 'quick', 'Force the update to be done in a single query' );
+ $this->addOption( 'background', 'Force replication-friendly mode; may be inefficient but
+ avoids locking tables or lagging slaves with large updates;
+ calculates counts on a slave if possible.
Background mode will be automatically used if the server is MySQL 4.0
(which does not support subqueries) or if multiple servers are listed
-in \$wgDBservers, usually indicating a replication environment.
+in $wgDBservers, usually indicating a replication environment.' );
+ $this->mDescription = "Batch-recalculate user_editcount fields from the revision table";
+ }
-");
-}
-$dbw = wfGetDB( DB_MASTER );
-$user = $dbw->tableName( 'user' );
-$revision = $dbw->tableName( 'revision' );
+ public function execute() {
+ global $wgDBservers;
+ $dbw = wfGetDB( DB_MASTER );
+ $user = $dbw->tableName( 'user' );
+ $revision = $dbw->tableName( 'revision' );
-$dbver = $dbw->getServerVersion();
+ $dbver = $dbw->getServerVersion();
-// Autodetect mode...
-$backgroundMode = count( $wgDBservers ) > 1 ||
- ($dbw instanceof DatabaseMySql && version_compare( $dbver, '4.1' ) < 0);
+ // Autodetect mode...
+ $backgroundMode = count( $wgDBservers ) > 1 ||
+ ($dbw instanceof DatabaseMySql && version_compare( $dbver, '4.1' ) < 0);
+
+ if( $this->hasOption('background') ) {
+ $backgroundMode = true;
+ } elseif( $this->hasOption('quick') ) {
+ $backgroundMode = false;
+ }
-if( isset( $options['background'] ) ) {
- $backgroundMode = true;
-} elseif( isset( $options['quick'] ) ) {
- $backgroundMode = false;
-}
+ if( $backgroundMode ) {
+ $this->output( "Using replication-friendly background mode...\n" );
-if( $backgroundMode ) {
- echo "Using replication-friendly background mode...\n";
-
- $dbr = wfGetDB( DB_SLAVE );
- $chunkSize = 100;
- $lastUser = $dbr->selectField( 'user', 'MAX(user_id)', '', __FUNCTION__ );
-
- $start = microtime( true );
- $migrated = 0;
- for( $min = 0; $min <= $lastUser; $min += $chunkSize ) {
- $max = $min + $chunkSize;
- $result = $dbr->query(
- "SELECT
- user_id,
- COUNT(rev_user) AS user_editcount
- FROM $user
- LEFT OUTER JOIN $revision ON user_id=rev_user
- WHERE user_id > $min AND user_id <= $max
- GROUP BY user_id",
- __FUNCTION__ );
-
- while( $row = $dbr->fetchObject( $result ) ) {
- $dbw->update( 'user',
- array( 'user_editcount' => $row->user_editcount ),
- array( 'user_id' => $row->user_id ),
- __FUNCTION__ );
- ++$migrated;
+ $dbr = wfGetDB( DB_SLAVE );
+ $chunkSize = 100;
+ $lastUser = $dbr->selectField( 'user', 'MAX(user_id)', '', __METHOD__ );
+
+ $start = microtime( true );
+ $migrated = 0;
+ for( $min = 0; $min <= $lastUser; $min += $chunkSize ) {
+ $max = $min + $chunkSize;
+ $result = $dbr->query(
+ "SELECT
+ user_id,
+ COUNT(rev_user) AS user_editcount
+ FROM $user
+ LEFT OUTER JOIN $revision ON user_id=rev_user
+ WHERE user_id > $min AND user_id <= $max
+ GROUP BY user_id",
+ __METHOD__ );
+
+ foreach( $result as $row ) {
+ $dbw->update( 'user',
+ array( 'user_editcount' => $row->user_editcount ),
+ array( 'user_id' => $row->user_id ),
+ __METHOD__ );
+ ++$migrated;
+ }
+ $dbr->freeResult( $result );
+
+ $delta = microtime( true ) - $start;
+ $rate = ($delta == 0.0) ? 0.0 : $migrated / $delta;
+ $this->output( sprintf( "%s %d (%0.1f%%) done in %0.1f secs (%0.3f accounts/sec).\n",
+ wfWikiID(),
+ $migrated,
+ min( $max, $lastUser ) / $lastUser * 100.0,
+ $delta,
+ $rate ) );
+
+ wfWaitForSlaves( 10 );
+ }
+ } else {
+ // Subselect should work on modern MySQLs etc
+ $this->output( "Using single-query mode...\n" );
+ $sql = "UPDATE $user SET user_editcount=(SELECT COUNT(*) FROM $revision WHERE rev_user=user_id)";
+ $dbw->query( $sql );
}
- $dbr->freeResult( $result );
-
- $delta = microtime( true ) - $start;
- $rate = ($delta == 0.0) ? 0.0 : $migrated / $delta;
- printf( "%s %d (%0.1f%%) done in %0.1f secs (%0.3f accounts/sec).\n",
- $wgDBname,
- $migrated,
- min( $max, $lastUser ) / $lastUser * 100.0,
- $delta,
- $rate );
-
- wfWaitForSlaves( 10 );
+
+ $this->output( "Done!\n" );
}
-} else {
- // Subselect should work on modern MySQLs etc
- echo "Using single-query mode...\n";
- $sql = "UPDATE $user SET user_editcount=(SELECT COUNT(*) FROM $revision WHERE rev_user=user_id)";
- $dbw->query( $sql );
}
-echo "Done!\n";
-
-
+$maintClass = "InitEditCount";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/initStats.inc b/maintenance/initStats.inc
deleted file mode 100644
index b1660ce2..00000000
--- a/maintenance/initStats.inc
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-/**
- * @file
- * @ingroup Maintenance
- */
-
-function wfInitStats( $options=array() ) {
- $dbr = wfGetDB( DB_SLAVE );
-
- wfOut( "Counting total edits..." );
- $edits = $dbr->selectField( 'revision', 'COUNT(*)', '', __METHOD__ );
- $edits += $dbr->selectField( 'archive', 'COUNT(*)', '', __METHOD__ );
- wfOut( "{$edits}\nCounting number of articles..." );
-
- global $wgContentNamespaces;
- $good = $dbr->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $wgContentNamespaces, 'page_is_redirect' => 0, 'page_len > 0' ), __METHOD__ );
- wfOut( "{$good}\nCounting total pages..." );
-
- $pages = $dbr->selectField( 'page', 'COUNT(*)', '', __METHOD__ );
- wfOut( "{$pages}\nCounting number of users..." );
-
- $users = $dbr->selectField( 'user', 'COUNT(*)', '', __METHOD__ );
- wfOut( "{$users}\nCounting number of admins..." );
-
- $admin = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), __METHOD__ );
- wfOut( "{$admin}\nCounting number of images..." );
-
- $image = $dbr->selectField( 'image', 'COUNT(*)', '', __METHOD__ );
- wfOut( "{$image}\n" );
-
- if( !isset( $options['noviews'] ) ) {
- wfOut( "Counting total page views..." );
- $views = $dbr->selectField( 'page', 'SUM(page_counter)', '', __METHOD__ );
- wfOut( "{$views}\n" );
- }
-
- wfOut( "\nUpdating site statistics..." );
-
- $dbw = wfGetDB( DB_MASTER );
- $values = array( 'ss_total_edits' => $edits,
- 'ss_good_articles' => $good,
- 'ss_total_pages' => $pages,
- 'ss_users' => $users,
- 'ss_admins' => $admin,
- 'ss_images' => $image );
- $conds = array( 'ss_row_id' => 1 );
- $views = array( 'ss_total_views' => isset( $views ) ? $views : 0 );
-
- if( isset( $options['update'] ) ) {
- $dbw->update( 'site_stats', $values, $conds, __METHOD__ );
- } else {
- $dbw->delete( 'site_stats', $conds, __METHOD__ );
- $dbw->insert( 'site_stats', array_merge( $values, $conds, $views ), __METHOD__ );
- }
-
- wfOut( "done.\n" );
-}
diff --git a/maintenance/initStats.php b/maintenance/initStats.php
index d206c202..b92d46c5 100644
--- a/maintenance/initStats.php
+++ b/maintenance/initStats.php
@@ -3,29 +3,82 @@
/**
* Maintenance script to re-initialise or update the site statistics table
*
- * @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
+ *
* @ingroup Maintenance
* @author Brion Vibber
* @author Rob Church <robchur@gmail.com>
* @licence GNU General Public Licence 2.0 or later
*/
-
-$options = array( 'help', 'update', 'noviews' );
-require_once( 'commandLine.inc' );
-echo( "Refresh Site Statistics\n\n" );
-
-if( isset( $options['help'] ) ) {
- showHelp();
- exit();
-}
-require "$IP/maintenance/initStats.inc";
-wfInitStats( $options );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class InitStats 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( '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' );
+ }
+
+ public function execute() {
+ $this->output( "Refresh Site Statistics\n\n" );
+ $counter = new SiteStatsInit( $this->hasOption( 'use-master' ) );
+
+ $this->output( "Counting total edits..." );
+ $edits = $counter->edits();
+ $this->output( "{$edits}\nCounting number of articles..." );
+
+ $good = $counter->articles();
+ $this->output( "{$good}\nCounting total pages..." );
+
+ $pages = $counter->pages();
+ $this->output( "{$pages}\nCounting number of users..." );
+
+ $users = $counter->users();
+ $this->output( "{$users}\nCounting number of images..." );
+
+ $image = $counter->files();
+ $this->output( "{$image}\n" );
+
+ if( !$this->hasOption('noviews') ) {
+ $this->output( "Counting total page views..." );
+ $views = $counter->views();
+ $this->output( "{$views}\n" );
+ }
+
+ if( $this->hasOption( 'active' ) ) {
+ $this->output( "Counting active users..." );
+ $active = SiteStatsUpdate::cacheUpdate();
+ $this->output( "{$active}\n" );
+ }
+
+ $this->output( "\nUpdating site statistics..." );
+
+ if( $this->hasOption( 'update' ) ) {
+ $counter->update();
+ } else {
+ $counter->refresh();
+ }
-function showHelp() {
- echo( "Re-initialise the site statistics tables.\n\n" );
- echo( "Usage: php initStats.php [--update|--noviews]\n\n" );
- echo( " --update : Update the existing statistics (preserves the ss_total_views field)\n" );
- echo( "--noviews : Don't update the page view counter\n\n" );
+ $this->output( "done.\n" );
+ }
}
+$maintClass = "InitStats";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/install-utils.inc b/maintenance/install-utils.inc
new file mode 100644
index 00000000..f2d48e9a
--- /dev/null
+++ b/maintenance/install-utils.inc
@@ -0,0 +1,219 @@
+<?php
+
+/**
+ * This file contains functions used by the install script (config/index.php)
+ * and maintenance scripts. It is not loaded in normal web requests.
+ *
+ * @file
+ */
+
+function install_version_checks() {
+ # We dare not turn output buffer _off_ since this will break completely
+ # if PHP is globally configured to run through a gzip filter.
+ @ob_implicit_flush( true );
+
+ if( !function_exists( 'version_compare' ) ) {
+ # version_compare was introduced in 4.1.0
+ echo "Your PHP version is much too old; 4.0.x will _not_ work. 5.1.0 or higher is required. ABORTING.\n";
+ die( 1 );
+ }
+ if( version_compare( phpversion(), '5.1.0' ) < 0 ) {
+ echo "PHP 5.1.0 or higher is required. If PHP 5 is available only when \n".
+ "PHP files have a .php5 extension, please navigate to <a href=\"index.php5\">index.php5</a> \n".
+ "to continue installation. ABORTING.\n";
+ die( 1 );
+ }
+
+ $test = new PhpXmlBugTester();
+ if( !$test->ok ) {
+ echo "Your system has a combination of PHP and libxml2 versions which 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 (http://bugs.php.net/bug.php?id=45996 for details).\n";
+ die( 1 );
+ }
+
+ $test = new PhpRefCallBugTester;
+ $test->execute();
+ if ( !$test->ok ) {
+ echo "PHP 5.3.1 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";
+ die( 1 );
+ }
+
+ global $wgCommandLineMode;
+ $wgCommandLineMode = true;
+ umask( 000 );
+ @set_time_limit( 0 );
+}
+
+/**
+ * Test for PHP+libxml2 bug which breaks XML input subtly with certain versions.
+ * http://bugs.php.net/bug.php?id=45996
+ * Known fixed with PHP 5.2.9 + libxml2-2.7.3
+ */
+class PhpXmlBugTester {
+ private $parsedData = '';
+ public $ok = false;
+ public function __construct() {
+ $charData = '<b>c</b>';
+ $xml = '<a>' . htmlspecialchars( $charData ) . '</a>';
+
+ $parser = xml_parser_create();
+ xml_set_character_data_handler( $parser, array( $this, 'chardata' ) );
+ $parsedOk = xml_parse($parser, $xml, true);
+ $this->ok = $parsedOk && ($this->parsedData == $charData);
+ }
+ public function chardata($parser, $data) {
+ $this->parsedData .= $data;
+ }
+}
+
+/**
+ * Test for PHP bug #50394 (PHP 5.3.x conversion to null only, not 5.2.x)
+ */
+class PhpRefCallBugTester {
+ public $ok = false;
+
+ function __call( $name, $args ) {
+ $old = error_reporting( E_ALL & ~E_WARNING );
+ call_user_func_array( array( $this, 'checkForBrokenRef' ), $args );
+ error_reporting( $old );
+ }
+
+ function checkForBrokenRef( &$var ) {
+ if ( $var ) {
+ $this->ok = true;
+ }
+ }
+
+ function execute() {
+ $var = true;
+ call_user_func_array( array( $this, 'foo' ), array( &$var ) );
+ }
+}
+
+function readconsole( $prompt = '' ) {
+ static $isatty = null;
+ if ( is_null( $isatty ) ) {
+ if ( !function_exists( 'posix_isatty' ) || posix_isatty( 0 /*STDIN*/ ) ) {
+ $isatty = true;
+ } else {
+ $isatty = false;
+ }
+ }
+
+ if ( $isatty && function_exists( 'readline' ) ) {
+ return readline( $prompt );
+ } else {
+ if ( $isatty ) {
+ $st = readlineEmulation( $prompt );
+ } else {
+ if ( feof( STDIN ) ) {
+ $st = false;
+ } else {
+ $st = fgets(STDIN, 1024);
+ }
+ }
+ if ($st === false) return false;
+ $resp = trim( $st );
+ return $resp;
+ }
+}
+
+function findExecutable( $name ) {
+ $paths = explode( PATH_SEPARATOR, getenv( "PATH" ) );
+ foreach( $paths as $path ) {
+ $full = $path . DIRECTORY_SEPARATOR . $name;
+ if( file_exists( $full ) ) {
+ if( wfIsWindows() || is_executable( $full ) ) {
+ return $full;
+ }
+ }
+ }
+ return false;
+}
+
+function readlineEmulation( $prompt ) {
+ $bash = "bash";
+ if( !wfIsWindows() && findExecutable( $bash ) ) {
+ $retval = false;
+ $encPrompt = wfEscapeShellArg( $prompt );
+ $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
+ $encCommand = wfEscapeShellArg( $command );
+ $line = wfShellExec( "$bash -c $encCommand", $retval );
+
+ if( $retval == 0 ) {
+ return $line;
+ } elseif( $retval == 127 ) {
+ // Couldn't execute bash even though we thought we saw it.
+ // Shell probably spit out an error message, sorry :(
+ // Fall through to fgets()...
+ } else {
+ // EOF/ctrl+D
+ return false;
+ }
+ }
+
+ // Fallback... we'll have no editing controls, EWWW
+ if ( feof( STDIN ) ) {
+ return false;
+ }
+ print $prompt;
+ return fgets(STDIN, 1024);
+}
+
+
+#
+# Read and execute SQL commands from a file
+#
+function dbsource( $fname, $db = false ) {
+ wfDeprecated( __METHOD__ );
+ if ( !$db ) {
+ // Try $wgDatabase, which is used in the install and update scripts
+ global $wgDatabase;
+ if ( isset( $wgDatabase ) ) {
+ $db = $wgDatabase;
+ } else {
+ // No? Well, we must be outside of those scripts, so use the standard method
+ $db = wfGetDB( DB_MASTER );
+ }
+ }
+ $error = $db->sourceFile( $fname );
+ if ( $error !== true ) {
+ print $error;
+ exit(1);
+ }
+}
+
+/**
+ * Get the value of session.save_path
+ *
+ * Per http://www.php.net/manual/en/ref.session.php#ini.session.save-path,
+ * this might have some additional preceding parts which need to be
+ * ditched
+ *
+ * @return string
+ */
+function mw_get_session_save_path() {
+ $path = ini_get( 'session.save_path' );
+ $path = substr( $path, strrpos( $path, ';' ) );
+ return $path;
+}
+
+/**
+ * Is dl() available to us?
+ *
+ * According to http://www.php.net/manual/en/function.dl.php, dl()
+ * is *not* available when `enable_dl` is off, or under `safe_mode`
+ *
+ * @return bool
+ */
+function mw_have_dl() {
+ return function_exists( 'dl' )
+ && is_callable( 'dl' )
+ && wfIniGetBool( 'enable_dl' )
+ && !wfIniGetBool( 'safe_mode' );
+}
diff --git a/maintenance/installExtension.php b/maintenance/installExtension.php
index d5c4f4be..ea4c191c 100644
--- a/maintenance/installExtension.php
+++ b/maintenance/installExtension.php
@@ -23,7 +23,7 @@
$optionsWithArgs = array( 'target', 'repository', 'repos' );
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
define('EXTINST_NOPATCH', 0);
define('EXTINST_WRITEPATCH', 6);
diff --git a/maintenance/interwiki.sql b/maintenance/interwiki.sql
index 2521d381..2ce0e23f 100644
--- a/maintenance/interwiki.sql
+++ b/maintenance/interwiki.sql
@@ -39,13 +39,14 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES
('lugkr','http://lug-kr.sourceforge.net/cgi-bin/lugwiki.pl?$1',0),
('mathsongswiki','http://SeedWiki.com/page.cfm?wikiid=237&doc=$1',0),
('meatball','http://www.usemod.com/cgi-bin/mb.pl?$1',0),
-('mediazilla','http://bugzilla.wikipedia.org/$1',1),
+('mediazilla','https://bugzilla.wikimedia.org/$1',1),
('mediawikiwiki','http://www.mediawiki.org/wiki/$1',0),
('memoryalpha','http://www.memory-alpha.org/en/index.php/$1',0),
('metawiki','http://sunir.org/apps/meta.pl?$1',0),
('metawikipedia','http://meta.wikimedia.org/wiki/$1',0),
('moinmoin','http://purl.net/wiki/moin/$1',0),
('mozillawiki','http://wiki.mozilla.org/index.php/$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),
@@ -79,19 +80,20 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES
('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.wikicities.com/index.php/$1',0),
+('wikicities','http://www.wikia.com/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),
('wikimedia','http://wikimediafoundation.org/wiki/$1',0),
-('wikiquote','http://en.wikiquote.org/wiki/$1',1),
('wikinews','http://en.wikinews.org/wiki/$1',1),
-('wikisource','http://sources.wikipedia.org/wiki/$1',1),
-('wikispecies','http://species.wikipedia.org/wiki/$1',1),
+('wikiquote','http://en.wikiquote.org/wiki/$1',1),
+('wikipedia', 'http://en.wikipedia.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),
-('wikipedia', 'http://en.wikipedia.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),
-('wikt','http://en.wiktionary.org/wiki/$1',1);
+('zzz wiki','http://wiki.zzz.ee/index.php/$1',0);
diff --git a/maintenance/lag.php b/maintenance/lag.php
new file mode 100644
index 00000000..47b4c47b
--- /dev/null
+++ b/maintenance/lag.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Shows database lag
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class DatabaseLag extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Shows database lag";
+ $this->addOption( 'r', "Don't exit immediately, but show the lag every 5 seconds" );
+ }
+
+ public function execute() {
+ if ( $this->hasOption( 'r' ) ) {
+ $lb = wfGetLB();
+ echo 'time ';
+ for( $i = 1; $i < $lb->getServerCount(); $i++ ) {
+ $hostname = $lb->getServerName( $i );
+ printf( "%-12s ", $hostname );
+ }
+ echo "\n";
+
+ while( 1 ) {
+ $lb->clearLagTimeCache();
+ $lags = $lb->getLagTimes();
+ unset( $lags[0] );
+ echo gmdate( 'H:i:s' ) . ' ';
+ foreach( $lags as $i => $lag ) {
+ printf( "%-12s " , $lag === false ? 'false' : $lag );
+ }
+ echo "\n";
+ sleep( 5 );
+ }
+ } else {
+ $lb = wfGetLB();
+ $lags = $lb->getLagTimes();
+ foreach( $lags as $i => $lag ) {
+ $name = $lb->getServerName( $i );
+ $this->output( sprintf( "%-20s %s\n" , $name, $lag === false ? 'false' : $lag ) );
+ }
+ }
+ }
+}
+
+$maintClass = "DatabaseLag";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/language/StatOutputs.php b/maintenance/language/StatOutputs.php
index b41278b4..169a4d41 100644
--- a/maintenance/language/StatOutputs.php
+++ b/maintenance/language/StatOutputs.php
@@ -32,7 +32,7 @@ class statsOutput {
class wikiStatsOutput extends statsOutput {
function heading() {
global $IP;
- $version = SpecialVersion::getVersion( $IP );
+ $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";
@@ -72,18 +72,6 @@ class wikiStatsOutput extends statsOutput {
}
}
-/** Outputs WikiText and appends category and text only used for Meta-Wiki */
-class metawikiStatsOutput extends wikiStatsOutput {
- function heading() {
- echo "See [[MediaWiki localisation]] to learn how you can help translating MediaWiki.\n\n";
- parent::heading();
- }
- function footer() {
- parent::footer();
- echo "\n[[Category:Localisation|Statistics]]\n";
- }
-}
-
/** Output text. To be used on a terminal for example. */
class textStatsOutput extends statsOutput {
function element( $in, $heading = false ) {
diff --git a/maintenance/language/alltrans.php b/maintenance/language/alltrans.php
index 67c870e6..420386fd 100644
--- a/maintenance/language/alltrans.php
+++ b/maintenance/language/alltrans.php
@@ -1,16 +1,40 @@
<?php
/**
- * @file
- * @ingroup MaintenanceLanguage
- *
* Get all the translations messages, as defined in the English language 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
+ *
+ * @ingroup MaintenanceLanguage
*/
-require_once( dirname(__FILE__).'/../commandLine.inc' );
+require_once( dirname(__FILE__) . '/../Maintenance.php' );
-$wgEnglishMessages = array_keys( Language::getMessagesFor( 'en' ) );
-foreach( $wgEnglishMessages as $key ) {
- echo "$key\n";
-}
+class AllTrans extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Get all messages as defined by the English language file";
+ }
+ public function execute() {
+ $wgEnglishMessages = array_keys( Language::getMessagesFor( 'en' ) );
+ foreach( $wgEnglishMessages as $key ) {
+ $this->output( "$key\n" );
+ }
+ }
+}
+$maintClass = "AllTrans";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/language/checkDupeMessages.php b/maintenance/language/checkDupeMessages.php
new file mode 100644
index 00000000..81eafccf
--- /dev/null
+++ b/maintenance/language/checkDupeMessages.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * @todo document
+ * @file
+ * @ingroup MaintenanceLanguage
+ */
+
+require_once( dirname(__FILE__).'/../commandLine.inc' );
+$messagesDir = dirname(__FILE__).'/../../languages/messages/';
+$runTest = false;
+$run = false;
+$runMode = 'text';
+
+// Check parameters
+if ( isset( $options['lang'] ) && isset( $options['clang'] )) {
+ if (!isset( $options['mode'] )) {
+ $runMode = 'text';
+ } else {
+ if (!strcmp($options['mode'],'wiki')) {
+ $runMode = 'wiki';
+ } else if (!strcmp($options['mode'],'php')) {
+ $runMode = 'php';
+ } else if (!strcmp($options['mode'],'raw')) {
+ $runMode = 'raw';
+ } else {
+ }
+ }
+ $runTest = true;
+} else {
+ echo <<<TEXT
+Run this script to print out the duplicates against a message array.
+Parameters:
+ * lang: Language code to be checked.
+ * clang: Language code to be compared.
+Options:
+ * mode: Output format, can be either:
+ * text: Text output on the console (default)
+ * wiki: Wiki format, with * at beginning of each line
+ * php: Output text as PHP syntax in a array $dupeMessages
+ * raw: Raw output for duplicates
+TEXT;
+}
+
+// Check file exists
+if ( $runTest ) {
+ $langCode = $options['lang'];
+ $langCodeC = $options['clang'];
+ $langCodeF = ucfirst(strtolower(preg_replace('/-/','_',$langCode)));
+ $langCodeFC = ucfirst(strtolower(preg_replace('/-/','_',$langCodeC)));
+ $messagesFile = $messagesDir.'Messages'.$langCodeF.'.php';
+ $messagesFileC = $messagesDir.'Messages'.$langCodeFC.'.php';
+ if (file_exists($messagesFile) && file_exists($messagesFileC)) {
+ $run = true;
+ }
+ else {
+ echo "Messages file(s) could not be found.\nMake sure both files are exists.\n";
+ }
+}
+
+// Run to check the dupes
+if ( $run ) {
+ if (!strcmp($runMode,'wiki')) {
+ $runMode = 'wiki';
+ } else if (!strcmp($runMode,'raw')) {
+ $runMode = 'raw';
+ }
+ include( $messagesFile );
+ $messageExist = isset($messages);
+ if ($messageExist)
+ $wgMessages[$langCode] = $messages;
+ include( $messagesFileC );
+ $messageCExist = isset($messages);
+ if ($messageCExist)
+ $wgMessages[$langCodeC] = $messages;
+ $count = 0;
+
+ if (($messageExist) && ($messageCExist)) {
+
+ if (!strcmp($runMode,'php')) {
+ print("<?php\n");
+ print('$dupeMessages = array('."\n");
+ }
+ foreach ($wgMessages[$langCodeC] as $key => $value) {
+ foreach ($wgMessages[$langCode] as $ckey => $cvalue) {
+ if (!strcmp($key,$ckey)) {
+ if ((!strcmp($key,$ckey)) && (!strcmp($value,$cvalue))) {
+ if (!strcmp($runMode,'raw')) {
+ print("$key\n");
+ } else if (!strcmp($runMode,'php')) {
+ print("'$key' => '',\n");
+ } else if (!strcmp($runMode,'wiki')) {
+ $uKey = ucfirst($key);
+ print("* MediaWiki:$uKey/$langCode\n");
+ } else {
+ print("* $key\n");
+ }
+ $count++;
+ }
+ }
+ }
+ }
+ if (!strcmp($runMode,'php')) {
+ print(");\n");
+ }
+ if (!strcmp($runMode,'text')) {
+ if ($count == 1) {
+ echo "\nThere are $count duplicated message in $langCode, against to $langCodeC.\n";
+ } else {
+ echo "\nThere are $count duplicated messages in $langCode, against to $langCodeC.\n";
+ }
+ }
+ } else {
+ if (!$messageExist)
+ echo "There are no messages defined in $langCode.\n";
+ if (!$messageCExist)
+ echo "There are no messages defined in $langCodeC.\n";
+ }
+}
diff --git a/maintenance/language/checkExtensions.php b/maintenance/language/checkExtensions.php
index ab6f9ba8..ed1855c1 100644
--- a/maintenance/language/checkExtensions.php
+++ b/maintenance/language/checkExtensions.php
@@ -11,13 +11,13 @@ require_once( 'languages.inc' );
require_once( 'checkLanguage.inc' );
if( !class_exists( 'MessageGroups' ) || !class_exists( 'PremadeMediawikiExtensionGroups' ) ) {
- echo <<<END
+ echo <<<TEXT
Please add the Translate extension to LocalSettings.php, and enable the extension groups:
require_once( 'extensions/Translate/Translate.php' );
\$wgTranslateEC = array_keys( \$wgTranslateAC );
If you still get this message, update Translate to its latest version.
-END;
+TEXT;
exit(-1);
}
diff --git a/maintenance/language/checkLanguage.inc b/maintenance/language/checkLanguage.inc
index 52281b57..fc77aad3 100644
--- a/maintenance/language/checkLanguage.inc
+++ b/maintenance/language/checkLanguage.inc
@@ -7,6 +7,7 @@ class CheckLanguageCLI {
protected $code = null;
protected $level = 2;
protected $doLinks = false;
+ protected $linksPrefix = '';
protected $wikiCode = 'en';
protected $checkAll = false;
protected $output = 'plain';
@@ -24,7 +25,7 @@ class CheckLanguageCLI {
public function __construct( Array $options ) {
if ( isset( $options['help'] ) ) {
echo $this->help();
- exit();
+ exit(1);
}
if ( isset( $options['lang'] ) ) {
@@ -42,6 +43,10 @@ class CheckLanguageCLI {
$this->includeExif = !isset( $options['noexif'] );
$this->checkAll = isset( $options['all'] );
+ if ( isset( $options['prefix'] ) ) {
+ $this->linksPrefix = $options['prefix'];
+ }
+
if ( isset( $options['wikilang'] ) ) {
$this->wikiCode = $options['wikilang'];
}
@@ -190,6 +195,7 @@ Parameters:
* help: Show this help.
* level: Show the following display level (default: 2).
* 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).
* whitelist: Do only the following checks (form: code,code).
* blacklist: Don't do the following checks (form: code,code).
@@ -292,7 +298,7 @@ ENDS;
foreach ( $this->checks as $check ) {
if ( isset( $checkBlacklist[$code] ) &&
in_array( $check, $checkBlacklist[$code] ) ) {
- $result[$check] = array();
+ $results[$check] = array();
continue;
}
@@ -316,9 +322,9 @@ ENDS;
if ( $this->doLinks ) {
$displayKey = ucfirst( $key );
if ( $code == $this->wikiCode ) {
- return "[[MediaWiki:$displayKey|$key]]";
+ return "[[{$this->linksPrefix}MediaWiki:$displayKey|$key]]";
} else {
- return "[[MediaWiki:$displayKey/$code|$key]]";
+ return "[[{$this->linksPrefix}MediaWiki:$displayKey/$code|$key]]";
}
} else {
return $key;
@@ -378,7 +384,7 @@ ENDS;
function outputWiki() {
global $wgContLang, $IP;
$detailText = '';
- $rows[] = '! Language !! Code !! Total !! ' . implode( ' !! ', $this->checks );
+ $rows[] = '! Language !! Code !! Total !! ' . implode( ' !! ', array_diff( $this->checks, $this->nonMessageChecks() ) );
foreach ( $this->results as $code => $results ) {
$detailTextForLang = "==$code==\n";
$numbers = array();
@@ -418,7 +424,7 @@ ENDS;
$tableRows = implode( "\n|-\n", $rows );
- $version = SpecialVersion::getVersion( $IP );
+ $version = SpecialVersion::getVersion( 'nodb' );
echo <<<EOL
'''Check results are for:''' <code>$version</code>
@@ -459,7 +465,7 @@ class CheckExtensionsCLI extends CheckLanguageCLI {
public function __construct( Array $options, $extension ) {
if ( isset( $options['help'] ) ) {
echo $this->help();
- exit();
+ exit(1);
}
if ( isset( $options['lang'] ) ) {
@@ -587,7 +593,7 @@ Check codes (ideally, all of them should result 0; all the checks are executed b
* 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 shouldn't 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).
@@ -645,25 +651,40 @@ ENDS;
# Blacklist some checks for some languages
$checkBlacklist = array(
#'code' => array( 'check1', 'check2' ... )
+'az' => array( 'plural' ),
+'bo' => 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( 'chars' ), // Uses a lot zwnj
+'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' ),
diff --git a/maintenance/language/countMessages.php b/maintenance/language/countMessages.php
index 7d16915a..826c43cb 100644
--- a/maintenance/language/countMessages.php
+++ b/maintenance/language/countMessages.php
@@ -1,40 +1,65 @@
<?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
+ *
+ * @ingroup MaintenanceLanguage
+ */
-require_once( dirname(__FILE__).'/../commandLine.inc' );
+require_once( dirname(__FILE__) . '/../Maintenance.php' );
-global $IP;
-
-if ( !isset( $args[0] ) ) {
- $dir = "$IP/languages/messages";
-} else {
- $dir = $args[0];
-}
-
-$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;
+class CountMessages extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Count how many messages we have defined for each language";
}
- $code = str_replace( '_', '-', strtolower( $m[1] ) );
- $numMessages = wfGetNumMessages( $file );
- //print "$code: $numMessages\n";
- $total += $numMessages;
- if ( $numMessages > 0 ) {
- $nonZero ++;
+
+ 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;
+ }
+ $code = str_replace( '_', '-', strtolower( $m[1] ) );
+ $numMessages = $this->getNumMessages( $file );
+ //print "$code: $numMessages\n";
+ $total += $numMessages;
+ if ( $numMessages > 0 ) {
+ $nonZero ++;
+ }
+ }
+ $this->output( "\nTotal: $total\n" );
+ $this->output( "Languages: $nonZero\n" );
}
-}
-print "\nTotal: $total\n";
-print "Languages: $nonZero\n";
-function wfGetNumMessages( $file ) {
- // Separate function to limit scope
- require( $file );
- if ( isset( $messages ) ) {
- return count( $messages );
- } else {
- return 0;
+ private function getNumMessages( $file ) {
+ // Separate function to limit scope
+ require( $file );
+ if ( isset( $messages ) ) {
+ return count( $messages );
+ } else {
+ return 0;
+ }
}
}
+$maintClass = "CountMessages";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/language/date-formats.php b/maintenance/language/date-formats.php
index 834d2bd8..54a6a26d 100644
--- a/maintenance/language/date-formats.php
+++ b/maintenance/language/date-formats.php
@@ -1,50 +1,76 @@
<?php
/**
- * @file
+ * Test various language time and date functions
+ *
+ * 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
+ *
* @ingroup MaintenanceLanguage
*/
-$ts = '20010115123456';
+require_once( dirname(__FILE__) . '/../Maintenance.php' );
-
-$IP = dirname( __FILE__ ) . '/../..';
-require_once( "$IP/maintenance/commandLine.inc" );
+class DateFormats extends Maintenance {
-foreach ( glob( "$IP/languages/messages/Messages*.php" ) as $filename ) {
- $base = basename( $filename );
- $m = array();
- if ( !preg_match( '/Messages(.*)\.php$/', $base, $m ) ) {
- continue;
- }
- $code = str_replace( '_', '-', strtolower( $m[1] ) );
- print "$code ";
- $lang = Language::factory( $code );
- $prefs = $lang->getDatePreferences();
- if ( !$prefs ) {
- $prefs = array( 'default' );
- }
- print "date: ";
- foreach ( $prefs as $index => $pref ) {
- if ( $index > 0 ) {
- print ' | ';
- }
- print $lang->date( $ts, false, $pref );
- }
- print "\n$code time: ";
- foreach ( $prefs as $index => $pref ) {
- if ( $index > 0 ) {
- print ' | ';
- }
- print $lang->time( $ts, false, $pref );
+ private $ts = '20010115123456';
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Test various language time and date functions";
}
- print "\n$code both: ";
- foreach ( $prefs as $index => $pref ) {
- if ( $index > 0 ) {
- print ' | ';
+
+ public function execute() {
+ global $IP;
+ foreach ( glob( "$IP/languages/messages/Messages*.php" ) as $filename ) {
+ $base = basename( $filename );
+ $m = array();
+ if ( !preg_match( '/Messages(.*)\.php$/', $base, $m ) ) {
+ continue;
+ }
+ $code = str_replace( '_', '-', strtolower( $m[1] ) );
+ $this->output( "$code " );
+ $lang = Language::factory( $code );
+ $prefs = $lang->getDatePreferences();
+ if ( !$prefs ) {
+ $prefs = array( 'default' );
+ }
+ $this->output( "date: " );
+ foreach ( $prefs as $index => $pref ) {
+ if ( $index > 0 ) {
+ $this->output( ' | ' );
+ }
+ $this->output( $lang->date( $this->ts, false, $pref ) );
+ }
+ $this->output( "\n$code time: " );
+ foreach ( $prefs as $index => $pref ) {
+ if ( $index > 0 ) {
+ $this->output( ' | ' );
+ }
+ $this->output( $lang->time( $this->ts, false, $pref ) );
+ }
+ $this->output( "\n$code both: " );
+ foreach ( $prefs as $index => $pref ) {
+ if ( $index > 0 ) {
+ $this->output( ' | ' );
+ }
+ $this->output( $lang->timeanddate( $this->ts, false, $pref ) );
+ }
+ $this->output( "\n\n" );
}
- print $lang->timeanddate( $ts, false, $pref );
}
- print "\n\n";
}
-
+$maintClass = "DateFormats";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/language/diffLanguage.php b/maintenance/language/diffLanguage.php
index 9d395b3c..bbdb8653 100644
--- a/maintenance/language/diffLanguage.php
+++ b/maintenance/language/diffLanguage.php
@@ -69,7 +69,7 @@ function ucfirstlcrest($string) {
/**
* Return a $wgAllmessages array shipped in MediaWiki
- * @param string $languageCode Formated language code
+ * @param $languageCode String: formated language code
* @return array The MediaWiki default $wgAllMessages array requested
*/
function getMediawikiMessages($languageCode = 'En') {
@@ -93,8 +93,8 @@ function getMediawikiMessages($languageCode = 'En') {
/**
* Return a $wgAllmessages array in a given file. Language of the array
* need to be given cause we can not detect which language it provides
- * @param string $filename Filename of the file containing a message array
- * @param string $languageCode Language of the external array
+ * @param $filename String: filename of the file containing a message array
+ * @param $languageCode String: language of the external array
* @return array A $wgAllMessages array from an external file.
*/
function getExternalMessages($filename, $languageCode) {
diff --git a/maintenance/language/digit2html.php b/maintenance/language/digit2html.php
index b020d812..54630af0 100644
--- a/maintenance/language/digit2html.php
+++ b/maintenance/language/digit2html.php
@@ -1,29 +1,61 @@
<?php
/**
- * @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
+ *
* @ingroup MaintenanceLanguage
*/
-require( '../commandLine.inc' );
+require_once( dirname(__FILE__).'/../Maintenance.php' );
-# A list of unicode numerals is available at:
-# http://www.fileformat.info/info/unicode/category/Nd/list.htm
-$langs = array( 'Ar', 'As', 'Bh', 'Bo', 'Dz', 'Fa', 'Gu', 'Hi', 'Km', 'Kn', 'Ks', 'Lo', 'Ml', 'Mr', 'Ne', 'New', 'Or', 'Pa', 'Pi', 'Sa' );
+class Digit2Html extends Maintenance {
-foreach( $langs as $code ) {
- $filename = Language::getMessagesFileName( $code );
- echo "Loading language [$code] ... ";
- unset( $digitTransformTable );
- require_once( $filename );
- if( !isset( $digitTransformTable ) ) {
- print "\$digitTransformTable not found\n";
- continue;
+ # A list of unicode numerals is available at:
+ # http://www.fileformat.info/info/unicode/category/Nd/list.htm
+ private $mLangs = array(
+ 'Ar', 'As', 'Bh', 'Bo', 'Dz',
+ 'Fa', 'Gu', 'Hi', 'Km', 'Kn',
+ 'Ks', 'Lo', 'Ml', 'Mr', 'Ne',
+ 'New', 'Or', 'Pa', 'Pi', 'Sa'
+ );
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Check digit transformation";
}
- print "OK\n\$digitTransformTable = array(\n";
- foreach( $digitTransformTable as $latin => $translation ) {
- $htmlent = utf8ToHexSequence( $translation );
- print "'$latin' => '$translation', # &#x$htmlent;\n";
+ public function execute() {
+ foreach( $this->mLangs as $code ) {
+ $filename = Language::getMessagesFileName( $code );
+ $this->output( "Loading language [$code] ... " );
+ unset( $digitTransformTable );
+ require_once( $filename );
+ if( !isset( $digitTransformTable ) ) {
+ $this->error( "\$digitTransformTable not found for lang: $code" );
+ continue;
+ }
+
+ $this->output( "OK\n\$digitTransformTable = array(\n" );
+ foreach( $digitTransformTable as $latin => $translation ) {
+ $htmlent = utf8ToHexSequence( $translation );
+ $this->output( "'$latin' => '$translation', # &#x$htmlent;\n" );
+ }
+ $this->output( ");\n" );
+ }
}
- print ");\n";
}
+
+$maintClass = "Digit2Html";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/language/dumpMessages.php b/maintenance/language/dumpMessages.php
index 35aeeb75..a0f0a9ab 100644
--- a/maintenance/language/dumpMessages.php
+++ b/maintenance/language/dumpMessages.php
@@ -1,18 +1,44 @@
<?php
/**
- * @todo document
- * @file
+ * Dump an entire language, using the keys from English
+ * so we get all the values, not just the customized ones
+ *
+ * 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
+ *
* @ingroup MaintenanceLanguage
+ * @todo Make this more useful, right now just dumps $wgContentLang
*/
-/** */
-require_once( dirname(__FILE__).'/../commandLine.inc' );
-$messages = array();
-$wgEnglishMessages = array_keys( Language::getMessagesFor( 'en' ) );
-foreach ( $wgEnglishMessages as $key ) {
- $messages[$key] = wfMsg( $key );
-}
-print "MediaWiki $wgVersion language file\n";
-print serialize( $messages );
+require_once( dirname(__FILE__) . '/../Maintenance.php' );
+class DumpMessages extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Dump an entire language, using the keys from English";
+ }
+
+ public function execute() {
+ $messages = array();
+ foreach ( array_keys( Language::getMessagesFor( 'en' ) ) as $key ) {
+ $messages[$key] = wfMsg( $key );
+ }
+ $this->output( "MediaWiki $wgVersion language file\n" );
+ $this->output( serialize( $messages ) );
+ }
+}
+$maintClass = "DumpMessages";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/language/generateNormalizerData.php b/maintenance/language/generateNormalizerData.php
new file mode 100644
index 00000000..d6b7aaa6
--- /dev/null
+++ b/maintenance/language/generateNormalizerData.php
@@ -0,0 +1,137 @@
+<?php
+
+require_once( dirname( __FILE__ ) . '/../Maintenance.php' );
+
+require_once( dirname( __FILE__ ) . '/../../includes/normal/UtfNormalUtil.php' );
+
+/**
+ * Generates normalizer data files for Arabic and Malayalam.
+ * For NFC see includes/normal.
+ */
+class GenerateNormalizerData extends Maintenance {
+ var $dataFile;
+
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( 'unicode-data-file', 'The local location of the data file ' .
+ 'from http://unicode.org/Public/UNIDATA/UnicodeData.txt', false, true );
+ }
+
+ 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>" );
+ exit( 1 );
+ }
+ } else {
+ $this->dataFile = $this->getOption( 'unicode-data-file' );
+ if ( !file_exists( $this->dataFile ) ) {
+ $this->error( 'Unable to find the specified data file.' );
+ exit( 1 );
+ }
+ }
+
+ $this->generateArabic();
+ $this->generateMalayalam();
+ }
+
+ function generateArabic() {
+ $file = fopen( $this->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',
+ 'Bidi_Mirrored',
+ 'Unicode_1_Name',
+ 'ISO_Comment',
+ 'Simple_Uppercase_Mapping',
+ 'Simple_Lowercase_Mapping',
+ 'Simple_Titlecase_Mapping'
+ );
+
+ $pairs = 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];
+ }
+
+ $code = base_convert( $data['Code'], 16, 10 );
+ if ( ( $code >= 0xFB50 && $code <= 0xFDFF ) # Arabic presentation forms A
+ || ( $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 ) )
+ {
+ $this->error( "Can't parse Decomposition_Type/Mapping on line $lineNum" );
+ $this->error( $line );
+ continue;
+ }
+
+ $source = hexSequenceToUtf8( $data['Code'] );
+ $dest = hexSequenceToUtf8( $m[2] );
+ $pairs[$source] = $dest;
+ }
+ }
+
+ global $IP;
+ 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';
+require_once( DO_MAINTENANCE );
+
diff --git a/maintenance/language/lang2po.php b/maintenance/language/lang2po.php
index 1009ed6c..c7484d63 100644
--- a/maintenance/language/lang2po.php
+++ b/maintenance/language/lang2po.php
@@ -6,15 +6,27 @@
* - generate .po header
* - fix escaping of \
*
- * @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
+ *
* @ingroup MaintenanceLanguage
*/
-$optionsWithArgs[] = 'lang';
-
/** This is a command line script */
-require_once(dirname(__FILE__).'/../commandLine.inc');
-require_once(dirname(__FILE__).'/languages.inc');
+require_once(dirname(__FILE__) . '/../Maintenance.php' );
+require_once(dirname(__FILE__) . '/languages.inc' );
define('ALL_LANGUAGES', true);
define('XGETTEXT_BIN', 'xgettext');
@@ -26,28 +38,45 @@ define('MSGMERGE_OPTIONS', ' -v ');
define('LOCALE_OUTPUT_DIR', $IP.'/locale');
+class Lang2Po extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "";
+ $this->addOption( 'lang', 'a lang code you want to generate a .po for (default: all langs)', false, true );
+ }
-if( isset($options['help']) ) { usage(); wfDie(); }
-// default output is WikiText
-if( !isset($options['lang']) ) { $options['lang'] = ALL_LANGUAGES; }
-
-function usage() {
-print <<<END
-Usage: php lang2po.php [--help] [--lang=<langcode>] [--stdout]
- --help: this message.
- --lang: a lang code you want to generate a .po for (default: all languages).
-
-END;
-}
-
+ public function execute() {
+ // Generate a template .pot based on source tree
+ $this->output( "Getting 'gettext' default messages from sources:" );
+ $this->generatePot();
+ $this->output( "done.\n" );
+
+
+ $langTool = new languages();
+ if( $this->getOption( 'lang', ALL_LANGUAGES ) === ALL_LANGUAGES ) {
+ $codes = $langTool->getLanguages();
+ } else {
+ $codes = array( $this->getOption( 'lang' ) );
+ }
+
+ // Do all languages
+ foreach ( $codes as $langcode) {
+ $this->output( "Loading messages for $langcode:\n" );
+ if( !$this->generatePo($langcode, $langTool->getMessages($langcode) ) ) {
+ $this->error( "ERROR: Failed to write file." );
+ } else {
+ $this->output( "Applying template:" );
+ $this->applyPot($langcode);
+ }
+ }
+ }
-/**
- * Return a dummy header for later edition.
- * @return string A dummy header
- */
-function poHeader() {
-return
-'# SOME DESCRIPTIVE TITLE.
+ /**
+ * Return a dummy header for later edition.
+ * @return string A dummy header
+ */
+ private function poHeader() {
+ return '# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2005 MediaWiki
# This file is distributed under the same license as the MediaWiki package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
@@ -65,94 +94,72 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
';
-}
-
-/**
- * generate and write a file in .po format.
- *
- * @param string $langcode Code of a language it will process.
- * @param array &$messages Array containing the various messages.
- * @return string Filename where stuff got saved or false.
- */
-function generatePo($langcode, $messages) {
- $data = poHeader();
-
- // Generate .po entries
- foreach($messages['all'] as $identifier => $content) {
- $data .= "msgid \"$identifier\"\n";
-
- // Escape backslashes
- $tmp = str_replace('\\', '\\\\', $content);
- // Escape doublelquotes
- $tmp = preg_replace( "/(?<!\\\\)\"/", '\"', $tmp);
- // Rewrite multilines to gettext format
- $tmp = str_replace("\n", "\"\n\"", $tmp);
-
- $data .= 'msgstr "'. $tmp . "\"\n\n";
}
- // Write the content to a file in locale/XX/messages.po
- $dir = LOCALE_OUTPUT_DIR.'/'.$langcode;
- if( !is_dir($dir) ) { mkdir( $dir, 0770 ); }
- $filename = $dir.'/fromlanguagefile.po';
-
- $file = fopen( $filename , 'wb' );
- if( fwrite( $file, $data ) ) {
- fclose( $file );
- return $filename;
- } else {
- fclose( $file );
- return false;
+ /**
+ * generate and write a file in .po format.
+ *
+ * @param string $langcode Code of a language it will process.
+ * @param array &$messages Array containing the various messages.
+ * @return string Filename where stuff got saved or false.
+ */
+ private function generatePo($langcode, $messages) {
+ $data = $this->poHeader();
+
+ // Generate .po entries
+ foreach( $messages['all'] as $identifier => $content ) {
+ $data .= "msgid \"$identifier\"\n";
+
+ // Escape backslashes
+ $tmp = str_replace('\\', '\\\\', $content);
+ // Escape doublelquotes
+ $tmp = preg_replace( "/(?<!\\\\)\"/", '\"', $tmp);
+ // Rewrite multilines to gettext format
+ $tmp = str_replace("\n", "\"\n\"", $tmp);
+
+ $data .= 'msgstr "'. $tmp . "\"\n\n";
+ }
+
+ // Write the content to a file in locale/XX/messages.po
+ $dir = LOCALE_OUTPUT_DIR.'/'.$langcode;
+ if( !is_dir($dir) ) { mkdir( $dir, 0770 ); }
+ $filename = $dir.'/fromlanguagefile.po';
+
+ $file = fopen( $filename , 'wb' );
+ if( fwrite( $file, $data ) ) {
+ fclose( $file );
+ return $filename;
+ } else {
+ fclose( $file );
+ return false;
+ }
}
-}
-
-function generatePot() {
- global $IP;
- $curdir = getcwd();
- chdir($IP);
- exec( XGETTEXT_BIN
- .' '.XGETTEXT_OPTIONS
- .' -o '.LOCALE_OUTPUT_DIR.'/wfMsg.pot'
- .' includes/*php'
- );
- chdir($curdir);
-}
-
-function applyPot($langcode) {
- $langdir = LOCALE_OUTPUT_DIR.'/'.$langcode;
-
- $from = $langdir.'/fromlanguagefile.po';
- $pot = LOCALE_OUTPUT_DIR.'/wfMsg.pot';
- $dest = $langdir.'/messages.po';
- // Merge template and generate file to get final .po
- exec(MSGMERGE_BIN.MSGMERGE_OPTIONS." $from $pot -o $dest ");
- // delete no more needed file
-// unlink($from);
-}
-
-// Generate a template .pot based on source tree
-echo "Getting 'gettext' default messages from sources:";
-generatePot();
-echo "done.\n";
-
-
-$langTool = new languages();
-
-if( $options['lang'] === ALL_LANGUAGES ) {
- $codes = $langTool->getLanguages();
-} else {
- $codes = array( $options['lang'] );
-}
-
-// Do all languages
-foreach ( $codes as $langcode) {
- echo "Loading messages for $langcode:\n";
- if( ! generatePo($langcode, $langTool->getMessages($langcode) ) ) {
- echo "ERROR: Failed to write file.\n";
- } else {
- echo "Applying template:";
- applyPot($langcode);
+ private function generatePot() {
+ global $IP;
+ $curdir = getcwd();
+ chdir($IP);
+ exec( XGETTEXT_BIN
+ .' '.XGETTEXT_OPTIONS
+ .' -o '.LOCALE_OUTPUT_DIR.'/wfMsg.pot'
+ .' includes/*php'
+ );
+ chdir($curdir);
+ }
+
+ private function applyPot($langcode) {
+ $langdir = LOCALE_OUTPUT_DIR.'/'.$langcode;
+
+ $from = $langdir.'/fromlanguagefile.po';
+ $pot = LOCALE_OUTPUT_DIR.'/wfMsg.pot';
+ $dest = $langdir.'/messages.po';
+
+ // Merge template and generate file to get final .po
+ exec(MSGMERGE_BIN.MSGMERGE_OPTIONS." $from $pot -o $dest ");
+ // delete no more needed file
+ // unlink($from);
}
}
+$maintClass = "Lang2Po";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/language/langmemusage.php b/maintenance/language/langmemusage.php
index 9bfb3cdc..71135474 100644
--- a/maintenance/language/langmemusage.php
+++ b/maintenance/language/langmemusage.php
@@ -3,31 +3,57 @@
* Dumb program that tries to get the memory usage
* for each language file.
*
- * @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
+ *
* @ingroup MaintenanceLanguage
*/
/** This is a command line script */
-require_once(dirname(__FILE__).'/../commandLine.inc');
-require_once(dirname(__FILE__).'/languages.inc');
+require_once( dirname(__FILE__) . '/../Maintenance.php' );
+require_once( dirname(__FILE__) . '/languages.inc' );
-$langtool = new languages();
+class LangMemUsage extends Maintenance {
-if ( ! function_exists( 'memory_get_usage' ) )
- wfDie( "You must compile PHP with --enable-memory-limit\n" );
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Dumb program that tries to get the memory usage\n" .
+ "for each language file";
+ }
-$memlast = $memstart = memory_get_usage();
+ public function execute() {
+ if ( !function_exists( 'memory_get_usage' ) )
+ $this->error( "You must compile PHP with --enable-memory-limit", true );
-print 'Base memory usage: '.$memstart."\n";
+ $langtool = new languages();
+ $memlast = $memstart = memory_get_usage();
-foreach ( $langtool->getLanguages() as $langcode ) {
- Language::factory( $langcode );
- $memstep = memory_get_usage();
- printf( "%12s: %d\n", $langcode, ($memstep- $memlast) );
- $memlast = $memstep;
-}
+ $this->output( "Base memory usage: $memstart\n" );
+
+ foreach ( $langtool->getLanguages() as $langcode ) {
+ Language::factory( $langcode );
+ $memstep = memory_get_usage();
+ $this->output( sprintf( "%12s: %d\n", $langcode, ($memstep- $memlast) ) );
+ $memlast = $memstep;
+ }
-$memend = memory_get_usage();
+ $memend = memory_get_usage();
-echo ' Total Usage: '.($memend - $memstart)."\n";
+ $this->output( ' Total Usage: '.($memend - $memstart)."\n" );
+ }
+}
+$maintClass = "LangMemUsage";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/language/languages.inc b/maintenance/language/languages.inc
index 6159e844..98464292 100644
--- a/maintenance/language/languages.inc
+++ b/maintenance/language/languages.inc
@@ -14,6 +14,7 @@ class languages {
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
@@ -76,6 +77,7 @@ class languages {
*/
protected function loadFile( $code ) {
if ( isset( $this->mRawMessages[$code] ) &&
+ isset( $this->mFallback[$code] ) &&
isset( $this->mNamespaceNames[$code] ) &&
isset( $this->mNamespaceAliases[$code] ) &&
isset( $this->mMagicWords[$code] ) &&
@@ -83,6 +85,7 @@ class languages {
return;
}
$this->mRawMessages[$code] = array();
+ $this->mFallback[$code] = '';
$this->mNamespaceNames[$code] = array();
$this->mNamespaceAliases[$code] = array();
$this->mMagicWords[$code] = array();
@@ -93,6 +96,9 @@ class languages {
if ( isset( $messages ) ) {
$this->mRawMessages[$code] = $messages;
}
+ if ( isset( $fallback ) ) {
+ $this->mFallback[$code] = $fallback;
+ }
if ( isset( $namespaceNames ) ) {
$this->mNamespaceNames[$code] = $namespaceNames;
}
@@ -207,6 +213,18 @@ class languages {
}
/**
+ * Get fallback language code for a specific language.
+ *
+ * @param $code The language code.
+ *
+ * @return Fallback code.
+ */
+ public function getFallback( $code ) {
+ $this->loadFile( $code );
+ return $this->mFallback[$code];
+ }
+
+ /**
* Get namespace names for a specific language.
*
* @param $code The language code.
diff --git a/maintenance/language/makeMessageDB.php b/maintenance/language/makeMessageDB.php
deleted file mode 100644
index f853b395..00000000
--- a/maintenance/language/makeMessageDB.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/**
- * Proof of principle script
- */
-
-require( dirname( __FILE__ ) . '/../commandLine.inc' );
-
-$obj = new MakeMessagesDB;
-$obj->run();
-
-class MakeMessagesDB {
-
- function run() {
- global $wgExtensionMessagesFiles, $wgMessageCache, $IP;
-
- $nameHash = md5( implode( "\n", array_keys( $wgExtensionMessagesFiles ) ) );
- $dir = "$IP/cache/ext-msgs";
- wfMkdirParents( $dir );
- $db = dba_open( "$dir/$nameHash.cdb", 'n', 'cdb' );
- if ( !$db ) {
- echo "Cannot open DB file\n";
- exit( 1 );
- }
-
- # Load extension messages
- foreach ( $wgExtensionMessagesFiles as $file ) {
- $messages = $magicWords = array();
- require( $file );
- foreach ( $messages as $lang => $unused ) {
- $wgMessageCache->processMessagesArray( $messages, $lang );
- }
- }
-
- # Write them to the file
- foreach ( $wgMessageCache->mExtensionMessages as $lang => $messages ) {
- foreach ( $messages as $key => $text ) {
- dba_insert( "$lang:$key", $text, $db );
- }
- }
-
- dba_close( $db );
- }
-}
-
diff --git a/maintenance/language/messageTypes.inc b/maintenance/language/messageTypes.inc
index d155db94..9b979b19 100644
--- a/maintenance/language/messageTypes.inc
+++ b/maintenance/language/messageTypes.inc
@@ -25,6 +25,7 @@ $wgIgnoredMessages = array(
'accesskey-ca-viewsource',
'accesskey-ca-history',
'accesskey-ca-protect',
+ 'accesskey-ca-unprotect',
'accesskey-ca-delete',
'accesskey-ca-undelete',
'accesskey-ca-move',
@@ -35,6 +36,7 @@ $wgIgnoredMessages = array(
'accesskey-search-fulltext',
'accesskey-p-logo',
'accesskey-n-mainpage',
+ 'accesskey-n-mainpage-description',
'accesskey-n-portal',
'accesskey-n-currentevents',
'accesskey-n-recentchanges',
@@ -65,10 +67,10 @@ $wgIgnoredMessages = array(
'accesskey-preview',
'accesskey-diff',
'accesskey-compareselectedversions',
- 'accesskey-visualcomparison',
'accesskey-watch',
'accesskey-upload',
'addsection',
+ 'talkpageheader',
'anonnotice',
'autoblock_whitelist',
'searchmenu-help',
@@ -152,8 +154,12 @@ $wgIgnoredMessages = array(
'fewestrevisions-summary',
'upload-summary',
'newuserlogentry',
- 'restrictlogpage',
'wantedtemplates-summary',
+ 'activeusers-summary',
+ 'search-summary',
+ 'editpage-tos-summary',
+ 'addsection-preload',
+ 'addsection-editintro',
);
/** Optional messages, which may be translated only if changed in the target language. */
@@ -208,6 +214,7 @@ $wgOptionalMessages = array(
'skinname-chick',
'skinname-simple',
'skinname-modern',
+ 'skinname-vector',
'common.css',
'standard.css',
'nostalgia.css',
@@ -217,6 +224,7 @@ $wgOptionalMessages = array(
'chick.css',
'simple.css',
'modern.css',
+ 'vector.css',
'print.css',
'handheld.css',
'common.js',
@@ -228,6 +236,7 @@ $wgOptionalMessages = array(
'chick.js',
'simple.js',
'modern.js',
+ 'vector.js',
'widthheight',
'exif-fnumber-format',
'exif-focallength-format',
@@ -320,6 +329,7 @@ $wgOptionalMessages = array(
'hebrew-calendar-m10-gen',
'hebrew-calendar-m11-gen',
'hebrew-calendar-m12-gen',
+ 'version-svn-revision',
'catseparator',
'semicolon-separator',
'comma-separator',
@@ -328,15 +338,21 @@ $wgOptionalMessages = array(
'word-separator',
'ellipsis',
'percent',
+ 'parentheses',
'autocomment-prefix',
'listgrouprights-right-display',
+ 'listgrouprights-right-revoked',
'timezone-utc',
'whatlinkshere-backlink',
'recentchangeslinked-backlink',
+ 'unpatrolledletter',
'diff-with-additional',
'pagetitle-view-mainpage',
'trackback',
'trackbackexcerpt',
+ 'prefs-registration-date-time',
+ 'prefs-memberingroups-type',
+ 'shared-repo-name-wikimediacommons',
);
/** EXIF messages, which may be set as optional in several checks, but are generally mandatory */
@@ -559,6 +575,8 @@ $wgEXIFMessages = array(
'exif-gpslatitude-s',
'exif-gpslongitude-e',
'exif-gpslongitude-w',
+ 'exif-gpsaltitude-0',
+ 'exif-gpsaltitude-1',
'exif-gpsstatus-a',
'exif-gpsstatus-v',
'exif-gpsmeasuremode-2',
@@ -566,6 +584,9 @@ $wgEXIFMessages = array(
'exif-gpsspeed-k',
'exif-gpsspeed-m',
'exif-gpsspeed-n',
+ 'exif-gpsdestdistance-k',
+ 'exif-gpsdestdistance-m',
+ 'exif-gpsdestdistance-n',
'exif-gpsdirection-t',
'exif-gpsdirection-m',
);
diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc
index fb22bc9c..25bc8fd0 100644
--- a/maintenance/language/messages.inc
+++ b/maintenance/language/messages.inc
@@ -41,6 +41,7 @@ $wgMessageStructure = array(
'tog-enotifminoredits',
'tog-enotifrevealaddr',
'tog-shownumberswatching',
+ 'tog-oldsig',
'tog-fancysig',
'tog-externaleditor',
'tog-externaldiff',
@@ -65,6 +66,13 @@ $wgMessageStructure = array(
'underline-never',
'underline-default',
),
+ 'editfont' => array(
+ 'editfont-style',
+ 'editfont-default',
+ 'editfont-monospace',
+ 'editfont-sansserif',
+ 'editfont-serif',
+ ),
'dates' => array(
'sunday',
'monday',
@@ -133,6 +141,8 @@ $wgMessageStructure = array(
'category-file-count',
'category-file-count-limited',
'listingcontinuesabbrev',
+ 'index-category',
+ 'noindex-category',
),
'mainpage' => array(
'linkprefix',
@@ -144,6 +154,14 @@ $wgMessageStructure = array(
'article',
'newwindow',
'cancel',
+ 'moredotdotdot',
+ 'mypage',
+ 'mytalk',
+ 'anontalk',
+ 'navigation',
+ 'and',
+ ),
+ 'cologneblue' => array(
'qbfind',
'qbbrowse',
'qbedit',
@@ -151,15 +169,37 @@ $wgMessageStructure = array(
'qbpageinfo',
'qbmyoptions',
'qbspecialpages',
- 'moredotdotdot',
- 'mypage',
- 'mytalk',
- 'anontalk',
- 'navigation',
- 'and',
+ 'faq',
+ 'faqpage',
+ 'sitetitle',
+ 'sitesubtitle',
),
- 'metadata_help' => array(
- 'metadata_help',
+ 'vector' => array(
+ 'vector-action-addsection',
+ 'vector-action-delete',
+ 'vector-action-move',
+ 'vector-action-protect',
+ 'vector-action-undelete',
+ 'vector-action-unprotect',
+ 'vector-namespace-category',
+ 'vector-namespace-help',
+ 'vector-namespace-image',
+ 'vector-namespace-main',
+ 'vector-namespace-media',
+ 'vector-namespace-mediawiki',
+ 'vector-namespace-project',
+ 'vector-namespace-special',
+ 'vector-namespace-talk',
+ 'vector-namespace-template',
+ 'vector-namespace-user',
+ 'vector-view-create',
+ 'vector-view-edit',
+ 'vector-view-history',
+ 'vector-view-view',
+ 'vector-view-viewsource',
+ 'actions',
+ 'namespaces',
+ 'variants',
),
'miscellaneous2' => array(
'errorpagetitle',
@@ -211,18 +251,19 @@ $wgMessageStructure = array(
'otherlanguages',
'redirectedfrom',
'redirectpagesub',
+ 'talkpageheader',
'lastmodifiedat',
'viewcount',
'protectedpage',
'jumpto',
'jumptonavigation',
'jumptosearch',
+ 'view-pool-error',
),
'links' => array(
'aboutsite',
'aboutpage',
'copyright',
- 'copyrightpagename',
'copyrightpage',
'currentevents',
'currentevents-url',
@@ -230,8 +271,6 @@ $wgMessageStructure = array(
'disclaimerpage',
'edithelp',
'edithelppage',
- 'faq',
- 'faqpage',
'help',
'helppage',
'mainpage',
@@ -253,10 +292,8 @@ $wgMessageStructure = array(
),
'miscellaneous3' => array(
'ok',
- 'sitetitle',
'pagetitle',
'pagetitle-view-mainpage',
- 'sitesubtitle',
'retrievedfrom',
'youhavenewmessages',
'newmessageslink',
@@ -313,9 +350,6 @@ $wgMessageStructure = array(
'databaseerror',
'dberrortext',
'dberrortextcl',
- 'noconnect',
- 'nodb',
- 'cachederror',
'laggedslavemode',
'readonly',
'enterlockreason',
@@ -326,6 +360,8 @@ $wgMessageStructure = array(
'readonly_lag',
'internalerror',
'internalerror_info',
+ 'fileappenderrorread',
+ 'fileappenderror',
'filecopyerror',
'filerenameerror',
'filedeleteerror',
@@ -363,10 +399,8 @@ $wgMessageStructure = array(
'virus-unknownscanner',
),
'login' => array(
- 'logouttitle',
'logouttext',
'welcomecreation',
- 'loginpagetitle',
'yourname',
'yourpassword',
'yourpasswordagain',
@@ -377,6 +411,7 @@ $wgMessageStructure = array(
'nav-login-createaccount',
'loginprompt',
'userlogin',
+ 'userloginnocreate',
'logout',
'userlogout',
'notloggedin',
@@ -388,26 +423,8 @@ $wgMessageStructure = array(
'createaccountmail',
'badretype',
'userexists',
- 'youremail',
- 'username',
- 'uid',
- 'prefs-memberingroups',
- 'yourrealname',
- 'yourlanguage',
- 'yourvariant',
- 'yournick',
- 'badsig',
- 'badsiglength',
- 'yourgender',
- 'gender-unknown',
- 'gender-male',
- 'gender-female',
- 'prefs-help-gender',
- 'email',
- 'prefs-help-realname',
'loginerror',
- 'prefs-help-email',
- 'prefs-help-email-required',
+ 'createaccounterror',
'nocookiesnew',
'nocookieslogin',
'noname',
@@ -416,13 +433,16 @@ $wgMessageStructure = array(
'nosuchuser',
'nosuchusershort',
'nouserspecified',
+ 'login-userblocked',
'wrongpassword',
'wrongpasswordempty',
'passwordtooshort',
+ 'password-name-match',
'mailmypassword',
'passwordremindertitle',
'passwordremindertext',
'noemail',
+ 'noemailcreate',
'passwordsent',
'blocked-mailpassword',
'eauthentsent',
@@ -441,9 +461,11 @@ $wgMessageStructure = array(
'accountcreatedtext',
'createaccount-title',
'createaccount-text',
+ 'usernamehasherror',
'login-throttled',
'loginlanguagelabel',
'loginlanguagelinks',
+ 'suspicious-userlogout',
),
'resetpass' => array(
'resetpass',
@@ -455,16 +477,12 @@ $wgMessageStructure = array(
'retypenew',
'resetpass_submit',
'resetpass_success',
- 'resetpass_bad_temporary',
'resetpass_forbidden',
'resetpass-no-info',
'resetpass-submit-loggedin',
+ 'resetpass-submit-cancel',
'resetpass-wrong-oldpass',
'resetpass-temp-password',
- 'resetpass-log',
- 'resetpass-logtext',
- 'resetpass-logentry',
- 'resetpass-comment',
),
'toolbar' => array(
'bold_sample',
@@ -512,7 +530,6 @@ $wgMessageStructure = array(
'blockededitsource',
'whitelistedittitle',
'whitelistedittext',
- 'confirmedittitle',
'confirmedittext',
'nosuchsectiontitle',
'nosuchsectiontext',
@@ -527,10 +544,14 @@ $wgMessageStructure = array(
'talkpagetext',
'anontalkpagetext',
'noarticletext',
+ 'noarticletext-nopermission',
'noarticletextanon',
'userpage-userdoesnotexist',
+ 'userpage-userdoesnotexist-view',
+ 'blocked-notice-logextract',
'clearyourcache',
- 'usercssjsyoucanpreview',
+ 'usercssyoucanpreview',
+ 'userjsyoucanpreview',
'usercsspreview',
'userjspreview',
'userinvalidcssjstitle',
@@ -553,6 +574,7 @@ $wgMessageStructure = array(
'yourdiff',
'copyrightwarning',
'copyrightwarning2',
+ 'editpage-tos-summary',
'longpagewarning',
'longpageerror',
'readonlywarning',
@@ -570,17 +592,21 @@ $wgMessageStructure = array(
'nocreatetitle',
'nocreatetext',
'nocreate-loggedin',
+ 'sectioneditnotsupported-title',
+ 'sectioneditnotsupported-text',
'permissionserrors',
'permissionserrorstext',
'permissionserrorstext-withaction',
- 'recreate-deleted-warn',
- 'deleted-notice',
- 'deletelog-fulllog',
+ 'recreate-moveddeleted-warn',
+ 'moveddeleted-notice',
+ 'log-fulllog',
'edit-hook-aborted',
'edit-gone-missing',
'edit-conflict',
'edit-no-change',
'edit-already-exists',
+ 'addsection-preload',
+ 'addsection-editintro',
),
'parserwarnings' => array(
'expensive-parserfunction-warning',
@@ -591,6 +617,7 @@ $wgMessageStructure = array(
'post-expand-template-argument-category',
'parser-template-loop-warning',
'parser-template-recursion-depth-warning',
+ 'language-converter-depth-warning',
),
'undo' => array(
'undo-success',
@@ -622,8 +649,8 @@ $wgMessageStructure = array(
'page_last',
'histlegend',
'history-fieldset-title',
+ 'history-show-deleted',
'history_copyright',
- 'deletedrev',
'histfirst',
'histlast',
'historysize',
@@ -639,32 +666,46 @@ $wgMessageStructure = 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-toomanytargets-title',
- 'revdelete-toomanytargets-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-hide-image',
'revdelete-unsuppress',
'revdelete-log',
'revdelete-submit',
@@ -673,7 +714,9 @@ $wgMessageStructure = array(
'revdelete-logaction',
'logdelete-logaction',
'revdelete-success',
+ 'revdelete-failure',
'logdelete-success',
+ 'logdelete-failure',
'revdel-restore',
'pagehist',
'deletedhist',
@@ -686,6 +729,18 @@ $wgMessageStructure = array(
'revdelete-unhid',
'revdelete-log-message',
'logdelete-log-message',
+ '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',
@@ -724,73 +779,17 @@ $wgMessageStructure = array(
'difference',
'lineno',
'compareselectedversions',
- 'visualcomparison',
- 'wikicodecomparison',
+ 'showhideselectedversions',
'editundo',
'diff-multi',
- 'diff-movedto',
- 'diff-styleadded',
- 'diff-added',
- 'diff-changedto',
- 'diff-movedoutof',
- 'diff-styleremoved',
- 'diff-removed',
- 'diff-changedfrom',
- 'diff-src',
- 'diff-withdestination',
- 'diff-with',
- 'diff-with-additional',
- 'diff-with-final',
- 'diff-width',
- 'diff-height',
- 'diff-p',
- 'diff-blockquote',
- 'diff-h1',
- 'diff-h2',
- 'diff-h3',
- 'diff-h4',
- 'diff-h5',
- 'diff-pre',
- 'diff-div',
- 'diff-ul',
- 'diff-ol',
- 'diff-li',
- 'diff-table',
- 'diff-tbody',
- 'diff-tr',
- 'diff-td',
- 'diff-th',
- 'diff-br',
- 'diff-hr',
- 'diff-code',
- 'diff-dl',
- 'diff-dt',
- 'diff-dd',
- 'diff-input',
- 'diff-form',
- 'diff-img',
- 'diff-span',
- 'diff-a',
- 'diff-i',
- 'diff-b',
- 'diff-strong',
- 'diff-em',
- 'diff-font',
- 'diff-big',
- 'diff-del',
- 'diff-tt',
- 'diff-sub',
- 'diff-sup',
- 'diff-strike',
),
'search' => array(
+ 'search-summary',
'searchresults',
'searchresults-title',
'searchresulttext',
'searchsubtitle',
'searchsubtitleinvalid',
- 'noexactmatch',
- 'noexactmatch-nocreate',
'toomanymatches',
'titlematches',
'notitlematches',
@@ -809,7 +808,6 @@ $wgMessageStructure = array(
'searchmenu-prefix',
'searchmenu-help',
'searchprofile-articles',
- 'searchprofile-articles-and-proj',
'searchprofile-project',
'searchprofile-images',
'searchprofile-everything',
@@ -819,8 +817,6 @@ $wgMessageStructure = array(
'searchprofile-images-tooltip',
'searchprofile-everything-tooltip',
'searchprofile-advanced-tooltip',
- 'prefs-search-nsdefault',
- 'prefs-search-nscustom',
'search-result-size',
'search-result-score',
'search-redirect',
@@ -834,11 +830,12 @@ $wgMessageStructure = array(
'search-mwsuggest-disabled',
'search-relatedarticle',
'mwsuggest-disable',
+ 'searcheverything-enable',
'searchrelated',
'searchall',
'showingresults',
'showingresultsnum',
- 'showingresultstotal',
+ 'showingresultsheader',
'nonefound',
'search-nonefound',
'powersearch',
@@ -846,6 +843,9 @@ $wgMessageStructure = array(
'powersearch-ns',
'powersearch-redir',
'powersearch-field',
+ 'powersearch-togglelabel',
+ 'powersearch-toggleall',
+ 'powersearch-togglenone',
'search-external',
'searchdisabled',
'googlesearch',
@@ -853,6 +853,14 @@ $wgMessageStructure = array(
'opensearch' => array(
'opensearch-desc',
),
+ 'quickbar' => array(
+ 'qbsettings',
+ 'qbsettings-none',
+ 'qbsettings-fixedleft',
+ 'qbsettings-fixedright',
+ 'qbsettings-floatingleft',
+ 'qbsettings-floatingright',
+ ),
'preferences' => array(
'preferences',
'preferences-summary',
@@ -860,29 +868,12 @@ $wgMessageStructure = array(
'prefs-edits',
'prefsnologin',
'prefsnologintext',
- 'prefsreset',
- 'qbsettings',
- 'qbsettings-none',
- 'qbsettings-fixedleft',
- 'qbsettings-fixedright',
- 'qbsettings-floatingleft',
- 'qbsettings-floatingright',
'changepassword',
- 'skin',
+ 'prefs-skin',
'skin-preview',
- 'math',
- 'dateformat',
+ 'prefs-math',
'datedefault',
- 'datetime',
- 'math_failure',
- 'math_unknown_error',
- 'math_unknown_function',
- 'math_lexing_error',
- 'math_syntax_error',
- 'math_image_error',
- 'math_bad_tmpdir',
- 'math_bad_output',
- 'math_notexvc',
+ 'prefs-datetime',
'prefs-personal',
'prefs-rc',
'prefs-watchlist',
@@ -890,12 +881,15 @@ $wgMessageStructure = array(
'prefs-watchlist-days-max',
'prefs-watchlist-edits',
'prefs-watchlist-edits-max',
- 'prefs-misc',
+ 'prefs-watchlist-token',
+ 'prefs-misc', // continue checking if used from here on (r49916)
'prefs-resetpass',
+ 'prefs-email',
+ 'prefs-rendering',
'saveprefs',
'resetprefs',
'restoreprefs',
- 'textboxsize',
+ 'prefs-editing',
'prefs-edit-boxsize',
'rows',
'columns',
@@ -907,11 +901,11 @@ $wgMessageStructure = array(
'recentchangesdays',
'recentchangesdays-max',
'recentchangescount',
+ 'prefs-help-recentchangescount',
+ 'prefs-help-watchlist-token',
'savedprefs',
'timezonelegend',
- 'timezonetext',
'localtime',
- 'timezoneselect',
'timezoneuseserverdefault',
'timezoneuseoffset',
'timezoneoffset',
@@ -933,9 +927,47 @@ $wgMessageStructure = array(
'defaultns',
'default',
'defaultns',
- 'files',
+ 'prefs-files',
'prefs-custom-css',
'prefs-custom-js',
+ 'prefs-reset-intro',
+ 'prefs-emailconfirm-label',
+ 'prefs-textboxsize',
+ 'youremail',
+ 'username',
+ 'uid',
+ 'prefs-memberingroups',
+ 'prefs-memberingroups-type',
+ 'prefs-registration',
+ 'prefs-registration-date-time',
+ 'yourrealname',
+ 'yourlanguage',
+ 'yourvariant',
+ 'yournick',
+ 'prefs-help-signature',
+ 'badsig',
+ 'badsiglength',
+ 'yourgender',
+ 'gender-unknown',
+ 'gender-male',
+ 'gender-female',
+ 'prefs-help-gender',
+ 'email',
+ 'prefs-help-realname',
+ 'prefs-help-email',
+ 'prefs-help-email-required',
+ 'prefs-info',
+ 'prefs-i18n',
+ 'prefs-signature',
+ 'prefs-dateformat',
+ 'prefs-timeoffset',
+ 'prefs-advancedediting',
+ 'prefs-advancedrc',
+ 'prefs-advancedrendering',
+ 'prefs-advancedsearchoptions',
+ 'prefs-advancedwatchlist',
+ 'prefs-display',
+ 'prefs-diffs',
),
'userrights' => array(
'userrights',
@@ -947,6 +979,7 @@ $wgMessageStructure = array(
'userrights-editusergroup',
'saveusergroups',
'userrights-groupsmember',
+ 'userrights-groupsmember-auto',
'userrights-groups-help',
'userrights-reason',
'userrights-no-interwiki',
@@ -1010,6 +1043,7 @@ $wgMessageStructure = array(
'right-bigdelete',
'right-deleterevision',
'right-deletedhistory',
+ 'right-deletedtext',
'right-browsearchive',
'right-undelete',
'right-suppressrevision',
@@ -1023,6 +1057,8 @@ $wgMessageStructure = array(
'right-editprotected',
'right-editinterface',
'right-editusercssjs',
+ 'right-editusercss',
+ 'right-edituserjs',
'right-rollback',
'right-markbotedits',
'right-noratelimit',
@@ -1039,6 +1075,8 @@ $wgMessageStructure = array(
'right-siteadmin',
'right-reset-passwords',
'right-override-export-depth',
+ 'right-versiondetail',
+ 'right-sendemail',
),
'rightslog' => array(
'rightslog',
@@ -1089,6 +1127,15 @@ $wgMessageStructure = array(
'recentchanges-legend',
'recentchangestext',
'recentchanges-feed-description',
+ 'recentchanges-label-legend',
+ 'recentchanges-legend-newpage',
+ 'recentchanges-label-newpage',
+ 'recentchanges-legend-minor',
+ 'recentchanges-label-minor',
+ 'recentchanges-legend-bot',
+ 'recentchanges-label-bot',
+ 'recentchanges-legend-unpatrolled',
+ 'recentchanges-label-unpatrolled',
'rcnote',
'rcnotefrom',
'rclistfrom',
@@ -1106,6 +1153,7 @@ $wgMessageStructure = array(
'minoreditletter',
'newpageletter',
'boteditletter',
+ 'unpatrolledletter',
'sectionlink',
'number_of_watching_users_RCview',
'number_of_watching_users_pageview',
@@ -1118,6 +1166,8 @@ $wgMessageStructure = array(
),
'recentchangeslinked' => array(
'recentchangeslinked',
+ 'recentchangeslinked-feed',
+ 'recentchangeslinked-toolbox',
'recentchangeslinked-title',
'recentchangeslinked-backlink',
'recentchangeslinked-noresult',
@@ -1128,8 +1178,8 @@ $wgMessageStructure = array(
'upload' => array(
'upload',
'uploadbtn',
- 'reupload',
'reuploaddesc',
+ 'upload-tryagain',
'uploadnologin',
'uploadnologintext',
'upload_directory_missing',
@@ -1156,6 +1206,7 @@ $wgMessageStructure = array(
'minlength1',
'illegalfilename',
'badfilename',
+ 'filetype-mime-mismatch',
'filetype-badmime',
'filetype-bad-ie-mime',
'filetype-unwanted-type',
@@ -1167,7 +1218,6 @@ $wgMessageStructure = array(
'fileexists',
'filepageexists',
'fileexists-extension',
- 'fileexists-thumb',
'fileexists-thumbnail-yes',
'file-thumbnail-no',
'fileexists-forbidden',
@@ -1176,6 +1226,7 @@ $wgMessageStructure = array(
'file-deleted-duplicate',
'successfulupload',
'uploadwarning',
+ 'uploadwarning-text',
'savefile',
'uploadedimage',
'overwroteimage',
@@ -1183,11 +1234,14 @@ $wgMessageStructure = array(
'uploaddisabledtext',
'php-uploaddisabledtext',
'uploadscripted',
- 'uploadcorrupt',
'uploadvirus',
+ 'upload-source',
'sourcefilename',
+ 'sourceurl',
'destfilename',
'upload-maxfilesize',
+ 'upload-description',
+ 'upload-options',
'watchthisupload',
'filewasdeleted',
'upload-wasdeleted',
@@ -1201,7 +1255,36 @@ $wgMessageStructure = array(
'upload-file-error-text',
'upload-misc-error',
'upload-misc-error-text',
+ 'upload-too-many-redirects',
+ 'upload-unknown-size',
+ 'upload-http-error',
),
+
+ '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',
+ ),
+
+ 'http-errors' => array(
+ 'http-invalid-url',
+ 'http-invalid-scheme',
+ 'http-request-error',
+ 'http-read-error',
+ 'http-timed-out',
+ 'http-curl-error',
+ 'http-host-unreachable',
+ 'http-bad-status',
+ ),
+
'upload-curl-errors' => array(
'upload-curl-error6',
'upload-curl-error6-text',
@@ -1210,6 +1293,7 @@ $wgMessageStructure = array(
),
'licenses' => array(
'license',
+ 'license-header',
'nolicense',
'licenses',
'license-nopreview',
@@ -1229,6 +1313,7 @@ $wgMessageStructure = array(
'listfiles_count',
),
'filedescription' => array(
+ 'file-anchor-link',
'filehist',
'filehist-help',
'filehist-deleteall',
@@ -1243,6 +1328,7 @@ $wgMessageStructure = array(
'filehist-dimensions',
'filehist-filesize',
'filehist-comment',
+ 'filehist-missing',
'imagelinks',
'linkstoimage',
'linkstoimage-more',
@@ -1251,15 +1337,15 @@ $wgMessageStructure = array(
'redirectstofile',
'duplicatesoffile',
'sharedupload',
- 'shareduploadwiki',
- 'shareduploadwiki-desc',
- 'shareduploadwiki-linktext',
+ 'sharedupload-desc-there',
+ 'sharedupload-desc-here',
'shareddescriptionfollows',
- 'noimage',
- 'noimage-linktext',
+ 'filepage-nofile',
+ 'filepage-nofile-link',
'uploadnewversion-linktext',
'shared-repo-from',
'shared-repo',
+ 'shared-repo-name-wikimediacommons',
),
'filerevert' => array(
'filerevert',
@@ -1288,6 +1374,7 @@ $wgMessageStructure = array(
'filedelete-reason-otherlist',
'filedelete-reason-dropdown',
'filedelete-edit-reasonlist',
+ 'filedelete-maintenance',
),
'mimesearch' => array(
'mimesearch',
@@ -1325,6 +1412,7 @@ $wgMessageStructure = array(
'statistics-header-edits',
'statistics-header-views',
'statistics-header-users',
+ 'statistics-header-hooks',
'statistics-articles',
'statistics-pages',
'statistics-pages-desc',
@@ -1508,6 +1596,7 @@ $wgMessageStructure = array(
'deletedcontribs' => array(
'deletedcontributions',
'deletedcontributions-title',
+ 'sp-deletedcontributions-contribs',
),
'linksearch' => array(
'linksearch',
@@ -1522,6 +1611,18 @@ $wgMessageStructure = 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',
),
'newuserlog' => array(
'newuserlogpage',
@@ -1535,15 +1636,22 @@ $wgMessageStructure = array(
'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',
@@ -1629,6 +1737,7 @@ $wgMessageStructure = array(
'historywarning',
'confirmdeletetext',
'actioncomplete',
+ 'actionfailed',
'deletedtext',
'deletedarticle',
'suppressedarticle',
@@ -1653,6 +1762,7 @@ $wgMessageStructure = array(
'alreadyrolled',
'editcomment',
'revertpage',
+ 'revertpage-nouser',
'rollback-success',
'sessionfailure',
),
@@ -1671,7 +1781,7 @@ $wgMessageStructure = array(
'protectexpiry',
'protect_expiry_invalid',
'protect_expiry_old',
- 'protect-unchain',
+ 'protect-unchain-permissions',
'protect-text',
'protect-locked-blocked',
'protect-locked-dblock',
@@ -1728,6 +1838,7 @@ $wgMessageStructure = array(
'undelete-nodiff',
'undeletebtn',
'undeletelink',
+ 'undeleteviewlink',
'undeletereset',
'undeleteinvert',
'undeletecomment',
@@ -1771,7 +1882,11 @@ $wgMessageStructure = array(
'sp-contributions-newbies-sub',
'sp-contributions-newbies-title',
'sp-contributions-blocklog',
+ 'sp-contributions-deleted',
'sp-contributions-logs',
+ 'sp-contributions-talk',
+ 'sp-contributions-userrights',
+ 'sp-contributions-blocked-notice',
'sp-contributions-search',
'sp-contributions-username',
'sp-contributions-submit',
@@ -1802,6 +1917,7 @@ $wgMessageStructure = array(
),
'block' => array(
'blockip',
+ 'blockip-title',
'blockip-legend',
'blockiptext',
'ipaddress',
@@ -1845,6 +1961,8 @@ $wgMessageStructure = array(
'ipblocklist-sh-addressblocks',
'ipblocklist-summary',
'ipblocklist-submit',
+ 'ipblocklist-localblock',
+ 'ipblocklist-otherblocks',
'blocklistline',
'infiniteblock',
'expiringblock',
@@ -1861,7 +1979,8 @@ $wgMessageStructure = array(
'contribslink',
'autoblocker',
'blocklogpage',
- 'blocklog-fulllog',
+ 'blocklog-showlog',
+ 'blocklog-showsuppresslog',
'blocklogentry',
'reblock-logentry',
'blocklogtext',
@@ -1879,9 +1998,11 @@ $wgMessageStructure = array(
'ipb_hide_invalid',
'ipb_already_blocked',
'ipb-needreblock',
+ 'ipb-otherblocks-header',
'ipb_cant_unblock',
'ipb_blocked_as_range',
'ip_range_invalid',
+ 'ip_range_toolarge',
'blockme',
'proxyblocker',
'proxyblocker-disabled',
@@ -1891,6 +2012,7 @@ $wgMessageStructure = array(
'sorbsreason',
'sorbs_create_account_reason',
'cant-block-while-blocked',
+ 'cant-see-hidden-user'
),
'developertools' => array(
'lockdb',
@@ -1916,6 +2038,7 @@ $wgMessageStructure = array(
'movepagetext',
'movepagetalktext',
'movearticle',
+ 'moveuserpage-warning',
'movenologin',
'movenologintext',
'movenotallowed',
@@ -1966,6 +2089,10 @@ $wgMessageStructure = array(
'imageinvalidfilename',
'fix-double-redirects',
'move-leave-redirect',
+ 'protectedpagemovewarning',
+ 'semiprotectedpagemovewarning',
+ 'move-over-sharedrepo',
+ 'file-exists-sharedrepo',
),
'export' => array(
'export',
@@ -1988,8 +2115,14 @@ $wgMessageStructure = array(
'allmessagescurrent',
'allmessagestext',
'allmessagesnotsupportedDB',
- 'allmessagesfilter',
- 'allmessagesmodified',
+ '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',
@@ -1999,6 +2132,9 @@ $wgMessageStructure = array(
'djvu_no_xml',
'thumbnail_invalid_params',
'thumbnail_dest_directory',
+ 'thumbnail_image-type',
+ 'thumbnail_gd-library',
+ 'thumbnail_image-missing',
),
'import' => array(
'import',
@@ -2060,6 +2196,7 @@ $wgMessageStructure = array(
'accesskey-ca-viewsource',
'accesskey-ca-history',
'accesskey-ca-protect',
+ 'accesskey-ca-unprotect',
'accesskey-ca-delete',
'accesskey-ca-undelete',
'accesskey-ca-move',
@@ -2070,6 +2207,7 @@ $wgMessageStructure = array(
'accesskey-search-fulltext',
'accesskey-p-logo',
'accesskey-n-mainpage',
+ 'accesskey-n-mainpage-description',
'accesskey-n-portal',
'accesskey-n-currentevents',
'accesskey-n-recentchanges',
@@ -2101,7 +2239,6 @@ $wgMessageStructure = array(
'accesskey-preview',
'accesskey-diff',
'accesskey-compareselectedversions',
- 'accesskey-visualcomparison',
'accesskey-watch',
'accesskey-upload',
),
@@ -2122,6 +2259,7 @@ $wgMessageStructure = array(
'tooltip-ca-viewsource',
'tooltip-ca-history',
'tooltip-ca-protect',
+ 'tooltip-ca-unprotect',
'tooltip-ca-delete',
'tooltip-ca-undelete',
'tooltip-ca-move',
@@ -2132,6 +2270,7 @@ $wgMessageStructure = array(
'tooltip-search-fulltext',
'tooltip-p-logo',
'tooltip-n-mainpage',
+ 'tooltip-n-mainpage-description',
'tooltip-n-portal',
'tooltip-n-currentevents',
'tooltip-n-recentchanges',
@@ -2179,6 +2318,7 @@ $wgMessageStructure = array(
'chick.css',
'simple.css',
'modern.css',
+ 'vector.css',
'print.css',
'handheld.css',
),
@@ -2192,6 +2332,7 @@ $wgMessageStructure = array(
'chick.js',
'simple.js',
'modern.js',
+ 'vector.js',
),
'metadata_cc' => array(
'nodublincore',
@@ -2201,10 +2342,12 @@ $wgMessageStructure = array(
'attribution' => array(
'anonymous',
'siteuser',
+ 'anonuser',
'lastmodifiedatby',
'othercontribs',
'others',
'siteusers',
+ 'anonusers',
'creditspage',
'nocredits',
),
@@ -2233,6 +2376,7 @@ $wgMessageStructure = array(
'skinname-chick',
'skinname-simple',
'skinname-modern',
+ 'skinname-vector',
),
'math' => array(
'mw_math_png',
@@ -2242,6 +2386,17 @@ $wgMessageStructure = array(
'mw_math_modern',
'mw_math_mathml',
),
+ 'matherrors' => array(
+ 'math_failure',
+ 'math_unknown_error',
+ 'math_unknown_function',
+ 'math_lexing_error',
+ 'math_syntax_error',
+ 'math_image_error',
+ 'math_bad_tmpdir',
+ 'math_bad_output',
+ 'math_notexvc',
+ ),
'patrolling' => array(
'markaspatrolleddiff',
'markaspatrolledlink',
@@ -2275,9 +2430,6 @@ $wgMessageStructure = array(
'previousdiff',
'nextdiff',
),
- 'visual-comparison' => array(
- 'visual-comparison',
- ),
'media-info' => array(
'mediawarning',
'imagemaxsize',
@@ -2290,6 +2442,8 @@ $wgMessageStructure = array(
'svg-long-desc',
'show-big-image',
'show-big-image-thumb',
+ 'file-info-gif-looped',
+ 'file-info-gif-frames',
),
'newfiles' => array(
'newimages',
@@ -2687,6 +2841,7 @@ $wgMessageStructure = array(
'watchlistall2',
'namespacesall',
'monthsall',
+ 'limitall',
),
'confirmemail' => array(
'confirmemail',
@@ -2743,6 +2898,7 @@ $wgMessageStructure = array(
'word-separator',
'ellipsis',
'percent',
+ 'parentheses',
),
'imgmulti' => array(
'imgmultipageprev',
@@ -2891,6 +3047,7 @@ $wgMessageStructure = array(
'version-hook-name',
'version-hook-subscribedby',
'version-version',
+ 'version-svn-revision',
'version-license',
'version-software',
'version-software-product',
@@ -2958,6 +3115,17 @@ $wgMessageStructure = array(
'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-submit',
+ 'htmlform-reset',
+ 'htmlform-selectorother-other',
+ ),
);
/** Comments for each block */
@@ -2970,11 +3138,13 @@ XHTML id it should only appear once and include characters that are legal
XHTML id names.",
'toggles' => 'User preference toggles',
'underline' => '',
+ 'editfont' => 'Font style option in Special:Preferences',
'dates' => 'Dates',
'categorypages' => 'Categories related messages',
'mainpage' => '',
'miscellaneous1' => '',
- 'metadata_help' => 'Metadata in edit box',
+ '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) and the disambiguation template definition (see disambiguations).',
'badaccess' => '',
@@ -3000,6 +3170,7 @@ XHTML id names.",
'diffs' => 'Diffs',
'search' => 'Search results',
'opensearch' => 'OpenSearch description',
+ 'quickbar' => 'Quickbar',
'preferences' => 'Preferences page',
'userrights' => 'User rights',
'group' => 'Groups',
@@ -3012,6 +3183,8 @@ XHTML id names.",
'recentchangeslinked' => 'Recent changes linked',
'upload' => 'Upload',
'upload-errors' => '',
+ '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',
@@ -3039,6 +3212,7 @@ XHTML id names.",
'deletedcontribs' => 'Special:DeletedContributions',
'linksearch' => 'Special:LinkSearch',
'listusers' => 'Special:ListUsers',
+ 'activeusers' => 'Special:ActiveUsers',
'newuserlog' => 'Special:Log/newusers',
'listgrouprights' => 'Special:ListGroupRights',
'emailuser' => 'E-mail user',
@@ -3073,6 +3247,7 @@ XHTML id names.",
'info' => 'Info page',
'skin' => 'Skin names',
'math' => 'Math options',
+ 'matherrors' => 'Math errors',
'patrolling' => 'Patrolling',
'patrol-log' => 'Patrol log',
'imagedeletion' => 'Image deletion',
@@ -3089,7 +3264,6 @@ Variants for Chinese language",
'variantname-kk' => 'Variants for Kazakh language',
'variantname-ku' => 'Variants for Kurdish language',
'variantname-tg' => 'Variants for Tajiki language',
- 'visual-comparison' => 'Visual comparison',
'media-info' => 'Media information',
'metadata' => 'Metadata',
'exif' => 'EXIF tags',
@@ -3124,7 +3298,7 @@ Variants for Chinese language",
'exif-gpslongitude' => 'Pseudotags used for GPSLongitudeRef and GPSDestLongitudeRef',
'exif-gpsstatus' => '',
'exif-gpsmeasuremode' => '',
- 'exif-gpsspeed' => 'Pseudotags used for GPSSpeedRef and GPSDestDistanceRef',
+ 'exif-gpsspeed' => 'Pseudotags used for GPSSpeedRef',
'exif-gpsdirection' => 'Pseudotags used for GPSTrackRef, GPSImgDirectionRef and GPSDestBearingRef',
'edit-externally' => 'External editor support',
'all' => "'all' in various places, this might be different for inflected languages",
@@ -3157,35 +3331,5 @@ Variants for Chinese language",
'external_images' => 'External image whitelist',
'special-tags' => 'Special:Tags',
'db-error-messages' => 'Database error messages',
-);
-
-/** Short comments for standalone messages */
-$wgMessageComments = array(
- 'hidden-category-category' => 'Name of the category where hidden categories will be listed',
- 'lastmodifiedat' => '$1 date, $2 time',
- 'sitenotice' => 'the equivalent to wgSiteNotice',
- 'history-feed-item-nocomment' => 'user at time',
- 'sharedupload' => '$1 is the repo name, $2 is shareduploadwiki(-desc)',
- 'shared-repo-from' => '$1 is the repository name',
- 'shared-repo' => 'used when shared-repo-NAME does not exist',
- 'editcomment' => 'only shown if there is an edit comment',
- 'revertpage' => 'Additionally available: $3: revid of the revision reverted to, $4: timestamp of the revision reverted to, $5: revid of the revision reverted from, $6: timestamp of the revision reverted from',
- 'lastmodifiedatby' => '$1 date, $2 time, $3 user',
- 'exif-orientation-1' => '0th row: top; 0th column: left',
- 'exif-orientation-2' => '0th row: top; 0th column: right',
- 'exif-orientation-3' => '0th row: bottom; 0th column: right',
- 'exif-orientation-4' => '0th row: bottom; 0th column: left',
- 'exif-orientation-5' => '0th row: left; 0th column: top',
- 'exif-orientation-6' => '0th row: right; 0th column: top',
- 'exif-orientation-7' => '0th row: right; 0th column: bottom',
- 'exif-orientation-8' => '0th row: left; 0th column: bottom',
- 'movepage-moved' => 'The two titles are passed in plain text as $3 and $4 to allow additional goodies in the message.',
- 'ipboptions' => 'display1:time1,display2:time2,...',
- 'protect-expiry-options' => 'display1:time1,display2:time2,...',
- 'metadata-fields' => 'Do not translate list items',
- 'version' => 'Not used as normal message but as header for the special page itself',
- 'userrights' => 'Not used as normal message but as header for the special page itself',
- 'revision-info' => 'Additionally available: $3: revision id',
- 'revision-info-current' => 'Available parameters: $1: timestamp; $2: userlinks; $3: revision id',
- 'nocontribs' => 'Optional parameter: $1 is the user name',
+ 'html-forms' => 'HTML forms',
);
diff --git a/maintenance/language/rebuildLanguage.php b/maintenance/language/rebuildLanguage.php
index 91fda3f4..6c624ca3 100644
--- a/maintenance/language/rebuildLanguage.php
+++ b/maintenance/language/rebuildLanguage.php
@@ -17,18 +17,50 @@ require_once( 'writeMessagesArray.inc' );
* @param $code The language code.
* @param $write Write to the messages file?
* @param $listUnknown List the unknown messages?
- * @param $removeUnKnown Remove the unknown messages?
+ * @param $removeUnknown Remove the unknown messages?
+ * @param $removeDupes Remove the duplicated messages?
+ * @param $dupeMsgSource The source file intended to remove from the array.
*/
-function rebuildLanguage( $code, $write, $listUnknown, $removeUnknown ) {
+function rebuildLanguage( $code, $write, $listUnknown, $removeUnknown, $removeDupes, $dupeMsgSource ) {
global $wgLanguages;
$messages = $wgLanguages->getMessages( $code );
$messages = $messages['all'];
+ if ($removeDupes) {
+ $messages = removeDupes( $messages, $dupeMsgSource );
+ }
MessageWriter::writeMessagesToFile( $messages, $code, $write, $listUnknown, $removeUnknown );
}
+/**
+ * Remove duplicates from a message array.
+ *
+ * @param $oldMsgArray The input message array.
+ * @param $dupeMsgSource The source file path for duplicates.
+ * @return $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 <<<END
+ 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.
@@ -37,9 +69,10 @@ 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.
-END;
- exit();
+TEXT;
+ exit(1);
}
# Get the language code
@@ -49,10 +82,18 @@ if ( isset( $options['lang'] ) ) {
$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'] );
# Get language objects
$wgLanguages = new languages();
@@ -60,8 +101,8 @@ $wgLanguages = new languages();
# Write all the language
if ( $wgCode == 'all' ) {
foreach ( $wgLanguages->getLanguages() as $language ) {
- rebuildLanguage( $language, $wgWriteToFile, $wgListUnknownMessages, $wgRemoveUnknownMessages );
+ rebuildLanguage( $language, $wgWriteToFile, $wgListUnknownMessages, $wgRemoveUnknownMessages, $wgRemoveDuplicateMessages, $wgDupeMessageSource );
}
} else {
- rebuildLanguage( $wgCode, $wgWriteToFile, $wgListUnknownMessages, $wgRemoveUnknownMessages );
+ rebuildLanguage( $wgCode, $wgWriteToFile, $wgListUnknownMessages, $wgRemoveUnknownMessages, $wgRemoveDuplicateMessages, $wgDupeMessageSource );
}
diff --git a/maintenance/language/transstat.php b/maintenance/language/transstat.php
index b433abb4..eeded34e 100644
--- a/maintenance/language/transstat.php
+++ b/maintenance/language/transstat.php
@@ -9,7 +9,7 @@
* @author Ashar Voultoiz <thoane@altern.org>
*
* Output is posted from time to time on:
- * http://meta.wikimedia.org/wiki/Localization_statistics
+ * http://www.mediawiki.org/wiki/Localisation_statistics
*/
$optionsWithArgs = array( 'output' );
@@ -29,18 +29,17 @@ if ( !isset( $options['output'] ) ) {
/** Print a usage message*/
function showUsage() {
- print <<<END
+ print <<<TEXT
Usage: php transstat.php [--help] [--output=csv|text|wiki]
--help : this helpful message
--output : select an output engine one of:
* 'csv' : Comma Separated Values.
* 'wiki' : MediaWiki syntax (default).
- * 'metawiki' : MediaWiki syntax used for Meta-Wiki.
* 'text' : Text with tabs.
Example: php maintenance/transstat.php --output=text
-END;
- exit();
+TEXT;
+ exit(1);
}
@@ -48,16 +47,13 @@ END;
# Select an output engine
switch ( $options['output'] ) {
case 'wiki':
- $wgOut = new wikiStatsOutput();
- break;
- case 'metawiki':
- $wgOut = new metawikiStatsOutput();
+ $output = new wikiStatsOutput();
break;
case 'text':
- $wgOut = new textStatsOutput();
+ $output = new textStatsOutput();
break;
case 'csv':
- $wgOut = new csvStatsOutput();
+ $output = new csvStatsOutput();
break;
default:
showUsage();
@@ -67,17 +63,18 @@ switch ( $options['output'] ) {
$wgLanguages = new languages();
# Header
-$wgOut->heading();
-$wgOut->blockstart();
-$wgOut->element( 'Language', true );
-$wgOut->element( 'Code', true );
-$wgOut->element( 'Translated', true );
-$wgOut->element( '%', true );
-$wgOut->element( 'Obsolete', true );
-$wgOut->element( '%', true );
-$wgOut->element( 'Problematic', true );
-$wgOut->element( '%', true );
-$wgOut->blockend();
+$output->heading();
+$output->blockstart();
+$output->element( 'Language', true );
+$output->element( 'Code', true );
+$output->element( 'Fallback', true );
+$output->element( 'Translated', true );
+$output->element( '%', true );
+$output->element( 'Obsolete', true );
+$output->element( '%', true );
+$output->element( 'Problematic', true );
+$output->element( '%', true );
+$output->blockend();
$wgGeneralMessages = $wgLanguages->getGeneralMessages();
$wgRequiredMessagesNumber = count( $wgGeneralMessages['required'] );
@@ -90,34 +87,36 @@ foreach ( $wgLanguages->getLanguages() as $code ) {
# Calculate the numbers
$language = $wgContLang->getLanguageName( $code );
+ $fallback = $wgLanguages->getFallback( $code );
$messages = $wgLanguages->getMessages( $code );
$messagesNumber = count( $messages['translated'] );
$requiredMessagesNumber = count( $messages['required'] );
- $requiredMessagesPercent = $wgOut->formatPercent( $requiredMessagesNumber, $wgRequiredMessagesNumber );
+ $requiredMessagesPercent = $output->formatPercent( $requiredMessagesNumber, $wgRequiredMessagesNumber );
$obsoleteMessagesNumber = count( $messages['obsolete'] );
- $obsoleteMessagesPercent = $wgOut->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 = $wgOut->formatPercent( $problematicMessagesNumber, $messagesNumber, true );
+ $problematicMessagesPercent = $output->formatPercent( $problematicMessagesNumber, $messagesNumber, true );
# Output them
- $wgOut->blockstart();
- $wgOut->element( "$language" );
- $wgOut->element( "$code" );
- $wgOut->element( "$requiredMessagesNumber/$wgRequiredMessagesNumber" );
- $wgOut->element( $requiredMessagesPercent );
- $wgOut->element( "$obsoleteMessagesNumber/$messagesNumber" );
- $wgOut->element( $obsoleteMessagesPercent );
- $wgOut->element( "$problematicMessagesNumber/$messagesNumber" );
- $wgOut->element( $problematicMessagesPercent );
- $wgOut->blockend();
+ $output->blockstart();
+ $output->element( "$language" );
+ $output->element( "$code" );
+ $output->element( "$fallback" );
+ $output->element( "$requiredMessagesNumber/$wgRequiredMessagesNumber" );
+ $output->element( $requiredMessagesPercent );
+ $output->element( "$obsoleteMessagesNumber/$messagesNumber" );
+ $output->element( $obsoleteMessagesPercent );
+ $output->element( "$problematicMessagesNumber/$messagesNumber" );
+ $output->element( $problematicMessagesPercent );
+ $output->blockend();
}
# Footer
-$wgOut->footer();
+$output->footer();
diff --git a/maintenance/language/writeMessagesArray.inc b/maintenance/language/writeMessagesArray.inc
index 3a279cb6..e28a5c04 100644
--- a/maintenance/language/writeMessagesArray.inc
+++ b/maintenance/language/writeMessagesArray.inc
@@ -15,7 +15,6 @@ class MessageWriter {
static $messageStructure;
static $blockComments;
- static $messageComments;
static $ignoredMessages;
static $optionalMessages;
@@ -79,13 +78,11 @@ class MessageWriter {
require( $dir . '/messages.inc' );
self::$messageStructure = $wgMessageStructure;
self::$blockComments = $wgBlockComments;
- self::$messageComments = $wgMessageComments;
require( $dir . '/messageTypes.inc' );
self::$ignoredMessages = $wgIgnoredMessages;
self::$optionalMessages = $wgOptionalMessages;
-
# Sort messages to blocks
$sortedMessages['unknown'] = $messages;
foreach( self::$messageStructure as $blockName => $block ) {
@@ -113,7 +110,7 @@ class MessageWriter {
$ignored = array();
$optional = array();
}
- $comments = self::makeComments( array_keys($messages), self::$messageComments, $ignored, $optional );
+ $comments = self::makeComments( array_keys( $messages ), $ignored, $optional );
# Write the block
$messagesText .= self::writeMessagesBlock( self::$blockComments[$block], $messages, $comments );
@@ -134,33 +131,19 @@ class MessageWriter {
* Generates an array of comments for messages.
*
* @param $messages Key of messages.
- * @param $comments Comments for messages, indexed by key.
* @param $ignored List of ingored message keys.
* @param $optional List of optional message keys.
*/
- public static function makeComments( $messages, $comments, $ignored, $optional ) {
+ public static function makeComments( $messages, $ignored, $optional ) {
# Comment collector
$commentArray = array();
# List of keys only
foreach( $messages as $key ) {
- $commentsForKey = array();
-
- # Add descriptive comment for this message if there is one
- if( array_key_exists( $key, $comments ) ) {
- $commentsForKey[] = $comments[$key];
- }
-
- # For translator information only
if( in_array( $key, $ignored ) ) {
- $commentsForKey[] = self::$ignoredComment;
+ $commentArray[$key] = ' # ' . self::$ignoredComment;
} elseif( in_array( $key, $optional ) ) {
- $commentsForKey[] = self::$optionalComment;
- }
-
- # Format one or more comments nicely and store in array
- if( count( $commentsForKey ) ) {
- $commentArray[$key] = ' # ' . implode( '; ', $commentsForKey );
+ $commentArray[$key] = ' # ' . self::$optionalComment;
}
}
diff --git a/maintenance/mcc.php b/maintenance/mcc.php
index 53645df8..e90266ae 100644
--- a/maintenance/mcc.php
+++ b/maintenance/mcc.php
@@ -8,64 +8,36 @@
*/
/** */
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
-$mcc = new memcached( array('persistant' => true/*, 'debug' => true*/) );
+$mcc = new MWMemcached( array('persistant' => true/*, 'debug' => true*/) );
$mcc->set_servers( $wgMemCachedServers );
#$mcc->set_debug( true );
function mccShowHelp($command) {
-
- if(! $command ) { $command = 'fullhelp'; }
- $onlyone = true;
-
- switch ( $command ) {
-
- case 'fullhelp':
- // will show help for all commands
- $onlyone = false;
-
- case 'get':
- print "get: grabs something\n";
- if($onlyone) { break; }
-
- case 'getsock':
- print "getsock: lists sockets\n";
- if($onlyone) { break; }
-
- case 'set':
- print "set: changes something\n";
- if($onlyone) { break; }
-
- case 'delete':
- print "delete: deletes something\n";
- if($onlyone) { break; }
-
- case 'history':
- print "history: show command line history\n";
- if($onlyone) { break; }
-
- case 'server':
- print "server: show current memcached server\n";
- if($onlyone) { break; }
-
- case 'dumpmcc':
- print "dumpmcc: shows the whole thing\n";
- if($onlyone) { break; }
-
- case 'exit':
- case 'quit':
- print "exit or quit: exit mcc\n";
- if($onlyone) { break; }
-
- case 'help':
- print "help: help about a command\n";
- if($onlyone) { break; }
-
- default:
- if($onlyone) {
- print "$command: command does not exist or no help for it\n";
- }
+ $commandList = array(
+ 'get' => 'grabs something',
+ 'getsock' => 'lists sockets',
+ 'set' => 'changes something',
+ 'delete' => 'deletes something',
+ 'history' => 'show command line history',
+ 'server' => 'show current memcached server',
+ 'dumpmcc' => 'shows the whole thing',
+ 'exit' => 'exit mcc',
+ 'quit' => 'exit mcc',
+ 'help' => 'help about a command',
+ );
+ if( !$command ) {
+ $command = 'fullhelp';
+ }
+ if( $command === 'fullhelp' ) {
+ foreach( $commandList as $cmd => $desc ) {
+ print "$cmd: $desc\n";
+ }
+ } elseif( isset( $commandList[$command] ) ) {
+ print "$command: $commandList[$command]\n";
+ } else {
+ print "$command: command does not exist or no help for it\n";
}
}
@@ -114,6 +86,10 @@ do {
break;
case 'server':
+ if ( $mcc->_single_sock !== null ) {
+ print $mcc->_single_sock . "\n";
+ break;
+ }
$res = $mcc->get( $args[0] );
$hv = $mcc->_hashfunc( $args[0] );
for ( $i = 0; $i < 3; $i++ ) {
@@ -176,5 +152,3 @@ do {
}
}
} while ( !$quit );
-
-
diff --git a/maintenance/mctest.php b/maintenance/mctest.php
index aef6d6db..3667cb93 100644
--- a/maintenance/mctest.php
+++ b/maintenance/mctest.php
@@ -3,63 +3,81 @@
* This script makes several 'set', 'incr' and 'get' requests on every
* memcached server and shows a report.
*
- * $Id: mctest.php 37886 2008-07-21 17:06:35Z greg $
- * @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
+ *
* @ingroup Maintenance
*/
-$optionsWithArgs = array( 'i' );
-
-require_once('commandLine.inc');
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-function microtime_float()
-{
- list($usec, $sec) = explode(" ", microtime());
- return ((float)$usec + (float)$sec);
-}
-
-
-#$wgDebugLogFile = '/dev/stdout';
+class mcTest extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Makes several 'set', 'incr' and 'get' requests on every"
+ . " memcached server and shows a report";
+ $this->addOption( 'i', 'Number of iterations', false, true );
+ $this->addArg( 'server', 'Memcached server to test', false );
+ }
-if ( isset( $args[0] ) ) {
- $wgMemCachedServers = array( $args[0] );
-}
-if ( isset( $options['i'] ) ) {
- $iterations = $options['i'];
-} else {
- $iterations = 100;
-}
+ public function execute() {
+ global $wgMemCachedServers;
-foreach ( $wgMemCachedServers as $server ) {
- print "$server ";
- $mcc = new MemCachedClientforWiki( array('persistant' => true) );
- $mcc->set_servers( array( $server ) );
- $set = 0;
- $incr = 0;
- $get = 0;
- $time_start=microtime_float();
- for ( $i=1; $i<=$iterations; $i++ ) {
- if ( !is_null( $mcc->set( "test$i", $i ) ) ) {
- $set++;
- }
- }
+ $iterations = $this->getOption( 'i', 100 );
+ if( $this->hasArg() )
+ $wgMemCachedServers = array( $this->getArg() );
- for ( $i=1; $i<=$iterations; $i++ ) {
- if ( !is_null( $mcc->incr( "test$i", $i ) ) ) {
- $incr++;
+ foreach ( $wgMemCachedServers as $server ) {
+ $this->output( $server . " " );
+ $mcc = new MemCachedClientforWiki( array('persistant' => true) );
+ $mcc->set_servers( array( $server ) );
+ $set = 0;
+ $incr = 0;
+ $get = 0;
+ $time_start = $this->microtime_float();
+ for ( $i=1; $i<=$iterations; $i++ ) {
+ if ( !is_null( $mcc->set( "test$i", $i ) ) ) {
+ $set++;
+ }
+ }
+ for ( $i=1; $i<=$iterations; $i++ ) {
+ if ( !is_null( $mcc->incr( "test$i", $i ) ) ) {
+ $incr++;
+ }
+ }
+ for ( $i=1; $i<=$iterations; $i++ ) {
+ $value = $mcc->get( "test$i" );
+ if ( $value == $i*2 ) {
+ $get++;
+ }
+ }
+ $exectime = $this->microtime_float() - $time_start;
+
+ $this->output( "set: $set incr: $incr get: $get time: $exectime\n" );
}
}
- for ( $i=1; $i<=$iterations; $i++ ) {
- $value = $mcc->get( "test$i" );
- if ( $value == $i*2 ) {
- $get++;
- }
+ /**
+ * Return microtime() as a float
+ * @return float
+ */
+ private function microtime_float() {
+ list($usec, $sec) = explode(" ", microtime());
+ return ((float)$usec + (float)$sec);
}
- $exectime=microtime_float()-$time_start;
-
- print "set: $set incr: $incr get: $get time: $exectime\n";
}
-
-
+$maintClass = "mcTest";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/mergeMessageFileList.php b/maintenance/mergeMessageFileList.php
new file mode 100644
index 00000000..57d9ebb0
--- /dev/null
+++ b/maintenance/mergeMessageFileList.php
@@ -0,0 +1,72 @@
+<?php
+
+# Start from scratch
+define( 'MW_NO_EXTENSION_MESSAGES', 1 );
+
+require_once( dirname( __FILE__ ).'/Maintenance.php' );
+$maintClass = 'MergeMessageFileList';
+$mmfl = false;
+class MergeMessageFileList extends Maintenance {
+
+ function __construct() {
+ $this->addOption( 'list-file', 'A file containing a list of extension setup files, one per line.', 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.';
+ }
+
+ public function execute() {
+ global $IP, $mmfl;
+ if ( !$this->hasOption( 'list-file' ) ) {
+ $this->error( 'The --list-file option must be specified.' );
+ return;
+ }
+
+ $lines = file( $this->getOption( 'list-file' ) );
+ if ( $lines === false ) {
+ $this->error( 'Unable to open list file.' );
+ }
+ $mmfl = array( 'setupFiles' => array_map( 'trim', $lines ) );
+ if ( $this->hasOption( 'output' ) ) {
+ $mmfl['output'] = $this->getOption( 'output' );
+ }
+ }
+}
+
+require_once( DO_MAINTENANCE );
+
+foreach ( $mmfl['setupFiles'] as $fileName ) {
+ if ( strval( $fileName ) === '' ) {
+ continue;
+ }
+ $fileName = str_replace( '$IP', $IP, $fileName );
+ fwrite( STDERR, "Loading data from $fileName\n" );
+ include_once( $fileName );
+}
+fwrite( STDERR, "\n" );
+$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" .
+ '$wgExtensionAliasesFiles = ' . var_export( $wgExtensionAliasesFiles, true ) . ";\n";
+
+$dirs = array(
+ $IP,
+ dirname( dirname( __FILE__ ) ),
+ realpath( $IP )
+);
+
+foreach ( $dirs as $dir ) {
+ $s = preg_replace(
+ "/'" . preg_quote( $dir, '/' ) . "([^']*)'/",
+ '"$IP\1"',
+ $s );
+}
+
+if ( isset( $mmfl['output'] ) ) {
+ file_put_contents( $mmfl['output'], $s );
+} else {
+ echo $s;
+}
+
diff --git a/maintenance/migrateUserGroup.php b/maintenance/migrateUserGroup.php
new file mode 100644
index 00000000..6d584f7d
--- /dev/null
+++ b/maintenance/migrateUserGroup.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Re-assign users from an old group to a new one
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class MigrateUserGroup extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Re-assign users from an old group to a new one";
+ $this->addArg( 'oldgroup', 'Old user group key', true );
+ $this->addArg( 'newgroup', 'New user group key', true );
+ $this->setBatchSize( 200 );
+ }
+
+ public function execute() {
+ $count = 0;
+ $oldGroup = $this->getArg( 0 );
+ $newGroup = $this->getArg( 1 );
+ $dbw = wfGetDB( DB_MASTER );
+ $start = $dbw->selectField( 'user_groups', 'MIN(ug_user)',
+ array('ug_group' => $oldGroup), __FUNCTION__ );
+ $end = $dbw->selectField( 'user_groups', 'MAX(ug_user)',
+ array('ug_group' => $oldGroup), __FUNCTION__ );
+ if( $start === null ) {
+ $this->error( "Nothing to do - no users in the '$oldGroup' group", true );
+ }
+ # Do remaining chunk
+ $end += $this->mBatchSize - 1;
+ $blockStart = $start;
+ $blockEnd = $start + $this->mBatchSize - 1;
+ // Migrate users over in batches...
+ while( $blockEnd <= $end ) {
+ $this->output( "Doing users $blockStart to $blockEnd\n" );
+ $dbw->begin();
+ $dbw->update( 'user_groups',
+ array('ug_group' => $newGroup),
+ array('ug_group' => $oldGroup,
+ "ug_user BETWEEN $blockStart AND $blockEnd" )
+ );
+ $count += $dbw->affectedRows();
+ $dbw->commit();
+ $blockStart += $this->mBatchSize;
+ $blockEnd += $this->mBatchSize;
+ wfWaitForSlaves( 5 );
+ }
+ $this->output( "Done! $count user(s) in group '$oldGroup' are now in '$newGroup' instead.\n" );
+ }
+}
+
+$maintClass = "MigrateUserGroup";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/minify.php b/maintenance/minify.php
new file mode 100644
index 00000000..601a4d67
--- /dev/null
+++ b/maintenance/minify.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Minify a file or set of files
+ */
+
+require_once( dirname( __FILE__ ) . '/Maintenance.php' );
+
+class MinifyScript extends Maintenance {
+ var $outDir;
+
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( 'outfile',
+ 'File for output. Only a single file may be specified for input.',
+ false, true );
+ $this->addOption( 'outdir',
+ "Directory for output. If this is not specified, and neither is --outfile, then the\n" .
+ "output files will be sent to the same directories as the input files.",
+ false, true );
+ $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() {
+ if ( !count( $this->mArgs ) ) {
+ $this->error( "minify.php: At least one input file must be specified." );
+ exit( 1 );
+ }
+
+ if ( $this->hasOption( 'outfile' ) ) {
+ if ( count( $this->mArgs ) > 1 ) {
+ $this->error( '--outfile may only be used with a single input file.' );
+ exit( 1 );
+ }
+
+ // Minify one file
+ $this->minify( $this->getArg( 0 ), $this->getOption( 'outfile' ) );
+ return;
+ }
+
+ $outDir = $this->getOption( 'outdir', false );
+
+ foreach ( $this->mArgs as $arg ) {
+ $inPath = realpath( $arg );
+ $inName = basename( $inPath );
+ $inDir = dirname( $inPath );
+
+ if ( strpos( $inName, '.min.' ) !== false ) {
+ echo "Skipping $inName\n";
+ continue;
+ }
+
+ if ( !file_exists( $inPath ) ) {
+ $this->error( "File does not exist: $arg" );
+ exit( 1 );
+ }
+
+ $extension = $this->getExtension( $inName );
+ $outName = substr( $inName, 0, -strlen( $extension ) ) . 'min.' . $extension;
+ if ( $outDir === false ) {
+ $outPath = $inDir . '/' . $outName;
+ } else {
+ $outPath = $outDir . '/' . $outName;
+ }
+
+ $this->minify( $inPath, $outPath );
+ }
+ }
+
+ public function getExtension( $fileName ) {
+ $dotPos = strrpos( $fileName, '.' );
+ if ( $dotPos === false ) {
+ $this->error( "No file extension, cannot determine type: $arg" );
+ exit( 1 );
+ }
+ return substr( $fileName, $dotPos + 1 );
+ }
+
+ public function minify( $inPath, $outPath ) {
+ $extension = $this->getExtension( $inPath );
+ echo basename( $inPath ) . ' -> ' . basename( $outPath ) . '...';
+
+ $inText = file_get_contents( $inPath );
+ if ( $inText === false ) {
+ $this->error( "Unable to open file $inPath for reading." );
+ exit( 1 );
+ }
+ $outFile = fopen( $outPath, 'w' );
+ if ( !$outFile ) {
+ $this->error( "Unable to open file $outPath for writing." );
+ exit( 1 );
+ }
+
+ switch ( $extension ) {
+ case 'js':
+ $outText = JSMin::minify( $inText );
+ break;
+ default:
+ $this->error( "No minifier defined for extension \"$extension\"" );
+ }
+
+ fwrite( $outFile, $outText );
+ fclose( $outFile );
+ echo " ok\n";
+ }
+}
+
+$maintClass = 'MinifyScript';
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/moveBatch.php b/maintenance/moveBatch.php
index 67d513ed..d1d3193b 100644
--- a/maintenance/moveBatch.php
+++ b/maintenance/moveBatch.php
@@ -1,9 +1,22 @@
<?php
-
/**
* Maintenance script to move a batch of pages
*
- * @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
+ *
* @ingroup Maintenance
* @author Tim Starling
*
@@ -20,78 +33,78 @@
* e.g. immobile_namespace for namespaces which can't be moved
*/
-$oldCwd = getcwd();
-$optionsWithArgs = array( 'u', 'r', 'i' );
-require_once( 'commandLine.inc' );
-
-chdir( $oldCwd );
-
-# Options processing
-
-$filename = 'php://stdin';
-$user = 'Move page script';
-$reason = '';
-$interval = 0;
-
-if ( isset( $args[0] ) ) {
- $filename = $args[0];
-}
-if ( isset( $options['u'] ) ) {
- $user = $options['u'];
-}
-if ( isset( $options['r'] ) ) {
- $reason = $options['r'];
-}
-if ( isset( $options['i'] ) ) {
- $interval = $options['i'];
-}
-
-$wgUser = User::newFromName( $user );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-
-# Setup complete, now start
-
-$file = fopen( $filename, 'r' );
-if ( !$file ) {
- print "Unable to read file, exiting\n";
- exit;
-}
-
-$dbw = wfGetDB( DB_MASTER );
-
-for ( $linenum = 1; !feof( $file ); $linenum++ ) {
- $line = fgets( $file );
- if ( $line === false ) {
- break;
- }
- $parts = array_map( 'trim', explode( '|', $line ) );
- if ( count( $parts ) != 2 ) {
- print "Error on line $linenum, no pipe character\n";
- continue;
+class MoveBatch extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Moves a batch of pages";
+ $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->addArg( 'listfile', 'List of pages to move, newline delimited', false );
}
- $source = Title::newFromText( $parts[0] );
- $dest = Title::newFromText( $parts[1] );
- if ( is_null( $source ) || is_null( $dest ) ) {
- print "Invalid title on line $linenum\n";
- continue;
+
+ public function execute() {
+ global $wgUser;
+
+ # Change to current working directory
+ $oldCwd = getcwd();
+ chdir( $oldCwd );
+
+ # Options processing
+ $user = $this->getOption( 'u', 'Move page script' );
+ $reason = $this->getOption( 'r', '' );
+ $interval = $this->getOption( 'i', 0 );
+ if( $this->hasArg() ) {
+ $file = fopen( $this->getArg(), 'r' );
+ } else {
+ $file = $this->getStdin();
+ }
+
+ # Setup
+ if( !$file ) {
+ $this->error( "Unable to read file, exiting", true );
+ }
+ $wgUser = User::newFromName( $user );
+
+ # Setup complete, now start
+ $dbw = wfGetDB( DB_MASTER );
+ for ( $linenum = 1; !feof( $file ); $linenum++ ) {
+ $line = fgets( $file );
+ if ( $line === false ) {
+ break;
+ }
+ $parts = array_map( 'trim', explode( '|', $line ) );
+ if ( count( $parts ) != 2 ) {
+ $this->error( "Error on line $linenum, no pipe character" );
+ continue;
+ }
+ $source = Title::newFromText( $parts[0] );
+ $dest = Title::newFromText( $parts[1] );
+ if ( is_null( $source ) || is_null( $dest ) ) {
+ $this->error( "Invalid title on line $linenum" );
+ continue;
+ }
+
+
+ $this->output( $source->getPrefixedText() . ' --> ' . $dest->getPrefixedText() );
+ $dbw->begin();
+ $err = $source->moveTo( $dest, false, $reason );
+ if( $err !== true ) {
+ $msg = array_shift( $err[0] );
+ $this->output( "\nFAILED: " . wfMsg( $msg, $err[0] ) );
+ }
+ $dbw->commit();
+ $this->output( "\n" );
+
+ if ( $interval ) {
+ sleep( $interval );
+ }
+ wfWaitForSlaves( 5 );
+ }
}
-
-
- print $source->getPrefixedText() . ' --> ' . $dest->getPrefixedText();
- $dbw->begin();
- $err = $source->moveTo( $dest, false, $reason );
- if( $err !== true ) {
- $msg = array_shift( $err[0] );
- print "\nFAILED: " . wfMsg( $msg, $err[0] );
- }
- $dbw->immediateCommit();
- print "\n";
-
- if ( $interval ) {
- sleep( $interval );
- }
- wfWaitForSlaves( 5 );
}
-
-
+$maintClass = "MoveBatch";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/mwdocgen.php b/maintenance/mwdocgen.php
index 378229e0..b91922c3 100644
--- a/maintenance/mwdocgen.php
+++ b/maintenance/mwdocgen.php
@@ -61,6 +61,7 @@ $mwPathS = $mwPath.'skins/';
/** Variable to get user input */
$input = '';
+$exclude = '';
#
# Functions
@@ -128,8 +129,10 @@ function getSvnRevision( $dir ) {
* @param $currentVersion String: Version number of the software
* @param $svnstat String: path to the svnstat file
* @param $input String: Path to analyze.
+ * @param $exclude String: Additionals path regex to exlcude
+ * (LocalSettings.php, AdminSettings.php and .svn directories are always excluded)
*/
-function generateConfigFile( $doxygenTemplate, $outputDirectory, $stripFromPath, $currentVersion, $svnstat, $input ){
+function generateConfigFile( $doxygenTemplate, $outputDirectory, $stripFromPath, $currentVersion, $svnstat, $input, $exclude ){
global $tmpPath;
$template = file_get_contents( $doxygenTemplate );
@@ -141,6 +144,7 @@ function generateConfigFile( $doxygenTemplate, $outputDirectory, $stripFromPath,
'{{CURRENT_VERSION}}' => $currentVersion,
'{{SVNSTAT}}' => $svnstat,
'{{INPUT}}' => $input,
+ '{{EXCLUDE}}' => $exclude,
);
$tmpCfg = str_replace( array_keys( $replacements ), array_values( $replacements ), $template );
$tmpFileName = $tmpPath . 'mwdocgen'. rand() .'.tmp';
@@ -168,6 +172,7 @@ if( is_array( $argv ) && isset( $argv[1] ) ) {
$file = $argv[2];
}
break;
+ case '--no-extensions': $input = 6; break;
}
}
@@ -182,6 +187,7 @@ Several documentation possibilities:
3 : only maintenance
4 : only skins
5 : only a given file
+ 6 : all but the extensions directory
OPTIONS;
while ( !is_numeric($input) )
{
@@ -203,6 +209,9 @@ case 5:
$file = readaline( "Enter file name $mwPath" );
}
$input = $mwPath.$file;
+case 6:
+ $input = $mwPath;
+ $exclude = 'extensions';
}
$versionNumber = getSvnRevision( $input );
@@ -213,7 +222,7 @@ if( $versionNumber === false ){ #Not using subversion ?
$version = "trunk (r$versionNumber)";
}
-$generatedConf = generateConfigFile( $doxygenTemplate, $doxyOutput, $mwPath, $version, $svnstat, $input );
+$generatedConf = generateConfigFile( $doxygenTemplate, $doxyOutput, $mwPath, $version, $svnstat, $input, $exclude );
$command = $doxygenBin . ' ' . $generatedConf;
echo <<<TEXT
@@ -233,6 +242,6 @@ echo <<<TEXT
Doxygen execution finished.
Check above for possible errors.
-You might want to deleted the temporary file $generatedConf
+You might want to delete the temporary file $generatedConf
TEXT;
diff --git a/maintenance/namespace2sql.php b/maintenance/namespace2sql.php
deleted file mode 100644
index 43a8cc64..00000000
--- a/maintenance/namespace2sql.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-/**
- * Print SQL to insert namespace names into database.
- * This source code is in the public domain.
- *
- * @file
- * @ingroup Maintenance
- */
-
-require_once( "commandLine.inc" );
-
-for ($i = -2; $i < 16; ++$i) {
- $nsname = mysql_escape_string( $wgLang->getNsText( $i ) );
- $dbname = mysql_escape_string( $wgDBname );
- print "INSERT INTO ns_name(ns_db, ns_num, ns_name) VALUES('$dbname', $i, '$nsname');\n";
-}
-
-
diff --git a/maintenance/namespaceDupes.php b/maintenance/namespaceDupes.php
index c5b3ce96..dd29558c 100644
--- a/maintenance/namespaceDupes.php
+++ b/maintenance/namespaceDupes.php
@@ -1,54 +1,71 @@
<?php
-# Copyright (C) 2005-2007 Brion Vibber <brion@pobox.com>
-# http://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
+ * Check for articles to fix after adding/deleting namespaces
+ *
+ * Copyright (C) 2005-2007 Brion Vibber <brion@pobox.com>
+ * http://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
+ *
* @ingroup Maintenance
*/
-$options = array( 'fix', 'suffix', 'help' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-/** */
-require_once( 'commandLine.inc' );
+class NamespaceConflictChecker extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "";
+ $this->addOption( 'fix', 'Attempt to automatically fix errors' );
+ $this->addOption( 'suffix', "Dupes will be renamed with correct namespace with\n" .
+ "\t\t<text> Appended after the article name", false, true );
+ $this->addOption( 'prefix', "Do an explicit check for the given title prefix\n" .
+ "\t\tappended after the article name", false, true );
+ }
-if(isset( $options['help'] ) ) {
-print <<<ENDS
-usage: namespaceDupes.php [--fix] [--suffix=<text>] [--help]
- --help : this help message
- --fix : attempt to automatically fix errors
- --suffix=<text> : dupes will be renamed with correct namespace with <text>
- appended after the article name.
- --prefix=<text> : Do an explicit check for the given title prefix
- in place of the standard namespace list.
- --verbose : Display output for checked namespaces without conflicts
+ public function execute() {
+ global $wgTitle;
-ENDS;
-die;
-}
+ $this->db = wfGetDB( DB_MASTER );
+ $wgTitle = Title::newFromText( 'Namespace title conflict cleanup script' );
-class NamespaceConflictChecker {
- function NamespaceConflictChecker( $db, $verbose=false ) {
- $this->db = $db;
- $this->verbose = $verbose;
+ $fix = $this->hasOption( 'fix' );
+ $suffix = $this->getOption( 'suffix', '' );
+ $prefix = $this->getOption( 'prefix', '' );
+ $key = intval( $this->getOption( 'key', 0 ) );
+
+ if( $prefix ) {
+ $retval = $this->checkPrefix( $key, $prefix, $fix, $suffix );
+ } else {
+ $retval = $this->checkAll( $fix, $suffix );
+ }
+
+ if( $retval ) {
+ $this->output( "\nLooks good!\n" );
+ } else {
+ $this->output( "\nOh noeees\n" );
+ }
}
- function checkAll( $fix, $suffix = '' ) {
+ /**
+ * @todo Document
+ * @param $fix bool Whether or not to fix broken entries
+ * @param $suffix String Suffix to append to renamed articles
+ */
+ private function checkAll( $fix, $suffix = '' ) {
global $wgContLang, $wgNamespaceAliases, $wgCanonicalNamespaceNames;
global $wgCapitalLinks;
@@ -76,7 +93,7 @@ class NamespaceConflictChecker {
foreach( $wgNamespaceAliases as $name => $ns ) {
$spaces[$name] = $ns;
}
- foreach( $wgContLang->namespaceAliases as $name => $ns ) {
+ foreach( $wgContLang->getNamespaceAliases() as $name => $ns ) {
$spaces[$name] = $ns;
}
@@ -112,35 +129,36 @@ class NamespaceConflictChecker {
}
return $ok;
}
-
+
+ /**
+ * Get the interwiki list
+ * @todo Needs to respect interwiki cache!
+ * @return array
+ */
private function getInterwikiList() {
$result = $this->db->select( 'interwiki', array( 'iw_prefix' ) );
- while( $row = $this->db->fetchObject( $result ) ) {
+ $prefixes = array();
+ foreach( $result as $row ) {
$prefixes[] = $row->iw_prefix;
}
$this->db->freeResult( $result );
return $prefixes;
}
- function checkNamespace( $ns, $name, $fix, $suffix = '' ) {
- if( $ns == 0 ) {
- $header = "Checking interwiki prefix: \"$name\"\n";
- } else {
- $header = "Checking namespace $ns: \"$name\"\n";
- }
-
+ /**
+ * @todo Document
+ * @param $ns int A namespace id
+ * @param $name String
+ * @param $fix bool Whether to fix broken entries
+ * @param $suffix String Suffix to append to renamed articles
+ */
+ private function checkNamespace( $ns, $name, $fix, $suffix = '' ) {
$conflicts = $this->getConflicts( $ns, $name );
$count = count( $conflicts );
if( $count == 0 ) {
- if( $this->verbose ) {
- echo $header;
- echo "... no conflicts detected!\n";
- }
return true;
}
- echo $header;
- echo "... $count conflicts detected:\n";
$ok = true;
foreach( $conflicts as $row ) {
$resolvable = $this->reportConflict( $row, $suffix );
@@ -155,23 +173,28 @@ class NamespaceConflictChecker {
/**
* @todo: do this for reals
*/
- function checkPrefix( $key, $prefix, $fix, $suffix = '' ) {
- echo "Checking prefix \"$prefix\" vs namespace $key\n";
+ private function checkPrefix( $key, $prefix, $fix, $suffix = '' ) {
+ $this->output( "Checking prefix \"$prefix\" vs namespace $key\n" );
return $this->checkNamespace( $key, $prefix, $fix, $suffix );
}
- function getConflicts( $ns, $name ) {
+ /**
+ * Find pages in mainspace that have a prefix of the new namespace
+ * so we know titles that will need migrating
+ * @param $ns int Namespace id (id for new namespace?)
+ * @param $name String Prefix that is being made a namespace
+ */
+ private function getConflicts( $ns, $name ) {
$page = 'page';
$table = $this->db->tableName( $page );
$prefix = $this->db->strencode( $name );
- $likeprefix = str_replace( '_', '\\_', $prefix);
$encNamespace = $this->db->addQuotes( $ns );
$titleSql = "TRIM(LEADING '$prefix:' FROM {$page}_title)";
if( $ns == 0 ) {
// An interwiki; try an alternate encoding with '-' for ':'
- $titleSql = "CONCAT('$prefix-',$titleSql)";
+ $titleSql = $this->db->buildConcat( array( "'$prefix-'", $titleSql ) );
}
$sql = "SELECT {$page}_id AS id,
@@ -180,12 +203,12 @@ class NamespaceConflictChecker {
$titleSql AS title
FROM {$table}
WHERE {$page}_namespace=0
- AND {$page}_title LIKE '$likeprefix:%'";
+ AND {$page}_title " . $this->db->buildLike( $name . ':', $this->db->anyString() );
- $result = $this->db->query( $sql, 'NamespaceConflictChecker::getConflicts' );
+ $result = $this->db->query( $sql, __METHOD__ );
$set = array();
- while( $row = $this->db->fetchObject( $result ) ) {
+ foreach( $result as $row ) {
$set[] = $row;
}
$this->db->freeResult( $result );
@@ -193,104 +216,89 @@ class NamespaceConflictChecker {
return $set;
}
- function reportConflict( $row, $suffix ) {
+ /**
+ * Report any conflicts we find
+ */
+ private function reportConflict( $row, $suffix ) {
$newTitle = Title::makeTitleSafe( $row->namespace, $row->title );
if( is_null($newTitle) || !$newTitle->canExist() ) {
// Title is also an illegal title...
// For the moment we'll let these slide to cleanupTitles or whoever.
- printf( "... %d (0,\"%s\")\n",
+ $this->output( sprintf( "... %d (0,\"%s\")\n",
$row->id,
- $row->oldtitle );
- echo "... *** cannot resolve automatically; illegal title ***\n";
+ $row->oldtitle ) );
+ $this->output( "... *** cannot resolve automatically; illegal title ***\n" );
return false;
}
-
- printf( "... %d (0,\"%s\") -> (%d,\"%s\") [[%s]]\n",
+
+ $this->output( sprintf( "... %d (0,\"%s\") -> (%d,\"%s\") [[%s]]\n",
$row->id,
$row->oldtitle,
$newTitle->getNamespace(),
$newTitle->getDBkey(),
- $newTitle->getPrefixedText() );
+ $newTitle->getPrefixedText() ) );
$id = $newTitle->getArticleId();
if( $id ) {
- echo "... *** cannot resolve automatically; page exists with ID $id ***\n";
+ $this->output( "... *** cannot resolve automatically; page exists with ID $id ***\n" );
return false;
} else {
return true;
}
}
- function resolveConflict( $row, $resolvable, $suffix ) {
+ /**
+ * Resolve any conflicts
+ * @param $row Row from the page table to fix
+ * @param $resolveable bool
+ * @param $suffix String Suffix to append to the fixed page
+ */
+ private function resolveConflict( $row, $resolvable, $suffix ) {
if( !$resolvable ) {
- echo "... *** old title {$row->title}\n";
+ $this->output( "... *** old title {$row->title}\n" );
while( true ) {
$row->title .= $suffix;
- echo "... *** new title {$row->title}\n";
+ $this->output( "... *** new title {$row->title}\n" );
$title = Title::makeTitleSafe( $row->namespace, $row->title );
if ( ! $title ) {
- echo "... !!! invalid title\n";
+ $this->output( "... !!! invalid title\n" );
return false;
}
if ( $id = $title->getArticleId() ) {
- echo "... *** page exists with ID $id ***\n";
+ $this->output( "... *** page exists with ID $id ***\n" );
} else {
break;
}
}
- echo "... *** using suffixed form [[" . $title->getPrefixedText() . "]] ***\n";
- }
- $tables = array( 'page' );
- foreach( $tables as $table ) {
- $this->resolveConflictOn( $row, $table );
+ $this->output( "... *** using suffixed form [[" . $title->getPrefixedText() . "]] ***\n" );
}
+ $this->resolveConflictOn( $row, 'page', 'page' );
return true;
}
- function resolveConflictOn( $row, $table ) {
- echo "... resolving on $table... ";
+ /**
+ * Resolve a given conflict
+ * @param $row Row from the old broken entry
+ * @param $table String Table to update
+ * @param $prefix String Prefix for column name, like page or ar
+ */
+ private function resolveConflictOn( $row, $table, $prefix ) {
+ $this->output( "... resolving on $table... " );
$newTitle = Title::makeTitleSafe( $row->namespace, $row->title );
$this->db->update( $table,
array(
- "{$table}_namespace" => $newTitle->getNamespace(),
- "{$table}_title" => $newTitle->getDBkey(),
+ "{$prefix}_namespace" => $newTitle->getNamespace(),
+ "{$prefix}_title" => $newTitle->getDBkey(),
),
array(
- "{$table}_namespace" => 0,
- "{$table}_title" => $row->oldtitle,
+ "{$prefix}_namespace" => 0,
+ "{$prefix}_title" => $row->oldtitle,
),
__METHOD__ );
- echo "ok.\n";
+ $this->output( "ok.\n" );
return true;
}
}
-
-
-
-$wgTitle = Title::newFromText( 'Namespace title conflict cleanup script' );
-
-$verbose = isset( $options['verbose'] );
-$fix = isset( $options['fix'] );
-$suffix = isset( $options['suffix'] ) ? $options['suffix'] : '';
-$prefix = isset( $options['prefix'] ) ? $options['prefix'] : '';
-$key = isset( $options['key'] ) ? intval( $options['key'] ) : 0;
-
-$dbw = wfGetDB( DB_MASTER );
-$duper = new NamespaceConflictChecker( $dbw, $verbose );
-
-if( $prefix ) {
- $retval = $duper->checkPrefix( $key, $prefix, $fix, $suffix );
-} else {
- $retval = $duper->checkAll( $fix, $suffix );
-}
-
-if( $retval ) {
- echo "\nLooks good!\n";
- exit( 0 );
-} else {
- echo "\nOh noeees\n";
- exit( -1 );
-}
-
-
+$maintClass = "NamespaceConflictChecker";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/nextJobDB.php b/maintenance/nextJobDB.php
index 6af5cbec..75855bb3 100644
--- a/maintenance/nextJobDB.php
+++ b/maintenance/nextJobDB.php
@@ -2,59 +2,102 @@
/**
* Pick a database that has pending jobs
*
- * @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
+ *
+ * @todo Make this work on PostgreSQL and maybe other database servers
* @ingroup Maintenance
*/
-$options = array( 'type' );
-
-require_once( 'commandLine.inc' );
-
-$type = isset($options['type'])
- ? $options['type']
- : false;
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$mckey = $type === false
- ? "jobqueue:dbs"
- : "jobqueue:dbs:$type";
-
-$pendingDBs = $wgMemc->get( $mckey );
-if ( !$pendingDBs ) {
- $pendingDBs = array();
- # Cross-reference DBs by master DB server
- $dbsByMaster = array();
- foreach ( $wgLocalDatabases as $db ) {
- $lb = wfGetLB( $db );
- $dbsByMaster[$lb->getServerName(0)][] = $db;
+class nextJobDB extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Pick a database that has pending jobs";
+ $this->addOption( 'type', "The type of job to search for", false, true );
}
- foreach ( $dbsByMaster as $master => $dbs ) {
- $dbConn = wfGetDB( DB_MASTER, array(), $dbs[0] );
- $stype = $dbConn->addQuotes($type);
+ public function execute() {
+ global $wgMemc;
+ $type = $this->getOption( 'type', false );
+ $mckey = $type === false
+ ? "jobqueue:dbs"
+ : "jobqueue:dbs:$type";
+ $pendingDBs = $wgMemc->get( $mckey );
- # Padding row for MySQL bug
- $sql = "(SELECT '-------------------------------------------')";
- foreach ( $dbs as $dbName ) {
- if ( $sql != '' ) {
- $sql .= ' UNION ';
- }
- if ($type === false)
- $sql .= "(SELECT '$dbName' FROM `$dbName`.job LIMIT 1)";
- else
- $sql .= "(SELECT '$dbName' FROM `$dbName`.job WHERE job_cmd=$stype LIMIT 1)";
+ # If we didn't get it from the cache
+ if( !$pendingDBs ) {
+ $pendingDBs = $this->getPendingDbs( $type );
+ $wgMemc->get( $mckey, $pendingDBs, 300 );
}
- $res = $dbConn->query( $sql, 'nextJobDB.php' );
- $row = $dbConn->fetchRow( $res ); // discard padding row
- while ( $row = $dbConn->fetchRow( $res ) ) {
- $pendingDBs[] = $row[0];
+ # If we've got a pending job in a db, display it.
+ if ( $pendingDBs ) {
+ $this->output( $pendingDBs[mt_rand(0, count( $pendingDBs ) - 1)] );
}
}
- $wgMemc->set( $mckey, $pendingDBs, 300 );
-}
+ /**
+ * Get all databases that have a pending job
+ * @param $type String Job type
+ * @return array
+ */
+ private function getPendingDbs( $type ) {
+ global $wgLocalDatabases;
+ $pendingDBs = array();
+ # Cross-reference DBs by master DB server
+ $dbsByMaster = array();
+ foreach ( $wgLocalDatabases as $db ) {
+ $lb = wfGetLB( $db );
+ $dbsByMaster[$lb->getServerName(0)][] = $db;
+ }
-if ( $pendingDBs ) {
- echo $pendingDBs[mt_rand(0, count( $pendingDBs ) - 1)];
-}
+ foreach ( $dbsByMaster as $master => $dbs ) {
+ $dbConn = wfGetDB( DB_MASTER, array(), $dbs[0] );
+ $stype = $dbConn->addQuotes( $type );
+
+ # Padding row for MySQL bug
+ $sql = "(SELECT '-------------------------------------------' as db)";
+ foreach ( $dbs as $wikiId ) {
+ if ( $sql != '' ) {
+ $sql .= ' UNION ';
+ }
+ list( $dbName, $tablePrefix ) = wfSplitWikiID( $wikiId );
+ $dbConn->tablePrefix( $tablePrefix );
+ $jobTable = $dbConn->tableName( 'job' );
+
+ if ( $type === false )
+ $sql .= "(SELECT '$wikiId' as db FROM $dbName.$jobTable LIMIT 1)";
+ else
+ $sql .= "(SELECT '$wikiId' as db FROM $dbName.$jobTable WHERE job_cmd=$stype LIMIT 1)";
+ }
+ $res = $dbConn->query( $sql, __METHOD__ );
+ $first = true;
+ foreach ( $res as $row ) {
+ if ( $first ) {
+ // discard padding row
+ $first = false;
+ continue;
+ }
+ $pendingDBs[] = $row->db;
+ }
+ }
+ return $pendingDBs;
+ }
+}
+$maintClass = "nextJobDb";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/nukeNS.php b/maintenance/nukeNS.php
index 4f9fe926..21e921fe 100644
--- a/maintenance/nukeNS.php
+++ b/maintenance/nukeNS.php
@@ -13,96 +13,102 @@
* back up your DB if there's anything in the MediaWiki that is important to
* you.
*
- * @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
+ *
* @ingroup Maintenance
* @author Steve Sanbeg
* based on nukePage by Rob Church
*/
-require_once( 'commandLine.inc' );
-require_once( 'nukePage.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$ns = NS_MEDIAWIKI;
-$delete = false;
+class NukeNS extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Remove pages with only 1 revision from any namespace";
+ $this->addOption( 'delete', "Actually delete the page" );
+ $this->addOption( 'ns', 'Namespace to delete from, default NS_MEDIAWIKI', false, true );
+ }
-if (isset($options['ns']))
-{
- $ns = $options['ns'];
-}
+ public function execute() {
+ $ns = $this->getOption( 'ns', NS_MEDIAWIKI );
+ $delete = $this->getOption( 'delete', false );
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
-if (isset( $options['delete'] ) and $options['delete'])
-{
- $delete = true;
-}
+ $tbl_pag = $dbw->tableName( 'page' );
+ $tbl_rev = $dbw->tableName( 'revision' );
+ $res = $dbw->query( "SELECT page_title FROM $tbl_pag WHERE page_namespace = $ns" );
+ $n_deleted = 0;
-NukeNS( $ns, $delete);
+ foreach( $res as $row ) {
+ //echo "$ns_name:".$row->page_title, "\n";
+ $title = Title::makeTitle( $ns, $row->page_title );
+ $id = $title->getArticleID();
-function NukeNS($ns_no, $delete) {
+ // Get corresponding revisions
+ $res2 = $dbw->query( "SELECT rev_id FROM $tbl_rev WHERE rev_page = $id" );
+ $revs = array();
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
-
- $tbl_pag = $dbw->tableName( 'page' );
- $tbl_rev = $dbw->tableName( 'revision' );
- $res = $dbw->query( "SELECT page_title FROM $tbl_pag WHERE page_namespace = $ns_no" );
+ foreach( $res2 as $row2 ) {
+ $revs[] = $row2->rev_id;
+ }
+ $count = count( $revs );
- $n_deleted = 0;
-
- while( $row = $dbw->fetchObject( $res ) ) {
- //echo "$ns_name:".$row->page_title, "\n";
- $title = Title::newFromText($row->page_title, $ns_no);
- $id = $title->getArticleID();
+ //skip anything that looks modified (i.e. multiple revs)
+ if ( $count == 1 ) {
+ #echo $title->getPrefixedText(), "\t", $count, "\n";
+ $this->output( "delete: " . $title->getPrefixedText() . "\n" );
- // Get corresponding revisions
- $res2 = $dbw->query( "SELECT rev_id FROM $tbl_rev WHERE rev_page = $id" );
- $revs = array();
-
- while( $row2 = $dbw->fetchObject( $res2 ) ) {
- $revs[] = $row2->rev_id;
- }
- $count = count( $revs );
+ //as much as I hate to cut & paste this, it's a little different, and
+ //I already have the id & revs
+ if( $delete ) {
+ $dbw->query( "DELETE FROM $tbl_pag WHERE page_id = $id" );
+ $dbw->commit();
+ // Delete revisions as appropriate
+ $child = $this->runChild( 'NukePage', 'NukePage.php' );
+ $child->deleteRevisions( $revs );
+ $this->purgeRedundantText( true );
+ $n_deleted ++;
+ }
+ } else {
+ $this->output( "skip: " . $title->getPrefixedText() . "\n" );
+ }
+ }
+ $dbw->commit();
- //skip anything that looks modified (i.e. multiple revs)
- if (($count == 1)) {
- #echo $title->getPrefixedText(), "\t", $count, "\n";
- echo "delete: ", $title->getPrefixedText(), "\n";
-
- //as much as I hate to cut & paste this, it's a little different, and
- //I already have the id & revs
-
- if( $delete ) {
- $dbw->query( "DELETE FROM $tbl_pag WHERE page_id = $id" );
- $dbw->commit();
- // Delete revisions as appropriate
- DeleteRevisions( $revs );
- PurgeRedundantText( true );
- $n_deleted ++;
- }
- } else {
- echo "skip: ", $title->getPrefixedText(), "\n";
- }
-
-
- }
- $dbw->commit();
-
- if ($n_deleted > 0) {
- #update statistics - better to decrement existing count, or just count
- #the page table?
- $pages = $dbw->selectField('site_stats', 'ss_total_pages');
- $pages -= $n_deleted;
- $dbw->update( 'site_stats',
- array('ss_total_pages' => $pages ),
- array( 'ss_row_id' => 1),
- __METHOD__ );
-
- }
-
- if (!$delete) {
- echo( "To update the database, run the script with the --delete option.\n" );
- }
-
+ if ( $n_deleted > 0 ) {
+ #update statistics - better to decrement existing count, or just count
+ #the page table?
+ $pages = $dbw->selectField( 'site_stats', 'ss_total_pages' );
+ $pages -= $n_deleted;
+ $dbw->update(
+ 'site_stats',
+ array( 'ss_total_pages' => $pages ),
+ array( 'ss_row_id' => 1 ),
+ __METHOD__
+ );
+ }
+
+ if ( !$delete ) {
+ $this->output( "To update the database, run the script with the --delete option.\n" );
+ }
+ }
}
-
+$maintClass = "NukeNS";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/nukePage.inc b/maintenance/nukePage.inc
deleted file mode 100644
index a19c6df6..00000000
--- a/maintenance/nukePage.inc
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-/**
- * Support functions for the nukeArticle script
- *
- * @file
- * @ingroup Maintenance
- * @author Rob Church <robchur@gmail.com>
- */
-
-require_once( 'purgeOldText.inc' );
-
-function NukePage( $name, $delete = false ) {
-
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
-
- $tbl_pag = $dbw->tableName( 'page' );
- $tbl_rec = $dbw->tableName( 'recentchanges' );
- $tbl_rev = $dbw->tableName( 'revision' );
-
- # Get page ID
- echo( "Searching for \"$name\"..." );
- $title = Title::newFromText( $name );
- if( $title ) {
- $id = $title->getArticleID();
- $real = $title->getPrefixedText();
- $isGoodArticle = $title->isContentPage();
- echo( "found \"$real\" with ID $id.\n" );
-
- # Get corresponding revisions
- echo( "Searching for revisions..." );
- $res = $dbw->query( "SELECT rev_id FROM $tbl_rev WHERE rev_page = $id" );
- while( $row = $dbw->fetchObject( $res ) ) {
- $revs[] = $row->rev_id;
- }
- $count = count( $revs );
- echo( "found $count.\n" );
-
- # Delete the page record and associated recent changes entries
- if( $delete ) {
- echo( "Deleting page record..." );
- $dbw->query( "DELETE FROM $tbl_pag WHERE page_id = $id" );
- echo( "done.\n" );
- echo( "Cleaning up recent changes..." );
- $dbw->query( "DELETE FROM $tbl_rec WHERE rc_cur_id = $id" );
- echo( "done.\n" );
- }
-
- $dbw->commit();
-
- # Delete revisions as appropriate
- if( $delete && $count ) {
- echo( "Deleting revisions..." );
- DeleteRevisions( $revs );
- echo( "done.\n" );
- PurgeRedundantText( true );
- }
-
- # Update stats as appropriate
- if ( $delete ) {
- echo( "Updating site stats..." );
- $ga = $isGoodArticle ? -1 : 0; // if it was good, decrement that too
- $stats = new SiteStatsUpdate( 0, -$count, $ga, -1 );
- $stats->doUpdate();
- echo( "done.\n" );
- }
-
-
- } else {
- echo( "not found in database.\n" );
- $dbw->commit();
- }
-
-}
-
-function DeleteRevisions( $ids ) {
-
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
-
- $tbl_rev = $dbw->tableName( 'revision' );
-
- $set = implode( ', ', $ids );
- $dbw->query( "DELETE FROM $tbl_rev WHERE rev_id IN ( $set )" );
-
- $dbw->commit();
-
-}
-
-?> \ No newline at end of file
diff --git a/maintenance/nukePage.php b/maintenance/nukePage.php
index b3bfc762..16ff2e40 100644
--- a/maintenance/nukePage.php
+++ b/maintenance/nukePage.php
@@ -1,29 +1,113 @@
<?php
-
/**
* Erase a page record from the database
* Irreversible (can't use standard undelete) and does not update link tables
*
- * @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
+ *
* @ingroup Maintenance
* @author Rob Church <robchur@gmail.com>
*/
-require_once( 'commandLine.inc' );
-require_once( 'nukePage.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-echo( "Erase Page Record\n\n" );
+class NukePage extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Remove a page record from the database";
+ $this->addOption( 'delete', "Actually delete the page" );
+ $this->addArg( 'title', 'Title to delete' );
+ }
-if( isset( $args[0] ) ) {
- NukePage( $args[0], true );
-} else {
- ShowUsage();
-}
+ public function execute() {
+
+ $name = $this->getArg();
+ $delete = $this->getOption( 'delete', false );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+
+ $tbl_pag = $dbw->tableName( 'page' );
+ $tbl_rec = $dbw->tableName( 'recentchanges' );
+ $tbl_rev = $dbw->tableName( 'revision' );
+
+ # Get page ID
+ $this->output( "Searching for \"$name\"..." );
+ $title = Title::newFromText( $name );
+ if( $title ) {
+ $id = $title->getArticleID();
+ $real = $title->getPrefixedText();
+ $isGoodArticle = $title->isContentPage();
+ $this->output( "found \"$real\" with ID $id.\n" );
+
+ # Get corresponding revisions
+ $this->output( "Searching for revisions..." );
+ $res = $dbw->query( "SELECT rev_id FROM $tbl_rev WHERE rev_page = $id" );
+ foreach( $res as $row ) {
+ $revs[] = $row->rev_id;
+ }
+ $count = count( $revs );
+ $this->output( "found $count.\n" );
+
+ # Delete the page record and associated recent changes entries
+ if( $delete ) {
+ $this->output( "Deleting page record..." );
+ $dbw->query( "DELETE FROM $tbl_pag WHERE page_id = $id" );
+ $this->output( "done.\n" );
+ $this->output( "Cleaning up recent changes..." );
+ $dbw->query( "DELETE FROM $tbl_rec WHERE rc_cur_id = $id" );
+ $this->output( "done.\n" );
+ }
+
+ $dbw->commit();
+
+ # Delete revisions as appropriate
+ if( $delete && $count ) {
+ $this->output( "Deleting revisions..." );
+ $this->deleteRevisions( $revs );
+ $this->output( "done.\n" );
+ $this->purgeRedundantText( true );
+ }
+
+ # Update stats as appropriate
+ if ( $delete ) {
+ $this->output( "Updating site stats..." );
+ $ga = $isGoodArticle ? -1 : 0; // if it was good, decrement that too
+ $stats = new SiteStatsUpdate( 0, -$count, $ga, -1 );
+ $stats->doUpdate();
+ $this->output( "done.\n" );
+ }
+ } else {
+ $this->output( "not found in database.\n" );
+ $dbw->commit();
+ }
+ }
+
+ public function deleteRevisions( $ids ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+
+ $tbl_rev = $dbw->tableName( 'revision' );
+
+ $set = implode( ', ', $ids );
+ $dbw->query( "DELETE FROM $tbl_rev WHERE rev_id IN ( $set )" );
-/** Show script usage information */
-function ShowUsage() {
- echo( "Remove a page record from the database.\n\n" );
- echo( "Usage: php nukePage.php <title>\n\n" );
- echo( " <title> : Page title; spaces escaped with underscores\n\n" );
+ $dbw->commit();
+ }
}
+$maintClass = "NukePage";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/ora/patch_seq_names_pre1.16.sql b/maintenance/ora/patch_seq_names_pre1.16.sql
new file mode 100644
index 00000000..5346b141
--- /dev/null
+++ b/maintenance/ora/patch_seq_names_pre1.16.sql
@@ -0,0 +1,8 @@
+-- script for renameing sequence names to conform with <table>_<field>_seq format
+RENAME rev_rev_id_val TO revision_rev_id_seq;
+RENAME text_old_id_val TO text_old_id_seq;
+RENAME category_id_seq TO category_cat_id_seq;
+RENAME ipblocks_ipb_id_val TO ipblocks_ipb_id_seq;
+RENAME rc_rc_id_seq TO recentchanges_rc_id_seq;
+RENAME log_log_id_seq TO logging_log_id_seq;
+RENAME pr_id_val TO page_restrictions_pr_id_seq; \ No newline at end of file
diff --git a/maintenance/ora/tables.sql b/maintenance/ora/tables.sql
index 6d4b8ed5..d2d1a21b 100644
--- a/maintenance/ora/tables.sql
+++ b/maintenance/ora/tables.sql
@@ -1,443 +1,794 @@
--- SQL to create the initial tables for the MediaWiki database.
--- This is read and executed by the install script; you should
--- not have to run it by itself unless doing a manual install.
--- This is the Oracle version (based on PostgreSQL schema).
--- For information about each table, please see the notes in maintenance/tables.sql
+-- defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
+define mw_prefix='{$wgDBprefix}';
-CREATE SEQUENCE user_user_id_seq MINVALUE 0 START WITH 0;
-CREATE TABLE mwuser ( -- replace reserved word 'user'
- user_id INTEGER NOT NULL PRIMARY KEY,
- user_name VARCHAR(255) NOT NULL UNIQUE,
- user_real_name CLOB,
- user_password CLOB,
- user_newpassword CLOB,
- user_newpass_time TIMESTAMP WITH TIME ZONE,
- user_token CHAR(32),
- user_email CLOB,
- user_email_token CHAR(32),
- user_email_token_expires TIMESTAMP WITH TIME ZONE,
- user_email_authenticated TIMESTAMP WITH TIME ZONE,
+CREATE SEQUENCE user_user_id_seq MINVALUE 0 START WITH 0;
+CREATE TABLE &mw_prefix.mwuser ( -- replace reserved word 'user'
+ user_id NUMBER NOT NULL,
+ user_name VARCHAR2(255) NOT NULL,
+ user_real_name VARCHAR2(512),
+ user_password VARCHAR2(255),
+ user_newpassword VARCHAR2(255),
+ user_newpass_time TIMESTAMP(6) WITH TIME ZONE,
+ user_token VARCHAR2(32),
+ user_email VARCHAR2(255),
+ user_email_token VARCHAR2(32),
+ user_email_token_expires TIMESTAMP(6) WITH TIME ZONE,
+ user_email_authenticated TIMESTAMP(6) WITH TIME ZONE,
user_options CLOB,
- user_touched TIMESTAMP WITH TIME ZONE,
- user_registration TIMESTAMP WITH TIME ZONE,
- user_editcount INTEGER
+ user_touched TIMESTAMP(6) WITH TIME ZONE,
+ user_registration TIMESTAMP(6) WITH TIME ZONE,
+ user_editcount NUMBER
);
-CREATE INDEX user_email_token_idx ON mwuser (user_email_token);
+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);
+CREATE INDEX &mw_prefix.mwuser_i01 ON &mw_prefix.mwuser (user_email_token);
-- Create a dummy user to satisfy fk contraints especially with revisions
-INSERT INTO mwuser
+INSERT INTO &mw_prefix.mwuser
VALUES (user_user_id_seq.nextval,'Anonymous','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, '', current_timestamp, current_timestamp, 0);
-CREATE TABLE user_groups (
- ug_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
- ug_group CHAR(16) NOT NULL
+CREATE TABLE &mw_prefix.user_groups (
+ ug_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE,
+ ug_group VARCHAR2(16) NOT NULL
);
-CREATE UNIQUE INDEX user_groups_unique ON user_groups (ug_user, ug_group);
+CREATE UNIQUE INDEX &mw_prefix.user_groups_u01 ON &mw_prefix.user_groups (ug_user,ug_group);
+CREATE INDEX &mw_prefix.user_groups_i01 ON &mw_prefix.user_groups (ug_group);
-CREATE TABLE user_newtalk (
- user_id INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
- user_ip VARCHAR(40) NULL
+CREATE TABLE &mw_prefix.user_newtalk (
+ user_id NUMBER NOT NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE,
+ user_ip VARCHAR2(40) NULL,
+ user_last_timestamp TIMESTAMP(6) WITH TIME ZONE
);
-CREATE INDEX user_newtalk_id_idx ON user_newtalk (user_id);
-CREATE INDEX user_newtalk_ip_idx ON user_newtalk (user_ip);
+CREATE INDEX &mw_prefix.user_newtalk_i01 ON &mw_prefix.user_newtalk (user_id);
+CREATE INDEX &mw_prefix.user_newtalk_i02 ON &mw_prefix.user_newtalk (user_ip);
-CREATE SEQUENCE page_page_id_seq;
-CREATE TABLE page (
- page_id INTEGER NOT NULL PRIMARY KEY,
- page_namespace SMALLINT NOT NULL,
- page_title VARCHAR(255) NOT NULL,
- page_restrictions CLOB,
- page_counter INTEGER DEFAULT 0 NOT NULL,
- page_is_redirect CHAR DEFAULT 0 NOT NULL,
- page_is_new CHAR DEFAULT 0 NOT NULL,
- page_random NUMERIC(15,14) NOT NULL,
- page_touched TIMESTAMP WITH TIME ZONE,
- page_latest INTEGER NOT NULL, -- FK?
- page_len INTEGER NOT NULL
-);
-CREATE UNIQUE INDEX page_unique_name ON page (page_namespace, page_title);
-CREATE INDEX page_random_idx ON page (page_random);
-CREATE INDEX page_len_idx ON page (page_len);
-
-CREATE TRIGGER page_set_random BEFORE INSERT ON page
- FOR EACH ROW WHEN (new.page_random IS NULL)
- BEGIN
- SELECT dbms_random.value INTO :new.page_random FROM dual;
- END;
-/
-
-CREATE SEQUENCE rev_rev_id_val;
-CREATE TABLE revision (
- rev_id INTEGER NOT NULL PRIMARY KEY,
- rev_page INTEGER NULL REFERENCES page (page_id) ON DELETE CASCADE,
- rev_text_id INTEGER NULL, -- FK
- rev_comment CLOB,
- rev_user INTEGER NOT NULL REFERENCES mwuser(user_id),
- rev_user_text VARCHAR(255) NOT NULL,
- rev_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
- rev_minor_edit CHAR DEFAULT '0' NOT NULL,
- rev_deleted CHAR DEFAULT '0' NOT NULL,
- rev_len INTEGER NULL,
- rev_parent_id INTEGER DEFAULT NULL
-);
-CREATE UNIQUE INDEX revision_unique ON revision (rev_page, rev_id);
-CREATE INDEX rev_text_id_idx ON revision (rev_text_id);
-CREATE INDEX rev_timestamp_idx ON revision (rev_timestamp);
-CREATE INDEX rev_user_idx ON revision (rev_user);
-CREATE INDEX rev_user_text_idx ON revision (rev_user_text);
-
-
-CREATE SEQUENCE text_old_id_val;
-CREATE TABLE pagecontent ( -- replaces reserved word 'text'
- old_id INTEGER NOT NULL PRIMARY KEY,
- old_text CLOB,
- old_flags CLOB
+CREATE TABLE &mw_prefix.user_properties (
+ up_user NUMBER NOT NULL,
+ up_property VARCHAR2(32) NOT NULL,
+ up_value CLOB
);
+CREATE UNIQUE INDEX &mw_prefix.user_properties_u01 on &mw_prefix.user_properties (up_user,up_property);
+CREATE INDEX &mw_prefix.user_properties_i01 on &mw_prefix.user_properties (up_property);
-CREATE SEQUENCE pr_id_val;
-CREATE TABLE page_restrictions (
- pr_id INTEGER NOT NULL UNIQUE,
- pr_page INTEGER NULL REFERENCES page (page_id) ON DELETE CASCADE,
- pr_type VARCHAR(255) NOT NULL,
- pr_level VARCHAR(255) NOT NULL,
- pr_cascade SMALLINT NOT NULL,
- pr_user INTEGER NULL,
- pr_expiry TIMESTAMP WITH TIME ZONE NULL
+CREATE SEQUENCE page_page_id_seq;
+CREATE TABLE &mw_prefix.page (
+ page_id NUMBER NOT NULL,
+ page_namespace NUMBER NOT NULL,
+ page_title VARCHAR2(255) NOT NULL,
+ page_restrictions VARCHAR2(255),
+ page_counter NUMBER DEFAULT 0 NOT NULL,
+ page_is_redirect CHAR(1) DEFAULT 0 NOT NULL,
+ page_is_new CHAR(1) DEFAULT 0 NOT NULL,
+ page_random NUMBER(15,14) NOT NULL,
+ page_touched TIMESTAMP(6) WITH TIME ZONE,
+ page_latest NUMBER NOT NULL, -- FK?
+ page_len NUMBER NOT 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);
+CREATE INDEX &mw_prefix.page_i01 ON &mw_prefix.page (page_random);
+CREATE INDEX &mw_prefix.page_i02 ON &mw_prefix.page (page_len);
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.page_set_random BEFORE INSERT ON &mw_prefix.page
+ FOR EACH ROW WHEN (new.page_random IS NULL)
+BEGIN
+ SELECT dbms_random.value INTO :NEW.page_random FROM dual;
+END;
+/*$mw$*/
+
+CREATE SEQUENCE revision_rev_id_seq;
+CREATE TABLE &mw_prefix.revision (
+ rev_id NUMBER NOT NULL,
+ rev_page NUMBER NULL REFERENCES &mw_prefix.page (page_id) ON DELETE CASCADE,
+ rev_text_id NUMBER NULL,
+ rev_comment VARCHAR2(255),
+ rev_user NUMBER NOT NULL REFERENCES &mw_prefix.mwuser(user_id),
+ rev_user_text VARCHAR2(255) NOT NULL,
+ rev_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL,
+ rev_minor_edit CHAR(1) DEFAULT '0' NOT NULL,
+ rev_deleted CHAR(1) DEFAULT '0' NOT NULL,
+ rev_len NUMBER NULL,
+ rev_parent_id NUMBER DEFAULT NULL
+);
+ALTER TABLE &mw_prefix.revision ADD CONSTRAINT &mw_prefix.revision_pk PRIMARY KEY (rev_id);
+CREATE UNIQUE INDEX &mw_prefix.revision_u01 ON &mw_prefix.revision (rev_page, rev_id);
+CREATE INDEX &mw_prefix.revision_i01 ON &mw_prefix.revision (rev_timestamp);
+CREATE INDEX &mw_prefix.revision_i02 ON &mw_prefix.revision (rev_page,rev_timestamp);
+CREATE INDEX &mw_prefix.revision_i03 ON &mw_prefix.revision (rev_user,rev_timestamp);
+CREATE INDEX &mw_prefix.revision_i04 ON &mw_prefix.revision (rev_user_text,rev_timestamp);
+
+CREATE SEQUENCE text_old_id_seq;
+CREATE TABLE &mw_prefix.pagecontent ( -- replaces reserved word 'text'
+ old_id NUMBER NOT NULL,
+ old_text CLOB,
+ old_flags VARCHAR2(255)
);
-ALTER TABLE page_restrictions ADD CONSTRAINT page_restrictions_pk PRIMARY KEY (pr_page,pr_type);
+ALTER TABLE &mw_prefix.pagecontent ADD CONSTRAINT &mw_prefix.pagecontent_pk PRIMARY KEY (old_id);
-CREATE TABLE archive (
- ar_namespace SMALLINT NOT NULL,
- ar_title VARCHAR(255) NOT NULL,
+CREATE TABLE &mw_prefix.archive (
+ ar_namespace NUMBER NOT NULL,
+ ar_title VARCHAR2(255) NOT NULL,
ar_text CLOB,
- ar_comment CLOB,
- ar_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL,
- ar_user_text CLOB NOT NULL,
- ar_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
- ar_minor_edit CHAR DEFAULT '0' NOT NULL,
- ar_flags CLOB,
- ar_rev_id INTEGER,
- ar_text_id INTEGER,
- ar_deleted INTEGER DEFAULT '0' NOT NULL
-);
-CREATE INDEX archive_name_title_timestamp ON archive (ar_namespace,ar_title,ar_timestamp);
-
-CREATE TABLE redirect (
- rd_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
- rd_namespace SMALLINT NOT NULL,
- rd_title VARCHAR(255) NOT NULL
-);
-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,
- pl_namespace SMALLINT NOT NULL,
- pl_title VARCHAR(255) NOT NULL
-);
-CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title);
-
-CREATE TABLE templatelinks (
- tl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
- tl_namespace INTEGER NOT NULL,
- tl_title VARCHAR(255) NOT NULL
-);
-CREATE UNIQUE INDEX templatelinks_unique ON templatelinks (tl_namespace,tl_title,tl_from);
-
-CREATE TABLE imagelinks (
- il_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
- il_to VARCHAR(255) NOT NULL
-);
-CREATE UNIQUE INDEX il_from ON imagelinks (il_to,il_from);
-
-CREATE TABLE categorylinks (
- cl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
- cl_to VARCHAR(255) NOT NULL,
- cl_sortkey VARCHAR(86),
- cl_timestamp TIMESTAMP WITH TIME ZONE NOT NULL
-);
-CREATE UNIQUE INDEX cl_from ON categorylinks (cl_from, cl_to);
-CREATE INDEX cl_sortkey ON categorylinks (cl_to, cl_sortkey);
-
-CREATE TABLE externallinks (
- el_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
- el_to VARCHAR(2048) NOT NULL,
- el_index CLOB NOT NULL
-);
--- XXX CREATE INDEX externallinks_from_to ON externallinks (el_from,el_to);
--- XXX CREATE INDEX externallinks_index ON externallinks (el_index);
-
-CREATE TABLE langlinks (
- ll_from INTEGER NOT NULL REFERENCES page (page_id) ON DELETE CASCADE,
- ll_lang VARCHAR(10),
- ll_title VARCHAR(255)
-);
-CREATE UNIQUE INDEX langlinks_unique ON langlinks (ll_from,ll_lang);
-CREATE INDEX langlinks_lang_title ON langlinks (ll_lang,ll_title);
-
-
-CREATE TABLE site_stats (
- ss_row_id INTEGER NOT NULL UNIQUE,
- ss_total_views INTEGER DEFAULT 0,
- ss_total_edits INTEGER DEFAULT 0,
- ss_good_articles INTEGER DEFAULT 0,
- ss_total_pages INTEGER DEFAULT -1,
- ss_users INTEGER DEFAULT -1,
- ss_admins INTEGER DEFAULT -1,
- ss_images INTEGER DEFAULT 0
-);
-
-CREATE TABLE hitcounter (
- hc_id INTEGER NOT NULL
-);
-
-
-CREATE SEQUENCE ipblocks_ipb_id_val;
-CREATE TABLE ipblocks (
- ipb_id INTEGER NOT NULL PRIMARY KEY,
- ipb_address VARCHAR(255) NULL,
- ipb_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL,
- ipb_by INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
- ipb_reason VARCHAR(255) NOT NULL,
- ipb_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
- ipb_auto CHAR DEFAULT '0' NOT NULL,
- ipb_anon_only CHAR DEFAULT '0' NOT NULL,
- ipb_create_account CHAR DEFAULT '1' NOT NULL,
- ipb_enable_autoblock CHAR DEFAULT '1' NOT NULL,
- ipb_expiry TIMESTAMP WITH TIME ZONE NOT NULL,
- ipb_range_start CHAR(8),
- ipb_range_end CHAR(8),
- ipb_deleted INTEGER DEFAULT '0' NOT NULL
-);
-CREATE INDEX ipb_address ON ipblocks (ipb_address);
-CREATE INDEX ipb_user ON ipblocks (ipb_user);
-CREATE INDEX ipb_range ON ipblocks (ipb_range_start,ipb_range_end);
-
-
-CREATE TABLE image (
- img_name VARCHAR(255) NOT NULL PRIMARY KEY,
- img_size INTEGER NOT NULL,
- img_width INTEGER NOT NULL,
- img_height INTEGER NOT NULL,
+ ar_comment VARCHAR2(255),
+ ar_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL,
+ ar_user_text VARCHAR2(255) NOT NULL,
+ ar_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL,
+ ar_minor_edit CHAR(1) DEFAULT '0' NOT NULL,
+ ar_flags VARCHAR2(255),
+ ar_rev_id NUMBER,
+ ar_text_id NUMBER,
+ ar_deleted NUMBER DEFAULT '0' NOT NULL,
+ ar_len NUMBER,
+ ar_page_id NUMBER,
+ ar_parent_id NUMBER
+);
+CREATE INDEX &mw_prefix.archive_i01 ON &mw_prefix.archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX &mw_prefix.archive_i02 ON &mw_prefix.archive (ar_user_text,ar_timestamp);
+
+
+CREATE TABLE &mw_prefix.pagelinks (
+ pl_from NUMBER NOT NULL REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE,
+ pl_namespace NUMBER NOT NULL,
+ pl_title VARCHAR2(255) NOT NULL
+);
+CREATE UNIQUE INDEX &mw_prefix.pagelinks_u01 ON &mw_prefix.pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX &mw_prefix.pagelinks_u02 ON &mw_prefix.pagelinks (pl_namespace,pl_title,pl_from);
+
+CREATE TABLE &mw_prefix.templatelinks (
+ tl_from NUMBER NOT NULL REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE,
+ tl_namespace NUMBER NOT NULL,
+ tl_title VARCHAR2(255) NOT NULL
+);
+CREATE UNIQUE INDEX &mw_prefix.templatelinks_u01 ON &mw_prefix.templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX &mw_prefix.templatelinks_u02 ON &mw_prefix.templatelinks (tl_namespace,tl_title,tl_from);
+
+CREATE TABLE &mw_prefix.imagelinks (
+ il_from NUMBER NOT NULL REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE,
+ il_to VARCHAR2(255) NOT NULL
+);
+CREATE UNIQUE INDEX &mw_prefix.imagelinks_u01 ON &mw_prefix.imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX &mw_prefix.imagelinks_u02 ON &mw_prefix.imagelinks (il_to,il_from);
+
+
+CREATE TABLE &mw_prefix.categorylinks (
+ cl_from NUMBER NOT NULL REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE,
+ cl_to VARCHAR2(255) NOT NULL,
+ cl_sortkey VARCHAR2(255),
+ cl_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL
+);
+CREATE UNIQUE INDEX &mw_prefix.categorylinks_u01 ON &mw_prefix.categorylinks (cl_from,cl_to);
+CREATE INDEX &mw_prefix.categorylinks_i01 ON &mw_prefix.categorylinks (cl_to,cl_sortkey,cl_from);
+CREATE INDEX &mw_prefix.categorylinks_i02 ON &mw_prefix.categorylinks (cl_to,cl_timestamp);
+
+CREATE SEQUENCE category_cat_id_seq;
+CREATE TABLE &mw_prefix.category (
+ cat_id NUMBER NOT NULL,
+ cat_title VARCHAR2(255) NOT NULL,
+ cat_pages NUMBER DEFAULT 0 NOT NULL,
+ cat_subcats NUMBER DEFAULT 0 NOT NULL,
+ cat_files NUMBER DEFAULT 0 NOT NULL,
+ cat_hidden NUMBER DEFAULT 0 NOT NULL
+);
+ALTER TABLE &mw_prefix.category ADD CONSTRAINT &mw_prefix.category_pk PRIMARY KEY (cat_id);
+CREATE UNIQUE INDEX &mw_prefix.category_u01 ON &mw_prefix.category (cat_title);
+CREATE INDEX &mw_prefix.category_i01 ON &mw_prefix.category (cat_pages);
+
+CREATE TABLE &mw_prefix.externallinks (
+ el_from NUMBER NOT NULL REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE,
+ el_to VARCHAR2(2048) NOT NULL,
+ el_index VARCHAR2(2048) NOT NULL
+);
+CREATE INDEX &mw_prefix.externallinks_i01 ON &mw_prefix.externallinks (el_from, el_to);
+CREATE INDEX &mw_prefix.externallinks_i02 ON &mw_prefix.externallinks (el_to, el_from);
+CREATE INDEX &mw_prefix.externallinks_i03 ON &mw_prefix.externallinks (el_index);
+
+CREATE TABLE &mw_prefix.external_user (
+ eu_local_id NUMBER NOT NULL,
+ eu_external_id varchar2(255) NOT NULL
+);
+ALTER TABLE &mw_prefix.external_user ADD CONSTRAINT &mw_prefix.external_user_pk PRIMARY KEY (eu_local_id);
+CREATE UNIQUE INDEX &mw_prefix.external_user_u01 ON &mw_prefix.external_user (eu_external_id);
+
+CREATE TABLE &mw_prefix.langlinks (
+ ll_from NUMBER NOT NULL REFERENCES &mw_prefix.page (page_id) ON DELETE CASCADE,
+ ll_lang VARCHAR2(20),
+ ll_title VARCHAR2(255)
+);
+CREATE UNIQUE INDEX &mw_prefix.langlinks_u01 ON &mw_prefix.langlinks (ll_from, ll_lang);
+CREATE INDEX &mw_prefix.langlinks_i01 ON &mw_prefix.langlinks (ll_lang, ll_title);
+
+CREATE TABLE &mw_prefix.site_stats (
+ ss_row_id NUMBER NOT NULL ,
+ ss_total_views NUMBER DEFAULT 0,
+ ss_total_edits NUMBER DEFAULT 0,
+ ss_good_articles NUMBER DEFAULT 0,
+ ss_total_pages NUMBER DEFAULT -1,
+ ss_users NUMBER DEFAULT -1,
+ ss_active_users NUMBER DEFAULT -1,
+ ss_admins NUMBER DEFAULT -1,
+ ss_images NUMBER DEFAULT 0
+);
+CREATE UNIQUE INDEX &mw_prefix.site_stats_u01 ON &mw_prefix.site_stats (ss_row_id);
+
+CREATE TABLE &mw_prefix.hitcounter (
+ hc_id NUMBER NOT NULL
+);
+
+CREATE SEQUENCE ipblocks_ipb_id_seq;
+CREATE TABLE &mw_prefix.ipblocks (
+ ipb_id NUMBER NOT NULL,
+ ipb_address VARCHAR2(255) NULL,
+ ipb_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL,
+ ipb_by NUMBER NOT NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE,
+ ipb_by_text VARCHAR2(255) NOT NULL,
+ ipb_reason VARCHAR2(255) NOT NULL,
+ ipb_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL,
+ ipb_auto CHAR(1) DEFAULT '0' NOT NULL,
+ ipb_anon_only CHAR(1) DEFAULT '0' NOT NULL,
+ ipb_create_account CHAR(1) DEFAULT '1' NOT NULL,
+ ipb_enable_autoblock CHAR(1) DEFAULT '1' NOT NULL,
+ ipb_expiry TIMESTAMP(6) WITH TIME ZONE NOT NULL,
+ ipb_range_start VARCHAR2(255),
+ ipb_range_end VARCHAR2(255),
+ ipb_deleted CHAR(1) DEFAULT '0' NOT NULL,
+ ipb_block_email CHAR(1) DEFAULT '0' NOT NULL,
+ ipb_allow_usertalk CHAR(1) DEFAULT '0' NOT NULL
+);
+ALTER TABLE &mw_prefix.ipblocks ADD CONSTRAINT &mw_prefix.ipblocks_pk PRIMARY KEY (ipb_id);
+CREATE UNIQUE INDEX &mw_prefix.ipblocks_u01 ON &mw_prefix.ipblocks (ipb_address, ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX &mw_prefix.ipblocks_i01 ON &mw_prefix.ipblocks (ipb_user);
+CREATE INDEX &mw_prefix.ipblocks_i02 ON &mw_prefix.ipblocks (ipb_range_start, ipb_range_end);
+CREATE INDEX &mw_prefix.ipblocks_i03 ON &mw_prefix.ipblocks (ipb_timestamp);
+CREATE INDEX &mw_prefix.ipblocks_i04 ON &mw_prefix.ipblocks (ipb_expiry);
+
+CREATE TABLE &mw_prefix.image (
+ img_name VARCHAR2(255) NOT NULL,
+ img_size NUMBER NOT NULL,
+ img_width NUMBER NOT NULL,
+ img_height NUMBER NOT NULL,
img_metadata CLOB,
- img_bits SMALLINT,
- img_media_type CLOB,
- img_major_mime CLOB DEFAULT 'unknown',
- img_minor_mime CLOB DEFAULT 'unknown',
- img_description CLOB,
- img_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL,
- img_user_text CLOB NOT NULL,
- img_timestamp TIMESTAMP WITH TIME ZONE
-);
-CREATE INDEX img_size_idx ON image (img_size);
-CREATE INDEX img_timestamp_idx ON image (img_timestamp);
-
-CREATE TABLE oldimage (
- oi_name VARCHAR(255) NOT NULL REFERENCES image(img_name),
- oi_archive_name VARCHAR(255),
- oi_size INTEGER NOT NULL,
- oi_width INTEGER NOT NULL,
- oi_height INTEGER NOT NULL,
- oi_bits SMALLINT NOT NULL,
- oi_description CLOB,
- oi_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL,
- oi_user_text CLOB NOT NULL,
- oi_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
- oi_metadata CLOB,
- oi_media_type VARCHAR(10) DEFAULT NULL,
- oi_major_mime VARCHAR(11) DEFAULT 'unknown',
- oi_minor_mime VARCHAR(32) DEFAULT 'unknown',
- oi_deleted INTEGER DEFAULT 0 NOT NULL
-);
-CREATE INDEX oi_name_timestamp ON oldimage (oi_name,oi_timestamp);
-CREATE INDEX oi_name_archive_name ON oldimage (oi_name,oi_archive_name);
+ img_bits NUMBER,
+ img_media_type VARCHAR2(32),
+ img_major_mime VARCHAR2(32) DEFAULT 'unknown',
+ img_minor_mime VARCHAR2(100) DEFAULT 'unknown',
+ img_description VARCHAR2(255),
+ img_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL,
+ img_user_text VARCHAR2(255) NOT NULL,
+ img_timestamp TIMESTAMP(6) WITH TIME ZONE,
+ img_sha1 VARCHAR2(32)
+);
+ALTER TABLE &mw_prefix.image ADD CONSTRAINT &mw_prefix.image_pk PRIMARY KEY (img_name);
+CREATE INDEX &mw_prefix.image_i01 ON &mw_prefix.image (img_user_text,img_timestamp);
+CREATE INDEX &mw_prefix.image_i02 ON &mw_prefix.image (img_size);
+CREATE INDEX &mw_prefix.image_i03 ON &mw_prefix.image (img_timestamp);
+CREATE INDEX &mw_prefix.image_i04 ON &mw_prefix.image (img_sha1);
+
+
+CREATE TABLE &mw_prefix.oldimage (
+ oi_name VARCHAR2(255) NOT NULL REFERENCES &mw_prefix.image(img_name),
+ oi_archive_name VARCHAR2(255),
+ oi_size NUMBER NOT NULL,
+ oi_width NUMBER NOT NULL,
+ oi_height NUMBER NOT NULL,
+ oi_bits NUMBER NOT NULL,
+ oi_description VARCHAR2(255),
+ oi_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL,
+ oi_user_text VARCHAR2(255) NOT NULL,
+ oi_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL,
+ oi_metadata CLOB,
+ oi_media_type VARCHAR2(32) DEFAULT NULL,
+ oi_major_mime VARCHAR2(32) DEFAULT 'unknown',
+ oi_minor_mime VARCHAR2(100) DEFAULT 'unknown',
+ oi_deleted NUMBER DEFAULT 0 NOT NULL,
+ oi_sha1 VARCHAR2(32)
+);
+CREATE INDEX &mw_prefix.oldimage_i01 ON &mw_prefix.oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX &mw_prefix.oldimage_i02 ON &mw_prefix.oldimage (oi_name,oi_timestamp);
+CREATE INDEX &mw_prefix.oldimage_i03 ON &mw_prefix.oldimage (oi_name,oi_archive_name);
+CREATE INDEX &mw_prefix.oldimage_i04 ON &mw_prefix.oldimage (oi_sha1);
+
CREATE SEQUENCE filearchive_fa_id_seq;
-CREATE TABLE filearchive (
- fa_id INTEGER NOT NULL PRIMARY KEY,
- fa_name VARCHAR(255) NOT NULL,
- fa_archive_name VARCHAR(255),
- fa_storage_group VARCHAR(16),
- fa_storage_key CHAR(64),
- fa_deleted_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL,
- fa_deleted_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
+CREATE TABLE &mw_prefix.filearchive (
+ fa_id NUMBER NOT NULL,
+ fa_name VARCHAR2(255) NOT NULL,
+ fa_archive_name VARCHAR2(255),
+ fa_storage_group VARCHAR2(16),
+ fa_storage_key VARCHAR2(64),
+ fa_deleted_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL,
+ fa_deleted_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL,
fa_deleted_reason CLOB,
- fa_size SMALLINT NOT NULL,
- fa_width SMALLINT NOT NULL,
- fa_height SMALLINT NOT NULL,
+ fa_size NUMBER NOT NULL,
+ fa_width NUMBER NOT NULL,
+ fa_height NUMBER NOT NULL,
fa_metadata CLOB,
- fa_bits SMALLINT,
- fa_media_type CLOB,
- fa_major_mime CLOB DEFAULT 'unknown',
- fa_minor_mime CLOB DEFAULT 'unknown',
- fa_description CLOB NOT NULL,
- fa_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL,
- fa_user_text CLOB NOT NULL,
- fa_timestamp TIMESTAMP WITH TIME ZONE,
- fa_deleted INTEGER DEFAULT '0' NOT NULL
-);
-CREATE INDEX fa_name_time ON filearchive (fa_name, fa_timestamp);
-CREATE INDEX fa_dupe ON filearchive (fa_storage_group, fa_storage_key);
-CREATE INDEX fa_notime ON filearchive (fa_deleted_timestamp);
-CREATE INDEX fa_nouser ON filearchive (fa_deleted_user);
-
-
-CREATE SEQUENCE rc_rc_id_seq;
-CREATE TABLE recentchanges (
- rc_id INTEGER NOT NULL PRIMARY KEY,
- rc_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
- rc_cur_time TIMESTAMP WITH TIME ZONE NOT NULL,
- rc_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL,
- rc_user_text CLOB NOT NULL,
- rc_namespace SMALLINT NOT NULL,
- rc_title VARCHAR(255) NOT NULL,
- rc_comment VARCHAR(255),
- rc_minor CHAR DEFAULT '0' NOT NULL,
- rc_bot CHAR DEFAULT '0' NOT NULL,
- rc_new CHAR DEFAULT '0' NOT NULL,
- rc_cur_id INTEGER NULL REFERENCES page(page_id) ON DELETE SET NULL,
- rc_this_oldid INTEGER NOT NULL,
- rc_last_oldid INTEGER NOT NULL,
- rc_type CHAR DEFAULT '0' NOT NULL,
- rc_moved_to_ns SMALLINT,
- rc_moved_to_title CLOB,
- rc_patrolled CHAR DEFAULT '0' NOT NULL,
- rc_ip VARCHAR(15),
- rc_old_len INTEGER,
- rc_new_len INTEGER,
- rc_deleted INTEGER DEFAULT '0' NOT NULL,
- rc_logid INTEGER DEFAULT '0' NOT NULL,
- rc_log_type CLOB,
- rc_log_action CLOB,
- rc_params CLOB
-);
-CREATE INDEX rc_timestamp ON recentchanges (rc_timestamp);
-CREATE INDEX rc_namespace_title ON recentchanges (rc_namespace, rc_title);
-CREATE INDEX rc_cur_id ON recentchanges (rc_cur_id);
-CREATE INDEX new_name_timestamp ON recentchanges (rc_new, rc_namespace, rc_timestamp);
-CREATE INDEX rc_ip ON recentchanges (rc_ip);
-
-
-CREATE TABLE watchlist (
- wl_user INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
- wl_namespace SMALLINT DEFAULT 0 NOT NULL,
- wl_title VARCHAR(255) NOT NULL,
- wl_notificationtimestamp TIMESTAMP WITH TIME ZONE
-);
-CREATE UNIQUE INDEX wl_user_namespace_title ON watchlist (wl_namespace, wl_title, wl_user);
-
-
-CREATE TABLE math (
- math_inputhash VARCHAR(16) NOT NULL UNIQUE,
- math_outputhash VARCHAR(16) NOT NULL,
- math_html_conservativeness SMALLINT NOT NULL,
+ fa_bits NUMBER,
+ fa_media_type VARCHAR2(32) DEFAULT NULL,
+ fa_major_mime VARCHAR2(32) DEFAULT 'unknown',
+ fa_minor_mime VARCHAR2(100) DEFAULT 'unknown',
+ fa_description VARCHAR2(255),
+ fa_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL,
+ fa_user_text VARCHAR2(255) NOT NULL,
+ fa_timestamp TIMESTAMP(6) WITH TIME ZONE,
+ fa_deleted NUMBER DEFAULT '0' NOT NULL
+);
+ALTER TABLE &mw_prefix.filearchive ADD CONSTRAINT &mw_prefix.filearchive_pk PRIMARY KEY (fa_id);
+CREATE INDEX &mw_prefix.filearchive_i01 ON &mw_prefix.filearchive (fa_name, fa_timestamp);
+CREATE INDEX &mw_prefix.filearchive_i02 ON &mw_prefix.filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX &mw_prefix.filearchive_i03 ON &mw_prefix.filearchive (fa_deleted_timestamp);
+CREATE INDEX &mw_prefix.filearchive_i04 ON &mw_prefix.filearchive (fa_user_text,fa_timestamp);
+
+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_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL,
+ rc_user_text VARCHAR2(255) NOT NULL,
+ rc_namespace NUMBER NOT NULL,
+ rc_title VARCHAR2(255) NOT NULL,
+ rc_comment VARCHAR2(255),
+ rc_minor CHAR(1) DEFAULT '0' NOT NULL,
+ rc_bot CHAR(1) DEFAULT '0' NOT NULL,
+ rc_new CHAR(1) DEFAULT '0' NOT NULL,
+ rc_cur_id NUMBER NULL REFERENCES &mw_prefix.page(page_id) ON DELETE SET NULL,
+ rc_this_oldid NUMBER NOT NULL,
+ rc_last_oldid NUMBER NOT NULL,
+ rc_type CHAR(1) DEFAULT '0' NOT NULL,
+ rc_moved_to_ns NUMBER,
+ rc_moved_to_title VARCHAR2(255),
+ rc_patrolled CHAR(1) DEFAULT '0' NOT NULL,
+ rc_ip VARCHAR2(15),
+ rc_old_len NUMBER,
+ rc_new_len NUMBER,
+ rc_deleted NUMBER DEFAULT '0' NOT NULL,
+ rc_logid NUMBER DEFAULT '0' NOT NULL,
+ rc_log_type VARCHAR2(255),
+ rc_log_action VARCHAR2(255),
+ rc_params CLOB
+);
+ALTER TABLE &mw_prefix.recentchanges ADD CONSTRAINT &mw_prefix.recentchanges_pk PRIMARY KEY (rc_id);
+CREATE INDEX &mw_prefix.recentchanges_i01 ON &mw_prefix.recentchanges (rc_timestamp);
+CREATE INDEX &mw_prefix.recentchanges_i02 ON &mw_prefix.recentchanges (rc_namespace, rc_title);
+CREATE INDEX &mw_prefix.recentchanges_i03 ON &mw_prefix.recentchanges (rc_cur_id);
+CREATE INDEX &mw_prefix.recentchanges_i04 ON &mw_prefix.recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX &mw_prefix.recentchanges_i05 ON &mw_prefix.recentchanges (rc_ip);
+CREATE INDEX &mw_prefix.recentchanges_i06 ON &mw_prefix.recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX &mw_prefix.recentchanges_i07 ON &mw_prefix.recentchanges (rc_user_text, rc_timestamp);
+
+CREATE TABLE &mw_prefix.watchlist (
+ wl_user NUMBER NOT NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE,
+ wl_namespace NUMBER DEFAULT 0 NOT NULL,
+ wl_title VARCHAR2(255) NOT NULL,
+ wl_notificationtimestamp TIMESTAMP(6) WITH TIME ZONE
+);
+CREATE UNIQUE INDEX &mw_prefix.watchlist_u01 ON &mw_prefix.watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX &mw_prefix.watchlist_i01 ON &mw_prefix.watchlist (wl_namespace, wl_title);
+
+
+CREATE TABLE &mw_prefix.math (
+ math_inputhash VARCHAR2(32) NOT NULL,
+ math_outputhash VARCHAR2(32) NOT NULL,
+ math_html_conservativeness NUMBER NOT NULL,
math_html CLOB,
math_mathml CLOB
);
+CREATE UNIQUE INDEX &mw_prefix.math_u01 ON &mw_prefix.math (math_inputhash);
-
-CREATE TABLE interwiki (
- iw_prefix VARCHAR(32) NOT NULL UNIQUE,
- iw_url VARCHAR(127) NOT NULL,
- iw_local CHAR NOT NULL,
- iw_trans CHAR DEFAULT '0' NOT NULL
-);
-
-CREATE TABLE querycache (
- qc_type CHAR(32) NOT NULL,
- qc_value SMALLINT NOT NULL,
- qc_namespace SMALLINT NOT NULL,
- qc_title CHAR(255) NOT NULL
+CREATE TABLE &mw_prefix.searchindex (
+ si_page NUMBER NOT NULL,
+ si_title VARCHAR2(255) DEFAULT '' NOT NULL,
+ si_text CLOB NOT NULL
);
-CREATE INDEX querycache_type_value ON querycache (qc_type, qc_value);
+CREATE UNIQUE INDEX &mw_prefix.searchindex_u01 ON &mw_prefix.searchindex (si_page);
-CREATE TABLE querycache_info (
- qci_type VARCHAR(32) UNIQUE,
- qci_timestamp TIMESTAMP WITH TIME ZONE NULL
+CREATE TABLE &mw_prefix.interwiki (
+ iw_prefix VARCHAR2(32) NOT NULL,
+ iw_url VARCHAR2(127) NOT NULL,
+ iw_local CHAR(1) NOT NULL,
+ iw_trans CHAR(1) DEFAULT '0' NOT NULL
);
+CREATE UNIQUE INDEX &mw_prefix.interwiki_u01 ON &mw_prefix.interwiki (iw_prefix);
-CREATE TABLE querycachetwo (
- qcc_type CHAR(32) NOT NULL,
- qcc_value SMALLINT DEFAULT 0 NOT NULL,
- qcc_namespace INTEGER DEFAULT 0 NOT NULL,
- qcc_title CHAR(255) DEFAULT '' NOT NULL,
- qcc_namespacetwo INTEGER DEFAULT 0 NOT NULL,
- qcc_titletwo CHAR(255) DEFAULT '' NOT NULL
+CREATE TABLE &mw_prefix.querycache (
+ qc_type VARCHAR2(32) NOT NULL,
+ qc_value NUMBER NOT NULL,
+ qc_namespace NUMBER NOT NULL,
+ qc_title VARCHAR2(255) NOT NULL
);
-CREATE INDEX querycachetwo_type_value ON querycachetwo (qcc_type, qcc_value);
-CREATE INDEX querycachetwo_title ON querycachetwo (qcc_type,qcc_namespace,qcc_title);
-CREATE INDEX querycachetwo_titletwo ON querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
-
+CREATE INDEX &mw_prefix.querycache_u01 ON &mw_prefix.querycache (qc_type,qc_value);
-CREATE TABLE objectcache (
- keyname CHAR(255) UNIQUE,
+CREATE TABLE &mw_prefix.objectcache (
+ keyname VARCHAR2(255) ,
value BLOB,
- exptime TIMESTAMP WITH TIME ZONE NOT NULL
+ exptime TIMESTAMP(6) WITH TIME ZONE NOT NULL
);
-CREATE INDEX objectcacache_exptime ON objectcache (exptime);
+CREATE INDEX &mw_prefix.objectcache_i01 ON &mw_prefix.objectcache (exptime);
-CREATE TABLE transcache (
- tc_url VARCHAR(255) NOT NULL UNIQUE,
+CREATE TABLE &mw_prefix.transcache (
+ tc_url VARCHAR2(255) NOT NULL,
tc_contents CLOB NOT NULL,
- tc_time TIMESTAMP WITH TIME ZONE NOT NULL
+ tc_time TIMESTAMP(6) WITH TIME ZONE NOT NULL
+);
+CREATE UNIQUE INDEX &mw_prefix.transcache_u01 ON &mw_prefix.transcache (tc_url);
+
+
+CREATE SEQUENCE logging_log_id_seq;
+CREATE TABLE &mw_prefix.logging (
+ log_id NUMBER NOT NULL,
+ log_type VARCHAR2(10) NOT NULL,
+ log_action VARCHAR2(10) NOT NULL,
+ log_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL,
+ log_user NUMBER REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL,
+ log_user_text VARCHAR2(255),
+ log_namespace NUMBER NOT NULL,
+ log_title VARCHAR2(255) NOT NULL,
+ log_page NUMBER,
+ log_comment VARCHAR2(255),
+ log_params CLOB,
+ log_deleted NUMBER DEFAULT '0' NOT NULL
);
+ALTER TABLE &mw_prefix.logging ADD CONSTRAINT &mw_prefix.logging_pk PRIMARY KEY (log_id);
+CREATE INDEX &mw_prefix.logging_i01 ON &mw_prefix.logging (log_type, log_timestamp);
+CREATE INDEX &mw_prefix.logging_i02 ON &mw_prefix.logging (log_user, log_timestamp);
+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 SEQUENCE log_log_id_seq;
-CREATE TABLE logging (
- log_type VARCHAR(10) NOT NULL,
- log_action VARCHAR(10) NOT NULL,
- log_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
- log_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL,
- log_namespace SMALLINT NOT NULL,
- log_title VARCHAR(255) NOT NULL,
- log_comment VARCHAR(255),
- log_params CLOB,
- log_deleted INTEGER DEFAULT '0' NOT NULL,
- log_id INTEGER NOT NULL PRIMARY KEY
+CREATE TABLE &mw_prefix.log_search (
+ ls_field VARCHAR2(32) NOT NULL,
+ ls_value VARCHAR2(255) NOT NULL,
+ ls_log_id NuMBER DEFAULT 0 NOT NULL
);
-CREATE INDEX logging_type_name ON logging (log_type, log_timestamp);
-CREATE INDEX logging_user_time ON logging (log_timestamp, log_user);
-CREATE INDEX logging_page_time ON logging (log_namespace, log_title, log_timestamp);
+ALTER TABLE &mw_prefix.log_search ADD CONSTRAINT log_search_pk PRIMARY KEY (ls_field,ls_value,ls_log_id);
+CREATE INDEX &mw_prefix.log_search_i01 ON &mw_prefix.log_search (ls_log_id);
CREATE SEQUENCE trackbacks_tb_id_seq;
-CREATE TABLE trackbacks (
- tb_id INTEGER NOT NULL PRIMARY KEY,
- tb_page INTEGER REFERENCES page(page_id) ON DELETE CASCADE,
- tb_title VARCHAR(255) NOT NULL,
- tb_url VARCHAR(255) NOT NULL,
+CREATE TABLE &mw_prefix.trackbacks (
+ tb_id NUMBER NOT NULL,
+ tb_page NUMBER REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE,
+ tb_title VARCHAR2(255) NOT NULL,
+ tb_url VARCHAR2(255) NOT NULL,
tb_ex CLOB,
- tb_name VARCHAR(255)
+ tb_name VARCHAR2(255)
);
-CREATE INDEX trackback_page ON trackbacks (tb_page);
+ALTER TABLE &mw_prefix.trackbacks ADD CONSTRAINT &mw_prefix.trackbacks_pk PRIMARY KEY (tb_id);
+CREATE INDEX &mw_prefix.trackbacks_i01 ON &mw_prefix.trackbacks (tb_page);
CREATE SEQUENCE job_job_id_seq;
-CREATE TABLE job (
- job_id INTEGER NOT NULL PRIMARY KEY,
- job_cmd VARCHAR(255) NOT NULL,
- job_namespace SMALLINT NOT NULL,
- job_title VARCHAR(255) NOT NULL,
+CREATE TABLE &mw_prefix.job (
+ job_id NUMBER NOT NULL,
+ job_cmd VARCHAR2(60) NOT NULL,
+ job_namespace NUMBER NOT NULL,
+ job_title VARCHAR2(255) NOT NULL,
job_params CLOB NOT NULL
);
-CREATE INDEX job_cmd_namespace_title ON job (job_cmd, job_namespace, job_title);
+ALTER TABLE &mw_prefix.job ADD CONSTRAINT &mw_prefix.job_pk PRIMARY KEY (job_id);
+CREATE INDEX &mw_prefix.job_i01 ON &mw_prefix.job (job_cmd, job_namespace, job_title);
+
+CREATE TABLE &mw_prefix.querycache_info (
+ qci_type VARCHAR2(32) NOT NULL,
+ qci_timestamp TIMESTAMP(6) WITH TIME ZONE NULL
+);
+CREATE UNIQUE INDEX &mw_prefix.querycache_info_u01 ON &mw_prefix.querycache_info (qci_type);
+
+CREATE TABLE &mw_prefix.redirect (
+ rd_from NUMBER NOT NULL REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE,
+ rd_namespace NUMBER NOT NULL,
+ rd_title VARCHAR2(255) NOT NULL,
+ rd_interwiki VARCHAR2(32),
+ rd_fragment VARCHAR2(255)
+);
+CREATE INDEX &mw_prefix.redirect_i01 ON &mw_prefix.redirect (rd_namespace,rd_title,rd_from);
+
+CREATE TABLE &mw_prefix.querycachetwo (
+ qcc_type VARCHAR2(32) NOT NULL,
+ qcc_value NUMBER DEFAULT 0 NOT NULL,
+ qcc_namespace NUMBER DEFAULT 0 NOT NULL,
+ qcc_title VARCHAR2(255) DEFAULT '' NOT NULL,
+ qcc_namespacetwo NUMBER DEFAULT 0 NOT NULL,
+ qcc_titletwo VARCHAR2(255) DEFAULT '' NOT NULL
+);
+CREATE INDEX &mw_prefix.querycachetwo_i01 ON &mw_prefix.querycachetwo (qcc_type,qcc_value);
+CREATE INDEX &mw_prefix.querycachetwo_i02 ON &mw_prefix.querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX &mw_prefix.querycachetwo_i03 ON &mw_prefix.querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+
+CREATE SEQUENCE page_restrictions_pr_id_seq;
+CREATE TABLE &mw_prefix.page_restrictions (
+ pr_id NUMBER NOT NULL,
+ pr_page NUMBER NULL REFERENCES &mw_prefix.page (page_id) ON DELETE CASCADE,
+ pr_type VARCHAR2(255) NOT NULL,
+ pr_level VARCHAR2(255) NOT NULL,
+ pr_cascade NUMBER NOT NULL,
+ pr_user NUMBER NULL,
+ pr_expiry TIMESTAMP(6) WITH TIME ZONE NULL
+);
+ALTER TABLE &mw_prefix.page_restrictions ADD CONSTRAINT &mw_prefix.page_restrictions_pk PRIMARY KEY (pr_page,pr_type);
+CREATE INDEX &mw_prefix.page_restrictions_i01 ON &mw_prefix.page_restrictions (pr_type,pr_level);
+CREATE INDEX &mw_prefix.page_restrictions_i02 ON &mw_prefix.page_restrictions (pr_level);
+CREATE INDEX &mw_prefix.page_restrictions_i03 ON &mw_prefix.page_restrictions (pr_cascade);
+
+CREATE TABLE &mw_prefix.protected_titles (
+ pt_namespace NUMBER NOT NULL,
+ pt_title VARCHAR2(255) NOT NULL,
+ pt_user NUMBER NOT NULL,
+ pt_reason VARCHAR2(255),
+ pt_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL,
+ pt_expiry VARCHAR2(14) NOT NULL,
+ pt_create_perm VARCHAR2(60) NOT NULL
+);
+CREATE UNIQUE INDEX &mw_prefix.protected_titles_u01 ON &mw_prefix.protected_titles (pt_namespace,pt_title);
+CREATE INDEX &mw_prefix.protected_titles_i01 ON &mw_prefix.protected_titles (pt_timestamp);
+
+CREATE TABLE &mw_prefix.page_props (
+ pp_page NUMBER NOT NULL,
+ pp_propname VARCHAR2(60) NOT NULL,
+ pp_value BLOB NOT NULL
+);
+CREATE UNIQUE INDEX &mw_prefix.page_props_u01 ON &mw_prefix.page_props (pp_page,pp_propname);
+
+
+CREATE TABLE &mw_prefix.updatelog (
+ ul_key VARCHAR2(255) NOT NULL
+);
+ALTER TABLE &mw_prefix.updatelog ADD CONSTRAINT &mw_prefix.updatelog_pk PRIMARY KEY (ul_key);
+
+CREATE TABLE &mw_prefix.change_tag (
+ ct_rc_id NUMBER NULL,
+ ct_log_id NUMBER NULL,
+ ct_rev_id NUMBER NULL,
+ ct_tag VARCHAR2(255) NOT NULL,
+ ct_params BLOB NULL
+);
+CREATE UNIQUE INDEX &mw_prefix.change_tag_u01 ON &mw_prefix.change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX &mw_prefix.change_tag_u02 ON &mw_prefix.change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX &mw_prefix.change_tag_u03 ON &mw_prefix.change_tag (ct_rev_id,ct_tag);
+CREATE INDEX &mw_prefix.change_tag_i01 ON &mw_prefix.change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+
+CREATE TABLE &mw_prefix.tag_summary (
+ ts_rc_id NUMBER NULL,
+ ts_log_id NUMBER NULL,
+ ts_rev_id NUMBER NULL,
+ ts_tags BLOB NOT NULL
+);
+CREATE UNIQUE INDEX &mw_prefix.tag_summary_u01 ON &mw_prefix.tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX &mw_prefix.tag_summary_u02 ON &mw_prefix.tag_summary (ts_log_id);
+CREATE UNIQUE INDEX &mw_prefix.tag_summary_u03 ON &mw_prefix.tag_summary (ts_rev_id);
+
+CREATE TABLE &mw_prefix.valid_tag (
+ vt_tag VARCHAR2(255) NOT NULL
+);
+ALTER TABLE &mw_prefix.valid_tag ADD CONSTRAINT &mw_prefix.valid_tag_pk PRIMARY KEY (vt_tag);
-- This table is not used unless profiling is turned on
---CREATE TABLE profiling (
--- pf_count INTEGER DEFAULT 0 NOT NULL,
+--CREATE TABLE &mw_prefix.profiling (
+-- pf_count NUMBER DEFAULT 0 NOT NULL,
-- pf_time NUMERIC(18,10) DEFAULT 0 NOT NULL,
-- pf_name CLOB NOT NULL,
-- pf_server CLOB NULL
--);
---CREATE UNIQUE INDEX pf_name_server ON profiling (pf_name, pf_server);
-
-CREATE TABLE searchindex (
- si_page INTEGER UNIQUE NOT NULL,
- si_title VARCHAR(255) DEFAULT '' NOT NULL,
- si_text CLOB NOT NULL
-);
-
-
-CREATE INDEX si_title_idx ON searchindex(si_title) INDEXTYPE IS ctxsys.context;
-CREATE INDEX si_text_idx ON searchindex(si_text) INDEXTYPE IS ctxsys.context;
+--CREATE UNIQUE INDEX &mw_prefix.profiling_u01 ON &mw_prefix.profiling (pf_name, pf_server);
+
+CREATE INDEX si_title_idx ON &mw_prefix.searchindex(si_title) INDEXTYPE IS ctxsys.context;
+CREATE INDEX si_text_idx ON &mw_prefix.searchindex(si_text) INDEXTYPE IS ctxsys.context;
+
+CREATE TABLE &mw_prefix.l10n_cache (
+ lc_lang varchar2(32) NOT NULL,
+ lc_key varchar2(255) NOT NULL,
+ lc_value clob NOT NULL
+);
+CREATE INDEX &mw_prefix.l10n_cache_u01 ON &mw_prefix.l10n_cache (lc_lang, lc_key);
+
+-- do not prefix this table as it breaks parserTests
+CREATE TABLE wiki_field_info_full (
+table_name VARCHAR2(35) NOT NULL,
+column_name VARCHAR2(35) NOT NULL,
+data_default VARCHAR2(4000),
+data_length NUMBER NOT NULL,
+data_type VARCHAR2(106),
+not_null CHAR(1) NOT NULL,
+prim NUMBER(1),
+uniq NUMBER(1),
+nonuniq NUMBER(1)
+);
+ALTER TABLE wiki_field_info_full ADD CONSTRAINT wiki_field_info_full_pk PRIMARY KEY (table_name, column_name);
+
+/*$mw$*/
+CREATE PROCEDURE fill_wiki_info IS
+ BEGIN
+ DELETE wiki_field_info_full;
+
+ FOR x_rec IN (SELECT t.table_name table_name, t.column_name,
+ t.data_default, t.data_length, t.data_type,
+ DECODE (t.nullable, 'Y', '1', 'N', '0') not_null,
+ (SELECT 1
+ FROM user_cons_columns ucc,
+ user_constraints uc
+ WHERE ucc.table_name = t.table_name
+ AND ucc.column_name = t.column_name
+ AND uc.constraint_name = ucc.constraint_name
+ AND uc.constraint_type = 'P'
+ AND ROWNUM < 2) prim,
+ (SELECT 1
+ FROM user_ind_columns uic,
+ user_indexes ui
+ WHERE uic.table_name = t.table_name
+ AND uic.column_name = t.column_name
+ AND ui.index_name = uic.index_name
+ AND ui.uniqueness = 'UNIQUE'
+ AND ROWNUM < 2) uniq,
+ (SELECT 1
+ FROM user_ind_columns uic,
+ user_indexes ui
+ WHERE uic.table_name = t.table_name
+ AND uic.column_name = t.column_name
+ AND ui.index_name = uic.index_name
+ AND ui.uniqueness = 'NONUNIQUE'
+ AND ROWNUM < 2) nonuniq
+ FROM user_tab_columns t, user_tables ut
+ WHERE ut.table_name = t.table_name)
+ LOOP
+ INSERT INTO wiki_field_info_full
+ (table_name, column_name,
+ data_default, data_length,
+ data_type, not_null, prim,
+ uniq, nonuniq
+ )
+ VALUES (x_rec.table_name, x_rec.column_name,
+ x_rec.data_default, x_rec.data_length,
+ x_rec.data_type, x_rec.not_null, x_rec.prim,
+ x_rec.uniq, x_rec.nonuniq
+ );
+ END LOOP;
+ COMMIT;
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE OR REPLACE PROCEDURE duplicate_table(p_tabname IN VARCHAR2,
+ p_oldprefix IN VARCHAR2,
+ p_newprefix IN VARCHAR2,
+ p_temporary IN BOOLEAN) IS
+ e_table_not_exist EXCEPTION;
+ PRAGMA EXCEPTION_INIT(e_table_not_exist, -00942);
+BEGIN
+ BEGIN
+ EXECUTE IMMEDIATE 'DROP TABLE ' || p_newprefix || p_tabname ||
+ ' CASCADE CONSTRAINTS';
+ EXCEPTION
+ WHEN e_table_not_exist THEN
+ NULL;
+ END;
+ IF (p_temporary) THEN
+ EXECUTE IMMEDIATE 'CREATE GLOBAL TEMPORARY TABLE ' || p_newprefix ||
+ p_tabname || ' AS SELECT * FROM ' || p_oldprefix ||
+ p_tabname || ' WHERE ROWNUM = 0';
+ ELSE
+ EXECUTE IMMEDIATE 'CREATE TABLE ' || p_newprefix || p_tabname ||
+ ' AS SELECT * FROM ' || p_oldprefix || p_tabname ||
+ ' WHERE ROWNUM = 0';
+ END IF;
+ FOR rc IN (SELECT column_name, data_default
+ FROM user_tab_columns
+ WHERE table_name = p_oldprefix || p_tabname
+ AND data_default IS NOT NULL) LOOP
+ EXECUTE IMMEDIATE 'ALTER TABLE ' || p_newprefix || p_tabname ||
+ ' MODIFY ' || rc.column_name || ' DEFAULT ' ||
+ substr(rc.data_default, 1, 2000);
+ END LOOP;
+ FOR rc IN (SELECT REPLACE(REPLACE(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('CONSTRAINT',
+ constraint_name),
+ 32767,
+ 1),
+ USER || '"."' || p_oldprefix,
+ USER || '"."' || p_newprefix),
+ '"' || constraint_name || '"',
+ '"' || p_newprefix || constraint_name || '"') DDLVC2,
+ constraint_name
+ FROM user_constraints uc
+ WHERE table_name = p_oldprefix || p_tabname
+ AND constraint_type = 'P') LOOP
+ dbms_output.put_line(SUBSTR(rc.ddlvc2,
+ 1,
+ INSTR(rc.ddlvc2, 'PCTFREE') - 1));
+ EXECUTE IMMEDIATE SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'PCTFREE') - 1);
+ END LOOP;
+ FOR rc IN (SELECT REPLACE(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('REF_CONSTRAINT',
+ constraint_name),
+ 32767,
+ 1),
+ USER || '"."' || p_oldprefix,
+ USER || '"."' || p_newprefix) DDLVC2,
+ constraint_name
+ FROM user_constraints uc
+ WHERE table_name = p_oldprefix || p_tabname
+ AND constraint_type = 'R') LOOP
+ EXECUTE IMMEDIATE rc.ddlvc2;
+ END LOOP;
+ FOR rc IN (SELECT REPLACE(REPLACE(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('INDEX',
+ index_name),
+ 32767,
+ 1),
+ USER || '"."' || p_oldprefix,
+ USER || '"."' || p_newprefix),
+ '"' || index_name || '"',
+ '"' || p_newprefix || index_name || '"') DDLVC2,
+ index_name
+ FROM user_indexes ui
+ WHERE table_name = p_oldprefix || p_tabname
+ AND index_type != 'LOB'
+ AND NOT EXISTS
+ (SELECT NULL
+ FROM user_constraints
+ WHERE table_name = ui.table_name
+ AND constraint_name = ui.index_name)) LOOP
+ EXECUTE IMMEDIATE SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'PCTFREE') - 1);
+ END LOOP;
+ FOR rc IN (SELECT REPLACE(REPLACE(UPPER(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('TRIGGER',
+ trigger_name),
+ 32767,
+ 1)),
+ USER || '"."' || p_oldprefix,
+ USER || '"."' || p_newprefix),
+ ' ON ' || p_oldprefix || p_tabname,
+ ' ON ' || p_newprefix || p_tabname) DDLVC2,
+ trigger_name
+ FROM user_triggers
+ WHERE table_name = p_oldprefix || p_tabname) LOOP
+ EXECUTE IMMEDIATE SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'ALTER ') - 1);
+ END LOOP;
+END;
+/*$mw$*/
+
+/*$mw$*/
+BEGIN
+ fill_wiki_info;
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE OR REPLACE FUNCTION BITOR (x IN NUMBER, y IN NUMBER) RETURN NUMBER AS
+BEGIN
+ RETURN (x + y - BITAND(x, y));
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE OR REPLACE FUNCTION BITNOT (x IN NUMBER) RETURN NUMBER AS
+BEGIN
+ RETURN (4294967295 - x);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE OR REPLACE TYPE GET_OUTPUT_TYPE IS TABLE OF VARCHAR2(255);
+/*$mw$*/
+
+/*$mw$*/
+CREATE OR REPLACE FUNCTION GET_OUTPUT_LINES RETURN GET_OUTPUT_TYPE PIPELINED AS
+ v_line VARCHAR2(255);
+ v_status INTEGER := 0;
+BEGIN
+
+ LOOP
+ DBMS_OUTPUT.GET_LINE(v_line, v_status);
+ IF (v_status = 0) THEN RETURN; END IF;
+ PIPE ROW (v_line);
+ END LOOP;
+ RETURN;
+EXCEPTION
+ WHEN OTHERS THEN
+ RETURN;
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE OR REPLACE FUNCTION GET_SEQUENCE_VALUE(seq IN VARCHAR2) RETURN NUMBER AS
+ v_value NUMBER;
+BEGIN
+ EXECUTE IMMEDIATE 'SELECT '||seq||'.NEXTVAL INTO :outVar FROM DUAL' INTO v_value;
+ RETURN v_value;
+END;
+/*$mw$*/
diff --git a/maintenance/ora/user.sql b/maintenance/ora/user.sql
new file mode 100644
index 00000000..d54acf39
--- /dev/null
+++ b/maintenance/ora/user.sql
@@ -0,0 +1,16 @@
+-- defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
+define wiki_user='{$wgDBuser}';
+define wiki_pass='{$wgDBpassword}';
+define def_ts='{$wgDBOracleDefTS}';
+define temp_ts='{$wgDBOracleTempTS}';
+
+create user &wiki_user. identified by &wiki_pass. default tablespace &def_ts. temporary tablespace &temp_ts. quota unlimited on &def_ts.;
+grant connect, resource to &wiki_user.;
+grant alter session to &wiki_user.;
+grant ctxapp to &wiki_user.;
+grant execute on ctx_ddl to &wiki_user.;
+grant create view to &wiki_user.;
+grant create synonym to &wiki_user.;
+grant create table to &wiki_user.;
+grant create sequence to &wiki_user.;
+grant create trigger to &wiki_user.;
diff --git a/maintenance/orphans.php b/maintenance/orphans.php
index 480b7220..67403e43 100644
--- a/maintenance/orphans.php
+++ b/maintenance/orphans.php
@@ -1,206 +1,237 @@
<?php
-# Copyright (C) 2005 Brion Vibber <brion@pobox.com>
-# http://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
-
/**
* Look for 'orphan' revisions hooked to pages which don't exist
* And 'childless' pages with no revisions.
* Then, kill the poor widows and orphans.
* Man this is depressing.
*
- * @file
+ * Copyright (C) 2005 Brion Vibber <brion@pobox.com>
+ * http://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
+ *
* @author <brion@pobox.com>
* @ingroup Maintenance
*/
-$options = array( 'fix' );
-
-/** */
-require_once( 'commandLine.inc' );
-$wgTitle = Title::newFromText( 'Orphan revision cleanup script' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-checkOrphans( isset( $options['fix'] ) );
-checkSeparation( isset( $options['fix'] ) );
-#checkWidows( isset( $options['fix'] ) );
-
-# ------
-
-function checkOrphans( $fix ) {
- $dbw = wfGetDB( DB_MASTER );
- $page = $dbw->tableName( 'page' );
- $revision = $dbw->tableName( 'revision' );
-
- if( $fix ) {
- $dbw->query( "LOCK TABLES $page WRITE, $revision WRITE" );
+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";
+ $this->addOption( 'fix', 'Actually fix broken entries' );
}
- echo "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
- WHERE page_id IS NULL
- ");
- $orphans = $dbw->numRows( $result );
- if( $orphans > 0 ) {
- global $wgContLang;
- echo "$orphans orphan revisions...\n";
- printf( "%10s %10s %14s %20s %s\n", 'rev_id', 'rev_page', 'rev_timestamp', 'rev_user_text', 'rev_comment' );
- while( $row = $dbw->fetchObject( $result ) ) {
- $comment = ( $row->rev_comment == '' )
- ? ''
- : '(' . $wgContLang->truncate( $row->rev_comment, 40 ) . ')';
- printf( "%10d %10d %14s %20s %s\n",
- $row->rev_id,
- $row->rev_page,
- $row->rev_timestamp,
- $wgContLang->truncate( $row->rev_user_text, 17 ),
- $comment );
- if( $fix ) {
- $dbw->delete( 'revision', array( 'rev_id' => $row->rev_id ) );
- }
- }
- if( !$fix ) {
- echo "Run again with --fix to remove these entries automatically.\n";
- }
- } else {
- echo "No orphans! Yay!\n";
+ 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
+ # $this->checkWidows( $this->hasOption( 'fix' ) );
}
- if( $fix ) {
- $dbw->query( "UNLOCK TABLES" );
- }
-}
-
-/**
- * @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)
- */
-function checkWidows( $fix ) {
- $dbw = wfGetDB( DB_MASTER );
- $page = $dbw->tableName( 'page' );
- $revision = $dbw->tableName( 'revision' );
-
- if( $fix ) {
- $dbw->query( "LOCK TABLES $page WRITE, $revision WRITE" );
+ /**
+ * Lock the appropriate tables for the script
+ * @param $db Database object
+ * @param $extraTable String The name of any extra tables to lock (eg: text)
+ */
+ private function lockTables( &$db, $extraTable = null ) {
+ $tbls = array( 'page', 'revision', 'redirect' );
+ if( $extraTable )
+ $tbls[] = $extraTable;
+ $db->lockTables( array(), $tbls, __METHOD__, false );
}
- echo "\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
- WHERE rev_id IS NULL
- ");
- $widows = $dbw->numRows( $result );
- if( $widows > 0 ) {
- global $wgContLang;
- echo "$widows childless pages...\n";
- printf( "%10s %11s %2s %s\n", 'page_id', 'page_latest', 'ns', 'page_title' );
- while( $row = $dbw->fetchObject( $result ) ) {
- printf( "%10d %11d %2d %s\n",
- $row->page_id,
- $row->page_latest,
- $row->page_namespace,
- $row->page_title );
- if( $fix ) {
- $dbw->delete( 'page', array( 'page_id' => $row->page_id ) );
+ /**
+ * Check for orphan revisions
+ * @param $fix bool Whether to fix broken revisions when found
+ */
+ private function checkOrphans( $fix ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $page = $dbw->tableName( 'page' );
+ $revision = $dbw->tableName( 'revision' );
+
+ if( $fix ) {
+ $this->lockTables( $dbw );
+ }
+
+ $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
+ WHERE page_id IS NULL
+ ");
+ $orphans = $dbw->numRows( $result );
+ 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' ) );
+ foreach( $result as $row ) {
+ $comment = ( $row->rev_comment == '' )
+ ? ''
+ : '(' . $wgContLang->truncate( $row->rev_comment, 40 ) . ')';
+ $this->output( sprintf( "%10d %10d %14s %20s %s\n",
+ $row->rev_id,
+ $row->rev_page,
+ $row->rev_timestamp,
+ $wgContLang->truncate( $row->rev_user_text, 17 ),
+ $comment ) );
+ if( $fix ) {
+ $dbw->delete( 'revision', array( 'rev_id' => $row->rev_id ) );
+ }
+ }
+ if( !$fix ) {
+ $this->output( "Run again with --fix to remove these entries automatically.\n" );
}
+ } else {
+ $this->output( "No orphans! Yay!\n" );
}
- if( !$fix ) {
- echo "Run again with --fix to remove these entries automatically.\n";
+
+ if( $fix ) {
+ $dbw->unlockTables();
}
- } else {
- echo "No childless pages! Yay!\n";
- }
-
- if( $fix ) {
- $dbw->query( "UNLOCK TABLES" );
}
-}
-
-
-function checkSeparation( $fix ) {
- $dbw = wfGetDB( DB_MASTER );
- $page = $dbw->tableName( 'page' );
- $revision = $dbw->tableName( 'revision' );
- $text = $dbw->tableName( 'text' );
- if( $fix ) {
- $dbw->query( "LOCK TABLES $page WRITE, $revision WRITE, $text WRITE" );
- }
+ /**
+ * @param $fix bool
+ * @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)
+ */
+ private function checkWidows( $fix ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $page = $dbw->tableName( 'page' );
+ $revision = $dbw->tableName( 'revision' );
+
+ if( $fix ) {
+ $this->lockTables( $dbw );
+ }
- echo "\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
- ");
- $found = 0;
- while( $row = $dbw->fetchObject( $result ) ) {
- $result2 = $dbw->query( "
- SELECT MAX(rev_timestamp) as max_timestamp
- FROM $revision
- WHERE rev_page=$row->page_id
- " );
- $row2 = $dbw->fetchObject( $result2 );
- $dbw->freeResult( $result2 );
- if( $row2 ) {
- if( $row->rev_timestamp != $row2->max_timestamp ) {
- if( $found == 0 ) {
- printf( "%10s %10s %14s %14s\n",
- 'page_id', 'rev_id', 'timestamp', 'max timestamp' );
- }
- ++$found;
- printf( "%10d %10d %14s %14s\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
+ WHERE rev_id IS NULL
+ ");
+ $widows = $dbw->numRows( $result );
+ if( $widows > 0 ) {
+ global $wgContLang;
+ $this->output( "$widows childless pages...\n" );
+ $this->output( sprintf( "%10s %11s %2s %s\n", 'page_id', 'page_latest', 'ns', 'page_title' ) );
+ foreach( $result as $row ) {
+ printf( "%10d %11d %2d %s\n",
$row->page_id,
$row->page_latest,
- $row->rev_timestamp,
- $row2->max_timestamp );
+ $row->page_namespace,
+ $row->page_title );
if( $fix ) {
- # ...
- $maxId = $dbw->selectField(
- 'revision',
- 'rev_id',
- array(
- 'rev_page' => $row->page_id,
- 'rev_timestamp' => $row2->max_timestamp ) );
- echo "... updating to revision $maxId\n";
- $maxRev = Revision::newFromId( $maxId );
- $title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $article = new Article( $title );
- $article->updateRevisionOn( $dbw, $maxRev );
+ $dbw->delete( 'page', array( 'page_id' => $row->page_id ) );
}
}
+ if( !$fix ) {
+ $this->output( "Run again with --fix to remove these entries automatically.\n" );
+ }
} else {
- echo "wtf\n";
+ $this->output( "No childless pages! Yay!\n" );
+ }
+
+ if( $fix ) {
+ $dbw->unlockTables();
}
}
- if( $found ) {
- echo "Found $found pages with incorrect latest revision.\n";
- } else {
- echo "No pages with incorrect latest revision. Yay!\n";
- }
- if( !$fix && $found > 0 ) {
- echo "Run again with --fix to remove these entries automatically.\n";
- }
-
- if( $fix ) {
- $dbw->query( "UNLOCK TABLES" );
+ /**
+ * Check for pages where page_latest is wrong
+ * @param $fix bool Whether to fix broken entries
+ */
+ private function checkSeparation( $fix ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $page = $dbw->tableName( 'page' );
+ $revision = $dbw->tableName( 'revision' );
+ $text = $dbw->tableName( 'text' );
+
+ if( $fix ) {
+ $dbw->lockTables( $dbw, 'text' );
+ }
+
+ $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
+ ");
+ $found = 0;
+ foreach( $result as $row ) {
+ $result2 = $dbw->query( "
+ SELECT MAX(rev_timestamp) as max_timestamp
+ FROM $revision
+ WHERE rev_page=$row->page_id
+ " );
+ $row2 = $dbw->fetchObject( $result2 );
+ $dbw->freeResult( $result2 );
+ if( $row2 ) {
+ if( $row->rev_timestamp != $row2->max_timestamp ) {
+ if( $found == 0 ) {
+ $this->output( sprintf( "%10s %10s %14s %14s\n",
+ 'page_id', 'rev_id', 'timestamp', 'max timestamp' ) );
+ }
+ ++$found;
+ $this->output( sprintf( "%10d %10d %14s %14s\n",
+ $row->page_id,
+ $row->page_latest,
+ $row->rev_timestamp,
+ $row2->max_timestamp ) );
+ if( $fix ) {
+ # ...
+ $maxId = $dbw->selectField(
+ 'revision',
+ 'rev_id',
+ array(
+ 'rev_page' => $row->page_id,
+ 'rev_timestamp' => $row2->max_timestamp ) );
+ $this->output( "... updating to revision $maxId\n" );
+ $maxRev = Revision::newFromId( $maxId );
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $article = new Article( $title );
+ $article->updateRevisionOn( $dbw, $maxRev );
+ }
+ }
+ } else {
+ $this->output( "wtf\n" );
+ }
+ }
+
+ if( $found ) {
+ $this->output( "Found $found pages with incorrect latest revision.\n" );
+ } else {
+ $this->output( "No pages with incorrect latest revision. Yay!\n" );
+ }
+ if( !$fix && $found > 0 ) {
+ $this->output( "Run again with --fix to remove these entries automatically.\n" );
+ }
+
+ if( $fix ) {
+ $dbw->unlockTables();
+ }
}
}
+$maintClass = "Orphans";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/ourusers.php b/maintenance/ourusers.php
index a7a3132b..3b5da447 100644
--- a/maintenance/ourusers.php
+++ b/maintenance/ourusers.php
@@ -9,6 +9,7 @@
* @todo document
* @file
* @ingroup Maintenance
+ * @ingroup Wikimedia
*/
/** */
diff --git a/maintenance/parserTests.inc b/maintenance/parserTests.inc
index b689fc1b..6526da90 100644
--- a/maintenance/parserTests.inc
+++ b/maintenance/parserTests.inc
@@ -1,5 +1,5 @@
<?php
-# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
+# Copyright (C) 2004, 2010 Brion Vibber <brion@pobox.com>
# http://www.mediawiki.org/
#
# This program is free software; you can redistribute it and/or modify
@@ -25,10 +25,12 @@
*/
/** */
-$options = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record' );
-$optionsWithArgs = array( 'regex', 'seed' );
+$options = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' );
+$optionsWithArgs = array( 'regex', 'seed', 'setversion' );
-require_once( 'commandLine.inc' );
+if ( !defined( "NO_COMMAND_LINE" ) ) {
+ require_once( dirname(__FILE__) . '/commandLine.inc' );
+}
require_once( "$IP/maintenance/parserTestsParserHook.php" );
require_once( "$IP/maintenance/parserTestsStaticParserHook.php" );
require_once( "$IP/maintenance/parserTestsParserTime.php" );
@@ -97,7 +99,7 @@ class ParserTest {
isset( $options['quiet'] )
&& ( isset( $options['record'] )
|| isset( $options['compare'] ) ) ); // redundant output
-
+
$this->showOutput = isset( $options['show-output'] );
@@ -116,6 +118,10 @@ class ParserTest {
$this->recorder = new DbTestRecorder( $this );
} elseif( isset( $options['compare'] ) ) {
$this->recorder = new DbTestPreviewer( $this );
+ } elseif( isset( $options['upload'] ) ) {
+ $this->recorder = new RemoteTestRecorder( $this );
+ } elseif( class_exists( 'PHPUnitTestRecorder' ) ) {
+ $this->recorder = new PHPUnitTestRecorder( $this );
} else {
$this->recorder = new TestRecorder( $this );
}
@@ -125,6 +131,8 @@ class ParserTest {
$this->fuzzSeed = intval( $options['seed'] ) - 1;
}
+ $this->runDisabled = isset( $options['run-disabled'] );
+
$this->hooks = array();
$this->functionHooks = array();
}
@@ -132,7 +140,7 @@ class ParserTest {
/**
* Remove last character if it is a newline
*/
- private function chomp($s) {
+ public function chomp($s) {
if (substr($s, -1) === "\n") {
return substr($s, 0, -1);
}
@@ -270,7 +278,8 @@ class ParserTest {
$this->setupDatabase();
$ok = true;
foreach( $filenames as $filename ) {
- $ok = $this->runFile( $filename ) && $ok;
+ $tests = new TestFileIterator( $filename, $this );
+ $ok = $this->runTests( $tests ) && $ok;
}
$this->teardownDatabase();
$this->recorder->report();
@@ -278,118 +287,17 @@ class ParserTest {
return $ok;
}
- private function runFile( $filename ) {
- $infile = fopen( $filename, 'rt' );
- if( !$infile ) {
- wfDie( "Couldn't open $filename\n" );
- } else {
- global $IP;
- $relative = wfRelativePath( $filename, $IP );
- $this->showRunFile( $relative );
- }
-
- $data = array();
- $section = null;
- $n = 0;
+ function runTests($tests) {
$ok = true;
- while( false !== ($line = fgets( $infile ) ) ) {
- $n++;
- $matches = array();
- if( preg_match( '/^!!\s*(\w+)/', $line, $matches ) ) {
- $section = strtolower( $matches[1] );
- if( $section == 'endarticle') {
- if( !isset( $data['text'] ) ) {
- wfDie( "'endarticle' without 'text' at line $n of $filename\n" );
- }
- if( !isset( $data['article'] ) ) {
- wfDie( "'endarticle' without 'article' at line $n of $filename\n" );
- }
- $this->addArticle($this->chomp($data['article']), $this->chomp($data['text']), $n);
- $data = array();
- $section = null;
- continue;
- }
- if( $section == 'endhooks' ) {
- if( !isset( $data['hooks'] ) ) {
- wfDie( "'endhooks' without 'hooks' at line $n of $filename\n" );
- }
- foreach( explode( "\n", $data['hooks'] ) as $line ) {
- $line = trim( $line );
- if( $line ) {
- $this->requireHook( $line );
- }
- }
- $data = array();
- $section = null;
- continue;
- }
- if( $section == 'endfunctionhooks' ) {
- if( !isset( $data['functionhooks'] ) ) {
- wfDie( "'endfunctionhooks' without 'functionhooks' at line $n of $filename\n" );
- }
- foreach( explode( "\n", $data['functionhooks'] ) as $line ) {
- $line = trim( $line );
- if( $line ) {
- $this->requireFunctionHook( $line );
- }
- }
- $data = array();
- $section = null;
- continue;
- }
- if( $section == 'end' ) {
- if( !isset( $data['test'] ) ) {
- wfDie( "'end' without 'test' at line $n of $filename\n" );
- }
- if( !isset( $data['input'] ) ) {
- wfDie( "'end' without 'input' at line $n of $filename\n" );
- }
- if( !isset( $data['result'] ) ) {
- wfDie( "'end' without 'result' at line $n of $filename\n" );
- }
- if( !isset( $data['options'] ) ) {
- $data['options'] = '';
- }
- else {
- $data['options'] = $this->chomp( $data['options'] );
- }
- if (!isset( $data['config'] ) )
- $data['config'] = '';
-
- if (preg_match('/\\bdisabled\\b/i', $data['options'])
- || !preg_match("/{$this->regex}/i", $data['test'])) {
- # disabled test
- $data = array();
- $section = null;
- continue;
- }
- $result = $this->runTest(
- $this->chomp( $data['test'] ),
- $this->chomp( $data['input'] ),
- $this->chomp( $data['result'] ),
- $this->chomp( $data['options'] ),
- $this->chomp( $data['config'] )
- );
- $ok = $ok && $result;
- $this->recorder->record( $this->chomp( $data['test'] ), $result );
- $data = array();
- $section = null;
- continue;
- }
- if ( isset ($data[$section] ) ) {
- wfDie( "duplicate section '$section' at line $n of $filename\n" );
- }
- $data[$section] = '';
- continue;
- }
- if( $section ) {
- $data[$section] .= $line;
- }
+ foreach($tests as $i => $t) {
+ $result =
+ $this->runTest($t['test'], $t['input'], $t['result'], $t['options'], $t['config']);
+ $ok = $ok && $result;
+ $this->recorder->record( $t['test'], $result );
}
if ( $this->showProgress ) {
print "\n";
}
- return $ok;
}
/**
@@ -419,52 +327,56 @@ class ParserTest {
* @param string $result Result to output
* @return bool
*/
- private function runTest( $desc, $input, $result, $opts, $config ) {
+ public function runTest( $desc, $input, $result, $opts, $config ) {
if( $this->showProgress ) {
$this->showTesting( $desc );
}
+ $opts = $this->parseOptions( $opts );
$this->setupGlobals($opts, $config);
$user = new User();
$options = ParserOptions::newFromUser( $user );
- if (preg_match('/\\bmath\\b/i', $opts)) {
- # XXX this should probably be done by the ParserOptions
- $options->setUseTex(true);
- }
-
$m = array();
- if (preg_match('/title=\[\[(.*)\]\]/', $opts, $m)) {
- $titleText = $m[1];
+ if (isset( $opts['title'] ) ) {
+ $titleText = $opts['title'];
}
else {
$titleText = 'Parser test';
}
- $noxml = (bool)preg_match( '~\\b noxml \\b~x', $opts );
+ $noxml = isset( $opts['noxml'] );
+ $local = isset( $opts['local'] );
$parser = $this->getParser();
- $title =& Title::makeTitle( NS_MAIN, $titleText );
+ $title = Title::newFromText( $titleText );
$matches = array();
- if (preg_match('/\\bpst\\b/i', $opts)) {
+ if( isset( $opts['pst'] ) ) {
$out = $parser->preSaveTransform( $input, $title, $user, $options );
- } elseif (preg_match('/\\bmsg\\b/i', $opts)) {
+ } elseif( isset( $opts['msg'] ) ) {
$out = $parser->transformMsg( $input, $options );
- } elseif( preg_match( '/\\bsection=([\w-]+)\b/i', $opts, $matches ) ) {
- $section = $matches[1];
+ } elseif( isset( $opts['section'] ) ) {
+ $section = $opts['section'];
$out = $parser->getSection( $input, $section );
- } elseif( preg_match( '/\\breplace=([\w-]+),"(.*?)"/i', $opts, $matches ) ) {
- $section = $matches[1];
- $replace = $matches[2];
+ } elseif( isset( $opts['replace'] ) ) {
+ $section = $opts['replace'][0];
+ $replace = $opts['replace'][1];
$out = $parser->replaceSection( $input, $section, $replace );
+ } elseif( isset( $opts['comment'] ) ) {
+ $linker = $user->getSkin();
+ $out = $linker->formatComment( $input, $title, $local );
} else {
$output = $parser->parse( $input, $title, $options, true, true, 1337 );
$out = $output->getText();
- if (preg_match('/\\bill\\b/i', $opts)) {
+ if ( isset( $opts['showtitle'] ) ) {
+ if($output->getTitleText()) $title = $output->getTitleText();
+ $out = "$title\n$out";
+ }
+ if (isset( $opts['ill'] ) ) {
$out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) );
- } else if (preg_match('/\\bcat\\b/i', $opts)) {
+ } elseif( isset( $opts['cat'] ) ) {
global $wgOut;
$wgOut->addCategoryLinks($output->getCategories());
$cats = $wgOut->getCategoryLinks();
@@ -478,6 +390,7 @@ class ParserTest {
$result = $this->tidy($result);
}
+
$this->teardownGlobals();
if( $result === $out && ( $noxml === true || $this->wellFormed( $out ) ) ) {
@@ -490,37 +403,104 @@ class ParserTest {
/**
* Use a regex to find out the value of an option
- * @param $regex A regex, the first group will be the value returned
- * @param $opts Options line to look in
- * @param $defaults Default value returned if the regex does not match
+ * @param $key name of option val to retrieve
+ * @param $opts Options array to look in
+ * @param $defaults Default value returned if not found
*/
- private static function getOptionValue( $regex, $opts, $default ) {
- $m = array();
- if( preg_match( $regex, $opts, $m ) ) {
- return $m[1];
+ private static function getOptionValue( $key, $opts, $default ) {
+ $key = strtolower( $key );
+ if( isset( $opts[$key] ) ) {
+ return $opts[$key];
} else {
return $default;
}
}
+ private function parseOptions( $instring ) {
+ $opts = array();
+ $lines = explode( "\n", $instring );
+ // foo
+ // foo=bar
+ // foo="bar baz"
+ // foo=[[bar baz]]
+ // foo=bar,"baz quux"
+ $regex = '/\b
+ ([\w-]+) # Key
+ \b
+ (?:\s*
+ = # First sub-value
+ \s*
+ (
+ "
+ [^"]* # Quoted val
+ "
+ |
+ \[\[
+ [^]]* # Link target
+ \]\]
+ |
+ [\w-]+ # Plain word
+ )
+ (?:\s*
+ , # Sub-vals 1..N
+ \s*
+ (
+ "[^"]*" # Quoted val
+ |
+ \[\[[^]]*\]\] # Link target
+ |
+ [\w-]+ # Plain word
+ )
+ )*
+ )?
+ /x';
+
+ if( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
+ foreach( $matches as $bits ) {
+ $match = array_shift( $bits );
+ $key = strtolower( array_shift( $bits ) );
+ if( count( $bits ) == 0 ) {
+ $opts[$key] = true;
+ } elseif( count( $bits ) == 1 ) {
+ $opts[$key] = $this->cleanupOption( array_shift( $bits ) );
+ } else {
+ // Array!
+ $opts[$key] = array_map( array( $this, 'cleanupOption' ), $bits );
+ }
+ }
+ }
+ return $opts;
+ }
+
+ private function cleanupOption( $opt ) {
+ if( substr( $opt, 0, 1 ) == '"' ) {
+ return substr( $opt, 1, -1 );
+ }
+ if( substr( $opt, 0, 2 ) == '[[' ) {
+ return substr( $opt, 2, -2 );
+ }
+ return $opt;
+ }
+
/**
* Set up the global variables for a consistent environment for each test.
* Ideally this should replace the global configuration entirely.
*/
private function setupGlobals($opts = '', $config = '') {
+ global $wgDBtype;
if( !isset( $this->uploadDir ) ) {
$this->uploadDir = $this->setupUploadDir();
}
# Find out values for some special options.
$lang =
- self::getOptionValue( '/language=([a-z]+(?:_[a-z]+)?)/', $opts, 'en' );
+ self::getOptionValue( 'language', $opts, 'en' );
$variant =
- self::getOptionValue( '/variant=([a-z]+(?:-[a-z]+)?)/', $opts, false );
+ self::getOptionValue( 'variant', $opts, false );
$maxtoclevel =
- self::getOptionValue( '/wgMaxTocLevel=(\d+)/', $opts, 999 );
- $linkHolderBatchSize =
- self::getOptionValue( '/wgLinkHolderBatchSize=(\d+)/', $opts, 1000 );
+ self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
+ $linkHolderBatchSize =
+ self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
$settings = array(
'wgServer' => 'http://localhost',
@@ -539,20 +519,22 @@ class ParserTest {
'wgEnableUploads' => true,
'wgStyleSheetPath' => '/skins',
'wgSitename' => 'MediaWiki',
- 'wgServerName' => 'Britney Spears',
+ 'wgServerName' => 'Britney-Spears',
'wgLanguageCode' => $lang,
'wgContLanguageCode' => $lang,
- 'wgDBprefix' => 'parsertest_',
- 'wgRawHtml' => preg_match('/\\brawhtml\\b/i', $opts),
+ 'wgDBprefix' => $wgDBtype != 'oracle' ? 'parsertest_' : 'pt_',
+ 'wgRawHtml' => isset( $opts['rawhtml'] ),
'wgLang' => null,
'wgContLang' => null,
- 'wgNamespacesWithSubpages' => array( 0 => preg_match('/\\bsubpage\\b/i', $opts)),
+ 'wgNamespacesWithSubpages' => array( 0 => isset( $opts['subpage'] ) ),
'wgMaxTocLevel' => $maxtoclevel,
'wgCapitalLinks' => true,
'wgNoFollowLinks' => true,
'wgNoFollowDomainExceptions' => array(),
'wgThumbnailScriptPath' => false,
- 'wgUseTeX' => false,
+ 'wgUseImageResize' => false,
+ 'wgUseTeX' => isset( $opts['math'] ),
+ 'wgMathDirectory' => $this->uploadDir . '/math',
'wgLocaltimezone' => 'UTC',
'wgAllowExternalImages' => true,
'wgUseTidy' => false,
@@ -569,32 +551,38 @@ class ParserTest {
'wgDefaultExternalStore' => array(),
'wgForeignFileRepos' => array(),
'wgLinkHolderBatchSize' => $linkHolderBatchSize,
- 'wgEnforceHtmlIds' => true,
+ 'wgExperimentalHtmlIds' => false,
'wgExternalLinkTarget' => false,
'wgAlwaysUseTidy' => false,
- );
+ 'wgHtml5' => true,
+ 'wgWellFormedXml' => true,
+ 'wgAllowMicrodataAttributes' => true,
+ );
if ($config) {
$configLines = explode( "\n", $config );
-
+
foreach( $configLines as $line ) {
list( $var, $value ) = explode( '=', $line, 2 );
-
+
$settings[$var] = eval("return $value;" );
}
}
-
+
$this->savedGlobals = array();
foreach( $settings as $var => $val ) {
- $this->savedGlobals[$var] = $GLOBALS[$var];
+ if( array_key_exists( $var, $GLOBALS ) ) {
+ $this->savedGlobals[$var] = $GLOBALS[$var];
+ }
$GLOBALS[$var] = $val;
}
$langObj = Language::factory( $lang );
$GLOBALS['wgLang'] = $langObj;
$GLOBALS['wgContLang'] = $langObj;
$GLOBALS['wgMemc'] = new FakeMemCachedClient;
+ $GLOBALS['wgOut'] = new OutputPage;
- //$GLOBALS['wgMessageCache'] = new MessageCache( new BagOStuff(), false, 0, $GLOBALS['wgDBname'] );
+ MagicWord::clearCache();
global $wgUser;
$wgUser = new User();
@@ -611,13 +599,13 @@ class ParserTest {
'categorylinks', 'templatelinks', 'externallinks', 'langlinks',
'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage',
'recentchanges', 'watchlist', 'math', 'interwiki',
- 'querycache', 'objectcache', 'job', 'redirect', 'querycachetwo',
+ 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
'archive', 'user_groups', 'page_props', 'category'
);
- if ($wgDBtype === 'mysql')
+ if ($wgDBtype === 'mysql')
array_push( $tables, 'searchindex' );
-
+
// Allow extensions to add to the list of tables to duplicate;
// may be necessary if they hook into page save or other code
// which will require them while running tests.
@@ -631,12 +619,12 @@ class ParserTest {
* Currently this will only be done once per run, and any changes to
* the db will be visible to later tests in the run.
*/
- private function setupDatabase() {
+ function setupDatabase() {
global $wgDBprefix, $wgDBtype;
if ( $this->databaseSetupDone ) {
return;
}
- if ( $wgDBprefix === 'parsertest_' ) {
+ if ( $wgDBprefix === 'parsertest_' || ($wgDBtype == 'oracle' && $wgDBprefix === 'pt_')) {
throw new MWException( 'setupDatabase should be called before setupGlobals' );
}
$this->databaseSetupDone = true;
@@ -649,59 +637,30 @@ class ParserTest {
$this->useTemporaryTables = false;
}
- $temporary = $this->useTemporaryTables ? 'TEMPORARY' : '';
+ $temporary = $this->useTemporaryTables || $wgDBtype == 'postgres';
$db = wfGetDB( DB_MASTER );
$tables = $this->listTables();
- if ( !( $wgDBtype == 'mysql' && strcmp( $db->getServerVersion(), '4.1' ) < 0 ) ) {
- # Database that supports CREATE TABLE ... LIKE
-
- if( $wgDBtype == 'postgres' ) {
- $def = 'INCLUDING DEFAULTS';
- $temporary = 'TEMPORARY';
- } else {
- $def = '';
- }
- foreach ( $tables as $tbl ) {
- # Clean up from previous aborted run. So that table escaping
- # works correctly across DB engines, we need to change the pre-
- # fix back and forth so tableName() works right.
- $this->changePrefix( $this->oldTablePrefix );
- $oldTableName = $db->tableName( $tbl );
- $this->changePrefix( 'parsertest_' );
- $newTableName = $db->tableName( $tbl );
-
- if ( $db->tableExists( $tbl ) && $wgDBtype != 'postgres' ) {
- $db->query( "DROP TABLE $newTableName" );
- }
- # Create new table
- $db->query( "CREATE $temporary TABLE $newTableName (LIKE $oldTableName $def)" );
- }
- } else {
- # Hack for MySQL versions < 4.1, which don't support
- # "CREATE TABLE ... LIKE". Note that
- # "CREATE TEMPORARY TABLE ... SELECT * FROM ... LIMIT 0"
- # would not create the indexes we need....
- #
- # Note that we don't bother changing around the prefixes here be-
- # cause we know we're using MySQL anyway.
- foreach ($tables as $tbl) {
- $oldTableName = $db->tableName( $tbl );
- $res = $db->query("SHOW CREATE TABLE $oldTableName");
- $row = $db->fetchRow($res);
- $create = $row[1];
- $create_tmp = preg_replace('/CREATE TABLE `(.*?)`/',
- "CREATE $temporary TABLE `parsertest_$tbl`", $create);
- if ($create === $create_tmp) {
- # Couldn't do replacement
- wfDie("could not create temporary table $tbl");
- }
- $db->query($create_tmp);
+ foreach ( $tables as $tbl ) {
+ # Clean up from previous aborted run. So that table escaping
+ # works correctly across DB engines, we need to change the pre-
+ # fix back and forth so tableName() works right.
+ $this->changePrefix( $this->oldTablePrefix );
+ $oldTableName = $db->tableName( $tbl );
+ $this->changePrefix( $wgDBtype != 'oracle' ? 'parsertest_' : 'pt_' );
+ $newTableName = $db->tableName( $tbl );
+
+ if ( $db->tableExists( $tbl ) && $wgDBtype != 'postgres' && $wgDBtype != 'oracle' ) {
+ $db->query( "DROP TABLE $newTableName" );
}
+ # Create new table
+ $db->duplicateTableStructure( $oldTableName, $newTableName, $temporary );
}
+ if ($wgDBtype == 'oracle')
+ $db->query('BEGIN FILL_WIKI_INFO; END;');
- $this->changePrefix( 'parsertest_' );
+ $this->changePrefix( $wgDBtype != 'oracle' ? 'parsertest_' : 'pt_' );
# Hack: insert a few Wikipedia in-project interwiki prefixes,
# for testing inter-language links
@@ -726,6 +685,21 @@ class ParserTest {
'iw_local' => 1 ),
) );
+
+ if ($wgDBtype == 'oracle') {
+ # Insert 0 and 1 user_ids to prevent FK violations
+
+ #Anonymous user
+ $db->insert( 'user', array(
+ 'user_id' => 0,
+ 'user_name' => 'Anonymous') );
+
+ # Hack-on-Hack: Insert a test user to be able to insert an image
+ $db->insert( 'user', array(
+ 'user_id' => 1,
+ 'user_name' => 'Tester') );
+ }
+
# Hack: Insert an image to work with
$db->insert( 'image', array(
'img_name' => 'Foobar.jpg',
@@ -743,8 +717,32 @@ class ParserTest {
'img_metadata' => serialize( array() ),
) );
+ # This image will be blacklisted in [[MediaWiki:Bad image list]]
+ $db->insert( 'image', array(
+ 'img_name' => 'Bad.jpg',
+ 'img_size' => 12345,
+ 'img_description' => 'zomgnotcensored',
+ 'img_user' => 1,
+ 'img_user_text' => 'WikiSysop',
+ 'img_timestamp' => $db->timestamp( '20010115123500' ),
+ 'img_width' => 320,
+ 'img_height' => 240,
+ 'img_bits' => 24,
+ 'img_media_type' => MEDIATYPE_BITMAP,
+ 'img_major_mime' => "image",
+ 'img_minor_mime' => "jpeg",
+ 'img_metadata' => serialize( array() ),
+ ) );
+
# Update certain things in site_stats
- $db->insert( 'site_stats', array( 'ss_row_id' => 1, 'ss_images' => 1, 'ss_good_articles' => 1 ) );
+ $db->insert( 'site_stats', array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ) );
+
+ # Reinitialise the LocalisationCache to match the database state
+ Language::getLocalisationCache()->unloadAll();
+
+ # Make a new message cache
+ global $wgMessageCache, $wgMemc;
+ $wgMessageCache = new MessageCache( $wgMemc, true, 3600, '' );
}
/**
@@ -765,7 +763,7 @@ class ParserTest {
}
private function teardownDatabase() {
- global $wgDBprefix;
+ global $wgDBtype;
if ( !$this->databaseSetupDone ) {
return;
}
@@ -780,10 +778,14 @@ class ParserTest {
$tables = $this->listTables();
$db = wfGetDB( DB_MASTER );
foreach ( $tables as $table ) {
- $db->query( "DROP TABLE `parsertest_$table`" );
- }*/
+ $sql = $wgDBtype == 'oracle' ? "DROP TABLE pt_$table DROP CONSTRAINTS" : "DROP TABLE `parsertest_$table`";
+ $db->query( $sql );
+ }
+ if ($wgDBtype == 'oracle')
+ $db->query('BEGIN FILL_WIKI_INFO; END;');
+ */
}
-
+
/**
* Create a dummy uploads directory which will contain a couple
* of files in order to pass existence tests.
@@ -807,6 +809,9 @@ class ParserTest {
}
wfMkdirParents( $dir . '/3/3a' );
copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
+
+ wfMkdirParents( $dir . '/0/09' );
+ copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" );
return $dir;
}
@@ -816,7 +821,6 @@ class ParserTest {
*/
private function teardownGlobals() {
RepoGroup::destroySingleton();
- FileCache::destroySingleton();
LinkCache::singleton()->clear();
foreach( $this->savedGlobals as $var => $val ) {
$GLOBALS[$var] = $val;
@@ -843,6 +847,10 @@ class ParserTest {
"$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg",
"$dir/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg",
"$dir/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg",
+
+ "$dir/0/09/Bad.jpg",
+
+ "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
)
);
@@ -855,7 +863,14 @@ class ParserTest {
"$dir/thumb/3/3a/Foobar.jpg",
"$dir/thumb/3/3a",
"$dir/thumb/3",
+
+ "$dir/0/09/",
+ "$dir/0/",
"$dir/thumb",
+ "$dir/math/f/a/5",
+ "$dir/math/f/a",
+ "$dir/math/f",
+ "$dir/math",
"$dir",
)
);
@@ -993,7 +1008,7 @@ class ParserTest {
*
* @param String $path
*/
- protected function showRunFile( $path ){
+ public function showRunFile( $path ){
print $this->term->color( 1 ) .
"Reading tests from \"$path\"..." .
$this->term->reset() .
@@ -1006,7 +1021,7 @@ class ParserTest {
* @param string $text the article text
* @param int $line the input line number, for reporting errors
*/
- private function addArticle($name, $text, $line) {
+ public function addArticle($name, $text, $line) {
$this->setupGlobals();
$title = Title::newFromText( $name );
if ( is_null($title) ) {
@@ -1015,11 +1030,12 @@ class ParserTest {
$aid = $title->getArticleID( GAID_FOR_UPDATE );
if ($aid != 0) {
- wfDie( "duplicate article at line $line\n" );
+ wfDie( "duplicate article '$name' at line $line\n" );
}
$art = new Article($title);
$art->insertNewArticle($text, '', false, false );
+
$this->teardownGlobals();
}
@@ -1029,8 +1045,9 @@ class ParserTest {
* die a painful dead to warn the others.
* @param string $name
*/
- private function requireHook( $name ) {
+ public function requireHook( $name ) {
global $wgParser;
+ $wgParser->firstCallInit( ); //make sure hooks are loaded.
if( isset( $wgParser->mTagHooks[$name] ) ) {
$this->hooks[$name] = $wgParser->mTagHooks[$name];
} else {
@@ -1046,6 +1063,7 @@ class ParserTest {
*/
private function requireFunctionHook( $name ) {
global $wgParser;
+ $wgParser->firstCallInit( ); //make sure hooks are loaded.
if( isset( $wgParser->mFunctionHooks[$name] ) ) {
$this->functionHooks[$name] = $wgParser->mFunctionHooks[$name];
} else {
@@ -1222,11 +1240,11 @@ class DbTestPreviewer extends TestRecorder {
* and all that fun stuff
*/
function start() {
- global $wgDBtype, $wgDBprefix;
+ global $wgDBtype;
parent::start();
- if( ! $this->db->tableExists( 'testrun' )
- or ! $this->db->tableExists( 'testitem' ) )
+ if( ! $this->db->tableExists( 'testrun' )
+ or ! $this->db->tableExists( 'testitem' ) )
{
print "WARNING> `testrun` table not found in database.\n";
$this->prevRun = false;
@@ -1262,7 +1280,7 @@ class DbTestPreviewer extends TestRecorder {
$res = $this->db->select( 'testitem', array( 'ti_name', 'ti_success' ),
array( 'ti_run' => $this->prevRun ), __METHOD__ );
foreach ( $res as $row ) {
- if ( !$this->parent->regex
+ if ( !$this->parent->regex
|| preg_match( "/{$this->parent->regex}/i", $row->ti_name ) )
{
$prevResults[$row->ti_name] = $row->ti_success;
@@ -1335,7 +1353,7 @@ class DbTestPreviewer extends TestRecorder {
// Otherwise, this test has previous recorded results.
// See when this test last had a different result to what we're seeing now.
- $conds = array(
+ $conds = array(
'ti_name' => $testname,
'ti_success' => ($after == 'f' ? "1" : "0") );
if ( $this->curRun ) {
@@ -1395,26 +1413,29 @@ class DbTestRecorder extends DbTestPreviewer {
* and all that fun stuff
*/
function start() {
- global $wgDBtype, $wgDBprefix;
+ global $wgDBtype, $options;
$this->db->begin();
- if( ! $this->db->tableExists( 'testrun' )
- or ! $this->db->tableExists( 'testitem' ) )
+ if( ! $this->db->tableExists( 'testrun' )
+ or ! $this->db->tableExists( 'testitem' ) )
{
print "WARNING> `testrun` table not found in database. Trying to create table.\n";
if ($wgDBtype === 'postgres')
$this->db->sourceFile( dirname(__FILE__) . '/testRunner.postgres.sql' );
+ elseif ($wgDBtype === 'oracle')
+ $this->db->sourceFile( dirname(__FILE__) . '/testRunner.ora.sql' );
else
$this->db->sourceFile( dirname(__FILE__) . '/testRunner.sql' );
echo "OK, resuming.\n";
}
-
+
parent::start();
$this->db->insert( 'testrun',
array(
'tr_date' => $this->db->timestamp(),
- 'tr_mw_version' => SpecialVersion::getVersion(),
+ 'tr_mw_version' => isset( $options['setversion'] ) ?
+ $options['setversion'] : SpecialVersion::getVersion(),
'tr_php_version' => phpversion(),
'tr_db_version' => $this->db->getServerVersion(),
'tr_uname' => php_uname()
@@ -1442,3 +1463,257 @@ class DbTestRecorder extends DbTestPreviewer {
__METHOD__ );
}
}
+
+class RemoteTestRecorder extends TestRecorder {
+ function start() {
+ parent::start();
+ $this->results = array();
+ $this->ping( 'running' );
+ }
+
+ function record( $test, $result ) {
+ parent::record( $test, $result );
+ $this->results[$test] = (bool)$result;
+ }
+
+ function end() {
+ $this->ping( 'complete', $this->results );
+ parent::end();
+ }
+
+ /**
+ * Inform a CodeReview instance that we've started or completed a test run...
+ * @param $remote array: info on remote target
+ * @param $status string: "running" - tell it we've started
+ * "complete" - provide test results array
+ * "abort" - something went horribly awry
+ * @param $data array of test name => true/false
+ */
+ function ping( $status, $results=false ) {
+ global $wgParserTestRemote, $IP;
+
+ $remote = $wgParserTestRemote;
+ $revId = SpecialVersion::getSvnRevision( $IP );
+ $jsonResults = json_encode( $results );
+
+ if( !$remote ) {
+ print "Can't do remote upload without configuring \$wgParserTestRemote!\n";
+ exit( 1 );
+ }
+
+ // Generate a hash MAC to validate our credentials
+ $message = array(
+ $remote['repo'],
+ $remote['suite'],
+ $revId,
+ $status,
+ );
+ if( $status == "complete" ) {
+ $message[] = $jsonResults;
+ }
+ $hmac = hash_hmac( "sha1", implode( "|", $message ), $remote['secret'] );
+
+ $postData = array(
+ 'action' => 'codetestupload',
+ 'format' => 'json',
+ 'repo' => $remote['repo'],
+ 'suite' => $remote['suite'],
+ 'rev' => $revId,
+ 'status' => $status,
+ 'hmac' => $hmac,
+ );
+ if( $status == "complete" ) {
+ $postData['results'] = $jsonResults;
+ }
+ $response = $this->post( $remote['api-url'], $postData );
+
+ if( $response === false ) {
+ print "CodeReview info upload failed to reach server.\n";
+ exit( 1 );
+ }
+ $responseData = json_decode( $response, true );
+ if( !is_array( $responseData ) ) {
+ print "CodeReview API response not recognized...\n";
+ wfDebug( "Unrecognized CodeReview API response: $response\n" );
+ exit( 1 );
+ }
+ if( isset( $responseData['error'] ) ) {
+ $code = $responseData['error']['code'];
+ $info = $responseData['error']['info'];
+ print "CodeReview info upload failed: $code $info\n";
+ exit( 1 );
+ }
+ }
+
+ function post( $url, $data ) {
+ return Http::post( $url, array( 'postData' => $data) );
+ }
+}
+
+class TestFileIterator implements Iterator {
+ private $file;
+ private $fh;
+ private $parser;
+ private $index = 0;
+ private $test;
+ private $lineNum;
+ private $eof;
+
+ function __construct( $file, $parser = null ) {
+ global $IP;
+
+ $this->file = $file;
+ $this->fh = fopen($this->file, "rt");
+ if( !$this->fh ) {
+ wfDie( "Couldn't open file '$file'\n" );
+ }
+
+ $this->parser = $parser;
+
+ if( $this->parser ) $this->parser->showRunFile( wfRelativePath( $this->file, $IP ) );
+ $this->lineNum = $this->index = 0;
+ }
+
+ function setParser( ParserTest $parser ) {
+ $this->parser = $parser;
+ }
+
+ function rewind() {
+ if(fseek($this->fh, 0)) {
+ wfDie( "Couldn't fseek to the start of '$filename'\n" );
+ }
+ $this->index = 0;
+ $this->lineNum = 0;
+ $this->eof = false;
+ $this->readNextTest();
+
+ return true;
+ }
+
+ function current() {
+ return $this->test;
+ }
+
+ function key() {
+ return $this->index;
+ }
+
+ function next() {
+ if($this->readNextTest()) {
+ $this->index++;
+ return true;
+ } else {
+ $this->eof = true;
+ }
+ }
+
+ function valid() {
+ return $this->eof != true;
+ }
+
+ function readNextTest() {
+ $data = array();
+ $section = null;
+
+ while( false !== ($line = fgets( $this->fh ) ) ) {
+ $this->lineNum++;
+ $matches = array();
+ if( preg_match( '/^!!\s*(\w+)/', $line, $matches ) ) {
+ $section = strtolower( $matches[1] );
+ if( $section == 'endarticle') {
+ if( !isset( $data['text'] ) ) {
+ wfDie( "'endarticle' without 'text' at line {$this->lineNum} of $filename\n" );
+ }
+ if( !isset( $data['article'] ) ) {
+ wfDie( "'endarticle' without 'article' at line {$this->lineNum} of $filename\n" );
+ }
+ if( $this->parser ) $this->parser->addArticle($this->parser->chomp($data['article']), $this->parser->chomp($data['text']),
+ $this->lineNum);
+ $data = array();
+ $section = null;
+ continue;
+ }
+ if( $section == 'endhooks' ) {
+ if( !isset( $data['hooks'] ) ) {
+ wfDie( "'endhooks' without 'hooks' at line {$this->lineNum} of $filename\n" );
+ }
+ foreach( explode( "\n", $data['hooks'] ) as $line ) {
+ $line = trim( $line );
+ if( $line ) {
+ if( $this->parser ) $this->parser->requireHook( $line );
+ }
+ }
+ $data = array();
+ $section = null;
+ continue;
+ }
+ if( $section == 'endfunctionhooks' ) {
+ if( !isset( $data['functionhooks'] ) ) {
+ wfDie( "'endfunctionhooks' without 'functionhooks' at line {$this->lineNum} of $filename\n" );
+ }
+ foreach( explode( "\n", $data['functionhooks'] ) as $line ) {
+ $line = trim( $line );
+ if( $line ) {
+ if( $this->parser ) $this->parser->requireFunctionHook( $line );
+ }
+ }
+ $data = array();
+ $section = null;
+ continue;
+ }
+ if( $section == 'end' ) {
+ if( !isset( $data['test'] ) ) {
+ wfDie( "'end' without 'test' at line {$this->lineNum} of $filename\n" );
+ }
+ if( !isset( $data['input'] ) ) {
+ wfDie( "'end' without 'input' at line {$this->lineNum} of $filename\n" );
+ }
+ if( !isset( $data['result'] ) ) {
+ wfDie( "'end' without 'result' at line {$this->lineNum} of $filename\n" );
+ }
+ if( !isset( $data['options'] ) ) {
+ $data['options'] = '';
+ }
+ if (!isset( $data['config'] ) )
+ $data['config'] = '';
+
+ if ( $this->parser && (preg_match('/\\bdisabled\\b/i', $data['options'])
+ || !preg_match("/{$this->parser->regex}/i", $data['test'])) && !$this->parser->runDisabled ) {
+ # disabled test
+ $data = array();
+ $section = null;
+ continue;
+ }
+ if ( $this->parser &&
+ preg_match('/\\bmath\\b/i', $data['options']) && !$this->parser->savedGlobals['wgUseTeX'] ) {
+ # don't run math tests if $wgUseTeX is set to false in LocalSettings
+ $data = array();
+ $section = null;
+ continue;
+ }
+
+ if( $this->parser ) {
+ $this->test = array(
+ 'test' => $this->parser->chomp( $data['test'] ),
+ 'input' => $this->parser->chomp( $data['input'] ),
+ 'result' => $this->parser->chomp( $data['result'] ),
+ 'options' => $this->parser->chomp( $data['options'] ),
+ 'config' => $this->parser->chomp( $data['config'] ) );
+ } else {
+ $this->test['test'] = $data['test'];
+ }
+ return true;
+ }
+ if ( isset ($data[$section] ) ) {
+ wfDie( "duplicate section '$section' at line {$this->lineNum} of $filename\n" );
+ }
+ $data[$section] = '';
+ continue;
+ }
+ if( $section ) {
+ $data[$section] .= $line;
+ }
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/maintenance/parserTests.php b/maintenance/parserTests.php
index 9a1a4807..78530716 100644
--- a/maintenance/parserTests.php
+++ b/maintenance/parserTests.php
@@ -40,16 +40,29 @@ Options:
--file=<testfile> Run test cases from a custom file instead of parserTests.txt
--record Record tests in database
--compare Compare with recorded results, without updating the database.
+ --setversion When using --record, set the version string to use (useful
+ with git-svn so that you can get the exact revision)
--keep-uploads Re-use the same upload directory for each test, don't delete it
--fuzz Do a fuzz test instead of a normal test
--seed <n> Start the fuzz test from the specified seed
--help Show this help message
-
+ --run-disabled run disabled tests
+ --upload Upload test results to remote wiki (per \$wgParserTestRemote)
ENDS;
exit( 0 );
}
+# Cases of weird db corruption were encountered when running tests on earlyish
+# versions of SQLite
+if ( $wgDBtype == 'sqlite' ) {
+ $db = wfGetDB( DB_MASTER );
+ $version = $db->getServerVersion();
+ if ( version_compare( $version, '3.6' ) < 0 ) {
+ die( "Parser tests require SQLite version 3.6 or later, you have $version\n" );
+ }
+}
+
# There is a convention that the parser should never
# refer to $wgTitle directly, but instead use the title
# passed to it.
diff --git a/maintenance/parserTests.txt b/maintenance/parserTests.txt
index 2d3907d8..500341d1 100644
--- a/maintenance/parserTests.txt
+++ b/maintenance/parserTests.txt
@@ -1,4 +1,4 @@
-# MediaWiki Parser test cases
+# MediaWiki Parser test cases
# Some taken from http://meta.wikimedia.org/wiki/Parser_testing
# All (C) their respective authors and released under the GPL
#
@@ -21,6 +21,9 @@
# language=XXX set content language to XXX for this test
# variant=XXX set the variant of language for this test (eg zh-tw)
# disabled do not run test
+# showtitle make the first line the title
+# comment run through Linker::formatComment() instead of main parser
+# local format section links in edit comment text as local links
#
# For testing purposes, temporary articles can created:
# !!article / NAMESPACE:TITLE / !!text / ARTICLE TEXT / !!endarticle
@@ -441,7 +444,7 @@ Definition list with URL link
!! input
; http://example.com/ : definition
!! result
-<dl><dt> <a href="http://example.com/" class="external free" title="http://example.com/" rel="nofollow">http://example.com/</a>&nbsp;</dt><dd> definition
+<dl><dt> <a href="http://example.com/" class="external free" rel="nofollow">http://example.com/</a>&nbsp;</dt><dd> definition
</dd></dl>
!! end
@@ -451,7 +454,7 @@ Definition list with bracketed URL link
!! input
;[http://www.example.com/ Example]:Something about it
!! result
-<dl><dt><a href="http://www.example.com/" class="external text" title="http://www.example.com/" rel="nofollow">Example</a></dt><dd>Something about it
+<dl><dt><a href="http://www.example.com/" class="external text" rel="nofollow">Example</a></dt><dd>Something about it
</dd></dl>
!! end
@@ -472,7 +475,7 @@ Definition list with news link containing colon
!! input
; news:alt.wikipedia.rox: This isn't even a real newsgroup!
!! result
-<dl><dt> <a href="news:alt.wikipedia.rox" class="external free" title="news:alt.wikipedia.rox" rel="nofollow">news:alt.wikipedia.rox</a></dt><dd> This isn't even a real newsgroup!
+<dl><dt> <a href="news:alt.wikipedia.rox" class="external free" rel="nofollow">news:alt.wikipedia.rox</a></dt><dd> This isn't even a real newsgroup!
</dd></dl>
!! end
@@ -482,7 +485,7 @@ Malformed definition list with colon
!! input
; news:alt.wikipedia.rox -- don't crash or enter an infinite loop
!! result
-<dl><dt> <a href="news:alt.wikipedia.rox" class="external free" title="news:alt.wikipedia.rox" rel="nofollow">news:alt.wikipedia.rox</a> -- don't crash or enter an infinite loop
+<dl><dt> <a href="news:alt.wikipedia.rox" class="external free" rel="nofollow">news:alt.wikipedia.rox</a> -- don't crash or enter an infinite loop
</dt></dl>
!! end
@@ -492,7 +495,7 @@ Definition lists: colon in external link text
!! input
; [http://www.wikipedia2.org/ Wikipedia : The Next Generation]: OK, I made that up
!! result
-<dl><dt> <a href="http://www.wikipedia2.org/" class="external text" title="http://www.wikipedia2.org/" rel="nofollow">Wikipedia&nbsp;: The Next Generation</a></dt><dd> OK, I made that up
+<dl><dt> <a href="http://www.wikipedia2.org/" class="external text" rel="nofollow">Wikipedia&nbsp;: The Next Generation</a></dt><dd> OK, I made that up
</dd></dl>
!! end
@@ -527,7 +530,7 @@ External links: non-bracketed
!! input
Non-bracketed: http://example.com
!! result
-<p>Non-bracketed: <a href="http://example.com" class="external free" title="http://example.com" rel="nofollow">http://example.com</a>
+<p>Non-bracketed: <a href="http://example.com" class="external free" rel="nofollow">http://example.com</a>
</p>
!! end
@@ -538,9 +541,9 @@ Numbered: [http://example.com]
Numbered: [http://example.net]
Numbered: [http://example.com]
!! result
-<p>Numbered: <a href="http://example.com" class="external autonumber" title="http://example.com" rel="nofollow">[1]</a>
-Numbered: <a href="http://example.net" class="external autonumber" title="http://example.net" rel="nofollow">[2]</a>
-Numbered: <a href="http://example.com" class="external autonumber" title="http://example.com" rel="nofollow">[3]</a>
+<p>Numbered: <a href="http://example.com" class="external autonumber" rel="nofollow">[1]</a>
+Numbered: <a href="http://example.net" class="external autonumber" rel="nofollow">[2]</a>
+Numbered: <a href="http://example.com" class="external autonumber" rel="nofollow">[3]</a>
</p>
!!end
@@ -549,7 +552,7 @@ External links: specified text
!! input
Specified text: [http://example.com link]
!! result
-<p>Specified text: <a href="http://example.com" class="external text" title="http://example.com" rel="nofollow">link</a>
+<p>Specified text: <a href="http://example.com" class="external text" rel="nofollow">link</a>
</p>
!!end
@@ -558,7 +561,7 @@ External links: trail
!! input
Linktrails should not work for external links: [http://example.com link]s
!! result
-<p>Linktrails should not work for external links: <a href="http://example.com" class="external text" title="http://example.com" rel="nofollow">link</a>s
+<p>Linktrails should not work for external links: <a href="http://example.com" class="external text" rel="nofollow">link</a>s
</p>
!! end
@@ -567,7 +570,7 @@ External links: dollar sign in URL
!! input
http://example.com/1$2345
!! result
-<p><a href="http://example.com/1$2345" class="external free" title="http://example.com/1$2345" rel="nofollow">http://example.com/1$2345</a>
+<p><a href="http://example.com/1$2345" class="external free" rel="nofollow">http://example.com/1$2345</a>
</p>
!! end
@@ -576,7 +579,7 @@ External links: dollar sign in URL (named)
!! input
[http://example.com/1$2345]
!! result
-<p><a href="http://example.com/1$2345" class="external autonumber" title="http://example.com/1$2345" rel="nofollow">[1]</a>
+<p><a href="http://example.com/1$2345" class="external autonumber" rel="nofollow">[1]</a>
</p>
!!end
@@ -585,7 +588,7 @@ External links: open square bracket forbidden in URL (bug 4377)
!! input
http://example.com/1[2345
!! result
-<p><a href="http://example.com/1" class="external free" title="http://example.com/1" rel="nofollow">http://example.com/1</a>[2345
+<p><a href="http://example.com/1" class="external free" rel="nofollow">http://example.com/1</a>[2345
</p>
!! end
@@ -594,7 +597,7 @@ External links: open square bracket forbidden in URL (named) (bug 4377)
!! input
[http://example.com/1[2345]
!! result
-<p><a href="http://example.com/1" class="external text" title="http://example.com/1" rel="nofollow">[2345</a>
+<p><a href="http://example.com/1" class="external text" rel="nofollow">[2345</a>
</p>
!!end
@@ -603,7 +606,7 @@ External links: nowiki in URL link text (bug 6230)
!!input
[http://example.com/ <nowiki>''example site''</nowiki>]
!! result
-<p><a href="http://example.com/" class="external text" title="http://example.com/" rel="nofollow">''example site''</a>
+<p><a href="http://example.com/" class="external text" rel="nofollow">''example site''</a>
</p>
!! end
@@ -613,7 +616,7 @@ External links: newline forbidden in text (bug 6230 regression check)
[http://example.com/ first
second]
!! result
-<p>[<a href="http://example.com/" class="external free" title="http://example.com/" rel="nofollow">http://example.com/</a> first
+<p>[<a href="http://example.com/" class="external free" rel="nofollow">http://example.com/</a> first
second]
</p>
!!end
@@ -641,7 +644,7 @@ Link to non-http image, no img tag
!! input
Link to non-http image, no img tag: ftp://example.com/test.jpg
!! result
-<p>Link to non-http image, no img tag: <a href="ftp://example.com/test.jpg" class="external free" title="ftp://example.com/test.jpg" rel="nofollow">ftp://example.com/test.jpg</a>
+<p>Link to non-http image, no img tag: <a href="ftp://example.com/test.jpg" class="external free" rel="nofollow">ftp://example.com/test.jpg</a>
</p>
!! end
@@ -650,7 +653,7 @@ External links: terminating separator
!! input
Terminating separator: http://example.com/thing,
!! result
-<p>Terminating separator: <a href="http://example.com/thing" class="external free" title="http://example.com/thing" rel="nofollow">http://example.com/thing</a>,
+<p>Terminating separator: <a href="http://example.com/thing" class="external free" rel="nofollow">http://example.com/thing</a>,
</p>
!! end
@@ -659,7 +662,7 @@ External links: intervening separator
!! input
Intervening separator: http://example.com/1,2,3
!! result
-<p>Intervening separator: <a href="http://example.com/1,2,3" class="external free" title="http://example.com/1,2,3" rel="nofollow">http://example.com/1,2,3</a>
+<p>Intervening separator: <a href="http://example.com/1,2,3" class="external free" rel="nofollow">http://example.com/1,2,3</a>
</p>
!! end
@@ -668,7 +671,7 @@ External links: old bug with URL in query
!! input
Old bug with URL in query: [http://example.com/thing?url=http://example.com link]
!! result
-<p>Old bug with URL in query: <a href="http://example.com/thing?url=http://example.com" class="external text" title="http://example.com/thing?url=http://example.com" rel="nofollow">link</a>
+<p>Old bug with URL in query: <a href="http://example.com/thing?url=http://example.com" class="external text" rel="nofollow">link</a>
</p>
!! end
@@ -677,7 +680,7 @@ External links: old URL-in-URL bug, mixed protocols
!! input
And again with mixed protocols: [ftp://example.com?url=http://example.com link]
!! result
-<p>And again with mixed protocols: <a href="ftp://example.com?url=http://example.com" class="external text" title="ftp://example.com?url=http://example.com" rel="nofollow">link</a>
+<p>And again with mixed protocols: <a href="ftp://example.com?url=http://example.com" class="external text" rel="nofollow">link</a>
</p>
!!end
@@ -686,7 +689,7 @@ External links: URL in text
!! input
URL in text: [http://example.com http://example.com]
!! result
-<p>URL in text: <a href="http://example.com" class="external free" title="http://example.com" rel="nofollow">http://example.com</a>
+<p>URL in text: <a href="http://example.com" class="external free" rel="nofollow">http://example.com</a>
</p>
!! end
@@ -695,7 +698,7 @@ External links: Clickable images
!! input
ja-style clickable images: [http://example.com http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png]
!! result
-<p>ja-style clickable images: <a href="http://example.com" class="external text" title="http://example.com" rel="nofollow"><img src="http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png" alt="Ncwikicol.png" /></a>
+<p>ja-style clickable images: <a href="http://example.com" class="external text" rel="nofollow"><img src="http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png" alt="Ncwikicol.png" /></a>
</p>
!!end
@@ -704,7 +707,7 @@ External links: raw ampersand
!! input
Old &amp; use: http://x&y
!! result
-<p>Old &amp; use: <a href="http://x&amp;y" class="external free" title="http://x&amp;y" rel="nofollow">http://x&amp;y</a>
+<p>Old &amp; use: <a href="http://x&amp;y" class="external free" rel="nofollow">http://x&amp;y</a>
</p>
!! end
@@ -713,7 +716,7 @@ External links: encoded ampersand
!! input
Old &amp; use: http://x&amp;y
!! result
-<p>Old &amp; use: <a href="http://x&amp;y" class="external free" title="http://x&amp;y" rel="nofollow">http://x&amp;y</a>
+<p>Old &amp; use: <a href="http://x&amp;y" class="external free" rel="nofollow">http://x&amp;y</a>
</p>
!! end
@@ -722,7 +725,7 @@ External links: encoded equals (bug 6102)
!! input
http://example.com/?foo&#61;bar
!! result
-<p><a href="http://example.com/?foo=bar" class="external free" title="http://example.com/?foo=bar" rel="nofollow">http://example.com/?foo=bar</a>
+<p><a href="http://example.com/?foo=bar" class="external free" rel="nofollow">http://example.com/?foo=bar</a>
</p>
!! end
@@ -731,7 +734,7 @@ External links: [raw ampersand]
!! input
Old &amp; use: [http://x&y]
!! result
-<p>Old &amp; use: <a href="http://x&amp;y" class="external autonumber" title="http://x&amp;y" rel="nofollow">[1]</a>
+<p>Old &amp; use: <a href="http://x&amp;y" class="external autonumber" rel="nofollow">[1]</a>
</p>
!! end
@@ -740,7 +743,7 @@ External links: [encoded ampersand]
!! input
Old &amp; use: [http://x&amp;y]
!! result
-<p>Old &amp; use: <a href="http://x&amp;y" class="external autonumber" title="http://x&amp;y" rel="nofollow">[1]</a>
+<p>Old &amp; use: <a href="http://x&amp;y" class="external autonumber" rel="nofollow">[1]</a>
</p>
!! end
@@ -749,7 +752,7 @@ External links: [encoded equals] (bug 6102)
!! input
[http://example.com/?foo&#61;bar]
!! result
-<p><a href="http://example.com/?foo=bar" class="external autonumber" title="http://example.com/?foo=bar" rel="nofollow">[1]</a>
+<p><a href="http://example.com/?foo=bar" class="external autonumber" rel="nofollow">[1]</a>
</p>
!! end
@@ -758,7 +761,7 @@ External links: [IDN ignored character reference in hostname; strip it right off
!! input
[http://e&zwnj;xample.com/]
!! result
-<p><a href="http://example.com/" class="external autonumber" title="http://example.com/" rel="nofollow">[1]</a>
+<p><a href="http://example.com/" class="external autonumber" rel="nofollow">[1]</a>
</p>
!! end
@@ -767,7 +770,7 @@ External links: IDN ignored character reference in hostname; strip it right off
!! input
http://e&zwnj;xample.com/
!! result
-<p><a href="http://example.com/" class="external free" title="http://example.com/" rel="nofollow">http://example.com/</a>
+<p><a href="http://example.com/" class="external free" rel="nofollow">http://example.com/</a>
</p>
!! end
@@ -776,7 +779,7 @@ External links: www.jpeg.org (bug 554)
!! input
http://www.jpeg.org
!!result
-<p><a href="http://www.jpeg.org" class="external free" title="http://www.jpeg.org" rel="nofollow">http://www.jpeg.org</a>
+<p><a href="http://www.jpeg.org" class="external free" rel="nofollow">http://www.jpeg.org</a>
</p>
!! end
@@ -785,7 +788,7 @@ External links: URL within URL (original bug 2)
!! input
[http://www.unausa.org/newindex.asp?place=http://www.unausa.org/programs/mun.asp]
!! result
-<p><a href="http://www.unausa.org/newindex.asp?place=http://www.unausa.org/programs/mun.asp" class="external autonumber" title="http://www.unausa.org/newindex.asp?place=http://www.unausa.org/programs/mun.asp" rel="nofollow">[1]</a>
+<p><a href="http://www.unausa.org/newindex.asp?place=http://www.unausa.org/programs/mun.asp" class="external autonumber" rel="nofollow">[1]</a>
</p>
!! end
@@ -794,7 +797,7 @@ BUG 361: URL inside bracketed URL
!! input
[http://www.example.com/foo http://www.example.com/bar]
!! result
-<p><a href="http://www.example.com/foo" class="external text" title="http://www.example.com/foo" rel="nofollow">http://www.example.com/bar</a>
+<p><a href="http://www.example.com/foo" class="external text" rel="nofollow">http://www.example.com/bar</a>
</p>
!! end
@@ -803,7 +806,7 @@ BUG 361: URL within URL, not bracketed
!! input
http://www.example.com/foo?=http://www.example.com/bar
!! result
-<p><a href="http://www.example.com/foo?=http://www.example.com/bar" class="external free" title="http://www.example.com/foo?=http://www.example.com/bar" rel="nofollow">http://www.example.com/foo?=http://www.example.com/bar</a>
+<p><a href="http://www.example.com/foo?=http://www.example.com/bar" class="external free" rel="nofollow">http://www.example.com/foo?=http://www.example.com/bar</a>
</p>
!! end
@@ -812,7 +815,7 @@ BUG 289: ">"-token in URL-tail
!! input
http://www.example.com/<hello>
!! result
-<p><a href="http://www.example.com/" class="external free" title="http://www.example.com/" rel="nofollow">http://www.example.com/</a>&lt;hello&gt;
+<p><a href="http://www.example.com/" class="external free" rel="nofollow">http://www.example.com/</a>&lt;hello&gt;
</p>
!!end
@@ -821,7 +824,7 @@ BUG 289: literal ">"-token in URL-tail
!! input
http://www.example.com/<b>html</b>
!! result
-<p><a href="http://www.example.com/" class="external free" title="http://www.example.com/" rel="nofollow">http://www.example.com/</a><b>html</b>
+<p><a href="http://www.example.com/" class="external free" rel="nofollow">http://www.example.com/</a><b>html</b>
</p>
!!end
@@ -830,7 +833,7 @@ BUG 289: ">"-token in bracketed URL
!! input
[http://www.example.com/<hello> stuff]
!! result
-<p><a href="http://www.example.com/" class="external text" title="http://www.example.com/" rel="nofollow">&lt;hello&gt; stuff</a>
+<p><a href="http://www.example.com/" class="external text" rel="nofollow">&lt;hello&gt; stuff</a>
</p>
!!end
@@ -839,7 +842,7 @@ BUG 289: literal ">"-token in bracketed URL
!! input
[http://www.example.com/<b>html</b> stuff]
!! result
-<p><a href="http://www.example.com/" class="external text" title="http://www.example.com/" rel="nofollow"><b>html</b> stuff</a>
+<p><a href="http://www.example.com/" class="external text" rel="nofollow"><b>html</b> stuff</a>
</p>
!!end
@@ -848,7 +851,7 @@ BUG 289: literal double quote at end of URL
!! input
http://www.example.com/"hello"
!! result
-<p><a href="http://www.example.com/" class="external free" title="http://www.example.com/" rel="nofollow">http://www.example.com/</a>"hello"
+<p><a href="http://www.example.com/" class="external free" rel="nofollow">http://www.example.com/</a>"hello"
</p>
!!end
@@ -857,7 +860,7 @@ BUG 289: literal double quote in bracketed URL
!! input
[http://www.example.com/"hello" stuff]
!! result
-<p><a href="http://www.example.com/" class="external text" title="http://www.example.com/" rel="nofollow">"hello" stuff</a>
+<p><a href="http://www.example.com/" class="external text" rel="nofollow">"hello" stuff</a>
</p>
!!end
@@ -869,7 +872,7 @@ disabled
!! input
[http://www.example.com test]
!! result
-<p>[<a href="http://www.example.com" class="external free" title="http://www.example.com" rel="nofollow">http://www.example.com</a> test]
+<p>[<a href="http://www.example.com" class="external free" rel="nofollow">http://www.example.com</a> test]
</p>
!! end
@@ -878,7 +881,7 @@ External links: multiple legal whitespace is fine, Magnus. Don't break it please
!! input
[http://www.example.com test]
!! result
-<p><a href="http://www.example.com" class="external text" title="http://www.example.com" rel="nofollow">test</a>
+<p><a href="http://www.example.com" class="external text" rel="nofollow">test</a>
</p>
!! end
@@ -887,7 +890,7 @@ External links: wiki links within external link (Bug 3695)
!! input
[http://example.com [[wikilink]] embedded in ext link]
!! result
-<p><a href="http://example.com" class="external text" title="http://example.com" rel="nofollow"></a><a href="/index.php?title=Wikilink&amp;action=edit&amp;redlink=1" class="new" title="Wikilink (page does not exist)">wikilink</a><a href="http://example.com" class="external text" title="http://example.com" rel="nofollow"> embedded in ext link</a>
+<p><a href="http://example.com" class="external text" rel="nofollow"></a><a href="/index.php?title=Wikilink&amp;action=edit&amp;redlink=1" class="new" title="Wikilink (page does not exist)">wikilink</a><a href="http://example.com" class="external text" rel="nofollow"> embedded in ext link</a>
</p>
!! end
@@ -912,11 +915,11 @@ Bug 2702: Mismatched <i>, <b> and <a> tags are invalid
''Something [http://example.com mixed''''', even bold]'''
'''''Now [http://example.com both''''']
!! result
-<p><a href="http://example.com" class="external text" title="http://example.com" rel="nofollow"><i>text</i></a>
-<a href="http://example.com" class="external text" title="http://example.com" rel="nofollow"><b>text</b></a>
-<i>Something </i><a href="http://example.com" class="external text" title="http://example.com" rel="nofollow"><i>in italic</i></a>
-<i>Something </i><a href="http://example.com" class="external text" title="http://example.com" rel="nofollow"><i>mixed</i><b>, even bold</b></a>
-<i><b>Now </b></i><a href="http://example.com" class="external text" title="http://example.com" rel="nofollow"><i><b>both</b></i></a>
+<p><a href="http://example.com" class="external text" rel="nofollow"><i>text</i></a>
+<a href="http://example.com" class="external text" rel="nofollow"><b>text</b></a>
+<i>Something </i><a href="http://example.com" class="external text" rel="nofollow"><i>in italic</i></a>
+<i>Something </i><a href="http://example.com" class="external text" rel="nofollow"><i>mixed</i><b>, even bold</b></a>
+<i><b>Now </b></i><a href="http://example.com" class="external text" rel="nofollow"><i><b>both</b></i></a>
</p>
!! end
@@ -926,7 +929,7 @@ Bug 4781: %26 in URL
!! input
http://www.example.com/?title=AT%26T
!! result
-<p><a href="http://www.example.com/?title=AT%26T" class="external free" title="http://www.example.com/?title=AT%26T" rel="nofollow">http://www.example.com/?title=AT%26T</a>
+<p><a href="http://www.example.com/?title=AT%26T" class="external free" rel="nofollow">http://www.example.com/?title=AT%26T</a>
</p>
!! end
@@ -935,7 +938,7 @@ Bug 4781, 5267: %26 in URL
!! input
http://www.example.com/?title=100%25_Bran
!! result
-<p><a href="http://www.example.com/?title=100%25_Bran" class="external free" title="http://www.example.com/?title=100%25_Bran" rel="nofollow">http://www.example.com/?title=100%25_Bran</a>
+<p><a href="http://www.example.com/?title=100%25_Bran" class="external free" rel="nofollow">http://www.example.com/?title=100%25_Bran</a>
</p>
!! end
@@ -944,7 +947,7 @@ Bug 4781, 5267: %28, %29 in URL
!! input
http://www.example.com/?title=Ben-Hur_%281959_film%29
!! result
-<p><a href="http://www.example.com/?title=Ben-Hur_%281959_film%29" class="external free" title="http://www.example.com/?title=Ben-Hur_%281959_film%29" rel="nofollow">http://www.example.com/?title=Ben-Hur_%281959_film%29</a>
+<p><a href="http://www.example.com/?title=Ben-Hur_%281959_film%29" class="external free" rel="nofollow">http://www.example.com/?title=Ben-Hur_%281959_film%29</a>
</p>
!! end
@@ -954,7 +957,7 @@ Bug 4781: %26 in autonumber URL
!! input
[http://www.example.com/?title=AT%26T]
!! result
-<p><a href="http://www.example.com/?title=AT%26T" class="external autonumber" title="http://www.example.com/?title=AT%26T" rel="nofollow">[1]</a>
+<p><a href="http://www.example.com/?title=AT%26T" class="external autonumber" rel="nofollow">[1]</a>
</p>
!! end
@@ -963,7 +966,7 @@ Bug 4781, 5267: %26 in autonumber URL
!! input
[http://www.example.com/?title=100%25_Bran]
!! result
-<p><a href="http://www.example.com/?title=100%25_Bran" class="external autonumber" title="http://www.example.com/?title=100%25_Bran" rel="nofollow">[1]</a>
+<p><a href="http://www.example.com/?title=100%25_Bran" class="external autonumber" rel="nofollow">[1]</a>
</p>
!! end
@@ -972,7 +975,7 @@ Bug 4781, 5267: %28, %29 in autonumber URL
!! input
[http://www.example.com/?title=Ben-Hur_%281959_film%29]
!! result
-<p><a href="http://www.example.com/?title=Ben-Hur_%281959_film%29" class="external autonumber" title="http://www.example.com/?title=Ben-Hur_%281959_film%29" rel="nofollow">[1]</a>
+<p><a href="http://www.example.com/?title=Ben-Hur_%281959_film%29" class="external autonumber" rel="nofollow">[1]</a>
</p>
!! end
@@ -982,7 +985,7 @@ Bug 4781: %26 in bracketed URL
!! input
[http://www.example.com/?title=AT%26T link]
!! result
-<p><a href="http://www.example.com/?title=AT%26T" class="external text" title="http://www.example.com/?title=AT%26T" rel="nofollow">link</a>
+<p><a href="http://www.example.com/?title=AT%26T" class="external text" rel="nofollow">link</a>
</p>
!! end
@@ -991,7 +994,7 @@ Bug 4781, 5267: %26 in bracketed URL
!! input
[http://www.example.com/?title=100%25_Bran link]
!! result
-<p><a href="http://www.example.com/?title=100%25_Bran" class="external text" title="http://www.example.com/?title=100%25_Bran" rel="nofollow">link</a>
+<p><a href="http://www.example.com/?title=100%25_Bran" class="external text" rel="nofollow">link</a>
</p>
!! end
@@ -1000,7 +1003,7 @@ Bug 4781, 5267: %28, %29 in bracketed URL
!! input
[http://www.example.com/?title=Ben-Hur_%281959_film%29 link]
!! result
-<p><a href="http://www.example.com/?title=Ben-Hur_%281959_film%29" class="external text" title="http://www.example.com/?title=Ben-Hur_%281959_film%29" rel="nofollow">link</a>
+<p><a href="http://www.example.com/?title=Ben-Hur_%281959_film%29" class="external text" rel="nofollow">link</a>
</p>
!! end
@@ -1009,7 +1012,7 @@ External link containing double-single-quotes in text '' (bug 4598 sanity check)
!! input
Some [http://example.com/ pretty ''italics'' and stuff]!
!! result
-<p>Some <a href="http://example.com/" class="external text" title="http://example.com/" rel="nofollow">pretty <i>italics</i> and stuff</a>!
+<p>Some <a href="http://example.com/" class="external text" rel="nofollow">pretty <i>italics</i> and stuff</a>!
</p>
!! end
@@ -1018,7 +1021,7 @@ External link containing double-single-quotes in text embedded in italics (bug 4
!! input
''Some [http://example.com/ pretty ''italics'' and stuff]!''
!! result
-<p><i>Some </i><a href="http://example.com/" class="external text" title="http://example.com/" rel="nofollow"><i>pretty </i>italics<i> and stuff</i></a><i>!</i>
+<p><i>Some </i><a href="http://example.com/" class="external text" rel="nofollow"><i>pretty </i>italics<i> and stuff</i></a><i>!</i>
</p>
!! end
@@ -1269,6 +1272,8 @@ Invalid attributes in table cell (bug 1830)
# specified here is now valid XML, which is an improvement . . .
!! test
Table security: embedded pipes (http://lists.wikimedia.org/mailman/htdig/wikitech-l/2006-April/022293.html)
+!! options
+disabled
!! input
{|
| |[ftp://|x||]" onmouseover="alert(document.cookie)">test
@@ -1454,7 +1459,7 @@ Example for such a section: == < ==
!! input
[[%23%3c]][[%23%3e]]
!! result
-<p><a href="#.3C" title="">#&lt;</a><a href="#.3E" title="">#&gt;</a>
+<p><a href="#.3C">#&lt;</a><a href="#.3E">#&gt;</a>
</p>
!! end
@@ -1469,6 +1474,8 @@ Link containing "<#" and ">#" as a hex sequences
!! test
Link containing double-single-quotes '' (bug 4598)
+!! options
+disabled
!! input
[[Lista d''e paise d''o munno]]
!! result
@@ -1499,7 +1506,7 @@ Plain link to URL
!! input
[[http://www.example.com]]
!! result
-<p>[<a href="http://www.example.com" class="external autonumber" title="http://www.example.com" rel="nofollow">[1]</a>]
+<p>[<a href="http://www.example.com" class="external autonumber" rel="nofollow">[1]</a>]
</p>
!! end
@@ -1510,7 +1517,7 @@ Plain link to URL
# ----
# I'm changing it to match the current output--it arguably makes more
# sense in the light of the test above. Old expected result was:
-#<p>Piped link to URL: <a href="/index.php?title=Http://www.example.com&amp;action=edit" class="new" title="Http://www.example.com">an example URL</a>
+#<p>Piped link to URL: <a href="/index.php?title=Http://www.example.com&amp;action=edit" class="new">an example URL</a>
#</p>
# But I think this test is bordering on "garbage in, garbage out" anyway.
# -- wtm
@@ -1519,7 +1526,7 @@ Piped link to URL
!! input
Piped link to URL: [[http://www.example.com|an example URL]]
!! result
-<p>Piped link to URL: [<a href="http://www.example.com|an" class="external text" title="http://www.example.com|an" rel="nofollow">example URL</a>]
+<p>Piped link to URL: [<a href="http://www.example.com|an" class="external text" rel="nofollow">example URL</a>]
</p>
!! end
@@ -1591,6 +1598,16 @@ title=[[0]]
</p>
!! end
+!! test
+Non-breaking spaces in title
+!! input
+[[&nbsp; Main &nbsp; Page &nbsp;]]
+!! result
+<p><a href="/wiki/Main_Page" title="Main Page">&nbsp; Main &nbsp; Page &nbsp;</a>
+</p>
+!!end
+
+
###
### Interwiki links (see maintenance/interwiki.sql)
###
@@ -1946,7 +1963,6 @@ title=[[User:Ævar Arnfjörð Bjarmason]]
Magic Word: {{NAMESPACE}}
!! options
title=[[User:Ævar Arnfjörð Bjarmason]]
-disabled # FIXME
!! input
{{NAMESPACE}}
!! result
@@ -1958,7 +1974,6 @@ disabled # FIXME
Magic Word: {{NAMESPACEE}}
!! options
title=[[User:Ævar Arnfjörð Bjarmason]]
-disabled # FIXME
!! input
{{NAMESPACEE}}
!! result
@@ -1980,7 +1995,7 @@ Magic Word: {{NUMBEROFFILES}}
!! input
{{NUMBEROFFILES}}
!! result
-<p>1
+<p>2
</p>
!! end
@@ -1988,7 +2003,6 @@ Magic Word: {{NUMBEROFFILES}}
Magic Word: {{PAGENAME}}
!! options
title=[[User:Ævar Arnfjörð Bjarmason]]
-disabled # FIXME
!! input
{{PAGENAME}}
!! result
@@ -2003,7 +2017,7 @@ title=[[User:Ævar Arnfjörð Bjarmason]]
!! input
{{PAGENAMEE}}
!! result
-<p>User:%C3%86var_Arnfj%C3%B6r%C3%B0_Bjarmason
+<p>%C3%86var_Arnfj%C3%B6r%C3%B0_Bjarmason
</p>
!! end
@@ -2030,7 +2044,7 @@ Magic Word: {{SERVER}}
!! input
{{SERVER}}
!! result
-<p><a href="http://localhost" class="external free" title="http://localhost" rel="nofollow">http://localhost</a>
+<p><a href="http://localhost" class="external free" rel="nofollow">http://localhost</a>
</p>
!! end
@@ -2039,7 +2053,7 @@ Magic Word: {{SERVERNAME}}
!! input
{{SERVERNAME}}
!! result
-<p>Britney Spears
+<p>Britney-Spears
</p>
!! end
@@ -2180,7 +2194,7 @@ Magic links: RFC (bug 479)
!! input
RFC 822
!! result
-<p><a href="http://tools.ietf.org/html/rfc822" class="external" title="http://tools.ietf.org/html/rfc822">RFC 822</a>
+<p><a href="http://tools.ietf.org/html/rfc822" class="external mw-magiclink-rfc">RFC 822</a>
</p>
!! end
@@ -2189,7 +2203,7 @@ Magic links: ISBN (bug 1937)
!! input
ISBN 0-306-40615-2
!! result
-<p><a href="/wiki/Special:BookSources/0306406152" class="internal">ISBN 0-306-40615-2</a>
+<p><a href="/wiki/Special:BookSources/0306406152" class="internal mw-magiclink-isbn">ISBN 0-306-40615-2</a>
</p>
!! end
@@ -2198,7 +2212,7 @@ Magic links: PMID incorrectly converts space to underscore
!! input
PMID 1234
!! result
-<p><a href="http://www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" class="external" title="http://www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a>
+<p><a href="http://www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" class="external mw-magiclink-pmid">PMID 1234</a>
</p>
!! end
@@ -2681,8 +2695,8 @@ Bug 6563: Edit link generation for section shown by <includeonly>
!! input
{{includeonly section}}
!! result
-<a name="Includeonly_section" id="Includeonly_section"></a><h2><span class="editsection">[<a href="/index.php?title=Template:Includeonly_section&amp;action=edit&amp;section=T-1" title="Template:Includeonly section">edit</a>]</span> <span class="mw-headline">Includeonly section</span></h2>
-<a name="Section_T-1" id="Section_T-1"></a><h2><span class="editsection">[<a href="/index.php?title=Template:Includeonly_section&amp;action=edit&amp;section=T-2" title="Template:Includeonly section">edit</a>]</span> <span class="mw-headline">Section T-1</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Template:Includeonly_section&amp;action=edit&amp;section=T-1" title="Template:Includeonly section">edit</a>]</span> <span class="mw-headline" id="Includeonly_section">Includeonly section</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Template:Includeonly_section&amp;action=edit&amp;section=T-2" title="Template:Includeonly section">edit</a>]</span> <span class="mw-headline" id="Section_T-1">Section T-1</span></h2>
!! end
@@ -2708,7 +2722,7 @@ Bug 6563: Edit link generation for section suppressed by <includeonly>
</includeonly>
==Section 1==
!! result
-<a name="Section_1" id="Section_1"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a>]</span> <span class="mw-headline">Section 1</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Section 1">edit</a>]</span> <span class="mw-headline" id="Section_1">Section 1</span></h2>
!! end
@@ -2918,6 +2932,46 @@ pst
Foo
!! end
+!! article
+Template:SubstTest
+!!text
+{{<includeonly>subst:</includeonly>Includes}}
+!! endarticle
+
+!! article
+Template:SafeSubstTest
+!! text
+{{<includeonly>safesubst:</includeonly>Includes}}
+!! endarticle
+
+!! test
+bug 22297: safesubst: works during PST
+!! options
+pst
+!! input
+{{subst:SafeSubstTest}}{{safesubst:SubstTest}}
+!! result
+FoobarFoobar
+!! end
+
+!! test
+bug 22297: safesubst: works during normal parse
+!! input
+{{SafeSubstTest}}
+!! result
+<p>Foobar
+</p>
+!! end
+
+!! test:
+subst: does not work during normal parse
+!! input
+{{SubstTest}}
+!! result
+<p>{{subst:Includes}}
+</p>
+!! end
+
!! test
pre-save transform: context links ("pipe trick")
!! options
@@ -3130,7 +3184,7 @@ Simple image
!! input
[[Image:foobar.jpg]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="Image:foobar.jpg"><img alt="Image:foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3139,7 +3193,7 @@ Right-aligned image
!! input
[[Image:foobar.jpg|right]]
!! result
-<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Foobar.jpg"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a></div>
+<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div>
!! end
@@ -3148,7 +3202,7 @@ Simple image (using File: namespace, now canonical)
!! input
[[File:foobar.jpg]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="File:foobar.jpg"><img alt="File:foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3157,7 +3211,7 @@ Image with caption
!! input
[[Image:foobar.jpg|right|Caption text]]
!! result
-<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption text"><img alt="Caption text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a></div>
+<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption text"><img alt="Caption text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div>
!! end
@@ -3166,7 +3220,7 @@ Image with link parameter, wiki target
!! input
[[Image:foobar.jpg|link=Target page]]
!! result
-<p><a href="/wiki/Target_page" title="Target page"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/Target_page" title="Target page"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3175,7 +3229,7 @@ Image with link parameter, URL target
!! input
[[Image:foobar.jpg|link=http://example.com/]]
!! result
-<p><a href="http://example.com/"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="http://example.com/"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3184,7 +3238,25 @@ Image with empty link parameter
!! input
[[Image:foobar.jpg|link=]]
!! result
-<p><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" />
+<p><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" />
+</p>
+!! end
+
+!! test
+Image with link parameter (wiki target) and unnamed parameter
+!! input
+[[Image:foobar.jpg|link=Target page|Title]]
+!! result
+<p><a href="/wiki/Target_page" title="Title"><img alt="Title" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
+</p>
+!! end
+
+!! test
+Image with link parameter (URL target) and unnamed parameter
+!! input
+[[Image:foobar.jpg|link=http://example.com/|Title]]
+!! result
+<p><a href="http://example.com/" title="Title"><img alt="Title" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3195,7 +3267,7 @@ Image with frame and link
!! input
[[Image:Foobar.jpg|frame|left|This is a test image [[Main Page]]]]
!! result
-<div class="thumb tleft"><div class="thumbinner" style="width:1943px;"><a href="/wiki/File:Foobar.jpg" class="image" title="This is a test image Main Page"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" class="thumbimage" /></a> <div class="thumbcaption">This is a test image <a href="/wiki/Main_Page" title="Main Page">Main Page</a></div></div></div>
+<div class="thumb tleft"><div class="thumbinner" style="width:1943px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" class="thumbimage" /></a> <div class="thumbcaption">This is a test image <a href="/wiki/Main_Page" title="Main Page">Main Page</a></div></div></div>
!! end
@@ -3204,7 +3276,7 @@ Image with frame and link and explicit alt
!! input
[[Image:Foobar.jpg|frame|left|This is a test image [[Main Page]]|alt=Altitude]]
!! result
-<div class="thumb tleft"><div class="thumbinner" style="width:1943px;"><a href="/wiki/File:Foobar.jpg" class="image" title="This is a test image Main Page"><img alt="Altitude" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" class="thumbimage" /></a> <div class="thumbcaption">This is a test image <a href="/wiki/Main_Page" title="Main Page">Main Page</a></div></div></div>
+<div class="thumb tleft"><div class="thumbinner" style="width:1943px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Altitude" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" class="thumbimage" /></a> <div class="thumbcaption">This is a test image <a href="/wiki/Main_Page" title="Main Page">Main Page</a></div></div></div>
!! end
@@ -3213,7 +3285,7 @@ Image with wiki markup in implicit alt
!! input
[[Image:Foobar.jpg|testing '''bold''' in alt]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="testing bold in alt"><img alt="testing bold in alt" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image" title="testing bold in alt"><img alt="testing bold in alt" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3222,7 +3294,7 @@ Image with wiki markup in explicit alt
!! input
[[Image:Foobar.jpg|alt=testing '''bold''' in alt]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="Foobar.jpg"><img alt="testing bold in alt" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="testing bold in alt" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3238,11 +3310,20 @@ Add test with existing image page
!! end
!! test
+bug 18784 Link to non-existent image page with caption should use caption as link text
+!! input
+[[:Image:test|caption]]
+!! result
+<p><a href="/index.php?title=File:Test&amp;action=edit&amp;redlink=1" class="new" title="File:Test (page does not exist)">caption</a>
+</p>
+!! end
+
+!! test
Frameless image caption with a free URL
!! input
[[Image:foobar.jpg|http://example.com]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="http://example.com"><img alt="http://example.com" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image" title="http://example.com"><img alt="http://example.com" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3251,7 +3332,7 @@ Thumbnail image caption with a free URL
!! input
[[Image:foobar.jpg|thumb|http://example.com]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image" title="http://example.com"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="http://example.com" class="external free" title="http://example.com" rel="nofollow">http://example.com</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a></div></div></div>
!! end
@@ -3260,7 +3341,7 @@ Thumbnail image caption with a free URL and explicit alt
!! input
[[Image:foobar.jpg|thumb|http://example.com|alt=Alteration]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image" title="http://example.com"><img alt="Alteration" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="http://example.com" class="external free" title="http://example.com" rel="nofollow">http://example.com</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Alteration" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a></div></div></div>
!! end
@@ -3269,7 +3350,7 @@ BUG 1887: A ISBN with a thumbnail
!! input
[[Image:foobar.jpg|thumb|ISBN 1235467890]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image" title="ISBN 1235467890"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="/wiki/Special:BookSources/1235467890" class="internal">ISBN 1235467890</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="/wiki/Special:BookSources/1235467890" class="internal mw-magiclink-isbn">ISBN 1235467890</a></div></div></div>
!! end
@@ -3278,7 +3359,7 @@ BUG 1887: A RFC with a thumbnail
!! input
[[Image:foobar.jpg|thumb|This is RFC 12354]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image" title="This is RFC 12354"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is <a href="http://tools.ietf.org/html/rfc12354" class="external" title="http://tools.ietf.org/html/rfc12354">RFC 12354</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is <a href="http://tools.ietf.org/html/rfc12354" class="external mw-magiclink-rfc">RFC 12354</a></div></div></div>
!! end
@@ -3287,7 +3368,7 @@ BUG 1887: A mailto link with a thumbnail
!! input
[[Image:foobar.jpg|thumb|Please mailto:nobody@example.com]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image" title="Please mailto:nobody@example.com"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Please <a href="mailto:nobody@example.com" class="external free" title="mailto:nobody@example.com" rel="nofollow">mailto:nobody@example.com</a></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Please <a href="mailto:nobody@example.com" class="external free" rel="nofollow">mailto:nobody@example.com</a></div></div></div>
!! end
@@ -3297,7 +3378,7 @@ so math is not stripped and turns up as escaped &lt;math&gt; tags.
!! input
[[Image:foobar.jpg|thumb|<math>2+2</math>]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image" title="&lt;math&gt;2+2&lt;/math&gt;"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>&lt;math&gt;2+2&lt;/math&gt;</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>&lt;math&gt;2+2&lt;/math&gt;</div></div></div>
!! end
@@ -3308,7 +3389,7 @@ math
!! input
[[Image:foobar.jpg|thumb|<math>2+2</math>]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image" title="2 + 2"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><span class="texhtml">2 + 2</span></div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><span class="texhtml">2 + 2</span></div></div></div>
!! end
@@ -3318,7 +3399,7 @@ BUG 648: Frameless image caption with a link
!! input
[[Image:foobar.jpg|text with a [[link]] in it]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3327,7 +3408,7 @@ BUG 648: Frameless image caption with a link (suffix)
!! input
[[Image:foobar.jpg|text with a [[link]]foo in it]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a linkfoo in it"><img alt="text with a linkfoo in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a linkfoo in it"><img alt="text with a linkfoo in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3336,7 +3417,7 @@ BUG 648: Frameless image caption with an interwiki link
!! input
[[Image:foobar.jpg|text with a [[MeatBall:Link]] in it]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a MeatBall:Link in it"><img alt="text with a MeatBall:Link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a MeatBall:Link in it"><img alt="text with a MeatBall:Link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3345,7 +3426,7 @@ BUG 648: Frameless image caption with a piped interwiki link
!! input
[[Image:foobar.jpg|text with a [[MeatBall:Link|link]] in it]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3354,7 +3435,7 @@ Escape HTML special chars in image alt text
!! input
[[Image:foobar.jpg|& < > "]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="&amp; &lt; &gt; &quot;"><img alt="&amp; &lt; &gt; &quot;" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image" title="&amp; &lt; &gt; &quot;"><img alt="&amp; &lt; &gt; &quot;" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3363,7 +3444,7 @@ BUG 499: Alt text should have &#1234;, not &amp;1234;
!! input
[[Image:foobar.jpg|&#9792;]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="♀"><img alt="♀" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image" title="♀"><img alt="♀" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!! end
@@ -3381,7 +3462,7 @@ Image caption containing another image
!! input
[[Image:Foobar.jpg|thumb|This is a caption with another [[Image:icon.png|image]] inside it!]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image" title="This is a caption with another File:Icon.png inside it!"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is a caption with another <a href="/index.php?title=Special:Upload&amp;wpDestFile=Icon.png" class="new" title="File:Icon.png">File:Icon.png</a> inside it!</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is a caption with another <a href="/index.php?title=Special:Upload&amp;wpDestFile=Icon.png" class="new" title="File:Icon.png">image</a> inside it!</div></div></div>
!! end
@@ -3391,7 +3472,7 @@ Image caption containing a newline
[[Image:Foobar.jpg|This
*is some text]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="This *is some text"><img alt="This *is some text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image" title="This *is some text"><img alt="This *is some text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!!end
@@ -3401,7 +3482,7 @@ Bug 3090: External links other than http: in image captions
!! input
[[Image:Foobar.jpg|thumb|200px|This caption has [irc://example.net irc] and [https://example.com Secure] ext links in it.]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image" title="This caption has irc and Secure ext links in it."><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This caption has <a href="irc://example.net" class="external text" title="irc://example.net" rel="nofollow">irc</a> and <a href="https://example.com" class="external text" title="https://example.com" rel="nofollow">Secure</a> ext links in it.</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="200" height="23" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This caption has <a href="irc://example.net" class="external text" rel="nofollow">irc</a> and <a href="https://example.com" class="external text" rel="nofollow">Secure</a> ext links in it.</div></div></div>
!! end
@@ -3471,7 +3552,7 @@ Link to category
!! input
[[:Category:MediaWiki User's Guide]]
!! result
-<p><a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User&#39;s Guide">Category:MediaWiki User's Guide</a>
+<p><a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">Category:MediaWiki User's Guide</a>
</p>
!! end
@@ -3482,7 +3563,7 @@ cat
!! input
[[Category:MediaWiki User's Guide]]
!! result
-<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User&#039;s Guide">MediaWiki User's Guide</a>
+<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a>
!! end
!! test
@@ -3523,13 +3604,13 @@ More
===Smaller headline===
Blah blah
!! result
-<a name="Headline_1" id="Headline_1"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Headline 1">edit</a>]</span> <span class="mw-headline"> Headline 1 </span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Headline 1">edit</a>]</span> <span class="mw-headline" id="Headline_1"> Headline 1 </span></h2>
<p>Some text
</p>
-<a name="Headline_2" id="Headline_2"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Headline 2">edit</a>]</span> <span class="mw-headline">Headline 2</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Headline 2">edit</a>]</span> <span class="mw-headline" id="Headline_2">Headline 2</span></h2>
<p>More
</p>
-<a name="Smaller_headline" id="Smaller_headline"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: Smaller headline">edit</a>]</span> <span class="mw-headline">Smaller headline</span></h3>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: Smaller headline">edit</a>]</span> <span class="mw-headline" id="Smaller_headline">Smaller headline</span></h3>
<p>Blah blah
</p>
!! end
@@ -3546,36 +3627,36 @@ Section headings with TOC
Some text
===Another headline===
!! result
-<table id="toc" class="toc" summary="Contents"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
<ul>
-<li class="toclevel-1"><a href="#Headline_1"><span class="tocnumber">1</span> <span class="toctext">Headline 1</span></a>
+<li class="toclevel-1 tocsection-1"><a href="#Headline_1"><span class="tocnumber">1</span> <span class="toctext">Headline 1</span></a>
<ul>
-<li class="toclevel-2"><a href="#Subheadline_1"><span class="tocnumber">1.1</span> <span class="toctext">Subheadline 1</span></a>
+<li class="toclevel-2 tocsection-2"><a href="#Subheadline_1"><span class="tocnumber">1.1</span> <span class="toctext">Subheadline 1</span></a>
<ul>
-<li class="toclevel-3"><a href="#Skipping_a_level"><span class="tocnumber">1.1.1</span> <span class="toctext">Skipping a level</span></a>
+<li class="toclevel-3 tocsection-3"><a href="#Skipping_a_level"><span class="tocnumber">1.1.1</span> <span class="toctext">Skipping a level</span></a>
<ul>
-<li class="toclevel-4"><a href="#Skipping_a_level_2"><span class="tocnumber">1.1.1.1</span> <span class="toctext">Skipping a level</span></a></li>
+<li class="toclevel-4 tocsection-4"><a href="#Skipping_a_level_2"><span class="tocnumber">1.1.1.1</span> <span class="toctext">Skipping a level</span></a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
-<li class="toclevel-1"><a href="#Headline_2"><span class="tocnumber">2</span> <span class="toctext">Headline 2</span></a>
+<li class="toclevel-1 tocsection-5"><a href="#Headline_2"><span class="tocnumber">2</span> <span class="toctext">Headline 2</span></a>
<ul>
-<li class="toclevel-2"><a href="#Another_headline"><span class="tocnumber">2.1</span> <span class="toctext">Another headline</span></a></li>
+<li class="toclevel-2 tocsection-6"><a href="#Another_headline"><span class="tocnumber">2.1</span> <span class="toctext">Another headline</span></a></li>
</ul>
</li>
</ul>
-</td></tr></table><script type="text/javascript"> if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
-<a name="Headline_1" id="Headline_1"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Headline 1">edit</a>]</span> <span class="mw-headline"> Headline 1 </span></h2>
-<a name="Subheadline_1" id="Subheadline_1"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Subheadline 1">edit</a>]</span> <span class="mw-headline"> Subheadline 1 </span></h3>
-<a name="Skipping_a_level" id="Skipping_a_level"></a><h5><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: Skipping a level">edit</a>]</span> <span class="mw-headline"> Skipping a level </span></h5>
-<a name="Skipping_a_level_2" id="Skipping_a_level_2"></a><h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Skipping a level">edit</a>]</span> <span class="mw-headline"> Skipping a level </span></h6>
-<a name="Headline_2" id="Headline_2"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: Headline 2">edit</a>]</span> <span class="mw-headline"> Headline 2 </span></h2>
+</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Headline 1">edit</a>]</span> <span class="mw-headline" id="Headline_1"> Headline 1 </span></h2>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Subheadline 1">edit</a>]</span> <span class="mw-headline" id="Subheadline_1"> Subheadline 1 </span></h3>
+<h5><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: Skipping a level">edit</a>]</span> <span class="mw-headline" id="Skipping_a_level"> Skipping a level </span></h5>
+<h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Skipping a level">edit</a>]</span> <span class="mw-headline" id="Skipping_a_level_2"> Skipping a level </span></h6>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: Headline 2">edit</a>]</span> <span class="mw-headline" id="Headline_2"> Headline 2 </span></h2>
<p>Some text
</p>
-<a name="Another_headline" id="Another_headline"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Another headline">edit</a>]</span> <span class="mw-headline">Another headline</span></h3>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Another headline">edit</a>]</span> <span class="mw-headline" id="Another_headline">Another headline</span></h3>
!! end
@@ -3594,23 +3675,23 @@ Handling of sections up to level 6 and beyond
========= Level 9 Heading=========
========== Level 10 Heading==========
!! result
-<table id="toc" class="toc" summary="Contents"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
<ul>
-<li class="toclevel-1"><a href="#Level_1_Heading"><span class="tocnumber">1</span> <span class="toctext">Level 1 Heading</span></a>
+<li class="toclevel-1 tocsection-1"><a href="#Level_1_Heading"><span class="tocnumber">1</span> <span class="toctext">Level 1 Heading</span></a>
<ul>
-<li class="toclevel-2"><a href="#Level_2_Heading"><span class="tocnumber">1.1</span> <span class="toctext">Level 2 Heading</span></a>
+<li class="toclevel-2 tocsection-2"><a href="#Level_2_Heading"><span class="tocnumber">1.1</span> <span class="toctext">Level 2 Heading</span></a>
<ul>
-<li class="toclevel-3"><a href="#Level_3_Heading"><span class="tocnumber">1.1.1</span> <span class="toctext">Level 3 Heading</span></a>
+<li class="toclevel-3 tocsection-3"><a href="#Level_3_Heading"><span class="tocnumber">1.1.1</span> <span class="toctext">Level 3 Heading</span></a>
<ul>
-<li class="toclevel-4"><a href="#Level_4_Heading"><span class="tocnumber">1.1.1.1</span> <span class="toctext">Level 4 Heading</span></a>
+<li class="toclevel-4 tocsection-4"><a href="#Level_4_Heading"><span class="tocnumber">1.1.1.1</span> <span class="toctext">Level 4 Heading</span></a>
<ul>
-<li class="toclevel-5"><a href="#Level_5_Heading"><span class="tocnumber">1.1.1.1.1</span> <span class="toctext">Level 5 Heading</span></a>
+<li class="toclevel-5 tocsection-5"><a href="#Level_5_Heading"><span class="tocnumber">1.1.1.1.1</span> <span class="toctext">Level 5 Heading</span></a>
<ul>
-<li class="toclevel-6"><a href="#Level_6_Heading"><span class="tocnumber">1.1.1.1.1.1</span> <span class="toctext">Level 6 Heading</span></a></li>
-<li class="toclevel-6"><a href="#.3D_Level_7_Heading.3D"><span class="tocnumber">1.1.1.1.1.2</span> <span class="toctext">= Level 7 Heading=</span></a></li>
-<li class="toclevel-6"><a href="#.3D.3D_Level_8_Heading.3D.3D"><span class="tocnumber">1.1.1.1.1.3</span> <span class="toctext">== Level 8 Heading==</span></a></li>
-<li class="toclevel-6"><a href="#.3D.3D.3D_Level_9_Heading.3D.3D.3D"><span class="tocnumber">1.1.1.1.1.4</span> <span class="toctext">=== Level 9 Heading===</span></a></li>
-<li class="toclevel-6"><a href="#.3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D"><span class="tocnumber">1.1.1.1.1.5</span> <span class="toctext">==== Level 10 Heading====</span></a></li>
+<li class="toclevel-6 tocsection-6"><a href="#Level_6_Heading"><span class="tocnumber">1.1.1.1.1.1</span> <span class="toctext">Level 6 Heading</span></a></li>
+<li class="toclevel-6 tocsection-7"><a href="#.3D_Level_7_Heading.3D"><span class="tocnumber">1.1.1.1.1.2</span> <span class="toctext">= Level 7 Heading=</span></a></li>
+<li class="toclevel-6 tocsection-8"><a href="#.3D.3D_Level_8_Heading.3D.3D"><span class="tocnumber">1.1.1.1.1.3</span> <span class="toctext">== Level 8 Heading==</span></a></li>
+<li class="toclevel-6 tocsection-9"><a href="#.3D.3D.3D_Level_9_Heading.3D.3D.3D"><span class="tocnumber">1.1.1.1.1.4</span> <span class="toctext">=== Level 9 Heading===</span></a></li>
+<li class="toclevel-6 tocsection-10"><a href="#.3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D"><span class="tocnumber">1.1.1.1.1.5</span> <span class="toctext">==== Level 10 Heading====</span></a></li>
</ul>
</li>
</ul>
@@ -3622,17 +3703,17 @@ Handling of sections up to level 6 and beyond
</ul>
</li>
</ul>
-</td></tr></table><script type="text/javascript"> if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
-<a name="Level_1_Heading" id="Level_1_Heading"></a><h1><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Level 1 Heading">edit</a>]</span> <span class="mw-headline"> Level 1 Heading</span></h1>
-<a name="Level_2_Heading" id="Level_2_Heading"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Level 2 Heading">edit</a>]</span> <span class="mw-headline"> Level 2 Heading</span></h2>
-<a name="Level_3_Heading" id="Level_3_Heading"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: Level 3 Heading">edit</a>]</span> <span class="mw-headline"> Level 3 Heading</span></h3>
-<a name="Level_4_Heading" id="Level_4_Heading"></a><h4><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Level 4 Heading">edit</a>]</span> <span class="mw-headline"> Level 4 Heading</span></h4>
-<a name="Level_5_Heading" id="Level_5_Heading"></a><h5><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: Level 5 Heading">edit</a>]</span> <span class="mw-headline"> Level 5 Heading</span></h5>
-<a name="Level_6_Heading" id="Level_6_Heading"></a><h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Level 6 Heading">edit</a>]</span> <span class="mw-headline"> Level 6 Heading</span></h6>
-<a name=".3D_Level_7_Heading.3D" id=".3D_Level_7_Heading.3D"></a><h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=7" title="Edit section: = Level 7 Heading=">edit</a>]</span> <span class="mw-headline">= Level 7 Heading=</span></h6>
-<a name=".3D.3D_Level_8_Heading.3D.3D" id=".3D.3D_Level_8_Heading.3D.3D"></a><h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=8" title="Edit section: == Level 8 Heading==">edit</a>]</span> <span class="mw-headline">== Level 8 Heading==</span></h6>
-<a name=".3D.3D.3D_Level_9_Heading.3D.3D.3D" id=".3D.3D.3D_Level_9_Heading.3D.3D.3D"></a><h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=9" title="Edit section: === Level 9 Heading===">edit</a>]</span> <span class="mw-headline">=== Level 9 Heading===</span></h6>
-<a name=".3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D" id=".3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D"></a><h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=10" title="Edit section: ==== Level 10 Heading====">edit</a>]</span> <span class="mw-headline">==== Level 10 Heading====</span></h6>
+</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
+<h1><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Level 1 Heading">edit</a>]</span> <span class="mw-headline" id="Level_1_Heading"> Level 1 Heading</span></h1>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Level 2 Heading">edit</a>]</span> <span class="mw-headline" id="Level_2_Heading"> Level 2 Heading</span></h2>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: Level 3 Heading">edit</a>]</span> <span class="mw-headline" id="Level_3_Heading"> Level 3 Heading</span></h3>
+<h4><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Level 4 Heading">edit</a>]</span> <span class="mw-headline" id="Level_4_Heading"> Level 4 Heading</span></h4>
+<h5><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: Level 5 Heading">edit</a>]</span> <span class="mw-headline" id="Level_5_Heading"> Level 5 Heading</span></h5>
+<h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Level 6 Heading">edit</a>]</span> <span class="mw-headline" id="Level_6_Heading"> Level 6 Heading</span></h6>
+<h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=7" title="Edit section: = Level 7 Heading=">edit</a>]</span> <span class="mw-headline" id=".3D_Level_7_Heading.3D">= Level 7 Heading=</span></h6>
+<h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=8" title="Edit section: == Level 8 Heading==">edit</a>]</span> <span class="mw-headline" id=".3D.3D_Level_8_Heading.3D.3D">== Level 8 Heading==</span></h6>
+<h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=9" title="Edit section: === Level 9 Heading===">edit</a>]</span> <span class="mw-headline" id=".3D.3D.3D_Level_9_Heading.3D.3D.3D">=== Level 9 Heading===</span></h6>
+<h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=10" title="Edit section: ==== Level 10 Heading====">edit</a>]</span> <span class="mw-headline" id=".3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D">==== Level 10 Heading====</span></h6>
!! end
@@ -3646,31 +3727,31 @@ TOC regression (bug 9764)
== title 2 ==
=== title 2.1 ===
!! result
-<table id="toc" class="toc" summary="Contents"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
<ul>
-<li class="toclevel-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a>
+<li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a>
<ul>
-<li class="toclevel-2"><a href="#title_1.1"><span class="tocnumber">1.1</span> <span class="toctext">title 1.1</span></a>
+<li class="toclevel-2 tocsection-2"><a href="#title_1.1"><span class="tocnumber">1.1</span> <span class="toctext">title 1.1</span></a>
<ul>
-<li class="toclevel-3"><a href="#title_1.1.1"><span class="tocnumber">1.1.1</span> <span class="toctext">title 1.1.1</span></a></li>
+<li class="toclevel-3 tocsection-3"><a href="#title_1.1.1"><span class="tocnumber">1.1.1</span> <span class="toctext">title 1.1.1</span></a></li>
</ul>
</li>
-<li class="toclevel-2"><a href="#title_1.2"><span class="tocnumber">1.2</span> <span class="toctext">title 1.2</span></a></li>
+<li class="toclevel-2 tocsection-4"><a href="#title_1.2"><span class="tocnumber">1.2</span> <span class="toctext">title 1.2</span></a></li>
</ul>
</li>
-<li class="toclevel-1"><a href="#title_2"><span class="tocnumber">2</span> <span class="toctext">title 2</span></a>
+<li class="toclevel-1 tocsection-5"><a href="#title_2"><span class="tocnumber">2</span> <span class="toctext">title 2</span></a>
<ul>
-<li class="toclevel-2"><a href="#title_2.1"><span class="tocnumber">2.1</span> <span class="toctext">title 2.1</span></a></li>
+<li class="toclevel-2 tocsection-6"><a href="#title_2.1"><span class="tocnumber">2.1</span> <span class="toctext">title 2.1</span></a></li>
</ul>
</li>
</ul>
-</td></tr></table><script type="text/javascript"> if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
-<a name="title_1" id="title_1"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline"> title 1 </span></h2>
-<a name="title_1.1" id="title_1.1"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline"> title 1.1 </span></h3>
-<a name="title_1.1.1" id="title_1.1.1"></a><h4><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: title 1.1.1">edit</a>]</span> <span class="mw-headline"> title 1.1.1 </span></h4>
-<a name="title_1.2" id="title_1.2"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: title 1.2">edit</a>]</span> <span class="mw-headline"> title 1.2 </span></h3>
-<a name="title_2" id="title_2"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline"> title 2 </span></h2>
-<a name="title_2.1" id="title_2.1"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: title 2.1">edit</a>]</span> <span class="mw-headline"> title 2.1 </span></h3>
+</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline" id="title_1"> title 1 </span></h2>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1"> title 1.1 </span></h3>
+<h4><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: title 1.1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1.1"> title 1.1.1 </span></h4>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: title 1.2">edit</a>]</span> <span class="mw-headline" id="title_1.2"> title 1.2 </span></h3>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline" id="title_2"> title 2 </span></h2>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: title 2.1">edit</a>]</span> <span class="mw-headline" id="title_2.1"> title 2.1 </span></h3>
!! end
@@ -3686,27 +3767,27 @@ wgMaxTocLevel=3
== title 2 ==
=== title 2.1 ===
!! result
-<table id="toc" class="toc" summary="Contents"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
<ul>
-<li class="toclevel-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a>
+<li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a>
<ul>
-<li class="toclevel-2"><a href="#title_1.1"><span class="tocnumber">1.1</span> <span class="toctext">title 1.1</span></a></li>
-<li class="toclevel-2"><a href="#title_1.2"><span class="tocnumber">1.2</span> <span class="toctext">title 1.2</span></a></li>
+<li class="toclevel-2 tocsection-2"><a href="#title_1.1"><span class="tocnumber">1.1</span> <span class="toctext">title 1.1</span></a></li>
+<li class="toclevel-2 tocsection-4"><a href="#title_1.2"><span class="tocnumber">1.2</span> <span class="toctext">title 1.2</span></a></li>
</ul>
</li>
-<li class="toclevel-1"><a href="#title_2"><span class="tocnumber">2</span> <span class="toctext">title 2</span></a>
+<li class="toclevel-1 tocsection-5"><a href="#title_2"><span class="tocnumber">2</span> <span class="toctext">title 2</span></a>
<ul>
-<li class="toclevel-2"><a href="#title_2.1"><span class="tocnumber">2.1</span> <span class="toctext">title 2.1</span></a></li>
+<li class="toclevel-2 tocsection-6"><a href="#title_2.1"><span class="tocnumber">2.1</span> <span class="toctext">title 2.1</span></a></li>
</ul>
</li>
</ul>
-</td></tr></table><script type="text/javascript"> if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
-<a name="title_1" id="title_1"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline"> title 1 </span></h2>
-<a name="title_1.1" id="title_1.1"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline"> title 1.1 </span></h3>
-<a name="title_1.1.1" id="title_1.1.1"></a><h4><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: title 1.1.1">edit</a>]</span> <span class="mw-headline"> title 1.1.1 </span></h4>
-<a name="title_1.2" id="title_1.2"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: title 1.2">edit</a>]</span> <span class="mw-headline"> title 1.2 </span></h3>
-<a name="title_2" id="title_2"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline"> title 2 </span></h2>
-<a name="title_2.1" id="title_2.1"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: title 2.1">edit</a>]</span> <span class="mw-headline"> title 2.1 </span></h3>
+</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline" id="title_1"> title 1 </span></h2>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1"> title 1.1 </span></h3>
+<h4><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: title 1.1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1.1"> title 1.1.1 </span></h4>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: title 1.2">edit</a>]</span> <span class="mw-headline" id="title_1.2"> title 1.2 </span></h3>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline" id="title_2"> title 2 </span></h2>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: title 2.1">edit</a>]</span> <span class="mw-headline" id="title_2.1"> title 2.1 </span></h3>
!! end
@@ -3716,8 +3797,8 @@ Resolving duplicate section names
== Foo bar ==
== Foo bar ==
!! result
-<a name="Foo_bar" id="Foo_bar"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline"> Foo bar </span></h2>
-<a name="Foo_bar_2" id="Foo_bar_2"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline"> Foo bar </span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline" id="Foo_bar"> Foo bar </span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline" id="Foo_bar_2"> Foo bar </span></h2>
!! end
@@ -3727,8 +3808,8 @@ Resolving duplicate section names with differing case (bug 10721)
== Foo bar ==
== Foo Bar ==
!! result
-<a name="Foo_bar" id="Foo_bar"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline"> Foo bar </span></h2>
-<a name="Foo_Bar_2" id="Foo_Bar_2"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Foo Bar">edit</a>]</span> <span class="mw-headline"> Foo Bar </span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline" id="Foo_bar"> Foo bar </span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Foo Bar">edit</a>]</span> <span class="mw-headline" id="Foo_Bar_2"> Foo Bar </span></h2>
!! end
@@ -3747,10 +3828,10 @@ __NOTOC__
{{sections}}
==Section 4==
!! result
-<a name="Section_0" id="Section_0"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Section 0">edit</a>]</span> <span class="mw-headline">Section 0</span></h2>
-<a name="Section_1" id="Section_1"></a><h3><span class="editsection">[<a href="/index.php?title=Template:Sections&amp;action=edit&amp;section=T-1" title="Template:Sections">edit</a>]</span> <span class="mw-headline">Section 1</span></h3>
-<a name="Section_2" id="Section_2"></a><h2><span class="editsection">[<a href="/index.php?title=Template:Sections&amp;action=edit&amp;section=T-2" title="Template:Sections">edit</a>]</span> <span class="mw-headline">Section 2</span></h2>
-<a name="Section_4" id="Section_4"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Section 4">edit</a>]</span> <span class="mw-headline">Section 4</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Section 0">edit</a>]</span> <span class="mw-headline" id="Section_0">Section 0</span></h2>
+<h3><span class="editsection">[<a href="/index.php?title=Template:Sections&amp;action=edit&amp;section=T-1" title="Template:Sections">edit</a>]</span> <span class="mw-headline" id="Section_1">Section 1</span></h3>
+<h2><span class="editsection">[<a href="/index.php?title=Template:Sections&amp;action=edit&amp;section=T-2" title="Template:Sections">edit</a>]</span> <span class="mw-headline" id="Section_2">Section 2</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: Section 4">edit</a>]</span> <span class="mw-headline" id="Section_4">Section 4</span></h2>
!! end
@@ -3761,8 +3842,8 @@ __NOEDITSECTION__
==Section 1==
==Section 2==
!! result
-<a name="Section_1" id="Section_1"></a><h2> <span class="mw-headline">Section 1</span></h2>
-<a name="Section_2" id="Section_2"></a><h2> <span class="mw-headline">Section 2</span></h2>
+<h2> <span class="mw-headline" id="Section_1">Section 1</span></h2>
+<h2> <span class="mw-headline" id="Section_2">Section 2</span></h2>
!! end
@@ -3771,7 +3852,7 @@ Link inside a section heading
!! input
==Section with a [[Main Page|link]] in it==
!! result
-<a name="Section_with_a_link_in_it" id="Section_with_a_link_in_it"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Section with a link in it">edit</a>]</span> <span class="mw-headline">Section with a <a href="/wiki/Main_Page" title="Main Page">link</a> in it</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Section with a link in it">edit</a>]</span> <span class="mw-headline" id="Section_with_a_link_in_it">Section with a <a href="/wiki/Main_Page" title="Main Page">link</a> in it</span></h2>
!! end
@@ -3783,19 +3864,19 @@ __TOC__
=== title 1.1 ===
== title 2 ==
!! result
-<table id="toc" class="toc" summary="Contents"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
<ul>
-<li class="toclevel-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a>
+<li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a>
<ul>
-<li class="toclevel-2"><a href="#title_1.1"><span class="tocnumber">1.1</span> <span class="toctext">title 1.1</span></a></li>
+<li class="toclevel-2 tocsection-2"><a href="#title_1.1"><span class="tocnumber">1.1</span> <span class="toctext">title 1.1</span></a></li>
</ul>
</li>
-<li class="toclevel-1"><a href="#title_2"><span class="tocnumber">2</span> <span class="toctext">title 2</span></a></li>
+<li class="toclevel-1 tocsection-3"><a href="#title_2"><span class="tocnumber">2</span> <span class="toctext">title 2</span></a></li>
</ul>
-</td></tr></table><script type="text/javascript"> if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
-<a name="title_1" id="title_1"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline"> title 1 </span></h2>
-<a name="title_1.1" id="title_1.1"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline"> title 1.1 </span></h3>
-<a name="title_2" id="title_2"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline"> title 2 </span></h2>
+</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline" id="title_1"> title 1 </span></h2>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1"> title 1.1 </span></h3>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline" id="title_2"> title 2 </span></h2>
!! end
@@ -3804,7 +3885,7 @@ BUG 1219 URL next to image (good)
!! input
http://example.com [[Image:foobar.jpg]]
!! result
-<p><a href="http://example.com" class="external free" title="http://example.com" rel="nofollow">http://example.com</a> <a href="/wiki/File:Foobar.jpg" class="image" title="Image:foobar.jpg"><img alt="Image:foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a> <a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!!end
@@ -3813,7 +3894,7 @@ BUG 1219 URL next to image (broken)
!! input
http://example.com[[Image:foobar.jpg]]
!! result
-<p><a href="http://example.com" class="external free" title="http://example.com" rel="nofollow">http://example.com</a><a href="/wiki/File:Foobar.jpg" class="image" title="Image:foobar.jpg"><img alt="Image:foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a>
+<p><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>
</p>
!!end
@@ -3822,7 +3903,7 @@ Bug 1186 news: in the middle of text
!! input
http://en.wikinews.org/wiki/Wikinews:Workplace
!! result
-<p><a href="http://en.wikinews.org/wiki/Wikinews:Workplace" class="external free" title="http://en.wikinews.org/wiki/Wikinews:Workplace" rel="nofollow">http://en.wikinews.org/wiki/Wikinews:Workplace</a>
+<p><a href="http://en.wikinews.org/wiki/Wikinews:Workplace" class="external free" rel="nofollow">http://en.wikinews.org/wiki/Wikinews:Workplace</a>
</p>
!!end
@@ -4679,7 +4760,7 @@ Sanitizer: Escaping of spaces, multibyte characters, colons & other stuff in id=
!! input
<span id="æ: v">byte</span>[[#æ: v|backlink]]
!! result
-<p><span id=".C3.A6:_v">byte</span><a href="#.C3.A6:_v" title="">backlink</a>
+<p><span id=".C3.A6:_v">byte</span><a href="#.C3.A6:_v">backlink</a>
</p>
!! end
@@ -4784,6 +4865,8 @@ HTML bullet list, closed tags (bug 5497)
!! test
HTML bullet list, unclosed tags (bug 5497)
+!! options
+disabled
!! input
<ul>
<li>One
@@ -4814,6 +4897,8 @@ HTML ordered list, closed tags (bug 5497)
!! test
HTML ordered list, unclosed tags (bug 5497)
+!! options
+disabled
!! input
<ol>
<li>One
@@ -4854,6 +4939,8 @@ HTML nested bullet list, closed tags (bug 5497)
!! test
HTML nested bullet list, open tags (bug 5497)
+!! options
+disabled
!! input
<ul>
<li>One
@@ -4902,6 +4989,8 @@ HTML nested ordered list, closed tags (bug 5497)
!! test
HTML nested ordered list, open tags (bug 5497)
+!! options
+disabled
!! input
<ol>
<li>One
@@ -4945,11 +5034,11 @@ ftp://inlineftp
mailto:inline@mail.tld
!! result
-<p><a href="http://first/" class="external autonumber" title="http://first/" rel="nofollow">[1]</a> <a href="http://second" class="external autonumber" title="http://second" rel="nofollow">[2]</a> <a href="ftp://ftp" class="external autonumber" title="ftp://ftp" rel="nofollow">[3]</a>
-</p><p><a href="ftp://inlineftp" class="external free" title="ftp://inlineftp" rel="nofollow">ftp://inlineftp</a>
-</p><p><a href="mailto:enclosed@mail.tld" class="external text" title="mailto:enclosed@mail.tld" rel="nofollow">With target</a>
-</p><p><a href="mailto:enclosed@mail.tld" class="external autonumber" title="mailto:enclosed@mail.tld" rel="nofollow">[4]</a>
-</p><p><a href="mailto:inline@mail.tld" class="external free" title="mailto:inline@mail.tld" rel="nofollow">mailto:inline@mail.tld</a>
+<p><a href="http://first/" class="external autonumber" rel="nofollow">[1]</a> <a href="http://second" class="external autonumber" rel="nofollow">[2]</a> <a href="ftp://ftp" class="external autonumber" rel="nofollow">[3]</a>
+</p><p><a href="ftp://inlineftp" class="external free" rel="nofollow">ftp://inlineftp</a>
+</p><p><a href="mailto:enclosed@mail.tld" class="external text" rel="nofollow">With target</a>
+</p><p><a href="mailto:enclosed@mail.tld" class="external autonumber" rel="nofollow">[4]</a>
+</p><p><a href="mailto:inline@mail.tld" class="external free" rel="nofollow">mailto:inline@mail.tld</a>
</p>
!! end
@@ -4980,12 +5069,12 @@ Fuzz testing: Parser14
== onmouseover= ==
http://__TOC__
!! result
-<a name="onmouseover.3D" id="onmouseover.3D"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: onmouseover=">edit</a>]</span> <span class="mw-headline"> onmouseover= </span></h2>
-http://<table id="toc" class="toc" summary="Contents"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: onmouseover=">edit</a>]</span> <span class="mw-headline" id="onmouseover.3D"> onmouseover= </span></h2>
+http://<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
<ul>
-<li class="toclevel-1"><a href="#onmouseover.3D"><span class="tocnumber">1</span> <span class="toctext">onmouseover=</span></a></li>
+<li class="toclevel-1 tocsection-1"><a href="#onmouseover.3D"><span class="tocnumber">1</span> <span class="toctext">onmouseover=</span></a></li>
</ul>
-</td></tr></table><script type="text/javascript"> if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
+</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
!! end
@@ -4995,7 +5084,7 @@ Fuzz testing: Parser14-table
==a==
{| STYLE=__TOC__
!! result
-<a name="a" id="a"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: a">edit</a>]</span> <span class="mw-headline">a</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: a">edit</a>]</span> <span class="mw-headline" id="a">a</span></h2>
<table style="&#95;_TOC&#95;_">
<tr><td></td></tr>
</table>
@@ -5029,7 +5118,7 @@ Fuzz testing: Parser21
!! result
<table>
<tr>
-<th> <a href="irc://{{ftp://a" class="external free" title="irc://{{ftp://a" rel="nofollow">irc://{{ftp://a</a>" onmouseover="alert('hello world');"
+<th> <a href="irc://{{ftp://a" class="external free" rel="nofollow">irc://{{ftp://a</a>" onmouseover="alert('hello world');"
</th><td>
</td>
</tr>
@@ -5044,7 +5133,7 @@ http://===r:::https://b
{|
!!result
-<p><a href="http://===r:::https://b" class="external free" title="http://===r:::https://b" rel="nofollow">http://===r:::https://b</a>
+<p><a href="http://===r:::https://b" class="external free" rel="nofollow">http://===r:::https://b</a>
</p>
<table>
<tr><td></td></tr>
@@ -5108,7 +5197,7 @@ Fuzz testing: URL adjacent extension (with space, clean)
!! input
http://example.com <nowiki>junk</nowiki>
!! result
-<p><a href="http://example.com" class="external free" title="http://example.com" rel="nofollow">http://example.com</a> junk
+<p><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a> junk
</p>
!!end
@@ -5118,7 +5207,7 @@ Fuzz testing: URL adjacent extension (no space, dirty; nowiki)
!! input
http://example.com<nowiki>junk</nowiki>
!! result
-<p><a href="http://example.com" class="external free" title="http://example.com" rel="nofollow">http://example.com</a>junk
+<p><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a>junk
</p>
!!end
@@ -5128,7 +5217,7 @@ Fuzz testing: URL adjacent extension (no space, dirty; pre)
!! input
http://example.com<pre>junk</pre>
!! result
-<a href="http://example.com" class="external free" title="http://example.com" rel="nofollow">http://example.com</a><pre>junk</pre>
+<a href="http://example.com" class="external free" rel="nofollow">http://example.com</a><pre>junk</pre>
!!end
@@ -5218,6 +5307,8 @@ New wiki paragraph
!! test
Inline HTML vs wiki block nesting
+!! options
+disabled
!! input
<b>Bold paragraph
@@ -6136,7 +6227,7 @@ Handling of &#x0A; in URLs
!! input
**irc://&#x0A;a
!! result
-<ul><li><ul><li><a href="irc://%0Aa" class="external free" title="irc://%0Aa" rel="nofollow">irc://%0Aa</a>
+<ul><li><ul><li><a href="irc://%0Aa" class="external free" rel="nofollow">irc://%0Aa</a>
</li></ul>
</li></ul>
@@ -6246,7 +6337,7 @@ image4 |300px| centre
<td><div class="gallerybox" style="width: 155px;">
<div style="height: 152px;">Image5.svg</div>
<div class="gallerytext">
-<p><a href="http://///////" class="external free" title="http://///////" rel="nofollow">http://///////</a>
+<p><a href="http://///////" class="external free" rel="nofollow">http://///////</a>
</p>
</div>
</div></td>
@@ -6284,7 +6375,7 @@ ISBN code coverage
!! input
ISBN 978-0-1234-56&#x20;789
!! result
-<p><a href="/wiki/Special:BookSources/9780123456" class="internal">ISBN 978-0-1234-56</a>&#x20;789
+<p><a href="/wiki/Special:BookSources/9780123456" class="internal mw-magiclink-isbn">ISBN 978-0-1234-56</a>&#x20;789
</p>
!! end
@@ -6302,7 +6393,7 @@ Double ISBN
!! input
ISBN ISBN 1234567890
!! result
-<p>ISBN <a href="/wiki/Special:BookSources/1234567890" class="internal">ISBN 1234567890</a>
+<p>ISBN <a href="/wiki/Special:BookSources/1234567890" class="internal mw-magiclink-isbn">ISBN 1234567890</a>
</p>
!! end
@@ -6311,7 +6402,7 @@ Double RFC
!! input
RFC RFC 1234
!! result
-<p>RFC <a href="http://tools.ietf.org/html/rfc1234" class="external" title="http://tools.ietf.org/html/rfc1234">RFC 1234</a>
+<p>RFC <a href="http://tools.ietf.org/html/rfc1234" class="external mw-magiclink-rfc">RFC 1234</a>
</p>
!! end
@@ -6329,7 +6420,7 @@ RFC code coverage
!! input
RFC 983&#x20;987
!! result
-<p><a href="http://tools.ietf.org/html/rfc983" class="external" title="http://tools.ietf.org/html/rfc983">RFC 983</a>&#x20;987
+<p><a href="http://tools.ietf.org/html/rfc983" class="external mw-magiclink-rfc">RFC 983</a>&#x20;987
</p>
!! end
@@ -6338,7 +6429,7 @@ Centre-aligned image
!! input
[[Image:foobar.jpg|centre]]
!! result
-<div class="center"><div class="floatnone"><a href="/wiki/File:Foobar.jpg" class="image" title="Foobar.jpg"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a></div></div>
+<div class="center"><div class="floatnone"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div></div>
!!end
@@ -6347,7 +6438,7 @@ None-aligned image
!! input
[[Image:foobar.jpg|none]]
!! result
-<div class="floatnone"><a href="/wiki/File:Foobar.jpg" class="image" title="Foobar.jpg"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" border="0" /></a></div>
+<div class="floatnone"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div>
!!end
@@ -6356,7 +6447,7 @@ Width + Height sized image (using px) (height is ignored)
!! input
[[Image:foobar.jpg|640x480px]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="Foobar.jpg"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a>
</p>
!!end
@@ -6365,7 +6456,7 @@ Width-sized image (using px, no following whitespace)
!! input
[[Image:foobar.jpg|640px]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="Foobar.jpg"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a>
</p>
!!end
@@ -6374,7 +6465,7 @@ Width-sized image (using px, with following whitespace - test regression from r3
!! input
[[Image:foobar.jpg|640px ]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="Foobar.jpg"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a>
</p>
!!end
@@ -6383,7 +6474,7 @@ Width-sized image (using px, with preceding whitespace - test regression from r3
!! input
[[Image:foobar.jpg| 640px]]
!! result
-<p><a href="/wiki/File:Foobar.jpg" class="image" title="Foobar.jpg"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" border="0" /></a>
+<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a>
</p>
!!end
@@ -6401,6 +6492,8 @@ Another italics / bold test
# ...<dd> </dt></dl> </dd...
!! test
dt/dd/dl test
+!! options
+disabled
!! input
:;;;::
!! result
@@ -6418,10 +6511,12 @@ dt/dd/dl test
# Images with the "|" character in external URLs in comment tags; Eats half the comment, leaves unmatched "</a>" tag.
!! test
Images with the "|" character in the comment
+!! options
+disabled
!! input
[[image:Foobar.jpg|thumb|An [http://test/?param1=|left|&param2=|x external] URL]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image" title="An external URL"><img alt="An external URL" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>An <a href="http://test/?param1=|left|&amp;param2=|x" class="external text" title="http://test/?param1=|left|&amp;param2=|x" rel="nofollow">external</a> URL</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="An external URL" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>An <a href="http://test/?param1=|left|&amp;param2=|x" class="external text" rel="nofollow">external</a> URL</div></div></div>
!!end
@@ -6578,7 +6673,7 @@ Inclusion of !userCanEdit() content
!! input
{{MediaWiki:Fake}}
!! result
-<a name="header" id="header"></a><h2><span class="editsection">[<a href="/index.php?title=MediaWiki:Fake&amp;action=edit&amp;section=T-1" title="MediaWiki:Fake">edit</a>]</span> <span class="mw-headline">header</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=MediaWiki:Fake&amp;action=edit&amp;section=T-1" title="MediaWiki:Fake">edit</a>]</span> <span class="mw-headline" id="header">header</span></h2>
!! end
@@ -6593,28 +6688,28 @@ Out-of-order TOC heading levels
=====5=====
==2==
!! result
-<table id="toc" class="toc" summary="Contents"><tr><td><div id="toctitle"><h2>Contents</h2></div>
+<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div>
<ul>
-<li class="toclevel-1"><a href="#2"><span class="tocnumber">1</span> <span class="toctext">2</span></a>
+<li class="toclevel-1 tocsection-1"><a href="#2"><span class="tocnumber">1</span> <span class="toctext">2</span></a>
<ul>
-<li class="toclevel-2"><a href="#6"><span class="tocnumber">1.1</span> <span class="toctext">6</span></a></li>
-<li class="toclevel-2"><a href="#3"><span class="tocnumber">1.2</span> <span class="toctext">3</span></a></li>
+<li class="toclevel-2 tocsection-2"><a href="#6"><span class="tocnumber">1.1</span> <span class="toctext">6</span></a></li>
+<li class="toclevel-2 tocsection-3"><a href="#3"><span class="tocnumber">1.2</span> <span class="toctext">3</span></a></li>
</ul>
</li>
-<li class="toclevel-1"><a href="#1"><span class="tocnumber">2</span> <span class="toctext">1</span></a>
+<li class="toclevel-1 tocsection-4"><a href="#1"><span class="tocnumber">2</span> <span class="toctext">1</span></a>
<ul>
-<li class="toclevel-2"><a href="#5"><span class="tocnumber">2.1</span> <span class="toctext">5</span></a></li>
-<li class="toclevel-2"><a href="#2_2"><span class="tocnumber">2.2</span> <span class="toctext">2</span></a></li>
+<li class="toclevel-2 tocsection-5"><a href="#5"><span class="tocnumber">2.1</span> <span class="toctext">5</span></a></li>
+<li class="toclevel-2 tocsection-6"><a href="#2_2"><span class="tocnumber">2.2</span> <span class="toctext">2</span></a></li>
</ul>
</li>
</ul>
-</td></tr></table><script type="text/javascript"> if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
-<a name="2" id="2"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: 2">edit</a>]</span> <span class="mw-headline">2</span></h2>
-<a name="6" id="6"></a><h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: 6">edit</a>]</span> <span class="mw-headline">6</span></h6>
-<a name="3" id="3"></a><h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: 3">edit</a>]</span> <span class="mw-headline">3</span></h3>
-<a name="1" id="1"></a><h1><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: 1">edit</a>]</span> <span class="mw-headline">1</span></h1>
-<a name="5" id="5"></a><h5><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: 5">edit</a>]</span> <span class="mw-headline">5</span></h5>
-<a name="2_2" id="2_2"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: 2">edit</a>]</span> <span class="mw-headline">2</span></h2>
+</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: 2">edit</a>]</span> <span class="mw-headline" id="2">2</span></h2>
+<h6><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: 6">edit</a>]</span> <span class="mw-headline" id="6">6</span></h6>
+<h3><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: 3">edit</a>]</span> <span class="mw-headline" id="3">3</span></h3>
+<h1><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: 1">edit</a>]</span> <span class="mw-headline" id="1">1</span></h1>
+<h5><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: 5">edit</a>]</span> <span class="mw-headline" id="5">5</span></h5>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: 2">edit</a>]</span> <span class="mw-headline" id="2_2">2</span></h2>
!! end
@@ -6634,7 +6729,7 @@ ISBN with space-delimited number
!! input
ISBN 92 9017 032 8
!! result
-<p><a href="/wiki/Special:BookSources/9290170328" class="internal">ISBN 92 9017 032 8</a>
+<p><a href="/wiki/Special:BookSources/9290170328" class="internal mw-magiclink-isbn">ISBN 92 9017 032 8</a>
</p>
!! end
@@ -6659,7 +6754,7 @@ ISBN 1234567890
ISBN 12345678901
!! result
<p>ISBN 123456789
-</p><p><a href="/wiki/Special:BookSources/1234567890" class="internal">ISBN 1234567890</a>
+</p><p><a href="/wiki/Special:BookSources/1234567890" class="internal mw-magiclink-isbn">ISBN 1234567890</a>
</p><p>ISBN 12345678901
</p>
!! end
@@ -6672,22 +6767,13 @@ ISBN 1-234-56789-0 - 2006
ISBN 1 234 56789 0 - 2006
!! result
-<p><a href="/wiki/Special:BookSources/1234567890" class="internal">ISBN 1-234-56789-0</a> - 2006
-</p><p><a href="/wiki/Special:BookSources/1234567890" class="internal">ISBN 1 234 56789 0</a> - 2006
+<p><a href="/wiki/Special:BookSources/1234567890" class="internal mw-magiclink-isbn">ISBN 1-234-56789-0</a> - 2006
+</p><p><a href="/wiki/Special:BookSources/1234567890" class="internal mw-magiclink-isbn">ISBN 1 234 56789 0</a> - 2006
</p>
!! end
!! test
-Pages in namespace (Magic word disabled currently)
-!! input
-{{PAGESINNAMESPACE:}}
-!! result
-
-!! end
-
-
-!! test
anchorencode
!! input
{{anchorencode:foo bar©#%n}}
@@ -6799,9 +6885,9 @@ Simple category in language variants
!! options
language=sr cat
!! input
-[[:Category:МедиаWики Усер'с Гуиде]]
+[[Category:МедиаWики Усер'с Гуиде]]
!! result
-<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User&#039;s Guide">MediaWiki User's Guide</a>
+<a href="/wiki/%D0%9A%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%98%D0%B0:MediaWiki_User%27s_Guide" title="Категорија:MediaWiki User's Guide">MediaWiki User's Guide</a>
!! end
@@ -6860,7 +6946,7 @@ language=sr variant=sr-ec
!! input
== -{Naslov}- ==
!! result
-<a name="-.7BNaslov.7D-" id="-.7BNaslov.7D-"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Уреди део: Naslov">уреди</a>]</span> <span class="mw-headline"> Naslov </span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Уреди део: Naslov">уреди</a>]</span> <span class="mw-headline" id="-.7BNaslov.7D-"> Naslov </span></h2>
!! end
@@ -6878,28 +6964,58 @@ language=zh variant=zh-tw
!! test
-Adding explicit session-wise language variant mapping (A flag)
+Explicit session-wise language variant mapping (A flag and - flag)
!! options
language=zh variant=zh-tw
!! input
--{A|zh:China;zh-tw:Taiwan}- is China
+Taiwan is not China.
+But -{A|zh:China;zh-tw:Taiwan}- is China,
+(This-{-|zh:China;zh-tw:Taiwan}- should be stripped!)
+and -{China}- is China.
!! result
-<p>Taiwan is Taiwan
+<p>Taiwan is not China.
+But Taiwan is Taiwan,
+(This should be stripped!)
+and China is China.
</p>
!! end
+!! test
+Explicit session-wise language variant mapping (H flag for hide)
+!! options
+language=zh variant=zh-tw
+!! input
+(This-{H|zh:China;zh-tw:Taiwan}- should be stripped!)
+Taiwan is China.
+!! result
+<p>(This should be stripped!)
+Taiwan is Taiwan.
+</p>
+!! end
!! test
Adding explicit conversion rule for title (T flag)
!! options
-language=zh variant=zh-tw
+language=zh variant=zh-tw showtitle
!! input
Should be stripped-{T|zh:China;zh-tw:Taiwan}-!
!! result
+Taiwan
<p>Should be stripped!
</p>
!! end
+!! test
+Testing that changing the language variant here in the tests actually works
+!! options
+language=zh variant=zh showtitle
+!! input
+Should be stripped-{T|zh:China;zh-tw:Taiwan}-!
+!! result
+China
+<p>Should be stripped!
+</p>
+!! end
!! test
Raw output of variant escape tags (R flag)
@@ -6912,6 +7028,16 @@ Raw: -{R|zh:China;zh-tw:Taiwan}-
</p>
!! end
+!! test
+Nested using of manual convert syntax
+!! options
+language=zh variant=zh-hk
+!! input
+Nested: -{zh-hans:Hi -{zh-cn:China;zh-sg:Singapore;}-;zh-hant:Hello -{zh-tw:Taiwan;zh-hk:H-{ong}- K-{}-ong;}-;}-!
+!! result
+<p>Nested: Hello Hong Kong!
+</p>
+!! end
!! test
Do not convert roman numbers to language variants
@@ -6924,6 +7050,28 @@ Fridrih IV je car.
</p>
!! end
+!! test
+Unclosed language converter markup "-{"
+!! options
+language=sr
+!! input
+-{T|hello
+!! result
+<p>-{T|hello
+</p>
+!! end
+
+!! test
+Don't convert raw rule "-{R|=&gt;}-" to "=>"
+!! options
+language=sr
+!! input
+-{R|=&gt;}-
+!! result
+<p>=&gt;
+</p>
+!!end
+
!!article
Template:Bullet
!!text
@@ -6992,6 +7140,32 @@ Bug 5678: Double-parsed template invocation
!! end
!! test
+Case insensitivity of parser functions for non-ASCII characters (bug 8143)
+!! options
+language=cs
+title=[[Main Page]]
+!! input
+{{PRVNÍVELKÉ:ěščř}}
+{{prvnívelké:ěščř}}
+{{PRVNÍMALÉ:ěščř}}
+{{prvnímalé:ěščř}}
+{{MALÁ:ěščř}}
+{{malá:ěščř}}
+{{VELKÁ:ěščř}}
+{{velká:ěščř}}
+!! result
+<p>Ěščř
+Ěščř
+ěščř
+ěščř
+ěščř
+ěščř
+ĚŠČŘ
+ĚŠČŘ
+</p>
+!! end
+
+!! test
Morwen/13: Unclosed link followed by heading
!! input
[[link
@@ -6999,7 +7173,7 @@ Morwen/13: Unclosed link followed by heading
!! result
<p>[[link
</p>
-<a name="heading" id="heading"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: heading">edit</a>]</span> <span class="mw-headline">heading</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: heading">edit</a>]</span> <span class="mw-headline" id="heading">heading</span></h2>
!! end
@@ -7011,7 +7185,7 @@ HHP2.1: Heuristics for headings in preprocessor parenthetical structures
!! result
<p>{{foo|
</p>
-<a name="heading" id="heading"></a><h1> <span class="mw-headline">heading</span></h1>
+<h1> <span class="mw-headline" id="heading">heading</span></h1>
!! end
@@ -7023,7 +7197,7 @@ HHP2.2: Heuristics for headings in preprocessor parenthetical structures
!! result
<p>{{foo|
</p>
-<a name="heading" id="heading"></a><h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: heading">edit</a>]</span> <span class="mw-headline">heading</span></h2>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: heading">edit</a>]</span> <span class="mw-headline" id="heading">heading</span></h2>
!! end
@@ -7099,6 +7273,8 @@ Line two
# Bug 6200: <blockquote> should behave like <div> with respect to line breaks
!! test
Bug 6200: paragraphs inside blockquotes (no extra line breaks)
+!! options
+disabled
!! input
<blockquote>Line one
@@ -7111,6 +7287,8 @@ Line two</blockquote>
!! test
Bug 6200: paragraphs inside blockquotes (extra line break on open)
+!! options
+disabled
!! input
<blockquote>
Line one
@@ -7126,6 +7304,8 @@ Line two</blockquote>
!! test
Bug 6200: paragraphs inside blockquotes (extra line break on close)
+!! options
+disabled
!! input
<blockquote>Line one
@@ -7141,6 +7321,8 @@ Line two
!! test
Bug 6200: paragraphs inside blockquotes (extra line break on open and close)
+!! options
+disabled
!! input
<blockquote>
Line one
@@ -7235,7 +7417,7 @@ Free external link invading image caption
!! input
[[Image:Foobar.jpg|thumb|http://x|hello]]
!! result
-<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image" title="hello"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" border="0" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>hello</div></div></div>
+<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>hello</div></div></div>
!! end
@@ -7246,7 +7428,7 @@ language=fa
!! input
[http://en.wikipedia.org/]
!! result
-<p><a href="http://en.wikipedia.org/" class="external autonumber" title="http://en.wikipedia.org/" rel="nofollow">[۱]</a>
+<p><a href="http://en.wikipedia.org/" class="external autonumber" rel="nofollow">[۱]</a>
</p>
!! end
@@ -7306,12 +7488,268 @@ wgUseDynamicDates=false
!! result
<p><a href="/index.php?title=2009-03-24&amp;action=edit&amp;redlink=1" class="new" title="2009-03-24 (page does not exist)">2009-03-24</a>
</p>
-!!end
+!! end
+
+!! test
+Spacing of numbers in formatted dates
+!! input
+{{#formatdate:January 15}}
+!! result
+<p><span class="mw-formatted-date" title="01-15">January 15</span>
+</p>
+!! end
+
+!! test
+Spacing of numbers in formatted dates (linked)
+!! config
+wgUseDynamicDates=true
+!! input
+[[January 15]]
+!! result
+<p><span class="mw-formatted-date" title="01-15"><a href="/index.php?title=January_15&amp;action=edit&amp;redlink=1" class="new" title="January 15 (page does not exist)">January 15</a></span>
+</p>
+!! end
#
#
#
+#
+# Edit comments
+#
+
+!! test
+Edit comment with link
+!! options
+comment
+!! input
+I like the [[Main Page]] a lot
+!! result
+I like the <a href="/wiki/Main_Page" title="Main Page">Main Page</a> a lot
+!!end
+
+!! test
+Edit comment with link and link text
+!! options
+comment
+!! input
+I like the [[Main Page|best pages]] a lot
+!! result
+I like the <a href="/wiki/Main_Page" title="Main Page">best pages</a> a lot
+!!end
+
+!! test
+Edit comment with link and link text with suffix
+!! options
+comment
+!! input
+I like the [[Main Page|best page]]s a lot
+!! result
+I like the <a href="/wiki/Main_Page" title="Main Page">best pages</a> a lot
+!!end
+
+!! test
+Edit comment with section link (non-local, eg in history list)
+!! options
+comment title=[[Main Page]]
+!! input
+/* External links */ removed bogus entries
+!! result
+<span class="autocomment"><a href="/wiki/Main_Page#External_links" title="Main Page">→</a>External links: </span> removed bogus entries
+!!end
+
+!! test
+Edit comment with section link (local, eg in diff view)
+!! options
+comment local title=[[Main Page]]
+!! input
+/* External links */ removed bogus entries
+!! result
+<span class="autocomment"><a href="#External_links">→</a>External links: </span> removed bogus entries
+!!end
+
+!! test
+Edit comment with subpage link (bug 14080)
+!! options
+comment
+subpage
+title=[[Subpage test]]
+!! input
+Poked at a [[/subpage]] here...
+!! result
+Poked at a <a href="/wiki/Subpage_test/subpage" title="Subpage test/subpage">/subpage</a> here...
+!!end
+
+!! test
+Edit comment with subpage link and link text (bug 14080)
+!! options
+comment
+subpage
+title=[[Subpage test]]
+!! input
+Poked at a [[/subpage|neat little page]] here...
+!! result
+Poked at a <a href="/wiki/Subpage_test/subpage" title="Subpage test/subpage">neat little page</a> here...
+!!end
+
+!! test
+Edit comment with bogus subpage link in non-subpage NS (bug 14080)
+!! options
+comment
+title=[[Subpage test]]
+!! input
+Poked at a [[/subpage]] here...
+!! result
+Poked at a <a href="/index.php?title=/subpage&amp;action=edit&amp;redlink=1" class="new" title="/subpage (page does not exist)">/subpage</a> here...
+!!end
+
+!! test
+Edit comment with bare anchor link (local, as on diff)
+!! options
+comment
+local
+title=[[Main Page]]
+!!input
+[[#section]]
+!! result
+<a href="#section">#section</a>
+!! end
+
+!! test
+Edit comment with bare anchor link (non-local, as on history)
+!! options
+comment
+title=[[Main Page]]
+!!input
+[[#section]]
+!! result
+<a href="/wiki/Main_Page#section" title="Main Page">#section</a>
+!! end
+
+!!article
+MediaWiki:bad image list
+!!text
+* [[File:Bad.jpg]] except [[Nasty page]]
+!!endarticle
+
+!! test
+Bad images - basic functionality
+!! input
+[[File:Bad.jpg]]
+!! result
+!! end
+
+!! test
+Bad images - bug 16039: text after bad image disappears
+!! input
+Foo bar
+[[File:Bad.jpg]]
+Bar foo
+!! result
+<p>Foo bar
+</p><p>Bar foo
+</p>
+!! end
+
+!! test
+Verify that displaytitle works (bug #22501) no displaytitle
+!! options
+showtitle
+!! config
+wgAllowDisplayTitle=true
+wgRestrictDisplayTitle=false
+!! input
+this is not the the title
+!! result
+Parser test
+<p>this is not the the title
+</p>
+!! end
+
+!! test
+Verify that displaytitle works (bug #22501) RestrictDisplayTitle=false
+!! options
+showtitle
+title=[[Screen]]
+!! config
+wgAllowDisplayTitle=true
+wgRestrictDisplayTitle=false
+!! input
+this is not the the title
+{{DISPLAYTITLE:whatever}}
+!! result
+whatever
+<p>this is not the the title
+</p>
+!! end
+
+!! test
+Verify that displaytitle works (bug #22501) RestrictDisplayTitle=true mismatch
+!! options
+showtitle
+title=[[Screen]]
+!! config
+wgAllowDisplayTitle=true
+wgRestrictDisplayTitle=true
+!! input
+this is not the the title
+{{DISPLAYTITLE:whatever}}
+!! result
+Screen
+<p>this is not the the title
+</p>
+!! end
+
+!! test
+Verify that displaytitle works (bug #22501) RestrictDisplayTitle=true matching
+!! options
+showtitle
+title=[[Screen]]
+!! config
+wgAllowDisplayTitle=true
+wgRestrictDisplayTitle=true
+!! input
+this is not the the title
+{{DISPLAYTITLE:screen}}
+!! result
+screen
+<p>this is not the the title
+</p>
+!! end
+
+!! test
+Verify that displaytitle works (bug #22501) AllowDisplayTitle=false
+!! options
+showtitle
+title=[[Screen]]
+!! config
+wgAllowDisplayTitle=false
+!! input
+this is not the the title
+{{DISPLAYTITLE:screen}}
+!! result
+Screen
+<p>this is not the the title
+<a href="/index.php?title=Template:DISPLAYTITLE:screen&amp;action=edit&amp;redlink=1" class="new" title="Template:DISPLAYTITLE:screen (page does not exist)">Template:DISPLAYTITLE:screen</a>
+</p>
+!! end
+
+!! test
+Verify that displaytitle works (bug #22501) AllowDisplayTitle=false no DISPLAYTITLE
+!! options
+showtitle
+title=[[Screen]]
+!! config
+wgAllowDisplayTitle=false
+!! input
+this is not the the title
+!! result
+Screen
+<p>this is not the the title
+</p>
+!! end
+
+
TODO:
more images
more tables
diff --git a/maintenance/patchSql.php b/maintenance/patchSql.php
index 42380eea..69cb0f56 100644
--- a/maintenance/patchSql.php
+++ b/maintenance/patchSql.php
@@ -3,34 +3,57 @@
* Manually run an SQL patch outside of the general updaters.
* This ensures that the DB options (charset, prefix, engine) are correctly set.
*
- * @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
+ *
* @ingroup Maintenance
*/
-require_once 'commandLine.inc';
-require_once "$IP/maintenance/updaters.inc";
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+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' );
+ }
+
+ public function getDbType() {
+ return Maintenance::DB_ADMIN;
+ }
-if( $args ) {
- foreach( $args as $arg ) {
- $files = array(
- $arg,
- archive( $arg ),
- archive( "patch-$arg.sql" ),
- );
- foreach( $files as $file ) {
- if( file_exists( $file ) ) {
- echo "$file ...\n";
- dbsource( $file );
- continue 2;
+ public function execute() {
+ $dbw = wfGetDB( DB_MASTER );
+ foreach( $this->mArgs as $arg ) {
+ $files = array(
+ $arg,
+ DatabaseBase::patchPath( $arg ),
+ DatabaseBase::patchPath( "patch-$arg.sql" ),
+ );
+ foreach( $files as $file ) {
+ if( file_exists( $file ) ) {
+ $this->output( "$file ...\n" );
+ $dbw->sourceFile( $file );
+ continue 2;
+ }
}
+ $this->error( "Could not find $arg\n" );
}
- echo "Could not find $arg\n";
+ $this->output( "done.\n" );
}
- echo "done.\n";
-} else {
- echo "Run an SQL file into the DB, replacing prefix and charset vars.\n";
- echo "Usage:\n";
- echo " php maintenance/patchSql.php file1.sql file2.sql ...\n";
- echo "\n";
- echo "Paths in maintenance/archive are automatically expanded if a local file isn't found.\n";
}
+
+$maintClass = "PatchSql";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/populateCategory.php b/maintenance/populateCategory.php
index 1dca7b74..bf84bb0a 100644
--- a/maintenance/populateCategory.php
+++ b/maintenance/populateCategory.php
@@ -7,11 +7,16 @@
$optionsWithArgs = array( 'begin', 'max-slave-lag', 'throttle' );
-require_once "commandLine.inc";
-require_once "populateCategory.inc";
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if( isset( $options['help'] ) ) {
- echo <<<TEXT
+
+class PopulateCategory extends Maintenance {
+
+ const REPORTING_INTERVAL = 1000;
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = <<<TEXT
This script will populate the category table, added in MediaWiki 1.13. It will
print out progress indicators every 1000 categories it adds to the table. The
script is perfectly safe to run on large, live wikis, and running it multiple
@@ -25,29 +30,98 @@ added after the software update and so will be populated anyway.
When the script has finished, it will make a note of this in the database, and
will not run again without the --force option.
-
-Usage:
- php populateCategory.php [--max-slave-lag <seconds>] [--begin <name>]
-[--throttle <seconds>] [--force]
-
- --begin: Only do categories whose names are alphabetically after the pro-
-vided name. Default: empty (start from beginning).
- --max-slave-lag: If slave lag exceeds this many seconds, wait until it
-drops before continuing. Default: 10.
- --throttle: Wait this many milliseconds after each category. Default: 0.
- --force: Run regardless of whether the database says it's been run already.
-
TEXT;
- exit( 0 );
-}
+ $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' );
+ }
+
+ public function execute() {
+ $begin = $this->getOption( 'begin', '' );
+ $maxSlaveLag = $this->getOption( 'max-slave-lag', 10 );
+ $throttle = $this->getOption( 'throttle', 0 );
+ $force = $this->getOption( 'force', false );
+ $this->doPopulateCategory( $begin, $maxSlaveLag, $throttle, $force );
+ }
-$defaults = array(
- 'begin' => '',
- 'max-slave-lag' => 10,
- 'throttle' => 0,
- 'force' => false
-);
-$options = array_merge( $defaults, $options );
+ private function doPopulateCategory( $begin, $maxlag, $throttle, $force ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ if( !$force ) {
+ $row = $dbw->selectRow(
+ 'updatelog',
+ '1',
+ array( 'ul_key' => 'populate category' ),
+ __FUNCTION__
+ );
+ if( $row ) {
+ $this->output( "Category table already populated. Use php ".
+ "maintenance/populateCategory.php\n--force from the command line ".
+ "to override.\n" );
+ return true;
+ }
+ }
+
+ $maxlag = intval( $maxlag );
+ $throttle = intval( $throttle );
+ $force = (bool)$force;
+ if( $begin !== '' ) {
+ $where = 'cl_to > '.$dbw->addQuotes( $begin );
+ } else {
+ $where = null;
+ }
+ $i = 0;
+
+ while( true ) {
+ # Find which category to update
+ $row = $dbw->selectRow(
+ 'categorylinks',
+ 'cl_to',
+ $where,
+ __FUNCTION__,
+ array(
+ 'ORDER BY' => 'cl_to'
+ )
+ );
+ if( !$row ) {
+ # Done, hopefully.
+ break;
+ }
+ $name = $row->cl_to;
+ $where = 'cl_to > '.$dbw->addQuotes( $name );
+
+ # Use the row to update the category count
+ $cat = Category::newFromName( $name );
+ if( !is_object( $cat ) ) {
+ $this->output( "The category named $name is not valid?!\n" );
+ } else {
+ $cat->refreshCounts();
+ }
+
+ ++$i;
+ if( !($i % self::REPORTING_INTERVAL) ) {
+ $this->output( "$name\n" );
+ wfWaitForSlaves( $maxlag );
+ }
+ usleep( $throttle*1000 );
+ }
+
+ if( $dbw->insert(
+ 'updatelog',
+ array( 'ul_key' => 'populate category' ),
+ __FUNCTION__,
+ 'IGNORE'
+ )
+ ) {
+ wfOut( "Category population complete.\n" );
+ return true;
+ } else {
+ wfOut( "Could not insert category population row.\n" );
+ return false;
+ }
+ }
+}
-populateCategory( $options['begin'], $options['max-slave-lag'],
- $options['throttle'], $options['force'] );
+$maintClass = "PopulateCategory";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/populateLogSearch.inc b/maintenance/populateLogSearch.inc
new file mode 100644
index 00000000..b5e34fb7
--- /dev/null
+++ b/maintenance/populateLogSearch.inc
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Makes the required database updates for log display in Special:RevisionDelete
+ *
+ * Run via update.php or directly through populateLogSearch.php
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+define( 'LOG_SEARCH_BATCH_SIZE', 300 );
+
+function migrate_log_params( $db ) {
+ $start = $db->selectField( 'logging', 'MIN(log_id)', false, __FUNCTION__ );
+ if( !$start ) {
+ echo "Nothing to do.\n";
+ return true;
+ }
+ $end = $db->selectField( 'logging', 'MAX(log_id)', false, __FUNCTION__ );
+
+ # Do remaining chunk
+ $end += LOG_SEARCH_BATCH_SIZE - 1;
+ $blockStart = $start;
+ $blockEnd = $start + LOG_SEARCH_BATCH_SIZE - 1;
+ while( $blockEnd <= $end ) {
+ echo "...doing log_id from $blockStart to $blockEnd\n";
+ $cond = array("log_id BETWEEN $blockStart AND $blockEnd");
+ # Applicable log types
+ $cond['log_type'] = array('delete','suppress');
+ $res = $db->select( 'logging', '*', $cond, __FUNCTION__ );
+ $batch = array();
+ while( $row = $db->fetchObject( $res ) ) {
+ // RevisionDelete logs - revisions
+ if( LogEventsList::typeAction( $row, array('delete','suppress'), 'revision' ) ) {
+ $params = LogPage::extractParams( $row->log_params );
+ // Param format: <urlparam> <item CSV> [<ofield> <nfield>]
+ if( count($params) >= 2 ) {
+ $field = RevisionDeleter::getRelationType($params[0]);
+ // B/C, the params may start with a title key
+ if( $field == null ) {
+ array_shift($params);
+ $field = RevisionDeleter::getRelationType($params[0]);
+ }
+ if( $field == null ) {
+ echo "Invalid param type for $row->log_id\n";
+ continue; // skip this row
+ }
+ $items = explode(',',$params[1]);
+ $log = new LogPage( $row->log_type );
+ $log->addRelations( $field, $items, $row->log_id );
+ }
+ // RevisionDelete logs - log events
+ } else if( LogEventsList::typeAction( $row, array('delete','suppress'), 'event' ) ) {
+ $params = LogPage::extractParams( $row->log_params );
+ // Param format: <item CSV> [<ofield> <nfield>]
+ if( count($params) >= 1 ) {
+ $items = explode(',',$params[0]);
+ $log = new LogPage( $row->log_type );
+ $log->addRelations( 'log_id', $items, $row->log_id );
+ }
+ }
+ }
+ $blockStart += LOG_SEARCH_BATCH_SIZE;
+ $blockEnd += LOG_SEARCH_BATCH_SIZE;
+ wfWaitForSlaves( 5 );
+ }
+ if( $db->insert(
+ 'updatelog',
+ array( 'ul_key' => 'populate log_search' ),
+ __FUNCTION__,
+ 'IGNORE'
+ )
+ ) {
+ wfOut( "log_search population complete.\n" );
+ return true;
+ } else {
+ wfOut( "Could not insert log_search population row.\n" );
+ return false;
+ }
+}
diff --git a/maintenance/populateLogSearch.php b/maintenance/populateLogSearch.php
new file mode 100644
index 00000000..b045104e
--- /dev/null
+++ b/maintenance/populateLogSearch.php
@@ -0,0 +1,153 @@
+<?php
+/**
+ * Makes the required database updates for populating the
+ * log_search table retroactively
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class PopulateLogSearch extends Maintenance {
+
+ const LOG_SEARCH_BATCH_SIZE = 100;
+
+ static $tableMap = array('rev' => 'revision', 'fa' => 'filearchive', 'oi' => 'oldimage', 'ar' => 'archive');
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Migrate log params to new table and index for searching";
+ }
+
+ public function execute() {
+ $db = wfGetDB( DB_MASTER );
+ if ( !$db->tableExists( 'log_search' ) ) {
+ $this->error( "log_search does not exist", true );
+ }
+ $start = $db->selectField( 'logging', 'MIN(log_id)', false, __FUNCTION__ );
+ if( !$start ) {
+ $this->output( "Nothing to do.\n" );
+ return true;
+ }
+ $end = $db->selectField( 'logging', 'MAX(log_id)', false, __FUNCTION__ );
+
+ # Do remaining chunk
+ $end += self::LOG_SEARCH_BATCH_SIZE - 1;
+ $blockStart = $start;
+ $blockEnd = $start + self::LOG_SEARCH_BATCH_SIZE - 1;
+
+ $delTypes = array('delete','suppress'); // revisiondelete types
+ while( $blockEnd <= $end ) {
+ $this->output( "...doing log_id from $blockStart to $blockEnd\n" );
+ $cond = "log_id BETWEEN $blockStart AND $blockEnd";
+ $res = $db->select( 'logging', '*', $cond, __FUNCTION__ );
+ $batch = array();
+ foreach( $res as $row ) {
+ // RevisionDelete logs - revisions
+ if( LogEventsList::typeAction( $row, $delTypes, 'revision' ) ) {
+ $params = LogPage::extractParams( $row->log_params );
+ // Param format: <urlparam> <item CSV> [<ofield> <nfield>]
+ if( count($params) < 2 ) continue; // bad row?
+ $field = RevisionDeleter::getRelationType($params[0]);
+ // B/C, the params may start with a title key (<title> <urlparam> <CSV>)
+ if( $field == null ) {
+ array_shift($params); // remove title param
+ $field = RevisionDeleter::getRelationType($params[0]);
+ if( $field == null ) {
+ $this->output( "Invalid param type for {$row->log_id}\n" );
+ continue; // skip this row
+ } else {
+ // Clean up the row...
+ $db->update( 'logging',
+ array('log_params' => implode(',',$params) ),
+ array('log_id' => $row->log_id ) );
+ }
+ }
+ $items = explode(',',$params[1]);
+ $log = new LogPage( $row->log_type );
+ // Add item relations...
+ $log->addRelations( $field, $items, $row->log_id );
+ // Determine what table to query...
+ $prefix = substr( $field, 0, strpos($field,'_') ); // db prefix
+ if( !isset(self::$tableMap[$prefix]) )
+ continue; // bad row?
+ $table = self::$tableMap[$prefix];
+ $userField = $prefix.'_user';
+ $userTextField = $prefix.'_user_text';
+ // Add item author relations...
+ $userIds = $userIPs = array();
+ $sres = $db->select( $table,
+ array($userField,$userTextField),
+ array($field => $items)
+ );
+ foreach( $sres as $srow ) {
+ if( $srow->$userField > 0 )
+ $userIds[] = intval($srow->$userField);
+ else if( $srow->$userTextField != '' )
+ $userIPs[] = $srow->$userTextField;
+ }
+ // 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
+ } else if( LogEventsList::typeAction( $row, $delTypes, 'event' ) ) {
+ $params = LogPage::extractParams( $row->log_params );
+ // Param format: <item CSV> [<ofield> <nfield>]
+ if( count($params) < 1 ) continue; // bad row
+ $items = explode( ',', $params[0] );
+ $log = new LogPage( $row->log_type );
+ // Add item relations...
+ $log->addRelations( 'log_id', $items, $row->log_id );
+ // Add item author relations...
+ $userIds = $userIPs = array();
+ $sres = $db->select( 'logging',
+ array('log_user','log_user_text'),
+ array('log_id' => $items)
+ );
+ foreach( $sres as $srow ) {
+ if( $srow->log_user > 0 )
+ $userIds[] = intval($srow->log_user);
+ else if( IP::isIPAddress($srow->log_user_text) )
+ $userIPs[] = $srow->log_user_text;
+ }
+ $log->addRelations( 'target_author_id', $userIds, $row->log_id );
+ $log->addRelations( 'target_author_ip', $userIPs, $row->log_id );
+ }
+ }
+ $blockStart += self::LOG_SEARCH_BATCH_SIZE;
+ $blockEnd += self::LOG_SEARCH_BATCH_SIZE;
+ wfWaitForSlaves( 5 );
+ }
+ if( $db->insert(
+ 'updatelog',
+ array( 'ul_key' => 'populate log_search' ),
+ __FUNCTION__,
+ 'IGNORE'
+ )
+ ) {
+ $this->output( "log_search population complete.\n" );
+ return true;
+ } else {
+ $this->output( "Could not insert log_search population row.\n" );
+ return false;
+ }
+ }
+}
+
+$maintClass = "PopulateLogSearch";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/populateLogUsertext.php b/maintenance/populateLogUsertext.php
new file mode 100644
index 00000000..a491b2b0
--- /dev/null
+++ b/maintenance/populateLogUsertext.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Makes the required database updates for Special:ProtectedPages
+ * to show all protected pages, even ones before the page restrictions
+ * schema change. All remaining page_restriction column values are moved
+ * to the new table.
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class PopulateLogUsertext extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Populates the log_user_text";
+ $this->setBatchSize( 100 );
+ }
+
+ public function execute() {
+ $db = wfGetDB( DB_MASTER );
+ $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__ );
+
+ # Do remaining chunk
+ $end += $this->mBatchSize - 1;
+ $blockStart = $start;
+ $blockEnd = $start + $this->mBatchSize - 1;
+ while( $blockEnd <= $end ) {
+ $this->output( "...doing log_id from $blockStart to $blockEnd\n" );
+ $cond = "log_id BETWEEN $blockStart AND $blockEnd AND log_user = user_id";
+ $res = $db->select( array('logging','user'),
+ array('log_id','user_name'), $cond, __METHOD__ );
+ $batch = array();
+ $db->begin();
+ foreach( $res as $row ) {
+ $db->update( 'logging', array('log_user_text' => $row->user_name),
+ array('log_id' => $row->log_id), __METHOD__ );
+ }
+ $db->commit();
+ $blockStart += $this->mBatchSize;
+ $blockEnd += $this->mBatchSize;
+ wfWaitForSlaves( 5 );
+ }
+ if( $db->insert(
+ 'updatelog',
+ array( 'ul_key' => 'populate log_usertext' ),
+ __METHOD__,
+ 'IGNORE'
+ )
+ ) {
+ $this->output( "log_usertext population complete.\n" );
+ return true;
+ } else {
+ $this->output( "Could not insert log_usertext population row.\n" );
+ return false;
+ }
+ }
+}
+
+$maintClass = "PopulateLogUsertext";
+require_once( DO_MAINTENANCE );
+
diff --git a/maintenance/populateParentId.php b/maintenance/populateParentId.php
index 01730033..bf81cb68 100644
--- a/maintenance/populateParentId.php
+++ b/maintenance/populateParentId.php
@@ -1,18 +1,119 @@
<?php
-
/*
* Makes the required database updates for rev_parent_id
* to be of any use. It can be used for some simple tracking
* and to find new page edits by users.
+ *
+ * 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
+ *
+ * @ingroup Maintenance
*/
-require_once 'commandLine.inc';
-require_once 'populateParentId.inc';
-
-$db =& wfGetDB( DB_MASTER );
-if ( !$db->tableExists( 'revision' ) ) {
- echo "revision table does not exist\n";
- exit( 1 );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class PopulateParentId extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Populates rev_parent_id";
+ $this->setBatchSize( 200 );
+ }
+
+ public function execute() {
+ $db = wfGetDB( DB_MASTER );
+ if ( !$db->tableExists( 'revision' ) ) {
+ $this->error( "revision table does not exist", true );
+ }
+ $this->output( "Populating rev_parent_id column\n" );
+ $start = $db->selectField( 'revision', 'MIN(rev_id)', false, __FUNCTION__ );
+ $end = $db->selectField( 'revision', 'MAX(rev_id)', false, __FUNCTION__ );
+ if( is_null( $start ) || is_null( $end ) ){
+ $this->output( "...revision table seems to be empty.\n" );
+ $db->insert( 'updatelog',
+ array( 'ul_key' => 'populate rev_parent_id' ),
+ __METHOD__,
+ 'IGNORE' );
+ return;
+ }
+ # Do remaining chunk
+ $end += $this->mBatchSize - 1;
+ $blockStart = intval( $start );
+ $blockEnd = intval( $start ) + $this->mBatchSize - 1;
+ $count = 0;
+ $changed = 0;
+ while( $blockEnd <= $end ) {
+ $this->output( "...doing rev_id from $blockStart to $blockEnd\n" );
+ $cond = "rev_id BETWEEN $blockStart AND $blockEnd";
+ $res = $db->select( 'revision',
+ array('rev_id','rev_page','rev_timestamp','rev_parent_id'),
+ $cond, __METHOD__ );
+ # Go through and update rev_parent_id from these rows.
+ # Assume that the previous revision of the title was
+ # the original previous revision of the title when the
+ # edit was made...
+ foreach( $res as $row ) {
+ # First, check rows with the same timestamp other than this one
+ # with a smaller rev ID. The highest ID "wins". This avoids loops
+ # as timestamp can only decrease and never loops with IDs (from parent to parent)
+ $previousID = $db->selectField( 'revision', 'rev_id',
+ array( 'rev_page' => $row->rev_page, 'rev_timestamp' => $row->rev_timestamp,
+ "rev_id < " . intval( $row->rev_id ) ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_id DESC' ) );
+ # If there are none, check the the highest ID with a lower timestamp
+ if( !$previousID ) {
+ # Get the highest older timestamp
+ $lastTimestamp = $db->selectField( 'revision', 'rev_timestamp',
+ array( 'rev_page' => $row->rev_page, "rev_timestamp < " . $db->addQuotes( $row->rev_timestamp ) ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_timestamp DESC' ) );
+ # If there is one, let the highest rev ID win
+ if( $lastTimestamp ) {
+ $previousID = $db->selectField( 'revision', 'rev_id',
+ array( 'rev_page' => $row->rev_page, 'rev_timestamp' => $lastTimestamp ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_id DESC' ) );
+ }
+ }
+ $previousID = intval($previousID);
+ if( $previousID != $row->rev_parent_id )
+ $changed++;
+ # Update the row...
+ $db->update( 'revision',
+ array( 'rev_parent_id' => $previousID ),
+ array( 'rev_id' => $row->rev_id ),
+ __METHOD__ );
+ $count++;
+ }
+ $blockStart += $this->mBatchSize - 1;
+ $blockEnd += $this->mBatchSize - 1;
+ wfWaitForSlaves( 5 );
+ }
+ $logged = $db->insert( 'updatelog',
+ array( 'ul_key' => 'populate rev_parent_id' ),
+ __METHOD__,
+ 'IGNORE' );
+ if( $logged ) {
+ $this->output( "rev_parent_id population complete ... {$count} rows [{$changed} changed]\n" );
+ return true;
+ } else {
+ $this->output( "Could not insert rev_parent_id population row.\n" );
+ return false;
+ }
+ }
}
-populate_rev_parent_id( $db );
+$maintClass = "PopulateParentId";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/populateSha1.php b/maintenance/populateSha1.php
new file mode 100644
index 00000000..72ef9461
--- /dev/null
+++ b/maintenance/populateSha1.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Optional upgrade script to populate the img_sha1 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__).'/Maintenance.php' );
+
+class PopulateSha1 extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Populate the img_sha1 field";
+ $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 );
+ }
+
+ public function execute() {
+ $method = $this->getOption( 'method', 'normal' );
+ $file = $this->getOption( 'file' );
+
+ $t = -microtime( true );
+ $dbw = wfGetDB( DB_MASTER );
+ if( $file ) {
+ $res = $dbw->selectRow(
+ 'image',
+ array( 'img_name' ),
+ array( 'img_name' => $dbw->addQuotes( $file ) ),
+ __METHOD__
+ );
+ if( !$res ) {
+ $this->error( "No such file: $file", true );
+ return;
+ }
+ } else {
+ $res = $dbw->select( 'image', array( 'img_name' ), array( 'img_sha1' => '' ), __METHOD__ );
+ }
+ $imageTable = $dbw->tableName( 'image' );
+ $oldimageTable = $dbw->tableName( 'oldimage' );
+ $batch = array();
+
+ if ( $method == 'pipe' ) {
+ // @fixme kill this and replace with a second unbuffered DB connection.
+ global $wgDBuser, $wgDBserver, $wgDBpassword, $wgDBname;
+ $cmd = 'mysql -u' . wfEscapeShellArg( $wgDBuser ) .
+ ' -h' . wfEscapeShellArg( $wgDBserver ) .
+ ' -p' . wfEscapeShellArg( $wgDBpassword, $wgDBname );
+ $this->output( "Using pipe method\n" );
+ $pipe = popen( $cmd, 'w' );
+ }
+
+ $numRows = $res->numRows();
+ $i = 0;
+ foreach ( $res as $row ) {
+ if ( $i % 100 == 0 ) {
+ $this->output( sprintf( "Done %d of %d, %5.3f%% \r", $i, $numRows, $i / $numRows * 100 ) );
+ wfWaitForSlaves( 5 );
+ }
+ $file = wfLocalFile( $row->img_name );
+ if ( !$file ) {
+ continue;
+ }
+ $sha1 = File::sha1Base36( $file->getPath() );
+ if ( strval( $sha1 ) !== '' ) {
+ $sql = "UPDATE $imageTable SET img_sha1=" . $dbw->addQuotes( $sha1 ) .
+ " WHERE img_name=" . $dbw->addQuotes( $row->img_name );
+ if ( $method == 'pipe' ) {
+ fwrite( $pipe, "$sql;\n" );
+ } else {
+ $dbw->query( $sql, __METHOD__ );
+ }
+ }
+ $i++;
+ }
+ if ( $method == 'pipe' ) {
+ fflush( $pipe );
+ pclose( $pipe );
+ }
+ $t += microtime( true );
+ $this->output( sprintf( "\nDone %d files in %.1f seconds\n", $numRows, $t ) );
+ }
+}
+
+$maintClass = "PopulateSha1";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/postgres/archives/patch-l10n_cache.sql b/maintenance/postgres/archives/patch-l10n_cache.sql
new file mode 100644
index 00000000..9b39b1b7
--- /dev/null
+++ b/maintenance/postgres/archives/patch-l10n_cache.sql
@@ -0,0 +1,8 @@
+CREATE TABLE l10n_cache (
+ lc_lang TEXT NOT NULL,
+ lc_key TEXT NOT NULL,
+ lc_value TEXT NOT NULL
+);
+CREATE INDEX l10n_cache_lc_lang_key ON l10n_cache (lc_lang, lc_key);
+
+
diff --git a/maintenance/postgres/archives/patch-log_search.sql b/maintenance/postgres/archives/patch-log_search.sql
new file mode 100644
index 00000000..20a61fd7
--- /dev/null
+++ b/maintenance/postgres/archives/patch-log_search.sql
@@ -0,0 +1,9 @@
+
+CREATE TABLE log_search (
+ ls_field TEXT NOT NULL,
+ ls_value TEXT NOT NULL,
+ ls_log_id INTEGER NOT NULL DEFAULT 0
+);
+
+ALTER TABLE log_search ADD CONSTRAINT log_search_pk PRIMARY KEY(ls_field, ls_value, ls_log_id);
+CREATE INDEX ls_log_id ON log_search (ls_log_id);
diff --git a/maintenance/postgres/archives/patch-update_sequences.sql b/maintenance/postgres/archives/patch-update_sequences.sql
new file mode 100644
index 00000000..a3d30681
--- /dev/null
+++ b/maintenance/postgres/archives/patch-update_sequences.sql
@@ -0,0 +1,20 @@
+ALTER SEQUENCE rev_rev_id_val RENAME TO revision_rev_id_seq;
+ALTER TABLE revision ALTER COLUMN rev_id SET DEFAULT NEXTVAL('revision_rev_id_seq');
+
+ALTER SEQUENCE text_old_id_val RENAME TO text_old_id_seq;
+ALTER TABLE pagecontent ALTER COLUMN old_id SET DEFAULT nextval('text_old_id_seq');
+
+ALTER SEQUENCE category_id_seq RENAME TO category_cat_id_seq;
+ALTER TABLE category ALTER COLUMN cat_id SET DEFAULT nextval('category_cat_id_seq');
+
+ALTER SEQUENCE ipblocks_ipb_id_val RENAME TO ipblocks_ipb_id_seq;
+ALTER TABLE ipblocks ALTER COLUMN ipb_id SET DEFAULT nextval('ipblocks_ipb_id_seq');
+
+ALTER SEQUENCE rc_rc_id_seq RENAME TO recentchanges_rc_id_seq;
+ALTER TABLE recentchanges ALTER COLUMN rc_id SET DEFAULT nextval('recentchanges_rc_id_seq');
+
+ALTER SEQUENCE log_log_id_seq RENAME TO logging_log_id_seq;
+ALTER TABLE logging ALTER COLUMN log_id SET DEFAULT nextval('logging_log_id_seq');
+
+ALTER SEQUENCE pr_id_val RENAME TO page_restrictions_pr_id_seq;
+ALTER TABLE page_restrictions ALTER COLUMN pr_id SET DEFAULT nextval('page_restrictions_pr_id_seq');
diff --git a/maintenance/postgres/archives/patch-user_properties.sql b/maintenance/postgres/archives/patch-user_properties.sql
new file mode 100644
index 00000000..b40fa85f
--- /dev/null
+++ b/maintenance/postgres/archives/patch-user_properties.sql
@@ -0,0 +1,8 @@
+CREATE TABLE user_properties(
+ up_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
+ up_property TEXT NOT NULL,
+ up_value TEXT
+);
+
+CREATE UNIQUE INDEX user_properties_user_property on user_properties (up_user,up_property);
+CREATE INDEX user_properties_property on user_properties (up_property);
diff --git a/maintenance/postgres/compare_schemas.pl b/maintenance/postgres/compare_schemas.pl
index 850244ba..9bddf504 100644
--- a/maintenance/postgres/compare_schemas.pl
+++ b/maintenance/postgres/compare_schemas.pl
@@ -129,6 +129,8 @@ sub parse_sql {
}
elsif (m{^CREATE (?:UNIQUE )?(?:FULLTEXT )?INDEX /\*i\*/(\w+) ON /\*_\*/(\w+) \((.+?)\);}) {
}
+ elsif (m{^\s*PRIMARY KEY \([\w,]+\)}) {
+ }
else {
die "Cannot parse line $. of $oldfile:\n$_\n";
}
@@ -258,6 +260,9 @@ while (<$newfh>) {
}
$lastcomma = $3 ? 1 : 0;
}
+ elsif (m{^\s*PRIMARY KEY \([\w,]+\)}) {
+ $lastcomma = 0;
+ }
else {
die "Cannot parse line $. of $new:\n$_\n";
}
@@ -302,7 +307,8 @@ ar_comment tinyblob TEXT
fa_description tinyblob TEXT
img_description tinyblob TEXT
ipb_reason tinyblob TEXT
-log_action varbinary(10) TEXT
+log_action varbinary(32) TEXT
+log_type varbinary(32) TEXT
oi_description tinyblob TEXT
rev_comment tinyblob TEXT
rc_log_action varbinary(255) TEXT
@@ -318,12 +324,17 @@ ipb_address tinyblob TEXT # IP address or username
ipb_range_end tinyblob TEXT # hexadecimal
ipb_range_start tinyblob TEXT # hexadecimal
img_minor_mime varbinary(32) TEXT
+lc_lang varbinary(32) TEXT
+lc_value varbinary(32) TEXT
+
img_sha1 varbinary(32) TEXT
job_cmd varbinary(60) TEXT # Should we limit to 60 as well?
keyname varbinary(255) TEXT # No tablename prefix (objectcache)
ll_lang varbinary(20) TEXT # Language code
+lc_value mediumblob TEXT
log_params blob TEXT # LF separated list of args
log_type varbinary(10) TEXT
+ls_field varbinary(32) TEXT
oi_minor_mime varbinary(32) TEXT
oi_sha1 varbinary(32) TEXT
old_flags tinyblob TEXT
@@ -343,6 +354,8 @@ rc_params blob TEXT
rlc_to_blob blob TEXT
ts_tags blob TEXT
ug_group varbinary(16) TEXT
+up_property varbinary(32) TEXT
+up_value blob TEXT
user_email_token binary(32) TEXT
user_ip varbinary(40) TEXT
user_newpassword tinyblob TEXT
diff --git a/maintenance/postgres/mediawiki_mysql2postgres.pl b/maintenance/postgres/mediawiki_mysql2postgres.pl
index a3b17f94..220c779b 100644
--- a/maintenance/postgres/mediawiki_mysql2postgres.pl
+++ b/maintenance/postgres/mediawiki_mysql2postgres.pl
@@ -1,7 +1,7 @@
#!/usr/bin/perl
## Convert data from a MySQL mediawiki database into a Postgres mediawiki database
-## svn: $Id: mediawiki_mysql2postgres.pl 43845 2008-11-22 06:44:45Z greg $
+## svn: $Id: mediawiki_mysql2postgres.pl 59489 2009-11-27 15:34:54Z greg $
## NOTE: It is probably easier to dump your wiki using maintenance/dumpBackup.php
## and then import it with maintenance/importDump.php
@@ -181,7 +181,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: 43845 $}.qq{)
+-- Version: $VERSION (subversion }.q{$LastChangedRevision: 59489 $}.qq{)
-- Author: Greg Sabino Mullane <greg\@turnstep.com> Comments welcome
--
-- This file was created: $now
@@ -416,7 +416,7 @@ SELECT setval('page_page_id_seq', 1+coalesce(max(page_id),0),false) FROM pa
SELECT setval('pr_id_val', 1+coalesce(max(pr_id) ,0),false) FROM page_restrictions;
SELECT setval('rc_rc_id_seq', 1+coalesce(max(rc_id) ,0),false) FROM recentchanges;
SELECT setval('rev_rev_id_val', 1+coalesce(max(rev_id) ,0),false) FROM revision;
-SELECT setval('text_old_id_val', 1+coalesce(max(old_id) ,0),false) FROM pagecontent;
+SELECT setval('text_old_id_seq', 1+coalesce(max(old_id) ,0),false) FROM pagecontent;
SELECT setval('trackbacks_tb_id_seq', 1+coalesce(max(tb_id) ,0),false) FROM trackbacks;
SELECT setval('user_user_id_seq', 1+coalesce(max(user_id),0),false) FROM mwuser;
};
diff --git a/maintenance/postgres/tables.sql b/maintenance/postgres/tables.sql
index 23e8b596..38b607d9 100644
--- a/maintenance/postgres/tables.sql
+++ b/maintenance/postgres/tables.sql
@@ -82,9 +82,9 @@ $mw$;
CREATE TRIGGER page_deleted AFTER DELETE ON page
FOR EACH ROW EXECUTE PROCEDURE page_deleted();
-CREATE SEQUENCE rev_rev_id_val;
+CREATE SEQUENCE revision_rev_id_seq;
CREATE TABLE revision (
- rev_id INTEGER NOT NULL UNIQUE DEFAULT nextval('rev_rev_id_val'),
+ rev_id INTEGER NOT NULL UNIQUE DEFAULT nextval('revision_rev_id_seq'),
rev_page INTEGER NULL REFERENCES page (page_id) ON DELETE CASCADE,
rev_text_id INTEGER NULL, -- FK
rev_comment TEXT,
@@ -103,17 +103,17 @@ CREATE INDEX rev_user_idx ON revision (rev_user);
CREATE INDEX rev_user_text_idx ON revision (rev_user_text);
-CREATE SEQUENCE text_old_id_val;
+CREATE SEQUENCE text_old_id_seq;
CREATE TABLE pagecontent ( -- replaces reserved word 'text'
- old_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('text_old_id_val'),
+ old_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('text_old_id_seq'),
old_text TEXT,
old_flags TEXT
);
-CREATE SEQUENCE pr_id_val;
+CREATE SEQUENCE page_restrictions_pr_id_seq;
CREATE TABLE page_restrictions (
- pr_id INTEGER NOT NULL UNIQUE DEFAULT nextval('pr_id_val'),
+ pr_id INTEGER NOT NULL UNIQUE DEFAULT nextval('page_restrictions_pr_id_seq'),
pr_page INTEGER NULL REFERENCES page (page_id) ON DELETE CASCADE,
pr_type TEXT NOT NULL,
pr_level TEXT NOT NULL,
@@ -155,7 +155,9 @@ CREATE INDEX archive_user_text ON archive (ar_user_text);
CREATE TABLE redirect (
rd_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE,
rd_namespace SMALLINT NOT NULL,
- rd_title TEXT NOT NULL
+ rd_title TEXT NOT NULL,
+ rd_interwiki TEXT NULL,
+ rd_fragment TEXT NULL
);
CREATE INDEX redirect_ns_title ON redirect (rd_namespace,rd_title,rd_from);
@@ -198,6 +200,13 @@ CREATE TABLE externallinks (
CREATE INDEX externallinks_from_to ON externallinks (el_from,el_to);
CREATE INDEX externallinks_index ON externallinks (el_index);
+CREATE TABLE external_user (
+ eu_local_id INTEGER NOT NULL PRIMARY KEY,
+ eu_external_id TEXT
+);
+
+CREATE UNIQUE INDEX eu_external_id ON external_user (eu_external_id);
+
CREATE TABLE langlinks (
ll_from INTEGER NOT NULL REFERENCES page (page_id) ON DELETE CASCADE,
ll_lang TEXT,
@@ -224,9 +233,9 @@ CREATE TABLE hitcounter (
);
-CREATE SEQUENCE ipblocks_ipb_id_val;
+CREATE SEQUENCE ipblocks_ipb_id_seq;
CREATE TABLE ipblocks (
- ipb_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('ipblocks_ipb_id_val'),
+ ipb_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('ipblocks_ipb_id_seq'),
ipb_address TEXT NULL,
ipb_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL,
ipb_by INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
@@ -288,7 +297,7 @@ CREATE TABLE oldimage (
oi_deleted SMALLINT NOT NULL DEFAULT 0,
oi_sha1 TEXT NOT NULL DEFAULT ''
);
-ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascade FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE;
+ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascaded FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE ON UPDATE CASCADE;
CREATE INDEX oi_name_timestamp ON oldimage (oi_name,oi_timestamp);
CREATE INDEX oi_name_archive_name ON oldimage (oi_name,oi_archive_name);
CREATE INDEX oi_sha1 ON oldimage (oi_sha1);
@@ -324,9 +333,9 @@ CREATE INDEX fa_notime ON filearchive (fa_deleted_timestamp);
CREATE INDEX fa_nouser ON filearchive (fa_deleted_user);
-CREATE SEQUENCE rc_rc_id_seq;
+CREATE SEQUENCE recentchanges_rc_id_seq;
CREATE TABLE recentchanges (
- rc_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('rc_rc_id_seq'),
+ 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_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL,
@@ -426,9 +435,9 @@ CREATE TABLE transcache (
);
-CREATE SEQUENCE log_log_id_seq;
+CREATE SEQUENCE logging_log_id_seq;
CREATE TABLE logging (
- log_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('log_log_id_seq'),
+ log_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('logging_log_id_seq'),
log_type TEXT NOT NULL,
log_action TEXT NOT NULL,
log_timestamp TIMESTAMPTZ NOT NULL,
@@ -437,12 +446,24 @@ CREATE TABLE logging (
log_title TEXT NOT NULL,
log_comment TEXT,
log_params TEXT,
- log_deleted SMALLINT NOT NULL DEFAULT 0
+ log_deleted SMALLINT NOT NULL DEFAULT 0,
+ log_user_text TEXT NOT NULL DEFAULT '',
+ log_page INTEGER
);
CREATE INDEX logging_type_name ON logging (log_type, log_timestamp);
CREATE INDEX logging_user_time ON logging (log_timestamp, log_user);
CREATE INDEX logging_page_time ON logging (log_namespace, log_title, log_timestamp);
+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 TABLE log_search (
+ ls_field TEXT NOT NULL,
+ ls_value TEXT NOT NULL,
+ ls_log_id INTEGER NOT NULL DEFAULT 0,
+ PRIMARY KEY (ls_field,ls_value,ls_log_id)
+);
+CREATE INDEX ls_log_id ON log_search (ls_log_id);
CREATE SEQUENCE trackbacks_tb_id_seq;
CREATE TABLE trackbacks (
@@ -542,9 +563,9 @@ CREATE TABLE updatelog (
);
-CREATE SEQUENCE category_id_seq;
+CREATE SEQUENCE category_cat_id_seq;
CREATE TABLE category (
- cat_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('category_id_seq'),
+ cat_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('category_cat_id_seq'),
cat_title TEXT NOT NULL,
cat_pages INTEGER NOT NULL DEFAULT 0,
cat_subcats INTEGER NOT NULL DEFAULT 0,
@@ -580,6 +601,14 @@ CREATE TABLE valid_tag (
vt_tag TEXT NOT NULL PRIMARY KEY
);
+CREATE TABLE user_properties (
+ up_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE CASCADE,
+ up_property TEXT NOT NULL,
+ up_value TEXT
+);
+CREATE UNIQUE INDEX user_properties_user_property ON user_properties (up_user,up_property);
+CREATE INDEX user_properties_property ON user_properties (up_property);
+
CREATE TABLE mediawiki_version (
type TEXT NOT NULL,
mw_version TEXT NOT NULL,
@@ -599,5 +628,11 @@ CREATE TABLE mediawiki_version (
);
INSERT INTO mediawiki_version (type,mw_version,sql_version,sql_date)
- VALUES ('Creation','??','$LastChangedRevision: 48615 $','$LastChangedDate: 2009-03-20 12:15:41 +1100 (Fri, 20 Mar 2009) $');
+ VALUES ('Creation','??','$LastChangedRevision: 59842 $','$LastChangedDate: 2009-12-09 06:32:17 +1100 (Wed, 09 Dec 2009) $');
+CREATE TABLE l10n_cache (
+ lc_lang TEXT NOT NULL,
+ lc_key TEXT NOT NULL,
+ lc_value TEXT NOT NULL
+);
+CREATE INDEX l10n_cache_lc_lang_key ON l10n_cache (lc_lang, lc_key);
diff --git a/maintenance/preprocessorFuzzTest.php b/maintenance/preprocessorFuzzTest.php
index 34960e01..c271b117 100644
--- a/maintenance/preprocessorFuzzTest.php
+++ b/maintenance/preprocessorFuzzTest.php
@@ -4,7 +4,7 @@
* @ingroup Maintenance
*/
-require_once( dirname( __FILE__ ). '/../maintenance/commandLine.inc' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
$wgHooks['BeforeParserFetchTemplateAndtitle'][] = 'PPFuzzTester::templateHook';
@@ -102,7 +102,8 @@ class PPFuzzTester {
// This resolves a few differences between the old preprocessor and the
// XML-based one, which doesn't like illegals and converts line endings.
// It's done by the MW UI, so it's a reasonably legitimate thing to do.
- $s = UtfNormal::cleanUp( $s );
+ global $wgContLang;
+ $s = $wgContLang->normalize( $s );
return $s;
}
diff --git a/maintenance/protect.php b/maintenance/protect.php
new file mode 100644
index 00000000..126707a7
--- /dev/null
+++ b/maintenance/protect.php
@@ -0,0 +1,68 @@
+<?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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class Protect extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Protect or unprotect an article from the command line.";
+ $this->addOption( 'unprotect', 'Removes protection' );
+ $this->addOption( 'semiprotect', 'Adds semi-protection' );
+ $this->addOption( 'u', 'Username to protect with', false, true );
+ $this->addOption( 'r', 'Reason for un/protection', false, true );
+ }
+
+ public function execute() {
+ global $wgUser, $wgTitle, $wgArticle;
+
+ $userName = $this->getOption( 'u', 'Maintenance script' );
+ $reason = $this->getOption( 'r', '' );
+
+ $protection = "sysop";
+ if ( $this->hasOption('semiprotect') ) {
+ $protection = "autoconfirmed";
+ } elseif ( $this->hasOption('unprotect') ) {
+ $protection = "";
+ }
+
+ $wgUser = User::newFromName( $userName );
+ $restrictions = array( 'edit' => $protection, 'move' => $protection );
+
+ $wgTitle = Title::newFromText( $this->getArg() );
+ if ( !$wgTitle ) {
+ $this->error( "Invalid title", true );
+ }
+
+ $wgArticle = new Article( $wgTitle );
+
+ # un/protect the article
+ $this->output( "Updating protection status... " );
+ $success = $wgArticle->updateRestrictions($restrictions, $reason);
+ if ( $success ) {
+ $this->output( "done\n" );
+ } else {
+ $this->output( "failed\n" );
+ }
+ }
+}
+
+$maintClass = "Protect";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/purgeList.php b/maintenance/purgeList.php
index ab8ff9fb..7168a203 100644
--- a/maintenance/purgeList.php
+++ b/maintenance/purgeList.php
@@ -2,38 +2,62 @@
/**
* Send purge requests for listed pages to squid
*
- * @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
+ *
* @ingroup Maintenance
*/
-require_once( "commandLine.inc" );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class PurgeList extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Send purge requests for listed pages to squid";
+ }
-$stdin = fopen( "php://stdin", "rt" );
-$urls = array();
+ public function execute() {
+ $stdin = $this->getStdin();
+ $urls = array();
-while( !feof( $stdin ) ) {
- $page = trim( fgets( $stdin ) );
- if ( substr( $page, 0, 7 ) == 'http://' ) {
- $urls[] = $page;
- } elseif( $page !== '' ) {
- $title = Title::newFromText( $page );
- if( $title ) {
- $url = $title->getFullUrl();
- echo "$url\n";
- $urls[] = $url;
- if( isset( $options['purge'] ) ) {
- $title->invalidateCache();
+ while( !feof( $stdin ) ) {
+ $page = trim( fgets( $stdin ) );
+ if ( substr( $page, 0, 7 ) == 'http://' ) {
+ $urls[] = $page;
+ } elseif( $page !== '' ) {
+ $title = Title::newFromText( $page );
+ if( $title ) {
+ $url = $title->getFullUrl();
+ $this->output( "$url\n" );
+ $urls[] = $url;
+ if( isset( $options['purge'] ) ) {
+ $title->invalidateCache();
+ }
+ } else {
+ $this->output( "(Invalid title '$page')\n" );
+ }
}
- } else {
- echo "(Invalid title '$page')\n";
}
- }
-}
-
-echo "Purging " . count( $urls ) . " urls...\n";
-$u = new SquidUpdate( $urls );
-$u->doUpdate();
-echo "Done!\n";
+ $this->output( "Purging " . count( $urls ) . " urls...\n" );
+ $u = new SquidUpdate( $urls );
+ $u->doUpdate();
+ $this->output( "Done!\n" );
+ }
+}
+$maintClass = "PurgeList";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/purgeOldText.inc b/maintenance/purgeOldText.inc
index e41c374d..0bd5f2eb 100644
--- a/maintenance/purgeOldText.inc
+++ b/maintenance/purgeOldText.inc
@@ -20,7 +20,7 @@ function PurgeRedundantText( $delete = false ) {
# Get "active" text records from the revisions table
echo( "Searching for active text records in revisions table..." );
- $res = $dbw->query( "SELECT DISTINCTROW rev_text_id FROM $tbl_rev" );
+ $res = $dbw->query( "SELECT DISTINCT rev_text_id FROM $tbl_rev" );
while( $row = $dbw->fetchObject( $res ) ) {
$cur[] = $row->rev_text_id;
}
@@ -28,7 +28,7 @@ function PurgeRedundantText( $delete = false ) {
# Get "active" text records from the archive table
echo( "Searching for active text records in archive table..." );
- $res = $dbw->query( "SELECT DISTINCTROW ar_text_id FROM $tbl_arc" );
+ $res = $dbw->query( "SELECT DISTINCT ar_text_id FROM $tbl_arc" );
while( $row = $dbw->fetchObject( $res ) ) {
$cur[] = $row->ar_text_id;
}
diff --git a/maintenance/purgeOldText.php b/maintenance/purgeOldText.php
index 4a4be482..9621cb39 100644
--- a/maintenance/purgeOldText.php
+++ b/maintenance/purgeOldText.php
@@ -1,29 +1,39 @@
<?php
-
/**
* Purge old text records from the database
*
- * @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
+ *
* @ingroup Maintenance
* @author Rob Church <robchur@gmail.com>
*/
-$options = array( 'purge', 'help' );
-require_once( 'commandLine.inc' );
-require_once( 'purgeOldText.inc' );
-
-echo( "Purge Old Text\n\n" );
-
-if( @$options['help'] ) {
- ShowUsage();
-} else {
- PurgeRedundantText( @$options['purge'] );
-}
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-function ShowUsage() {
- echo( "Prunes unused text records from the database.\n\n" );
- echo( "Usage: php purgeOldText.php [--purge]\n\n" );
- echo( "purge : Performs the deletion\n" );
- echo( " help : Show this usage information\n" );
+class PurgeOldText extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Purge old text records from the database";
+ $this->addOption( 'purge', 'Performs the deletion' );
+ }
+
+ public function execute() {
+ $this->purgeRedundantText( $this->hasOption('purge') );
+ }
}
+$maintClass = "PurgeOldText";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/reassignEdits.inc.php b/maintenance/reassignEdits.inc.php
deleted file mode 100644
index e68b4cf5..00000000
--- a/maintenance/reassignEdits.inc.php
+++ /dev/null
@@ -1,143 +0,0 @@
-<?php
-
-/**
- * Support functions for the reassignEdits script
- *
- * @file
- * @ingroup Maintenance
- * @author Rob Church <robchur@gmail.com>
- * @licence GNU General Public Licence 2.0 or later
- */
-
-/**
- * Reassign edits from one user to another
- *
- * @param $from User to take edits from
- * @param $to User to assign edits to
- * @param $rc Update the recent changes table
- * @param $report Don't change things; just echo numbers
- * @return integer Number of entries changed, or that would be changed
- */
-function reassignEdits( &$from, &$to, $rc = false, $report = false ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->immediateBegin();
- $fname = 'reassignEdits';
-
- # Count things
- out( "Checking current edits..." );
- $res = $dbw->select( 'revision', 'COUNT(*) AS count', userConditions( $from, 'rev_user', 'rev_user_text' ), $fname );
- $row = $dbw->fetchObject( $res );
- $cur = $row->count;
- out( "found {$cur}.\n" );
-
- out( "Checking deleted edits..." );
- $res = $dbw->select( 'archive', 'COUNT(*) AS count', userConditions( $from, 'ar_user', 'ar_user_text' ), $fname );
- $row = $dbw->fetchObject( $res );
- $del = $row->count;
- out( "found {$del}.\n" );
-
- # Don't count recent changes if we're not supposed to
- if( $rc ) {
- out( "Checking recent changes..." );
- $res = $dbw->select( 'recentchanges', 'COUNT(*) AS count', userConditions( $from, 'rc_user', 'rc_user_text' ), $fname );
- $row = $dbw->fetchObject( $res );
- $rec = $row->count;
- out( "found {$rec}.\n" );
- } else {
- $rec = 0;
- }
-
- $total = $cur + $del + $rec;
- out( "\nTotal entries to change: {$total}\n" );
-
- if( !$report ) {
- if( $total ) {
- # Reassign edits
- out( "\nReassigning current edits..." );
- $res = $dbw->update( 'revision', userSpecification( $to, 'rev_user', 'rev_user_text' ), userConditions( $from, 'rev_user', 'rev_user_text' ), $fname );
- out( "done.\nReassigning deleted edits..." );
- $res = $dbw->update( 'archive', userSpecification( $to, 'ar_user', 'ar_user_text' ), userConditions( $from, 'ar_user', 'ar_user_text' ), $fname );
- out( "done.\n" );
- # Update recent changes if required
- if( $rc ) {
- out( "Updating recent changes..." );
- $res = $dbw->update( 'recentchanges', userSpecification( $to, 'rc_user', 'rc_user_text' ), userConditions( $from, 'rc_user', 'rc_user_text' ), $fname );
- out( "done.\n" );
- }
- }
- }
-
- $dbw->immediateCommit();
- return (int)$total;
-}
-
-/**
- * Return the most efficient set of user conditions
- * i.e. a user => id mapping, or a user_text => text mapping
- *
- * @param $user User for the condition
- * @param $idfield Field name containing the identifier
- * @param $utfield Field name containing the user text
- * @return array
- */
-function userConditions( &$user, $idfield, $utfield ) {
- return $user->getId() ? array( $idfield => $user->getId() ) : array( $utfield => $user->getName() );
-}
-
-/**
- * Return user specifications
- * i.e. user => id, user_text => text
- *
- * @param $user User for the spec
- * @param $idfield Field name containing the identifier
- * @param $utfield Field name containing the user text
- * @return array
- */
-function userSpecification( &$user, $idfield, $utfield ) {
- return array( $idfield => $user->getId(), $utfield => $user->getName() );
-}
-
-/**
- * Echo output if $wgSilent is off
- *
- * @param $output Output to echo
- * @return bool True if the output was echoed
- */
-function out( $output ) {
- global $wgSilent;
- if( !$wgSilent ) {
- echo( $output );
- return true;
- } else {
- return false;
- }
-}
-
-/**
- * Mutator for $wgSilent
- *
- * @param $silent Switch on $wgSilent
- */
-function silent( $silent = true ) {
- global $wgSilent;
- $wgSilent = $silent;
-}
-
-/**
- * Initialise the user object
- *
- * @param $username Username or IP address
- * @return User
- */
-function initialiseUser( $username ) {
- if( User::isIP( $username ) ) {
- $user = new User();
- $user->setId( 0 );
- $user->setName( $username );
- } else {
- $user = User::newFromName( $username );
- }
- $user->load();
- return $user;
-}
-
diff --git a/maintenance/reassignEdits.php b/maintenance/reassignEdits.php
index 0d640202..e595e5b3 100644
--- a/maintenance/reassignEdits.php
+++ b/maintenance/reassignEdits.php
@@ -1,56 +1,169 @@
<?php
-
/**
* Reassign edits from a user or IP address to another user
*
- * @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
+ *
* @ingroup Maintenance
* @author Rob Church <robchur@gmail.com>
* @licence GNU General Public Licence 2.0 or later
*/
-$options = array( 'force', 'norc', 'quiet', 'report' );
-require_once( 'commandLine.inc' );
-require_once( 'reassignEdits.inc.php' );
-
-# Set silent mode; --report overrides --quiet
-if( !@$options['report'] && @$options['quiet'] )
- setSilent();
-
-out( "Reassign Edits\n\n" );
-
-if( @$args[0] && @$args[1] ) {
-
- # Set up the users involved
- $from =& initialiseUser( $args[0] );
- $to =& initialiseUser( $args[1] );
-
- # If the target doesn't exist, and --force is not set, stop here
- if( $to->getId() || @$options['force'] ) {
- # Reassign the edits
- $report = @$options['report'];
- $count = reassignEdits( $from, $to, !@$options['norc'], $report );
- # If reporting, and there were items, advise the user to run without --report
- if( $report )
- out( "Run the script again without --report to update.\n" );
- } else {
- $ton = $to->getName();
- echo( "User '{$ton}' not found.\n" );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class ReassignEdits extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Reassign edits from one user to another";
+ $this->addOption( "force", "Reassign even if the target user doesn't exist" );
+ $this->addOption( "norc", "Don't update the recent changes table" );
+ $this->addOption( "report", "Print out details of what would be changed, but don't update it" );
+ $this->addArg( 'from', 'Old user to take edits from' );
+ $this->addArg( 'to', 'New user to give edits to' );
}
-} else {
- ShowUsage();
-}
+ public function execute() {
+ if( $this->hasArg(0) && $this->hasArg(1) ) {
+ # Set up the users involved
+ $from = $this->initialiseUser( $this->getArg(0) );
+ $to = $this->initialiseUser( $this->getArg(1) );
+
+ # If the target doesn't exist, and --force is not set, stop here
+ if( $to->getId() || $this->hasOption('force') ) {
+ # Reassign the edits
+ $report = $this->hasOption('report');
+ $count = $this->doReassignEdits( $from, $to, !$this->hasOption('norc'), $report );
+ # If reporting, and there were items, advise the user to run without --report
+ if( $report )
+ $this->output( "Run the script again without --report to update.\n" );
+ } else {
+ $ton = $to->getName();
+ $this->error( "User '{$ton}' not found." );
+ }
+ }
+ }
+
+ /**
+ * Reassign edits from one user to another
+ *
+ * @param $from User to take edits from
+ * @param $to User to assign edits to
+ * @param $rc Update the recent changes table
+ * @param $report Don't change things; just echo numbers
+ * @return integer Number of entries changed, or that would be changed
+ */
+ private function doReassignEdits( &$from, &$to, $rc = false, $report = false ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+
+ # Count things
+ $this->output( "Checking current edits..." );
+ $res = $dbw->select( 'revision', 'COUNT(*) AS count', $this->userConditions( $from, 'rev_user', 'rev_user_text' ), __METHOD__ );
+ $row = $dbw->fetchObject( $res );
+ $cur = $row->count;
+ $this->output( "found {$cur}.\n" );
+
+ $this->output( "Checking deleted edits..." );
+ $res = $dbw->select( 'archive', 'COUNT(*) AS count', $this->userConditions( $from, 'ar_user', 'ar_user_text' ), __METHOD__ );
+ $row = $dbw->fetchObject( $res );
+ $del = $row->count;
+ $this->output( "found {$del}.\n" );
+
+ # Don't count recent changes if we're not supposed to
+ if( $rc ) {
+ $this->output( "Checking recent changes..." );
+ $res = $dbw->select( 'recentchanges', 'COUNT(*) AS count', $this->userConditions( $from, 'rc_user', 'rc_user_text' ), __METHOD__ );
+ $row = $dbw->fetchObject( $res );
+ $rec = $row->count;
+ $this->output( "found {$rec}.\n" );
+ } else {
+ $rec = 0;
+ }
+
+ $total = $cur + $del + $rec;
+ $this->output( "\nTotal entries to change: {$total}\n" );
+
+ if( !$report ) {
+ if( $total ) {
+ # Reassign edits
+ $this->output( "\nReassigning current edits..." );
+ $res = $dbw->update( 'revision', $this->userSpecification( $to, 'rev_user', 'rev_user_text' ), $this->userConditions( $from, 'rev_user', 'rev_user_text' ), __METHOD__ );
+ $this->output( "done.\nReassigning deleted edits..." );
+ $res = $dbw->update( 'archive', $this->userSpecification( $to, 'ar_user', 'ar_user_text' ), $this->userConditions( $from, 'ar_user', 'ar_user_text' ), __METHOD__ );
+ $this->output( "done.\n" );
+ # Update recent changes if required
+ if( $rc ) {
+ $this->output( "Updating recent changes..." );
+ $res = $dbw->update( 'recentchanges', $this->userSpecification( $to, 'rc_user', 'rc_user_text' ), $this->userConditions( $from, 'rc_user', 'rc_user_text' ), __METHOD__ );
+ $this->output( "done.\n" );
+ }
+ }
+ }
+
+ $dbw->commit();
+ return (int)$total;
+ }
+
+ /**
+ * Return the most efficient set of user conditions
+ * i.e. a user => id mapping, or a user_text => text mapping
+ *
+ * @param $user User for the condition
+ * @param $idfield Field name containing the identifier
+ * @param $utfield Field name containing the user text
+ * @return array
+ */
+ private function userConditions( &$user, $idfield, $utfield ) {
+ return $user->getId() ? array( $idfield => $user->getId() ) : array( $utfield => $user->getName() );
+ }
+
+ /**
+ * Return user specifications
+ * i.e. user => id, user_text => text
+ *
+ * @param $user User for the spec
+ * @param $idfield Field name containing the identifier
+ * @param $utfield Field name containing the user text
+ * @return array
+ */
+ private function userSpecification( &$user, $idfield, $utfield ) {
+ return array( $idfield => $user->getId(), $utfield => $user->getName() );
+ }
+
+ /**
+ * Initialise the user object
+ *
+ * @param $username Username or IP address
+ * @return User
+ */
+ private function initialiseUser( $username ) {
+ if( User::isIP( $username ) ) {
+ $user = new User();
+ $user->setId( 0 );
+ $user->setName( $username );
+ } else {
+ $user = User::newFromName( $username );
+ }
+ $user->load();
+ return $user;
+ }
+
-/** Show script usage information */
-function ShowUsage() {
- echo( "Reassign edits from one user to another.\n\n" );
- echo( "Usage: php reassignEdits.php [--force|--quiet|--norc|--report] <from> <to>\n\n" );
- echo( " <from> : Name of the user to assign edits from\n" );
- echo( " <to> : Name of the user to assign edits to\n" );
- echo( " --force : Reassign even if the target user doesn't exist\n" );
- echo( " --quiet : Don't print status information (except for errors)\n" );
- echo( " --norc : Don't update the recent changes table\n" );
- echo( " --report : Print out details of what would be changed, but don't update it\n\n" );
}
+$maintClass = "ReassignEdits";
+require_once( DO_MAINTENANCE );
+
diff --git a/maintenance/rebuildFileCache.php b/maintenance/rebuildFileCache.php
index 8c01b90f..2a4e4884 100644
--- a/maintenance/rebuildFileCache.php
+++ b/maintenance/rebuildFileCache.php
@@ -2,91 +2,124 @@
/**
* Build file cache for content pages
*
- * @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
+ *
* @ingroup Maintenance
*/
-/** */
-require_once( "commandLine.inc" );
-if( !$wgUseFileCache ) {
- echo "Nothing to do -- \$wgUseFileCache is disabled.\n";
- exit(0);
-}
-$wgDisableCounters = false; // no real hits here
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$start = isset($args[0]) ? intval($args[0]) : 0;
-$overwrite = isset( $args[1] ) && $args[1] === 'overwrite';
-echo "Building content page file cache from page {$start}!\n";
-echo "Format: <start> [overwrite]\n";
+class RebuildFileCache extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Build file cache for content pages";
+ $this->addArg( 'start', 'Page_id to start from', true );
+ $this->addArg( 'overwrite', 'Refresh page cache', false );
+ $this->setBatchSize( 100 );
+ }
-$dbr = wfGetDB( DB_SLAVE );
-$start = $start > 0 ? $start : $dbr->selectField( 'page', 'MIN(page_id)', false, __FUNCTION__ );
-$end = $dbr->selectField( 'page', 'MAX(page_id)', false, __FUNCTION__ );
-if( !$start ) {
- die("Nothing to do.\n");
-}
+ public function execute() {
+ global $wgUseFileCache, $wgDisableCounters, $wgContentNamespaces;
+ global $wgTitle, $wgArticle, $wgOut, $wgUser;
+ if( !$wgUseFileCache ) {
+ $this->error( "Nothing to do -- \$wgUseFileCache is disabled.", true );
+ }
+ $wgDisableCounters = false;
+ $start = $this->getArg( 0, "0" );
+ if( !ctype_digit($start) ) {
+ $this->error( "Invalid value for start parameter.", true );
+ }
+ $start = intval($start);
+ $overwrite = $this->hasArg(1) && $this->getArg(1) === 'overwrite';
+ $this->output( "Building content page file cache from page {$start}!\n" );
-$_SERVER['HTTP_ACCEPT_ENCODING'] = 'bgzip'; // hack, no real client
-OutputPage::setEncodings(); # Not really used yet
+ $dbr = wfGetDB( DB_SLAVE );
+ $start = $start > 0 ? $start : $dbr->selectField( 'page', 'MIN(page_id)', false, __FUNCTION__ );
+ $end = $dbr->selectField( 'page', 'MAX(page_id)', false, __FUNCTION__ );
+ if( !$start ) {
+ $this->error( "Nothing to do.", true );
+ }
-$BATCH_SIZE = 100;
-# Do remaining chunk
-$end += $BATCH_SIZE - 1;
-$blockStart = $start;
-$blockEnd = $start + $BATCH_SIZE - 1;
+ $_SERVER['HTTP_ACCEPT_ENCODING'] = 'bgzip'; // hack, no real client
+ OutputPage::setEncodings(); # Not really used yet
-$dbw = wfGetDB( DB_MASTER );
-// Go through each page and save the output
-while( $blockEnd <= $end ) {
- // Get the pages
- $res = $dbr->select( 'page', array('page_namespace','page_title','page_id'),
- array('page_namespace' => $wgContentNamespaces,
- "page_id BETWEEN $blockStart AND $blockEnd" ),
- array('ORDER BY' => 'page_id ASC','USE INDEX' => 'PRIMARY')
- );
- while( $row = $dbr->fetchObject( $res ) ) {
- $rebuilt = false;
- $wgTitle = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
- if( null == $wgTitle ) {
- echo "Page {$row->page_id} bad title\n";
- continue; // broken title?
- }
- $wgArticle = new Article( $wgTitle );
- // If the article is cacheable, then load it
- if( $wgArticle->isFileCacheable() ) {
- $cache = new HTMLFileCache( $wgTitle );
- if( $cache->isFileCacheGood() ) {
- if( $overwrite ) {
- $rebuilt = true;
+ # Do remaining chunk
+ $end += $this->mBatchSize - 1;
+ $blockStart = $start;
+ $blockEnd = $start + $this->mBatchSize - 1;
+
+ $dbw = wfGetDB( DB_MASTER );
+ // Go through each page and save the output
+ while( $blockEnd <= $end ) {
+ // Get the pages
+ $res = $dbr->select( 'page', array('page_namespace','page_title','page_id'),
+ array('page_namespace' => $wgContentNamespaces,
+ "page_id BETWEEN $blockStart AND $blockEnd" ),
+ array('ORDER BY' => 'page_id ASC','USE INDEX' => 'PRIMARY')
+ );
+ foreach( $res as $row ) {
+ $rebuilt = false;
+ $wgTitle = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
+ if( null == $wgTitle ) {
+ $this->output( "Page {$row->page_id} has bad title\n" );
+ continue; // broken title?
+ }
+ $wgOut->setTitle( $wgTitle ); // set display title
+ $wgUser->getSkin( $wgTitle ); // set skin title
+ $wgArticle = new Article( $wgTitle );
+ // If the article is cacheable, then load it
+ if( $wgArticle->isFileCacheable() ) {
+ $cache = new HTMLFileCache( $wgTitle );
+ if( $cache->isFileCacheGood() ) {
+ if( $overwrite ) {
+ $rebuilt = true;
+ } else {
+ $this->output( "Page {$row->page_id} already cached\n" );
+ continue; // done already!
+ }
+ }
+ ob_start( array(&$cache, 'saveToFileCache' ) ); // save on ob_end_clean()
+ $wgUseFileCache = false; // hack, we don't want $wgArticle fiddling with filecache
+ $wgArticle->view();
+ @$wgOut->output(); // header notices
+ $wgUseFileCache = true;
+ ob_end_clean(); // clear buffer
+ $wgOut = new OutputPage(); // empty out any output page garbage
+ if( $rebuilt )
+ $this->output( "Re-cached page {$row->page_id}\n" );
+ else
+ $this->output( "Cached page {$row->page_id}\n" );
} else {
- echo "Page {$row->page_id} already cached\n";
- continue; // done already!
+ $this->output( "Page {$row->page_id} not cacheable\n" );
}
+ $dbw->commit(); // commit any changes
}
- ob_start( array(&$cache, 'saveToFileCache' ) ); // save on ob_end_clean()
- $wgUseFileCache = false; // hack, we don't want $wgArticle fiddling with filecache
- $wgArticle->view();
- @$wgOut->output(); // header notices
- $wgUseFileCache = true;
- ob_end_clean(); // clear buffer
- $wgOut = new OutputPage(); // empty out any output page garbage
- if( $rebuilt )
- echo "Re-cached page {$row->page_id}\n";
- else
- echo "Cached page {$row->page_id}\n";
- } else {
- echo "Page {$row->page_id} not cacheable\n";
+ $blockStart += $this->mBatchSize;
+ $blockEnd += $this->mBatchSize;
+ wfWaitForSlaves( 5 );
}
- $dbw->commit(); // commit any changes
+ $this->output( "Done!\n" );
+
+ // Remove these to be safe
+ if( isset($wgTitle) )
+ unset($wgTitle);
+ if( isset($wgArticle) )
+ unset($wgArticle);
}
- $blockStart += $BATCH_SIZE;
- $blockEnd += $BATCH_SIZE;
- wfWaitForSlaves( 5 );
}
-echo "Done!\n";
-// Remove these to be safe
-if( isset($wgTitle) )
- unset($wgTitle);
-if( isset($wgArticle) )
- unset($wgArticle);
+$maintClass = "RebuildFileCache";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/rebuildImages.php b/maintenance/rebuildImages.php
index 46b5d0ed..0d3bdb3f 100644
--- a/maintenance/rebuildImages.php
+++ b/maintenance/rebuildImages.php
@@ -32,7 +32,7 @@
$options = array( 'missing', 'dry-run' );
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
require_once( 'FiveUpgrade.inc' );
class ImageBuilder extends FiveUpgrade {
@@ -104,7 +104,7 @@ class ImageBuilder extends FiveUpgrade {
$result = $this->dbr->query( $sql, $fname );
while( $row = $this->dbr->fetchObject( $result ) ) {
- $update = call_user_func( $callback, $row );
+ $update = call_user_func( $callback, $row, null );
if( $update ) {
$this->progress( 1 );
} else {
@@ -120,7 +120,7 @@ class ImageBuilder extends FiveUpgrade {
$this->buildTable( 'image', 'img_name', $callback );
}
- function imageCallback( $row ) {
+ function imageCallback( $row, $copy ) {
// Create a File object from the row
// This will also upgrade it
$file = $this->getRepo()->newFileFromRow( $row );
@@ -132,7 +132,7 @@ class ImageBuilder extends FiveUpgrade {
array( &$this, 'oldimageCallback' ) );
}
- function oldimageCallback( $row ) {
+ function oldimageCallback( $row, $copy ) {
// Create a File object from the row
// This will also upgrade it
if ( $row->oi_archive_name == '' ) {
diff --git a/maintenance/rebuildInterwiki.inc b/maintenance/rebuildInterwiki.inc
index 923c25a9..93261f86 100644
--- a/maintenance/rebuildInterwiki.inc
+++ b/maintenance/rebuildInterwiki.inc
@@ -6,6 +6,7 @@
* @file
* @todo document
* @ingroup Maintenance
+ * @ingroup Wikimedia
*/
/**
diff --git a/maintenance/rebuildInterwiki.php b/maintenance/rebuildInterwiki.php
index 8a25ce3c..d3f3a4d2 100644
--- a/maintenance/rebuildInterwiki.php
+++ b/maintenance/rebuildInterwiki.php
@@ -6,13 +6,14 @@
* @file
* @todo document
* @ingroup Maintenance
+ * @ingroup Wikimedia
*/
/** */
$oldCwd = getcwd();
$optionsWithArgs = array( "d" );
-require( "commandLine.inc" );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
require( "rebuildInterwiki.inc" );
chdir( $oldCwd );
diff --git a/maintenance/rebuildLocalisationCache.php b/maintenance/rebuildLocalisationCache.php
new file mode 100644
index 00000000..1c517415
--- /dev/null
+++ b/maintenance/rebuildLocalisationCache.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * Rebuild the localisation cache. Useful if you disabled automatic updates
+ * using $wgLocalisationCacheConf['manualRecache'] = true;
+ *
+ * Usage:
+ * php rebuildLocalisationCache.php [--force] [--threads=N]
+ *
+ * Use --force to rebuild all files, even the ones that are not out of date.
+ * Use --threads=N to fork more threads.
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class RebuildLocalisationCache extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Rebuild the localisation cache";
+ $this->addOption( 'force', 'Rebuild all files, even ones not out of date' );
+ $this->addOption( 'threads', 'Fork more than one thread', false, true );
+ }
+
+ public function memoryLimit() {
+ return '200M';
+ }
+
+ public function execute() {
+ global $wgLocalisationCacheConf;
+
+ $force = $this->hasOption('force');
+ $threads = $this->getOption( 'threads', 1 );
+ if( $threads < 1 || $threads != intval( $threads ) ) {
+ $this->output( "Invalid thread count specified; running single-threaded.\n" );
+ $threads = 1;
+ }
+ if( $threads > 1 && wfIsWindows() ) {
+ $this->output( "Threaded rebuild is not supported on Windows; running single-threaded.\n" );
+ $threads = 1;
+ }
+ if( $threads > 1 && !function_exists( 'pcntl_fork' ) ) {
+ $this->output( "PHP pcntl extension is not present; running single-threaded.\n" );
+ $threads = 1;
+ }
+
+ $conf = $wgLocalisationCacheConf;
+ $conf['manualRecache'] = false; // Allow fallbacks to create CDB files
+ if ( $force ) {
+ $conf['forceRecache'] = true;
+ }
+ $lc = new LocalisationCache_BulkLoad( $conf );
+
+ $codes = array_keys( Language::getLanguageNames( true ) );
+ sort( $codes );
+
+ // Initialise and split into chunks
+ $numRebuilt = 0;
+ $total = count($codes);
+ $chunks = array_chunk( $codes, ceil(count($codes)/$threads) );
+ $pids = array();
+ foreach ( $chunks as $codes ) {
+ // Do not fork for only one thread
+ $pid = ( $threads > 1 ) ? pcntl_fork() : -1;
+
+ if ( $pid === 0 ) {
+ // Child, reseed because there is no bug in PHP:
+ // http://bugs.php.net/bug.php?id=42465
+ mt_srand(getmypid());
+ $numRebuilt = $this->doRebuild( $codes, $lc, $force );
+ // Abuse the exit value for the count of rebuild languages
+ exit($numRebuilt);
+ } elseif ($pid === -1) {
+ // Fork failed or one thread, do it serialized
+ $numRebuilt += $this->doRebuild( $codes, $lc, $force );
+ } else {
+ // Main thread
+ $pids[] = $pid;
+ }
+ }
+ // Wait for all children
+ foreach ( $pids as $pid ) {
+ $status = 0;
+ pcntl_waitpid($pid, $status);
+ // Fetch the count from the return value
+ $numRebuilt += pcntl_wexitstatus($status);
+ }
+
+ $this->output( "$numRebuilt languages rebuilt out of $total\n" );
+ if ( $numRebuilt === 0 ) {
+ $this->output( "Use --force to rebuild the caches which are still fresh.\n" );
+ }
+ }
+
+ /**
+ * Helper function to rebuild list of languages codes. Prints the code
+ * for each language which is rebuilt.
+ * @param $codes list List of language codes to rebuild.
+ * @param $lc object Instance of LocalisationCache_BulkLoad (?)
+ * @param $force bool Rebuild up-to-date languages
+ * @return int Number of rebuilt languages
+ */
+ private function doRebuild( $codes, $lc, $force ) {
+ $numRebuilt = 0;
+ foreach ( $codes as $code ) {
+ if ( $force || $lc->isExpired( $code ) ) {
+ $this->output( "Rebuilding $code...\n" );
+ $lc->recache( $code );
+ $numRebuilt++;
+ }
+ }
+ return $numRebuilt;
+ }
+}
+
+$maintClass = "RebuildLocalisationCache";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/rebuildall.php b/maintenance/rebuildall.php
index ca7e4c06..a2c1be93 100644
--- a/maintenance/rebuildall.php
+++ b/maintenance/rebuildall.php
@@ -3,40 +3,54 @@
* Rebuild link tracking tables from scratch. This takes several
* hours, depending on the database size and server configuration.
*
- * @file
- * @todo document
+ * 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
+ *
* @ingroup Maintenance
*/
-/** */
-require_once( "commandLine.inc" );
-
-#require_once( "rebuildlinks.inc" );
-require_once( "refreshLinks.inc" );
-require_once( "rebuildtextindex.inc" );
-require_once( "rebuildrecentchanges.inc" );
-
-$dbclass = 'Database' . ucfirst( $wgDBtype ) ;
-$database = new $dbclass( $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname );
-
-if ($wgDBtype == 'mysql') {
- print "** Rebuilding fulltext search index (if you abort this will break searching; run this script again to fix):\n";
- dropTextIndex( $database );
- rebuildTextIndex( $database );
- createTextIndex( $database );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class RebuildAll extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Rebuild links, text index and recent changes";
+ }
+
+ public function execute() {
+ global $wgDBtype;
+ // Rebuild the text index
+ if ( $wgDBtype != 'postgres' ) {
+ $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();
+ }
+
+ // Rebuild RC
+ $this->output( "\n\n** Rebuilding recentchanges table:\n" );
+ $rebuildRC = $this->runChild( 'RebuildRecentchanges', 'rebuildrecentchanges.php' );
+ $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" );
+ $rebuildLinks = $this->runChild( 'RefreshLinks', 'refreshLinks.php' );
+ $rebuildLinks->execute();
+
+ $this->output( "Done.\n" );
+ }
}
-print "\n\n** Rebuilding recentchanges table:\n";
-rebuildRecentChangesTable();
-
-# Doesn't work anymore
-# rebuildLinkTables();
-
-# Use the slow incomplete one instead. It's designed to work in the background
-print "\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";
-refreshLinks( 1 );
-
-print "Done.\n";
-exit();
-
-
+$maintClass = "RebuildAll";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/rebuildmessages.php b/maintenance/rebuildmessages.php
index b0adc875..546d5214 100644
--- a/maintenance/rebuildmessages.php
+++ b/maintenance/rebuildmessages.php
@@ -1,22 +1,50 @@
<?php
/**
- * This script purges all language messages from memcached
- * @file
+ * This script purges all language messages from the cache
+ *
+ * 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
+ *
* @ingroup Maintenance
*/
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if( $wgLocalDatabases ) {
- $databases = $wgLocalDatabases;
-} else {
- $databases = array( $wgDBname );
-}
+class RebuildMessages extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Purge all language messages from the cache";
+ }
-foreach( $databases as $db ) {
- echo "Deleting message cache for {$db}... ";
- $messageMemc->delete( "{$db}:messages" );
- if( $wgEnableSidebarCache )
- $messageMemc->delete( "{$db}:sidebar" );
- echo "Deleted\n";
+ public function execute() {
+ global $wgLocalDatabases, $wgDBname, $wgEnableSidebarCache, $messageMemc;
+ if( $wgLocalDatabases ) {
+ $databases = $wgLocalDatabases;
+ } else {
+ $databases = array( $wgDBname );
+ }
+
+ foreach( $databases as $db ) {
+ $this->output( "Deleting message cache for {$db}... " );
+ $messageMemc->delete( "{$db}:messages" );
+ if( $wgEnableSidebarCache )
+ $messageMemc->delete( "{$db}:sidebar" );
+ $this->output( "Deleted\n" );
+ }
+ }
}
+
+$maintClass = "RebuildMessages";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/rebuildrecentchanges.inc b/maintenance/rebuildrecentchanges.inc
deleted file mode 100644
index f846d2b7..00000000
--- a/maintenance/rebuildrecentchanges.inc
+++ /dev/null
@@ -1,246 +0,0 @@
-<?php
-/**
- * Rebuild recent changes table.
- *
- * @file
- * @todo document
- * @ingroup Maintenance
- */
-
-/** Public entry; more passes might come in! :) */
-function rebuildRecentChangesTable() {
- rebuildRecentChangesTablePass1();
- rebuildRecentChangesTablePass2();
- rebuildRecentChangesTablePass3();
- rebuildRecentChangesTablePass4();
-}
-
-/** */
-function rebuildRecentChangesTablePass1()
-{
- $dbw = wfGetDB( DB_MASTER );
-
- $dbw->delete( 'recentchanges', '*' );
-
- print( "Loading from page and revision tables...\n" );
-
- global $wgRCMaxAge;
-
- print( '$wgRCMaxAge=' . $wgRCMaxAge );
- $days = $wgRCMaxAge / 24 / 3600;
- if ( intval($days) == $days ) {
- print( " (" . $days . " days)\n" );
- } else {
- print( " (approx. " . intval($days) . " days)\n" );
- }
-
- $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_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(
- 'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $cutoff ) ),
- 'rev_page=page_id'
- ), __METHOD__,
- array(), // INSERT options
- array( 'ORDER BY' => 'rev_timestamp DESC', 'LIMIT' => 5000 ) // SELECT options
- );
-}
-
-function rebuildRecentChangesTablePass2()
-{
- $dbw = wfGetDB( DB_MASTER );
- list ($recentchanges, $revision) = $dbw->tableNamesN( 'recentchanges', 'revision' );
-
- print( "Updating links and size differences...\n" );
-
- # Fill in the rc_last_oldid field, which points to the previous edit
- $sql = "SELECT rc_cur_id,rc_this_oldid,rc_timestamp FROM $recentchanges " .
- "ORDER BY rc_cur_id,rc_timestamp";
- $res = $dbw->query( $sql, DB_MASTER );
-
- $lastCurId = 0;
- $lastOldId = 0;
- while ( $obj = $dbw->fetchObject( $res ) ) {
- $new = 0;
- if( $obj->rc_cur_id != $lastCurId ) {
- # Switch! Look up the previous last edit, if any
- $lastCurId = intval( $obj->rc_cur_id );
- $emit = $obj->rc_timestamp;
- $sql2 = "SELECT rev_id,rev_len FROM $revision " .
- "WHERE rev_page={$lastCurId} ".
- "AND rev_timestamp<'{$emit}' ORDER BY rev_timestamp DESC LIMIT 1";
- $res2 = $dbw->query( $sql2 );
- if( $row = $dbw->fetchObject( $res2 ) ) {
- $lastOldId = intval($row->rev_id);
- # Grab the last text size if available
- $lastSize = !is_null($row->rev_len) ? intval($row->rev_len) : 'NULL';
- } else {
- # No previous edit
- $lastOldId = 0;
- $lastSize = 'NULL';
- $new = 1; // probably true
- }
- $dbw->freeResult( $res2 );
- }
- if( $lastCurId == 0 ) {
- print "Uhhh, something wrong? No curid\n";
- } else {
- # Grab the entry's text size
- $size = $dbw->selectField( 'revision', 'rev_len', array('rev_id' => $obj->rc_this_oldid ) );
- $size = !is_null($size) ? intval($size) : 'NULL';
-
- $sql3 = "UPDATE $recentchanges SET rc_last_oldid=$lastOldId,rc_new=$new,rc_type=$new," .
- "rc_old_len=$lastSize,rc_new_len=$size " .
- "WHERE rc_cur_id={$lastCurId} AND rc_this_oldid={$obj->rc_this_oldid}";
- $dbw->query( $sql3 );
-
- $lastOldId = intval( $obj->rc_this_oldid );
- }
- }
- $dbw->freeResult( $res );
-}
-
-function rebuildRecentChangesTablePass3()
-{
- $dbw = wfGetDB( DB_MASTER );
-
- print( "Loading from user, page, and logging tables...\n" );
-
- global $wgRCMaxAge;
- // Some logs don't go in RC. This can't really detect all of those.
- // At least do the basics logs for a standard install...
- // FIXME: this needs to be maintained
- $basicRCLogs = array(
- 'block',
- 'protect',
- 'rights',
- 'delete',
- 'upload',
- 'move',
- 'import',
- 'merge' );
- // Escape...blah blah
- $selectLogs = array();
- foreach( $basicRCLogs as $logtype ) {
- $safetype = $dbw->strencode( $logtype );
- $selectLogs[] = "'$safetype'";
- }
-
- $cutoff = time() - $wgRCMaxAge;
- $dbw->insertSelect( 'recentchanges', array( 'logging', 'page', 'user' ),
- 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,
- 'rc_this_oldid' => 0,
- 'rc_last_oldid' => 0,
- 'rc_type' => RC_LOG,
- 'rc_cur_id' => 'page_id',
- 'rc_log_type' => 'log_type',
- 'rc_log_action' => 'log_action',
- '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_namespace=page_namespace',
- 'log_title=page_title',
- 'log_type IN(' . implode(',',$selectLogs) . ')'
- ), __METHOD__,
- array(), // INSERT options
- array( 'ORDER BY' => 'log_timestamp DESC', 'LIMIT' => 5000 ) // SELECT options
- );
-}
-
-function rebuildRecentChangesTablePass4()
-{
- global $wgGroupPermissions, $wgUseRCPatrol;
-
- $dbw = wfGetDB( DB_MASTER );
-
- list($recentchanges,$usergroups,$user) = $dbw->tableNamesN( 'recentchanges', 'user_groups', 'user' );
-
- $botgroups = $autopatrolgroups = array();
- foreach( $wgGroupPermissions as $group => $rights ) {
- if( isset( $rights['bot'] ) && $rights['bot'] == true ) {
- $botgroups[] = $dbw->addQuotes( $group );
- }
- if( $wgUseRCPatrol && isset( $rights['autopatrol'] ) && $rights['autopatrol'] == true ) {
- $autopatrolgroups[] = $dbw->addQuotes( $group );
- }
- }
- # Flag our recent bot edits
- if( !empty($botgroups) ) {
- $botwhere = implode(',',$botgroups);
- $botusers = array();
-
- print( "Flagging bot account edits...\n" );
-
- # Find all users that are bots
- $sql = "SELECT DISTINCT user_name FROM $usergroups, $user " .
- "WHERE ug_group IN($botwhere) AND user_id = ug_user";
- $res = $dbw->query( $sql, DB_MASTER );
-
- while( $obj = $dbw->fetchObject( $res ) ) {
- $botusers[] = $dbw->addQuotes( $obj->user_name );
- }
- # Fill in the rc_bot field
- if( !empty($botusers) ) {
- $botwhere = implode(',',$botusers);
- $sql2 = "UPDATE $recentchanges SET rc_bot=1 " .
- "WHERE rc_user_text IN($botwhere)";
- $dbw->query( $sql2 );
- }
- }
- global $wgMiserMode;
- # Flag our recent autopatrolled edits
- if( !$wgMiserMode && !empty($autopatrolgroups) ) {
- $patrolwhere = implode(',',$autopatrolgroups);
- $patrolusers = array();
-
- print( "Flagging auto-patrolled edits...\n" );
-
- # Find all users in RC with autopatrol rights
- $sql = "SELECT DISTINCT user_name FROM $usergroups, $user " .
- "WHERE ug_group IN($patrolwhere) AND user_id = ug_user";
- $res = $dbw->query( $sql, DB_MASTER );
-
- while( $obj = $dbw->fetchObject( $res ) ) {
- $patrolusers[] = $dbw->addQuotes( $obj->user_name );
- }
-
- # Fill in the rc_patrolled field
- if( !empty($patrolusers) ) {
- $patrolwhere = implode(',',$patrolusers);
- $sql2 = "UPDATE $recentchanges SET rc_patrolled=1 " .
- "WHERE rc_user_text IN($patrolwhere)";
- $dbw->query( $sql2 );
- }
- }
-
- $dbw->freeResult( $res );
-}
diff --git a/maintenance/rebuildrecentchanges.php b/maintenance/rebuildrecentchanges.php
index 9311d0fa..6c76dc59 100644
--- a/maintenance/rebuildrecentchanges.php
+++ b/maintenance/rebuildrecentchanges.php
@@ -3,22 +3,292 @@
* Rebuild link tracking tables from scratch. This takes several
* hours, depending on the database size and server configuration.
*
- * @file
- * @todo document
+ * 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
+ *
* @ingroup Maintenance
+ * @todo Document
*/
-/** */
-require_once( "commandLine.inc" );
-require_once( "rebuildrecentchanges.inc" );
-$wgTitle = Title::newFromText( "Rebuild recent changes script" );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class RebuildRecentchanges extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Rebuild recent changes";
+ }
+
+ public function execute() {
+ global $wgTitle;
+ $wgTitle = Title::newFromText( "Rebuild recent changes script" );
+ $this->rebuildRecentChangesTablePass1();
+ $this->rebuildRecentChangesTablePass2();
+ $this->rebuildRecentChangesTablePass3();
+ $this->rebuildRecentChangesTablePass4();
+ $this->purgeFeeds();
+ $this->output( "Done.\n" );
+ }
+
+ /**
+ * Rebuild pass 1
+ * DOCUMENT ME!
+ */
+ function rebuildRecentChangesTablePass1()
+ {
+ $dbw = wfGetDB( DB_MASTER );
+
+ $dbw->delete( 'recentchanges', '*' );
+
+ $this->output( "Loading from page and revision tables...\n" );
+
+ global $wgRCMaxAge;
+
+ $this->output( '$wgRCMaxAge=' . $wgRCMaxAge );
+ $days = $wgRCMaxAge / 24 / 3600;
+ if ( intval($days) == $days ) {
+ $this->output( " (" . $days . " days)\n" );
+ } else {
+ $this->output( " (approx. " . intval($days) . " days)\n" );
+ }
+
+ $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_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(
+ 'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $cutoff ) ),
+ 'rev_page=page_id'
+ ), __METHOD__,
+ array(), // INSERT options
+ array( 'ORDER BY' => 'rev_timestamp DESC', 'LIMIT' => 5000 ) // SELECT options
+ );
+ }
+
+ /**
+ * Rebuild pass 2
+ * DOCUMENT ME!
+ */
+ private function rebuildRecentChangesTablePass2() {
+ $dbw = wfGetDB( DB_MASTER );
+ list ($recentchanges, $revision) = $dbw->tableNamesN( 'recentchanges', 'revision' );
+
+ $this->output( "Updating links and size differences...\n" );
+
+ # Fill in the rc_last_oldid field, which points to the previous edit
+ $sql = "SELECT rc_cur_id,rc_this_oldid,rc_timestamp FROM $recentchanges " .
+ "ORDER BY rc_cur_id,rc_timestamp";
+ $res = $dbw->query( $sql, DB_MASTER );
+
+ $lastCurId = 0;
+ $lastOldId = 0;
+ foreach ( $res as $obj ) {
+ $new = 0;
+ if( $obj->rc_cur_id != $lastCurId ) {
+ # Switch! Look up the previous last edit, if any
+ $lastCurId = intval( $obj->rc_cur_id );
+ $emit = $obj->rc_timestamp;
+ $sql2 = "SELECT rev_id,rev_len FROM $revision " .
+ "WHERE rev_page={$lastCurId} ".
+ "AND rev_timestamp<'{$emit}' ORDER BY rev_timestamp DESC";
+ $sql2 = $dbw->limitResult($sql2, 1, false);
+ $res2 = $dbw->query( $sql2 );
+ if( $row = $dbw->fetchObject( $res2 ) ) {
+ $lastOldId = intval($row->rev_id);
+ # Grab the last text size if available
+ $lastSize = !is_null($row->rev_len) ? intval($row->rev_len) : 'NULL';
+ } else {
+ # No previous edit
+ $lastOldId = 0;
+ $lastSize = 'NULL';
+ $new = 1; // probably true
+ }
+ $dbw->freeResult( $res2 );
+ }
+ if( $lastCurId == 0 ) {
+ $this->output( "Uhhh, something wrong? No curid\n" );
+ } else {
+ # Grab the entry's text size
+ $size = $dbw->selectField( 'revision', 'rev_len', array('rev_id' => $obj->rc_this_oldid ) );
+ $size = !is_null($size) ? intval($size) : 'NULL';
+
+ $sql3 = "UPDATE $recentchanges SET rc_last_oldid=$lastOldId,rc_new=$new,rc_type=$new," .
+ "rc_old_len=$lastSize,rc_new_len=$size " .
+ "WHERE rc_cur_id={$lastCurId} AND rc_this_oldid={$obj->rc_this_oldid}";
+ $dbw->query( $sql3 );
+
+ $lastOldId = intval( $obj->rc_this_oldid );
+ $lastSize = $size;
+ }
+ }
+ $dbw->freeResult( $res );
+ }
+
+ /**
+ * Rebuild pass 3
+ * DOCUMENT ME!
+ */
+ private function rebuildRecentChangesTablePass3() {
+ $dbw = wfGetDB( DB_MASTER );
+
+ $this->output( "Loading from user, page, and logging tables...\n" );
+
+ global $wgRCMaxAge, $wgLogTypes, $wgLogRestrictions;
+ // 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)" ),
+ 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,
+ '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_log_action' => 'log_action',
+ '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__,
+ array(), // INSERT options
+ array( 'ORDER BY' => 'log_timestamp DESC', 'LIMIT' => 5000 ) // SELECT options
+ );
+ }
+
+ /**
+ * Rebuild pass 4
+ * DOCUMENT ME!
+ */
+ private function rebuildRecentChangesTablePass4() {
+ global $wgGroupPermissions, $wgUseRCPatrol;
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ list($recentchanges,$usergroups,$user) = $dbw->tableNamesN( 'recentchanges', 'user_groups', 'user' );
+
+ $botgroups = $autopatrolgroups = array();
+ foreach( $wgGroupPermissions as $group => $rights ) {
+ if( isset( $rights['bot'] ) && $rights['bot'] == true ) {
+ $botgroups[] = $dbw->addQuotes( $group );
+ }
+ if( $wgUseRCPatrol && isset( $rights['autopatrol'] ) && $rights['autopatrol'] == true ) {
+ $autopatrolgroups[] = $dbw->addQuotes( $group );
+ }
+ }
+ # Flag our recent bot edits
+ if( !empty($botgroups) ) {
+ $botwhere = implode(',',$botgroups);
+ $botusers = array();
+
+ $this->output( "Flagging bot account edits...\n" );
+
+ # Find all users that are bots
+ $sql = "SELECT DISTINCT user_name FROM $usergroups, $user " .
+ "WHERE ug_group IN($botwhere) AND user_id = ug_user";
+ $res = $dbw->query( $sql, DB_MASTER );
+
+ foreach( $res as $obj ) {
+ $botusers[] = $dbw->addQuotes( $obj->user_name );
+ }
+ # Fill in the rc_bot field
+ if( !empty($botusers) ) {
+ $botwhere = implode(',',$botusers);
+ $sql2 = "UPDATE $recentchanges SET rc_bot=1 " .
+ "WHERE rc_user_text IN($botwhere)";
+ $dbw->query( $sql2 );
+ }
+ }
+ global $wgMiserMode;
+ # Flag our recent autopatrolled edits
+ if( !$wgMiserMode && !empty($autopatrolgroups) ) {
+ $patrolwhere = implode(',',$autopatrolgroups);
+ $patrolusers = array();
+
+ $this->output( "Flagging auto-patrolled edits...\n" );
+
+ # Find all users in RC with autopatrol rights
+ $sql = "SELECT DISTINCT user_name FROM $usergroups, $user " .
+ "WHERE ug_group IN($patrolwhere) AND user_id = ug_user";
+ $res = $dbw->query( $sql, DB_MASTER );
+
+ foreach( $res as $obj ) {
+ $patrolusers[] = $dbw->addQuotes( $obj->user_name );
+ }
+
+ # Fill in the rc_patrolled field
+ if( !empty($patrolusers) ) {
+ $patrolwhere = implode(',',$patrolusers);
+ $sql2 = "UPDATE $recentchanges SET rc_patrolled=1 " .
+ "WHERE rc_user_text IN($patrolwhere)";
+ $dbw->query( $sql2 );
+ }
+ }
+
+ $dbw->freeResult( $res );
+ }
-$wgDBuser = $wgDBadminuser;
-$wgDBpassword = $wgDBadminpassword;
+ /**
+ * Purge cached feeds in $messageMemc
+ */
+ private function purgeFeeds() {
+ global $wgFeedClasses, $messageMemc;
-rebuildRecentChangesTable();
+ $this->output( "Deleting feed timestamps.\n" );
-print "Done.\n";
-exit();
+ foreach( $wgFeedClasses as $feed => $className ) {
+ $messageMemc->delete( wfMemcKey( 'rcfeed', $feed, 'timestamp' ) ); # Good enough for now.
+ }
+ }
+}
+$maintClass = "RebuildRecentchanges";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/rebuildtextindex.inc b/maintenance/rebuildtextindex.inc
deleted file mode 100644
index 0f58fffa..00000000
--- a/maintenance/rebuildtextindex.inc
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-require_once 'counter.php';
-/**
- * Rebuild the fulltext search indexes. This may take a while
- * depending on the database size and server configuration.
- *
- * Rebuilding is faster if you drop the index and recreate it,
- * but that will prevent searches from working while it runs.
- *
- * @file
- * @todo document
- * @ingroup Maintenance
- */
-
-/** */
-define( "RTI_CHUNK_SIZE", 500 );
-
-function dropTextIndex( &$database )
-{
- $searchindex = $database->tableName( 'searchindex' );
- if ( $database->indexExists( "searchindex", "si_title" ) ) {
- echo "Dropping index...\n";
- $sql = "ALTER TABLE $searchindex DROP INDEX si_title, DROP INDEX si_text";
- $database->query($sql, "dropTextIndex" );
- }
-}
-
-function createTextIndex( &$database )
-{
- $searchindex = $database->tableName( 'searchindex' );
- echo "\nRebuild the index...\n";
- $sql = "ALTER TABLE $searchindex ADD FULLTEXT si_title (si_title), " .
- "ADD FULLTEXT si_text (si_text)";
- $database->query($sql, "createTextIndex" );
-}
-
-function rebuildTextIndex( &$database )
-{
- list ($page, $revision, $text, $searchindex) = $database->tableNamesN( 'page', 'revision', 'text', 'searchindex' );
-
- $sql = "SELECT MAX(page_id) AS count FROM $page";
- $res = $database->query($sql, "rebuildTextIndex" );
- $s = $database->fetchObject($res);
- $count = $s->count;
- echo "Rebuilding index fields for {$count} pages...\n";
- $n = 0;
-
- while ( $n < $count ) {
- print_c( $n - 1, $n);
- $end = $n + RTI_CHUNK_SIZE - 1;
- $sql = "SELECT page_id, page_namespace, page_title, old_flags, old_text
- FROM $page, $revision, $text
- WHERE page_id BETWEEN $n AND $end
- AND page_latest=rev_id
- AND rev_text_id=old_id";
- $res = $database->query($sql, "rebuildTextIndex" );
-
- while( $s = $database->fetchObject($res) ) {
- $revtext = Revision::getRevisionText( $s );
- $u = new SearchUpdate( $s->page_id, $s->page_title, $revtext );
- $u->doUpdate();
- }
- $database->freeResult( $res );
- $n += RTI_CHUNK_SIZE;
- }
-}
diff --git a/maintenance/rebuildtextindex.php b/maintenance/rebuildtextindex.php
index 73dca87f..4521c6f5 100644
--- a/maintenance/rebuildtextindex.php
+++ b/maintenance/rebuildtextindex.php
@@ -1,33 +1,129 @@
<?php
/**
- * Rebuild search index table from scratch. This takes several
+ * Rebuild search index table from scratch. This may take several
* hours, depending on the database size and server configuration.
*
- * This is only for MySQL (see bug 9905).
* Postgres is trigger-based and should never need rebuilding.
*
- * @file
- * @todo document
+ * 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
+ *
* @ingroup Maintenance
+ * @todo document
*/
-/** */
-require_once( "commandLine.inc" );
-require_once( "rebuildtextindex.inc" );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$database = wfGetDB( DB_MASTER );
-if( !$database instanceof DatabaseMysql ) {
- print "This script is only for MySQL.\n";
- exit();
-}
+class RebuildTextIndex extends Maintenance {
+ const RTI_CHUNK_SIZE = 500;
+ private $db;
-$wgTitle = Title::newFromText( "Rebuild text index script" );
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Rebuild search index table from scratch";
+ }
-dropTextIndex( $database );
-rebuildTextIndex( $database );
-createTextIndex( $database );
+ public function getDbType() {
+ return Maintenance::DB_ADMIN;
+ }
-print "Done.\n";
-exit();
+ public function execute() {
+ global $wgTitle, $wgDBtype;
+ // Shouldn't be needed for Postgres
+ if ( $wgDBtype == 'postgres' ) {
+ $this->error( "This script is not needed when using Postgres.\n", true );
+ }
+
+ $this->db = wfGetDB( DB_MASTER );
+ $wgTitle = Title::newFromText( "Rebuild text index script" );
+
+ if ( $wgDBtype == 'mysql' ) {
+ $this->dropMysqlTextIndex();
+ $this->populateSearchIndex();
+ $this->createMysqlTextIndex();
+ } else {
+ $this->clearSearchIndex();
+ $this->populateSearchIndex();
+ }
+
+ $this->output( "Done.\n" );
+ }
+
+ /**
+ * Populates the search index with content from all pages
+ */
+ protected function populateSearchIndex() {
+ $res = $this->db->select( 'page', 'MAX(page_id) AS count' );
+ $s = $this->db->fetchObject($res);
+ $count = $s->count;
+ $this->output( "Rebuilding index fields for {$count} pages...\n" );
+ $n = 0;
+
+ while ( $n < $count ) {
+ $this->output( $n . "\n" );
+ $end = $n + self::RTI_CHUNK_SIZE - 1;
+
+ $res = $this->db->select( array( 'page', 'revision', 'text' ),
+ array( 'page_id', 'page_namespace', 'page_title', 'old_flags', 'old_text' ),
+ array( "page_id BETWEEN $n AND $end", 'page_latest = rev_id', 'rev_text_id = old_id' ),
+ __METHOD__
+ );
+
+ foreach( $res as $s ) {
+ $revtext = Revision::getRevisionText( $s );
+ $u = new SearchUpdate( $s->page_id, $s->page_title, $revtext );
+ $u->doUpdate();
+ }
+ $this->db->freeResult( $res );
+ $n += self::RTI_CHUNK_SIZE;
+ }
+ }
+
+ /**
+ * (MySQL only) Drops fulltext index before populating the table.
+ */
+ private function dropMysqlTextIndex() {
+ $searchindex = $this->db->tableName( 'searchindex' );
+ if ( $this->db->indexExists( 'searchindex', 'si_title' ) ) {
+ $this->output( "Dropping index...\n" );
+ $sql = "ALTER TABLE $searchindex DROP INDEX si_title, DROP INDEX si_text";
+ $this->db->query($sql, __METHOD__ );
+ }
+ }
+
+ /**
+ * (MySQL only) Adds back fulltext index after populating the table.
+ */
+ private function createMysqlTextIndex() {
+ $searchindex = $this->db->tableName( 'searchindex' );
+ $this->output( "\nRebuild the index...\n" );
+ $sql = "ALTER TABLE $searchindex ADD FULLTEXT si_title (si_title), " .
+ "ADD FULLTEXT si_text (si_text)";
+ $this->db->query( $sql, __METHOD__ );
+ }
+
+ /**
+ * Deletes everything from search index.
+ */
+ private function clearSearchIndex() {
+ $this->output( 'Clearing searchindex table...' );
+ $this->db->delete( 'searchindex', '*', __METHOD__ );
+ $this->output( "Done\n" );
+ }
+}
+$maintClass = "RebuildTextIndex";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/refreshImageCount.php b/maintenance/refreshImageCount.php
index 14f842b9..44794cc7 100644
--- a/maintenance/refreshImageCount.php
+++ b/maintenance/refreshImageCount.php
@@ -3,27 +3,52 @@
* Quickie hack; patch-ss_images.sql uses variables which don't
* replicate properly.
*
- * @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
+ *
* @ingroup Maintenance
*/
-require_once( "commandLine.inc" );
-
-$dbw = wfGetDB( DB_MASTER );
-
-// Load the current value from the master
-$count = $dbw->selectField( 'site_stats', 'ss_images' );
-
-echo wfWikiID().": forcing ss_images to $count\n";
-
-// First set to NULL so that it changes on the master
-$dbw->update( 'site_stats',
- array( 'ss_images' => null ),
- array( 'ss_row_id' => 1 ) );
-
-// Now this update will be forced to go out
-$dbw->update( 'site_stats',
- array( 'ss_images' => $count ),
- array( 'ss_row_id' => 1 ) );
-
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+class RefreshImageCount extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Resets ss_image count, forcing slaves to pick it up.";
+ }
+
+ public function execute() {
+ $dbw = wfGetDB( DB_MASTER );
+
+ // Load the current value from the master
+ $count = $dbw->selectField( 'site_stats', 'ss_images' );
+
+ $this->output( wfWikiID() . ": forcing ss_images to $count\n" );
+
+ // First set to NULL so that it changes on the master
+ $dbw->update( 'site_stats',
+ array( 'ss_images' => null ),
+ array( 'ss_row_id' => 1 ) );
+
+ // Now this update will be forced to go out
+ $dbw->update( 'site_stats',
+ array( 'ss_images' => $count ),
+ array( 'ss_row_id' => 1 ) );
+ }
+}
+
+$maintClass = "RefreshImageCount";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/refreshLinks.inc b/maintenance/refreshLinks.inc
deleted file mode 100644
index b7d531c7..00000000
--- a/maintenance/refreshLinks.inc
+++ /dev/null
@@ -1,202 +0,0 @@
-<?php
-/**
- * @todo document
- * @file
- * @ingroup Maintenance
- */
-
-function refreshLinks( $start, $newOnly = false, $maxLag = false, $end = 0, $redirectsOnly = false, $oldRedirectsOnly = false ) {
- global $wgUser, $wgParser, $wgUseTidy;
-
- $reportingInterval = 100;
- $fname = 'refreshLinks';
- $dbr = wfGetDB( DB_SLAVE );
- $start = intval( $start );
-
- # Don't generate TeX PNGs (lack of a sensible current directory causes errors anyway)
- $wgUser->setOption('math', MW_MATH_SOURCE);
-
- # Don't generate extension images (e.g. Timeline)
- if( method_exists( $wgParser, "clearTagHooks" ) ) {
- $wgParser->clearTagHooks();
- }
-
- # Don't use HTML tidy
- $wgUseTidy = false;
-
- $what = $redirectsOnly ? "redirects" : "links";
-
- if( $oldRedirectsOnly ) {
- # This entire code path is cut-and-pasted from below. Hurrah.
- $res = $dbr->query(
- "SELECT page_id ".
- "FROM page ".
- "LEFT JOIN redirect ON page_id=rd_from ".
- "WHERE page_is_redirect=1 AND rd_from IS NULL AND ".
- ($end == 0 ? "page_id >= $start"
- : "page_id BETWEEN $start AND $end"),
- $fname
- );
- $num = $dbr->numRows( $res );
- print "Refreshing $num old redirects from $start...\n";
-
- while( $row = $dbr->fetchObject( $res ) ) {
- if ( !( ++$i % $reportingInterval ) ) {
- print "$i\n";
- wfWaitForSlaves( $maxLag );
- }
- fixRedirect( $row->page_id );
- }
- } elseif( $newOnly ) {
- print "Refreshing $what from ";
- $res = $dbr->select( 'page',
- array( 'page_id' ),
- array(
- 'page_is_new' => 1,
- "page_id >= $start" ),
- $fname
- );
- $num = $dbr->numRows( $res );
- print "$num new articles...\n";
-
- $i = 0;
- while ( $row = $dbr->fetchObject( $res ) ) {
- if ( !( ++$i % $reportingInterval ) ) {
- print "$i\n";
- wfWaitForSlaves( $maxLag );
- }
- if($redirectsOnly)
- fixRedirect( $row->page_id );
- else
- fixLinksFromArticle( $row->page_id );
- }
- } else {
- print "Refreshing $what table.\n";
- if ( !$end ) {
- $end = $dbr->selectField( 'page', 'max(page_id)', false );
- }
- print("Starting from page_id $start of $end.\n");
-
- for ($id = $start; $id <= $end; $id++) {
-
- if ( !($id % $reportingInterval) ) {
- print "$id\n";
- wfWaitForSlaves( $maxLag );
- }
- if($redirectsOnly)
- fixRedirect( $id );
- else
- fixLinksFromArticle( $id );
- }
- }
-}
-
-function fixRedirect( $id ){
- global $wgTitle, $wgArticle;
-
- $wgTitle = Title::newFromID( $id );
- $dbw = wfGetDB( DB_MASTER );
-
- if ( is_null( $wgTitle ) ) {
- return;
- }
- $wgArticle = new Article($wgTitle);
-
- $rt = $wgArticle->followRedirect();
-
- if($rt == false || !is_object($rt))
- return;
-
- $wgArticle->updateRedirectOn($dbw,$rt);
-}
-
-function fixLinksFromArticle( $id ) {
- global $wgTitle, $wgParser;
-
- $wgTitle = Title::newFromID( $id );
- $dbw = wfGetDB( DB_MASTER );
-
- $linkCache =& LinkCache::singleton();
- $linkCache->clear();
-
- if ( is_null( $wgTitle ) ) {
- return;
- }
- $dbw->begin();
-
- $revision = Revision::newFromTitle( $wgTitle );
- if ( !$revision ) {
- return;
- }
-
- $options = new ParserOptions;
- $parserOutput = $wgParser->parse( $revision->getText(), $wgTitle, $options, true, true, $revision->getId() );
- $update = new LinksUpdate( $wgTitle, $parserOutput, false );
- $update->doUpdate();
- $dbw->immediateCommit();
-}
-
-/*
- * Removes non-existing links from pages from pagelinks, imagelinks,
- * categorylinks, templatelinks and externallinks tables.
- *
- * @param $maxLag
- * @param $batchSize The size of deletion batches
- *
- * @author Merlijn van Deen <valhallasw@arctus.nl>
- */
-function deleteLinksFromNonexistent( $maxLag = 0, $batchSize = 100 ) {
- wfWaitForSlaves( $maxLag );
-
- $dbw = wfGetDB( DB_MASTER );
-
- $lb = wfGetLBFactory()->newMainLB();
- $dbr = $lb->getConnection( DB_SLAVE );
- $dbr->bufferResults( false );
-
- $linksTables = array( // table name => page_id field
- 'pagelinks' => 'pl_from',
- 'imagelinks' => 'il_from',
- 'categorylinks' => 'cl_from',
- 'templatelinks' => 'tl_from',
- 'externallinks' => 'el_from',
- );
-
- foreach ( $linksTables as $table => $field ) {
- print "Retrieving illegal entries from $table... ";
-
- // SELECT DISTINCT( $field ) FROM $table LEFT JOIN page ON $field=page_id WHERE page_id IS NULL;
- $results = $dbr->select( array( $table, 'page' ),
- $field,
- array('page_id' => null ),
- __METHOD__,
- 'DISTINCT',
- array( 'page' => array( 'LEFT JOIN', "$field=page_id"))
- );
-
- $counter = 0;
- $list = array();
- print "0..";
-
- foreach( $results as $row ) {
- $counter++;
- $list[] = $row->$field;
- if ( ( $counter % $batchSize ) == 0 ) {
- wfWaitForSlaves(5);
- $dbw->delete( $table, array( $field => $list ), __METHOD__ );
-
- print $counter . "..";
- $list = array();
- }
- }
-
- print $counter;
- if (count($list) > 0) {
- $dbw->delete( $table, array( $field => $list ), __METHOD__ );
- }
-
- print "\n";
- }
-
- $lb->closeAll();
-}
diff --git a/maintenance/refreshLinks.php b/maintenance/refreshLinks.php
index c7667520..863dd022 100644
--- a/maintenance/refreshLinks.php
+++ b/maintenance/refreshLinks.php
@@ -1,56 +1,283 @@
<?php
/**
- * @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
+ *
* @ingroup Maintenance
*/
-/** */
-$optionsWithArgs = array('batch-size', 'm', 'e' );
-
-require_once( "commandLine.inc" );
-require_once( "refreshLinks.inc" );
-
-if( isset( $options['help'] ) ) {
- echo <<<TEXT
-Usage:
- php refreshLinks.php --help
- php refreshLinks.php [<start>] [-e <end>] [-m <maxlag>] [--dfn-only]
- [--batch-size <size>] [--new-only] [--redirects-only]
- php refreshLinks.php [<start>] [-e <end>] [-m <maxlag>] --old-redirects-only
-
- --help : This help message
- --dfn-only : Delete links from nonexistent articles only
- --batch-size <number> : The delete batch size when removing links from
- nonexistent articles (defaults to 100)
- --new-only : Only affect articles with just a single edit
- --redirects-only : Only fix redirects, not all links
- --old-redirects-only : Only fix redirects with no redirect table entry
- -m <number> : Maximum replication lag
- <start> : First page id to refresh
- -e <number> : Last page id to refresh
-
-TEXT;
- exit(0);
-}
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-error_reporting( E_ALL & (~E_NOTICE) );
+class RefreshLinks extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Refresh link tables";
+ $this->addOption( 'dfn-only', 'Delete links from nonexistent articles only' );
+ $this->addOption( 'new-only', 'Only affect articles with just a single edit' );
+ $this->addOption( 'redirects-only', 'Only fix redirects, not all links' );
+ $this->addOption( 'old-redirects-only', 'Only fix redirects with no redirect table entry' );
+ $this->addOption( 'm', 'Maximum replication lag', false, true );
+ $this->addOption( 'e', 'Last page id to refresh', false, true );
+ $this->addArg( 'start', 'Page_id to start from, default 1', false );
+ $this->setBatchSize( 100 );
+ }
-if ( !$options['dfn-only'] ) {
- if ( isset( $args[0] ) ) {
- $start = (int)$args[0];
- } else {
- $start = 1;
+ public function execute() {
+ if( !$this->hasOption( 'dfn-only' ) ) {
+ $start = $this->getArg( 0, 1 );
+ $new = $this->getOption( 'new-only', false );
+ $max = $this->getOption( 'm', false );
+ $end = $this->getOption( 'e', 0 );
+ $redir = $this->getOption( 'redirects-only', false );
+ $oldRedir = $this->getOption( 'old-redirects-only', false );
+ $this->doRefreshLinks( $start, $new, $max, $end, $redir, $oldRedir );
+ }
+ $this->deleteLinksFromNonexistent( $max, $this->mBatchSize );
}
- refreshLinks( $start, $options['new-only'], $options['m'], $options['e'], $options['redirects-only'], $options['old-redirects-only'] );
-}
+ /**
+ * 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
+ */
+ private function doRefreshLinks( $start, $newOnly = false, $maxLag = false,
+ $end = 0, $redirectsOnly = false, $oldRedirectsOnly = false ) {
+ global $wgUser, $wgParser, $wgUseTidy;
-if ( !isset( $options['batch-size'] ) ) {
- $options['batch-size'] = 100;
-}
+ $reportingInterval = 100;
+ $dbr = wfGetDB( DB_SLAVE );
+ $start = intval( $start );
+
+ # Don't generate TeX PNGs (lack of a sensible current directory causes errors anyway)
+ $wgUser->setOption('math', MW_MATH_SOURCE);
+
+ # Don't generate extension images (e.g. Timeline)
+ if( method_exists( $wgParser, "clearTagHooks" ) ) {
+ $wgParser->clearTagHooks();
+ }
+
+ # Don't use HTML tidy
+ $wgUseTidy = false;
+
+ $what = $redirectsOnly ? "redirects" : "links";
+
+ if( $oldRedirectsOnly ) {
+ # This entire code path is cut-and-pasted from below. Hurrah.
+ $res = $dbr->query(
+ "SELECT page_id ".
+ "FROM page ".
+ "LEFT JOIN redirect ON page_id=rd_from ".
+ "WHERE page_is_redirect=1 AND rd_from IS NULL AND ".
+ ($end == 0 ? "page_id >= $start"
+ : "page_id BETWEEN $start AND $end"),
+ __METHOD__
+ );
+ $num = $dbr->numRows( $res );
+ $this->output( "Refreshing $num old redirects from $start...\n" );
+
+ foreach( $res as $row ) {
+ if ( !( ++$i % $reportingInterval ) ) {
+ $this->output( "$i\n" );
+ wfWaitForSlaves( $maxLag );
+ }
+ $this->fixRedirect( $row->page_id );
+ }
+ } elseif( $newOnly ) {
+ $this->output( "Refreshing $what from " );
+ $res = $dbr->select( 'page',
+ array( 'page_id' ),
+ array(
+ 'page_is_new' => 1,
+ "page_id >= $start" ),
+ __METHOD__
+ );
+ $num = $dbr->numRows( $res );
+ $this->output( "$num new articles...\n" );
+
+ $i = 0;
+ foreach ( $res as $row ) {
+ if ( !( ++$i % $reportingInterval ) ) {
+ $this->output( "$i\n" );
+ wfWaitForSlaves( $maxLag );
+ }
+ if($redirectsOnly)
+ $this->fixRedirect( $row->page_id );
+ else
+ $this->fixLinksFromArticle( $row->page_id );
+ }
+ } else {
+ if ( !$end ) {
+ $maxPage = $dbr->selectField( 'page', 'max(page_id)', false );
+ $maxRD = $dbr->selectField( 'redirect', 'max(rd_from)', false );
+ $end = max( $maxPage, $maxRD );
+ }
+ $this->output( "Refreshing redirects table.\n" );
+ $this->output( "Starting from page_id $start of $end.\n" );
+
+ for ($id = $start; $id <= $end; $id++) {
+
+ if ( !($id % $reportingInterval) ) {
+ $this->output( "$id\n" );
+ wfWaitForSlaves( $maxLag );
+ }
+ $this->fixRedirect( $id );
+ }
+
+ if(!$redirectsOnly) {
+ $this->output( "Refreshing links table.\n" );
+ $this->output( "Starting from page_id $start of $end.\n" );
+
+ for ($id = $start; $id <= $end; $id++) {
+
+ if ( !($id % $reportingInterval) ) {
+ $this->output( "$id\n" );
+ wfWaitForSlaves( $maxLag );
+ }
+ $this->fixLinksFromArticle( $id );
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the redirect entry for a given page
+ * @param $id int The page_id of the redirect
+ */
+ private function fixRedirect( $id ){
+ global $wgTitle, $wgArticle;
+
+ $wgTitle = Title::newFromID( $id );
+ $dbw = wfGetDB( DB_MASTER );
+
+ if ( is_null( $wgTitle ) ) {
+ // This page doesn't exist (any more)
+ // Delete any redirect table entry for it
+ $dbw->delete( 'redirect', array( 'rd_from' => $id ),
+ __METHOD__ );
+ return;
+ }
+ $wgArticle = new Article($wgTitle);
+
+ $rt = $wgArticle->followRedirect();
+
+ if($rt == false || !is_object($rt)) {
+ // $wgTitle is not a redirect
+ // Delete any redirect table entry for it
+ $dbw->delete( 'redirect', array( 'rd_from' => $id ),
+ __METHOD__ );
+ } else {
+ $wgArticle->updateRedirectOn($dbw,$rt);
+ }
+ }
+
+ /**
+ * Run LinksUpdate for all links on a given page_id
+ * @param $id int The page_id
+ */
+ private function fixLinksFromArticle( $id ) {
+ global $wgTitle, $wgParser;
-deleteLinksFromNonexistent($options['m'], $options['batch-size']);
+ $wgTitle = Title::newFromID( $id );
+ $dbw = wfGetDB( DB_MASTER );
-if ( $options['globals'] ) {
- print_r( $GLOBALS );
+ $linkCache =& LinkCache::singleton();
+ $linkCache->clear();
+
+ if ( is_null( $wgTitle ) ) {
+ return;
+ }
+ $dbw->begin();
+
+ $revision = Revision::newFromTitle( $wgTitle );
+ if ( !$revision ) {
+ return;
+ }
+
+ $options = new ParserOptions;
+ $parserOutput = $wgParser->parse( $revision->getText(), $wgTitle, $options, true, true, $revision->getId() );
+ $update = new LinksUpdate( $wgTitle, $parserOutput, false );
+ $update->doUpdate();
+ $dbw->commit();
+ }
+
+ /*
+ * Removes non-existing links from pages from pagelinks, imagelinks,
+ * categorylinks, templatelinks and externallinks tables.
+ *
+ * @param $maxLag
+ * @param $batchSize The size of deletion batches
+ *
+ * @author Merlijn van Deen <valhallasw@arctus.nl>
+ */
+ private function deleteLinksFromNonexistent( $maxLag = 0, $batchSize = 100 ) {
+ wfWaitForSlaves( $maxLag );
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $lb = wfGetLBFactory()->newMainLB();
+ $dbr = $lb->getConnection( DB_SLAVE );
+ $dbr->bufferResults( false );
+
+ $linksTables = array( // table name => page_id field
+ 'pagelinks' => 'pl_from',
+ 'imagelinks' => 'il_from',
+ 'categorylinks' => 'cl_from',
+ 'templatelinks' => 'tl_from',
+ 'externallinks' => 'el_from',
+ );
+
+ foreach ( $linksTables as $table => $field ) {
+ $this->output( "Retrieving illegal entries from $table... " );
+
+ // SELECT DISTINCT( $field ) FROM $table LEFT JOIN page ON $field=page_id WHERE page_id IS NULL;
+ $results = $dbr->select( array( $table, 'page' ),
+ $field,
+ array('page_id' => null ),
+ __METHOD__,
+ 'DISTINCT',
+ array( 'page' => array( 'LEFT JOIN', "$field=page_id"))
+ );
+
+ $counter = 0;
+ $list = array();
+ $this->output( "0.." );
+
+ foreach( $results as $row ) {
+ $counter++;
+ $list[] = $row->$field;
+ if ( ( $counter % $batchSize ) == 0 ) {
+ wfWaitForSlaves(5);
+ $dbw->delete( $table, array( $field => $list ), __METHOD__ );
+
+ $this->output( $counter . ".." );
+ $list = array();
+ }
+ }
+ $this->output( $counter );
+ if (count($list) > 0) {
+ $dbw->delete( $table, array( $field => $list ), __METHOD__ );
+ }
+ $this->output( "\n" );
+ }
+ $lb->closeAll();
+ }
}
+
+$maintClass = 'RefreshLinks';
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/removeUnusedAccounts.inc b/maintenance/removeUnusedAccounts.inc
deleted file mode 100644
index 02c07c1f..00000000
--- a/maintenance/removeUnusedAccounts.inc
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-/**
- * Support functions for the removeUnusedAccounts maintenance script
- *
- * @file
- * @ingroup Maintenance
- * @author Rob Church <robchur@gmail.com>
- */
-
-/**
- * Could the specified user account be deemed inactive?
- * (No edits, no deleted edits, no log entries, no current/old uploads)
- *
- * @param $id User's ID
- * @param $master Perform checking on the master
- * @return bool
- */
-function isInactiveAccount( $id, $master = false ) {
- $dbo = wfGetDB( $master ? DB_MASTER : DB_SLAVE );
- $fname = 'isInactiveAccount';
- $checks = array( 'revision' => 'rev', 'archive' => 'ar', 'logging' => 'log',
- 'image' => 'img', 'oldimage' => 'oi' );
- $count = 0;
-
- $dbo->immediateBegin();
- foreach( $checks as $table => $fprefix ) {
- $conds = array( $fprefix . '_user' => $id );
- $count += (int)$dbo->selectField( $table, 'COUNT(*)', $conds, $fname );
- }
- $dbo->immediateCommit();
-
- return $count == 0;
-}
-
-/**
- * Show help for the maintenance script
- */
-function showHelp() {
- echo( "Delete unused user accounts from the database.\n\n" );
- echo( "USAGE: php removeUnusedAccounts.php [--delete]\n\n" );
- echo( " --delete : Delete accounts which are discovered to be inactive\n" );
- echo( " --ignore-touched=x : Ignore accounts touched within the lasts x days\n" );
- echo( " --ignore-groups=x,y : Ignore accounts within these groups\n" );
- echo( "\n" );
-}
diff --git a/maintenance/removeUnusedAccounts.php b/maintenance/removeUnusedAccounts.php
index 419955b0..7b0535b7 100644
--- a/maintenance/removeUnusedAccounts.php
+++ b/maintenance/removeUnusedAccounts.php
@@ -3,68 +3,108 @@
* Remove unused user accounts from the database
* An unused account is one which has made no edits
*
- * @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
+ *
* @ingroup Maintenance
* @author Rob Church <robchur@gmail.com>
*/
-$options = array( 'help', 'delete' );
-require_once( 'commandLine.inc' );
-require_once( 'removeUnusedAccounts.inc' );
-echo( "Remove Unused Accounts\n\n" );
-$fname = 'removeUnusedAccounts';
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if( isset( $options['help'] ) ) {
- showHelp();
- exit();
-}
+class RemoveUnusedAccounts extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( 'delete', 'Actually delete the account' );
+ $this->addOption( 'ignore-groups', 'List of comma-separated groups to exclude', false, true );
+ $this->addOption( 'ignore-touched', 'Skip accounts touched in last N days', false, true );
+ }
+
+ public function execute() {
-# Do an initial scan for inactive accounts and report the result
-echo( "Checking for unused user accounts...\n" );
-$del = array();
-$dbr = wfGetDB( DB_SLAVE );
-$res = $dbr->select( 'user', array( 'user_id', 'user_name', 'user_touched' ), '', $fname );
-if( isset( $options['ignore-groups'] ) ) {
- $excludedGroups = explode( ',', $options['ignore-groups'] );
-} else { $excludedGroups = array(); }
-$touchedSeconds = 0;
-if( isset( $options['ignore-touched'] ) ) {
- $touchedParamError = 0;
- if( ctype_digit( $options['ignore-touched'] ) ) {
- if( $options['ignore-touched'] <= 0 ) {
- $touchedParamError = 1;
+ $this->output( "Remove unused accounts\n\n" );
+
+ # Do an initial scan for inactive accounts and report the result
+ $this->output( "Checking for unused user accounts...\n" );
+ $del = array();
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'user', array( 'user_id', 'user_name', 'user_touched' ), '', __METHOD__ );
+ if( $this->hasOption('ignore-groups') ) {
+ $excludedGroups = explode( ',', $this->getOption('ignore-groups') );
+ } else {
+ $excludedGroups = array();
}
- } else { $touchedParamError = 1; }
- if( $touchedParamError == 1 ) {
- die( "Please put a valid positive integer on the --ignore-touched parameter.\n" );
- } else { $touchedSeconds = 86400 * $options['ignore-touched']; }
-}
-while( $row = $dbr->fetchObject( $res ) ) {
- # Check the account, but ignore it if it's within a $excludedGroups group or if it's touched within the $touchedSeconds seconds.
- $instance = User::newFromId( $row->user_id );
- if( count( array_intersect( $instance->getEffectiveGroups(), $excludedGroups ) ) == 0
- && isInactiveAccount( $row->user_id, true )
- && wfTimestamp( TS_UNIX, $row->user_touched ) < wfTimestamp( TS_UNIX, time() - $touchedSeconds )
- ) {
- # Inactive; print out the name and flag it
- $del[] = $row->user_id;
- echo( $row->user_name . "\n" );
+ $touched = $this->getOption( 'ignore-touched', "1" );
+ if( !ctype_digit( $touched ) ) {
+ $this->error( "Please put a valid positive integer on the --ignore-touched parameter.", true );
+ }
+ $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.
+ $instance = User::newFromId( $row->user_id );
+ if( count( array_intersect( $instance->getEffectiveGroups(), $excludedGroups ) ) == 0
+ && $this->isInactiveAccount( $row->user_id, true )
+ && wfTimestamp( TS_UNIX, $row->user_touched ) < wfTimestamp( TS_UNIX, time() - $touchedSeconds )
+ ) {
+ # Inactive; print out the name and flag it
+ $del[] = $row->user_id;
+ $this->output( $row->user_name . "\n" );
+ }
+ }
+ $count = count( $del );
+ $this->output( "...found {$count}.\n" );
+
+ # If required, go back and delete each marked account
+ if( $count > 0 && $this->hasOption('delete') ) {
+ $this->output( "\nDeleting inactive accounts..." );
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete( 'user', array( 'user_id' => $del ), __METHOD__ );
+ $this->output( "done.\n" );
+ # Update the site_stats.ss_users field
+ $users = $dbw->selectField( 'user', 'COUNT(*)', array(), __METHOD__ );
+ $dbw->update( 'site_stats', array( 'ss_users' => $users ), array( 'ss_row_id' => 1 ), __METHOD__ );
+ } elseif( $count > 0 ) {
+ $this->output( "\nRun the script again with --delete to remove them from the database.\n" );
+ }
+ $this->output( "\n" );
+ }
+
+ /**
+ * Could the specified user account be deemed inactive?
+ * (No edits, no deleted edits, no log entries, no current/old uploads)
+ *
+ * @param $id User's ID
+ * @param $master Perform checking on the master
+ * @return bool
+ */
+ private function isInactiveAccount( $id, $master = false ) {
+ $dbo = wfGetDB( $master ? DB_MASTER : DB_SLAVE );
+ $checks = array( 'revision' => 'rev', 'archive' => 'ar', 'logging' => 'log',
+ 'image' => 'img', 'oldimage' => 'oi' );
+ $count = 0;
+
+ $dbo->begin();
+ foreach( $checks as $table => $fprefix ) {
+ $conds = array( $fprefix . '_user' => $id );
+ $count += (int)$dbo->selectField( $table, 'COUNT(*)', $conds, __METHOD__ );
+ }
+ $dbo->commit();
+
+ return $count == 0;
}
}
-$count = count( $del );
-echo( "...found {$count}.\n" );
-# If required, go back and delete each marked account
-if( $count > 0 && isset( $options['delete'] ) ) {
- echo( "\nDeleting inactive accounts..." );
- $dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'user', array( 'user_id' => $del ), $fname );
- echo( "done.\n" );
- # Update the site_stats.ss_users field
- $users = $dbw->selectField( 'user', 'COUNT(*)', array(), $fname );
- $dbw->update( 'site_stats', array( 'ss_users' => $users ), array( 'ss_row_id' => 1 ), $fname );
-} else {
- if( $count > 0 )
- echo( "\nRun the script again with --delete to remove them from the database.\n" );
-}
-echo( "\n" );
+$maintClass = "RemoveUnusedAccounts";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/renameDbPrefix.php b/maintenance/renameDbPrefix.php
index 17568b4a..f73db508 100644
--- a/maintenance/renameDbPrefix.php
+++ b/maintenance/renameDbPrefix.php
@@ -1,68 +1,85 @@
<?php
/**
- * Run this script to after changing $wgDBPrefix on a wiki.
+ * Run this script to after changing $wgDBprefix on a wiki.
* The wiki will have to get downtime to do this correctly.
*
- * @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
+ *
* @ingroup Maintenance
*/
-$optionsWithArgs = array('old','new','help');
-
-require_once( 'commandLine.inc' );
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if( @$options['help'] || !isset($options['old']) || !isset($options['new']) ) {
- print "usage:updateSpecialPages.php [--help] [--old x] [new y]\n";
- print " --help : this help message\n";
- print " --old x : old db prefix x\n";
- print " --old 0 : EMPTY old db prefix x\n";
- print " --new y : new db prefix y\n";
- print " --new 0 : EMPTY new db prefix\n";
- wfDie();
-}
-
-// Allow for no old prefix
-if( $options['old'] === '0' ) {
- $old = '';
-} else {
- // Use nice safe, sane, prefixes
- preg_match( '/^[a-zA-Z]+_$/', $options['old'], $m );
- $old = isset($m[0]) ? $m[0] : false;
-}
-// Allow for no new prefix
-if( $options['new'] === '0' ) {
- $new = '';
-} else {
- // Use nice safe, sane, prefixes
- preg_match( '/^[a-zA-Z]+_$/', $options['new'], $m );
- $new = isset($m[0]) ? $m[0] : false;
-}
-
-if( $old===false || $new===false ) {
- print "Invalid prefix!\n";
- wfDie();
-}
-if( $old === $new ) {
- print "Same prefix. Nothing to rename!\n";
- wfDie();
-}
+class RenameDbPrefix extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( "old", "Old db prefix [0 for none]", true, true );
+ $this->addOption( "new", "New db prefix [0 for none]", true, true );
+ }
-print "Renaming DB prefix for tables of $wgDBname from '$old' to '$new'\n";
-$count = 0;
+ public function getDbType() {
+ return Maintenance::DB_ADMIN;
+ }
-$dbw = wfGetDB( DB_MASTER );
-$res = $dbw->query( "SHOW TABLES LIKE '".$dbw->escapeLike($old)."%'" );
-foreach( $res as $row ) {
- // XXX: odd syntax. MySQL outputs an oddly cased "Tables of X"
- // sort of message. Best not to try $row->x stuff...
- $fields = get_object_vars( $row );
- // Silly for loop over one field...
- foreach( $fields as $resName => $table ) {
- // $old should be regexp safe ([a-zA-Z_])
- $newTable = preg_replace( '/^'.$old.'/',$new,$table);
- print "Renaming table $table to $newTable\n";
- $dbw->query( "RENAME TABLE $table TO $newTable" );
+ public function execute() {
+ // Allow for no old prefix
+ if( $this->getOption( 'old', 0 ) === '0' ) {
+ $old = '';
+ } else {
+ // Use nice safe, sane, prefixes
+ preg_match( '/^[a-zA-Z]+_$/', $this->getOption('old'), $m );
+ $old = isset( $m[0] ) ? $m[0] : false;
+ }
+ // Allow for no new prefix
+ if( $this->getOption( 'new', 0 ) === '0' ) {
+ $new = '';
+ } else {
+ // Use nice safe, sane, prefixes
+ preg_match( '/^[a-zA-Z]+_$/', $this->getOption('new'), $m );
+ $new = isset( $m[0] ) ? $m[0] : false;
+ }
+
+ if( $old === false || $new === false ) {
+ $this->error( "Invalid prefix!", true );
+ }
+ if( $old === $new ) {
+ $this->output( "Same prefix. Nothing to rename!\n", true );
+ }
+
+ $this->output( "Renaming DB prefix for tables of $wgDBname from '$old' to '$new'\n" );
+ $count = 0;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $res = $dbw->query( "SHOW TABLES LIKE '".$dbw->escapeLike( $old )."%'" );
+ foreach( $res as $row ) {
+ // XXX: odd syntax. MySQL outputs an oddly cased "Tables of X"
+ // sort of message. Best not to try $row->x stuff...
+ $fields = get_object_vars( $row );
+ // Silly for loop over one field...
+ foreach( $fields as $resName => $table ) {
+ // $old should be regexp safe ([a-zA-Z_])
+ $newTable = preg_replace( '/^'.$old.'/', $new, $table );
+ $this->output( "Renaming table $table to $newTable\n" );
+ $dbw->query( "RENAME TABLE $table TO $newTable" );
+ }
+ $count++;
+ }
+ $this->output( "Done! [$count tables]\n" );
}
- $count++;
}
-print "Done! [$count tables]\n";
+$maintClass = "RenameDbPrefix";
+require_once( DO_MAINTENANCE ); \ No newline at end of file
diff --git a/maintenance/renamewiki.php b/maintenance/renamewiki.php
index 66de326b..36437bea 100644
--- a/maintenance/renamewiki.php
+++ b/maintenance/renamewiki.php
@@ -3,60 +3,87 @@
* Why yes, this *is* another special-purpose Wikimedia maintenance script!
* Should be fixed up and generalized.
*
+ * 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
+ * @ingroup Wikimedia
*/
-require_once( "commandLine.inc" );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if ( count( $args ) != 2 ) {
- wfDie( "Rename external storage dbs and leave a new one...\n" .
- "Usage: php renamewiki.php <olddb> <newdb>\n" );
-}
+class RenameWiki extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Rename external storage dbs and leave a new one";
+ $this->addArg( 'olddb', 'Old DB name' );
+ $this->addArg( 'newdb', 'New DB name' );
+ }
+
+ public function getDbType() {
+ return Maintenance::DB_ADMIN;
+ }
-list( $from, $to ) = $args;
+ public function execute() {
+ global $wgDefaultExternalStore;
-echo "Renaming blob tables in ES from $from to $to...\n";
-echo "Sleeping 5 seconds...";
-sleep(5);
-echo "\n";
+ # Setup
+ $from = $this->getArg( 0 );
+ $to = $this->getArg( 1 );
+ $this->output( "Renaming blob tables in ES from $from to $to...\n" );
+ $this->output( "Sleeping 5 seconds...\n" );
+ sleep(5);
-$maintenance = "$IP/maintenance";
+ # Initialise external storage
+ if ( is_array( $wgDefaultExternalStore ) ) {
+ $stores = $wgDefaultExternalStore;
+ } elseif ( $wgDefaultExternalStore ) {
+ $stores = array( $wgDefaultExternalStore );
+ } else {
+ $stores = array();
+ }
-# Initialise external storage
-if ( is_array( $wgDefaultExternalStore ) ) {
- $stores = $wgDefaultExternalStore;
-} elseif ( $wgDefaultExternalStore ) {
- $stores = array( $wgDefaultExternalStore );
-} else {
- $stores = array();
-}
-if ( count( $stores ) ) {
- require_once( 'ExternalStoreDB.php' );
- print "Initialising external storage $store...\n";
- global $wgDBuser, $wgDBpassword, $wgExternalServers;
- foreach ( $stores as $storeURL ) {
- $m = array();
- if ( !preg_match( '!^DB://(.*)$!', $storeURL, $m ) ) {
- continue;
+ if ( count( $stores ) ) {
+ $this->output( "Initialising external storage $store...\n" );
+ global $wgDBuser, $wgDBpassword, $wgExternalServers;
+ foreach ( $stores as $storeURL ) {
+ $m = array();
+ if ( !preg_match( '!^DB://(.*)$!', $storeURL, $m ) ) {
+ continue;
+ }
+
+ $cluster = $m[1];
+
+ # Hack
+ $wgExternalServers[$cluster][0]['user'] = $wgDBuser;
+ $wgExternalServers[$cluster][0]['password'] = $wgDBpassword;
+
+ $store = new ExternalStoreDB;
+ $extdb =& $store->getMaster( $cluster );
+ $extdb->query( "SET table_type=InnoDB" );
+ $extdb->query( "CREATE DATABASE {$to}" );
+ $extdb->query( "ALTER TABLE {$from}.blobs RENAME TO {$to}.blobs" );
+ $extdb->selectDB( $from );
+ $extdb->sourceFile( $this->getDir() . '/storage/blobs.sql' );
+ $extdb->commit();
+ }
}
-
- $cluster = $m[1];
-
- # Hack
- $wgExternalServers[$cluster][0]['user'] = $wgDBuser;
- $wgExternalServers[$cluster][0]['password'] = $wgDBpassword;
-
- $store = new ExternalStoreDB;
- $extdb =& $store->getMaster( $cluster );
- $extdb->query( "SET table_type=InnoDB" );
- $extdb->query( "CREATE DATABASE {$to}" );
- $extdb->query( "ALTER TABLE {$from}.blobs RENAME TO {$to}.blobs" );
- $extdb->selectDB( $from );
- dbsource( "$maintenance/storage/blobs.sql", $extdb );
- $extdb->immediateCommit();
+ $this->output( "done.\n" );
}
}
-echo "done.\n";
-
+$maintClass = "RenameWiki";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/renderDump.php b/maintenance/renderDump.php
index 97797792..d36953f8 100644
--- a/maintenance/renderDump.php
+++ b/maintenance/renderDump.php
@@ -27,42 +27,61 @@
* @file
* @ingroup Maintenance
*/
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-$optionsWithArgs = array( 'report' );
+class DumpRenderer extends Maintenance {
-require_once( 'commandLine.inc' );
+ private $count = 0;
+ private $outputDirectory, $startTime;
-class DumpRenderer {
- function __construct( $dir ) {
- $this->stderr = fopen( "php://stderr", "wt" );
- $this->outputDirectory = $dir;
- $this->count = 0;
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Take page text out of an XML dump file and render basic HTML out to files";
+ $this->addOption( 'output-dir', 'The directory to output the HTML files to', true, true );
}
- function handleRevision( $rev ) {
+ public function execute() {
+ $this->outputDirectory = $this->getOption( 'output-dir' );
+ $this->startTime = wfTime();
+
+ $source = new ImportStreamSource( $this->getStdin() );
+ $importer = new WikiImporter( $source );
+
+ $importer->setRevisionCallback(
+ array( &$this, 'handleRevision' ) );
+
+ return $importer->doImport();
+ }
+
+ /**
+ * Callback function for each revision, turn into HTML and save
+ * @param $rev Revision
+ */
+ private function handleRevision( $rev ) {
$title = $rev->getTitle();
if (!$title) {
- fprintf( $this->stderr, "Got bogus revision with null title!" );
+ $this->error( "Got bogus revision with null title!" );
return;
}
$display = $title->getPrefixedText();
-
+
$this->count++;
-
+
$sanitized = rawurlencode( $display );
$filename = sprintf( "%s/wiki-%07d-%s.html",
$this->outputDirectory,
$this->count,
$sanitized );
- fprintf( $this->stderr, "%s\n", $filename, $display );
-
- // fixme
+ $this->output( sprintf( $this->stderr, "%s\n", $filename, $display ) );
+
+ // fixme (what?)
$user = new User();
$parser = new Parser();
$options = ParserOptions::newFromUser( $user );
-
+
$output = $parser->parse( $rev->getText(), $title, $options );
-
+
file_put_contents( $filename,
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " .
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" .
@@ -76,27 +95,7 @@ class DumpRenderer {
"</body>\n" .
"</html>" );
}
-
- function run() {
- $this->startTime = wfTime();
-
- $file = fopen( 'php://stdin', 'rt' );
- $source = new ImportStreamSource( $file );
- $importer = new WikiImporter( $source );
-
- $importer->setRevisionCallback(
- array( &$this, 'handleRevision' ) );
-
- return $importer->doImport();
- }
}
-if( isset( $options['output-dir'] ) ) {
- $dir = $options['output-dir'];
-} else {
- wfDie( "Must use --output-dir=/some/dir\n" );
-}
-$render = new DumpRenderer( $dir );
-$render->run();
-
-
+$maintClass = "DumpRenderer";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/rollbackEdits.php b/maintenance/rollbackEdits.php
new file mode 100644
index 00000000..5d6a80a4
--- /dev/null
+++ b/maintenance/rollbackEdits.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Rollback all edits by a given user or IP provided they're the most
+ * recent edit (just like real rollback)
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/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->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' );
+ }
+
+ public function execute() {
+ $user = $this->getOption( 'user' );
+ $username = User::isIP( $user ) ? $user : User::getCanonicalName( $user );
+ if( !$username ) {
+ $this->error( 'Invalid username', true );
+ }
+
+ $bot = $this->hasOption( 'bot' );
+ $summary = $this->getOption( 'summary', $this->mSelf . ' mass rollback' );
+ $titles = array();
+ $results = array();
+ if( $this->hasOption( 'titles' ) ) {
+ foreach( explode( '|', $this->getOption( 'titles' ) ) as $title ) {
+ $t = Title::newFromText( $title );
+ if( !$t ) {
+ $this->error( 'Invalid title, ' . $title );
+ } else {
+ $titles[] = $t;
+ }
+ }
+ } else {
+ $titles = $this->getRollbackTitles( $user );
+ }
+
+ if( !$titles ) {
+ $this->output( 'No suitable titles to be rolled back' );
+ return;
+ }
+
+ foreach( $titles as $t ) {
+ $a = new Article( $t );
+ $this->output( 'Processing ' . $t->getPrefixedText() . '...' );
+ if( !$a->commitRollback( $user, $summary, $bot, $results ) ) {
+ $this->output( "done\n" );
+ } else {
+ $this->output( "failed\n" );
+ }
+ }
+ }
+
+ /**
+ * Get all pages that should be rolled back for a given user
+ * @param $user String a name to check against rev_user_text
+ */
+ private function getRollbackTitles( $user ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $titles = array();
+ $results = $dbr->select(
+ array( 'page', 'revision' ),
+ array( 'page_namespace', 'page_title' ),
+ array( 'page_latest = rev_id', 'rev_user_text' => $user ),
+ __METHOD__
+ );
+ while( $row = $dbr->fetchObject( $results ) ) {
+ $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
+ }
+ return $titles;
+ }
+}
+
+$maintClass = 'RollbackEdits';
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/runBatchedQuery.php b/maintenance/runBatchedQuery.php
new file mode 100644
index 00000000..03c56aa9
--- /dev/null
+++ b/maintenance/runBatchedQuery.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Run a database query in batches and wait for slaves. This is used on large
+ * wikis to prevent replication lag from going through the roof when executing
+ * large write queries.
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/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" .
+ "NOTE: You need to set a LIMIT clause yourself.";
+ $this->addOption( 'wait', "Wait for replication lag to go down to this value. Default: 5", false, true );
+ }
+
+ public function execute() {
+ if ( !$this->hasArg() )
+ $this->error( "No query specified. Specify the query as a command line parameter.", true );
+
+ $query = $this->getArg();
+ $wait = $this->getOption( 'wait', 5 );
+ $n = 1;
+ $dbw = wfGetDb( DB_MASTER );
+ do {
+ $this->output( "Batch $n: " );
+ $n++;
+ $dbw->query( $query );
+ $affected = $dbw->affectedRows();
+ $this->output( "$affected rows\n" );
+ wfWaitForSlaves( $wait );
+ } while ( $affected > 0 );
+ }
+
+ public function getDbType() {
+ return Maintenance::DB_ADMIN;
+ }
+}
+
+
+$maintClass = "BatchedQueryRunner";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/runJobs.php b/maintenance/runJobs.php
index 1340a857..e03bf5d1 100644
--- a/maintenance/runJobs.php
+++ b/maintenance/runJobs.php
@@ -6,75 +6,98 @@
* --maxjobs <num> (default 10000)
* --type <job_cmd>
*
- * @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
+ *
* @ingroup Maintenance
*/
-$optionsWithArgs = array( 'maxjobs', 'type', 'procs' );
-$wgUseNormalUser = true;
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if ( isset( $options['procs'] ) ) {
- $procs = intval( $options['procs'] );
- if ( $procs < 1 || $procs > 1000 ) {
- echo "Invalid argument to --procs\n";
- exit( 1 );
+class RunJobs extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Run pending jobs";
+ $this->addOption( 'maxjobs', 'Maximum number of jobs to run', false, true );
+ $this->addOption( 'type', 'Type of job to run', false, true );
+ $this->addOption( 'procs', 'Number of processes to use', false, true );
}
- $fc = new ForkController( $procs );
- if ( $fc->start( $procs ) != 'child' ) {
- exit( 0 );
+
+ public function memoryLimit() {
+ // Don't eat all memory on the machine if we get a bad job.
+ return "150M";
}
-}
-
-if ( isset( $options['maxjobs'] ) ) {
- $maxJobs = $options['maxjobs'];
-} else {
- $maxJobs = 10000;
-}
-
-$type = false;
-if ( isset( $options['type'] ) )
- $type = $options['type'];
-
-$wgTitle = Title::newFromText( 'RunJobs.php' );
-$dbw = wfGetDB( DB_MASTER );
-$n = 0;
-$conds = '';
-if ($type !== false)
- $conds = "job_cmd = " . $dbw->addQuotes($type);
-
-while ( $dbw->selectField( 'job', 'job_id', $conds, 'runJobs.php' ) ) {
- $offset=0;
- for (;;) {
- $job = ($type == false) ?
- Job::pop($offset)
- : Job::pop_type($type);
-
- if ($job == false)
- break;
-
- wfWaitForSlaves( 5 );
- $t = microtime( true );
- $offset=$job->id;
- $status = $job->run();
- $t = microtime( true ) - $t;
- $timeMs = intval( $t * 1000 );
- if ( !$status ) {
- runJobsLog( $job->toString() . " t=$timeMs error={$job->error}" );
- } else {
- runJobsLog( $job->toString() . " t=$timeMs good" );
+ public function execute() {
+ global $wgTitle;
+ if ( $this->hasOption( 'procs' ) ) {
+ $procs = intval( $this->getOption('procs') );
+ if ( $procs < 1 || $procs > 1000 ) {
+ $this->error( "Invalid argument to --procs", true );
+ }
+ $fc = new ForkController( $procs );
+ if ( $fc->start( $procs ) != 'child' ) {
+ exit( 0 );
+ }
}
- if ( $maxJobs && ++$n > $maxJobs ) {
- break 2;
+ $maxJobs = $this->getOption( 'maxjobs', 10000 );
+ $type = $this->getOption( 'type', false );
+ $wgTitle = Title::newFromText( 'RunJobs.php' );
+ $dbw = wfGetDB( DB_MASTER );
+ $n = 0;
+ $conds = '';
+ if ($type !== false)
+ $conds = "job_cmd = " . $dbw->addQuotes($type);
+
+ while ( $dbw->selectField( 'job', 'job_id', $conds, 'runJobs.php' ) ) {
+ $offset=0;
+ for (;;) {
+ $job = ($type == false) ?
+ Job::pop($offset)
+ : Job::pop_type($type);
+
+ if ($job == false)
+ break;
+
+ wfWaitForSlaves( 5 );
+ $t = microtime( true );
+ $offset=$job->id;
+ $status = $job->run();
+ $t = microtime( true ) - $t;
+ $timeMs = intval( $t * 1000 );
+ if ( !$status ) {
+ $this->runJobsLog( $job->toString() . " t=$timeMs error={$job->error}" );
+ } else {
+ $this->runJobsLog( $job->toString() . " t=$timeMs good" );
+ }
+ if ( $maxJobs && ++$n > $maxJobs ) {
+ break 2;
+ }
+ }
}
}
-}
-
-function runJobsLog( $msg ) {
- print wfTimestamp( TS_DB ) . " $msg\n";
- wfDebugLog( 'runJobs', $msg );
+ /**
+ * Log the job message
+ * @param $msg String The message to log
+ */
+ private function runJobsLog( $msg ) {
+ $this->output( wfTimestamp( TS_DB ) . " $msg\n" );
+ wfDebugLog( 'runJobs', $msg );
+ }
}
-
+$maintClass = "RunJobs";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/showJobs.php b/maintenance/showJobs.php
index 6e38c854..b385c50a 100644
--- a/maintenance/showJobs.php
+++ b/maintenance/showJobs.php
@@ -4,15 +4,52 @@
*
* Report number of jobs currently waiting in master database.
*
- * @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
+ *
* @ingroup Maintenance
* @author Tim Starling
* @author Ashar Voultoiz
*/
-require_once( 'commandLine.inc' );
-
-$dbw = wfGetDB( DB_MASTER );
-$count = $dbw->selectField( 'job', 'count(*)', '', 'runJobs.php' );
-print $count."\n";
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+class ShowJobs extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Show number of jobs waiting in master database";
+ $this->addOption( 'group', 'Show number of jobs per job type' );
+ }
+ public function execute() {
+ $dbw = wfGetDB( DB_MASTER );
+ if ( $this->hasOption( 'group' ) ) {
+ $res = $dbw->select(
+ 'job',
+ array( 'job_cmd', 'count(*) as count' ),
+ array(),
+ __METHOD__,
+ array( 'GROUP BY' => 'job_cmd' )
+ );
+ foreach( $res as $row ) {
+ $this->output( $row->job_cmd . ': ' . $row->count . "\n" );
+ }
+ } else {
+ $this->output( $dbw->selectField( 'job', 'count(*)', '', __METHOD__ ) . "\n" );
+ }
+ }
+}
+$maintClass = "ShowJobs";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/showStats.php b/maintenance/showStats.php
index ef13f654..dfce3613 100644
--- a/maintenance/showStats.php
+++ b/maintenance/showStats.php
@@ -4,7 +4,21 @@
* Maintenance script to show the cached statistics.
* Give out the same output as [[Special:Statistics]]
*
- * @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
+ *
* @ingroup Maintenance
* @author Ashar Voultoiz <hashar@altern.org>
* Based on initStats.php by:
@@ -14,35 +28,41 @@
* @license GNU General Public License 2.0 or later
*/
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-#
-# Configuration
-#
-$fields = array(
- 'ss_total_views' => 'Total views',
- 'ss_total_edits' => 'Total edits',
- 'ss_good_articles' => 'Number of articles',
- 'ss_total_pages' => 'Total pages',
- 'ss_users' => 'Number of users',
- 'ss_admins' => 'Number of admins',
- 'ss_images' => 'Number of images',
-);
-
-// Get cached stats from slave database
-$dbr = wfGetDB( DB_SLAVE );
-$fname = 'showStats';
-$stats = $dbr->selectRow( 'site_stats', '*', '' );
-
-// Get maximum size for each column
-$max_length_value = $max_length_desc = 0;
-foreach( $fields as $field => $desc ) {
- $max_length_value = max( $max_length_value, strlen( $stats->$field ) );
- $max_length_desc = max( $max_length_desc , strlen( $desc )) ;
+class ShowStats extends Maintenance {
+ public function __construct() {
+ $this->mDescription = "Show the cached statistics";
+ }
+ public function execute() {
+ $fields = array(
+ 'ss_total_views' => 'Total views',
+ 'ss_total_edits' => 'Total edits',
+ 'ss_good_articles' => 'Number of articles',
+ 'ss_total_pages' => 'Total pages',
+ 'ss_users' => 'Number of users',
+ 'ss_admins' => 'Number of admins',
+ 'ss_images' => 'Number of images',
+ );
+
+ // Get cached stats from slave database
+ $dbr = wfGetDB( DB_SLAVE );
+ $stats = $dbr->selectRow( 'site_stats', '*', '', __METHOD__ );
+
+ // Get maximum size for each column
+ $max_length_value = $max_length_desc = 0;
+ foreach( $fields as $field => $desc ) {
+ $max_length_value = max( $max_length_value, strlen( $stats->$field ) );
+ $max_length_desc = max( $max_length_desc , strlen( $desc )) ;
+ }
+
+ // Show them
+ foreach( $fields as $field => $desc ) {
+ $this->output( sprintf( "%-{$max_length_desc}s: %{$max_length_value}d\n", $desc, $stats->$field ) );
+ }
+ }
}
-// Show them
-foreach( $fields as $field => $desc ) {
- printf( "%-{$max_length_desc}s: %{$max_length_value}d\n", $desc, $stats->$field );
-}
+$maintClass = "ShowStats";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/sql.php b/maintenance/sql.php
index ab6546b9..fd4be19a 100644
--- a/maintenance/sql.php
+++ b/maintenance/sql.php
@@ -3,42 +3,78 @@
* Send SQL queries from the specified file to the database, performing
* variable replacement along the way.
*
- * @file
- * @ingroup Database Maintenance
+ * 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
+ *
+ * @ingroup Maintenance
*/
-require_once( dirname(__FILE__) . '/' . 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if ( isset( $options['help'] ) ) {
- echo "Send SQL queries to a MediaWiki database.\nUsage: php sql.php [<file>]\n";
- exit( 1 );
-}
+class MwSql extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Send SQL queries to a MediaWiki database";
+ }
-if ( isset( $args[0] ) ) {
- $fileName = $args[0];
- $file = fopen( $fileName, 'r' );
- $promptCallback = false;
-} else {
- $file = STDIN;
- $promptObject = new SqlPromptPrinter( "> " );
- $promptCallback = $promptObject->cb();
-}
+ public function execute() {
+ if ( $this->hasArg() ) {
+ $fileName = $this->getArg();
+ $file = fopen( $fileName, 'r' );
+ $promptCallback = false;
+ } else {
+ $file = $this->getStdin();
+ $promptObject = new SqlPromptPrinter( "> " );
+ $promptCallback = $promptObject->cb();
+ }
+
+ if ( !$file )
+ $this->error( "Unable to open input file", true );
-if ( !$file ) {
- echo "Unable to open input file\n";
- exit( 1 );
-}
+ $dbw = wfGetDB( DB_MASTER );
+ $error = $dbw->sourceStream( $file, $promptCallback, array( $this, 'sqlPrintResult' ) );
+ if ( $error !== true ) {
+ $this->error( $error, true );
+ } else {
+ exit( 0 );
+ }
+ }
-$dbw =& wfGetDB( DB_MASTER );
-$error = $dbw->sourceStream( $file, $promptCallback, 'sqlPrintResult' );
-if ( $error !== true ) {
- echo $error;
- exit( 1 );
-} else {
- exit( 0 );
+ /**
+ * Print the results, callback for $db->sourceStream()
+ * @param $res The results object
+ * @param $db Database object
+ */
+ public function sqlPrintResult( $res, $db ) {
+ if ( !$res ) {
+ // Do nothing
+ } elseif ( is_object( $res ) && $res->numRows() ) {
+ foreach ( $res as $row ) {
+ $this->output( print_r( $row, true ) );
+ }
+ } else {
+ $affected = $db->affectedRows();
+ $this->output( "Query OK, $affected row(s) affected\n" );
+ }
+ }
+
+ public function getDbType() {
+ return Maintenance::DB_ADMIN;
+ }
}
-//-----------------------------------------------------------------------------
class SqlPromptPrinter {
function __construct( $prompt ) {
$this->prompt = $prompt;
@@ -53,17 +89,5 @@ class SqlPromptPrinter {
}
}
-function sqlPrintResult( $res, $db ) {
- if ( !$res ) {
- // Do nothing
- } elseif ( is_object( $res ) && $res->numRows() ) {
- while ( $row = $res->fetchObject() ) {
- print_r( $row );
- }
- } else {
- $affected = $db->affectedRows();
- echo "Query OK, $affected row(s) affected\n";
- }
-}
-
-
+$maintClass = "MwSql";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/sqlite.php b/maintenance/sqlite.php
new file mode 100644
index 00000000..8886fe74
--- /dev/null
+++ b/maintenance/sqlite.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Performs some operations specific to SQLite database backend
+ *
+ * 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
+ *
+ * @ingroup Maintenance
+ */
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+
+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( 'integrity', 'Check database for integrity' );
+ $this->addOption( 'backup-to', 'Backup database to the given file', false, true );
+ }
+
+ /**
+ * While we use database connection, this simple lie prevents useless --dbpass and
+ * --dbuser options from appearing in help message for this script.
+ */
+ public function getDbType() {
+ return Maintenance::DB_NONE;
+ }
+
+ public function execute() {
+ global $wgDBtype;
+
+ if ( $wgDBtype != 'sqlite' ) {
+ $this->error( "This maintenance script requires a SQLite database.\n" );
+ return;
+ }
+
+ $this->db = wfGetDB( DB_MASTER );
+
+ if ( $this->hasOption( 'vacuum' ) ) {
+ $this->vacuum();
+ }
+
+ if ( $this->hasOption( 'integrity' ) ) {
+ $this->integrityCheck();
+ }
+
+ if ( $this->hasOption( 'backup-to' ) ) {
+ $this->backup( $this->getOption( 'backup-to' ) );
+ }
+ }
+
+ private function vacuum() {
+ $prevSize = filesize( $this->db->mDatabaseFile );
+ if ( $prevSize == 0 ) {
+ $this->error( "Can't vacuum an empty database.\n", true );
+ }
+
+ $this->output( 'VACUUM: ' );
+ if ( $this->db->query( 'VACUUM' ) ) {
+ clearstatcache();
+ $newSize = filesize( $this->db->mDatabaseFile );
+ $this->output( sprintf( "Database size was %d, now %d (%.1f%% reduction).\n",
+ $prevSize, $newSize, ( $prevSize - $newSize) * 100.0 / $prevSize ) );
+ } else {
+ $this->output( 'Error\n' );
+ }
+ }
+
+ private function integrityCheck() {
+ $this->output( "Performing database integrity checks:\n" );
+ $res = $this->db->query( 'PRAGMA integrity_check' );
+
+ if ( !$res || $res->numRows() == 0 ) {
+ $this->error( "Error: integrity check query returned nothing.\n" );
+ return;
+ }
+
+ foreach ( $res as $row ) {
+ $this->output( $row->integrity_check );
+ }
+ }
+
+ private function backup( $fileName ) {
+ $this->output( "Backing up database:\n Locking..." );
+ $this->db->query( 'BEGIN IMMEDIATE TRANSACTION', __METHOD__ );
+ $ourFile = $this->db->mDatabaseFile;
+ $this->output( " Copying database file $ourFile to $fileName... " );
+ wfSuppressWarnings( false );
+ if ( !copy( $ourFile, $fileName ) ) {
+ $err = error_get_last();
+ $this->error( " {$err['message']}" );
+ }
+ wfSuppressWarnings( true );
+ $this->output( " Releasing lock...\n" );
+ $this->db->query( 'COMMIT TRANSACTION', __METHOD__ );
+ }
+}
+
+$maintClass = "SqliteMaintenance";
+require_once( DO_MAINTENANCE ); \ No newline at end of file
diff --git a/maintenance/sqlite/archives/initial-indexes.sql b/maintenance/sqlite/archives/initial-indexes.sql
index a88b7a26..f0851163 100644
--- a/maintenance/sqlite/archives/initial-indexes.sql
+++ b/maintenance/sqlite/archives/initial-indexes.sql
@@ -4,8 +4,7 @@
-- the CREATE INDEX statement will fail if there are duplicate values.
--
-- Ignore duplicates, several tables will have them (e.g. bug 16966) but in
--- most cases it's harmless to discard them. We'll keep the old tables with
--- duplicates in so that the user can recover them in case of disaster.
+-- most cases it's harmless to discard them.
--------------------------------------------------------------------------------
-- Drop temporary tables from aborted runs
@@ -296,41 +295,41 @@ INSERT OR IGNORE INTO /*_*/page_props_tmp SELECT * FROM /*_*/page_props;
-- Do the table renames
--------------------------------------------------------------------------------
-ALTER TABLE /*_*/user RENAME TO /*_*/user_old_13;
+DROP TABLE /*_*/user;
ALTER TABLE /*_*/user_tmp RENAME TO /*_*/user;
-ALTER TABLE /*_*/user_groups RENAME TO /*_*/user_groups_old_13;
+DROP TABLE /*_*/user_groups;
ALTER TABLE /*_*/user_groups_tmp RENAME TO /*_*/user_groups;
-ALTER TABLE /*_*/page RENAME TO /*_*/page_old_13;
+DROP TABLE /*_*/page;
ALTER TABLE /*_*/page_tmp RENAME TO /*_*/page;
-ALTER TABLE /*_*/revision RENAME TO /*_*/revision_old_13;
+DROP TABLE /*_*/revision;
ALTER TABLE /*_*/revision_tmp RENAME TO /*_*/revision;
-ALTER TABLE /*_*/pagelinks RENAME TO /*_*/pagelinks_old_13;
+DROP TABLE /*_*/pagelinks;
ALTER TABLE /*_*/pagelinks_tmp RENAME TO /*_*/pagelinks;
-ALTER TABLE /*_*/templatelinks RENAME TO /*_*/templatelinks_old_13;
+DROP TABLE /*_*/templatelinks;
ALTER TABLE /*_*/templatelinks_tmp RENAME TO /*_*/templatelinks;
-ALTER TABLE /*_*/imagelinks RENAME TO /*_*/imagelinks_old_13;
+DROP TABLE /*_*/imagelinks;
ALTER TABLE /*_*/imagelinks_tmp RENAME TO /*_*/imagelinks;
-ALTER TABLE /*_*/categorylinks RENAME TO /*_*/categorylinks_old_13;
+DROP TABLE /*_*/categorylinks;
ALTER TABLE /*_*/categorylinks_tmp RENAME TO /*_*/categorylinks;
-ALTER TABLE /*_*/category RENAME TO /*_*/category_old_13;
+DROP TABLE /*_*/category;
ALTER TABLE /*_*/category_tmp RENAME TO /*_*/category;
-ALTER TABLE /*_*/langlinks RENAME TO /*_*/langlinks_old_13;
+DROP TABLE /*_*/langlinks;
ALTER TABLE /*_*/langlinks_tmp RENAME TO /*_*/langlinks;
-ALTER TABLE /*_*/site_stats RENAME TO /*_*/site_stats_old_13;
+DROP TABLE /*_*/site_stats;
ALTER TABLE /*_*/site_stats_tmp RENAME TO /*_*/site_stats;
-ALTER TABLE /*_*/ipblocks RENAME TO /*_*/ipblocks_old_13;
+DROP TABLE /*_*/ipblocks;
ALTER TABLE /*_*/ipblocks_tmp RENAME TO /*_*/ipblocks;
-ALTER TABLE /*_*/watchlist RENAME TO /*_*/watchlist_old_13;
+DROP TABLE /*_*/watchlist;
ALTER TABLE /*_*/watchlist_tmp RENAME TO /*_*/watchlist;
-ALTER TABLE /*_*/math RENAME TO /*_*/math_old_13;
+DROP TABLE /*_*/math;
ALTER TABLE /*_*/math_tmp RENAME TO /*_*/math;
-ALTER TABLE /*_*/interwiki RENAME TO /*_*/interwiki_old_13;
+DROP TABLE /*_*/interwiki;
ALTER TABLE /*_*/interwiki_tmp RENAME TO /*_*/interwiki;
-ALTER TABLE /*_*/page_restrictions RENAME TO /*_*/page_restrictions_old_13;
+DROP TABLE /*_*/page_restrictions;
ALTER TABLE /*_*/page_restrictions_tmp RENAME TO /*_*/page_restrictions;
-ALTER TABLE /*_*/protected_titles RENAME TO /*_*/protected_titles_old_13;
+DROP TABLE /*_*/protected_titles;
ALTER TABLE /*_*/protected_titles_tmp RENAME TO /*_*/protected_titles;
-ALTER TABLE /*_*/page_props RENAME TO /*_*/page_props_old_13;
+DROP TABLE /*_*/page_props;
ALTER TABLE /*_*/page_props_tmp RENAME TO /*_*/page_props;
--------------------------------------------------------------------------------
diff --git a/maintenance/sqlite/archives/patch-log_user_text.sql b/maintenance/sqlite/archives/patch-log_user_text.sql
new file mode 100644
index 00000000..c7fcc75f
--- /dev/null
+++ b/maintenance/sqlite/archives/patch-log_user_text.sql
@@ -0,0 +1,5 @@
+ALTER TABLE /*$wgDBprefix*/logging ADD COLUMN log_user_text TEXT NOT NULL default '';
+ALTER TABLE /*$wgDBprefix*/logging ADD COLUMN log_page INTEGER NULL;
+
+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);
diff --git a/maintenance/sqlite/archives/patch-rd_interwiki.sql b/maintenance/sqlite/archives/patch-rd_interwiki.sql
new file mode 100644
index 00000000..ae4870a4
--- /dev/null
+++ b/maintenance/sqlite/archives/patch-rd_interwiki.sql
@@ -0,0 +1,5 @@
+-- Add interwiki and fragment columns to redirect table
+
+ALTER TABLE /*$wgDBprefix*/redirect ADD COLUMN rd_interwiki TEXT default NULL;
+ALTER TABLE /*$wgDBprefix*/redirect ADD COLUMN rd_fragment TEXT default NULL;
+
diff --git a/maintenance/sqlite/archives/patch-tc-timestamp.sql b/maintenance/sqlite/archives/patch-tc-timestamp.sql
new file mode 100644
index 00000000..551a5f1c
--- /dev/null
+++ b/maintenance/sqlite/archives/patch-tc-timestamp.sql
@@ -0,0 +1,3 @@
+UPDATE /*_*/transcache SET tc_time = strftime('%Y%m%d%H%M%S', datetime(tc_time, 'unixepoch'));
+
+INSERT INTO /*_*/updatelog VALUES ('convert transcache field');
diff --git a/maintenance/sqlite/archives/searchindex-fts3.sql b/maintenance/sqlite/archives/searchindex-fts3.sql
new file mode 100644
index 00000000..c3a86894
--- /dev/null
+++ b/maintenance/sqlite/archives/searchindex-fts3.sql
@@ -0,0 +1,18 @@
+-- Patch that introduces fulltext search capabilities to SQLite schema
+-- Requires that SQLite must be compiled with FTS3 module (comes with core amalgamation).
+-- See http://sqlite.org/fts3.html for details of syntax.
+-- Will fail if FTS3 is not present,
+DROP TABLE IF EXISTS /*_*/searchindex;
+CREATE VIRTUAL TABLE /*_*/searchindex USING FTS3(
+ -- Key to page_id
+ -- Disabled, instead we use the built-in rowid column
+ -- si_page INTEGER NOT NULL,
+
+ -- Munged version of title
+ si_title,
+
+ -- Munged version of body text
+ si_text
+);
+
+INSERT INTO /*_*/updatelog VALUES ('fts3'); \ No newline at end of file
diff --git a/maintenance/sqlite/archives/searchindex-no-fts.sql b/maintenance/sqlite/archives/searchindex-no-fts.sql
new file mode 100644
index 00000000..bc014b3d
--- /dev/null
+++ b/maintenance/sqlite/archives/searchindex-no-fts.sql
@@ -0,0 +1,25 @@
+-- Searchindex table definition for cases when no full-text search SQLite module is present
+-- (currently, only FTS3 is supported).
+-- Use it if you are moving your database from environment with FTS support
+-- to environment without it.
+
+DROP TABLE IF EXISTS /*_*/searchindex;
+
+-- These are pieces of FTS3-enabled searchindex
+DROP TABLE IF EXISTS /*_*/searchindex_content;
+DROP TABLE IF EXISTS /*_*/searchindex_segdir;
+DROP TABLE IF EXISTS /*_*/searchindex_segments;
+
+CREATE TABLE /*_*/searchindex (
+ -- Key to page_id
+ -- Disabled, instead we use the built-in rowid column
+ -- si_page INTEGER NOT NULL,
+
+ -- Munged version of title
+ si_title TEXT,
+
+ -- Munged version of body text
+ si_text TEXT
+);
+
+DELETE FROM /*_*/updatelog WHERE ul_key='fts3'; \ No newline at end of file
diff --git a/maintenance/stats.php b/maintenance/stats.php
index 00f79ded..e20c345a 100644
--- a/maintenance/stats.php
+++ b/maintenance/stats.php
@@ -1,58 +1,89 @@
<?php
/**
- * Show statistics from memcached
+ * Show statistics from the cache
+ *
+ * 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('commandLine.inc');
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if( get_class( $wgMemc ) == 'FakeMemCachedClient' ) {
- die("You are running FakeMemCachedClient, I can not provide any statistics.\n");
-}
-$session = intval($wgMemc->get(wfMemcKey('stats','request_with_session')));
-$noSession = intval($wgMemc->get(wfMemcKey('stats','request_without_session')));
-$total = $session + $noSession;
-if ( $total == 0 ) {
- die("You either have no stats or memcached isn't running. Aborting.\n");
+class CacheStats extends Maintenance {
+
+ public function __construct() {
+ $this->mDescription = "Show statistics from the cache";
+ }
+
+ public function execute() {
+ global $wgMemc;
+
+ // Can't do stats if
+ if( get_class( $wgMemc ) == 'FakeMemCachedClient' ) {
+ $this->error( "You are running FakeMemCachedClient, I can not provide any statistics.", 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')));
+ $invalid = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_invalid')));
+ $expired = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_expired')));
+ $absent = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_absent')));
+ $stub = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_stub')));
+ $total = $hits + $invalid + $expired + $absent + $stub;
+ $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 ) );
+ $this->output( sprintf( "invalid: %-10d %6.2f%%\n", $invalid, $invalid/$total*100 ) );
+ $this->output( sprintf( "expired: %-10d %6.2f%%\n", $expired, $expired/$total*100 ) );
+ $this->output( sprintf( "absent: %-10d %6.2f%%\n", $absent, $absent/$total*100 ) );
+ $this->output( sprintf( "stub threshold: %-10d %6.2f%%\n", $stub, $stub/$total*100 ) );
+ $this->output( sprintf( "total: %-10d %6.2f%%\n", $total, 100 ) );
+
+ $hits = intval($wgMemc->get(wfMemcKey('stats','image_cache_hit')));
+ $misses = intval($wgMemc->get(wfMemcKey('stats','image_cache_miss')));
+ $updates = intval($wgMemc->get(wfMemcKey('stats','image_cache_update')));
+ $total = $hits + $misses;
+ $this->output("\nImage cache\n");
+ $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 ) );
+ $this->output( sprintf( "misses: %-10d %6.2f%%\n", $misses, $misses/$total*100 ) );
+ $this->output( sprintf( "updates: %-10d\n", $updates ) );
+
+ $hits = intval($wgMemc->get(wfMemcKey('stats','diff_cache_hit')));
+ $misses = intval($wgMemc->get(wfMemcKey('stats','diff_cache_miss')));
+ $uncacheable = intval($wgMemc->get(wfMemcKey('stats','diff_uncacheable')));
+ $total = $hits + $misses + $uncacheable;
+ $this->output("\nDiff cache\n");
+ $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 ) );
+ $this->output( sprintf( "misses: %-10d %6.2f%%\n", $misses, $misses/$total*100 ) );
+ $this->output( sprintf( "uncacheable: %-10d %6.2f%%\n", $uncacheable, $uncacheable/$total*100 ) );
+ }
}
-print "Requests\n";
-printf( "with session: %-10d %6.2f%%\n", $session, $session/$total*100 );
-printf( "without session: %-10d %6.2f%%\n", $noSession, $noSession/$total*100 );
-printf( "total: %-10d %6.2f%%\n", $total, 100 );
-
-
-print "\nParser cache\n";
-$hits = intval($wgMemc->get(wfMemcKey('stats','pcache_hit')));
-$invalid = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_invalid')));
-$expired = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_expired')));
-$absent = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_absent')));
-$stub = intval($wgMemc->get(wfMemcKey('stats','pcache_miss_stub')));
-$total = $hits + $invalid + $expired + $absent + $stub;
-printf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 );
-printf( "invalid: %-10d %6.2f%%\n", $invalid, $invalid/$total*100 );
-printf( "expired: %-10d %6.2f%%\n", $expired, $expired/$total*100 );
-printf( "absent: %-10d %6.2f%%\n", $absent, $absent/$total*100 );
-printf( "stub threshold: %-10d %6.2f%%\n", $stub, $stub/$total*100 );
-printf( "total: %-10d %6.2f%%\n", $total, 100 );
-
-$hits = intval($wgMemc->get(wfMemcKey('stats','image_cache_hit')));
-$misses = intval($wgMemc->get(wfMemcKey('stats','image_cache_miss')));
-$updates = intval($wgMemc->get(wfMemcKey('stats','image_cache_update')));
-$total = $hits + $misses;
-print("\nImage cache\n");
-printf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 );
-printf( "misses: %-10d %6.2f%%\n", $misses, $misses/$total*100 );
-printf( "updates: %-10d\n", $updates );
-
-$hits = intval($wgMemc->get(wfMemcKey('stats','diff_cache_hit')));
-$misses = intval($wgMemc->get(wfMemcKey('stats','diff_cache_miss')));
-$uncacheable = intval($wgMemc->get(wfMemcKey('stats','diff_uncacheable')));
-$total = $hits + $misses + $uncacheable;
-print("\nDiff cache\n");
-printf( "hits: %-10d %6.2f%%\n", $hits, $hits/$total*100 );
-printf( "misses: %-10d %6.2f%%\n", $misses, $misses/$total*100 );
-printf( "uncacheable: %-10d %6.2f%%\n", $uncacheable, $uncacheable/$total*100 );
+
+$maintClass = "CacheStats";
+require_once( DO_MAINTENANCE );
+
+
diff --git a/maintenance/storage/compressOld.inc b/maintenance/storage/compressOld.inc
index fb8cc422..981cfda5 100644
--- a/maintenance/storage/compressOld.inc
+++ b/maintenance/storage/compressOld.inc
@@ -57,7 +57,8 @@ function compressPage( $row, $extdb ) {
'old_text' => $compress
), array( /* WHERE */
'old_id' => $row->old_id
- ), $fname, 'LIMIT 1'
+ ), $fname,
+ array( 'LIMIT' => 1 )
);
return true;
}
@@ -104,7 +105,8 @@ function compressWithConcat( $startId, $maxChunkSize, $beginDate,
# 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 LIKE '%object%' AND old_flags NOT LIKE '%external%'");
+ '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 ) ) {
diff --git a/maintenance/storage/compressOld.php b/maintenance/storage/compressOld.php
index 6f8b48eb..7ff102a5 100644
--- a/maintenance/storage/compressOld.php
+++ b/maintenance/storage/compressOld.php
@@ -68,6 +68,6 @@ if ( $success ) {
print "Done.\n";
}
-exit();
+exit(0);
diff --git a/maintenance/storage/dumpRev.php b/maintenance/storage/dumpRev.php
index c84d8aa5..95404244 100644
--- a/maintenance/storage/dumpRev.php
+++ b/maintenance/storage/dumpRev.php
@@ -1,56 +1,79 @@
<?php
/**
- * @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
+ *
* @ingroup Maintenance ExternalStorage
*/
-require_once( dirname(__FILE__) . '/../commandLine.inc' );
+require_once( dirname(__FILE__) . '/../Maintenance.php' );
-$wgDebugLogFile = '/dev/stdout';
-
-
-$dbr = wfGetDB( DB_SLAVE );
-$row = $dbr->selectRow(
- array( 'text', 'revision' ),
- array( 'old_flags', 'old_text' ),
- array( 'old_id=rev_text_id', 'rev_id' => $args[0] )
-);
-if ( !$row ) {
- print "Row not found\n";
- exit;
-}
+class DumpRev extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->addArg( 'rev-id', 'Revision ID', true );
+ }
-$flags = explode( ',', $row->old_flags );
-$text = $row->old_text;
-if ( in_array( 'external', $flags ) ) {
- print "External $text\n";
- if ( preg_match( '!^DB://(\w+)/(\w+)/(\w+)$!', $text, $m ) ) {
- $es = ExternalStore::getStoreObject( 'DB' );
- $blob = $es->fetchBlob( $m[1], $m[2], $m[3] );
- if ( strtolower( get_class( $blob ) ) == 'concatenatedgziphistoryblob' ) {
- print "Found external CGZ\n";
- $blob->uncompress();
- print "Items: (" . implode( ', ', array_keys( $blob->mItems ) ) . ")\n";
- $text = $blob->getItem( $m[3] );
+ public function execute() {
+ $dbr = wfGetDB( DB_SLAVE );
+ $row = $dbr->selectRow(
+ array( 'text', 'revision' ),
+ array( 'old_flags', 'old_text' ),
+ array( 'old_id=rev_text_id', 'rev_id' => $this->getArg() )
+ );
+ if ( !$row ) {
+ $this->error( "Row not found", true );
+ }
+
+ $flags = explode( ',', $row->old_flags );
+ $text = $row->old_text;
+ if ( in_array( 'external', $flags ) ) {
+ $this->output( "External $text\n" );
+ if ( preg_match( '!^DB://(\w+)/(\w+)/(\w+)$!', $text, $m ) ) {
+ $es = ExternalStore::getStoreObject( 'DB' );
+ $blob = $es->fetchBlob( $m[1], $m[2], $m[3] );
+ if ( strtolower( get_class( $blob ) ) == 'concatenatedgziphistoryblob' ) {
+ $this->output( "Found external CGZ\n" );
+ $blob->uncompress();
+ $this->output( "Items: (" . implode( ', ', array_keys( $blob->mItems ) ) . ")\n" );
+ $text = $blob->getItem( $m[3] );
+ } else {
+ $this->output( "CGZ expected at $text, got " . gettype( $blob ) . "\n" );
+ $text = $blob;
+ }
+ } else {
+ $this->output( "External plain $text\n" );
+ $text = ExternalStore::fetchFromURL( $text );
+ }
+ }
+ if ( in_array( 'gzip', $flags ) ) {
+ $text = gzinflate( $text );
+ }
+ if ( in_array( 'object', $flags ) ) {
+ $obj = unserialize( $text );
+ $text = $obj->getText();
+ }
+
+ if ( is_object( $text ) ) {
+ $this->error( "Unexpectedly got object of type: " . get_class( $text ) );
} else {
- print "CGZ expected at $text, got " . gettype( $blob ) . "\n";
- $text = $blob;
+ $this->output( "Text length: " . strlen( $text ) ."\n" );
+ $this->output( substr( $text, 0, 100 ) . "\n" );
}
- } else {
- print "External plain $text\n";
- $text = ExternalStore::fetchFromURL( $text );
}
}
-if ( in_array( 'gzip', $flags ) ) {
- $text = gzinflate( $text );
-}
-if ( in_array( 'object', $flags ) ) {
- $text = unserialize( $text );
-}
-if ( is_object( $text ) ) {
- print "Unexpectedly got object of type: " . get_class( $text ) . "\n";
-} else {
- print "Text length: " . strlen( $text ) ."\n";
- print substr( $text, 0, 100 ) . "\n";
-}
+$maintClass = "DumpRev";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/storage/fixBug20757.php b/maintenance/storage/fixBug20757.php
new file mode 100644
index 00000000..922d4725
--- /dev/null
+++ b/maintenance/storage/fixBug20757.php
@@ -0,0 +1,314 @@
+<?php
+
+require_once( dirname( __FILE__ ) . '/../Maintenance.php' );
+
+class FixBug20757 extends Maintenance {
+ var $batchSize = 10000;
+ var $mapCache = array();
+ var $mapCacheSize = 0;
+ var $maxMapCacheSize = 1000000;
+
+ function __construct() {
+ parent::__construct();
+ $this->mDescription = 'Script to fix bug 20757 assuming that blob_tracking is intact';
+ $this->addOption( 'dry-run', 'Report only' );
+ $this->addOption( 'start', 'old_id to start at', false, true );
+ }
+
+ function execute() {
+ $dbr = wfGetDB( DB_SLAVE );
+ $dbw = wfGetDB( DB_MASTER );
+
+ $dryRun = $this->getOption( 'dry-run' );
+ if ( $dryRun ) {
+ print "Dry run only.\n";
+ }
+
+ $startId = $this->getOption( 'start', 0 );
+ $numGood = 0;
+ $numFixed = 0;
+ $numBad = 0;
+
+ $totalRevs = $dbr->selectField( 'text', 'MAX(old_id)', false, __METHOD__ );
+
+ while ( true ) {
+ print "ID: $startId / $totalRevs\r";
+
+ $res = $dbr->select(
+ 'text',
+ array( 'old_id', 'old_flags', 'old_text' ),
+ array(
+ 'old_id > ' . intval( $startId ),
+ 'old_flags LIKE \'%object%\' AND old_flags NOT LIKE \'%external%\'',
+ 'LOWER(CONVERT(LEFT(old_text,22) USING latin1)) = \'o:15:"historyblobstub"\'',
+ ),
+ __METHOD__,
+ array(
+ 'ORDER BY' => 'old_id',
+ 'LIMIT' => $this->batchSize,
+ )
+ );
+
+ if ( !$res->numRows() ) {
+ break;
+ }
+
+ $secondaryIds = array();
+ $stubs = array();
+
+ foreach ( $res as $row ) {
+ $startId = $row->old_id;
+
+ // Basic sanity checks
+ $obj = unserialize( $row->old_text );
+ if ( $obj === false ) {
+ print "{$row->old_id}: unrecoverable: cannot unserialize\n";
+ ++$numBad;
+ continue;
+ }
+
+ if ( !is_object( $obj ) ) {
+ print "{$row->old_id}: unrecoverable: unserialized to type " .
+ gettype( $obj ) . ", possible double-serialization\n";
+ ++$numBad;
+ continue;
+ }
+
+ if ( strtolower( get_class( $obj ) ) !== 'historyblobstub' ) {
+ print "{$row->old_id}: unrecoverable: unexpected object class " .
+ get_class( $obj ) . "\n";
+ ++$numBad;
+ continue;
+ }
+
+ // Process flags
+ $flags = explode( ',', $row->old_flags );
+ if ( in_array( 'utf-8', $flags ) || in_array( 'utf8', $flags ) ) {
+ $legacyEncoding = false;
+ } else {
+ $legacyEncoding = true;
+ }
+
+ // Queue the stub for future batch processing
+ $id = intval( $obj->mOldId );
+ $secondaryIds[] = $id;
+ $stubs[$row->old_id] = array(
+ 'legacyEncoding' => $legacyEncoding,
+ 'secondaryId' => $id,
+ 'hash' => $obj->mHash,
+ );
+ }
+
+ $secondaryIds = array_unique( $secondaryIds );
+
+ if ( !count( $secondaryIds ) ) {
+ continue;
+ }
+
+ // Run the batch query on blob_tracking
+ $res = $dbr->select(
+ 'blob_tracking',
+ '*',
+ array(
+ 'bt_text_id' => $secondaryIds,
+ ),
+ __METHOD__
+ );
+ $trackedBlobs = array();
+ foreach ( $res as $row ) {
+ $trackedBlobs[$row->bt_text_id] = $row;
+ }
+
+ // Process the stubs
+ $stubsToFix = array();
+ foreach ( $stubs as $primaryId => $stub ) {
+ $secondaryId = $stub['secondaryId'];
+ if ( !isset( $trackedBlobs[$secondaryId] ) ) {
+ // No tracked blob. Work out what went wrong
+ $secondaryRow = $dbr->selectRow(
+ 'text',
+ array( 'old_flags', 'old_text' ),
+ array( 'old_id' => $secondaryId ),
+ __METHOD__
+ );
+ if ( !$secondaryRow ) {
+ print "$primaryId: unrecoverable: secondary row is missing\n";
+ ++$numBad;
+ } elseif ( $this->isUnbrokenStub( $stub, $secondaryRow ) ) {
+ // Not broken yet, and not in the tracked clusters so it won't get
+ // broken by the current RCT run.
+ ++$numGood;
+ } elseif ( strpos( $secondaryRow->old_flags, 'external' ) !== false ) {
+ print "$primaryId: unrecoverable: secondary gone to {$secondaryRow->old_text}\n";
+ ++$numBad;
+ } else {
+ print "$primaryId: unrecoverable: miscellaneous corruption of secondary row\n";
+ ++$numBad;
+ }
+ unset( $stubs[$primaryId] );
+ continue;
+ }
+ $trackRow = $trackedBlobs[$secondaryId];
+
+ // Check that the specified text really is available in the tracked source row
+ $url = "DB://{$trackRow->bt_cluster}/{$trackRow->bt_blob_id}/{$stub['hash']}";
+ $text = ExternalStore::fetchFromURL( $url );
+ if ( $text === false ) {
+ print "$primaryId: unrecoverable: source text missing\n";
+ ++$numBad;
+ unset( $stubs[$primaryId] );
+ continue;
+ }
+ if ( md5( $text ) !== $stub['hash'] ) {
+ print "$primaryId: unrecoverable: content hashes do not match\n";
+ ++$numBad;
+ unset( $stubs[$primaryId] );
+ continue;
+ }
+
+ // Find the page_id and rev_id
+ // The page is probably the same as the page of the secondary row
+ $pageId = intval( $trackRow->bt_page );
+ if ( !$pageId ) {
+ $revId = $pageId = 0;
+ } else {
+ $revId = $this->findTextIdInPage( $pageId, $primaryId );
+ if ( !$revId ) {
+ // Actually an orphan
+ $pageId = $revId = 0;
+ }
+ }
+
+ $newFlags = $stub['legacyEncoding'] ? 'external' : 'external,utf-8';
+
+ if ( !$dryRun ) {
+ // Reset the text row to point to the original copy
+ $dbw->begin();
+ $dbw->update(
+ 'text',
+ // SET
+ array(
+ 'old_flags' => $newFlags,
+ 'old_text' => $url
+ ),
+ // WHERE
+ array( 'old_id' => $primaryId ),
+ __METHOD__
+ );
+
+ // Add a blob_tracking row so that the new reference can be recompressed
+ // without needing to run trackBlobs.php again
+ $dbw->insert( 'blob_tracking',
+ array(
+ 'bt_page' => $pageId,
+ 'bt_rev_id' => $revId,
+ 'bt_text_id' => $primaryId,
+ 'bt_cluster' => $trackRow->bt_cluster,
+ 'bt_blob_id' => $trackRow->bt_blob_id,
+ 'bt_cgz_hash' => $stub['hash'],
+ 'bt_new_url' => null,
+ 'bt_moved' => 0,
+ ),
+ __METHOD__
+ );
+ $dbw->commit();
+ $this->waitForSlaves();
+ }
+
+ print "$primaryId: resolved to $url\n";
+ ++$numFixed;
+ }
+ }
+
+ print "\n";
+ print "Fixed: $numFixed\n";
+ print "Unrecoverable: $numBad\n";
+ print "Good stubs: $numGood\n";
+ }
+
+ function waitForSlaves() {
+ static $iteration = 0;
+ ++$iteration;
+ if ( ++$iteration > 50 == 0 ) {
+ wfWaitForSlaves( 5 );
+ $iteration = 0;
+ }
+ }
+
+ function findTextIdInPage( $pageId, $textId ) {
+ $ids = $this->getRevTextMap( $pageId );
+ if ( !isset( $ids[$textId] ) ) {
+ return null;
+ } else {
+ return $ids[$textId];
+ }
+ }
+
+ function getRevTextMap( $pageId ) {
+ if ( !isset( $this->mapCache[$pageId] ) ) {
+ // Limit cache size
+ while ( $this->mapCacheSize > $this->maxMapCacheSize ) {
+ $key = key( $this->mapCache );
+ $this->mapCacheSize -= count( $this->mapCache[$key] );
+ unset( $this->mapCache[$key] );
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $map = array();
+ $res = $dbr->select( 'revision',
+ array( 'rev_id', 'rev_text_id' ),
+ array( 'rev_page' => $pageId ),
+ __METHOD__
+ );
+ foreach ( $res as $row ) {
+ $map[$row->rev_text_id] = $row->rev_id;
+ }
+ $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.
+ */
+ function isUnbrokenStub( $stub, $secondaryRow ) {
+ $flags = explode( ',', $secondaryRow->old_flags );
+ $text = $secondaryRow->old_text;
+ if( in_array( 'external', $flags ) ) {
+ $url = $text;
+ @list( /* $proto */ , $path ) = explode( '://', $url, 2 );
+ if ( $path == "" ) {
+ return false;
+ }
+ $text = ExternalStore::fetchFromUrl( $url );
+ }
+ if( !in_array( 'object', $flags ) ) {
+ return false;
+ }
+
+ if( in_array( 'gzip', $flags ) ) {
+ $obj = unserialize( gzinflate( $text ) );
+ } else {
+ $obj = unserialize( $text );
+ }
+
+ if( !is_object( $obj ) ) {
+ // Correct for old double-serialization bug.
+ $obj = unserialize( $obj );
+ }
+
+ if ( !is_object( $obj ) ) {
+ return false;
+ }
+
+ $obj->uncompress();
+ $text = $obj->getItem( $stub['hash'] );
+ return $text !== false;
+ }
+}
+
+$maintClass = 'FixBug20757';
+require_once( DO_MAINTENANCE );
+
diff --git a/maintenance/storage/make-blobs b/maintenance/storage/make-blobs
index 9eb7e83e..36cf9ced 100755
--- a/maintenance/storage/make-blobs
+++ b/maintenance/storage/make-blobs
@@ -1,11 +1,16 @@
#!/bin/bash
-if [ X$2 == X ];then
- echo 'Usage: make-blobs <server> <db>'
+if [ -z $2 ];then
+ echo 'Usage: make-blobs <server> <db> [<table name>]'
exit 1
fi
+if [ -z $3 ]; then
+ table=blobs
+else
+ table=$3
+fi
echo "CREATE DATABASE $2" | mysql -u wikiadmin -p`wikiadmin_pass` -h $1 && \
-mysql -u wikiadmin -p`wikiadmin_pass` -h $1 $2 < blobs.sql
+sed "s/blobs\>/$table/" blobs.sql | mysql -u wikiadmin -p`wikiadmin_pass` -h $1 $2
diff --git a/maintenance/storage/moveToExternal.php b/maintenance/storage/moveToExternal.php
index a8b2f93b..dc11856a 100644
--- a/maintenance/storage/moveToExternal.php
+++ b/maintenance/storage/moveToExternal.php
@@ -62,7 +62,7 @@ function moveToExternal( $cluster, $maxID, $minID = 1 ) {
$res = $dbr->select( 'text', array( 'old_id', 'old_flags', 'old_text' ),
array(
"old_id BETWEEN $blockStart AND $blockEnd",
- "old_flags NOT LIKE '%external%'",
+ 'old_flags NOT ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ),
), $fname );
while ( $row = $dbr->fetchObject( $res ) ) {
# Resolve stubs
diff --git a/maintenance/storage/orphanStats.php b/maintenance/storage/orphanStats.php
index afea815e..63f9025b 100644
--- a/maintenance/storage/orphanStats.php
+++ b/maintenance/storage/orphanStats.php
@@ -2,21 +2,43 @@
/**
* Show some statistics on the blob_orphans table, created with trackBlobs.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
+ *
+ * @ingroup Maintenance ExternalStorage
*/
-require_once( dirname(__FILE__).'/../commandLine.inc' );
+require_once( dirname(__FILE__) . '/../Maintenance.php' );
-$stats = new OrphanStats;
-$stats->execute();
+class OrphanStats extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "how some statistics on the blob_orphans table, created with trackBlobs.php";
+ }
-class OrphanStats {
- function getDB( $cluster ) {
+ private function getDB( $cluster ) {
$lb = wfGetLBFactory()->getExternalLB( $cluster );
return $lb->getConnection( DB_SLAVE );
}
- function execute() {
+ public function execute() {
$extDBs = array();
$dbr = wfGetDB( DB_SLAVE );
+ if( !$dbr->tableExists( 'blob_orphans' ) ) {
+ $this->error( "blob_orphans doesn't seem to exist, need to run trackBlobs.php first", true );
+ }
$res = $dbr->select( 'blob_orphans', '*', false, __METHOD__ );
$num = 0;
@@ -36,11 +58,14 @@ class OrphanStats {
}
unset( $res );
- echo "Number of orphans: $num\n";
+ $this->output( "Number of orphans: $num\n" );
if ( $num > 0 ) {
- echo "Average size: " . round( $totalSize / $num, 0 ) . " bytes\n" .
+ $this->output( "Average size: " . round( $totalSize / $num, 0 ) . " bytes\n" .
"Max size: $maxSize\n" .
- "Number of unique texts: " . count( $hashes ) . "\n";
+ "Number of unique texts: " . count( $hashes ) . "\n" );
}
}
}
+
+$maintClass = "OrphanStats";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/storage/recompressTracked.php b/maintenance/storage/recompressTracked.php
index d8d2e4ef..e43dbe5c 100644
--- a/maintenance/storage/recompressTracked.php
+++ b/maintenance/storage/recompressTracked.php
@@ -31,11 +31,13 @@ class RecompressTracked {
var $copyOnly = false;
var $isChild = false;
var $slaveId = false;
+ var $noCount = false;
var $debugLog, $infoLog, $criticalLog;
var $store;
static $optionsWithArgs = array( 'procs', 'slave-id', 'debug-log', 'info-log', 'critical-log' );
static $cmdLineOptionMap = array(
+ 'no-count' => 'noCount',
'procs' => 'numProcs',
'copy-only' => 'copyOnly',
'child' => 'isChild',
@@ -259,12 +261,16 @@ class RecompressTracked {
$dbr = wfGetDB( DB_SLAVE );
$i = 0;
$startId = 0;
- $numPages = $dbr->selectField( 'blob_tracking',
- 'COUNT(DISTINCT bt_page)',
- # A condition is required so that this query uses the index
- array( 'bt_moved' => 0 ),
- __METHOD__
- );
+ if ( $this->noCount ) {
+ $numPages = '[unknown]';
+ } else {
+ $numPages = $dbr->selectField( 'blob_tracking',
+ 'COUNT(DISTINCT bt_page)',
+ # A condition is required so that this query uses the index
+ array( 'bt_moved' => 0 ),
+ __METHOD__
+ );
+ }
if ( $this->copyOnly ) {
$this->info( "Copying pages..." );
} else {
@@ -310,7 +316,7 @@ class RecompressTracked {
if ( $current == $end || $this->numBatches >= $this->reportingInterval ) {
$this->numBatches = 0;
$this->info( "$label: $current / $end" );
- wfWaitForSlaves( 5 );
+ $this->waitForSlaves();
}
}
@@ -321,12 +327,16 @@ class RecompressTracked {
$dbr = wfGetDB( DB_SLAVE );
$startId = 0;
$i = 0;
- $numOrphans = $dbr->selectField( 'blob_tracking',
- 'COUNT(DISTINCT bt_text_id)',
- array( 'bt_moved' => 0, 'bt_page' => 0 ),
- __METHOD__ );
- if ( !$numOrphans ) {
- return;
+ if ( $this->noCount ) {
+ $numOrphans = '[unknown]';
+ } else {
+ $numOrphans = $dbr->selectField( 'blob_tracking',
+ 'COUNT(DISTINCT bt_text_id)',
+ array( 'bt_moved' => 0, 'bt_page' => 0 ),
+ __METHOD__ );
+ if ( !$numOrphans ) {
+ return;
+ }
}
if ( $this->copyOnly ) {
$this->info( "Copying orphans..." );
@@ -404,7 +414,7 @@ class RecompressTracked {
case 'quit':
return;
}
- wfWaitForSlaves( 5 );
+ $this->waitForSlaves();
}
}
@@ -469,6 +479,7 @@ class RecompressTracked {
$this->debug( "$titleText: committing blob with " . $trx->getSize() . " items" );
$trx->commit();
$trx = new CgzCopyTransaction( $this, $this->pageBlobClass );
+ $this->waitForSlaves();
}
}
$startId = $row->bt_text_id;
@@ -545,6 +556,9 @@ class RecompressTracked {
$this->debug( 'Incomplete: ' . $res->numRows() . ' rows' );
foreach ( $res as $row ) {
$this->moveTextRow( $row->bt_text_id, $row->bt_new_url );
+ if ( $row->bt_text_id % 10 == 0 ) {
+ $this->waitForSlaves();
+ }
}
$startId = $row->bt_text_id;
}
@@ -604,11 +618,26 @@ class RecompressTracked {
$this->debug( "[orphan]: committing blob with " . $trx->getSize() . " rows" );
$trx->commit();
$trx = new CgzCopyTransaction( $this, $this->orphanBlobClass );
+ $this->waitForSlaves();
}
}
$this->debug( "[orphan]: committing blob with " . $trx->getSize() . " rows" );
$trx->commit();
}
+
+ /**
+ * Wait for slaves (quietly)
+ */
+ function waitForSlaves() {
+ $lb = wfGetLB();
+ while ( true ) {
+ list( $host, $maxLag ) = $lb->getMaxLag();
+ if ( $maxLag < 2 ) {
+ break;
+ }
+ sleep( 5 );
+ }
+ }
}
/**
diff --git a/maintenance/storage/resolveStubs.php b/maintenance/storage/resolveStubs.php
index 3db9e480..346151e9 100644
--- a/maintenance/storage/resolveStubs.php
+++ b/maintenance/storage/resolveStubs.php
@@ -35,11 +35,9 @@ function resolveStubs() {
$res = $dbr->select( 'text', array( 'old_id', 'old_text', 'old_flags' ),
"old_id>=$start AND old_id<=$end " .
- # Using a more restrictive flag set for now, until I do some more analysis -- TS
- #"AND old_flags LIKE '%object%' AND old_flags NOT LIKE '%external%' ".
-
- "AND old_flags='object' " .
- "AND LOWER(LEFT(old_text,22)) = 'O:15:\"historyblobstub\"'", $fname );
+ "AND old_flags LIKE '%object%' AND old_flags NOT LIKE '%external%' ".
+ 'AND LOWER(CONVERT(LEFT(old_text,22) USING latin1)) = \'o:15:"historyblobstub"\'',
+ $fname );
while ( $row = $dbr->fetchObject( $res ) ) {
resolveStub( $row->old_id, $row->old_text, $row->old_flags );
}
@@ -69,7 +67,7 @@ function resolveStub( $id, $stubText, $flags ) {
# Get the (maybe) external row
$externalRow = $dbr->selectRow( 'text', array( 'old_text' ),
- array( 'old_id' => $stub->mOldId, "old_flags LIKE '%external%'" ),
+ array( 'old_id' => $stub->mOldId, 'old_flags' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ) ),
$fname
);
diff --git a/maintenance/storage/storageTypeStats.php b/maintenance/storage/storageTypeStats.php
new file mode 100644
index 00000000..85858620
--- /dev/null
+++ b/maintenance/storage/storageTypeStats.php
@@ -0,0 +1,98 @@
+<?php
+
+require_once( dirname(__FILE__).'/../Maintenance.php' );
+
+class StorageTypeStats extends Maintenance {
+ function execute() {
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $endId = $dbr->selectField( 'text', 'MAX(old_id)', false, __METHOD__ );
+ if ( !$endId ) {
+ echo "No text rows!\n";
+ exit( 1 );
+ }
+
+ $rangeStart = 0;
+ $binSize = intval( pow( 10, floor( log10( $endId ) ) - 3 ) );
+ if ( $binSize < 100 ) {
+ $binSize = 100;
+ }
+ echo "Using bin size of $binSize\n";
+
+ $stats = array();
+
+ $classSql = <<<SQL
+ IF(old_flags LIKE '%external%',
+ IF(old_text REGEXP '^DB://[[:alnum:]]+/[0-9]+/[0-9a-f]{32}$',
+ 'CGZ pointer',
+ IF(old_text REGEXP '^DB://[[:alnum:]]+/[0-9]+/[0-9]{1,6}$',
+ 'DHB pointer',
+ IF(old_text REGEXP '^DB://[[:alnum:]]+/[0-9]+$',
+ 'simple pointer',
+ 'UNKNOWN pointer'
+ )
+ )
+ ),
+ IF(old_flags LIKE '%object%',
+ TRIM('"' FROM SUBSTRING_INDEX(SUBSTRING_INDEX(old_text, ':', 3), ':', -1)),
+ '[none]'
+ )
+ )
+SQL;
+
+ for ( $rangeStart = 0; $rangeStart < $endId; $rangeStart += $binSize ) {
+ if ( $rangeStart / $binSize % 10 == 0 ) {
+ echo "$rangeStart\r";
+ }
+ $res = $dbr->select(
+ 'text',
+ array(
+ 'old_flags',
+ "$classSql AS class",
+ 'COUNT(*) as count',
+ ),
+ array(
+ 'old_id >= ' . intval( $rangeStart ),
+ 'old_id < ' . intval( $rangeStart + $binSize )
+ ),
+ __METHOD__,
+ array( 'GROUP BY' => 'old_flags, class' )
+ );
+
+ foreach ( $res as $row ) {
+ $flags = $row->old_flags;
+ if ( $flags === '' ) {
+ $flags = '[none]';
+ }
+ $class = $row->class;
+ $count = $row->count;
+ if ( !isset( $stats[$flags][$class] ) ) {
+ $stats[$flags][$class] = array(
+ 'count' => 0,
+ 'first' => $rangeStart,
+ 'last' => 0
+ );
+ }
+ $entry =& $stats[$flags][$class];
+ $entry['count'] += $count;
+ $entry['last'] = max( $entry['last'], $rangeStart + $binSize );
+ unset( $entry );
+ }
+ }
+ echo "\n\n";
+
+ $format = "%-29s %-39s %-19s %-29s\n";
+ printf( $format, "Flags", "Class", "Count", "old_id range" );
+ echo str_repeat( '-', 120 ) . "\n";
+ foreach ( $stats as $flags => $flagStats ) {
+ foreach ( $flagStats as $class => $entry ) {
+ printf( $format, $flags, $class, $entry['count'],
+ sprintf( "%-13d - %-13d", $entry['first'], $entry['last'] ) );
+ }
+ }
+ }
+}
+
+$maintClass = 'StorageTypeStats';
+require_once( DO_MAINTENANCE );
+
diff --git a/maintenance/storage/trackBlobs.php b/maintenance/storage/trackBlobs.php
index 0f25fb94..63327d53 100644
--- a/maintenance/storage/trackBlobs.php
+++ b/maintenance/storage/trackBlobs.php
@@ -12,6 +12,7 @@ if ( count( $args ) < 1 ) {
}
$tracker = new TrackBlobs( $args );
$tracker->run();
+echo "All done.\n";
class TrackBlobs {
var $clusters, $textClause;
@@ -59,7 +60,7 @@ class TrackBlobs {
if ( $this->textClause != '' ) {
$this->textClause .= ' OR ';
}
- $this->textClause .= 'old_text LIKE ' . $dbr->addQuotes( $dbr->escapeLike( "DB://$cluster/" ) . '%' );
+ $this->textClause .= 'old_text' . $dbr->buildLike( "DB://$cluster/", $dbr->anyString() );
}
}
return $this->textClause;
@@ -72,7 +73,7 @@ class TrackBlobs {
return array(
'cluster' => $m[1],
'id' => intval( $m[2] ),
- 'hash' => isset( $m[3] ) ? $m[2] : null
+ 'hash' => isset( $m[3] ) ? $m[3] : null
);
}
@@ -98,7 +99,7 @@ class TrackBlobs {
'rev_id > ' . $dbr->addQuotes( $startId ),
'rev_text_id=old_id',
$textClause,
- "old_flags LIKE '%external%'",
+ 'old_flags ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ),
),
__METHOD__,
array(
@@ -174,7 +175,7 @@ class TrackBlobs {
array(
'old_id>' . $dbr->addQuotes( $startId ),
$textClause,
- "old_flags LIKE '%external%'",
+ 'old_flags ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ),
'bt_text_id IS NULL'
),
__METHOD__,
@@ -263,6 +264,10 @@ class TrackBlobs {
if ( is_null( $table ) ) {
$table = 'blobs';
}
+ if ( !$extDB->tableExists( $table ) ) {
+ echo "No blobs table on cluster $cluster\n";
+ continue;
+ }
$startId = 0;
$batchesDone = 0;
$actualBlobs = gmp_init( 0 );
@@ -300,6 +305,7 @@ class TrackBlobs {
// Traverse the orphan list
$insertBatch = array();
$id = 0;
+ $numOrphans = 0;
while ( true ) {
$id = gmp_scan1( $orphans, $id );
if ( $id == -1 ) {
@@ -309,12 +315,18 @@ class TrackBlobs {
'bo_cluster' => $cluster,
'bo_blob_id' => $id
);
+ if ( count( $insertBatch ) > $this->batchSize ) {
+ $dbw->insert( 'blob_orphans', $insertBatch, __METHOD__ );
+ $insertBatch = array();
+ }
+
++$id;
+ ++$numOrphans;
}
-
- // Insert the batch
- echo "Found " . count( $insertBatch ) . " orphan(s) in $cluster\n";
- $dbw->insert( 'blob_orphans', $insertBatch, __METHOD__ );
+ if ( $insertBatch ) {
+ $dbw->insert( 'blob_orphans', $insertBatch, __METHOD__ );
+ }
+ echo "Found $numOrphans orphan(s) in $cluster\n";
}
}
}
diff --git a/maintenance/tables.sql b/maintenance/tables.sql
index 3b1e8eb7..0809f4b6 100644
--- a/maintenance/tables.sql
+++ b/maintenance/tables.sql
@@ -86,6 +86,8 @@ CREATE TABLE /*_*/user (
-- Newline-separated list of name=value defining the user
-- preferences
+ -- Now obsolete in favour of user_properties table;
+ -- old values will be migrated from here transparently.
user_options blob NOT NULL,
-- This is a timestamp which is updated when a user
@@ -160,8 +162,10 @@ CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+--
-- Stores notifications of user talk page changes, for the display
-- of the "you have new messages" box
+--
CREATE TABLE /*_*/user_newtalk (
-- Key to user.user_id
user_id int NOT NULL default 0,
@@ -179,6 +183,29 @@ CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
--
+-- User preferences and perhaps other fun stuff. :)
+-- Replaces the old user.user_options blob, with a couple nice properties:
+--
+-- 1) We only store non-default settings, so changes to the defauls
+-- are now reflected for everybody, not just new accounts.
+-- 2) We can more easily do bulk lookups, statistics, or modifications of
+-- saved options since it's a sane table structure.
+--
+CREATE TABLE /*_*/user_properties (
+ -- Foreign key to user.user_id
+ up_user int NOT NULL,
+
+ -- Name of the option being saved. This is indexed for bulk lookup.
+ up_property varbinary(32) NOT NULL,
+
+ -- Property value as a string.
+ up_value blob
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE 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.
--
@@ -548,6 +575,20 @@ CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
+--
+-- Track external user accounts, if ExternalAuth is used
+--
+CREATE TABLE /*_*/external_user (
+ -- Foreign key to user_id
+ eu_local_id int unsigned NOT NULL PRIMARY KEY,
+
+ -- Some opaque identifier provided by the external database
+ eu_external_id varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id);
+
+
--
-- Track interlanguage links
--
@@ -723,7 +764,7 @@ CREATE TABLE /*_*/image (
-- 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 varbinary(32) NOT NULL default "unknown",
+ img_minor_mime varbinary(100) NOT NULL default "unknown",
-- Description field as entered by the uploader.
-- This is displayed in image upload history and logs.
@@ -775,7 +816,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_minor_mime varbinary(32) 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 ''
) /*$wgDBTableOptions*/;
@@ -825,7 +866,7 @@ CREATE TABLE /*_*/filearchive (
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_minor_mime varbinary(32) default "unknown",
+ fa_minor_mime varbinary(100) default "unknown",
fa_description tinyblob,
fa_user int unsigned default 0,
fa_user_text varchar(255) binary,
@@ -1054,7 +1095,7 @@ CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
CREATE TABLE /*_*/transcache (
tc_url varbinary(255) NOT NULL,
tc_contents text,
- tc_time int NOT NULL
+ tc_time binary(14) NOT NULL
) /*$wgDBTableOptions*/;
CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
@@ -1067,8 +1108,8 @@ CREATE TABLE /*_*/logging (
-- 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 varbinary(10) NOT NULL default '',
- log_action varbinary(10) NOT NULL default '',
+ log_type varbinary(32) NOT NULL default '',
+ log_action varbinary(32) NOT NULL default '',
-- Timestamp. Duh.
log_timestamp binary(14) NOT NULL default '19700101000000',
@@ -1076,10 +1117,14 @@ CREATE TABLE /*_*/logging (
-- The user who performed this action; key to user_id
log_user int unsigned NOT NULL default 0,
+ -- Name of the user who performed this action
+ log_user_text varchar(255) binary 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 varchar(255) binary NOT NULL default '',
+ log_page int unsigned NULL,
-- Freeform text. Interpreted as edit history comments.
log_comment varchar(255) NOT NULL default '',
@@ -1095,6 +1140,20 @@ 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 TABLE /*_*/log_search (
+ -- The type of ID (rev ID, log ID, rev timestamp, username)
+ ls_field varbinary(32) NOT NULL,
+ -- The value of the ID
+ ls_value varchar(255) NOT NULL,
+ -- Key to log_id
+ ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+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);
CREATE TABLE /*_*/trackbacks (
@@ -1126,7 +1185,7 @@ CREATE TABLE /*_*/job (
job_params blob NOT NULL
) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title);
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
-- Details of updates to cached special pages
@@ -1152,7 +1211,9 @@ CREATE TABLE /*_*/redirect (
-- and deletions may refer to different page records as time
-- goes by.
rd_namespace int NOT NULL default 0,
- rd_title varchar(255) binary NOT NULL default ''
+ rd_title varchar(255) binary NOT NULL default '',
+ rd_interwiki varchar(32) default NULL,
+ rd_fragment varchar(255) binary default NULL
) /*$wgDBTableOptions*/;
CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
@@ -1237,10 +1298,15 @@ CREATE TABLE /*_*/updatelog (
-- A table to track tags for revisions, logs and recent changes.
CREATE TABLE /*_*/change_tag (
+ -- RCID for the change
ct_rc_id int NULL,
+ -- LOGID for the change
ct_log_id int NULL,
+ -- REVID for the change
ct_rev_id int NULL,
+ -- Tag applied
ct_tag varchar(255) NOT NULL,
+ -- Parameters for the tag, presently unused
ct_params blob NULL
) /*$wgDBTableOptions*/;
@@ -1251,11 +1317,16 @@ CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag
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+
+-- 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,
+ -- LOGID for the change
ts_log_id int NULL,
+ -- REVID for the change
ts_rev_id int NULL,
+ -- Comma-separated list of tags
ts_tags blob NOT NULL
) /*$wgDBTableOptions*/;
@@ -1268,5 +1339,15 @@ CREATE TABLE /*_*/valid_tag (
vt_tag varchar(255) NOT NULL PRIMARY KEY
) /*$wgDBTableOptions*/;
+-- Table for storing localisation data
+CREATE TABLE /*_*/l10n_cache (
+ -- Language code
+ lc_lang varbinary(32) NOT NULL,
+ -- Cache key
+ lc_key varchar(255) NOT NULL,
+ -- Value
+ lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
-- vim: sw=2 sts=2 et
diff --git a/maintenance/testRunner.ora.sql b/maintenance/testRunner.ora.sql
new file mode 100644
index 00000000..6e3e1b7c
--- /dev/null
+++ b/maintenance/testRunner.ora.sql
@@ -0,0 +1,37 @@
+--
+-- Optional tables for parserTests recording mode
+-- With --record option, success data will be saved to these tables,
+-- and comparisons of what's changed from the previous run will be
+-- displayed at the end of each run.
+--
+-- defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
+define mw_prefix='{$wgDBprefix}';
+
+DROP TABLE &mw_prefix.testitem CASCADE CONSTRAINTS;
+DROP TABLE &mw_prefix.testrun CASCADE CONSTRAINTS;
+
+CREATE SEQUENCE testrun_tr_id_seq;
+CREATE TABLE &mw_prefix.testrun (
+ tr_id NUMBER NOT NULL,
+ tr_date DATE,
+ tr_mw_version BLOB,
+ tr_php_version BLOB,
+ tr_db_version BLOB,
+ tr_uname BLOB,
+);
+ALTER TABLE &mw_prefix.testrun ADD CONSTRAINT &mw_prefix.testrun_pk PRIMARY KEY (tr_id);
+CREATE OR REPLACE TRIGGER &mw_prefix.testrun_bir
+BEFORE UPDATE FOR EACH ROW
+ON &mw_prefix.testrun
+BEGIN
+ SELECT testrun_tr_id_seq.NEXTVAL into :NEW.tr_id FROM dual;
+END;
+
+CREATE TABLE /*$wgDBprefix*/testitem (
+ ti_run NUMBER NOT NULL REFERENCES &mw_prefix.testrun (tr_id) ON DELETE CASCADE,
+ ti_name VARCHAR22(255),
+ ti_success NUMBER(1)
+);
+CREATE UNIQUE INDEX &mw_prefix.testitem_u01 ON &mw_prefix.testitem (ti_run, ti_name);
+CREATE UNIQUE INDEX &mw_prefix.testitem_u01 ON &mw_prefix.testitem (ti_run, ti_success);
+
diff --git a/maintenance/tests/.svnignore b/maintenance/tests/.svnignore
new file mode 100644
index 00000000..20cb61e9
--- /dev/null
+++ b/maintenance/tests/.svnignore
@@ -0,0 +1,6 @@
+LocalTestSettings.php
+*~
+bin
+.classpath
+.project
+project.index
diff --git a/maintenance/tests/ApiSetup.php b/maintenance/tests/ApiSetup.php
new file mode 100644
index 00000000..549d8aef
--- /dev/null
+++ b/maintenance/tests/ApiSetup.php
@@ -0,0 +1,39 @@
+<?php
+
+abstract class ApiSetup extends PHPUnit_Framework_TestCase {
+ protected static $userName;
+ protected static $passWord;
+ protected static $user;
+ protected static $apiUrl;
+
+ function setup() {
+ global $wgServerName, $wgServer, $wgContLang, $wgAuth, $wgScriptPath,
+ $wgScriptExtension, $wgMemc, $wgRequest;
+
+ self::$apiUrl = $wgServer.$wgScriptPath."/api".$wgScriptExtension;
+
+ $wgMemc = new FakeMemCachedClient;
+ $wgContLang = Language::factory( 'en' );
+ $wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' );
+ $wgRequest = new FauxRequest(array());
+ self::setupUser();
+ }
+
+ static function setupUser() {
+ if ( self::$user == NULL ) {
+ self::$userName = "Useruser";
+ self::$passWord = User::randomPassword();
+
+ self::$user = User::newFromName(self::$userName);
+ if ( !self::$user->getID() ) {
+ self::$user = User::createNew(self::$userName, array(
+ "password" => self::$passWord,
+ "email" => "test@example.com",
+ "real_name" => "Test User"));
+ } else {
+ self::$user->setPassword(self::$passWord);
+ }
+ self::$user->saveSettings();
+ }
+ }
+}
diff --git a/maintenance/tests/ApiTest.php b/maintenance/tests/ApiTest.php
new file mode 100644
index 00000000..d098b1a2
--- /dev/null
+++ b/maintenance/tests/ApiTest.php
@@ -0,0 +1,164 @@
+<?php
+
+require_once( "ApiSetup.php" );
+
+class MockApi extends ApiBase {
+ public function execute() {}
+ public function getVersion() {}
+
+ public function __construct() {}
+
+ public function getAllowedParams() {
+ $params = array(
+ 'filename' => null,
+ 'enablechunks' => false,
+ 'sessionkey' => null,
+ );
+ }
+
+
+}
+
+
+class ApiTest extends ApiSetup {
+
+ function setup() {
+ parent::setup();
+ }
+
+ function testRequireOnlyOneParameterDefault() {
+ $mock = new MockApi();
+
+ $this->assertEquals(
+ null, $mock->requireOnlyOneParameter(array("filename" => "foo.txt",
+ "enablechunks" => false), "filename", "enablechunks"));
+ }
+
+ /**
+ * @expectedException UsageException
+ */
+ function testRequireOnlyOneParameterZero() {
+ $mock = new MockApi();
+
+ $this->assertEquals(
+ null, $mock->requireOnlyOneParameter(array("filename" => "foo.txt",
+ "enablechunks" => 0), "filename", "enablechunks"));
+ }
+
+ /**
+ * @expectedException UsageException
+ */
+ function testRequireOnlyOneParameterTrue() {
+ $mock = new MockApi();
+
+ $this->assertEquals(
+ null, $mock->requireOnlyOneParameter(array("filename" => "foo.txt",
+ "enablechunks" => true), "filename", "enablechunks"));
+ }
+
+ function testApi() {
+ if(!isset($wgServername) || !isset($wgServer)) {
+ $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '.
+ 'be set in LocalSettings.php');
+ }
+ /* Haven't thought about test ordering yet -- but this depends on HttpTest.php */
+ $resp = Http::get( self::$apiUrl . "?format=xml" );
+
+ libxml_use_internal_errors( true );
+ $sxe = simplexml_load_string( $resp );
+ $this->assertNotType( "bool", $sxe );
+ $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) );
+ }
+
+ function testApiLoginNoName() {
+ if(!isset($wgServername) || !isset($wgServer)) {
+ $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '.
+ 'be set in LocalSettings.php');
+ }
+ $resp = Http::post( self::$apiUrl . "?action=login&format=xml",
+ array( "postData" => array(
+ "lgname" => "",
+ "lgpassword" => self::$passWord ) ) );
+ libxml_use_internal_errors( true );
+ $sxe = simplexml_load_string( $resp );
+ $this->assertNotType( "bool", $sxe );
+ $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) );
+ $a = $sxe->login[0]->attributes()->result;
+ $this->assertEquals( ' result="NoName"', $a->asXML() );
+ }
+
+ function testApiLoginBadPass() {
+ if(!isset($wgServername) || !isset($wgServer)) {
+ $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '.
+ 'be set in LocalSettings.php');
+ }
+ $resp = Http::post( self::$apiUrl . "?action=login&format=xml",
+ array( "postData" => array(
+ "lgname" => self::$userName,
+ "lgpassword" => "bad" ) ) );
+ libxml_use_internal_errors( true );
+ $sxe = simplexml_load_string( $resp );
+ $this->assertNotType( "bool", $sxe );
+ $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) );
+ $a = $sxe->login[0]->attributes()->result;
+ $this->assertEquals( ' result="WrongPass"', $a->asXML() );
+ }
+
+ function testApiLoginGoodPass() {
+ if(!isset($wgServername) || !isset($wgServer)) {
+ $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '.
+ 'be set in LocalSettings.php');
+ }
+ $resp = Http::post( self::$apiUrl . "?action=login&format=xml",
+ array( "postData" => array(
+ "lgname" => self::$userName,
+ "lgpassword" => self::$passWord ) ) );
+ libxml_use_internal_errors( true );
+ $sxe = simplexml_load_string( $resp );
+ $this->assertNotType( "bool", $sxe );
+ $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) );
+ $a = $sxe->login[0]->attributes()->result;
+ $this->assertEquals( ' result="Success"', $a->asXML() );
+ }
+
+ function testApiGotCookie() {
+ global $wgScriptPath, $wgServerName;
+
+ if(!isset($wgServername) || !isset($wgServer)) {
+ $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '.
+ 'be set in LocalSettings.php');
+ }
+ $req = HttpRequest::factory( self::$apiUrl . "?action=login&format=xml",
+ array( "method" => "POST",
+ "postData" => array( "lgname" => self::$userName,
+ "lgpassword" => self::$passWord ) ) );
+ $req->execute();
+ $cj = $req->getCookieJar();
+ $this->assertRegexp( '/_session=[^;]*; .*UserID=[0-9]*; .*UserName=' . self::$userName . '; .*Token=/',
+ $cj->serializeToHttpRequest( $wgScriptPath, $wgServerName ) );
+
+
+ return $cj;
+ }
+
+ /**
+ * @depends testApiGotCookie
+ */
+ function testApiListPages(CookieJar $cj) {
+ $this->markTestIncomplete("Not done with this yet");
+
+ if($wgServerName == "localhost" || $wgServer == "http://localhost") {
+ $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '.
+ 'be set in LocalSettings.php');
+ }
+ $req = HttpRequest::factory( self::$apiUrl . "?action=query&format=xml&prop=revisions&".
+ "titles=Main%20Page&rvprop=timestamp|user|comment|content" );
+ $req->setCookieJar($cj);
+ $req->execute();
+ libxml_use_internal_errors( true );
+ $sxe = simplexml_load_string( $req->getContent() );
+ $this->assertNotType( "bool", $sxe );
+ $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) );
+ $a = $sxe->query[0]->pages[0]->page[0]->attributes();
+ }
+}
diff --git a/maintenance/tests/CdbTest.php b/maintenance/tests/CdbTest.php
new file mode 100644
index 00000000..444229e7
--- /dev/null
+++ b/maintenance/tests/CdbTest.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * Test the CDB reader/writer
+ */
+
+class CdbTest extends PHPUnit_Framework_TestCase {
+
+ public function setup() {
+ if ( !CdbReader::haveExtension() ) {
+ $this->markTestIncomplete( 'This test requires native CDB support to be present.' );
+ }
+ }
+
+ public function testCdb() {
+ $w1 = new CdbWriter_PHP( 'php.cdb' );
+ $w2 = new CdbWriter_DBA( 'dba.cdb' );
+
+ $data = array();
+ for ( $i = 0; $i < 1000; $i++ ) {
+ $key = $this->randomString();
+ $value = $this->randomString();
+ $w1->set( $key, $value );
+ $w2->set( $key, $value );
+
+ if ( !isset( $data[$key] ) ) {
+ $data[$key] = $value;
+ }
+ }
+
+ $w1->close();
+ $w2->close();
+
+ $this->assertEquals(
+ md5_file( 'dba.cdb' ),
+ md5_file( 'php.cdb' ),
+ 'same hash'
+ );
+
+ $r1 = new CdbReader_PHP( 'php.cdb' );
+ $r2 = new CdbReader_DBA( 'dba.cdb' );
+
+ foreach ( $data as $key => $value ) {
+ if ( $key === '' ) {
+ // Known bug
+ continue;
+ }
+ $v1 = $r1->get( $key );
+ $v2 = $r2->get( $key );
+
+ $v1 = $v1 === false ? '(not found)' : $v1;
+ $v2 = $v2 === false ? '(not found)' : $v2;
+
+ #cdbAssert( 'Mismatch', $key, $v1, $v2 );
+ $this->cdbAssert( "PHP error", $key, $v1, $value );
+ $this->cdbAssert( "DBA error", $key, $v2, $value );
+ }
+
+ unlink( 'dba.cdb' );
+ unlink( 'php.cdb' );
+ }
+
+ private function randomString() {
+ $len = mt_rand( 0, 10 );
+ $s = '';
+ for ( $j = 0; $j < $len; $j++ ) {
+ $s .= chr( mt_rand( 0, 255 ) );
+ }
+ return $s;
+ }
+
+ private function cdbAssert( $msg, $key, $v1, $v2 ) {
+ $this->assertEquals(
+ $v2,
+ $v1,
+ $msg . ', k=' . bin2hex( $key )
+ );
+ }
+}
diff --git a/maintenance/tests/DatabaseSqliteTest.php b/maintenance/tests/DatabaseSqliteTest.php
new file mode 100644
index 00000000..011ef798
--- /dev/null
+++ b/maintenance/tests/DatabaseSqliteTest.php
@@ -0,0 +1,57 @@
+<?php
+
+class MockDatabaseSqlite extends DatabaseSqliteStandalone {
+ var $lastQuery;
+
+ function __construct( ) {
+ parent::__construct( '' );
+ }
+
+ function query( $sql, $fname = '', $tempIgnore = false ) {
+ $this->lastQuery = $sql;
+ return true;
+ }
+
+ function replaceVars( $s ) {
+ return parent::replaceVars( $s );
+ }
+}
+
+class DatabaseSqliteTest extends PHPUnit_Framework_TestCase {
+ var $db;
+
+ function setup() {
+ if ( !extension_loaded( 'pdo_sqlite' ) ) {
+ $this->markTestIncomplete( 'No SQLite support detected' );
+ }
+ $this->db = new MockDatabaseSqlite();
+ }
+
+ function replaceVars( $sql ) {
+ // normalize spacing to hide implementation details
+ return preg_replace( '/\s+/', ' ', $this->db->replaceVars( $sql ) );
+ }
+
+ function testReplaceVars() {
+ $this->assertEquals( 'foo', $this->replaceVars( 'foo' ), "Don't break anything accidentally" );
+
+ $this->assertEquals( "CREATE TABLE /**/foo (foo_key INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
+ . "foo_name TEXT NOT NULL DEFAULT '', foo_int INTEGER, foo_int2 INTEGER );",
+ $this->replaceVars( "CREATE TABLE /**/foo (foo_key int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ foo_name varchar(255) binary NOT NULL DEFAULT '', foo_int tinyint( 8 ), foo_int2 int(16) ) ENGINE=MyISAM;" )
+ );
+
+ $this->assertEquals( "CREATE TABLE foo ( foo_binary1 BLOB, foo_binary2 BLOB );",
+ $this->replaceVars( "CREATE TABLE foo ( foo_binary1 binary(16), foo_binary2 varbinary(32) );" )
+ );
+
+ $this->assertEquals( "CREATE TABLE text ( text_foo TEXT );",
+ $this->replaceVars( "CREATE TABLE text ( text_foo tinytext );" ),
+ 'Table name changed'
+ );
+
+ $this->assertEquals( "ALTER TABLE foo ADD COLUMN foo_bar INTEGER DEFAULT 42",
+ $this->replaceVars( "ALTER TABLE foo\nADD COLUMN foo_bar int(10) unsigned DEFAULT 42")
+ );
+ }
+} \ No newline at end of file
diff --git a/maintenance/tests/DatabaseTest.php b/maintenance/tests/DatabaseTest.php
new file mode 100644
index 00000000..aa50de2e
--- /dev/null
+++ b/maintenance/tests/DatabaseTest.php
@@ -0,0 +1,92 @@
+<?php
+
+class DatabaseTest extends PHPUnit_Framework_TestCase {
+ var $db;
+
+ function setUp() {
+ $this->db = wfGetDB( DB_SLAVE );
+ }
+
+ function testAddQuotesNull() {
+ $check = "NULL";
+ if ( $this->db->getType() === 'sqlite' ) {
+ $check = "''";
+ }
+ $this->assertEquals( $check, $this->db->addQuotes( null ) );
+ }
+
+ function testAddQuotesInt() {
+ # returning just "1234" should be ok too, though...
+ # maybe
+ $this->assertEquals(
+ "'1234'",
+ $this->db->addQuotes( 1234 ) );
+ }
+
+ function testAddQuotesFloat() {
+ # returning just "1234.5678" would be ok too, though
+ $this->assertEquals(
+ "'1234.5678'",
+ $this->db->addQuotes( 1234.5678 ) );
+ }
+
+ function testAddQuotesString() {
+ $this->assertEquals(
+ "'string'",
+ $this->db->addQuotes( 'string' ) );
+ }
+
+ function testAddQuotesStringQuote() {
+ $check = "'string''s cause trouble'";
+ if ( $this->db->getType() === 'mysql' ) {
+ $check = "'string\'s cause trouble'";
+ }
+ $this->assertEquals(
+ $check,
+ $this->db->addQuotes( "string's cause trouble" ) );
+ }
+
+ function testFillPreparedEmpty() {
+ $sql = $this->db->fillPrepared(
+ 'SELECT * FROM interwiki', array() );
+ $this->assertEquals(
+ "SELECT * FROM interwiki",
+ $sql);
+ }
+
+ function testFillPreparedQuestion() {
+ $sql = $this->db->fillPrepared(
+ 'SELECT * FROM cur WHERE cur_namespace=? AND cur_title=?',
+ array( 4, "Snicker's_paradox" ) );
+
+ $check = "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker''s_paradox'";
+ if ( $this->db->getType() === 'mysql' ) {
+ $check = "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker\'s_paradox'";
+ }
+ $this->assertEquals( $check, $sql );
+ }
+
+ function testFillPreparedBang() {
+ $sql = $this->db->fillPrepared(
+ 'SELECT user_id FROM ! WHERE user_name=?',
+ array( '"user"', "Slash's Dot" ) );
+
+ $check = "SELECT user_id FROM \"user\" WHERE user_name='Slash''s Dot'";
+ if ( $this->db->getType() === 'mysql' ) {
+ $check = "SELECT user_id FROM \"user\" WHERE user_name='Slash\'s Dot'";
+ }
+ $this->assertEquals( $check, $sql );
+ }
+
+ function testFillPreparedRaw() {
+ $sql = $this->db->fillPrepared(
+ "SELECT * FROM cur WHERE cur_title='This_\\&_that,_WTF\\?\\!'",
+ array( '"user"', "Slash's Dot" ) );
+ $this->assertEquals(
+ "SELECT * FROM cur WHERE cur_title='This_&_that,_WTF?!'",
+ $sql);
+ }
+
+}
+
+
diff --git a/maintenance/tests/GlobalTest.php b/maintenance/tests/GlobalTest.php
new file mode 100644
index 00000000..ec694241
--- /dev/null
+++ b/maintenance/tests/GlobalTest.php
@@ -0,0 +1,212 @@
+<?php
+
+class GlobalTest extends PHPUnit_Framework_TestCase {
+ function setUp() {
+ global $wgReadOnlyFile;
+ $this->originals['wgReadOnlyFile'] = $wgReadOnlyFile;
+ $wgReadOnlyFile = tempnam(wfTempDir(), "mwtest_readonly");
+ unlink( $wgReadOnlyFile );
+ }
+
+ function tearDown() {
+ global $wgReadOnlyFile;
+ if( file_exists( $wgReadOnlyFile ) ) {
+ unlink( $wgReadOnlyFile );
+ }
+ $wgReadOnlyFile = $this->originals['wgReadOnlyFile'];
+ }
+
+ function testRandom() {
+ # This could hypothetically fail, but it shouldn't ;)
+ $this->assertFalse(
+ wfRandom() == wfRandom() );
+ }
+
+ function testUrlencode() {
+ $this->assertEquals(
+ "%E7%89%B9%E5%88%A5:Contributions/Foobar",
+ wfUrlencode( "\xE7\x89\xB9\xE5\x88\xA5:Contributions/Foobar" ) );
+ }
+
+ function testReadOnlyEmpty() {
+ global $wgReadOnly;
+ $wgReadOnly = null;
+
+ $this->assertFalse( wfReadOnly() );
+ $this->assertFalse( wfReadOnly() );
+ }
+
+ function testReadOnlySet() {
+ global $wgReadOnly, $wgReadOnlyFile;
+
+ $f = fopen( $wgReadOnlyFile, "wt" );
+ fwrite( $f, 'Message' );
+ fclose( $f );
+ $wgReadOnly = null;
+
+ $this->assertTrue( wfReadOnly() );
+ $this->assertTrue( wfReadOnly() );
+
+ unlink( $wgReadOnlyFile );
+ $wgReadOnly = null;
+
+ $this->assertFalse( wfReadOnly() );
+ $this->assertFalse( wfReadOnly() );
+ }
+
+ function testQuotedPrintable() {
+ $this->assertEquals(
+ "=?UTF-8?Q?=C4=88u=20legebla=3F?=",
+ wfQuotedPrintable( "\xc4\x88u legebla?", "UTF-8" ) );
+ }
+
+ function testTime() {
+ $start = wfTime();
+ $this->assertType( 'float', $start );
+ $end = wfTime();
+ $this->assertTrue( $end > $start, "Time is running backwards!" );
+ }
+
+ function testArrayToCGI() {
+ $this->assertEquals(
+ "baz=AT%26T&foo=bar",
+ wfArrayToCGI(
+ array( 'baz' => 'AT&T', 'ignore' => '' ),
+ array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) );
+ }
+
+ function testMimeTypeMatch() {
+ $this->assertEquals(
+ 'text/html',
+ mimeTypeMatch( 'text/html',
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.7,
+ 'text/plain' => 0.3 ) ) );
+ $this->assertEquals(
+ 'text/*',
+ mimeTypeMatch( 'text/html',
+ array( 'image/*' => 1.0,
+ 'text/*' => 0.5 ) ) );
+ $this->assertEquals(
+ '*/*',
+ mimeTypeMatch( 'text/html',
+ array( '*/*' => 1.0 ) ) );
+ $this->assertNull(
+ mimeTypeMatch( 'text/html',
+ array( 'image/png' => 1.0,
+ 'image/svg+xml' => 0.5 ) ) );
+ }
+
+ function testNegotiateType() {
+ $this->assertEquals(
+ 'text/html',
+ wfNegotiateType(
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.7,
+ 'text/plain' => 0.5,
+ 'text/*' => 0.2 ),
+ array( 'text/html' => 1.0 ) ) );
+ $this->assertEquals(
+ 'application/xhtml+xml',
+ wfNegotiateType(
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.7,
+ 'text/plain' => 0.5,
+ 'text/*' => 0.2 ),
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.5 ) ) );
+ $this->assertEquals(
+ 'text/html',
+ wfNegotiateType(
+ array( 'text/html' => 1.0,
+ 'text/plain' => 0.5,
+ 'text/*' => 0.5,
+ 'application/xhtml+xml' => 0.2 ),
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.5 ) ) );
+ $this->assertEquals(
+ 'text/html',
+ wfNegotiateType(
+ array( 'text/*' => 1.0,
+ 'image/*' => 0.7,
+ '*/*' => 0.3 ),
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.5 ) ) );
+ $this->assertNull(
+ wfNegotiateType(
+ array( 'text/*' => 1.0 ),
+ array( 'application/xhtml+xml' => 1.0 ) ) );
+ }
+
+ function testTimestamp() {
+ $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
+ $this->assertEquals(
+ '20010115123456',
+ wfTimestamp( TS_MW, $t ),
+ 'TS_UNIX to TS_MW' );
+ $this->assertEquals(
+ 979562096,
+ wfTimestamp( TS_UNIX, $t ),
+ 'TS_UNIX to TS_UNIX' );
+ $this->assertEquals(
+ '2001-01-15 12:34:56',
+ wfTimestamp( TS_DB, $t ),
+ 'TS_UNIX to TS_DB' );
+
+ $this->assertEquals(
+ '20010115123456',
+ wfTimestamp( TS_MW, '20010115123456' ),
+ 'TS_MW to TS_MW' );
+ $this->assertEquals(
+ 979562096,
+ wfTimestamp( TS_UNIX, '20010115123456' ),
+ 'TS_MW to TS_UNIX' );
+ $this->assertEquals(
+ '2001-01-15 12:34:56',
+ wfTimestamp( TS_DB, '20010115123456' ),
+ 'TS_MW to TS_DB' );
+
+ $this->assertEquals(
+ '20010115123456',
+ wfTimestamp( TS_MW, '2001-01-15 12:34:56' ),
+ 'TS_DB to TS_MW' );
+ $this->assertEquals(
+ 979562096,
+ wfTimestamp( TS_UNIX, '2001-01-15 12:34:56' ),
+ 'TS_DB to TS_UNIX' );
+ $this->assertEquals(
+ '2001-01-15 12:34:56',
+ wfTimestamp( TS_DB, '2001-01-15 12:34:56' ),
+ 'TS_DB to TS_DB' );
+ }
+
+ function testBasename() {
+ $sets = array(
+ '' => '',
+ '/' => '',
+ '\\' => '',
+ '//' => '',
+ '\\\\' => '',
+ 'a' => 'a',
+ 'aaaa' => 'aaaa',
+ '/a' => 'a',
+ '\\a' => 'a',
+ '/aaaa' => 'aaaa',
+ '\\aaaa' => 'aaaa',
+ '/aaaa/' => 'aaaa',
+ '\\aaaa\\' => 'aaaa',
+ '\\aaaa\\' => 'aaaa',
+ '/mnt/upload3/wikipedia/en/thumb/8/8b/Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg' => '93px-Zork_Grand_Inquisitor_box_cover.jpg',
+ 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE' => 'VIEWER.EXE',
+ 'Östergötland_coat_of_arms.png' => 'Östergötland_coat_of_arms.png',
+ );
+ foreach( $sets as $from => $to ) {
+ $this->assertEquals( $to, wfBaseName( $from ),
+ "wfBaseName('$from') => '$to'");
+ }
+ }
+
+ /* TODO: many more! */
+}
+
+
diff --git a/maintenance/tests/HttpTest.php b/maintenance/tests/HttpTest.php
new file mode 100644
index 00000000..83734910
--- /dev/null
+++ b/maintenance/tests/HttpTest.php
@@ -0,0 +1,567 @@
+<?php
+
+class MockCookie extends Cookie {
+ public function canServeDomain($arg) { return parent::canServeDomain($arg); }
+ public function canServePath($arg) { return parent::canServePath($arg); }
+ public function isUnExpired() { return parent::isUnExpired(); }
+}
+
+class HttpTest extends PhpUnit_Framework_TestCase {
+ static $content;
+ static $headers;
+ static $has_curl;
+ static $has_fopen;
+ static $has_proxy = false;
+ static $proxy = "http://hulk:8080/";
+ var $test_geturl = array(
+ "http://www.example.com/",
+ "http://pecl.php.net/feeds/pkg_apc.rss",
+ "http://toolserver.org/~jan/poll/dev/main.php?page=wiki_output&id=3",
+ "http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw",
+ "http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&format=php",
+ );
+ var $test_requesturl = array( "http://en.wikipedia.org/wiki/Special:Export/User:MarkAHershberger" );
+
+ var $test_posturl = array( "http://www.comp.leeds.ac.uk/cgi-bin/Perl/environment-example" => "review=test" );
+
+ function setup() {
+ putenv("http_proxy"); /* Remove any proxy env var, so curl doesn't get confused */
+ if ( is_array( self::$content ) ) {
+ return;
+ }
+ self::$has_curl = function_exists( 'curl_init' );
+ self::$has_fopen = wfIniGetBool( 'allow_url_fopen' );
+
+ if ( !file_exists("/usr/bin/curl") ) {
+ $this->markTestIncomplete("This test requires the curl binary at /usr/bin/curl. If you have curl, please file a bug on this test, or, better yet, provide a patch.");
+ }
+
+ $content = tempnam( wfTempDir(), "" );
+ $headers = tempnam( wfTempDir(), "" );
+ if ( !$content && !$headers ) {
+ die( "Couldn't create temp file!" );
+ }
+
+ // This probably isn't the best test for a proxy, but it works on my system!
+ system("curl -0 -o $content -s ".self::$proxy);
+ $out = file_get_contents( $content );
+ if( $out ) {
+ self::$has_proxy = true;
+ }
+
+ /* Maybe use wget instead of curl here ... just to use a different codebase? */
+ foreach ( $this->test_geturl as $u ) {
+ system( "curl -0 -s -D $headers '$u' -o $content" );
+ self::$content["GET $u"] = file_get_contents( $content );
+ self::$headers["GET $u"] = file_get_contents( $headers );
+ }
+ foreach ( $this->test_requesturl as $u ) {
+ system( "curl -0 -s -X POST -H 'Content-Length: 0' -D $headers '$u' -o $content" );
+ self::$content["POST $u"] = file_get_contents( $content );
+ self::$headers["POST $u"] = file_get_contents( $headers );
+ }
+ foreach ( $this->test_posturl as $u => $postData ) {
+ system( "curl -0 -s -X POST -d '$postData' -D $headers '$u' -o $content" );
+ self::$content["POST $u => $postData"] = file_get_contents( $content );
+ self::$headers["POST $u => $postData"] = file_get_contents( $headers );
+ }
+ unlink( $content );
+ unlink( $headers );
+ }
+
+
+ function testInstantiation() {
+ Http::$httpEngine = false;
+
+ $r = HttpRequest::factory("http://www.example.com/");
+ if ( self::$has_curl ) {
+ $this->assertThat($r, $this->isInstanceOf( 'CurlHttpRequest' ));
+ } else {
+ $this->assertThat($r, $this->isInstanceOf( 'PhpHttpRequest' ));
+ }
+ unset($r);
+
+ if( !self::$has_fopen ) {
+ $this->setExpectedException( 'MWException' );
+ }
+ Http::$httpEngine = 'php';
+ $r = HttpRequest::factory("http://www.example.com/");
+ $this->assertThat($r, $this->isInstanceOf( 'PhpHttpRequest' ));
+ unset($r);
+
+ if( !self::$has_curl ) {
+ $this->setExpectedException( 'MWException' );
+ }
+ Http::$httpEngine = 'curl';
+ $r = HttpRequest::factory("http://www.example.com/");
+ if( self::$has_curl ) {
+ $this->assertThat($r, $this->isInstanceOf( 'CurlHttpRequest' ));
+ }
+ }
+
+ function runHTTPFailureChecks() {
+ // Each of the following requests should result in a failure.
+
+ $timeout = 1;
+ $start_time = time();
+ $r = HTTP::get( "http://www.example.com:1/", $timeout);
+ $end_time = time();
+ $this->assertLessThan($timeout+2, $end_time - $start_time,
+ "Request took less than {$timeout}s via ".Http::$httpEngine);
+ $this->assertEquals($r, false, "false -- what we get on error from Http::get()");
+
+ $r = HTTP::get( "http://www.example.com/this-file-does-not-exist", $timeout);
+ $this->assertFalse($r, "False on 404s");
+
+
+ $r = HttpRequest::factory( "http://www.example.com/this-file-does-not-exist" );
+ $er = $r->execute();
+ if ( is_a($r, 'PhpHttpRequest') && version_compare( '5.2.10', phpversion(), '>' ) ) {
+ $this->assertRegexp("/HTTP request failed/", $er->getWikiText());
+ } else {
+ $this->assertRegexp("/404 Not Found/", $er->getWikiText());
+ }
+ }
+
+ function testFailureDefault() {
+ Http::$httpEngine = false;
+ self::runHTTPFailureChecks();
+ }
+
+ function testFailurePhp() {
+ if ( !self::$has_fopen ) {
+ $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
+ }
+
+ Http::$httpEngine = "php";
+ self::runHTTPFailureChecks();
+ }
+
+ function testFailureCurl() {
+ if ( !self::$has_curl ) {
+ $this->markTestIncomplete( "This test requires curl." );
+ }
+
+ Http::$httpEngine = "curl";
+ self::runHTTPFailureChecks();
+ }
+
+ /* ./phase3/includes/Import.php:1108: $data = Http::request( $method, $url ); */
+ /* ./includes/Import.php:1124: $link = Title::newFromText( "$interwiki:Special:Export/$page" ); */
+ /* ./includes/Import.php:1134: return ImportStreamSource::newFromURL( $url, "POST" ); */
+ function runHTTPRequests($proxy=null) {
+ $opt = array();
+
+ if($proxy) {
+ $opt['proxy'] = $proxy;
+ } elseif( $proxy === false ) {
+ $opt['noProxy'] = true;
+ }
+
+ /* no postData here because the only request I could find in code so far didn't have any */
+ foreach ( $this->test_requesturl as $u ) {
+ $r = Http::request( "POST", $u, $opt );
+ $this->assertEquals( self::$content["POST $u"], "$r", "POST $u with ".Http::$httpEngine );
+ }
+ }
+
+ function testRequestDefault() {
+ Http::$httpEngine = false;
+ self::runHTTPRequests();
+ }
+
+ function testRequestPhp() {
+ if ( !self::$has_fopen ) {
+ $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
+ }
+
+ Http::$httpEngine = "php";
+ self::runHTTPRequests();
+ }
+
+ function testRequestCurl() {
+ if ( !self::$has_curl ) {
+ $this->markTestIncomplete( "This test requires curl." );
+ }
+
+ Http::$httpEngine = "curl";
+ self::runHTTPRequests();
+ }
+
+ /* ./extensions/SpamBlacklist/SpamBlacklist_body.php:164: $httpText = Http::get( $fileName ); */
+ /* ./extensions/ApiSVGProxy/ApiSVGProxy.body.php:44: $contents = Http::get( $file->getFullUrl() ); */
+ /* ./extensions/BookInformation/drivers/IsbnDb.php:24: if( ( $xml = Http::get( $uri ) ) !== false ) { */
+ /* ./extensions/BookInformation/drivers/Amazon.php:23: if( ( $xml = Http::get( $uri ) ) !== false ) { */
+ /* ./extensions/TitleBlacklist/TitleBlacklist.list.php:217: $result = Http::get( $url ); */
+ /* ./extensions/TSPoll/TSPoll.php:68: $get_server = Http::get( 'http://toolserver.org/~jan/poll/dev/main.php?page=wiki_output&id='.$id ); */
+ /* ./extensions/TSPoll/TSPoll.php:70: $get_server = Http::get( 'http://toolserver.org/~jan/poll/main.php?page=wiki_output&id='.$id ); */
+ /* ./extensions/DoubleWiki/DoubleWiki.php:56: $translation = Http::get( $url.$sep.'action=render' ); */
+ /* ./extensions/ExternalPages/ExternalPages_body.php:177: $serializedText = Http::get( $this->mPageURL ); */
+ /* ./extensions/Translate/utils/TranslationHelpers.php:143: $suggestions = Http::get( $url, $timeout ); */
+ /* ./extensions/Translate/SpecialImportTranslations.php:169: $filedata = Http::get( $url ); ; */
+ /* ./extensions/Translate/TranslateEditAddons.php:338: $suggestions = Http::get( $url, $timeout ); */
+ /* ./extensions/SecurePoll/includes/user/Auth.php:283: $value = Http::get( $url, 20, $curlParams ); */
+ /* ./extensions/DumpHTML/dumpHTML.inc:778: $contents = Http::get( $url ); */
+ /* ./extensions/DumpHTML/dumpHTML.inc:1298: $contents = Http::get( $sourceUrl ); */
+ /* ./extensions/DumpHTML/dumpHTML.inc:1373: $contents = Http::get( $sourceUrl ); */
+ /* ./phase3/maintenance/rebuildInterwiki.inc:101: $intermap = Http::get( 'http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw', 30 ); */
+ /* ./phase3/maintenance/findhooks.php:98: $allhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' ); */
+ /* ./phase3/maintenance/findhooks.php:109: $oldhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Removed_hooks&cmlimit=500&format=php' ); */
+ /* ./phase3/maintenance/dumpInterwiki.inc:95: $intermap = Http::get( 'http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw', 30 ); */
+ /* ./phase3/includes/parser/Parser.php:3204: $text = Http::get($url); */
+ /* ./phase3/includes/filerepo/ForeignAPIRepo.php:131: $data = Http::get( $url ); */
+ /* ./phase3/includes/filerepo/ForeignAPIRepo.php:205: $thumb = Http::get( $foreignUrl ); */
+ /* ./phase3/includes/filerepo/File.php:1105: $res = Http::get( $renderUrl ); */
+ /* ./phase3/includes/GlobalFunctions.php:2760: * @deprecated Use Http::get() instead */
+ /* ./phase3/includes/GlobalFunctions.php:2764: return Http::get( $url ); */
+ /* ./phase3/includes/ExternalStoreHttp.php:18: $ret = Http::get( $url ); */
+ /* ./phase3/includes/Import.php:357: $data = Http::get( $src ); */
+ /* ./extensions/ExternalData/ED_Utils.php:291: return Http::get( $url, 'default', array(CURLOPT_SSL_VERIFYPEER => false) ); */
+ /* ./extensions/ExternalData/ED_Utils.php:293: return Http::get( $url ); */
+ /* ./extensions/ExternalData/ED_Utils.php:306: $page = Http::get( $url, 'default', array(CURLOPT_SSL_VERIFYPEER => false) ); */
+ /* ./extensions/ExternalData/ED_Utils.php:308: $page = Http::get( $url ); */
+ /* ./extensions/CodeReview/backend/Subversion.php:320: $blob = Http::get( $target, $this->mTimeout ); */
+ /* ./extensions/AmazonPlus/AmazonPlus.php:214: $this->response = Http::get( $urlstr ); */
+ /* ./extensions/StaticWiki/StaticWiki.php:24: $text = Http::get( $url ) ; */
+ /* ./extensions/StaticWiki/StaticWiki.php:64: $history = Http::get ( $wgStaticWikiExternalSite . "index.php?title=" . urlencode ( $url_title ) . "&action=history" ) ; */
+ /* ./extensions/Configure/scripts/findSettings.php:126: $cont = Http::get( "http://www.mediawiki.org/w/index.php?title={$page}&action=raw" ); */
+ /* ./extensions/TorBlock/TorBlock.class.php:148: $data = Http::get( $url ); */
+ /* ./extensions/HoneypotIntegration/HoneypotIntegration.class.php:60: $data = Http::get( $wgHoneypotURLSource, 'default', */
+ /* ./extensions/SemanticForms/includes/SF_Utils.inc:378: $page_contents = Http::get($url); */
+ /* ./extensions/LocalisationUpdate/LocalisationUpdate.class.php:172: $basefilecontents = Http::get( $basefile ); */
+ /* ./extensions/APC/SpecialAPC.php:245: $rss = Http::get( 'http://pecl.php.net/feeds/pkg_apc.rss' ); */
+ /* ./extensions/Interlanguage/Interlanguage.php:56: $a = Http::get( $url ); */
+ /* ./extensions/MWSearch/MWSearch_body.php:492: $data = Http::get( $searchUrl, $wgLuceneSearchTimeout, $httpOpts); */
+ function runHTTPGets($proxy=null) {
+ $opt = array();
+
+ if($proxy) {
+ $opt['proxy'] = $proxy;
+ } elseif( $proxy === false ) {
+ $opt['noProxy'] = true;
+ }
+
+ foreach ( $this->test_geturl as $u ) {
+ $r = Http::get( $u, 30, $opt ); /* timeout of 30s */
+ $this->assertEquals( self::$content["GET $u"], "$r", "Get $u with ".Http::$httpEngine );
+ }
+ }
+
+ function testGetDefault() {
+ Http::$httpEngine = false;
+ self::runHTTPGets();
+ }
+
+ function testGetPhp() {
+ if ( !self::$has_fopen ) {
+ $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
+ }
+
+ Http::$httpEngine = "php";
+ self::runHTTPGets();
+ }
+
+ function testGetCurl() {
+ if ( !self::$has_curl ) {
+ $this->markTestIncomplete( "This test requires curl." );
+ }
+
+ Http::$httpEngine = "curl";
+ self::runHTTPGets();
+ }
+
+ /* ./phase3/maintenance/parserTests.inc:1618: return Http::post( $url, array( 'postData' => wfArrayToCGI( $data ) ) ); */
+ function runHTTPPosts($proxy=null) {
+ $opt = array();
+
+ if($proxy) {
+ $opt['proxy'] = $proxy;
+ } elseif( $proxy === false ) {
+ $opt['noProxy'] = true;
+ }
+
+ foreach ( $this->test_posturl as $u => $postData ) {
+ $opt['postData'] = $postData;
+ $r = Http::post( $u, $opt );
+ $this->assertEquals( self::$content["POST $u => $postData"], "$r",
+ "POST $u (postData=$postData) with ".Http::$httpEngine );
+ }
+ }
+
+ function testPostDefault() {
+ Http::$httpEngine = false;
+ self::runHTTPPosts();
+ }
+
+ function testPostPhp() {
+ if ( !self::$has_fopen ) {
+ $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
+ }
+
+ Http::$httpEngine = "php";
+ self::runHTTPPosts();
+ }
+
+ function testPostCurl() {
+ if ( !self::$has_curl ) {
+ $this->markTestIncomplete( "This test requires curl." );
+ }
+
+ Http::$httpEngine = "curl";
+ self::runHTTPPosts();
+ }
+
+ function runProxyRequests() {
+ if(!self::$has_proxy) {
+ $this->markTestIncomplete( "This test requires a proxy." );
+ }
+ self::runHTTPGets(self::$proxy);
+ self::runHTTPPosts(self::$proxy);
+ self::runHTTPRequests(self::$proxy);
+
+ // Set false here to do noProxy
+ self::runHTTPGets(false);
+ self::runHTTPPosts(false);
+ self::runHTTPRequests(false);
+ }
+
+ function testProxyDefault() {
+ Http::$httpEngine = false;
+ self::runProxyRequests();
+ }
+
+ function testProxyPhp() {
+ if ( !self::$has_fopen ) {
+ $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
+ }
+
+ Http::$httpEngine = 'php';
+ self::runProxyRequests();
+ }
+
+ function testProxyCurl() {
+ if ( !self::$has_curl ) {
+ $this->markTestIncomplete( "This test requires curl." );
+ }
+
+ Http::$httpEngine = 'curl';
+ self::runProxyRequests();
+ }
+
+ function testIsLocalUrl() {
+ }
+
+ /* ./extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.body.php:559: $user_agent = Http::userAgent(); */
+ function testUserAgent() {
+ }
+
+ function testIsValidUrl() {
+ }
+
+ function testValidateCookieDomain() {
+ $this->assertFalse( Cookie::validateCookieDomain( "co.uk" ) );
+ $this->assertFalse( Cookie::validateCookieDomain( ".co.uk" ) );
+ $this->assertFalse( Cookie::validateCookieDomain( "gov.uk" ) );
+ $this->assertFalse( Cookie::validateCookieDomain( ".gov.uk" ) );
+ $this->assertTrue( Cookie::validateCookieDomain( "supermarket.uk" ) );
+ $this->assertFalse( Cookie::validateCookieDomain( "uk" ) );
+ $this->assertFalse( Cookie::validateCookieDomain( ".uk" ) );
+ $this->assertFalse( Cookie::validateCookieDomain( "127.0.0." ) );
+ $this->assertFalse( Cookie::validateCookieDomain( "127." ) );
+ $this->assertFalse( Cookie::validateCookieDomain( "127.0.0.1." ) );
+ $this->assertTrue( Cookie::validateCookieDomain( "127.0.0.1" ) );
+ $this->assertFalse( Cookie::validateCookieDomain( "333.0.0.1" ) );
+ $this->assertTrue( Cookie::validateCookieDomain( "example.com" ) );
+ $this->assertFalse( Cookie::validateCookieDomain( "example.com." ) );
+ $this->assertTrue( Cookie::validateCookieDomain( ".example.com" ) );
+
+ $this->assertTrue( Cookie::validateCookieDomain( ".example.com", "www.example.com" ) );
+ $this->assertFalse( Cookie::validateCookieDomain( "example.com", "www.example.com" ) );
+ $this->assertTrue( Cookie::validateCookieDomain( "127.0.0.1", "127.0.0.1" ) );
+ $this->assertFalse( Cookie::validateCookieDomain( "127.0.0.1", "localhost" ) );
+
+
+ }
+
+ function testSetCooke() {
+ $c = new MockCookie( "name", "value",
+ array(
+ "domain" => "ac.th",
+ "path" => "/path/",
+ ) );
+ $this->assertFalse($c->canServeDomain("ac.th"));
+
+ $c = new MockCookie( "name", "value",
+ array(
+ "domain" => "example.com",
+ "path" => "/path/",
+ ) );
+
+ $this->assertTrue($c->canServeDomain("example.com"));
+ $this->assertFalse($c->canServeDomain("www.example.com"));
+
+ $c = new MockCookie( "name", "value",
+ array(
+ "domain" => ".example.com",
+ "path" => "/path/",
+ ) );
+
+ $this->assertFalse($c->canServeDomain("www.example.net"));
+ $this->assertFalse($c->canServeDomain("example.com"));
+ $this->assertTrue($c->canServeDomain("www.example.com"));
+
+ $this->assertFalse($c->canServePath("/"));
+ $this->assertFalse($c->canServePath("/bogus/path/"));
+ $this->assertFalse($c->canServePath("/path"));
+ $this->assertTrue($c->canServePath("/path/"));
+
+ $this->assertTrue($c->isUnExpired());
+
+ $this->assertEquals("", $c->serializeToHttpRequest("/path/", "www.example.net"));
+ $this->assertEquals("", $c->serializeToHttpRequest("/", "www.example.com"));
+ $this->assertEquals("name=value", $c->serializeToHttpRequest("/path/", "www.example.com"));
+
+ $c = new MockCookie( "name", "value",
+ array(
+ "domain" => "www.example.com",
+ "path" => "/path/",
+ ) );
+ $this->assertFalse($c->canServeDomain("example.com"));
+ $this->assertFalse($c->canServeDomain("www.example.net"));
+ $this->assertTrue($c->canServeDomain("www.example.com"));
+
+ $c = new MockCookie( "name", "value",
+ array(
+ "domain" => ".example.com",
+ "path" => "/path/",
+ "expires" => "-1 day",
+ ) );
+ $this->assertFalse($c->isUnExpired());
+ $this->assertEquals("", $c->serializeToHttpRequest("/path/", "www.example.com"));
+
+ $c = new MockCookie( "name", "value",
+ array(
+ "domain" => ".example.com",
+ "path" => "/path/",
+ "expires" => "+1 day",
+ ) );
+ $this->assertTrue($c->isUnExpired());
+ $this->assertEquals("name=value", $c->serializeToHttpRequest("/path/", "www.example.com"));
+ }
+
+ function testCookieJarSetCookie() {
+ $cj = new CookieJar;
+ $cj->setCookie( "name", "value",
+ array(
+ "domain" => ".example.com",
+ "path" => "/path/",
+ ) );
+ $cj->setCookie( "name2", "value",
+ array(
+ "domain" => ".example.com",
+ "path" => "/path/sub",
+ ) );
+ $cj->setCookie( "name3", "value",
+ array(
+ "domain" => ".example.com",
+ "path" => "/",
+ ) );
+ $cj->setCookie( "name4", "value",
+ array(
+ "domain" => ".example.net",
+ "path" => "/path/",
+ ) );
+ $cj->setCookie( "name5", "value",
+ array(
+ "domain" => ".example.net",
+ "path" => "/path/",
+ "expires" => "-1 day",
+ ) );
+
+ $this->assertEquals("name4=value", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+ $this->assertEquals("name3=value", $cj->serializeToHttpRequest("/", "www.example.com"));
+ $this->assertEquals("name=value; name3=value", $cj->serializeToHttpRequest("/path/", "www.example.com"));
+
+ $cj->setCookie( "name5", "value",
+ array(
+ "domain" => ".example.net",
+ "path" => "/path/",
+ "expires" => "+1 day",
+ ) );
+ $this->assertEquals("name4=value; name5=value", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+
+ $cj->setCookie( "name4", "value",
+ array(
+ "domain" => ".example.net",
+ "path" => "/path/",
+ "expires" => "-1 day",
+ ) );
+ $this->assertEquals("name5=value", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+ }
+
+ function testParseResponseHeader() {
+ $cj = new CookieJar;
+
+ $h[] = "Set-Cookie: name4=value; domain=.example.com; path=/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
+ $cj->parseCookieResponseHeader( $h[0], "www.example.com" );
+ $this->assertEquals("name4=value", $cj->serializeToHttpRequest("/", "www.example.com"));
+
+ $h[] = "name4=value2; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
+ $cj->parseCookieResponseHeader( $h[1], "www.example.com" );
+ $this->assertEquals("", $cj->serializeToHttpRequest("/", "www.example.com"));
+ $this->assertEquals("name4=value2", $cj->serializeToHttpRequest("/path/", "www.example.com"));
+
+ $h[] = "name5=value3; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
+ $cj->parseCookieResponseHeader( $h[2], "www.example.com" );
+ $this->assertEquals("name4=value2; name5=value3", $cj->serializeToHttpRequest("/path/", "www.example.com"));
+
+ $h[] = "name6=value3; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
+ $cj->parseCookieResponseHeader( $h[3], "www.example.com" );
+ $this->assertEquals("", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+
+ $h[] = "name6=value0; domain=.example.net; path=/path/; expires=Mon, 09-Dec-1999 13:46:00 GMT";
+ $cj->parseCookieResponseHeader( $h[4], "www.example.net" );
+ $this->assertEquals("", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+
+ $h[] = "name6=value4; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
+ $cj->parseCookieResponseHeader( $h[5], "www.example.net" );
+ $this->assertEquals("name6=value4", $cj->serializeToHttpRequest("/path/", "www.example.net"));
+ }
+
+ function runCookieRequests() {
+ $r = HttpRequest::factory( "http://www.php.net/manual" );
+ $r->execute();
+
+ $jar = $r->getCookieJar();
+ $this->assertThat( $jar, $this->isInstanceOf( 'CookieJar' ) );
+
+ if ( is_a($r, 'PhpHttpRequest') && version_compare( '5.1.7', phpversion(), '>' ) ) {
+ $this->markTestSkipped( 'Redirection fails or crashes PHP on 5.1.6 and prior' );
+ }
+ $serialized = $jar->serializeToHttpRequest( "/search?q=test", "www.php.net" );
+ $this->assertRegExp( '/\bCOUNTRY=[^=;]+/', $serialized );
+ $this->assertRegExp( '/\bLAST_LANG=[^=;]+/', $serialized );
+ $this->assertEquals( '', $jar->serializeToHttpRequest( "/search?q=test", "www.php.com" ) );
+ }
+
+ function testCookieRequestDefault() {
+ Http::$httpEngine = false;
+ self::runCookieRequests();
+ }
+ function testCookieRequestPhp() {
+ if ( !self::$has_fopen ) {
+ $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
+ }
+
+ Http::$httpEngine = 'php';
+ self::runCookieRequests();
+ }
+ function testCookieRequestCurl() {
+ if ( !self::$has_curl ) {
+ $this->markTestIncomplete( "This test requires curl." );
+ }
+
+ Http::$httpEngine = 'curl';
+ self::runCookieRequests();
+ }
+} \ No newline at end of file
diff --git a/maintenance/tests/IPTest.php b/maintenance/tests/IPTest.php
new file mode 100644
index 00000000..9db77f72
--- /dev/null
+++ b/maintenance/tests/IPTest.php
@@ -0,0 +1,52 @@
+<?php
+/*
+ * Tests for IP validity functions. Ported from /t/inc/IP.t by avar.
+ */
+
+class IPTest extends PHPUnit_Framework_TestCase {
+
+ public function testValidIPs() {
+ foreach ( range( 0, 255 ) as $i ) {
+ $a = sprintf( "%03d", $i );
+ $b = sprintf( "%02d", $i );
+ $c = sprintf( "%01d", $i );
+ foreach ( array_unique( array( $a, $b, $c ) ) as $f ) {
+ $ip = "$f.$f.$f.$f";
+ $this->assertTrue( IP::isValid( $ip ) , "$ip is a valid IPv4 address" );
+ }
+ }
+ }
+
+ public function testInvalidIPs() {
+ foreach ( range( 256, 999 ) as $i ) {
+ $a = sprintf( "%03d", $i );
+ $b = sprintf( "%02d", $i );
+ $c = sprintf( "%01d", $i );
+ foreach ( array_unique( array( $a, $b, $c ) ) as $f ) {
+ $ip = "$f.$f.$f.$f";
+ $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" );
+ }
+ }
+ }
+
+ public function testBogusIPs() {
+ $invalid = array(
+ 'www.xn--var-xla.net',
+ '216.17.184.G',
+ '216.17.184.1.',
+ '216.17.184',
+ '216.17.184.',
+ '256.17.184.1'
+ );
+ foreach ( $invalid as $i ) {
+ $this->assertFalse( IP::isValid( $i ), "$i is an invalid IPv4 address" );
+ }
+ }
+
+ public function testPrivateIPs() {
+ $private = array( '10.0.0.1', '172.16.0.1', '192.168.0.1' );
+ foreach ( $private as $p ) {
+ $this->assertFalse( IP::isPublic( $p ), "$p is not a public IP address" );
+ }
+ }
+}
diff --git a/maintenance/tests/ImageFunctionsTest.php b/maintenance/tests/ImageFunctionsTest.php
new file mode 100644
index 00000000..9794a2a2
--- /dev/null
+++ b/maintenance/tests/ImageFunctionsTest.php
@@ -0,0 +1,48 @@
+<?php
+
+class ImageFunctionsTest extends PHPUnit_Framework_TestCase {
+ function testFitBoxWidth() {
+ $vals = array(
+ array(
+ 'width' => 50,
+ 'height' => 50,
+ 'tests' => array(
+ 50 => 50,
+ 17 => 17,
+ 18 => 18 ) ),
+ array(
+ 'width' => 366,
+ 'height' => 300,
+ 'tests' => array(
+ 50 => 61,
+ 17 => 21,
+ 18 => 22 ) ),
+ array(
+ 'width' => 300,
+ 'height' => 366,
+ 'tests' => array(
+ 50 => 41,
+ 17 => 14,
+ 18 => 15 ) ),
+ array(
+ 'width' => 100,
+ 'height' => 400,
+ 'tests' => array(
+ 50 => 12,
+ 17 => 4,
+ 18 => 4 ) ) );
+ foreach( $vals as $row ) {
+ extract( $row );
+ foreach( $tests as $max => $expected ) {
+ $y = round( $expected * $height / $width );
+ $result = wfFitBoxWidth( $width, $height, $max );
+ $y2 = round( $result * $height / $width );
+ $this->assertEquals( $expected,
+ $result,
+ "($width, $height, $max) wanted: {$expected}x$y, got: {$result}x$y2" );
+ }
+ }
+ }
+}
+
+
diff --git a/maintenance/tests/LanguageConverterTest.php b/maintenance/tests/LanguageConverterTest.php
new file mode 100644
index 00000000..22b396e7
--- /dev/null
+++ b/maintenance/tests/LanguageConverterTest.php
@@ -0,0 +1,148 @@
+<?php
+
+class LanguageConverterTest extends PHPUnit_Framework_TestCase {
+ protected $lang = null;
+ protected $lc = null;
+
+ function setUp() {
+ global $wgMemc, $wgRequest, $wgUser, $wgContLang;
+
+ $wgUser = new User;
+ $wgRequest = new FauxRequest(array());
+ $wgMemc = new FakeMemCachedClient;
+ $wgContLang = Language::factory( 'tg' );
+ $this->lang = new LanguageTest();
+ $this->lc = new TestConverter( $this->lang, 'tg',
+ array( 'tg', 'tg-latn' ) );
+ }
+
+ function tearDown() {
+ global $wgMemc;
+ unset($wgMemc);
+ unset($this->lc);
+ unset($this->lang);
+ }
+
+ function testGetPreferredVariantDefaults() {
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, true));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true));
+ }
+
+ function testGetPreferredVariantHeaders() {
+ global $wgRequest;
+ $wgRequest->setHeader('Accept-Language', 'tg-latn');
+
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false));
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(false, true));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true));
+ }
+
+ function testGetPreferredVariantHeaderWeight() {
+ global $wgRequest;
+ $wgRequest->setHeader('Accept-Language', 'tg;q=1');
+
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, true));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true));
+ }
+
+ function testGetPreferredVariantHeaderWeight2() {
+ global $wgRequest;
+ $wgRequest->setHeader('Accept-Language', 'tg-latn;q=1');
+
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false));
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(false, true));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true));
+ }
+
+ function testGetPreferredVariantHeaderMulti() {
+ global $wgRequest;
+ $wgRequest->setHeader('Accept-Language', 'en, tg-latn;q=1');
+
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false));
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(false, true));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true));
+ }
+
+ function testGetPreferredVariantUserOption() {
+ global $wgUser;
+
+ $wgUser = new User;
+ $wgUser->setId(1);
+ $wgUser->setOption('variant', 'tg-latn');
+
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, true));
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, false));
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, true));
+ }
+
+ function testGetPreferredVariantHeaderUserVsUrl() {
+ global $wgRequest, $wgUser, $wgContLang;
+
+ $wgContLang = Language::factory( 'tg-latn' );
+ $wgRequest->setVal('variant', 'tg');
+ $wgUser = User::newFromId("admin");
+ $wgUser->setId(1);
+ $wgUser->setOption('variant', 'tg-latn'); // The user's data is ignored
+ // because the variant is set in the URL.
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true));
+ }
+
+
+ function testGetPreferredVariantDefaultLanguageVariant() {
+ global $wgDefaultLanguageVariant;
+
+ $wgDefaultLanguageVariant = 'tg-latn';
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(false, false));
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(false, true));
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, false));
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, true));
+ }
+
+ function testGetPreferredVariantDefaultLanguageVsUrlVariant() {
+ global $wgDefaultLanguageVariant, $wgRequest, $wgContLang;
+
+ $wgContLang = Language::factory( 'tg-latn' );
+ $wgDefaultLanguageVariant = 'tg';
+ $wgRequest->setVal('variant', null);
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false));
+ $this->assertEquals('tg', $this->lc->getPreferredVariant(false, true));
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, false));
+ $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, true));
+ }
+}
+
+/**
+ * Test converter (from Tajiki to latin orthography)
+ */
+class TestConverter extends LanguageConverter {
+ private $table = array(
+ 'б' => 'b',
+ 'в' => 'v',
+ 'г' => 'g',
+ );
+
+ function loadDefaultTables() {
+ $this->mTables = array(
+ 'tg-latn' => new ReplacementArray( $this->table ),
+ 'tg' => new ReplacementArray()
+ );
+ }
+
+}
+
+class LanguageTest extends Language {
+ function __construct() {
+ parent::__construct();
+ $variants = array( 'tg', 'tg-latn' );
+ $this->mConverter = new TestConverter( $this, 'tg', $variants );
+ }
+}
diff --git a/maintenance/tests/LicensesTest.php b/maintenance/tests/LicensesTest.php
new file mode 100644
index 00000000..c5357f89
--- /dev/null
+++ b/maintenance/tests/LicensesTest.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @group Broken
+ */
+class LicensesTest extends PHPUnit_Framework_TestCase {
+
+ function testLicenses() {
+ $str = "
+* Free licenses:
+** GFLD|Debian disagrees
+";
+
+ $lc = new Licenses( $str );
+ $this->assertTrue( is_a( $lc, 'Licenses' ), 'Correct class' );
+ }
+} \ No newline at end of file
diff --git a/maintenance/tests/LocalFileTest.php b/maintenance/tests/LocalFileTest.php
new file mode 100644
index 00000000..e57798be
--- /dev/null
+++ b/maintenance/tests/LocalFileTest.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * These tests should work regardless of $wgCapitalLinks
+ */
+
+class LocalFileTest extends PHPUnit_Framework_TestCase {
+ function setUp() {
+ global $wgContLang;
+ $wgContLang = new Language;
+ $info = array(
+ 'name' => 'test',
+ 'directory' => '/testdir',
+ 'url' => '/testurl',
+ 'hashLevels' => 2,
+ 'transformVia404' => false,
+ );
+ $this->repo_hl0 = new LocalRepo( array( 'hashLevels' => 0 ) + $info );
+ $this->repo_hl2 = new LocalRepo( array( 'hashLevels' => 2 ) + $info );
+ $this->repo_lc = new LocalRepo( array( 'initialCapital' => false ) + $info );
+ $this->file_hl0 = $this->repo_hl0->newFile( 'test!' );
+ $this->file_hl2 = $this->repo_hl2->newFile( 'test!' );
+ $this->file_lc = $this->repo_lc->newFile( 'test!' );
+ }
+
+ function tearDown() {
+ global $wgContLang;
+ unset($wgContLang);
+ }
+
+ function testGetHashPath() {
+ $this->assertEquals( '', $this->file_hl0->getHashPath() );
+ $this->assertEquals( 'a/a2/', $this->file_hl2->getHashPath() );
+ $this->assertEquals( 'c/c4/', $this->file_lc->getHashPath() );
+ }
+
+ function testGetRel() {
+ $this->assertEquals( 'Test!', $this->file_hl0->getRel() );
+ $this->assertEquals( 'a/a2/Test!', $this->file_hl2->getRel() );
+ $this->assertEquals( 'c/c4/test!', $this->file_lc->getRel() );
+ }
+
+ function testGetUrlRel() {
+ $this->assertEquals( 'Test%21', $this->file_hl0->getUrlRel() );
+ $this->assertEquals( 'a/a2/Test%21', $this->file_hl2->getUrlRel() );
+ $this->assertEquals( 'c/c4/test%21', $this->file_lc->getUrlRel() );
+ }
+
+ function testGetArchivePath() {
+ $this->assertEquals( '/testdir/archive', $this->file_hl0->getArchivePath() );
+ $this->assertEquals( '/testdir/archive/a/a2', $this->file_hl2->getArchivePath() );
+ $this->assertEquals( '/testdir/archive/!', $this->file_hl0->getArchivePath( '!' ) );
+ $this->assertEquals( '/testdir/archive/a/a2/!', $this->file_hl2->getArchivePath( '!' ) );
+ }
+
+ function testGetThumbPath() {
+ $this->assertEquals( '/testdir/thumb/Test!', $this->file_hl0->getThumbPath() );
+ $this->assertEquals( '/testdir/thumb/a/a2/Test!', $this->file_hl2->getThumbPath() );
+ $this->assertEquals( '/testdir/thumb/Test!/x', $this->file_hl0->getThumbPath( 'x' ) );
+ $this->assertEquals( '/testdir/thumb/a/a2/Test!/x', $this->file_hl2->getThumbPath( 'x' ) );
+ }
+
+ function testGetArchiveUrl() {
+ $this->assertEquals( '/testurl/archive', $this->file_hl0->getArchiveUrl() );
+ $this->assertEquals( '/testurl/archive/a/a2', $this->file_hl2->getArchiveUrl() );
+ $this->assertEquals( '/testurl/archive/%21', $this->file_hl0->getArchiveUrl( '!' ) );
+ $this->assertEquals( '/testurl/archive/a/a2/%21', $this->file_hl2->getArchiveUrl( '!' ) );
+ }
+
+ function testGetThumbUrl() {
+ $this->assertEquals( '/testurl/thumb/Test%21', $this->file_hl0->getThumbUrl() );
+ $this->assertEquals( '/testurl/thumb/a/a2/Test%21', $this->file_hl2->getThumbUrl() );
+ $this->assertEquals( '/testurl/thumb/Test%21/x', $this->file_hl0->getThumbUrl( 'x' ) );
+ $this->assertEquals( '/testurl/thumb/a/a2/Test%21/x', $this->file_hl2->getThumbUrl( 'x' ) );
+ }
+
+ function testGetArchiveVirtualUrl() {
+ $this->assertEquals( 'mwrepo://test/public/archive', $this->file_hl0->getArchiveVirtualUrl() );
+ $this->assertEquals( 'mwrepo://test/public/archive/a/a2', $this->file_hl2->getArchiveVirtualUrl() );
+ $this->assertEquals( 'mwrepo://test/public/archive/%21', $this->file_hl0->getArchiveVirtualUrl( '!' ) );
+ $this->assertEquals( 'mwrepo://test/public/archive/a/a2/%21', $this->file_hl2->getArchiveVirtualUrl( '!' ) );
+ }
+
+ function testGetThumbVirtualUrl() {
+ $this->assertEquals( 'mwrepo://test/thumb/Test%21', $this->file_hl0->getThumbVirtualUrl() );
+ $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21', $this->file_hl2->getThumbVirtualUrl() );
+ $this->assertEquals( 'mwrepo://test/thumb/Test%21/%21', $this->file_hl0->getThumbVirtualUrl( '!' ) );
+ $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21/%21', $this->file_hl2->getThumbVirtualUrl( '!' ) );
+ }
+
+ function testGetUrl() {
+ $this->assertEquals( '/testurl/Test%21', $this->file_hl0->getUrl() );
+ $this->assertEquals( '/testurl/a/a2/Test%21', $this->file_hl2->getUrl() );
+ }
+}
+
+
diff --git a/maintenance/tests/Makefile b/maintenance/tests/Makefile
new file mode 100644
index 00000000..b2c0fb71
--- /dev/null
+++ b/maintenance/tests/Makefile
@@ -0,0 +1,23 @@
+# See
+# http://lists.wikimedia.org/pipermail/wikitech-l/2010-February/046657.html
+# for why prove(1) is the default target and not phpunit(1)
+
+.PHONY: help test
+all test: tap
+
+tap:
+ prove -e 'phpunit --tap' *Test*.php
+
+phpunit:
+ phpunit
+
+install:
+ pear channel-discover pear.phpunit.de
+ pear install phpunit/PHPUnit
+
+help:
+ # Options:
+ # test (default) Run the tests individually through Test::Harness's prove(1)
+ # phpunit Run all the tests with phpunit
+ # install Install PHPUnit from phpunit.de
+ # help You're looking at it!
diff --git a/maintenance/tests/MediaWikiParserTest.php b/maintenance/tests/MediaWikiParserTest.php
new file mode 100644
index 00000000..a545b3bb
--- /dev/null
+++ b/maintenance/tests/MediaWikiParserTest.php
@@ -0,0 +1,283 @@
+<?php
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ exit;
+}
+
+global $IP;
+define( "NO_COMMAND_LINE", 1 );
+define( "PARSER_TESTS", "$IP/maintenance/parserTests.txt" );
+
+require_once( "$IP/maintenance/parserTests.inc" );
+
+class PHPUnitTestRecorder extends TestRecorder {
+
+ function record( $test, $result ) {
+ $this->total++;
+ $this->success += $result;
+
+ }
+
+ function reportPercentage( $success, $total ) {}
+}
+
+class MediaWikiParserTestSuite extends PHPUnit_Framework_TestSuite {
+#implements PHPUnit_Framework_SelfDescribing {
+ static private $count;
+ static public $parser;
+ static public $iter;
+
+ public static function suite() {
+ $suite = new PHPUnit_Framework_TestSuite();
+
+ self::$iter = new TestFileIterator( PARSER_TESTS );
+
+ foreach(self::$iter as $i => $test) {
+ $suite->addTest(new ParserUnitTest($i, $test['test']));
+ self::$count++;
+ }
+ unset($tests);
+
+ self::$parser = new PTShell;
+ self::$iter->setParser(self::$parser);
+ self::$parser->recorder->start();
+ self::$parser->setupDatabase();
+ self::$iter->rewind();
+ /* } */
+ /* function setUp() { */
+ global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgDeferredUpdateList,
+ $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache,
+ $wgMessageCache, $wgUseDatabaseMessages, $wgMsgCacheExpiry, $parserMemc,
+ $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
+ $wgNamespacesWithSubpages, $wgThumbnailScriptPath, $wgScriptPath,
+ $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath;
+
+ $wgScript = '/index.php';
+ $wgScriptPath = '/';
+ $wgArticlePath = '/wiki/$1';
+ $wgStyleSheetPath = '/skins';
+ $wgStylePath = '/skins';
+ $wgThumbnailScriptPath = false;
+ $wgLocalFileRepo = array(
+ 'class' => 'LocalRepo',
+ 'name' => 'local',
+ 'directory' => '',
+ 'url' => 'http://example.com/images',
+ 'hashLevels' => 2,
+ 'transformVia404' => false,
+ );
+ $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
+ $wgNamespaceAliases['Image'] = NS_FILE;
+ $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
+
+
+ $wgEnableParserCache = false;
+ $wgDeferredUpdateList = array();
+ $wgMemc =& wfGetMainCache();
+ $messageMemc =& wfGetMessageCacheStorage();
+ $parserMemc =& wfGetParserCacheStorage();
+
+ $wgContLang = new StubContLang;
+ $wgUser = new StubUser;
+ $wgLang = new StubUserLang;
+ $wgOut = new StubObject( 'wgOut', 'OutputPage' );
+ $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
+ $wgRequest = new WebRequest;
+
+ $wgMessageCache = new StubObject( 'wgMessageCache', 'MessageCache',
+ array( $messageMemc, $wgUseDatabaseMessages,
+ $wgMsgCacheExpiry, wfWikiID() ) );
+ if( $wgStyleDirectory === false) $wgStyleDirectory = "$IP/skins";
+
+ return $suite;
+ }
+
+ public function tearDown() {
+ $this->teardownDatabase();
+ $this->recorder->report();
+ $this->recorder->end();
+ $this->teardownUploadDir($this->uploadDir);
+ }
+
+ public function count() {return self::$count;}
+
+ public function toString() {
+ return "MediaWiki Parser Tests";
+ }
+
+
+ private $db;
+ private $uploadDir;
+ private $keepUploads;
+ /**
+ * Remove the dummy uploads directory
+ */
+ private function teardownUploadDir( $dir ) {
+ if ( $this->keepUploads ) {
+ return;
+ }
+
+ // delete the files first, then the dirs.
+ self::deleteFiles(
+ array (
+ "$dir/3/3a/Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg",
+ "$dir/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg",
+
+ "$dir/0/09/Bad.jpg",
+ )
+ );
+
+ self::deleteDirs(
+ array (
+ "$dir/3/3a",
+ "$dir/3",
+ "$dir/thumb/6/65",
+ "$dir/thumb/6",
+ "$dir/thumb/3/3a/Foobar.jpg",
+ "$dir/thumb/3/3a",
+ "$dir/thumb/3",
+
+ "$dir/0/09/",
+ "$dir/0/",
+
+ "$dir/thumb",
+ "$dir",
+ )
+ );
+ }
+
+ /**
+ * Delete the specified files, if they exist.
+ * @param array $files full paths to files to delete.
+ */
+ private static function deleteFiles( $files ) {
+ foreach( $files as $file ) {
+ if( file_exists( $file ) ) {
+ unlink( $file );
+ }
+ }
+ }
+ /**
+ * Delete the specified directories, if they exist. Must be empty.
+ * @param array $dirs full paths to directories to delete.
+ */
+ private static function deleteDirs( $dirs ) {
+ foreach( $dirs as $dir ) {
+ if( is_dir( $dir ) ) {
+ rmdir( $dir );
+ }
+ }
+ }
+
+ /**
+ * Create a dummy uploads directory which will contain a couple
+ * of files in order to pass existence tests.
+ * @return string The directory
+ */
+ private function setupUploadDir() {
+ global $IP;
+ if ( $this->keepUploads ) {
+ $dir = wfTempDir() . '/mwParser-images';
+ if ( is_dir( $dir ) ) {
+ return $dir;
+ }
+ } else {
+ $dir = wfTempDir() . "/mwParser-" . mt_rand() . "-images";
+ }
+
+ wfDebug( "Creating upload directory $dir\n" );
+ if ( file_exists( $dir ) ) {
+ wfDebug( "Already exists!\n" );
+ return $dir;
+ }
+ wfMkdirParents( $dir . '/3/3a' );
+ copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
+
+ wfMkdirParents( $dir . '/0/09' );
+ copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" );
+ return $dir;
+ }
+}
+
+class ParserUnitTest extends PHPUnit_Framework_TestCase {
+ private $number = 0;
+ private $test = "";
+
+ public function __construct($number, $test) {
+ $this->number = $number;
+ $this->test = $test;
+ }
+
+ function count() {return 1;}
+
+ public function run(PHPUnit_Framework_TestResult $result = NULL) {
+ PHPUnit_Framework_Assert::resetCount();
+ if ($result === NULL) {
+ $result = new PHPUnit_Framework_TestResult;
+ }
+
+ $t = MediaWikiParserTestSuite::$iter->current();
+ $k = MediaWikiParserTestSuite::$iter->key();
+
+ if(!MediaWikiParserTestSuite::$iter->valid()) {
+ return;
+ }
+
+ // The only way this should happen is if the parserTest.txt
+ // file were modified while the script is running.
+ if($k != $this->number) {
+ $i = $this->number;
+ wfDie("I got confused!\n");
+ }
+
+ $result->startTest($this);
+ PHPUnit_Util_Timer::start();
+
+ $r = false;
+ try {
+ $r = MediaWikiParserTestSuite::$parser->runTest(
+ $t['test'], $t['input'], $t['result'], $t['options'], $t['config']
+ );
+ PHPUnit_Framework_Assert::assertTrue(true, $t['test']);
+ }
+ catch (PHPUnit_Framework_AssertionFailedError $e) {
+ $result->addFailure($this, $e, PHPUnit_Util_Timer::stop());
+ }
+ catch (Exception $e) {
+ $result->addError($this, $e, PHPUnit_Util_Timer::stop());
+ }
+ PHPUnit_Framework_Assert::assertTrue(true, $t['test']);
+
+ $result->endTest($this, PHPUnit_Util_Timer::stop());
+
+ MediaWikiParserTestSuite::$parser->recorder->record($t['test'], $r);
+ MediaWikiParserTestSuite::$iter->next();
+ $this->addToAssertionCount(PHPUnit_Framework_Assert::getCount());
+
+ return $result;
+ }
+
+}
+
+class PTShell extends ParserTest {
+ function showTesting( $desc ) {
+ }
+
+ function showRunFile( $path ) {
+ }
+
+ function showSuccess( $desc ) {
+ PHPUnit_Framework_Assert::assertTrue(true, $desc);
+ return true;
+ }
+
+ function showFailure( $desc, $expected, $got ) {
+ PHPUnit_Framework_Assert::assertEquals($expected, $got, $desc);
+ }
+
+}
+
+
diff --git a/maintenance/tests/MediaWiki_Setup.php b/maintenance/tests/MediaWiki_Setup.php
new file mode 100644
index 00000000..e7acc338
--- /dev/null
+++ b/maintenance/tests/MediaWiki_Setup.php
@@ -0,0 +1,28 @@
+<?php
+
+abstract class MediaWiki_Setup extends PHPUnit_Framework_TestCase {
+
+ protected function buildTestDatabase( $tables ) {
+ global $wgDBprefix;
+
+ $db = wfGetDB( DB_MASTER );
+ $oldTableNames = array();
+ foreach( $tables as $table )
+ $oldTableNames[$table] = $db->tableName( $table );
+ $db->tablePrefix( 'parsertest_' );
+
+ if( $db->isOpen() ) {
+ foreach ( $tables as $tbl ) {
+ $newTableName = $db->tableName( $tbl );
+ $tableName = $oldTableNames[$tbl];
+ $db->query( "DROP TABLE IF EXISTS $newTableName", __METHOD__ );
+ $db->duplicateTableStructure( $tableName, $newTableName, __METHOD__ );
+ }
+ return $db;
+ } else {
+ // Something amiss
+ return null;
+ }
+ }
+}
+
diff --git a/maintenance/tests/README b/maintenance/tests/README
new file mode 100644
index 00000000..b52e790e
--- /dev/null
+++ b/maintenance/tests/README
@@ -0,0 +1,24 @@
+Some quickie unit tests done with the PHPUnit testing framework. To run the
+test suite, run 'make test' in this dir. This directly invokes 'phpunit.'
+
+PHPUnit is no longer maintained by PEAR. To get the current version of
+PHPUnit, first uninstall any old version of PHPUnit or PHPUnit2 from PEAR,
+then install the current version from phpunit.de like this:
+
+# pear channel-discover pear.phpunit.de
+# pear install phpunit/PHPUnit
+
+You also may wish to install this via your normal package mechanism:
+
+# aptitude install phpunit
+ - or -
+# yum install phpunit
+
+Notes:
+- Label currently broken tests in the group Broken and they will not
+ be run by phpunit. You can add them to the group by putting the
+ following comment at the top of the file:
+ /**
+ * @group Broken
+ */
+- Need to fix some broken tests
diff --git a/maintenance/tests/RevisionTest.php b/maintenance/tests/RevisionTest.php
new file mode 100644
index 00000000..78fcc7c3
--- /dev/null
+++ b/maintenance/tests/RevisionTest.php
@@ -0,0 +1,114 @@
+<?php
+
+class RevisionTest extends PHPUnit_Framework_TestCase {
+ var $saveGlobals = array();
+
+ function setUp() {
+ global $wgContLang;
+ $wgContLang = Language::factory( 'en' );
+ $globalSet = array(
+ 'wgLegacyEncoding' => false,
+ 'wgCompressRevisions' => false,
+ 'wgInputEncoding' => 'utf-8',
+ 'wgOutputEncoding' => 'utf-8' );
+ foreach( $globalSet as $var => $data ) {
+ $this->saveGlobals[$var] = $GLOBALS[$var];
+ $GLOBALS[$var] = $data;
+ }
+ }
+
+ function tearDown() {
+ foreach( $this->saveGlobals as $var => $data ) {
+ $GLOBALS[$var] = $data;
+ }
+ }
+
+ function testGetRevisionText() {
+ $row = new stdClass;
+ $row->old_flags = '';
+ $row->old_text = 'This is a bunch of revision text.';
+ $this->assertEquals(
+ 'This is a bunch of revision text.',
+ Revision::getRevisionText( $row ) );
+ }
+
+ function testGetRevisionTextGzip() {
+ $row = new stdClass;
+ $row->old_flags = 'gzip';
+ $row->old_text = gzdeflate( 'This is a bunch of revision text.' );
+ $this->assertEquals(
+ 'This is a bunch of revision text.',
+ Revision::getRevisionText( $row ) );
+ }
+
+ function testGetRevisionTextUtf8Native() {
+ $row = new stdClass;
+ $row->old_flags = 'utf-8';
+ $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
+ $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
+ $this->assertEquals(
+ "Wiki est l'\xc3\xa9cole superieur !",
+ Revision::getRevisionText( $row ) );
+ }
+
+ function testGetRevisionTextUtf8Legacy() {
+ $row = new stdClass;
+ $row->old_flags = '';
+ $row->old_text = "Wiki est l'\xe9cole superieur !";
+ $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
+ $this->assertEquals(
+ "Wiki est l'\xc3\xa9cole superieur !",
+ Revision::getRevisionText( $row ) );
+ }
+
+ function testGetRevisionTextUtf8NativeGzip() {
+ $row = new stdClass;
+ $row->old_flags = 'gzip,utf-8';
+ $row->old_text = gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" );
+ $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
+ $this->assertEquals(
+ "Wiki est l'\xc3\xa9cole superieur !",
+ Revision::getRevisionText( $row ) );
+ }
+
+ function testGetRevisionTextUtf8LegacyGzip() {
+ $row = new stdClass;
+ $row->old_flags = 'gzip';
+ $row->old_text = gzdeflate( "Wiki est l'\xe9cole superieur !" );
+ $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1';
+ $this->assertEquals(
+ "Wiki est l'\xc3\xa9cole superieur !",
+ Revision::getRevisionText( $row ) );
+ }
+
+ function testCompressRevisionTextUtf8() {
+ $row = new stdClass;
+ $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
+ $row->old_flags = Revision::compressRevisionText( $row->old_text );
+ $this->assertTrue( false !== strpos( $row->old_flags, 'utf-8' ),
+ "Flags should contain 'utf-8'" );
+ $this->assertFalse( false !== strpos( $row->old_flags, 'gzip' ),
+ "Flags should not contain 'gzip'" );
+ $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
+ $row->old_text, "Direct check" );
+ $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
+ Revision::getRevisionText( $row ), "getRevisionText" );
+ }
+
+ function testCompressRevisionTextUtf8Gzip() {
+ $GLOBALS['wgCompressRevisions'] = true;
+ $row = new stdClass;
+ $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
+ $row->old_flags = Revision::compressRevisionText( $row->old_text );
+ $this->assertTrue( false !== strpos( $row->old_flags, 'utf-8' ),
+ "Flags should contain 'utf-8'" );
+ $this->assertTrue( false !== strpos( $row->old_flags, 'gzip' ),
+ "Flags should contain 'gzip'" );
+ $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
+ gzinflate( $row->old_text ), "Direct check" );
+ $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
+ Revision::getRevisionText( $row ), "getRevisionText" );
+ }
+}
+
+
diff --git a/maintenance/tests/SanitizerTest.php b/maintenance/tests/SanitizerTest.php
new file mode 100644
index 00000000..8a2287d5
--- /dev/null
+++ b/maintenance/tests/SanitizerTest.php
@@ -0,0 +1,73 @@
+<?php
+
+
+class SanitizerTest extends PHPUnit_Framework_TestCase {
+
+ function setUp() {
+ AutoLoader::loadClass( 'Sanitizer' );
+ }
+
+ function testDecodeNamedEntities() {
+ $this->assertEquals(
+ "\xc3\xa9cole",
+ Sanitizer::decodeCharReferences( '&eacute;cole' ),
+ 'decode named entities'
+ );
+ }
+
+ function testDecodeNumericEntities() {
+ $this->assertEquals(
+ "\xc4\x88io bonas dans l'\xc3\xa9cole!",
+ Sanitizer::decodeCharReferences( "&#x108;io bonas dans l'&#233;cole!" ),
+ 'decode numeric entities'
+ );
+ }
+
+ function testDecodeMixedEntities() {
+ $this->assertEquals(
+ "\xc4\x88io bonas dans l'\xc3\xa9cole!",
+ Sanitizer::decodeCharReferences( "&#x108;io bonas dans l'&eacute;cole!" ),
+ 'decode mixed numeric/named entities'
+ );
+ }
+
+ function testDecodeMixedComplexEntities() {
+ $this->assertEquals(
+ "\xc4\x88io bonas dans l'\xc3\xa9cole! (mais pas &#x108;io dans l'&eacute;cole)",
+ Sanitizer::decodeCharReferences(
+ "&#x108;io bonas dans l'&eacute;cole! (mais pas &amp;#x108;io dans l'&#38;eacute;cole)"
+ ),
+ 'decode mixed complex entities'
+ );
+ }
+
+ function testInvalidAmpersand() {
+ $this->assertEquals(
+ 'a & b',
+ Sanitizer::decodeCharReferences( 'a & b' ),
+ 'Invalid ampersand'
+ );
+ }
+
+ function testInvalidEntities() {
+ $this->assertEquals(
+ '&foo;',
+ Sanitizer::decodeCharReferences( '&foo;' ),
+ 'Invalid named entity'
+ );
+ }
+
+ function testInvalidNumberedEntities() {
+ $this->assertEquals( UTF8_REPLACEMENT, Sanitizer::decodeCharReferences( "&#88888888888888;" ), 'Invalid numbered entity' );
+ }
+
+ function testSelfClosingTag() {
+ $GLOBALS['wgUseTidy'] = false;
+ $this->assertEquals(
+ '<div>Hello world</div>',
+ Sanitizer::removeHTMLtags( '<div>Hello world</div />' ),
+ 'Self-closing closing div'
+ );
+ }
+}
+
diff --git a/maintenance/tests/SearchEngineTest.php b/maintenance/tests/SearchEngineTest.php
new file mode 100644
index 00000000..0cae2d42
--- /dev/null
+++ b/maintenance/tests/SearchEngineTest.php
@@ -0,0 +1,138 @@
+<?php
+
+require_once( 'MediaWiki_Setup.php' );
+
+/**
+ * @group Stub
+ */
+class SearchEngineTest extends MediaWiki_Setup {
+ var $db, $search;
+
+ function insertSearchData() {
+ $this->db->safeQuery( <<<SQL
+ INSERT INTO ! (page_id,page_namespace,page_title,page_latest)
+ VALUES (1, 0, 'Main_Page', 1),
+ (2, 1, 'Main_Page', 2),
+ (3, 0, 'Smithee', 3),
+ (4, 1, 'Smithee', 4),
+ (5, 0, 'Unrelated_page', 5),
+ (6, 0, 'Another_page', 6),
+ (7, 4, 'Help', 7),
+ (8, 0, 'Thppt', 8),
+ (9, 0, 'Alan_Smithee', 9),
+ (10, 0, 'Pages', 10)
+SQL
+ , $this->db->tableName( 'page' ) );
+ $this->db->safeQuery( <<<SQL
+ INSERT INTO ! (rev_id,rev_page)
+ VALUES (1, 1),
+ (2, 2),
+ (3, 3),
+ (4, 4),
+ (5, 5),
+ (6, 6),
+ (7, 7),
+ (8, 8),
+ (9, 9),
+ (10, 10)
+SQL
+ , $this->db->tableName( 'revision' ) );
+ $this->db->safeQuery( <<<SQL
+ INSERT INTO ! (old_id,old_text)
+ VALUES (1, 'This is a main page'),
+ (2, 'This is a talk page to the main page, see [[smithee]]'),
+ (3, 'A smithee is one who smiths. See also [[Alan Smithee]]'),
+ (4, 'This article sucks.'),
+ (5, 'Nothing in this page is about the S word.'),
+ (6, 'This page also is unrelated.'),
+ (7, 'Help me!'),
+ (8, 'Blah blah'),
+ (9, 'yum'),
+ (10,'are food')
+SQL
+ , $this->db->tableName( 'text' ) );
+ $this->db->safeQuery( <<<SQL
+ INSERT INTO ! (si_page,si_title,si_text)
+ VALUES (1, 'main page', 'this is a main page'),
+ (2, 'main page', 'this is a talk page to the main page, see smithee'),
+ (3, 'smithee', 'a smithee is one who smiths see also alan smithee'),
+ (4, 'smithee', 'this article sucks'),
+ (5, 'unrelated page', 'nothing in this page is about the s word'),
+ (6, 'another page', 'this page also is unrelated'),
+ (7, 'help', 'help me'),
+ (8, 'thppt', 'blah blah'),
+ (9, 'alan smithee', 'yum'),
+ (10, 'pages', 'are food')
+SQL
+ , $this->db->tableName( 'searchindex' ) );
+ }
+
+ function fetchIds( $results ) {
+ $matches = array();
+ while( $row = $results->next() ) {
+ $matches[] = $row->getTitle()->getPrefixedText();
+ }
+ $results->free();
+ # Search is not guaranteed to return results in a certain order;
+ # sort them numerically so we will compare simply that we received
+ # the expected matches.
+ sort( $matches );
+ return $matches;
+ }
+
+ function testTextSearch() {
+ if( is_null( $this->db ) ) {
+ $this->markTestIncomplete( "Can't find a database to test with." );
+ }
+ $this->assertEquals(
+ array( 'Smithee' ),
+ $this->fetchIds( $this->search->searchText( 'smithee' ) ),
+ "Plain search failed" );
+ }
+
+ function testTextPowerSearch() {
+ if( is_null( $this->db ) ) {
+ $this->markTestIncomplete( "Can't find a database to test with." );
+ }
+ $this->search->setNamespaces( array( 0, 1, 4 ) );
+ $this->assertEquals(
+ array(
+ 'Smithee',
+ 'Talk:Main Page',
+ ),
+ $this->fetchIds( $this->search->searchText( 'smithee' ) ),
+ "Power search failed" );
+ }
+
+ function testTitleSearch() {
+ if( is_null( $this->db ) ) {
+ $this->markTestIncomplete( "Can't find a database to test with." );
+ }
+ $this->assertEquals(
+ array(
+ 'Alan Smithee',
+ 'Smithee',
+ ),
+ $this->fetchIds( $this->search->searchTitle( 'smithee' ) ),
+ "Title search failed" );
+ }
+
+ function testTextTitlePowerSearch() {
+ if( is_null( $this->db ) ) {
+ $this->markTestIncomplete( "Can't find a database to test with." );
+ }
+ $this->search->setNamespaces( array( 0, 1, 4 ) );
+ $this->assertEquals(
+ array(
+ 'Alan Smithee',
+ 'Smithee',
+ 'Talk:Smithee',
+ ),
+ $this->fetchIds( $this->search->searchTitle( 'smithee' ) ),
+ "Title power search failed" );
+ }
+
+}
+
+
+
diff --git a/maintenance/tests/SearchMySQLTest.php b/maintenance/tests/SearchMySQLTest.php
new file mode 100644
index 00000000..526f6216
--- /dev/null
+++ b/maintenance/tests/SearchMySQLTest.php
@@ -0,0 +1,26 @@
+<?php
+require_once( 'SearchEngineTest.php' );
+
+class SearchMySQLTest extends SearchEngineTest {
+ var $db;
+
+ function setUp() {
+ $GLOBALS['wgContLang'] = new Language;
+ $this->db = $this->buildTestDatabase(
+ array( 'page', 'revision', 'text', 'searchindex', 'user' ) );
+ if( $this->db ) {
+ $this->insertSearchData();
+ }
+ $this->search = new SearchMySQL( $this->db );
+ }
+
+ function tearDown() {
+ if( !is_null( $this->db ) ) {
+ wfGetLB()->closeConnecton( $this->db );
+ }
+ unset( $this->db );
+ unset( $this->search );
+ }
+}
+
+
diff --git a/maintenance/tests/SearchUpdateTest.php b/maintenance/tests/SearchUpdateTest.php
new file mode 100644
index 00000000..d21319a4
--- /dev/null
+++ b/maintenance/tests/SearchUpdateTest.php
@@ -0,0 +1,103 @@
+<?php
+
+class DatabaseMock extends DatabaseBase {
+ function __construct( $server = false, $user = false, $password = false, $dbName = false,
+ $failFunction = false, $flags = 0, $tablePrefix = 'get from global' )
+ {
+ $this->mConn = true;
+ $this->mOpened = true;
+ }
+
+ function open( $server, $user, $password, $dbName ) { return true; }
+ function doQuery( $sql ) {}
+ function fetchObject( $res ) {}
+ function fetchRow( $res ) {}
+ function numRows( $res ) {}
+ function numFields( $res ) {}
+ function fieldName( $res, $n ) {}
+ function insertId() {}
+ function dataSeek( $res, $row ) {}
+ function lastErrno() { return 0; }
+ function lastError() { return ''; }
+ function affectedRows() {}
+ function fieldInfo( $table, $field ) {}
+ function strencode( $s ) {}
+ function getSoftwareLink() {}
+ function getServerVersion() {}
+ function getType() {}
+}
+
+class MockSearch extends SearchEngine {
+ public static $id;
+ public static $title;
+ public static $text;
+
+ public function __construct( $db ) {
+ }
+
+ public function update( $id, $title, $text ) {
+ self::$id = $id;
+ self::$title = $title;
+ self::$text = $text;
+ }
+}
+
+class SearchUpdateTest extends PHPUnit_Framework_TestCase {
+
+ function update( $text, $title = 'Test', $id = 1 ) {
+ $u = new SearchUpdate( $id, $title, $text );
+ $u->doUpdate();
+ return array( MockSearch::$title, MockSearch::$text );
+ }
+
+ function updateText( $text ) {
+ list( $title, $resultText ) = $this->update( $text );
+ $resultText = trim( $resultText ); // abstract from some implementation details
+ return $resultText;
+ }
+
+ function setUp() {
+ global $wgSearchType, $wgDBtype, $wgLBFactoryConf, $wgDBservers;
+ $wgSearchType = 'MockSearch';
+ $wgDBtype = 'mock';
+ $wgLBFactoryConf['class'] = 'LBFactory_Simple';
+ $wgDBservers = null;
+ LBFactory::destroyInstance();
+ }
+
+ function tearDown() {
+ LBFactory::destroyInstance();
+ }
+
+ function testUpdateText() {
+ $this->assertEquals(
+ 'test',
+ $this->updateText( '<div>TeSt</div>' ),
+ 'HTML stripped, text lowercased'
+ );
+
+ $this->assertEquals(
+ 'foo bar boz quux',
+ $this->updateText( <<<EOT
+<table style="color:red; font-size:100px">
+ <tr class="scary"><td><div>foo</div></td><tr>bar</td></tr>
+ <tr><td>boz</td><tr>quux</td></tr>
+</table>
+EOT
+ ), 'Stripping HTML tables' );
+
+ $this->assertEquals(
+ 'a b',
+ $this->updateText( 'a > b' ),
+ 'Handle unclosed tags'
+ );
+
+ $text = str_pad( "foo <barbarbar \n", 10000, 'x' );
+
+ $this->assertNotEquals(
+ '',
+ $this->updateText( $text ),
+ 'Bug 18609'
+ );
+ }
+}
diff --git a/maintenance/tests/SiteConfigurationTest.php b/maintenance/tests/SiteConfigurationTest.php
new file mode 100644
index 00000000..791b6fe5
--- /dev/null
+++ b/maintenance/tests/SiteConfigurationTest.php
@@ -0,0 +1,311 @@
+<?php
+
+function getSiteParams( $conf, $wiki ) {
+ $site = null;
+ $lang = null;
+ foreach( $conf->suffixes as $suffix ) {
+ if ( substr( $wiki, -strlen( $suffix ) ) == $suffix ) {
+ $site = $suffix;
+ $lang = substr( $wiki, 0, -strlen( $suffix ) );
+ break;
+ }
+ }
+ return array(
+ 'suffix' => $site,
+ 'lang' => $lang,
+ 'params' => array(
+ 'lang' => $lang,
+ 'site' => $site,
+ 'wiki' => $wiki,
+ ),
+ 'tags' => array( 'tag' ),
+ );
+}
+
+class SiteConfigurationTest extends PHPUnit_Framework_TestCase {
+ var $mConf;
+
+ function setUp() {
+ $this->mConf = new SiteConfiguration;
+
+ $this->mConf->suffixes = array( 'wiki' );
+ $this->mConf->wikis = array( 'enwiki', 'dewiki', 'frwiki' );
+ $this->mConf->settings = array(
+ 'simple' => array(
+ 'wiki' => 'wiki',
+ 'tag' => 'tag',
+ 'enwiki' => 'enwiki',
+ 'dewiki' => 'dewiki',
+ 'frwiki' => 'frwiki',
+ ),
+
+ 'fallback' => array(
+ 'default' => 'default',
+ 'wiki' => 'wiki',
+ 'tag' => 'tag',
+ ),
+
+ 'params' => array(
+ 'default' => '$lang $site $wiki',
+ ),
+
+ '+global' => array(
+ 'wiki' => array(
+ 'wiki' => 'wiki',
+ ),
+ 'tag' => array(
+ 'tag' => 'tag',
+ ),
+ 'enwiki' => array(
+ 'enwiki' => 'enwiki',
+ ),
+ 'dewiki' => array(
+ 'dewiki' => 'dewiki',
+ ),
+ 'frwiki' => array(
+ 'frwiki' => 'frwiki',
+ ),
+ ),
+
+ 'merge' => array(
+ '+wiki' => array(
+ 'wiki' => 'wiki',
+ ),
+ '+tag' => array(
+ 'tag' => 'tag',
+ ),
+ 'default' => array(
+ 'default' => 'default',
+ ),
+ '+enwiki' => array(
+ 'enwiki' => 'enwiki',
+ ),
+ '+dewiki' => array(
+ 'dewiki' => 'dewiki',
+ ),
+ '+frwiki' => array(
+ 'frwiki' => 'frwiki',
+ ),
+ ),
+ );
+
+ $GLOBALS['global'] = array( 'global' => 'global' );
+ }
+
+
+ function testSiteFromDB() {
+ $this->assertEquals(
+ array( 'wikipedia', 'en' ),
+ $this->mConf->siteFromDB( 'enwiki' ),
+ 'siteFromDB()'
+ );
+ $this->assertEquals(
+ array( 'wikipedia', '' ),
+ $this->mConf->siteFromDB( 'wiki' ),
+ 'siteFromDB() on a suffix'
+ );
+ $this->assertEquals(
+ array( null, null ),
+ $this->mConf->siteFromDB( 'wikien' ),
+ 'siteFromDB() on a non-existing wiki'
+ );
+
+ $this->mConf->suffixes = array( 'wiki', '' );
+ $this->assertEquals(
+ array( '', 'wikien' ),
+ $this->mConf->siteFromDB( 'wikien' ),
+ 'siteFromDB() on a non-existing wiki (2)'
+ );
+ }
+
+ function testGetLocalDatabases() {
+ $this->assertEquals(
+ array( 'enwiki', 'dewiki', 'frwiki' ),
+ $this->mConf->getLocalDatabases(),
+ 'getLocalDatabases()'
+ );
+ }
+
+ function testGet() {
+ $this->assertEquals(
+ 'enwiki',
+ $this->mConf->get( 'simple', 'enwiki', 'wiki' ),
+ 'get(): simple setting on an existing wiki'
+ );
+ $this->assertEquals(
+ 'dewiki',
+ $this->mConf->get( 'simple', 'dewiki', 'wiki' ),
+ 'get(): simple setting on an existing wiki (2)'
+ );
+ $this->assertEquals(
+ 'frwiki',
+ $this->mConf->get( 'simple', 'frwiki', 'wiki' ),
+ 'get(): simple setting on an existing wiki (3)'
+ );
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'simple', 'wiki', 'wiki' ),
+ 'get(): simple setting on an suffix'
+ );
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'simple', 'eswiki', 'wiki' ),
+ 'get(): simple setting on an non-existing wiki'
+ );
+
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'fallback', 'enwiki', 'wiki' ),
+ 'get(): fallback setting on an existing wiki'
+ );
+ $this->assertEquals(
+ 'tag',
+ $this->mConf->get( 'fallback', 'dewiki', 'wiki', array(), array( 'tag' ) ),
+ 'get(): fallback setting on an existing wiki (with wiki tag)'
+ );
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'fallback', 'wiki', 'wiki' ),
+ 'get(): fallback setting on an suffix'
+ );
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'fallback', 'wiki', 'wiki', array(), array( 'tag' ) ),
+ 'get(): fallback setting on an suffix (with wiki tag)'
+ );
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'fallback', 'eswiki', 'wiki' ),
+ 'get(): fallback setting on an non-existing wiki'
+ );
+ $this->assertEquals(
+ 'tag',
+ $this->mConf->get( 'fallback', 'eswiki', 'wiki', array(), array( 'tag' ) ),
+ 'get(): fallback setting on an non-existing wiki (with wiki tag)'
+ );
+
+ $common = array( 'wiki' => 'wiki', 'default' => 'default' );
+ $commonTag = array( 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' );
+ $this->assertEquals(
+ array( 'enwiki' => 'enwiki' ) + $common,
+ $this->mConf->get( 'merge', 'enwiki', 'wiki' ),
+ 'get(): merging setting on an existing wiki'
+ );
+ $this->assertEquals(
+ array( 'enwiki' => 'enwiki' ) + $commonTag,
+ $this->mConf->get( 'merge', 'enwiki', 'wiki', array(), array( 'tag' ) ),
+ 'get(): merging setting on an existing wiki (with tag)'
+ );
+ $this->assertEquals(
+ array( 'dewiki' => 'dewiki' ) + $common,
+ $this->mConf->get( 'merge', 'dewiki', 'wiki' ),
+ 'get(): merging setting on an existing wiki (2)'
+ );
+ $this->assertEquals(
+ array( 'dewiki' => 'dewiki' ) + $commonTag,
+ $this->mConf->get( 'merge', 'dewiki', 'wiki', array(), array( 'tag' ) ),
+ 'get(): merging setting on an existing wiki (2) (with tag)'
+ );
+ $this->assertEquals(
+ array( 'frwiki' => 'frwiki' ) + $common,
+ $this->mConf->get( 'merge', 'frwiki', 'wiki' ),
+ 'get(): merging setting on an existing wiki (3)'
+ );
+ $this->assertEquals(
+ array( 'frwiki' => 'frwiki' ) + $commonTag,
+ $this->mConf->get( 'merge', 'frwiki', 'wiki', array(), array( 'tag' ) ),
+ 'get(): merging setting on an existing wiki (3) (with tag)'
+ );
+ $this->assertEquals(
+ array( 'wiki' => 'wiki' ) + $common,
+ $this->mConf->get( 'merge', 'wiki', 'wiki' ),
+ 'get(): merging setting on an suffix'
+ );
+ $this->assertEquals(
+ array( 'wiki' => 'wiki' ) + $commonTag,
+ $this->mConf->get( 'merge', 'wiki', 'wiki', array(), array( 'tag' ) ),
+ 'get(): merging setting on an suffix (with tag)'
+ );
+ $this->assertEquals(
+ $common,
+ $this->mConf->get( 'merge', 'eswiki', 'wiki' ),
+ 'get(): merging setting on an non-existing wiki'
+ );
+ $this->assertEquals(
+ $commonTag,
+ $this->mConf->get( 'merge', 'eswiki', 'wiki', array(), array( 'tag' ) ),
+ 'get(): merging setting on an non-existing wiki (with tag)'
+ );
+ }
+
+ function testSiteFromDBWithCallback() {
+ $this->mConf->siteParamsCallback = 'getSiteParams';
+
+ $this->assertEquals(
+ array( 'wiki', 'en' ),
+ $this->mConf->siteFromDB( 'enwiki' ),
+ 'siteFromDB() with callback'
+ );
+ $this->assertEquals(
+ array( 'wiki', '' ),
+ $this->mConf->siteFromDB( 'wiki' ),
+ 'siteFromDB() with callback on a suffix'
+ );
+ $this->assertEquals(
+ array( null, null ),
+ $this->mConf->siteFromDB( 'wikien' ),
+ 'siteFromDB() with callback on a non-existing wiki'
+ );
+ }
+
+ function testParamReplacement() {
+ $this->mConf->siteParamsCallback = 'getSiteParams';
+
+ $this->assertEquals(
+ 'en wiki enwiki',
+ $this->mConf->get( 'params', 'enwiki', 'wiki' ),
+ 'get(): parameter replacement on an existing wiki'
+ );
+ $this->assertEquals(
+ 'de wiki dewiki',
+ $this->mConf->get( 'params', 'dewiki', 'wiki' ),
+ 'get(): parameter replacement on an existing wiki (2)'
+ );
+ $this->assertEquals(
+ 'fr wiki frwiki',
+ $this->mConf->get( 'params', 'frwiki', 'wiki' ),
+ 'get(): parameter replacement on an existing wiki (3)'
+ );
+ $this->assertEquals(
+ ' wiki wiki',
+ $this->mConf->get( 'params', 'wiki', 'wiki' ),
+ 'get(): parameter replacement on an suffix'
+ );
+ $this->assertEquals(
+ 'es wiki eswiki',
+ $this->mConf->get( 'params', 'eswiki', 'wiki' ),
+ 'get(): parameter replacement on an non-existing wiki'
+ );
+ }
+
+ function testGetAll() {
+ $this->mConf->siteParamsCallback = 'getSiteParams';
+
+ $getall = array(
+ 'simple' => 'enwiki',
+ 'fallback' => 'tag',
+ 'params' => 'en wiki enwiki',
+ 'global' => array( 'enwiki' => 'enwiki' ) + $GLOBALS['global'],
+ 'merge' => array( 'enwiki' => 'enwiki', 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' ),
+ );
+ $this->assertEquals( $getall, $this->mConf->getAll( 'enwiki' ), 'getAll()' );
+
+ $this->mConf->extractAllGlobals( 'enwiki', 'wiki' );
+
+ $this->assertEquals( $getall['simple'], $GLOBALS['simple'], 'extractAllGlobals(): simple setting' );
+ $this->assertEquals( $getall['fallback'], $GLOBALS['fallback'], 'extractAllGlobals(): fallback setting' );
+ $this->assertEquals( $getall['params'], $GLOBALS['params'], 'extractAllGlobals(): parameter replacement' );
+ $this->assertEquals( $getall['global'], $GLOBALS['global'], 'extractAllGlobals(): merging with global' );
+ $this->assertEquals( $getall['merge'], $GLOBALS['merge'], 'extractAllGlobals(): merging setting' );
+ }
+}
diff --git a/maintenance/tests/TimeAdjustTest.php b/maintenance/tests/TimeAdjustTest.php
new file mode 100644
index 00000000..bbd697bf
--- /dev/null
+++ b/maintenance/tests/TimeAdjustTest.php
@@ -0,0 +1,40 @@
+<?php
+
+class TimeAdjustTest extends PHPUnit_Framework_TestCase {
+
+ public function setUp() {
+ $this->iniSet( 'precision', 15 );
+ }
+
+ # Test offset usage for a given language::userAdjust
+ function testUserAdjust() {
+ global $wgLocalTZoffset, $wgContLang, $wgUser;
+
+ $wgContLang = $en = Language::factory( 'en' );
+
+ # Collection of parameters for Language_t_Offset.
+ # Format: date to be formatted, localTZoffset value, expected date
+ $userAdjust_tests = array(
+ array( 20061231235959, 0, 20061231235959 ),
+ array( 20061231235959, 5, 20070101000459 ),
+ array( 20061231235959, 15, 20070101001459 ),
+ array( 20061231235959, 60, 20070101005959 ),
+ array( 20061231235959, 90, 20070101012959 ),
+ array( 20061231235959, 120, 20070101015959 ),
+ array( 20061231235959, 540, 20070101085959 ),
+ array( 20061231235959, -5, 20061231235459 ),
+ array( 20061231235959, -30, 20061231232959 ),
+ array( 20061231235959, -60, 20061231225959 ),
+ );
+
+ foreach( $userAdjust_tests as $data ) {
+ $wgLocalTZoffset = $data[1];
+
+ $this->assertEquals(
+ strval( $data[2] ),
+ strval( $en->userAdjust( $data[0], '' ) ),
+ "User adjust {$data[0]} by {$data[1]} minutes should give {$data[2]}"
+ );
+ }
+ }
+}
diff --git a/maintenance/tests/TitleTest.php b/maintenance/tests/TitleTest.php
new file mode 100644
index 00000000..5b42c1c5
--- /dev/null
+++ b/maintenance/tests/TitleTest.php
@@ -0,0 +1,17 @@
+<?php
+
+class TitleTest extends PHPUnit_Framework_TestCase {
+
+ function testLegalChars() {
+ $titlechars = Title::legalChars();
+
+ foreach ( range( 1, 255 ) as $num ) {
+ $chr = chr( $num );
+ if ( strpos( "#[]{}<>|", $chr ) !== false || preg_match( "/[\\x00-\\x1f\\x7f]/", $chr ) ) {
+ $this->assertFalse( (bool)preg_match( "/[$titlechars]/", $chr ), "chr($num) = $chr is not a valid titlechar" );
+ } else {
+ $this->assertTrue( (bool)preg_match( "/[$titlechars]/", $chr ), "chr($num) = $chr is a valid titlechar" );
+ }
+ }
+ }
+}
diff --git a/maintenance/tests/XmlTest.php b/maintenance/tests/XmlTest.php
new file mode 100644
index 00000000..330e60c6
--- /dev/null
+++ b/maintenance/tests/XmlTest.php
@@ -0,0 +1,115 @@
+<?php
+
+class XmlTest extends PHPUnit_Framework_TestCase {
+
+ function testElementOpen() {
+ $this->assertEquals(
+ '<element>',
+ Xml::element( 'element', null, null ),
+ 'Opening element with no attributes'
+ );
+ }
+
+ function testElementEmpty() {
+ $this->assertEquals(
+ '<element />',
+ Xml::element( 'element', null, '' ),
+ 'Terminated empty element'
+ );
+ }
+
+ function testElementEscaping() {
+ $this->assertEquals(
+ '<element>hello &lt;there&gt; you &amp; you</element>',
+ Xml::element( 'element', null, 'hello <there> you & you' ),
+ 'Element with no attributes and content that needs escaping'
+ );
+ }
+
+ function testElementAttributes() {
+ $this->assertEquals(
+ '<element key="value" <>="&lt;&gt;">',
+ Xml::element( 'element', array( 'key' => 'value', '<>' => '<>' ), null ),
+ 'Element attributes, keys are not escaped'
+ );
+ }
+
+ function testOpenElement() {
+ $this->assertEquals(
+ '<element k="v">',
+ Xml::openElement( 'element', array( 'k' => 'v' ) ),
+ 'openElement() shortcut'
+ );
+ }
+
+ function testCloseElement() {
+ $this->assertEquals( '</element>', Xml::closeElement( 'element' ), 'closeElement() shortcut' );
+ }
+
+ #
+ # textarea
+ #
+ function testTextareaNoContent() {
+ $this->assertEquals(
+ '<textarea name="name" id="name" cols="40" rows="5"></textarea>',
+ Xml::textarea( 'name', '' ),
+ 'textarea() with not content'
+ );
+ }
+
+ function testTextareaAttribs() {
+ $this->assertEquals(
+ '<textarea name="name" id="name" cols="20" rows="10">&lt;txt&gt;</textarea>',
+ Xml::textarea( 'name', '<txt>', 20, 10 ),
+ 'textarea() with custom attribs'
+ );
+ }
+
+ #
+ # JS
+ #
+ function testEscapeJsStringSpecialChars() {
+ $this->assertEquals(
+ '\\\\\r\n',
+ Xml::escapeJsString( "\\\r\n" ),
+ 'escapeJsString() with special characters'
+ );
+ }
+
+ function testEncodeJsVarBoolean() {
+ $this->assertEquals(
+ 'true',
+ Xml::encodeJsVar( true ),
+ 'encodeJsVar() with boolean'
+ );
+ }
+
+ function testEncodeJsVarNull() {
+ $this->assertEquals(
+ 'null',
+ Xml::encodeJsVar( null ),
+ 'encodeJsVar() with null'
+ );
+ }
+
+ function testEncodeJsVarArray() {
+ $this->assertEquals(
+ '["a", 1]',
+ Xml::encodeJsVar( array( 'a', 1 ) ),
+ 'encodeJsVar() with array'
+ );
+ $this->assertEquals(
+ '{"a": "a", "b": 1}',
+ Xml::encodeJsVar( array( 'a' => 'a', 'b' => 1 ) ),
+ 'encodeJsVar() with associative array'
+ );
+ }
+
+ function testEncodeJsVarObject() {
+ $this->assertEquals(
+ '{"a": "a", "b": 1}',
+ Xml::encodeJsVar( (object)array( 'a' => 'a', 'b' => 1 ) ),
+ 'encodeJsVar() with object'
+ );
+ }
+}
diff --git a/maintenance/tests/bootstrap.php b/maintenance/tests/bootstrap.php
new file mode 100644
index 00000000..019bee07
--- /dev/null
+++ b/maintenance/tests/bootstrap.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Set up the MediaWiki environment when running tests with "phpunit" command
+ *
+ * Warning: this file is not included from global scope!
+ * @file
+ */
+
+global $wgCommandLineMode, $IP, $optionsWithArgs;
+$IP = dirname( dirname( dirname( __FILE__ ) ) );
+define( 'MW_PHPUNIT_TEST', true );
+
+require_once( "$IP/maintenance/commandLine.inc" );
+
diff --git a/maintenance/tests/phpunit.xml b/maintenance/tests/phpunit.xml
new file mode 100644
index 00000000..ce7d44f5
--- /dev/null
+++ b/maintenance/tests/phpunit.xml
@@ -0,0 +1,17 @@
+<!-- See http://www.phpunit.de/manual/3.3/en/appendixes.configuration.html -->
+<phpunit bootstrap="./bootstrap.php"
+ colors="false"
+ stopOnFailure="false">
+ <!-- convertErrorsToExceptions="true" -->
+ <!-- convertNoticesToExceptions="true" -->
+ <!-- convertWarningsToExceptions="true" -->
+ <testsuite name="MediaWiki Test Suite">
+ <directory>.</directory>
+ </testsuite>
+ <groups>
+ <exclude>
+ <group>Broken</group>
+ <group>Stub</group>
+ </exclude>
+ </groups>
+</phpunit> \ No newline at end of file
diff --git a/maintenance/tests/test-prefetch-current.xml b/maintenance/tests/test-prefetch-current.xml
new file mode 100644
index 00000000..a4c8bda3
--- /dev/null
+++ b/maintenance/tests/test-prefetch-current.xml
@@ -0,0 +1,75 @@
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.3/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.3/ http://www.mediawiki.org/xml/export-0.3.xsd" version="0.3" xml:lang="en">
+<siteinfo>
+ <sitename>DemoWiki</sitename>
+ <base>http://example.com/wiki/Main_Page</base>
+ <generator>MediaWiki 1.5.0</generator>
+ <case>first-letter</case>
+ <namespaces>
+ <namespace key="-2">Media</namespace>
+ <namespace key="-1">Special</namespace>
+ <namespace key="0"></namespace>
+ <namespace key="1">Talk</namespace>
+ <namespace key="2">User</namespace>
+ <namespace key="3">User talk</namespace>
+ <namespace key="4">DemoWiki</namespace>
+ <namespace key="5">DemoWIki talk</namespace>
+ <namespace key="6">Image</namespace>
+ <namespace key="7">Image talk</namespace>
+ <namespace key="8">MediaWiki</namespace>
+ <namespace key="9">MediaWiki talk</namespace>
+ <namespace key="10">Template</namespace>
+ <namespace key="11">Template talk</namespace>
+ <namespace key="12">Help</namespace>
+ <namespace key="13">Help talk</namespace>
+ <namespace key="14">Category</namespace>
+ <namespace key="15">Category talk</namespace>
+ </namespaces>
+</siteinfo>
+<page>
+ <title>First page</title>
+ <id>1</id>
+ <revision>
+ <id>1</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 1, rev 1</comment>
+ <text>page 1, rev 1</text>
+ </revision>
+ <revision>
+ <id>2</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 1, rev 2</comment>
+ <text>page 1, rev 2</text>
+ </revision>
+ <revision>
+ <id>4</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 1, rev 4</comment>
+ <text>page 1, rev 4</text>
+ </revision>
+</page>
+<page>
+ <title>Second page</title>
+ <id>2</id>
+ <revision>
+ <id>3</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 2, rev 3</comment>
+ <text>page 2, rev 3</text>
+ </revision>
+</page>
+<page>
+ <title>Third page</title>
+ <id>3</id>
+ <revision>
+ <id>5</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 3, rev 5</comment>
+ <text>page 3, rev 5</text>
+ </revision>
+</page>
+</mediawiki>
diff --git a/maintenance/tests/test-prefetch-previous.xml b/maintenance/tests/test-prefetch-previous.xml
new file mode 100644
index 00000000..95eb82dd
--- /dev/null
+++ b/maintenance/tests/test-prefetch-previous.xml
@@ -0,0 +1,57 @@
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.3/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.3/ http://www.mediawiki.org/xml/export-0.3.xsd" version="0.3" xml:lang="en">
+<siteinfo>
+ <sitename>DemoWiki</sitename>
+ <base>http://example.com/wiki/Main_Page</base>
+ <generator>MediaWiki 1.5.0</generator>
+ <case>first-letter</case>
+ <namespaces>
+ <namespace key="-2">Media</namespace>
+ <namespace key="-1">Special</namespace>
+ <namespace key="0"></namespace>
+ <namespace key="1">Talk</namespace>
+ <namespace key="2">User</namespace>
+ <namespace key="3">User talk</namespace>
+ <namespace key="4">DemoWiki</namespace>
+ <namespace key="5">DemoWIki talk</namespace>
+ <namespace key="6">Image</namespace>
+ <namespace key="7">Image talk</namespace>
+ <namespace key="8">MediaWiki</namespace>
+ <namespace key="9">MediaWiki talk</namespace>
+ <namespace key="10">Template</namespace>
+ <namespace key="11">Template talk</namespace>
+ <namespace key="12">Help</namespace>
+ <namespace key="13">Help talk</namespace>
+ <namespace key="14">Category</namespace>
+ <namespace key="15">Category talk</namespace>
+ </namespaces>
+</siteinfo>
+<page>
+ <title>First page</title>
+ <id>1</id>
+ <revision>
+ <id>1</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 1, rev 1</comment>
+ <text>page 1, rev 1</text>
+ </revision>
+ <revision>
+ <id>2</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 1, rev 2</comment>
+ <text>page 1, rev 2</text>
+ </revision>
+</page>
+<page>
+ <title>Second page</title>
+ <id>2</id>
+ <revision>
+ <id>3</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 2, rev 3</comment>
+ <text>page 2, rev 3</text>
+ </revision>
+</page>
+</mediawiki>
diff --git a/maintenance/tests/test-prefetch-stub.xml b/maintenance/tests/test-prefetch-stub.xml
new file mode 100644
index 00000000..59d43d2f
--- /dev/null
+++ b/maintenance/tests/test-prefetch-stub.xml
@@ -0,0 +1,75 @@
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.3/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.3/ http://www.mediawiki.org/xml/export-0.3.xsd" version="0.3" xml:lang="en">
+<siteinfo>
+ <sitename>DemoWiki</sitename>
+ <base>http://example.com/wiki/Main_Page</base>
+ <generator>MediaWiki 1.5.0</generator>
+ <case>first-letter</case>
+ <namespaces>
+ <namespace key="-2">Media</namespace>
+ <namespace key="-1">Special</namespace>
+ <namespace key="0"></namespace>
+ <namespace key="1">Talk</namespace>
+ <namespace key="2">User</namespace>
+ <namespace key="3">User talk</namespace>
+ <namespace key="4">DemoWiki</namespace>
+ <namespace key="5">DemoWIki talk</namespace>
+ <namespace key="6">Image</namespace>
+ <namespace key="7">Image talk</namespace>
+ <namespace key="8">MediaWiki</namespace>
+ <namespace key="9">MediaWiki talk</namespace>
+ <namespace key="10">Template</namespace>
+ <namespace key="11">Template talk</namespace>
+ <namespace key="12">Help</namespace>
+ <namespace key="13">Help talk</namespace>
+ <namespace key="14">Category</namespace>
+ <namespace key="15">Category talk</namespace>
+ </namespaces>
+</siteinfo>
+<page>
+ <title>First page</title>
+ <id>1</id>
+ <revision>
+ <id>1</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 1, rev 1</comment>
+ <text id="1" />
+ </revision>
+ <revision>
+ <id>2</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 1, rev 2</comment>
+ <text id="2" />
+ </revision>
+ <revision>
+ <id>4</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 1, rev 4</comment>
+ <text id="4" />
+ </revision>
+</page>
+<page>
+ <title>Second page</title>
+ <id>2</id>
+ <revision>
+ <id>3</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 2, rev 3</comment>
+ <text id="3" />
+ </revision>
+</page>
+<page>
+ <title>Third page</title>
+ <id>3</id>
+ <revision>
+ <id>5</id>
+ <timestamp>2001-01-15T12:00:00Z</timestamp>
+ <contributor><ip>10.0.0.1</ip></contributor>
+ <comment>page 3, rev 5</comment>
+ <text id="5" />
+ </revision>
+</page>
+</mediawiki>
diff --git a/maintenance/undelete.php b/maintenance/undelete.php
index b7b7df97..099ea88d 100644
--- a/maintenance/undelete.php
+++ b/maintenance/undelete.php
@@ -6,34 +6,35 @@
* @ingroup Maintenance
*/
-$usage = <<<EOT
-Undelete a page
-Usage: php undelete.php [-u <user>] [-r <reason>] <pagename>
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-EOT;
+class Undelete extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Undelete a page";
+ $this->addOption( 'u', 'The user to perform the undeletion', false, true );
+ $this->addOption( 'r', 'The reason to undelete', false, true );
+ $this->addArg( 'pagename', 'Page to undelete' );
+ }
-$optionsWithArgs = array( 'u', 'r' );
-require_once( 'commandLine.inc' );
+ public function execute() {
+ global $wgUser;
-$user = 'Command line script';
-$reason = '';
+ $user = $this->getOption( 'u', 'Command line script' );
+ $reason = $this->getOption( 'r', '' );
+ $pageName = $this->getArg();
-if ( isset( $options['u'] ) ) {
- $user = $options['u'];
+ $title = Title::newFromText( $pageName );
+ if ( !$title ) {
+ $this->error( "Invalid title", true );
+ }
+ $wgUser = User::newFromName( $user );
+ $archive = new PageArchive( $title );
+ $this->output( "Undeleting " . $title->getPrefixedDBkey() . '...' );
+ $archive->undelete( array(), $reason );
+ $this->output( "done\n" );
+ }
}
-if ( isset( $options['r'] ) ) {
- $reason = $options['r'];
-}
-$pageName = @$args[0];
-$title = Title::newFromText( $pageName );
-if ( !$title ) {
- echo $usage;
- exit( 1 );
-}
-$wgUser = User::newFromName( $user );
-$archive = new PageArchive( $title );
-echo "Undeleting " . $title->getPrefixedDBkey() . '...';
-$archive->undelete( array(), $reason );
-echo "done\n";
-
+$maintClass = "Undelete";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/update.php b/maintenance/update.php
index 3f484137..5977a4c1 100644
--- a/maintenance/update.php
+++ b/maintenance/update.php
@@ -1,5 +1,4 @@
<?php
-require_once 'counter.php';
/**
* Run all updaters.
*
@@ -11,49 +10,32 @@ require_once 'counter.php';
*/
/** */
+define( 'MW_CMDLINE_CALLBACK', 'wfSetupUpdateScript' );
$wgUseMasterForMaintenance = true;
-$options = array( 'quick', 'nopurge' );
-require_once( "commandLine.inc" );
-require_once( "updaters.inc" );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
+require( "updaters.inc" );
+
$wgTitle = Title::newFromText( "MediaWiki database updater" );
-$dbclass = 'Database' . ucfirst( $wgDBtype ) ;
echo( "MediaWiki {$wgVersion} Updater\n\n" );
-install_version_checks();
-
-# Do a pre-emptive check to ensure we've got credentials supplied
-# We can't, at this stage, check them, but we can detect their absence,
-# which seems to cause most of the problems people whinge about
-if( !isset( $wgDBadminuser ) || !isset( $wgDBadminpassword ) ) {
- echo( "No superuser credentials could be found. Please provide the details\n" );
- echo( "of a user with appropriate permissions to update the database. See\n" );
- echo( "AdminSettings.sample for more details.\n\n" );
- exit();
+if( !isset( $options['skip-compat-checks'] ) ) {
+ install_version_checks();
+} else {
+ print "Skipping compatibility checks, proceed at your own risk (Ctrl+C to abort)\n";
+ wfCountdown(5);
}
# Attempt to connect to the database as a privileged user
# This will vomit up an error if there are permissions problems
-$wgDatabase = new $dbclass( $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname, 1 );
-
-if( !$wgDatabase->isOpen() ) {
- # Appears to have failed
- echo( "A connection to the database could not be established. Check the\n" );
- echo( "values of \$wgDBadminuser and \$wgDBadminpassword.\n" );
- exit();
-}
+$wgDatabase = wfGetDB( DB_MASTER );
print "Going to run database updates for ".wfWikiID()."\n";
print "Depending on the size of your database this may take a while!\n";
if( !isset( $options['quick'] ) ) {
- print "Abort with control-c in the next five seconds... ";
-
- for ($i = 6; $i >= 1;) {
- print_c($i, --$i);
- sleep(1);
- }
- echo "\n";
+ print "Abort with control-c in the next five seconds (skip this countdown with --quick) ... ";
+ wfCountDown( 5 );
}
$shared = isset( $options['doshared'] );
@@ -63,4 +45,16 @@ do_all_updates( $shared, $purge );
print "Done.\n";
-
+function wfSetupUpdateScript() {
+ global $wgLocalisationCacheConf;
+
+ # Don't try to access the database
+ # This needs to be disabled early since extensions will try to use the l10n
+ # cache from $wgExtensionSetupFunctions (bug 20471)
+ $wgLocalisationCacheConf = array(
+ 'class' => 'LocalisationCache',
+ 'storeClass' => 'LCStore_Null',
+ 'storeDirectory' => false,
+ 'manualRecache' => false,
+ );
+}
diff --git a/maintenance/updateArticleCount.inc.php b/maintenance/updateArticleCount.inc.php
deleted file mode 100644
index a847a2ed..00000000
--- a/maintenance/updateArticleCount.inc.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-/**
- * Support class for the updateArticleCount.php maintenance script
- *
- * @file
- * @ingroup Maintenance
- * @author Rob Church <robchur@gmail.com>
- */
-
-class ArticleCounter {
-
- var $dbr;
- var $namespaces;
-
- function ArticleCounter() {
- global $wgContentNamespaces;
- $this->namespaces = $wgContentNamespaces;
- $this->dbr = wfGetDB( DB_SLAVE );
- }
-
- /**
- * Produce a comma-delimited set of namespaces
- * Includes paranoia
- *
- * @return string
- */
- function makeNsSet() {
- foreach( $this->namespaces as $namespace )
- $namespaces[] = intval( $namespace );
- return implode( ', ', $namespaces );
- }
-
- /**
- * Produce SQL for the query
- *
- * @return string
- */
- function makeSql() {
- list( $page, $pagelinks ) = $this->dbr->tableNamesN( 'page', 'pagelinks' );
- $nsset = $this->makeNsSet();
- return "SELECT COUNT(DISTINCT page_namespace, page_title) AS pagecount " .
- "FROM $page, $pagelinks " .
- "WHERE pl_from=page_id and page_namespace IN ( $nsset ) " .
- "AND page_is_redirect = 0 AND page_len > 0";
- }
-
- /**
- * Count the number of valid content pages in the wiki
- *
- * @return mixed Integer, or false if there's a problem
- */
- function count() {
- $res = $this->dbr->query( $this->makeSql(), __METHOD__ );
- $row = $this->dbr->fetchObject( $res );
- $this->dbr->freeResult( $res );
- return $row->pagecount;
- }
-
-}
-
-
diff --git a/maintenance/updateArticleCount.php b/maintenance/updateArticleCount.php
index a24984a8..e6818458 100644
--- a/maintenance/updateArticleCount.php
+++ b/maintenance/updateArticleCount.php
@@ -3,38 +3,99 @@
* Maintenance script to provide a better count of the number of articles
* and update the site statistics table, if desired
*
- * @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
+ *
* @ingroup Maintenance
* @author Rob Church <robchur@gmail.com>
*/
-$options = array( 'update', 'help' );
-require_once( 'commandLine.inc' );
-require_once( 'updateArticleCount.inc.php' );
-echo( "Update Article Count\n\n" );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-if( isset( $options['help'] ) && $options['help'] ) {
- echo( "Usage: php updateArticleCount.php [--update]\n\n" );
- echo( "--update : Update site statistics table\n" );
- exit( 0 );
-}
+class UpdateArticleCount extends Maintenance {
+
+ // Content namespaces
+ private $namespaces;
+
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Count of the number of articles and update the site statistics table";
+ $this->addOption( 'update', 'Update the site_stats table with the new count' );
+ }
+
+ public function execute() {
+ global $wgContentNamespaces;
+ $this->namespaces = $wgContentNamespaces;
+ $this->output( "Counting articles..." );
+ $result = $this->count();
+
+ if( $result !== false ) {
+ $this->output( "found {$result}.\n" );
+ 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__ );
+ $this->output( "done.\n" );
+ } else {
+ $this->output( "To update the site statistics table, run the script with the --update option.\n" );
+ }
+ } else {
+ $this->output( "failed.\n" );
+ }
+ }
+
+ /**
+ * Produce a comma-delimited set of namespaces
+ * Includes paranoia
+ *
+ * @return string
+ */
+ private function makeNsSet() {
+ foreach( $this->namespaces as $namespace )
+ $namespaces[] = intval( $namespace );
+ return implode( ', ', $namespaces );
+ }
+
+ /**
+ * Produce SQL for the query
+ *
+ * @param $dbr Database handle
+ * @return string
+ */
+ private function makeSql( $dbr ) {
+ list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' );
+ $nsset = $this->makeNsSet();
+ return "SELECT COUNT(DISTINCT page_id) AS pagecount " .
+ "FROM $page, $pagelinks " .
+ "WHERE pl_from=page_id and page_namespace IN ( $nsset ) " .
+ "AND page_is_redirect = 0 AND page_len > 0";
+ }
-echo( "Counting articles..." );
-$counter = new ArticleCounter();
-$result = $counter->count();
-
-if( $result !== false ) {
- echo( "found {$result}.\n" );
- if( isset( $options['update'] ) && $options['update'] ) {
- echo( "Updating site statistics table... " );
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'site_stats', array( 'ss_good_articles' => $result ), array( 'ss_row_id' => 1 ), __METHOD__ );
- echo( "done.\n" );
- } else {
- echo( "To update the site statistics table, run the script with the --update option.\n" );
+ /**
+ * Count the number of valid content pages in the wiki
+ *
+ * @return mixed Integer, or false if there's a problem
+ */
+ private function count() {
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->query( $this->makeSql( $dbr ), __METHOD__ );
+ $row = $dbr->fetchObject( $res );
+ $dbr->freeResult( $res );
+ return $row ? $row->pagecount : false;
}
-} else {
- echo( "failed.\n" );
}
-echo( "\n" );
+$maintClass = "UpdateArticleCount";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/updateRestrictions.php b/maintenance/updateRestrictions.php
index 59879eaa..80e98fd0 100644
--- a/maintenance/updateRestrictions.php
+++ b/maintenance/updateRestrictions.php
@@ -5,88 +5,106 @@
* schema change. All remaining page_restriction column values are moved
* to the new table.
*
- * @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
+ *
* @ingroup Maintenance
*/
-define( 'BATCH_SIZE', 100 );
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-require_once 'commandLine.inc';
-
-$db =& wfGetDB( DB_MASTER );
-if ( !$db->tableExists( 'page_restrictions' ) ) {
- echo "page_restrictions does not exist\n";
- exit( 1 );
-}
+class UpdateRestrictions extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Updates page_restrictions table from old page_restriction column";
+ $this->setBatchSize( 100 );
+ }
-migrate_page_restrictions( $db );
+ public function execute() {
+ $db = wfGetDB( DB_MASTER );
+ if( !$db->tableExists( 'page_restrictions' ) ) {
+ $this->error( "page_restrictions table does not exist", true );
+ }
-function migrate_page_restrictions( $db ) {
- $start = $db->selectField( 'page', 'MIN(page_id)', false, __FUNCTION__ );
- if( !$start ) {
- die("Nothing to do.\n");
- }
- $end = $db->selectField( 'page', 'MAX(page_id)', false, __FUNCTION__ );
+ $start = $db->selectField( 'page', 'MIN(page_id)', false, __METHOD__ );
+ if( !$start ) {
+ $this->error( "Nothing to do.", true );
+ }
+ $end = $db->selectField( 'page', 'MAX(page_id)', false, __METHOD__ );
- # Do remaining chunk
- $end += BATCH_SIZE - 1;
- $blockStart = $start;
- $blockEnd = $start + BATCH_SIZE - 1;
- $encodedExpiry = 'infinity';
- while( $blockEnd <= $end ) {
- echo "...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, __FUNCTION__ );
- $batch = array();
- while( $row = $db->fetchObject( $res ) ) {
- $oldRestrictions = array();
- foreach( explode( ':', trim( $row->page_restrictions ) ) as $restrict ) {
- $temp = explode( '=', trim( $restrict ) );
- // Make sure we are not settings restrictions to ""
- if( count($temp) == 1 && $temp[0] ) {
- // old old format should be treated as edit/move restriction
- $oldRestrictions["edit"] = trim( $temp[0] );
- $oldRestrictions["move"] = trim( $temp[0] );
- } else if( $temp[1] ) {
- $oldRestrictions[$temp[0]] = trim( $temp[1] );
+ # Do remaining chunk
+ $end += $this->mBatchSize - 1;
+ $blockStart = $start;
+ $blockEnd = $start + $this->mBatchSize - 1;
+ $encodedExpiry = 'infinity';
+ 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__ );
+ $batch = array();
+ foreach( $res as $row ) {
+ $oldRestrictions = array();
+ foreach( explode( ':', trim( $row->page_restrictions ) ) as $restrict ) {
+ $temp = explode( '=', trim( $restrict ) );
+ // Make sure we are not settings restrictions to ""
+ if( count($temp) == 1 && $temp[0] ) {
+ // old old format should be treated as edit/move restriction
+ $oldRestrictions["edit"] = trim( $temp[0] );
+ $oldRestrictions["move"] = trim( $temp[0] );
+ } else if( $temp[1] ) {
+ $oldRestrictions[$temp[0]] = trim( $temp[1] );
+ }
+ }
+ # Clear invalid columns
+ if( $row->page_namespace == NS_MEDIAWIKI ) {
+ $db->update( 'page', array( 'page_restrictions' => '' ),
+ array( 'page_id' => $row->page_id ), __FUNCTION__ );
+ $this->output( "...removed dead page_restrictions column for page {$row->page_id}\n" );
+ }
+ # Update restrictions table
+ foreach( $oldRestrictions as $action => $restrictions ) {
+ $batch[] = array(
+ 'pr_page' => $row->page_id,
+ 'pr_type' => $action,
+ 'pr_level' => $restrictions,
+ 'pr_cascade' => 0,
+ 'pr_expiry' => $encodedExpiry
+ );
}
}
- # Clear invalid columns
- if( $row->page_namespace == NS_MEDIAWIKI ) {
- $db->update( 'page', array( 'page_restrictions' => '' ),
- array( 'page_id' => $row->page_id ), __FUNCTION__ );
- echo "...removed dead page_restrictions column for page {$row->page_id}\n";
- }
- # Update restrictions table
- foreach( $oldRestrictions as $action => $restrictions ) {
- $batch[] = array(
- 'pr_page' => $row->page_id,
- 'pr_type' => $action,
- 'pr_level' => $restrictions,
- 'pr_cascade' => 0,
- 'pr_expiry' => $encodedExpiry
- );
- }
- }
- # We use insert() and not replace() as Article.php replaces
- # page_restrictions with '' when protected in the restrictions table
- if ( count( $batch ) ) {
- $ok = $db->deadlockLoop( array( $db, 'insert' ), 'page_restrictions',
- $batch, __FUNCTION__, array( 'IGNORE' ) );
- if( !$ok ) {
- throw new MWException( "Deadlock loop failed wtf :(" );
+ # We use insert() and not replace() as Article.php replaces
+ # page_restrictions with '' when protected in the restrictions table
+ if ( count( $batch ) ) {
+ $ok = $db->deadlockLoop( array( $db, 'insert' ), 'page_restrictions',
+ $batch, __FUNCTION__, array( 'IGNORE' ) );
+ if( !$ok ) {
+ throw new MWException( "Deadlock loop failed wtf :(" );
+ }
}
+ $blockStart += $this->mBatchSize - 1;
+ $blockEnd += $this->mBatchSize - 1;
+ wfWaitForSlaves( 5 );
}
- $blockStart += BATCH_SIZE - 1;
- $blockEnd += BATCH_SIZE - 1;
- wfWaitForSlaves( 5 );
+ $this->output( "...removing dead rows from page_restrictions\n" );
+ // 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) );
+ $this->output( "...Done!\n" );
}
- echo "...removing dead rows from page_restrictions\n";
- // 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) );
- echo "...Done!\n";
}
-
+$maintClass = "UpdateRestrictions";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/updateSearchIndex.inc b/maintenance/updateSearchIndex.inc
deleted file mode 100644
index 0cac4508..00000000
--- a/maintenance/updateSearchIndex.inc
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-/**
- * @file
- * @ingroup Maintenance
- */
-
-/** */
-function updateSearchIndex( $start, $end, $maxLockTime, $quiet ) {
- global $wgQuiet;
- global $wgDisableSearchUpdate;
-
- $fname = "updateSearchIndex";
-
- $wgQuiet = $quiet;
- $wgDisableSearchUpdate = false;
-
- $dbw = wfGetDB( DB_MASTER );
- $recentchanges = $dbw->tableName( 'recentchanges' );
-
- output( "Updating searchindex between $start and $end\n" );
-
- # Select entries from recentchanges which are on top and between the specified times
- $start = $dbw->strencode( $start );
- $end = $dbw->strencode( $end );
-
- $page = $dbw->tableName( 'page' );
- $sql = "SELECT rc_cur_id,rc_type,rc_moved_to_ns,rc_moved_to_title FROM $recentchanges
- JOIN $page ON rc_cur_id=page_id AND rc_this_oldid=page_latest
- WHERE rc_timestamp BETWEEN '$start' AND '$end'
- ";
- $res = $dbw->query( $sql, $fname );
-
-
- # Lock searchindex
- if ( $maxLockTime ) {
- output( " --- Waiting for lock ---" );
- lockSearchindex( $dbw );
- $lockTime = time();
- output( "\n" );
- }
-
- # Loop through the results and do a search update
- while ( $row = $dbw->fetchObject( $res ) ) {
- # Allow reads to be processed
- if ( $maxLockTime && time() > $lockTime + $maxLockTime ) {
- output( " --- Relocking ---" );
- relockSearchindex( $dbw );
- $lockTime = time();
- output( "\n" );
- }
- if ( $row->rc_type == RC_LOG ) {
- continue;
- } elseif ( $row->rc_type == RC_MOVE || $row->rc_type == RC_MOVE_OVER_REDIRECT ) {
- # Rename searchindex entry
- $titleObj = Title::makeTitle( $row->rc_moved_to_ns, $row->rc_moved_to_title );
- $title = $titleObj->getPrefixedDBkey();
- output( "$title..." );
- $u = new SearchUpdate( $row->rc_cur_id, $title, false );
- output( "\n" );
- } else {
- // Get current revision
- $rev = Revision::loadFromPageId( $dbw, $row->rc_cur_id );
- if( $rev ) {
- $titleObj = $rev->getTitle();
- $title = $titleObj->getPrefixedDBkey();
- output( $title );
- # Update searchindex
- $u = new SearchUpdate( $row->rc_cur_id, $titleObj->getText(), $rev->getText() );
- $u->doUpdate();
- output( "\n" );
- }
- }
- }
-
- # Unlock searchindex
- if ( $maxLockTime ) {
- output( " --- Unlocking --" );
- unlockSearchindex( $dbw );
- output( "\n" );
- }
- output( "Done\n" );
-}
-
-function lockSearchindex( &$db ) {
- $write = array( 'searchindex' );
- $read = array( 'page', 'revision', 'text', 'interwiki' );
- $items = array();
-
- foreach( $write as $table ) {
- $items[] = $db->tableName( $table ) . ' LOW_PRIORITY WRITE';
- }
- foreach( $read as $table ) {
- $items[] = $db->tableName( $table ) . ' READ';
- }
- $sql = "LOCK TABLES " . implode( ',', $items );
- $db->query( $sql, 'updateSearchIndex.inc ' . __METHOD__ );
-}
-
-function unlockSearchindex( &$db ) {
- $db->query( "UNLOCK TABLES", 'updateSearchIndex.inc ' . __METHOD__ );
-}
-
-# Unlock and lock again
-# Since the lock is low-priority, queued reads will be able to complete
-function relockSearchindex( &$db ) {
- unlockSearchindex( $db );
- lockSearchindex( $db );
-}
-
-function output( $text ) {
- global $wgQuiet;
- if ( !$wgQuiet ) {
- print $text;
- }
-}
diff --git a/maintenance/updateSearchIndex.php b/maintenance/updateSearchIndex.php
index f51617bb..152ce1b6 100644
--- a/maintenance/updateSearchIndex.php
+++ b/maintenance/updateSearchIndex.php
@@ -5,53 +5,169 @@
* Usage: php updateSearchIndex.php [-s START] [-e END] [-p POSFILE] [-l LOCKTIME] [-q]
* Where START is the starting timestamp
* END is the ending timestamp
- * POSFILE is a file to load timestamps from and save them to, searchUpdate.pos by default
- * LOCKTIME is how long the searchindex and cur tables will be locked for
+ * POSFILE is a file to load timestamps from and save them to, searchUpdate.WIKI_ID.pos by default
+ * LOCKTIME is how long the searchindex and revision tables will be locked for
* -q means quiet
*
- * @file
+ * 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
+ *
* @ingroup Maintenance
*/
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-/** */
-$optionsWithArgs = array( 's', 'e', 'p' );
+class UpdateSearchIndex extends Maintenance {
-require_once( 'commandLine.inc' );
-require_once( 'updateSearchIndex.inc' );
-
-if ( isset( $options['p'] ) ) {
- $posFile = $options['p'];
-} else {
- $posFile = 'searchUpdate.pos';
-}
+ public function __construct() {
+ parent::__construct();
+ $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 );
+ }
-if ( isset( $options['e'] ) ) {
- $end = $options['e'];
-} else {
- $end = wfTimestampNow();
-}
+ public function getDbType() {
+ return Maintenance::DB_ADMIN;
+ }
-if ( isset( $options['s'] ) ) {
- $start = $options['s'];
-} else {
- $start = @file_get_contents( $posFile );
- if ( !$start ) {
- $start = wfTimestamp( TS_MW, time() - 86400 );
+ public function execute() {
+ $posFile = $this->getOption( 'p', 'searchUpdate.' . wfWikiId() . '.pos' );
+ $end = $this->getOption( 'e', wfTimestampNow() );
+ if ( $this->hasOption( 's' ) ) {
+ $start = $this->getOption('s');
+ } elseif( is_readable( 'searchUpdate.pos' ) ) {
+ # B/c to the old position file name which was hardcoded
+ # We can safely delete the file when we're done though.
+ $start = file_get_contents( 'searchUpdate.pos' );
+ unlink( 'searchUpdate.pos' );
+ } else {
+ $start = @file_get_contents( $posFile );
+ if ( !$start ) {
+ $start = wfTimestamp( TS_MW, time() - 86400 );
+ }
+ }
+ $lockTime = $this->getOption( 'l', 20 );
+
+ $this->doUpdateSearchIndex( $start, $end, $lockTime );
+ $file = fopen( $posFile, 'w' );
+ fwrite( $file, $end );
+ fclose( $file );
}
-}
+
+ private function doUpdateSearchIndex( $start, $end, $maxLockTime ) {
+ global $wgDisableSearchUpdate;
-if ( isset( $options['l'] ) ) {
- $lockTime = $options['l'];
-} else {
- $lockTime = 20;
-}
+ $wgDisableSearchUpdate = false;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $recentchanges = $dbw->tableName( 'recentchanges' );
-$quiet = (bool)(@$options['q']);
+ $this->output( "Updating searchindex between $start and $end\n" );
-updateSearchIndex( $start, $end, $lockTime, $quiet );
+ # Select entries from recentchanges which are on top and between the specified times
+ $start = $dbw->timestamp( $start );
+ $end = $dbw->timestamp( $end );
-$file = fopen( $posFile, 'w' );
-fwrite( $file, $end );
-fclose( $file );
+ $page = $dbw->tableName( 'page' );
+ $sql = "SELECT rc_cur_id,rc_type,rc_moved_to_ns,rc_moved_to_title FROM $recentchanges
+ JOIN $page ON rc_cur_id=page_id AND rc_this_oldid=page_latest
+ WHERE rc_timestamp BETWEEN '$start' AND '$end'
+ ";
+ $res = $dbw->query( $sql, __METHOD__ );
+ # Lock searchindex
+ if ( $maxLockTime ) {
+ $this->output( " --- Waiting for lock ---" );
+ $this->lockSearchindex( $dbw );
+ $lockTime = time();
+ $this->output( "\n" );
+ }
+
+ # Loop through the results and do a search update
+ foreach ( $res as $row ) {
+ # Allow reads to be processed
+ if ( $maxLockTime && time() > $lockTime + $maxLockTime ) {
+ $this->output( " --- Relocking ---" );
+ $this->relockSearchindex( $dbw );
+ $lockTime = time();
+ $this->output( "\n" );
+ }
+ if ( $row->rc_type == RC_LOG ) {
+ continue;
+ } elseif ( $row->rc_type == RC_MOVE || $row->rc_type == RC_MOVE_OVER_REDIRECT ) {
+ # Rename searchindex entry
+ $titleObj = Title::makeTitle( $row->rc_moved_to_ns, $row->rc_moved_to_title );
+ $title = $titleObj->getPrefixedDBkey();
+ $this->output( "$title..." );
+ $u = new SearchUpdate( $row->rc_cur_id, $title, false );
+ $this->output( "\n" );
+ } else {
+ // Get current revision
+ $rev = Revision::loadFromPageId( $dbw, $row->rc_cur_id );
+ if( $rev ) {
+ $titleObj = $rev->getTitle();
+ $title = $titleObj->getPrefixedDBkey();
+ $this->output( $title );
+ # Update searchindex
+ $u = new SearchUpdate( $row->rc_cur_id, $titleObj->getText(), $rev->getText() );
+ $u->doUpdate();
+ $this->output( "\n" );
+ }
+ }
+ }
+
+ # Unlock searchindex
+ if ( $maxLockTime ) {
+ $this->output( " --- Unlocking --" );
+ $this->unlockSearchindex( $dbw );
+ $this->output( "\n" );
+ }
+ $this->output( "Done\n" );
+ }
+
+ /**
+ * Lock the search index
+ * @param &$db Database object
+ */
+ private function lockSearchindex( &$db ) {
+ $write = array( 'searchindex' );
+ $read = array( 'page', 'revision', 'text', 'interwiki' );
+ $db->lockTables( $read, $write, 'updateSearchIndex.php ' . __METHOD__ );
+ }
+
+ /**
+ * Unlock the tables
+ * @param &$db Database object
+ */
+ private function unlockSearchindex( &$db ) {
+ $db->unlockTables( 'updateSearchIndex.php ' . __METHOD__ );
+ }
+
+ /**
+ * Unlock and lock again
+ * Since the lock is low-priority, queued reads will be able to complete
+ * @param &$db Database object
+ */
+ private function relockSearchindex( &$db ) {
+ $this->unlockSearchindex( $db );
+ $this->lockSearchindex( $db );
+ }
+}
+
+$maintClass = "UpdateSearchIndex";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/updateSpecialPages.php b/maintenance/updateSpecialPages.php
index 3eaa6205..aaad3714 100644
--- a/maintenance/updateSpecialPages.php
+++ b/maintenance/updateSpecialPages.php
@@ -3,117 +3,136 @@
* Run this script periodically if you have miser mode enabled, to refresh the
* caches
*
- * @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
+ *
* @ingroup Maintenance
*/
-$options = array('only','help');
+
+require_once( dirname(__FILE__) . '/Maintenance.php' );
-require_once( 'commandLine.inc' );
+class UpdateSpecialPages extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( 'list', 'List special page names' );
+ $this->addOption( 'only', 'Only update "page". Ex: --only=BrokenRedirects', false, true );
+ $this->addOption( 'override', 'Also update pages that have updates disabled' );
+ }
-require_once( "$IP/includes/SpecialPage.php" );
-require_once( "$IP/includes/QueryPage.php" );
+ public function execute() {
+ global $IP, $wgOut, $wgSpecialPageCacheUpdates, $wgQueryPages, $wgQueryCacheLimit, $wgDisableQueryPageUpdate;
+ $wgOut->disable();
+ $dbw = wfGetDB( DB_MASTER );
-if(@$options['help']) {
- print "usage:updateSpecialPages.php [--help] [--only=page]\n";
- print " --help : this help message\n";
- print " --list : list special pages names\n";
- print " --only=page : only update 'page'. Ex: --only=BrokenRedirects\n";
- print " --override : update even pages which have had updates disabled\n";
- wfDie();
-}
+ foreach( $wgSpecialPageCacheUpdates as $special => $call ) {
+ if( !is_callable($call) ) {
+ $this->error( "Uncallable function $call!" );
+ continue;
+ }
+ $t1 = explode( ' ', microtime() );
+ call_user_func( $call, $dbw );
+ $t2 = explode( ' ', microtime() );
+ $this->output( sprintf( '%-30s ', $special ) );
+ $elapsed = ($t2[0] - $t1[0]) + ($t2[1] - $t1[1]);
+ $hours = intval( $elapsed / 3600 );
+ $minutes = intval( $elapsed % 3600 / 60 );
+ $seconds = $elapsed - $hours * 3600 - $minutes * 60;
+ if ( $hours ) {
+ $this->output( $hours . 'h ' );
+ }
+ if ( $minutes ) {
+ $this->output( $minutes . 'm ' );
+ }
+ $this->output( sprintf( "completed in %.2fs\n", $seconds ) );
+ # Wait for the slave to catch up
+ wfWaitForSlaves( 5 );
+ }
-$wgOut->disable();
-$dbw = wfGetDB( DB_MASTER );
+ // This is needed to initialise $wgQueryPages
+ require_once( "$IP/includes/QueryPage.php" );
-foreach( $wgSpecialPageCacheUpdates as $special => $call ) {
- if( !is_callable($call) ) {
- print "Uncallable function $call!\n";
- continue;
- }
- $t1 = explode( ' ', microtime() );
- call_user_func( $call, $dbw );
- $t2 = explode( ' ', microtime() );
- printf( '%-30s ', $special );
- $elapsed = ($t2[0] - $t1[0]) + ($t2[1] - $t1[1]);
- $hours = intval( $elapsed / 3600 );
- $minutes = intval( $elapsed % 3600 / 60 );
- $seconds = $elapsed - $hours * 3600 - $minutes * 60;
- if ( $hours ) {
- print $hours . 'h ';
- }
- if ( $minutes ) {
- print $minutes . 'm ';
- }
- printf( "completed in %.2fs\n", $seconds );
- # Wait for the slave to catch up
- wfWaitForSlaves( 5 );
-}
+ foreach( $wgQueryPages as $page ) {
+ @list( $class, $special, $limit ) = $page;
-foreach( $wgQueryPages as $page ) {
- @list( $class, $special, $limit ) = $page;
+ # --list : just show the name of pages
+ if( $this->hasOption('list') ) {
+ $this->output( "$special\n" );
+ continue;
+ }
- # --list : just show the name of pages
- if( @$options['list'] ) {
- print "$special\n";
- continue;
- }
+ if ( !$this->hasOption('override') && $wgDisableQueryPageUpdate && in_array( $special, $wgDisableQueryPageUpdate ) ) {
+ $this->output( sprintf( "%-30s disabled\n", $special ) );
+ continue;
+ }
- if ( !isset( $options['override'] ) && $wgDisableQueryPageUpdate && in_array( $special, $wgDisableQueryPageUpdate ) ) {
- printf("%-30s disabled\n", $special);
- continue;
- }
+ $specialObj = SpecialPage::getPage( $special );
+ if ( !$specialObj ) {
+ $this->output( "No such special page: $special\n" );
+ exit;
+ }
+ if ( !class_exists( $class ) ) {
+ $file = $specialObj->getFile();
+ require_once( $file );
+ }
+ $queryPage = new $class;
- $specialObj = SpecialPage::getPage( $special );
- if ( !$specialObj ) {
- print "No such special page: $special\n";
- exit;
- }
- if ( !class_exists( $class ) ) {
- $file = $specialObj->getFile();
- require_once( $file );
- }
- $queryPage = new $class;
+ if( !$this->hasOption('only') || $this->getOption('only') == $queryPage->getName() ) {
+ $this->output( sprintf( '%-30s ', $special ) );
+ if ( $queryPage->isExpensive() ) {
+ $t1 = explode( ' ', microtime() );
+ # Do the query
+ $num = $queryPage->recache( $limit === null ? $wgQueryCacheLimit : $limit );
+ $t2 = explode( ' ', microtime() );
+ if ( $num === false ) {
+ $this->output( "FAILED: database error\n" );
+ } else {
+ $this->output( "got $num rows in " );
- if( !isset($options['only']) or $options['only'] == $queryPage->getName() ) {
- printf( '%-30s ', $special );
- if ( $queryPage->isExpensive() ) {
- $t1 = explode( ' ', microtime() );
- # Do the query
- $num = $queryPage->recache( $limit === null ? $wgQueryCacheLimit : $limit );
- $t2 = explode( ' ', microtime() );
- if ( $num === false ) {
- print "FAILED: database error\n";
- } else {
- print "got $num rows in ";
-
- $elapsed = ($t2[0] - $t1[0]) + ($t2[1] - $t1[1]);
- $hours = intval( $elapsed / 3600 );
- $minutes = intval( $elapsed % 3600 / 60 );
- $seconds = $elapsed - $hours * 3600 - $minutes * 60;
- if ( $hours ) {
- print $hours . 'h ';
- }
- if ( $minutes ) {
- print $minutes . 'm ';
+ $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 ) );
+ }
+ # Reopen any connections that have closed
+ if ( !wfGetLB()->pingAll()) {
+ $this->output( "\n" );
+ do {
+ $this->error( "Connection failed, reconnecting in 10 seconds..." );
+ sleep( 10 );
+ } while ( !wfGetLB()->pingAll() );
+ $this->output( "Reconnected\n\n" );
+ } else {
+ # Commit the results
+ $dbw->commit();
+ }
+ # Wait for the slave to catch up
+ wfWaitForSlaves( 5 );
+ } else {
+ $this->output( "cheap, skipped\n" );
}
- printf( "%.2fs\n", $seconds );
- }
- # Reopen any connections that have closed
- if ( !wfGetLB()->pingAll()) {
- print "\n";
- do {
- print "Connection failed, reconnecting in 10 seconds...\n";
- sleep(10);
- } while ( !wfGetLB()->pingAll() );
- print "Reconnected\n\n";
- } else {
- # Commit the results
- $dbw->immediateCommit();
- }
- # Wait for the slave to catch up
- wfWaitForSlaves( 5 );
- } else {
- print "cheap, skipped\n";
+ }
}
}
}
+
+$maintClass = "UpdateSpecialPages";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/updaters.inc b/maintenance/updaters.inc
index 36ac5e37..81c260fc 100644
--- a/maintenance/updaters.inc
+++ b/maintenance/updaters.inc
@@ -11,12 +11,11 @@ if ( !defined( 'MEDIAWIKI' ) ) {
require_once 'convertLinks.inc';
require_once 'userDupes.inc';
-require_once 'deleteDefaultMessages.php';
# Extension updates
require_once( "$IP/includes/Hooks.php" );
/**
- * List of update functions to call for each DB type, in sequence. First item
+ * List of update functions to call for each DB type, in sequence. First item
* is function name, rest are parameters to pass.
*/
$wgUpdates = array(
@@ -30,7 +29,7 @@ $wgUpdates = array(
// do_linkscc_update obsolete
array( 'add_table', 'hitcounter', 'patch-hitcounter.sql' ),
array( 'add_field', 'recentchanges', 'rc_type', 'patch-rc_type.sql' ),
-
+
// 1.3
array( 'add_field', 'user', 'user_real_name', 'patch-user-realname.sql' ),
array( 'add_table', 'querycache', 'patch-querycache.sql' ),
@@ -40,7 +39,7 @@ $wgUpdates = array(
array( 'do_old_links_update' ),
array( 'fix_ancient_imagelinks' ),
array( 'add_field', 'recentchanges', 'rc_ip', 'patch-rc_ip.sql' ),
-
+
// 1.4
array( 'do_image_name_unique_update' ),
array( 'add_field', 'recentchanges', 'rc_id', 'patch-rc_id.sql' ),
@@ -54,7 +53,7 @@ $wgUpdates = array(
array( 'do_watchlist_update' ),
array( 'do_user_update' ),
// do_copy_newtalk_to_watchlist obsolete
-
+
// 1.5
array( 'do_schema_restructuring' ),
array( 'add_field', 'logging', 'log_params', 'patch-log_params.sql' ),
@@ -79,7 +78,7 @@ $wgUpdates = array(
array( 'add_table', 'transcache', 'patch-transcache.sql' ),
array( 'add_field', 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ),
array( 'add_table', 'trackbacks', 'patch-trackbacks.sql' ),
-
+
// 1.6
array( 'do_watchlist_null' ),
// do_image_index_update obsolete
@@ -96,16 +95,16 @@ $wgUpdates = array(
array( 'add_table', 'filearchive', 'patch-filearchive.sql' ),
array( 'add_field', 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ),
array( 'do_rc_indices_update' ),
-
+
// 1.9
array( 'add_field', 'user', 'user_newpass_time', 'patch-user_newpass_time.sql' ),
array( 'add_table', 'redirect', 'patch-redirect.sql' ),
- array( 'add_table', 'querycachetwo', 'patch-querycachetwo.sql' ),
+ array( 'add_table', 'querycachetwo', 'patch-querycachetwo.sql' ),
array( 'add_field', 'ipblocks', 'ipb_enable_autoblock', 'patch-ipb_optional_autoblock.sql' ),
array( 'do_backlinking_indices_update' ),
array( 'add_field', 'recentchanges', 'rc_old_len', 'patch-rc_len.sql' ),
array( 'add_field', 'user', 'user_editcount', 'patch-user_editcount.sql' ),
-
+
// 1.10
array( 'do_restrictions_update' ),
array( 'add_field', 'logging', 'log_id', 'patch-log_id.sql' ),
@@ -118,7 +117,7 @@ $wgUpdates = array(
array( 'add_field', 'ipblocks', 'ipb_deleted', 'patch-ipb_deleted.sql' ),
array( 'add_field', 'filearchive', 'fa_deleted', 'patch-fa_deleted.sql' ),
array( 'add_field', 'archive', 'ar_len', 'patch-ar_len.sql' ),
-
+
// 1.11
array( 'add_field', 'ipblocks', 'ipb_block_email', 'patch-ipb_emailban.sql' ),
array( 'do_categorylinks_indices_update' ),
@@ -131,7 +130,7 @@ $wgUpdates = array(
// 1.12
array( 'add_table', 'protected_titles', 'patch-protected_titles.sql' ),
-
+
// 1.13
array( 'add_field', 'ipblocks', 'ipb_by_text', 'patch-ipb_by_text.sql' ),
array( 'add_table', 'page_props', 'patch-page_props.sql' ),
@@ -144,30 +143,60 @@ $wgUpdates = array(
array( 'check_bin', 'protected_titles', 'pt_title', 'patch-pt_title-encoding.sql', ),
array( 'maybe_do_profiling_memory_update' ),
array( 'do_filearchive_indices_update' ),
-
+
// 1.14
- array( 'add_field', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
+ array( 'add_field', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
array( 'do_active_users_init' ),
- array( 'add_field', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
- array( 'do_unique_pl_tl_il' ),
+ array( 'add_field', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
// 1.15
- array( 'add_table', 'change_tag', 'patch-change_tag.sql' ),
- array( 'add_table', 'tag_summary', 'patch-change_tag.sql' ),
- array( 'add_table', 'valid_tag', 'patch-change_tag.sql' ),
+ array( 'do_unique_pl_tl_il' ),
+ array( 'add_table', 'change_tag', 'patch-change_tag.sql' ),
+ array( 'add_table', 'tag_summary', 'patch-change_tag.sql' ),
+ array( 'add_table', 'valid_tag', 'patch-change_tag.sql' ),
+
+ // 1.16
+ array( 'add_table', 'user_properties', 'patch-user_properties.sql' ),
+ array( 'add_table', 'log_search', 'patch-log_search.sql' ),
+ array( 'do_log_search_population' ),
+ array( 'add_field', 'logging', 'log_user_text', 'patch-log_user_text.sql' ),
+ array( 'add_table', 'l10n_cache', 'patch-l10n_cache.sql' ),
+ array( 'add_table', 'external_user', 'patch-external_user.sql' ),
+ array( 'add_index', 'log_search', 'ls_field_val', 'patch-log_search-rename-index.sql' ),
+ array( 'add_index', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ),
+ array( 'add_field', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ),
+ array( 'do_update_transcache_field' ),
+ // A field changed name mid-release cycle, so fix it for anyone using
+ // trunk
+ array( 'rename_eu_wiki_id' ),
+ array( 'do_update_mime_minor_field' ),
),
'sqlite' => array(
// 1.14
- array( 'add_field', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
+ array( 'add_field', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
array( 'do_active_users_init' ),
- array( 'add_field', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
+ array( 'add_field', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
array( 'sqlite_initial_indexes' ),
// 1.15
- array( 'add_table', 'change_tag', 'patch-change_tag.sql' ),
- array( 'add_table', 'tag_summary', 'patch-change_tag.sql' ),
- array( 'add_table', 'valid_tag', 'patch-change_tag.sql' ),
+ array( 'add_table', 'change_tag', 'patch-change_tag.sql' ),
+ array( 'add_table', 'tag_summary', 'patch-change_tag.sql' ),
+ array( 'add_table', 'valid_tag', 'patch-change_tag.sql' ),
+
+ // 1.16
+ array( 'add_table', 'user_properties', 'patch-user_properties.sql' ),
+ array( 'add_table', 'log_search', 'patch-log_search.sql' ),
+ array( 'do_log_search_population' ),
+ array( 'add_field', 'logging', 'log_user_text', 'patch-log_user_text.sql' ),
+ array( 'add_table', 'l10n_cache', 'patch-l10n_cache.sql' ),
+ array( 'add_table', 'external_user', 'patch-external_user.sql' ),
+ array( 'add_index', 'log_search', 'ls_field_val', 'patch-log_search-rename-index.sql' ),
+ array( 'add_index', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ),
+ array( 'add_field', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ),
+ array( 'do_update_transcache_field' ),
+ // version-independent searchindex setup, added in 1.16
+ array( 'sqlite_setup_searchindex' ),
),
);
@@ -176,8 +205,10 @@ $wgUpdates = array(
# $wgDBtype should be checked to specifiy the proper file
$wgExtNewTables = array(); // table, dir
$wgExtNewFields = array(); // table, column, dir
-$wgExtPGNewFields = array(); // table, column attributes; for PostgreSQL
+$wgExtPGNewFields = array(); // table, column, column attributes; for PostgreSQL
+$wgExtPGAlteredFields = array(); // table, column, new type, conversion method; for PostgreSQL
$wgExtNewIndexes = array(); // table, index, dir
+$wgExtModifiedFields = array(); //table, index, dir
# Helper function: check if the given key is present in the updatelog table.
# Obviously, only use this for updates that occur after the updatelog table was
@@ -200,7 +231,7 @@ function rename_table( $from, $to, $patch ) {
wfOut( "...can't move table $from to $to, $to already exists.\n" );
} else {
wfOut( "Moving table $from to $to..." );
- dbsource( archive($patch), $wgDatabase );
+ $wgDatabase->sourceFile( archive($patch) );
wfOut( "ok\n" );
}
} else {
@@ -217,14 +248,33 @@ function add_table( $name, $patch, $fullpath=false ) {
} else {
wfOut( "Creating $name table..." );
if( $fullpath ) {
- dbsource( $patch, $wgDatabase );
+ $wgDatabase->sourceFile( $patch );
+ } else {
+ $wgDatabase->sourceFile( archive($patch) );
+ }
+ wfOut( "ok\n" );
+ }
+}
+
+function modify_field($table, $field, $patch, $fullpath=false){
+ global $wgDatabase;
+ if ( !$wgDatabase->tableExists( $table ) ) {
+ wfOut( "...$table table does not exist, skipping modify field patch\n" );
+ } elseif (! $wgDatabase->fieldExists( $table, $field ) ) {
+ wfOut( "...$field field does not exist in $table table, skipping modify field patch\n" );
+ } else {
+ wfOut( "Modifying $field field of table $table..." );
+ if( $fullpath ) {
+ $wgDatabase->sourceFile( $patch );
} else {
- dbsource( archive($patch), $wgDatabase );
+ $wgDatabase->sourceFile( archive($patch) );
}
wfOut( "ok\n" );
}
}
+
+
function add_field( $table, $field, $patch, $fullpath=false ) {
global $wgDatabase;
if ( !$wgDatabase->tableExists( $table ) ) {
@@ -234,9 +284,9 @@ function add_field( $table, $field, $patch, $fullpath=false ) {
} else {
wfOut( "Adding $field field to table $table..." );
if( $fullpath ) {
- dbsource( $patch, $wgDatabase );
+ $wgDatabase->sourceFile( $patch );
} else {
- dbsource( archive($patch), $wgDatabase );
+ $wgDatabase->sourceFile( archive($patch) );
}
wfOut( "ok\n" );
}
@@ -249,9 +299,9 @@ function add_index( $table, $index, $patch, $fullpath=false ) {
} else {
wfOut( "Adding $index key to table $table... " );
if( $fullpath ) {
- dbsource( $patch, $wgDatabase );
+ $wgDatabase->sourceFile( $patch );
} else {
- dbsource( archive($patch), $wgDatabase );
+ $wgDatabase->sourceFile( archive($patch) );
}
wfOut( "ok\n" );
}
@@ -265,10 +315,10 @@ function do_interwiki_update() {
return true;
}
wfOut( "Creating interwiki table: " );
- dbsource( archive("patch-interwiki.sql") );
+ $wgDatabase->sourceFile( archive("patch-interwiki.sql") );
wfOut( "ok\n" );
wfOut( "Adding default interwiki definitions: " );
- dbsource( "$IP/maintenance/interwiki.sql" );
+ $wgDatabase->sourceFile( "$IP/maintenance/interwiki.sql" );
wfOut( "ok\n" );
}
@@ -278,7 +328,7 @@ function do_index_update() {
$meta = $wgDatabase->fieldInfo( "recentchanges", "rc_timestamp" );
if( !$meta->isMultipleKey() ) {
wfOut( "Updating indexes to 20031107: " );
- dbsource( archive("patch-indexes.sql") );
+ $wgDatabase->sourceFile( archive("patch-indexes.sql") );
wfOut( "ok\n" );
return true;
}
@@ -292,7 +342,7 @@ function do_image_index_update() {
$meta = $wgDatabase->fieldInfo( "image", "img_major_mime" );
if( !$meta->isMultipleKey() ) {
wfOut( "Updating indexes to 20050912: " );
- dbsource( archive("patch-mimesearch-indexes.sql") );
+ $wgDatabase->sourceFile( archive("patch-mimesearch-indexes.sql") );
wfOut( "ok\n" );
return true;
}
@@ -306,7 +356,7 @@ function do_image_name_unique_update() {
wfOut( "...image primary key already set.\n" );
} else {
wfOut( "Making img_name the primary key... " );
- dbsource( archive("patch-image_name_primary.sql"), $wgDatabase );
+ $wgDatabase->sourceFile( archive("patch-image_name_primary.sql") );
wfOut( "ok\n" );
}
}
@@ -317,7 +367,7 @@ function do_logging_timestamp_index() {
wfOut( "...timestamp key on logging already exists.\n" );
} else {
wfOut( "Adding timestamp key on logging table... " );
- dbsource( archive("patch-logging-times-index.sql"), $wgDatabase );
+ $wgDatabase->sourceFile( archive("patch-logging-times-index.sql") );
wfOut( "ok\n" );
}
}
@@ -328,7 +378,7 @@ function do_archive_user_index() {
wfOut( "...usertext,timestamp key on archive already exists.\n" );
} else {
wfOut( "Adding usertext,timestamp key on archive table... " );
- dbsource( archive("patch-archive-user-index.sql"), $wgDatabase );
+ $wgDatabase->sourceFile( archive("patch-archive-user-index.sql") );
wfOut( "ok\n" );
}
}
@@ -339,7 +389,7 @@ function do_image_user_index() {
wfOut( "...usertext,timestamp key on image already exists.\n" );
} else {
wfOut( "Adding usertext,timestamp key on image table... " );
- dbsource( archive("patch-image-user-index.sql"), $wgDatabase );
+ $wgDatabase->sourceFile( archive("patch-image-user-index.sql") );
wfOut( "ok\n" );
}
}
@@ -350,7 +400,7 @@ function do_oldimage_user_index() {
wfOut( "...usertext,timestamp key on oldimage already exists.\n" );
} else {
wfOut( "Adding usertext,timestamp key on oldimage table... " );
- dbsource( archive("patch-oldimage-user-index.sql"), $wgDatabase );
+ $wgDatabase->sourceFile( archive("patch-oldimage-user-index.sql") );
wfOut( "ok\n" );
}
}
@@ -363,7 +413,7 @@ function do_watchlist_update() {
} else {
wfOut( "Adding wl_notificationtimestamp field for email notification management." );
/* ALTER TABLE watchlist ADD (wl_notificationtimestamp varchar(14) binary NOT NULL default '0'); */
- dbsource( archive( 'patch-email-notification.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-email-notification.sql' ) );
wfOut( "ok\n" );
}
# Check if we need to add talk page rows to the watchlist
@@ -373,7 +423,7 @@ function do_watchlist_update() {
wfOut( "Adding missing watchlist talk page rows... " );
flush();
- $wgDatabase->insertSelect( 'watchlist', 'watchlist',
+ $wgDatabase->insertSelect( 'watchlist', 'watchlist',
array(
'wl_user' => 'wl_user',
'wl_namespace' => 'wl_namespace | 1',
@@ -388,7 +438,7 @@ function do_watchlist_update() {
function do_copy_newtalk_to_watchlist() {
global $wgDatabase;
- global $wgCommandLineMode; # this needs to be saved while getID() and getName() are called
+ global $wgCommandLineMode; # this needs to be saved while getID() and getName() are called
$res = $wgDatabase->safeQuery( 'SELECT user_id, user_ip FROM !',
$wgDatabase->tableName( 'user_newtalk' ) );
@@ -402,10 +452,10 @@ function do_copy_newtalk_to_watchlist() {
if ($user->isIP($wluser->user_ip)) { # do only if it really looks like an IP number (double checked)
$wgDatabase->replace( 'watchlist',
array(array('wl_user','wl_namespace', 'wl_title', 'wl_notificationtimestamp' )),
- array('wl_user' => 0,
- 'wl_namespace' => NS_USER_TALK,
- 'wl_title' => $wluser->user_ip,
- 'wl_notificationtimestamp' => '19700101000000'
+ array('wl_user' => 0,
+ 'wl_namespace' => NS_USER_TALK,
+ 'wl_title' => $wluser->user_ip,
+ 'wl_notificationtimestamp' => '19700101000000'
), 'updaters.inc::do_watchlist_update2'
);
}
@@ -413,10 +463,10 @@ function do_copy_newtalk_to_watchlist() {
$user->setID($wluser->user_id);
$wgDatabase->replace( 'watchlist',
array(array('wl_user','wl_namespace', 'wl_title', 'wl_notificationtimestamp' )),
- array('wl_user' => $user->getID(),
- 'wl_namespace' => NS_USER_TALK,
- 'wl_title' => $user->getName(),
- 'wl_notificationtimestamp' => '19700101000000'
+ array('wl_user' => $user->getID(),
+ 'wl_namespace' => NS_USER_TALK,
+ 'wl_title' => $user->getName(),
+ 'wl_notificationtimestamp' => '19700101000000'
), 'updaters.inc::do_watchlist_update3'
);
}
@@ -429,7 +479,7 @@ function do_user_update() {
global $wgDatabase;
if( $wgDatabase->fieldExists( 'user', 'user_emailauthenticationtimestamp' ) ) {
wfOut( "User table contains old email authentication field. Dropping... " );
- dbsource( archive( 'patch-email-authentication.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-email-authentication.sql' ) );
wfOut( "ok\n" );
} else {
wfOut( "...user table does not contain old email authentication field.\n" );
@@ -453,7 +503,7 @@ function check_bin( $table, $field, $patchFile ) {
wfOut( "$table table has correct $field encoding.\n" );
} else {
wfOut( "Fixing $field encoding on $table table... " );
- dbsource( archive( $patchFile ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( $patchFile ) );
wfOut( "ok\n" );
}
}
@@ -530,40 +580,40 @@ function do_schema_restructuring() {
wfOut( wfTimestamp( TS_DB ) );
wfOut( "......Creating tables.\n" );
$wgDatabase->query("CREATE TABLE $page (
- page_id int(8) unsigned NOT NULL auto_increment,
- page_namespace int NOT NULL,
- page_title varchar(255) binary NOT NULL,
- page_restrictions tinyblob NOT NULL,
- page_counter bigint(20) unsigned NOT NULL default '0',
- page_is_redirect tinyint(1) unsigned NOT NULL default '0',
- page_is_new tinyint(1) unsigned NOT NULL default '0',
- page_random real unsigned NOT NULL,
- page_touched char(14) binary NOT NULL default '',
- page_latest int(8) unsigned NOT NULL,
- page_len int(8) unsigned NOT NULL,
-
- PRIMARY KEY page_id (page_id),
- UNIQUE INDEX name_title (page_namespace,page_title),
- INDEX (page_random),
- INDEX (page_len)
+ page_id int(8) unsigned NOT NULL auto_increment,
+ page_namespace int NOT NULL,
+ page_title varchar(255) binary NOT NULL,
+ page_restrictions tinyblob NOT NULL,
+ page_counter bigint(20) unsigned NOT NULL default '0',
+ page_is_redirect tinyint(1) unsigned NOT NULL default '0',
+ page_is_new tinyint(1) unsigned NOT NULL default '0',
+ page_random real unsigned NOT NULL,
+ page_touched char(14) binary NOT NULL default '',
+ page_latest int(8) unsigned NOT NULL,
+ page_len int(8) unsigned NOT NULL,
+
+ PRIMARY KEY page_id (page_id),
+ UNIQUE INDEX name_title (page_namespace,page_title),
+ INDEX (page_random),
+ INDEX (page_len)
) ENGINE=InnoDB", $fname );
$wgDatabase->query("CREATE TABLE $revision (
- rev_id int(8) unsigned NOT NULL auto_increment,
- rev_page int(8) unsigned NOT NULL,
- rev_comment tinyblob NOT NULL,
- rev_user int(5) unsigned NOT NULL default '0',
- rev_user_text varchar(255) binary NOT NULL default '',
- rev_timestamp char(14) binary NOT NULL default '',
- rev_minor_edit tinyint(1) unsigned NOT NULL default '0',
+ rev_id int(8) unsigned NOT NULL auto_increment,
+ rev_page int(8) unsigned NOT NULL,
+ rev_comment tinyblob NOT NULL,
+ rev_user int(5) unsigned NOT NULL default '0',
+ rev_user_text varchar(255) binary NOT NULL default '',
+ rev_timestamp char(14) binary NOT NULL default '',
+ rev_minor_edit tinyint(1) unsigned NOT NULL default '0',
rev_deleted tinyint(1) unsigned NOT NULL default '0',
rev_len int(8) unsigned,
rev_parent_id int(8) unsigned default NULL,
- 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)
+ 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)
) ENGINE=InnoDB", $fname );
wfOut( wfTimestamp( TS_DB ) );
@@ -590,26 +640,26 @@ function do_schema_restructuring() {
$cur_flags = "''";
}
$wgDatabase->query( "INSERT INTO $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, $cur_flags
- FROM $cur", $fname );
+ 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, $cur_flags
+ FROM $cur", $fname );
wfOut( wfTimestamp( TS_DB ) );
wfOut( "......Setting up revision table.\n" );
$wgDatabase->query( "INSERT INTO $revision (rev_id, rev_page, rev_comment, rev_user, rev_user_text, rev_timestamp,
- rev_minor_edit)
+ rev_minor_edit)
SELECT old_id, cur_id, old_comment, old_user, old_user_text,
- old_timestamp, old_minor_edit
+ old_timestamp, old_minor_edit
FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title", $fname );
wfOut( wfTimestamp( TS_DB ) );
wfOut( "......Setting up page table.\n" );
$wgDatabase->query( "INSERT INTO $page (page_id, page_namespace, page_title, page_restrictions, page_counter,
- page_is_redirect, page_is_new, page_random, page_touched, page_latest, page_len)
- SELECT cur_id, cur_namespace, cur_title, cur_restrictions, cur_counter, cur_is_redirect, cur_is_new,
- cur_random, cur_touched, rev_id, LENGTH(cur_text)
- FROM $cur,$revision
- WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold}", $fname );
+ page_is_redirect, page_is_new, page_random, page_touched, page_latest, page_len)
+ SELECT cur_id, cur_namespace, cur_title, cur_restrictions, cur_counter, cur_is_redirect, cur_is_new,
+ cur_random, cur_touched, rev_id, LENGTH(cur_text)
+ FROM $cur,$revision
+ WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold}", $fname );
wfOut( wfTimestamp( TS_DB ) );
wfOut( "......Unlocking tables.\n" );
@@ -628,7 +678,7 @@ function do_inverse_timestamp() {
global $wgDatabase;
if( $wgDatabase->fieldExists( 'revision', 'inverse_timestamp' ) ) {
wfOut( "Removing revision.inverse_timestamp and fixing indexes... " );
- dbsource( archive( 'patch-inverse_timestamp.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-inverse_timestamp.sql' ) );
wfOut( "ok\n" );
} else {
wfOut( "revision timestamp indexes already up to 2005-03-13\n" );
@@ -641,7 +691,7 @@ function do_text_id() {
wfOut( "...rev_text_id already in place.\n" );
} else {
wfOut( "Adding rev_text_id field... " );
- dbsource( archive( 'patch-rev_text_id.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-rev_text_id.sql' ) );
wfOut( "ok\n" );
}
}
@@ -690,7 +740,7 @@ function do_pagelinks_update() {
wfOut( "...already have pagelinks table.\n" );
} else {
wfOut( "Converting links and brokenlinks tables to pagelinks... " );
- dbsource( archive( 'patch-pagelinks.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-pagelinks.sql' ) );
wfOut( "ok\n" );
flush();
@@ -729,7 +779,7 @@ function do_drop_img_type() {
if( $wgDatabase->fieldExists( 'image', 'img_type' ) ) {
wfOut( "Dropping unused img_type field in image table... " );
- dbsource( archive( 'patch-drop_img_type.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-drop_img_type.sql' ) );
wfOut( "ok\n" );
} else {
wfOut( "No img_type field in image table; Good.\n" );
@@ -751,7 +801,7 @@ function fix_ancient_imagelinks() {
if ( $info && $info->type() === 'string' ) {
wfOut( "Fixing ancient broken imagelinks table.\n" );
wfOut( "NOTE: you will have to run maintenance/refreshLinks.php after this.\n" );
- dbsource( archive( 'patch-fix-il_from.sql' ) );
+ $wgDatabase->sourceFile( archive( 'patch-fix-il_from.sql' ) );
wfOut( "ok\n" );
} else {
wfOut( "...il_from OK\n" );
@@ -768,7 +818,7 @@ function do_user_unique_update() {
wfOut( "WARNING: This next step will probably fail due to unfixed duplicates...\n" );
}
wfOut( "Adding unique index on user_name... " );
- dbsource( archive( 'patch-user_nameindex.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-user_nameindex.sql' ) );
wfOut( "ok\n" );
}
}
@@ -783,13 +833,13 @@ function do_user_groups_update() {
}
wfOut( "Adding user_groups table... " );
- dbsource( archive( 'patch-user_groups.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-user_groups.sql' ) );
wfOut( "ok\n" );
if( !$wgDatabase->tableExists( 'user_rights' ) ) {
if( $wgDatabase->fieldExists( 'user', 'user_rights' ) ) {
wfOut( "Upgrading from a 1.3 or older database? Breaking out user_rights for conversion..." );
- dbsource( archive( 'patch-user_rights.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-user_rights.sql' ) );
wfOut( "ok\n" );
} else {
wfOut( "*** WARNING: couldn't locate user_rights table or field for upgrade.\n" );
@@ -835,7 +885,7 @@ function do_user_groups_reformat() {
wfOut( "ok\n" );
wfOut( "Re-adding fresh user_groups table... " );
- dbsource( archive( 'patch-user_groups.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-user_groups.sql' ) );
wfOut( "ok\n" );
wfOut( "***\n" );
@@ -856,7 +906,7 @@ function do_watchlist_null() {
if( !$info->nullable() ) {
wfOut( "Making wl_notificationtimestamp nullable... " );
- dbsource( archive( 'patch-watchlist-null.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-watchlist-null.sql' ) );
wfOut( "ok\n" );
} else {
wfOut( "...wl_notificationtimestamp is already nullable.\n" );
@@ -888,7 +938,7 @@ function do_templatelinks_update() {
return;
}
wfOut( "Creating templatelinks table...\n" );
- dbsource( archive('patch-templatelinks.sql'), $wgDatabase );
+ $wgDatabase->sourceFile( archive('patch-templatelinks.sql') );
wfOut( "Populating...\n" );
if ( isset( $wgLoadBalancer ) && $wgLoadBalancer->getServerCount() > 1 ) {
// Slow, replication-friendly update
@@ -939,12 +989,12 @@ function do_rc_indices_update() {
'rc_ns_usertext' => 'patch-recentchanges-utindex.sql',
'rc_user_text' => 'patch-rc_user_text-index.sql',
);
-
+
foreach( $indexes as $index => $patch ) {
$info = $wgDatabase->indexInfo( 'recentchanges', $index, __METHOD__ );
if( !$info ) {
wfOut( "...index `{$index}` not found; adding..." );
- dbsource( archive( $patch ) );
+ $wgDatabase->sourceFile( archive( $patch ) );
wfOut( "done.\n" );
} else {
wfOut( "...index `{$index}` seems ok.\n" );
@@ -969,21 +1019,23 @@ function index_has_field($table, $index, $field) {
}
function do_backlinking_indices_update() {
+ global $wgDatabase;
wfOut( "Checking for backlinking indices...\n" );
if (!index_has_field('pagelinks', 'pl_namespace', 'pl_from') ||
!index_has_field('templatelinks', 'tl_namespace', 'tl_from') ||
!index_has_field('imagelinks', 'il_to', 'il_from'))
- {
- dbsource( archive( 'patch-backlinkindexes.sql' ) );
+ {
+ $wgDatabase->sourceFile( archive( 'patch-backlinkindexes.sql' ) );
wfOut( "...backlinking indices updated\n" );
}
}
function do_categorylinks_indices_update() {
+ global $wgDatabase;
wfOut( "Checking for categorylinks indices...\n" );
if (!index_has_field('categorylinks', 'cl_sortkey', 'cl_from'))
- {
- dbsource( archive( 'patch-categorylinksindex.sql' ) );
+ {
+ $wgDatabase->sourceFile( archive( 'patch-categorylinksindex.sql' ) );
wfOut( "...categorylinks indices updated\n" );
}
}
@@ -993,8 +1045,8 @@ function do_filearchive_indices_update() {
wfOut( "Checking filearchive indices...\n" );
$info = $wgDatabase->indexInfo( 'filearchive', 'fa_user_timestamp', __METHOD__ );
if ( !$info )
- {
- dbsource( archive( 'patch-filearhive-user-index.sql' ) );
+ {
+ $wgDatabase->sourceFile( archive( 'patch-filearchive-user-index.sql' ) );
wfOut( "...filearchive indices updated\n" );
}
}
@@ -1007,7 +1059,7 @@ function maybe_do_profiling_memory_update() {
wfOut( "profiling table has pf_memory field.\n" );
} else {
wfOut( "Adding pf_memory field to table profiling..." );
- dbsource( archive( 'patch-profiling-memory.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-profiling-memory.sql' ) );
wfOut( "ok\n" );
}
}
@@ -1025,21 +1077,18 @@ function do_stats_init() {
wfOut( "ok.\n" );
return;
}
-
- global $IP;
- require_once "$IP/maintenance/initStats.inc";
- wfInitStats();
+ SiteStatsInit::doAllAndCommit( false );
}
function do_active_users_init() {
global $wgDatabase;
$activeUsers = $wgDatabase->selectField( 'site_stats', 'ss_active_users', false, __METHOD__ );
if( $activeUsers == -1 ) {
- $activeUsers = $wgDatabase->selectField( 'recentchanges',
+ $activeUsers = $wgDatabase->selectField( 'recentchanges',
'COUNT( DISTINCT rc_user_text )',
- array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers'" ), __METHOD__
+ array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers'" ), __METHOD__
);
- $wgDatabase->update( 'site_stats',
+ $wgDatabase->update( 'site_stats',
array( 'ss_active_users' => intval($activeUsers) ),
array( 'ss_row_id' => 1 ), __METHOD__, array( 'LIMIT' => 1 )
);
@@ -1057,7 +1106,7 @@ function purge_cache() {
}
function do_all_updates( $shared = false, $purge = true ) {
- global $wgNewTables, $wgNewFields, $wgRenamedTables, $wgSharedDB, $wgSharedTables, $wgDatabase, $wgDBtype, $IP;
+ global $wgNewTables, $wgExtModifiedFields, $wgNewFields, $wgRenamedTables, $wgSharedDB, $wgSharedTables, $wgDatabase, $wgDBtype, $IP;
wfRunHooks('LoadExtensionSchemaUpdates');
@@ -1067,7 +1116,7 @@ function do_all_updates( $shared = false, $purge = true ) {
do_postgres_updates();
return;
}
-
+
# Run core updates in sequence...
global $wgUpdates;
if ( isset( $wgUpdates[$wgDBtype] ) ) {
@@ -1077,7 +1126,7 @@ function do_all_updates( $shared = false, $purge = true ) {
flush();
}
}
-
+
/// @fixme clean up this mess too!
global $wgExtNewTables, $wgExtNewFields, $wgExtNewIndexes;
# Add missing extension tables
@@ -1097,14 +1146,23 @@ function do_all_updates( $shared = false, $purge = true ) {
add_index( $fieldRecord[0], $fieldRecord[1], $fieldRecord[2], true );
flush();
}
+ # Add modified extension fields
+ foreach ( $wgExtModifiedFields as $fieldRecord ) {
+ modify_field($fieldRecord[0], $fieldRecord[1], $fieldRecord[2], true);
+ flush();
+ }
- wfOut( "Deleting old default messages (this may take a long time!)..." );
- deleteDefaultMessages();
+ wfOut( "Deleting old default messages (this may take a long time!)..." );
+ if( !defined( 'MW_NO_SETUP' ) ) {
+ define( 'MW_NO_SETUP', true );
+ }
+ require_once 'deleteDefaultMessages.php';
+ DeleteDefaultMessages::reallyExecute();
wfOut( "Done\n" );
-
+
do_stats_init();
-
+
if( $purge ) {
purge_cache();
}
@@ -1134,8 +1192,8 @@ function do_restrictions_update() {
wfOut( "...$name table already exists.\n" );
} else {
wfOut( "Creating $name table..." );
- dbsource( archive($patch), $wgDatabase );
- dbsource( archive($patch2), $wgDatabase );
+ $wgDatabase->sourceFile( archive($patch) );
+ $wgDatabase->sourceFile( archive($patch2) );
wfOut( "ok\n" );
wfOut( "Migrating old restrictions to new table..." );
@@ -1180,7 +1238,7 @@ function do_restrictions_update() {
$wgDatabase->update( 'page', array ( 'page_restrictions' => ''), array( 'page_id' => $id ), __METHOD__ );
}
-
+
foreach( $restrictions as $type => $level ) {
$wgDatabase->insert( 'page_restrictions', array ( 'pr_page' => $id,
'pr_type' => $type,
@@ -1214,7 +1272,7 @@ function do_populate_parent_id() {
return;
}
require_once( 'populateParentId.inc' );
-
+
global $wgDatabase;
populate_rev_parent_id( $wgDatabase );
}
@@ -1231,6 +1289,23 @@ function sqlite_initial_indexes() {
wfOut( "done\n" );
}
+function sqlite_setup_searchindex() {
+ global $wgDatabase;
+ $module = $wgDatabase->getFulltextSearchModule();
+ $fts3tTable = update_row_exists( 'fts3' );
+ if ( $fts3tTable && !$module ) {
+ wfOut( '...PHP is missing FTS3 support, downgrading tables...' );
+ $wgDatabase->sourceFile( archive( 'searchindex-no-fts.sql' ) );
+ wfOut( "done\n" );
+ } elseif ( !$fts3tTable && $module == 'FTS3' ) {
+ wfOut( '...adding FTS3 search capabilities...' );
+ $wgDatabase->sourceFile( archive( 'searchindex-fts3.sql' ) );
+ wfOut( "done\n" );
+ } else {
+ wfOut( "...fulltext search table appears to be in order.\n" );
+ }
+}
+
function do_unique_pl_tl_il() {
global $wgDatabase;
$info = $wgDatabase->indexInfo( 'pagelinks', 'pl_namespace' );
@@ -1238,23 +1313,72 @@ function do_unique_pl_tl_il() {
wfOut( "...pl_namespace, tl_namespace, il_to indices are already UNIQUE.\n" );
} else {
wfOut( "Making pl_namespace, tl_namespace and il_to indices UNIQUE... " );
- dbsource( archive( 'patch-pl-tl-il-unique.sql' ), $wgDatabase );
+ $wgDatabase->sourceFile( archive( 'patch-pl-tl-il-unique.sql' ) );
wfOut( "ok\n" );
}
}
+function do_log_search_population() {
+ global $wgDatabase;
+ if( update_row_exists( 'populate log_search' ) ) {
+ wfOut( "...log_search table already populated.\n" );
+ return;
+ }
+ require_once( 'populateLogSearch.inc' );
+ wfOut(
+"Populating log_search table, printing progress markers. For large\n" .
+"databases, you may want to hit Ctrl-C and do this manually with\n" .
+"maintenance/populateLogSearch.php.\n" );
+ migrate_log_params( $wgDatabase );
+ wfOut( "Done populating log_search table.\n" );
+}
+
+function rename_eu_wiki_id() {
+ global $wgDatabase;
+ wfOut( "Renaming eu_wiki_id -> eu_local_id... " );
+ if ( $wgDatabase->fieldExists( 'external_user', 'eu_local_id' ) ) {
+ wfOut( "already done.\n" );
+ return;
+ }
+ $wgDatabase->sourceFile( archive( 'patch-eu_local_id.sql' ) );
+ wfOut( "ok\n" );
+}
+
+function do_update_transcache_field() {
+ global $wgDatabase;
+ if( update_row_exists( 'convert transcache field' ) ) {
+ wfOut( "...transcache tc_time already converted.\n" );
+ return;
+ } else {
+ wfOut( "Converting tc_time from UNIX epoch to MediaWiki timestamp... " );
+ $wgDatabase->sourceFile( archive( 'patch-tc-timestamp.sql' ) );
+ wfOut( "ok\n" );
+ }
+}
+
+function do_update_mime_minor_field() {
+ if ( update_row_exists( 'mime_minor_length' ) ) {
+ wfOut( "*_mime_minor fields are already long enough.\n" );
+ } else {
+ global $wgDatabase;
+ wfOut( "Altering all *_mime_minor fields to 100 bytes in size ... " );
+ $wgDatabase->sourceFile( archive( 'patch-mime_minor_length.sql' ) );
+ wfOut( "ok\n" );
+ }
+}
+
+
+
/***********************************************************************
- * Start PG crap
+ * Start PG stuff
* TODO: merge with above
***********************************************************************/
-function
-pg_describe_table($table)
-{
-global $wgDatabase, $wgDBmwschema;
+function pg_describe_table($table) {
+ global $wgDatabase, $wgDBmwschema;
$q = <<<END
SELECT attname, attnum FROM pg_namespace, pg_class, pg_attribute
- WHERE pg_class.relnamespace = pg_namespace.oid
+ WHERE pg_class.relnamespace = pg_namespace.oid
AND attrelid=pg_class.oid AND attnum > 0
AND relname=%s AND nspname=%s
END;
@@ -1266,7 +1390,7 @@ END;
$cols = array();
while ($r = $wgDatabase->fetchRow($res)) {
- $cols[] = array(
+ $cols[] = array(
"name" => $r[0],
"ord" => $r[1],
);
@@ -1274,10 +1398,8 @@ END;
return $cols;
}
-function
-pg_describe_index($idx)
-{
-global $wgDatabase, $wgDBmwschema;
+function pg_describe_index($idx) {
+ global $wgDatabase, $wgDBmwschema;
// first fetch the key (which is a list of columns ords) and
// the table the index applies to (an oid)
@@ -1325,10 +1447,8 @@ END;
return $colnames;
}
-function
-pg_index_exists($table, $index)
-{
-global $wgDatabase, $wgDBmwschema;
+function pg_index_exists($table, $index) {
+ global $wgDatabase, $wgDBmwschema;
$exists = $wgDatabase->selectField("pg_indexes", "indexname",
array( "indexname" => $index,
"tablename" => $table,
@@ -1336,10 +1456,8 @@ global $wgDatabase, $wgDBmwschema;
return $exists === $index;
}
-function
-pg_fkey_deltype($fkey)
-{
-global $wgDatabase, $wgDBmwschema;
+function pg_fkey_deltype($fkey) {
+ global $wgDatabase, $wgDBmwschema;
$q = <<<END
SELECT confdeltype FROM pg_constraint, pg_namespace
WHERE connamespace=pg_namespace.oid
@@ -1354,10 +1472,8 @@ END;
return $row[0];
}
-function
-pg_rule_def($table, $rule)
-{
-global $wgDatabase, $wgDBmwschema;
+function pg_rule_def($table, $rule) {
+ global $wgDatabase, $wgDBmwschema;
$q = <<<END
SELECT definition FROM pg_rules
WHERE schemaname = %s
@@ -1439,8 +1555,8 @@ function do_postgres_updates() {
}
$newsequences = array(
- "log_log_id_seq",
- "pr_id_val",
+ "logging_log_id_seq",
+ "page_restrictions_pr_id_seq",
);
$newtables = array(
@@ -1456,8 +1572,11 @@ function do_postgres_updates() {
array("redirect", "patch-redirect.sql"),
array("updatelog", "patch-updatelog.sql"),
array('change_tag', 'patch-change_tag.sql'),
- array('tag_summary', 'patch-change_tag.sql'),
- array('valid_tag', 'patch-change_tag.sql'),
+ array('tag_summary', 'patch-change_tag.sql'),
+ array('valid_tag', 'patch-change_tag.sql'),
+ array('user_properties', 'patch-user_properties.sql'),
+ array('log_search', 'patch-log_search.sql'),
+ array('l10n_cache', 'patch-l10n_cache.sql'),
);
$newcols = array(
@@ -1475,7 +1594,7 @@ function do_postgres_updates() {
array("ipblocks", "ipb_enable_autoblock", "SMALLINT NOT NULL DEFAULT 1"),
array("filearchive", "fa_deleted", "SMALLINT NOT NULL DEFAULT 0"),
array("logging", "log_deleted", "SMALLINT NOT NULL DEFAULT 0"),
- array("logging", "log_id", "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('log_log_id_seq')"),
+ array("logging", "log_id", "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('logging_log_id_seq')"),
array("logging", "log_params", "TEXT"),
array("mwuser", "user_editcount", "INTEGER"),
array("mwuser", "user_hidden", "SMALLINT NOT NULL DEFAULT 0"),
@@ -1486,20 +1605,24 @@ function do_postgres_updates() {
array("oldimage", "oi_metadata", "BYTEA NOT NULL DEFAULT ''"),
array("oldimage", "oi_minor_mime", "TEXT NOT NULL DEFAULT 'unknown'"),
array("oldimage", "oi_sha1", "TEXT NOT NULL DEFAULT ''"),
- array("page_restrictions", "pr_id", "INTEGER NOT NULL UNIQUE DEFAULT nextval('pr_id_val')"),
+ array("page_restrictions", "pr_id", "INTEGER NOT NULL UNIQUE DEFAULT nextval('page_restrictions_pr_id_val')"),
array("profiling", "pf_memory", "NUMERIC(18,10) NOT NULL DEFAULT 0"),
array("recentchanges", "rc_deleted", "SMALLINT NOT NULL DEFAULT 0"),
array("recentchanges", "rc_log_action", "TEXT"),
- array("recentchanges", "rc_log_type", "TEXT"),
- array("recentchanges", "rc_logid", "INTEGER NOT NULL DEFAULT 0"),
+ array("recentchanges", "rc_log_type", "TEXT"),
+ array("recentchanges", "rc_logid", "INTEGER NOT NULL DEFAULT 0"),
array("recentchanges", "rc_new_len", "INTEGER"),
array("recentchanges", "rc_old_len", "INTEGER"),
array("recentchanges", "rc_params", "TEXT"),
+ array("redirect", "rd_interwiki", "TEXT NULL"),
+ array("redirect", "rd_fragment", "TEXT NULL"),
array("revision", "rev_deleted", "SMALLINT NOT NULL DEFAULT 0"),
array("revision", "rev_len", "INTEGER"),
array("revision", "rev_parent_id", "INTEGER DEFAULT NULL"),
array("site_stats", "ss_active_users", "INTEGER DEFAULT '-1'"),
array("user_newtalk", "user_last_timestamp", "TIMESTAMPTZ"),
+ array("logging", "log_user_text", "TEXT NOT NULL DEFAULT ''"),
+ array("logging", "log_page", "INTEGER"),
);
@@ -1568,19 +1691,26 @@ function do_postgres_updates() {
array("recentchanges", "rc_timestamp_bot", "(rc_timestamp) WHERE rc_bot = 0"),
array("templatelinks", "templatelinks_from", "(tl_from)"),
array("watchlist", "wl_user", "(wl_user)"),
+ array("logging", "logging_user_type_time", "(log_user, log_type, log_timestamp)"),
+ array("logging", "logging_page_id_time", "(log_page,log_timestamp)"),
);
$newrules = array(
);
+ #Check new sequences, rename if needed
foreach ($newsequences as $ns) {
- if ($wgDatabase->sequenceExists($ns)) {
- wfOut( "... sequence \"$ns\" already exists\n" );
+ if( $wgDatabase->sequenceExists('pr_id_val') ) {
+ wfOut( "Updating sequence names\n" );
+ $wgDatabase->sourceFile(archive('patch-update_sequences.sql'));
+ continue;
+ } elseif ( $wgDatabase->sequenceExists('page_restrictions_pr_id_seq') ) {
+ wfOut( "... sequences already updated\n" );
continue;
+ } else {
+ wfOut( "Creating sequence \"$ns\"\n" );
+ $wgDatabase->query("CREATE SEQUENCE $ns");
}
-
- wfOut( "Creating sequence \"$ns\"\n" );
- $wgDatabase->query("CREATE SEQUENCE $ns");
}
foreach ($newtables as $nt) {
@@ -1590,7 +1720,7 @@ function do_postgres_updates() {
}
wfOut( "Creating table \"$nt[0]\"\n" );
- dbsource(archive($nt[1]));
+ $wgDatabase->sourceFile(archive($nt[1]));
}
## Needed before newcols
@@ -1604,7 +1734,7 @@ function do_postgres_updates() {
wfOut( "Dropping rule \"archive_delete\"\n" );
$wgDatabase->query("DROP RULE archive_delete ON archive");
}
- dbsource(archive("patch-remove-archive2.sql"));
+ $wgDatabase->sourceFile(archive("patch-remove-archive2.sql"));
}
else
wfOut( "... obsolete table \"archive2\" does not exist\n" );
@@ -1699,21 +1829,27 @@ function do_postgres_updates() {
continue;
}
wfOut( "Adding rule \"$nr[1]\" to table \"$nr[0]\"\n" );
- dbsource(archive($nr[2]));
+ $wgDatabase->sourceFile(archive($nr[2]));
}
- if ($wgDatabase->hasConstraint("oldimage_oi_name_fkey")) {
- wfOut( "Making foreign key on table \"oldimage\" (to image) a cascade delete\n" );
- $wgDatabase->query( "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey" );
- $wgDatabase->query( "ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascade ".
- "FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE" );
+ if ($wgDatabase->hasConstraint("oldimage_oi_name_fkey_cascaded")) {
+ wfOut( "... table \"oldimage\" has correct cascading delete/update foreign key to image\n" );
+ }
+ else {
+ if ($wgDatabase->hasConstraint("oldimage_oi_name_fkey")) {
+ $wgDatabase->query( "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey" );
+ }
+ if ($wgDatabase->hasConstraint("oldimage_oi_name_fkey_cascade")) {
+ $wgDatabase->query( "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey_cascade" );
+ }
+ wfOut( "Making foreign key on table \"oldimage\" (to image) a cascade delete/update\n" );
+ $wgDatabase->query( "ALTER TABLE oldimage ADD CONSTRAINT oldimage_oi_name_fkey_cascaded ".
+ "FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE ON UPDATE CASCADE" );
}
- else
- wfOut( "... table \"oldimage\" has correct cascade delete foreign key to image\n" );
if (!$wgDatabase->triggerExists("page", "page_deleted")) {
wfOut( "Adding function and trigger \"page_deleted\" to table \"page\"\n" );
- dbsource(archive('patch-page_deleted.sql'));
+ $wgDatabase->sourceFile(archive('patch-page_deleted.sql'));
}
else
wfOut( "... table \"page\" has \"page_deleted\" trigger\n" );
@@ -1721,7 +1857,7 @@ function do_postgres_updates() {
$fi = $wgDatabase->fieldInfo("recentchanges", "rc_cur_id");
if (!$fi->nullable()) {
wfOut( "Removing NOT NULL constraint from \"recentchanges.rc_cur_id\"\n" );
- dbsource(archive('patch-rc_cur_id-not-null.sql'));
+ $wgDatabase->sourceFile(archive('patch-rc_cur_id-not-null.sql'));
}
else
wfOut( "... column \"recentchanges.rc_cur_id\" has a NOT NULL constraint\n" );
@@ -1747,7 +1883,7 @@ function do_postgres_updates() {
}
else {
wfOut( "Changing constraint \"revision_rev_user_fkey\" to ON DELETE RESTRICT\n" );
- dbsource(archive('patch-revision_rev_user_fkey.sql'));
+ $wgDatabase->sourceFile(archive('patch-revision_rev_user_fkey.sql'));
}
# Fix ipb_address index
@@ -1760,10 +1896,10 @@ function do_postgres_updates() {
}
else {
wfOut( "Adding ipb_address_unique index\n" );
- dbsource(archive('patch-ipb_address_unique.sql'));
+ $wgDatabase->sourceFile(archive('patch-ipb_address_unique.sql'));
}
- global $wgExtNewTables, $wgExtPGNewFields, $wgExtNewIndexes;
+ global $wgExtNewTables, $wgExtPGNewFields, $wgExtPGAlteredFields, $wgExtNewIndexes;
# Add missing extension tables
foreach ( $wgExtNewTables as $nt ) {
if ($wgDatabase->tableExists($nt[0])) {
@@ -1771,7 +1907,7 @@ function do_postgres_updates() {
continue;
}
wfOut( "Creating table \"$nt[0]\"\n" );
- dbsource($nt[1]);
+ $wgDatabase->sourceFile($nt[1]);
}
# Add missing extension fields
foreach ( $wgExtPGNewFields as $nc ) {
@@ -1783,6 +1919,26 @@ function do_postgres_updates() {
wfOut( "Adding column \"$nc[0].$nc[1]\"\n" );
$wgDatabase->query( "ALTER TABLE $nc[0] ADD $nc[1] $nc[2]" );
}
+ # Change altered columns
+ foreach ( $wgExtPGAlteredFields as $nc ) {
+ $fi = $wgDatabase->fieldInfo($nc[0], $nc[1]);
+ if (is_null($fi)) {
+ wfOut( "WARNING! Column \"$nc[0].$nc[1]\" does not exist but had an alter request! Please report this.\n" );
+ continue;
+ }
+ $oldtype = $fi->type();
+ $newtype = strtolower( $nc[2] );
+ if ($oldtype === $newtype) {
+ wfOut( "... column \"$nc[0].$nc[1]\" has correct type of \"$newtype\"\n" );
+ continue;
+ }
+ $command = "ALTER TABLE $nc[0] ALTER $nc[1] TYPE $nc[2]";
+ if ( isset( $nc[3] ) ) {
+ $command .= " USING $nc[3]";
+ }
+ wfOut( "Altering column \"$nc[0].$nc[1]\" from type \"$oldtype\" to \"$newtype\"\n" );
+ $wgDatabase->query( $command );
+ }
# Add missing extension indexes
foreach ( $wgExtNewIndexes as $ni ) {
if (pg_index_exists($ni[0], $ni[1])) {
@@ -1794,18 +1950,18 @@ function do_postgres_updates() {
$wgDatabase->query( "CREATE INDEX $ni[1] ON $ni[0] $ni[2]" );
}
else {
- dbsource($ni[2]);
+ $wgDatabase->sourceFile($ni[2]);
}
}
# Tweak the page_title tsearch2 trigger to filter out slashes
# This is create or replace, so harmless to call if not needed
- dbsource(archive('patch-ts2pagetitle.sql'));
+ $wgDatabase->sourceFile(archive('patch-ts2pagetitle.sql'));
## If the server is 8.3 or higher, rewrite the tsearch2 triggers
## in case they have the old 'default' versions
if ( $numver >= 8.3 )
- dbsource(archive('patch-tsearch2funcs.sql'));
+ $wgDatabase->sourceFile(archive('patch-tsearch2funcs.sql'));
## Put a new row in the mediawiki_version table
$wgDatabase->insert( 'mediawiki_version',
@@ -1814,8 +1970,8 @@ function do_postgres_updates() {
'ctype' => 'U',
'mw_version' => $wgVersion,
'pg_version' => $version,
- 'sql_version' => '$LastChangedRevision: 60080 $',
- 'sql_date' => '$LastChangedDate: 2009-12-16 04:24:12 +1100 (Wed, 16 Dec 2009) $',
+ 'sql_version' => '$LastChangedRevision: 62877 $',
+ 'sql_date' => '$LastChangedDate: 2010-02-24 01:13:36 +1100 (Wed, 24 Feb 2010) $',
) );
return;
}
diff --git a/maintenance/upgrade1_5.php b/maintenance/upgrade1_5.php
index 32634b67..4834a4a1 100644
--- a/maintenance/upgrade1_5.php
+++ b/maintenance/upgrade1_5.php
@@ -15,7 +15,7 @@
$options = array( 'step', 'noimages' );
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
require_once( 'FiveUpgrade.inc' );
echo "ATTENTION: This script is for upgrades from 1.4 to 1.5 (NOT 1.15) in very special cases.\n";
diff --git a/maintenance/userOptions.inc b/maintenance/userOptions.inc
index 00278f51..d660019d 100644
--- a/maintenance/userOptions.inc
+++ b/maintenance/userOptions.inc
@@ -8,7 +8,7 @@
$options = array( 'list', 'nowarn', 'quiet', 'usage', 'dry' );
$optionsWithArgs = array( 'old', 'new' );
-require_once( 'commandLine.inc' );
+require_once( dirname(__FILE__) . '/commandLine.inc' );
/**
* @ingroup Maintenance
@@ -246,13 +246,7 @@ Users with option <$this->mAnOption> = '$this->mOldValue' will be made to use '$
Abort with control-c in the next five seconds....
WARN;
- require('counter.php');
- for ($i=6;$i>=1;) {
- print_c($i, --$i);
- sleep(1);
- }
- print "\n";
-
+ wfCountDown( 5 );
return true;
}
diff --git a/maintenance/users.sql b/maintenance/users.sql
index 755bf9f7..1db32ae2 100644
--- a/maintenance/users.sql
+++ b/maintenance/users.sql
@@ -4,9 +4,9 @@
-- from local settings.
--
-GRANT DELETE,INSERT,SELECT,UPDATE,CREATE TEMPORARY TABLES ON `{$wgDBname}`.*
+GRANT ALL PRIVILEGES ON `{$wgDBname}`.*
TO '{$wgDBuser}'@'%' IDENTIFIED BY '{$wgDBpassword}';
-GRANT DELETE,INSERT,SELECT,UPDATE,CREATE TEMPORARY TABLES ON `{$wgDBname}`.*
+GRANT ALL PRIVILEGES ON `{$wgDBname}`.*
TO '{$wgDBuser}'@localhost IDENTIFIED BY '{$wgDBpassword}';
-GRANT DELETE,INSERT,SELECT,UPDATE,CREATE TEMPORARY TABLES ON `{$wgDBname}`.*
+GRANT ALL PRIVILEGES ON `{$wgDBname}`.*
TO '{$wgDBuser}'@localhost.localdomain IDENTIFIED BY '{$wgDBpassword}';
diff --git a/maintenance/waitForSlave.php b/maintenance/waitForSlave.php
index 309d0e7d..f2a532c9 100644
--- a/maintenance/waitForSlave.php
+++ b/maintenance/waitForSlave.php
@@ -1,15 +1,34 @@
<?php
/**
- * @see wfWaitForSlaves()
- * @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
+ *
* @ingroup Maintenance
+ * @see wfWaitForSlaves()
*/
-require_once( "commandLine.inc" );
-if ( isset( $args[0] ) ) {
- wfWaitForSlaves($args[0]);
-} else {
- wfWaitForSlaves(10);
-}
+require_once( dirname(__FILE__) . '/Maintenance.php' );
+class WaitForSlave extends Maintenance {
+ public function __construct() {
+ $this->addArg( 'maxlag', 'How long to wait for the slaves, default 10 seconds', false );
+ }
+ public function execute() {
+ wfWaitForSlaves( $this->getArg( 0, 10 ) );
+ }
+}
+$maintClass = "WaitForSlave";
+require_once( DO_MAINTENANCE );
diff --git a/maintenance/wikipedia-interwiki.sql b/maintenance/wikipedia-interwiki.sql
index 5c0857b5..f683439e 100644
--- a/maintenance/wikipedia-interwiki.sql
+++ b/maintenance/wikipedia-interwiki.sql
@@ -13,6 +13,7 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES
-- An alphabetical list of Wikipedia sites:
('aa','http://aa.wikipedia.org/wiki/$1',1),
('ab','http://ab.wikipedia.org/wiki/$1',1),
+('ace','http://ace.wikipedia.org/wiki/$1',1),
('af','http://af.wikipedia.org/wiki/$1',1),
('ak','http://ak.wikipedia.org/wiki/$1',1),
('als','http://als.wikipedia.org/wiki/$1',1),
@@ -21,6 +22,7 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES
('ang','http://ang.wikipedia.org/wiki/$1',1),
('ar','http://ar.wikipedia.org/wiki/$1',1),
('arc','http://arc.wikipedia.org/wiki/$1',1),
+('arz','http://arz.wikipedia.org/wiki/$1',1),
('as','http://as.wikipedia.org/wiki/$1',1),
('ast','http://ast.wikipedia.org/wiki/$1',1),
('av','http://av.wikipedia.org/wiki/$1',1),
@@ -52,6 +54,7 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES
('cho','http://cho.wikipedia.org/wiki/$1',1),
('chr','http://chr.wikipedia.org/wiki/$1',1),
('chy','http://chy.wikipedia.org/wiki/$1',1),
+('ckb','http://ckb.wikipedia.org/wiki/$1',1),
('co','http://co.wikipedia.org/wiki/$1',1),
('cr','http://cr.wikipedia.org/wiki/$1',1),
('crh','http://crh.wikipedia.org/wiki/$1',1),
@@ -154,6 +157,7 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES
('mdf','http://mdf.wikipedia.org/wiki/$1',1),
('mg','http://mg.wikipedia.org/wiki/$1',1),
('mh','http://mh.wikipedia.org/wiki/$1',1),
+('mhr','http://mhr.wikipedia.org/wiki/$1',1),
('mi','http://mi.wikipedia.org/wiki/$1',1),
('minnan','http://zh-min-nan.wikipedia.org/wiki/$1',1),
('mk','http://mk.wikipedia.org/wiki/$1',1),
@@ -164,6 +168,7 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES
('ms','http://ms.wikipedia.org/wiki/$1',1),
('mt','http://mt.wikipedia.org/wiki/$1',1),
('mus','http://mus.wikipedia.org/wiki/$1',1),
+('mwl','http://mwl.wikipedia.org/wiki/$1',1),
('my','http://my.wikipedia.org/wiki/$1',1),
('myv','http://myv.wikipedia.org/wiki/$1',1),
('mzn','http://mzn.wikipedia.org/wiki/$1',1),
@@ -196,6 +201,7 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES
('pih','http://pih.wikipedia.org/wiki/$1',1),
('pl','http://pl.wikipedia.org/wiki/$1',1),
('pms','http://pms.wikipedia.org/wiki/$1',1),
+('pnb','http://pnb.wikipedia.org/wiki/$1',1),
('ps','http://ps.wikipedia.org/wiki/$1',1),
('pt','http://pt.wikipedia.org/wiki/$1',1),
('qu','http://qu.wikipedia.org/wiki/$1',1),
@@ -242,7 +248,6 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES
('ti','http://ti.wikipedia.org/wiki/$1',1),
('tk','http://tk.wikipedia.org/wiki/$1',1),
('tl','http://tl.wikipedia.org/wiki/$1',1),
-('tlh','http://tlh.wikipedia.org/wiki/$1',1),
('tn','http://tn.wikipedia.org/wiki/$1',1),
('to','http://to.wikipedia.org/wiki/$1',1),
('tokipona','http://tokipona.wikipedia.org/wiki/$1',1),