diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2011-06-22 11:28:20 +0200 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2011-06-22 11:28:20 +0200 |
commit | 9db190c7e736ec8d063187d4241b59feaf7dc2d1 (patch) | |
tree | 46d1a0dee7febef5c2d57a9f7b972be16a163b3d /maintenance | |
parent | 78677c7bbdcc9739f6c10c75935898a20e1acd9e (diff) |
update to MediaWiki 1.17.0
Diffstat (limited to 'maintenance')
327 files changed, 16738 insertions, 16261 deletions
diff --git a/maintenance/7zip.inc b/maintenance/7zip.inc index 617083bf..4ac480ed 100644 --- a/maintenance/7zip.inc +++ b/maintenance/7zip.inc @@ -1,5 +1,12 @@ <?php /** + * 7z stream wrapper + * + * @file + * @ingroup Maintenance + */ + +/** * 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. @@ -8,62 +15,62 @@ */ 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' ) { + if ( $mode[0] == 'r' ) { $options = 'e -bd -so'; - } elseif( $mode[0] == 'w' ) { + } elseif ( $mode[0] == 'w' ) { $options = 'a -bd -si'; } else { return false; } $arg = wfEscapeShellArg( $this->stripPath( $path ) ); $command = "7za $options $arg"; - if( !wfIsWindows() ) { + if ( !wfIsWindows() ) { // Suppress the stupid messages on stderr $command .= ' 2>/dev/null'; } - $this->stream = popen( $command, $mode ); - return ($this->stream !== false); + $this->stream = popen( $command, $mode[0] ); // popen() doesn't like two-letter modes + 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 +stream_wrapper_register( 'mediawiki.compress.7z', 'SevenZipStream' ); diff --git a/maintenance/Doxyfile b/maintenance/Doxyfile index db737bff..3d037651 100644 --- a/maintenance/Doxyfile +++ b/maintenance/Doxyfile @@ -9,6 +9,7 @@ # {{INPUT}} # # A number of MediaWiki-specific aliases are near the end of this file. +# To generate documentation run: php mwdocgen.php --no-extensions #--------------------------------------------------------------------------- # Project related configuration options @@ -39,7 +40,6 @@ STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES MULTILINE_CPP_IS_BRIEF = NO -DETAILS_AT_TOP = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 @@ -76,6 +76,7 @@ ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_DIRECTORIES = NO +SHOW_NAMESPACES = NO FILE_VERSION_FILTER = {{SVNSTAT}} #--------------------------------------------------------------------------- # configuration options related to warning and progress messages @@ -135,7 +136,7 @@ FILE_PATTERNS = *.c \ RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = YES -EXCLUDE_PATTERNS = LocalSettings.php AdminSettings.php .svn {{EXCLUDE}} +EXCLUDE_PATTERNS = LocalSettings.php AdminSettings.php StartProfiler.php .svn */.git/* {{EXCLUDE}} EXAMPLE_PATH = EXAMPLE_PATTERNS = * EXAMPLE_RECURSIVE = NO @@ -245,7 +246,7 @@ SKIP_FUNCTION_MACROS = YES # Configuration::additions related to external references #--------------------------------------------------------------------------- TAGFILES = -GENERATE_TAGFILE = +GENERATE_TAGFILE = {{OUTPUT_DIRECTORY}}/html/tagfile.xml ALLEXTERNALS = NO EXTERNAL_GROUPS = YES PERL_PATH = /usr/bin/perl @@ -284,13 +285,16 @@ ALIASES = "type{1}=<b> \1 </b>:" \ "arrayof{2}=<b> Array </b> of \2" \ "null=\type{Null}" \ "boolean=\type{Boolean}" \ - "bool=\boolean" \ + "bool=\type{Boolean}" \ "integer=\type{Integer}" \ - "int=\integer" \ + "int=\type{Integer}" \ "string=\type{String}" \ - "str=\string" \ + "str=\type{String}" \ "mixed=\type{Mixed}" \ "access=\par Access:\n" \ "private=\access private" \ "protected=\access protected" \ - "public=\access public"
\ No newline at end of file + "public=\access public" \ + "copyright=\note" \ + "license=\note" + diff --git a/maintenance/FiveUpgrade.inc b/maintenance/FiveUpgrade.inc deleted file mode 100644 index be0112e9..00000000 --- a/maintenance/FiveUpgrade.inc +++ /dev/null @@ -1,1190 +0,0 @@ -<?php -/** - * @file - * @ingroup Maintenance - */ - -require_once( 'cleanupDupes.inc' ); -require_once( 'userDupes.inc' ); -require_once( 'updaters.inc' ); - -define( 'MW_UPGRADE_COPY', false ); -define( 'MW_UPGRADE_ENCODE', true ); -define( 'MW_UPGRADE_NULL', null ); -define( 'MW_UPGRADE_CALLBACK', null ); // for self-documentation only - -/** - * @ingroup Maintenance - */ -class FiveUpgrade { - function FiveUpgrade() { - $this->conversionTables = $this->prepareWindows1252(); - - $this->loadBalancers = array(); - $this->dbw = wfGetDB( DB_MASTER ); - $this->dbr = $this->streamConnection(); - - $this->cleanupSwaps = array(); - $this->emailAuth = false; # don't preauthenticate emails - $this->maxLag = 10; # if slaves are lagged more than 10 secs, wait - } - - function doing( $step ) { - return is_null( $this->step ) || $step == $this->step; - } - - function upgrade( $step ) { - $this->step = $step; - - $tables = array( - 'page', - 'links', - 'user', - 'image', - 'oldimage', - 'watchlist', - 'logging', - 'archive', - 'imagelinks', - 'categorylinks', - 'ipblocks', - 'recentchanges', - 'querycache' ); - foreach( $tables as $table ) { - if( $this->doing( $table ) ) { - $method = 'upgrade' . ucfirst( $table ); - $this->$method(); - } - } - - if( $this->doing( 'cleanup' ) ) { - $this->upgradeCleanup(); - } - } - - - /** - * Open a connection to the master server with the admin rights. - * @return Database - * @access private - */ - function newConnection() { - $lb = wfGetLBFactory()->newMainLB(); - $db = $lb->getConnection( DB_MASTER ); - - $this->loadBalancers[] = $lb; - return $db; - } - - /** - * Commit transactions and close the connections when we're done... - */ - function close() { - foreach( $this->loadBalancers as $lb ) { - $lb->commitMasterChanges(); - $lb->closeAll(); - } - } - - /** - * Open a second connection to the master server, with buffering off. - * This will let us stream large datasets in and write in chunks on the - * other end. - * @return Database - * @access private - */ - function streamConnection() { - global $wgDBtype; - - $timeout = 3600 * 24; - $db = $this->newConnection(); - $db->bufferResults( false ); - if ($wgDBtype == 'mysql') { - $db->query( "SET net_read_timeout=$timeout" ); - $db->query( "SET net_write_timeout=$timeout" ); - } - return $db; - } - - /** - * Prepare a conversion array for converting Windows Code Page 1252 to - * UTF-8. This should provide proper conversion of text that was miscoded - * as Windows-1252 by naughty user-agents, and doesn't rely on an outside - * iconv library. - * - * @return array - * @access private - */ - function prepareWindows1252() { - # Mappings from: - # http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT - static $cp1252 = array( - 0x80 => 0x20AC, #EURO SIGN - 0x81 => 0xFFFD, #REPLACEMENT CHARACTER (no mapping) - 0x82 => 0x201A, #SINGLE LOW-9 QUOTATION MARK - 0x83 => 0x0192, #LATIN SMALL LETTER F WITH HOOK - 0x84 => 0x201E, #DOUBLE LOW-9 QUOTATION MARK - 0x85 => 0x2026, #HORIZONTAL ELLIPSIS - 0x86 => 0x2020, #DAGGER - 0x87 => 0x2021, #DOUBLE DAGGER - 0x88 => 0x02C6, #MODIFIER LETTER CIRCUMFLEX ACCENT - 0x89 => 0x2030, #PER MILLE SIGN - 0x8A => 0x0160, #LATIN CAPITAL LETTER S WITH CARON - 0x8B => 0x2039, #SINGLE LEFT-POINTING ANGLE QUOTATION MARK - 0x8C => 0x0152, #LATIN CAPITAL LIGATURE OE - 0x8D => 0xFFFD, #REPLACEMENT CHARACTER (no mapping) - 0x8E => 0x017D, #LATIN CAPITAL LETTER Z WITH CARON - 0x8F => 0xFFFD, #REPLACEMENT CHARACTER (no mapping) - 0x90 => 0xFFFD, #REPLACEMENT CHARACTER (no mapping) - 0x91 => 0x2018, #LEFT SINGLE QUOTATION MARK - 0x92 => 0x2019, #RIGHT SINGLE QUOTATION MARK - 0x93 => 0x201C, #LEFT DOUBLE QUOTATION MARK - 0x94 => 0x201D, #RIGHT DOUBLE QUOTATION MARK - 0x95 => 0x2022, #BULLET - 0x96 => 0x2013, #EN DASH - 0x97 => 0x2014, #EM DASH - 0x98 => 0x02DC, #SMALL TILDE - 0x99 => 0x2122, #TRADE MARK SIGN - 0x9A => 0x0161, #LATIN SMALL LETTER S WITH CARON - 0x9B => 0x203A, #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK - 0x9C => 0x0153, #LATIN SMALL LIGATURE OE - 0x9D => 0xFFFD, #REPLACEMENT CHARACTER (no mapping) - 0x9E => 0x017E, #LATIN SMALL LETTER Z WITH CARON - 0x9F => 0x0178, #LATIN CAPITAL LETTER Y WITH DIAERESIS - ); - $pairs = array(); - for( $i = 0; $i < 0x100; $i++ ) { - $unicode = isset( $cp1252[$i] ) ? $cp1252[$i] : $i; - $pairs[chr( $i )] = codepointToUtf8( $unicode ); - } - return $pairs; - } - - /** - * Convert from 8-bit Windows-1252 to UTF-8 if necessary. - * @param string $text - * @return string - * @access private - */ - function conv( $text ) { - global $wgUseLatin1; - return is_null( $text ) - ? null - : ( $wgUseLatin1 - ? strtr( $text, $this->conversionTables ) - : $text ); - } - - /** - * Dump timestamp and message to output - * @param string $message - * @access private - */ - function log( $message ) { - echo wfWikiID() . ' ' . wfTimestamp( TS_DB ) . ': ' . $message . "\n"; - flush(); - } - - /** - * Initialize the chunked-insert system. - * Rows will be inserted in chunks of the given number, rather - * than in a giant INSERT...SELECT query, to keep the serialized - * MySQL database replication from getting hung up. This way other - * things can be going on during conversion without waiting for - * slaves to catch up as badly. - * - * @param int $chunksize Number of rows to insert at once - * @param int $final Total expected number of rows / id of last row, - * used for progress reports. - * @param string $table to insert on - * @param string $fname function name to report in SQL - * @access private - */ - function setChunkScale( $chunksize, $final, $table, $fname ) { - $this->chunkSize = $chunksize; - $this->chunkFinal = $final; - $this->chunkCount = 0; - $this->chunkStartTime = wfTime(); - $this->chunkOptions = array( 'IGNORE' ); - $this->chunkTable = $table; - $this->chunkFunction = $fname; - } - - /** - * Chunked inserts: perform an insert if we've reached the chunk limit. - * Prints a progress report with estimated completion time. - * @param array &$chunk -- This will be emptied if an insert is done. - * @param int $key A key identifier to use in progress estimation in - * place of the number of rows inserted. Use this if - * you provided a max key number instead of a count - * as the final chunk number in setChunkScale() - * @access private - */ - function addChunk( &$chunk, $key = null ) { - if( count( $chunk ) >= $this->chunkSize ) { - $this->insertChunk( $chunk ); - - $this->chunkCount += count( $chunk ); - $now = wfTime(); - $delta = $now - $this->chunkStartTime; - $rate = $this->chunkCount / $delta; - - if( is_null( $key ) ) { - $completed = $this->chunkCount; - } else { - $completed = $key; - } - $portion = $completed / $this->chunkFinal; - - $estimatedTotalTime = $delta / $portion; - $eta = $this->chunkStartTime + $estimatedTotalTime; - - printf( "%s: %6.2f%% done on %s; ETA %s [%d/%d] %.2f/sec\n", - wfTimestamp( TS_DB, intval( $now ) ), - $portion * 100.0, - $this->chunkTable, - wfTimestamp( TS_DB, intval( $eta ) ), - $completed, - $this->chunkFinal, - $rate ); - flush(); - - $chunk = array(); - } - } - - /** - * Chunked inserts: perform an insert unconditionally, at the end, and log. - * @param array &$chunk -- This will be emptied if an insert is done. - * @access private - */ - function lastChunk( &$chunk ) { - $n = count( $chunk ); - if( $n > 0 ) { - $this->insertChunk( $chunk ); - } - $this->log( "100.00% done on $this->chunkTable (last chunk $n rows)." ); - } - - /** - * Chunked inserts: perform an insert. - * @param array &$chunk -- This will be emptied if an insert is done. - * @access private - */ - function insertChunk( &$chunk ) { - // Give slaves a chance to catch up - wfWaitForSlaves( $this->maxLag ); - $this->dbw->insert( $this->chunkTable, $chunk, $this->chunkFunction, $this->chunkOptions ); - } - - - /** - * Copy and transcode a table to table_temp. - * @param string $name Base name of the source table - * @param string $tabledef CREATE TABLE definition, w/ $1 for the name - * @param array $fields set of destination fields to these constants: - * MW_UPGRADE_COPY - straight copy - * MW_UPGRADE_ENCODE - for old Latin1 wikis, conv to UTF-8 - * MW_UPGRADE_NULL - just put NULL - * @param callable $callback An optional callback to modify the data - * or perform other processing. Func should be - * ( object $row, array $copy ) and return $copy - * @access private - */ - function copyTable( $name, $tabledef, $fields, $callback = null ) { - $fname = 'FiveUpgrade::copyTable'; - - $name_temp = $name . '_temp'; - $this->log( "Migrating $name table to $name_temp..." ); - - $table_temp = $this->dbw->tableName( $name_temp ); - - // Create temporary table; we're going to copy everything in there, - // then at the end rename the final tables into place. - $def = str_replace( '$1', $table_temp, $tabledef ); - $this->dbw->query( $def, $fname ); - - $numRecords = $this->dbw->selectField( $name, 'COUNT(*)', '', $fname ); - $this->setChunkScale( 100, $numRecords, $name_temp, $fname ); - - // Pull all records from the second, streaming database connection. - $sourceFields = array_keys( array_filter( $fields, - create_function( '$x', 'return $x !== MW_UPGRADE_NULL;' ) ) ); - $result = $this->dbr->select( $name, - $sourceFields, - '', - $fname ); - - $add = array(); - while( $row = $this->dbr->fetchObject( $result ) ) { - $copy = array(); - foreach( $fields as $field => $source ) { - if( $source === MW_UPGRADE_COPY ) { - $copy[$field] = $row->$field; - } elseif( $source === MW_UPGRADE_ENCODE ) { - $copy[$field] = $this->conv( $row->$field ); - } elseif( $source === MW_UPGRADE_NULL ) { - $copy[$field] = null; - } else { - $this->log( "Unknown field copy type: $field => $source" ); - } - } - if( is_callable( $callback ) ) { - $copy = call_user_func( $callback, $row, $copy ); - } - $add[] = $copy; - $this->addChunk( $add ); - } - $this->lastChunk( $add ); - $this->dbr->freeResult( $result ); - - $this->log( "Done converting $name." ); - $this->cleanupSwaps[] = $name; - } - - function upgradePage() { - $fname = "FiveUpgrade::upgradePage"; - $chunksize = 100; - - if( $this->dbw->tableExists( 'page' ) ) { - $this->log( 'Page table already exists; aborting.' ); - die( -1 ); - } - - $this->log( "Checking cur table for unique title index and applying if necessary" ); - checkDupes( true ); - - $this->log( "...converting from cur/old to page/revision/text DB structure." ); - - list ($cur, $old, $page, $revision, $text) = $this->dbw->tableNamesN( 'cur', 'old', 'page', 'revision', 'text' ); - - $this->log( "Creating page and revision tables..." ); - $this->dbw->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 default '', - 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) - ) TYPE=InnoDB", $fname ); - $this->dbw->query("CREATE TABLE $revision ( - rev_id int(8) unsigned NOT NULL auto_increment, - rev_page int(8) unsigned NOT NULL, - rev_text_id int(8) unsigned NOT NULL, - rev_comment tinyblob NOT NULL default '', - 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', - - 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) - ) TYPE=InnoDB", $fname ); - - $maxold = intval( $this->dbw->selectField( 'old', 'max(old_id)', '', $fname ) ); - $this->log( "Last old record is {$maxold}" ); - - global $wgLegacySchemaConversion; - if( $wgLegacySchemaConversion ) { - // Create HistoryBlobCurStub entries. - // Text will be pulled from the leftover 'cur' table at runtime. - echo "......Moving metadata from cur; using blob references to text in cur table.\n"; - $cur_text = "concat('O:18:\"historyblobcurstub\":1:{s:6:\"mCurId\";i:',cur_id,';}')"; - $cur_flags = "'object'"; - } else { - // Copy all cur text in immediately: this may take longer but avoids - // having to keep an extra table around. - echo "......Moving text from cur.\n"; - $cur_text = 'cur_text'; - $cur_flags = "''"; - } - - $maxcur = $this->dbw->selectField( 'cur', 'max(cur_id)', '', $fname ); - $this->log( "Last cur entry is $maxcur" ); - - /** - * Copy placeholder records for each page's current version into old - * Don't do any conversion here; text records are converted at runtime - * based on the flags (and may be originally binary!) while the meta - * fields will be converted in the old -> rev and cur -> page steps. - */ - $this->setChunkScale( $chunksize, $maxcur, 'old', $fname ); - $result = $this->dbr->query( - "SELECT cur_id, cur_namespace, cur_title, $cur_text AS text, cur_comment, - cur_user, cur_user_text, cur_timestamp, cur_minor_edit, $cur_flags AS flags - FROM $cur - ORDER BY cur_id", $fname ); - $add = array(); - while( $row = $this->dbr->fetchObject( $result ) ) { - $add[] = array( - 'old_namespace' => $row->cur_namespace, - 'old_title' => $row->cur_title, - 'old_text' => $row->text, - 'old_comment' => $row->cur_comment, - 'old_user' => $row->cur_user, - 'old_user_text' => $row->cur_user_text, - 'old_timestamp' => $row->cur_timestamp, - 'old_minor_edit' => $row->cur_minor_edit, - 'old_flags' => $row->flags ); - $this->addChunk( $add, $row->cur_id ); - } - $this->lastChunk( $add ); - $this->dbr->freeResult( $result ); - - /** - * Copy revision metadata from old into revision. - * We'll also do UTF-8 conversion of usernames and comments. - */ - #$newmaxold = $this->dbw->selectField( 'old', 'max(old_id)', '', $fname ); - #$this->setChunkScale( $chunksize, $newmaxold, 'revision', $fname ); - #$countold = $this->dbw->selectField( 'old', 'count(old_id)', '', $fname ); - $countold = $this->dbw->selectField( 'old', 'max(old_id)', '', $fname ); - $this->setChunkScale( $chunksize, $countold, 'revision', $fname ); - - $this->log( "......Setting up revision table." ); - $result = $this->dbr->query( - "SELECT old_id, cur_id, old_comment, old_user, old_user_text, - old_timestamp, old_minor_edit - FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title", - $fname ); - - $add = array(); - while( $row = $this->dbr->fetchObject( $result ) ) { - $add[] = array( - 'rev_id' => $row->old_id, - 'rev_page' => $row->cur_id, - 'rev_text_id' => $row->old_id, - 'rev_comment' => $this->conv( $row->old_comment ), - 'rev_user' => $row->old_user, - 'rev_user_text' => $this->conv( $row->old_user_text ), - 'rev_timestamp' => $row->old_timestamp, - 'rev_minor_edit' => $row->old_minor_edit ); - $this->addChunk( $add ); - } - $this->lastChunk( $add ); - $this->dbr->freeResult( $result ); - - - /** - * Copy page metadata from cur into page. - * We'll also do UTF-8 conversion of titles. - */ - $this->log( "......Setting up page table." ); - $this->setChunkScale( $chunksize, $maxcur, 'page', $fname ); - $result = $this->dbr->query( " - 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) AS len - FROM $cur,$revision - WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold} - ORDER BY cur_id", $fname ); - $add = array(); - while( $row = $this->dbr->fetchObject( $result ) ) { - $add[] = array( - 'page_id' => $row->cur_id, - 'page_namespace' => $row->cur_namespace, - 'page_title' => $this->conv( $row->cur_title ), - 'page_restrictions' => $row->cur_restrictions, - 'page_counter' => $row->cur_counter, - 'page_is_redirect' => $row->cur_is_redirect, - 'page_is_new' => $row->cur_is_new, - 'page_random' => $row->cur_random, - 'page_touched' => $this->dbw->timestamp(), - 'page_latest' => $row->rev_id, - 'page_len' => $row->len ); - #$this->addChunk( $add, $row->cur_id ); - $this->addChunk( $add ); - } - $this->lastChunk( $add ); - $this->dbr->freeResult( $result ); - - $this->log( "...done with cur/old -> page/revision." ); - } - - function upgradeLinks() { - $fname = 'FiveUpgrade::upgradeLinks'; - $chunksize = 200; - list ($links, $brokenlinks, $pagelinks, $cur) = $this->dbw->tableNamesN( 'links', 'brokenlinks', 'pagelinks', 'cur' ); - - $this->log( 'Checking for interwiki table change in case of bogus items...' ); - if( $this->dbw->fieldExists( 'interwiki', 'iw_trans' ) ) { - $this->log( 'interwiki has iw_trans.' ); - } else { - global $IP; - $this->log( 'adding iw_trans...' ); - $this->dbw->sourceFile( $IP . '/maintenance/archives/patch-interwiki-trans.sql' ); - $this->log( 'added iw_trans.' ); - } - - $this->log( 'Creating pagelinks table...' ); - $this->dbw->query( " -CREATE TABLE $pagelinks ( - -- Key to the page_id of the page containing the link. - pl_from int(8) unsigned NOT NULL default '0', - - -- Key to page_namespace/page_title of the target page. - -- The target page may or may not exist, and due to renames - -- and deletions may refer to different page records as time - -- goes by. - pl_namespace int NOT NULL default '0', - pl_title varchar(255) binary NOT NULL default '', - - UNIQUE KEY pl_from(pl_from,pl_namespace,pl_title), - KEY (pl_namespace,pl_title) - -) TYPE=InnoDB" ); - - $this->log( 'Importing live links -> pagelinks' ); - $nlinks = $this->dbw->selectField( 'links', 'count(*)', '', $fname ); - if( $nlinks ) { - $this->setChunkScale( $chunksize, $nlinks, 'pagelinks', $fname ); - $result = $this->dbr->query( " - SELECT l_from,cur_namespace,cur_title - FROM $links, $cur - WHERE l_to=cur_id", $fname ); - $add = array(); - while( $row = $this->dbr->fetchObject( $result ) ) { - $add[] = array( - 'pl_from' => $row->l_from, - 'pl_namespace' => $row->cur_namespace, - 'pl_title' => $this->conv( $row->cur_title ) ); - $this->addChunk( $add ); - } - $this->lastChunk( $add ); - } else { - $this->log( 'no links!' ); - } - - $this->log( 'Importing brokenlinks -> pagelinks' ); - $nbrokenlinks = $this->dbw->selectField( 'brokenlinks', 'count(*)', '', $fname ); - if( $nbrokenlinks ) { - $this->setChunkScale( $chunksize, $nbrokenlinks, 'pagelinks', $fname ); - $result = $this->dbr->query( - "SELECT bl_from, bl_to FROM $brokenlinks", - $fname ); - $add = array(); - while( $row = $this->dbr->fetchObject( $result ) ) { - $pagename = $this->conv( $row->bl_to ); - $title = Title::newFromText( $pagename ); - if( is_null( $title ) ) { - $this->log( "** invalid brokenlink: $row->bl_from -> '$pagename' (converted from '$row->bl_to')" ); - } else { - $add[] = array( - 'pl_from' => $row->bl_from, - 'pl_namespace' => $title->getNamespace(), - 'pl_title' => $title->getDBkey() ); - $this->addChunk( $add ); - } - } - $this->lastChunk( $add ); - } else { - $this->log( 'no brokenlinks!' ); - } - - $this->log( 'Done with links.' ); - } - - function upgradeUser() { - // Apply unique index, if necessary: - $duper = new UserDupes( $this->dbw ); - if( $duper->hasUniqueIndex() ) { - $this->log( "Already have unique user_name index." ); - } else { - $this->log( "Clearing user duplicates..." ); - if( !$duper->clearDupes() ) { - $this->log( "WARNING: Duplicate user accounts, may explode!" ); - } - } - - $tabledef = <<<END -CREATE TABLE $1 ( - user_id int(5) unsigned NOT NULL auto_increment, - user_name varchar(255) binary NOT NULL default '', - user_real_name varchar(255) binary NOT NULL default '', - user_password tinyblob NOT NULL default '', - user_newpassword tinyblob NOT NULL default '', - user_email tinytext NOT NULL default '', - user_options blob NOT NULL default '', - user_touched char(14) binary NOT NULL default '', - user_token char(32) binary NOT NULL default '', - user_email_authenticated CHAR(14) BINARY, - user_email_token CHAR(32) BINARY, - user_email_token_expires CHAR(14) BINARY, - - PRIMARY KEY user_id (user_id), - UNIQUE INDEX user_name (user_name), - INDEX (user_email_token) - -) TYPE=InnoDB -END; - $fields = array( - 'user_id' => MW_UPGRADE_COPY, - 'user_name' => MW_UPGRADE_ENCODE, - 'user_real_name' => MW_UPGRADE_ENCODE, - 'user_password' => MW_UPGRADE_COPY, - 'user_newpassword' => MW_UPGRADE_COPY, - 'user_email' => MW_UPGRADE_ENCODE, - 'user_options' => MW_UPGRADE_ENCODE, - 'user_touched' => MW_UPGRADE_CALLBACK, - 'user_token' => MW_UPGRADE_COPY, - 'user_email_authenticated' => MW_UPGRADE_CALLBACK, - 'user_email_token' => MW_UPGRADE_NULL, - 'user_email_token_expires' => MW_UPGRADE_NULL ); - $this->copyTable( 'user', $tabledef, $fields, - array( &$this, 'userCallback' ) ); - } - - function userCallback( $row, $copy ) { - $now = $this->dbw->timestamp(); - $copy['user_touched'] = $now; - $copy['user_email_authenticated'] = $this->emailAuth ? $now : null; - return $copy; - } - - function upgradeImage() { - $tabledef = <<<END -CREATE TABLE $1 ( - img_name varchar(255) binary NOT NULL default '', - img_size int(8) unsigned NOT NULL default '0', - img_width int(5) NOT NULL default '0', - img_height int(5) NOT NULL default '0', - img_metadata mediumblob NOT NULL, - img_bits int(3) NOT NULL default '0', - img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, - img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown", - img_minor_mime varchar(32) NOT NULL default "unknown", - img_description tinyblob NOT NULL default '', - img_user int(5) unsigned NOT NULL default '0', - img_user_text varchar(255) binary NOT NULL default '', - img_timestamp char(14) binary NOT NULL default '', - - PRIMARY KEY img_name (img_name), - INDEX img_size (img_size), - INDEX img_timestamp (img_timestamp) -) TYPE=InnoDB -END; - $fields = array( - 'img_name' => MW_UPGRADE_ENCODE, - 'img_size' => MW_UPGRADE_COPY, - 'img_width' => MW_UPGRADE_CALLBACK, - 'img_height' => MW_UPGRADE_CALLBACK, - 'img_metadata' => MW_UPGRADE_CALLBACK, - 'img_bits' => MW_UPGRADE_CALLBACK, - 'img_media_type' => MW_UPGRADE_CALLBACK, - 'img_major_mime' => MW_UPGRADE_CALLBACK, - 'img_minor_mime' => MW_UPGRADE_CALLBACK, - 'img_description' => MW_UPGRADE_ENCODE, - 'img_user' => MW_UPGRADE_COPY, - 'img_user_text' => MW_UPGRADE_ENCODE, - 'img_timestamp' => MW_UPGRADE_COPY ); - $this->copyTable( 'image', $tabledef, $fields, - array( &$this, 'imageCallback' ) ); - } - - function imageCallback( $row, $copy ) { - global $options; - if( !isset( $options['noimage'] ) ) { - // Fill in the new image info fields - $info = $this->imageInfo( $row->img_name ); - - $copy['img_width' ] = $info['width']; - $copy['img_height' ] = $info['height']; - $copy['img_metadata' ] = ""; // loaded on-demand - $copy['img_bits' ] = $info['bits']; - $copy['img_media_type'] = $info['media']; - $copy['img_major_mime'] = $info['major']; - $copy['img_minor_mime'] = $info['minor']; - } - - // If doing UTF8 conversion the file must be renamed - $this->renameFile( $row->img_name, 'wfImageDir' ); - - return $copy; - } - - function imageInfo( $filename ) { - $info = array( - 'width' => 0, - 'height' => 0, - 'bits' => 0, - 'media' => '', - 'major' => '', - 'minor' => '' ); - - $magic = MimeMagic::singleton(); - $mime = $magic->guessMimeType( $filename, true ); - list( $info['major'], $info['minor'] ) = explode( '/', $mime ); - - $info['media'] = $magic->getMediaType( $filename, $mime ); - - $image = UnregisteredLocalFile::newFromPath( $filename, $mime ); - - $info['width'] = $image->getWidth(); - $info['height'] = $image->getHeight(); - - $gis = $image->getImageSize(); - if ( isset( $gis['bits'] ) ) { - $info['bits'] = $gis['bits']; - } - - return $info; - } - - - /** - * Truncate a table. - * @param string $table The table name to be truncated - */ - function clearTable( $table ) { - print "Clearing $table...\n"; - $tableName = $this->db->tableName( $table ); - $this->db->query( "TRUNCATE $tableName" ); - } - - /** - * Rename a given image or archived image file to the converted filename, - * leaving a symlink for URL compatibility. - * - * @param string $oldname pre-conversion filename - * @param string $basename pre-conversion base filename for dir hashing, if an archive - * @access private - */ - function renameFile( $oldname, $subdirCallback='wfImageDir', $basename=null ) { - $newname = $this->conv( $oldname ); - if( $newname == $oldname ) { - // No need to rename; another field triggered this row. - return false; - } - - if( is_null( $basename ) ) $basename = $oldname; - $ubasename = $this->conv( $basename ); - $oldpath = call_user_func( $subdirCallback, $basename ) . '/' . $oldname; - $newpath = call_user_func( $subdirCallback, $ubasename ) . '/' . $newname; - - $this->log( "$oldpath -> $newpath" ); - if( rename( $oldpath, $newpath ) ) { - $relpath = wfRelativePath( $newpath, dirname( $oldpath ) ); - if( !symlink( $relpath, $oldpath ) ) { - $this->log( "... symlink failed!" ); - } - return $newname; - } else { - $this->log( "... rename failed!" ); - return false; - } - } - - function upgradeOldImage() { - $tabledef = <<<END -CREATE TABLE $1 ( - -- Base filename: key to image.img_name - oi_name varchar(255) binary NOT NULL default '', - - -- Filename of the archived file. - -- This is generally a timestamp and '!' prepended to the base name. - oi_archive_name varchar(255) binary NOT NULL default '', - - -- Other fields as in image... - oi_size int(8) unsigned NOT NULL default 0, - oi_width int(5) NOT NULL default 0, - oi_height int(5) NOT NULL default 0, - oi_bits int(3) NOT NULL default 0, - oi_description tinyblob NOT NULL default '', - oi_user int(5) unsigned NOT NULL default '0', - oi_user_text varchar(255) binary NOT NULL default '', - oi_timestamp char(14) binary NOT NULL default '', - - INDEX oi_name (oi_name(10)) - -) TYPE=InnoDB; -END; - $fields = array( - 'oi_name' => MW_UPGRADE_ENCODE, - 'oi_archive_name' => MW_UPGRADE_ENCODE, - 'oi_size' => MW_UPGRADE_COPY, - 'oi_width' => MW_UPGRADE_CALLBACK, - 'oi_height' => MW_UPGRADE_CALLBACK, - 'oi_bits' => MW_UPGRADE_CALLBACK, - 'oi_description' => MW_UPGRADE_ENCODE, - 'oi_user' => MW_UPGRADE_COPY, - 'oi_user_text' => MW_UPGRADE_ENCODE, - 'oi_timestamp' => MW_UPGRADE_COPY ); - $this->copyTable( 'oldimage', $tabledef, $fields, - array( &$this, 'oldimageCallback' ) ); - } - - function oldimageCallback( $row, $copy ) { - global $options; - if( !isset( $options['noimage'] ) ) { - // Fill in the new image info fields - $info = $this->imageInfo( $row->oi_archive_name, 'wfImageArchiveDir', $row->oi_name ); - $copy['oi_width' ] = $info['width' ]; - $copy['oi_height'] = $info['height']; - $copy['oi_bits' ] = $info['bits' ]; - } - - // If doing UTF8 conversion the file must be renamed - $this->renameFile( $row->oi_archive_name, 'wfImageArchiveDir', $row->oi_name ); - - return $copy; - } - - - function upgradeWatchlist() { - $fname = 'FiveUpgrade::upgradeWatchlist'; - $chunksize = 100; - - list ($watchlist, $watchlist_temp) = $this->dbw->tableNamesN( 'watchlist', 'watchlist_temp' ); - - $this->log( 'Migrating watchlist table to watchlist_temp...' ); - $this->dbw->query( -"CREATE TABLE $watchlist_temp ( - -- Key to user_id - wl_user int(5) unsigned NOT NULL, - - -- Key to page_namespace/page_title - -- Note that users may watch patches which do not exist yet, - -- or existed in the past but have been deleted. - wl_namespace int NOT NULL default '0', - wl_title varchar(255) binary NOT NULL default '', - - -- Timestamp when user was last sent a notification e-mail; - -- cleared when the user visits the page. - -- FIXME: add proper null support etc - wl_notificationtimestamp varchar(14) binary NOT NULL default '0', - - UNIQUE KEY (wl_user, wl_namespace, wl_title), - KEY namespace_title (wl_namespace,wl_title) - -) TYPE=InnoDB;", $fname ); - - // Fix encoding for Latin-1 upgrades, add some fields, - // and double article to article+talk pairs - $numwatched = $this->dbw->selectField( 'watchlist', 'count(*)', '', $fname ); - - $this->setChunkScale( $chunksize, $numwatched * 2, 'watchlist_temp', $fname ); - $result = $this->dbr->select( 'watchlist', - array( - 'wl_user', - 'wl_namespace', - 'wl_title' ), - '', - $fname ); - - $add = array(); - while( $row = $this->dbr->fetchObject( $result ) ) { - $add[] = array( - 'wl_user' => $row->wl_user, - 'wl_namespace' => MWNamespace::getSubject( $row->wl_namespace ), - 'wl_title' => $this->conv( $row->wl_title ), - 'wl_notificationtimestamp' => '0' ); - $this->addChunk( $add ); - - $add[] = array( - 'wl_user' => $row->wl_user, - 'wl_namespace' => MWNamespace::getTalk( $row->wl_namespace ), - 'wl_title' => $this->conv( $row->wl_title ), - 'wl_notificationtimestamp' => '0' ); - $this->addChunk( $add ); - } - $this->lastChunk( $add ); - $this->dbr->freeResult( $result ); - - $this->log( 'Done converting watchlist.' ); - $this->cleanupSwaps[] = 'watchlist'; - } - - function upgradeLogging() { - $tabledef = <<<ENDS -CREATE TABLE $1 ( - -- Symbolic keys for the general log type and the action type - -- within the log. The output format will be controlled by the - -- action field, but only the type controls categorization. - log_type char(10) NOT NULL default '', - log_action char(10) NOT NULL default '', - - -- Timestamp. Duh. - log_timestamp char(14) NOT NULL default '19700101000000', - - -- The user who performed this action; key to user_id - log_user int unsigned NOT NULL default 0, - - -- 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 '', - - -- Freeform text. Interpreted as edit history comments. - log_comment varchar(255) NOT NULL default '', - - -- LF separated list of miscellaneous parameters - log_params blob NOT NULL default '', - - KEY type_time (log_type, log_timestamp), - KEY user_time (log_user, log_timestamp), - KEY page_time (log_namespace, log_title, log_timestamp) - -) TYPE=InnoDB -ENDS; - $fields = array( - 'log_type' => MW_UPGRADE_COPY, - 'log_action' => MW_UPGRADE_COPY, - 'log_timestamp' => MW_UPGRADE_COPY, - 'log_user' => MW_UPGRADE_COPY, - 'log_namespace' => MW_UPGRADE_COPY, - 'log_title' => MW_UPGRADE_ENCODE, - 'log_comment' => MW_UPGRADE_ENCODE, - 'log_params' => MW_UPGRADE_ENCODE ); - $this->copyTable( 'logging', $tabledef, $fields ); - } - - function upgradeArchive() { - $tabledef = <<<ENDS -CREATE TABLE $1 ( - ar_namespace int NOT NULL default '0', - ar_title varchar(255) binary NOT NULL default '', - ar_text mediumblob NOT NULL default '', - - ar_comment tinyblob NOT NULL default '', - ar_user int(5) unsigned NOT NULL default '0', - ar_user_text varchar(255) binary NOT NULL, - ar_timestamp char(14) binary NOT NULL default '', - ar_minor_edit tinyint(1) NOT NULL default '0', - - ar_flags tinyblob NOT NULL default '', - - ar_rev_id int(8) unsigned, - ar_text_id int(8) unsigned, - - KEY name_title_timestamp (ar_namespace,ar_title,ar_timestamp) - -) TYPE=InnoDB -ENDS; - $fields = array( - 'ar_namespace' => MW_UPGRADE_COPY, - 'ar_title' => MW_UPGRADE_ENCODE, - 'ar_text' => MW_UPGRADE_COPY, - 'ar_comment' => MW_UPGRADE_ENCODE, - 'ar_user' => MW_UPGRADE_COPY, - 'ar_user_text' => MW_UPGRADE_ENCODE, - 'ar_timestamp' => MW_UPGRADE_COPY, - 'ar_minor_edit' => MW_UPGRADE_COPY, - 'ar_flags' => MW_UPGRADE_COPY, - 'ar_rev_id' => MW_UPGRADE_NULL, - 'ar_text_id' => MW_UPGRADE_NULL ); - $this->copyTable( 'archive', $tabledef, $fields ); - } - - function upgradeImagelinks() { - global $wgUseLatin1; - if( $wgUseLatin1 ) { - $tabledef = <<<ENDS -CREATE TABLE $1 ( - -- Key to page_id of the page containing the image / media link. - il_from int(8) unsigned NOT NULL default '0', - - -- Filename of target image. - -- This is also the page_title of the file's description page; - -- all such pages are in namespace 6 (NS_FILE). - il_to varchar(255) binary NOT NULL default '', - - UNIQUE KEY il_from(il_from,il_to), - KEY (il_to) - -) TYPE=InnoDB -ENDS; - $fields = array( - 'il_from' => MW_UPGRADE_COPY, - 'il_to' => MW_UPGRADE_ENCODE ); - $this->copyTable( 'imagelinks', $tabledef, $fields ); - } - } - - function upgradeCategorylinks() { - global $wgUseLatin1; - if( $wgUseLatin1 ) { - $tabledef = <<<ENDS -CREATE TABLE $1 ( - cl_from int(8) unsigned NOT NULL default '0', - cl_to varchar(255) binary NOT NULL default '', - cl_sortkey varchar(86) binary NOT NULL default '', - cl_timestamp timestamp NOT NULL, - - UNIQUE KEY cl_from(cl_from,cl_to), - KEY cl_sortkey(cl_to,cl_sortkey), - KEY cl_timestamp(cl_to,cl_timestamp) -) TYPE=InnoDB -ENDS; - $fields = array( - 'cl_from' => MW_UPGRADE_COPY, - 'cl_to' => MW_UPGRADE_ENCODE, - 'cl_sortkey' => MW_UPGRADE_ENCODE, - 'cl_timestamp' => MW_UPGRADE_COPY ); - $this->copyTable( 'categorylinks', $tabledef, $fields ); - } - } - - function upgradeIpblocks() { - global $wgUseLatin1; - if( $wgUseLatin1 ) { - $tabledef = <<<ENDS -CREATE TABLE $1 ( - ipb_id int(8) NOT NULL auto_increment, - ipb_address varchar(40) binary NOT NULL default '', - ipb_user int(8) unsigned NOT NULL default '0', - ipb_by int(8) unsigned NOT NULL default '0', - ipb_reason tinyblob NOT NULL default '', - ipb_timestamp char(14) binary NOT NULL default '', - ipb_auto tinyint(1) NOT NULL default '0', - ipb_expiry char(14) binary NOT NULL default '', - - PRIMARY KEY ipb_id (ipb_id), - INDEX ipb_address (ipb_address), - INDEX ipb_user (ipb_user) - -) TYPE=InnoDB -ENDS; - $fields = array( - 'ipb_id' => MW_UPGRADE_COPY, - 'ipb_address' => MW_UPGRADE_COPY, - 'ipb_user' => MW_UPGRADE_COPY, - 'ipb_by' => MW_UPGRADE_COPY, - 'ipb_reason' => MW_UPGRADE_ENCODE, - 'ipb_timestamp' => MW_UPGRADE_COPY, - 'ipb_auto' => MW_UPGRADE_COPY, - 'ipb_expiry' => MW_UPGRADE_COPY ); - $this->copyTable( 'ipblocks', $tabledef, $fields ); - } - } - - function upgradeRecentchanges() { - // There's a format change in the namespace field - $tabledef = <<<ENDS -CREATE TABLE $1 ( - rc_id int(8) NOT NULL auto_increment, - 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 int 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_bot 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', - - rc_type tinyint(3) unsigned NOT NULL default '0', - rc_moved_to_ns tinyint(3) unsigned NOT NULL default '0', - rc_moved_to_title varchar(255) binary NOT NULL default '', - - rc_patrolled tinyint(3) unsigned NOT NULL default '0', - - rc_ip char(15) NOT NULL default '', - - PRIMARY KEY rc_id (rc_id), - INDEX rc_timestamp (rc_timestamp), - INDEX rc_namespace_title (rc_namespace, rc_title), - INDEX rc_cur_id (rc_cur_id), - INDEX new_name_timestamp(rc_new,rc_namespace,rc_timestamp), - INDEX rc_ip (rc_ip) - -) TYPE=InnoDB -ENDS; - $fields = array( - 'rc_id' => MW_UPGRADE_COPY, - 'rc_timestamp' => MW_UPGRADE_COPY, - 'rc_cur_time' => MW_UPGRADE_COPY, - 'rc_user' => MW_UPGRADE_COPY, - 'rc_user_text' => MW_UPGRADE_ENCODE, - 'rc_namespace' => MW_UPGRADE_COPY, - 'rc_title' => MW_UPGRADE_ENCODE, - 'rc_comment' => MW_UPGRADE_ENCODE, - 'rc_minor' => MW_UPGRADE_COPY, - 'rc_bot' => MW_UPGRADE_COPY, - 'rc_new' => MW_UPGRADE_COPY, - 'rc_cur_id' => MW_UPGRADE_COPY, - 'rc_this_oldid' => MW_UPGRADE_COPY, - 'rc_last_oldid' => MW_UPGRADE_COPY, - 'rc_type' => MW_UPGRADE_COPY, - 'rc_moved_to_ns' => MW_UPGRADE_COPY, - 'rc_moved_to_title' => MW_UPGRADE_ENCODE, - 'rc_patrolled' => MW_UPGRADE_COPY, - 'rc_ip' => MW_UPGRADE_COPY ); - $this->copyTable( 'recentchanges', $tabledef, $fields ); - } - - function upgradeQuerycache() { - // There's a format change in the namespace field - $tabledef = <<<ENDS -CREATE TABLE $1 ( - -- A key name, generally the base name of of the special page. - qc_type char(32) NOT NULL, - - -- Some sort of stored value. Sizes, counts... - qc_value int(5) unsigned NOT NULL default '0', - - -- Target namespace+title - qc_namespace int NOT NULL default '0', - qc_title char(255) binary NOT NULL default '', - - KEY (qc_type,qc_value) - -) TYPE=InnoDB -ENDS; - $fields = array( - 'qc_type' => MW_UPGRADE_COPY, - 'qc_value' => MW_UPGRADE_COPY, - 'qc_namespace' => MW_UPGRADE_COPY, - 'qc_title' => MW_UPGRADE_ENCODE ); - $this->copyTable( 'querycache', $tabledef, $fields ); - } - - /** - * Rename all our temporary tables into final place. - * We've left things in place so a read-only wiki can continue running - * on the old code during all this. - */ - function upgradeCleanup() { - $this->renameTable( 'old', 'text' ); - - foreach( $this->cleanupSwaps as $table ) { - $this->swap( $table ); - } - } - - function renameTable( $from, $to ) { - $this->log( "Renaming $from to $to..." ); - - $fromtable = $this->dbw->tableName( $from ); - $totable = $this->dbw->tableName( $to ); - $this->dbw->query( "ALTER TABLE $fromtable RENAME TO $totable" ); - } - - function swap( $base ) { - $this->renameTable( $base, "{$base}_old" ); - $this->renameTable( "{$base}_temp", $base ); - } - -} diff --git a/maintenance/Maintenance.php b/maintenance/Maintenance.php index ee35df7c..d7297e98 100644 --- a/maintenance/Maintenance.php +++ b/maintenance/Maintenance.php @@ -6,16 +6,27 @@ */ // Define this so scripts can easily find doMaintenance.php -define( 'DO_MAINTENANCE', dirname( __FILE__ ) . '/doMaintenance.php' ); +define( 'RUN_MAINTENANCE_IF_MAIN', dirname( __FILE__ ) . '/doMaintenance.php' ); +define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless + $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 " . +if ( version_compare( PHP_VERSION, '5.2.3' ) < 0 ) { + die ( "Sorry! This version of MediaWiki requires PHP 5.2.3; 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(); + "If you are sure you already have PHP 5.2.3 or higher installed, it may be\n" . + "installed in a different path from PHP " . PHP_VERSION . ". Check with your system\n" . + "administrator.\n" ); +} + +// Wrapper for posix_isatty() +if ( !function_exists( 'posix_isatty' ) ) { + # We default as considering stdin a tty (for nice readline methods) + # but treating stout as not a tty to avoid color codes + function posix_isatty( $fd ) { + return !$fd; + } } /** @@ -93,15 +104,38 @@ abstract class Maintenance { protected static $mCoreScripts = null; /** - * Default constructor. Children should call this if implementing + * Default constructor. Children should call this *first* if implementing * their own constructors */ public function __construct() { + // Setup $IP, using MW_INSTALL_PATH if it exists + global $IP; + $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== '' + ? getenv( 'MW_INSTALL_PATH' ) + : realpath( dirname( __FILE__ ) . '/..' ); + $this->addDefaultParams(); register_shutdown_function( array( $this, 'outputChanneled' ), false ); } /** + * Should we execute the maintenance script, or just allow it to be included + * as a standalone class? It checks that the call stack only includes this + * function and a require (meaning was called from the file scope) + * + * @return Boolean + */ + public static function shouldExecute() { + $bt = debug_backtrace(); + if( count( $bt ) !== 2 ) { + return false; + } + return $bt[1]['function'] == 'require_once' && + $bt[0]['class'] == 'Maintenance' && + $bt[0]['function'] == 'shouldExecute'; + } + + /** * Do the actual work. All child classes will need to implement this */ abstract public function execute(); @@ -110,10 +144,10 @@ abstract class Maintenance { * Add a parameter to the script. Will be displayed on --help * with the associated description * - * @param $name String The name of the param (help, version, etc) - * @param $description String The description of the param to show on --help - * @param $required boolean Is the param required? - * @param $withArg Boolean Is an argument required with this option? + * @param $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 ); @@ -121,8 +155,8 @@ abstract class Maintenance { /** * Checks to see if a particular param exists. - * @param $name String The name of the param - * @return boolean + * @param $name String: the name of the param + * @return Boolean */ protected function hasOption( $name ) { return isset( $this->mOptions[$name] ); @@ -130,12 +164,12 @@ abstract class Maintenance { /** * Get an option, or return the default - * @param $name String The name of the param - * @param $default mixed Anything you want, default null - * @return mixed + * @param $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 ) ) { + if ( $this->hasOption( $name ) ) { return $this->mOptions[$name]; } else { // Set it so we don't have to provide the default again @@ -146,22 +180,38 @@ abstract class Maintenance { /** * Add some args that are needed - * @param $arg String Name of the arg, like 'start' - * @param $description String Short description of the arg - * @param $required Boolean Is this required? + * @param $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( + $this->mArgList[] = array( 'name' => $arg, - 'desc' => $description, - 'require' => $required + 'desc' => $description, + 'require' => $required ); } /** + * Remove an option. Useful for removing options that won't be used in your script. + * @param $name String: the option to remove. + */ + protected function deleteOption( $name ) { + unset( $this->mParams[$name] ); + } + + /** + * Set the description text. + * @param $text String: the text of the description + */ + protected function addDescription( $text ) { + $this->mDescription = $text; + } + + /** * Does a given argument exist? - * @param $argId int The integer value (from zero) for the arg - * @return boolean + * @param $argId Integer: the integer value (from zero) for the arg + * @return Boolean */ protected function hasArg( $argId = 0 ) { return isset( $this->mArgs[$argId] ); @@ -169,8 +219,8 @@ abstract class Maintenance { /** * Get an argument. - * @param $argId int The integer value (from zero) for the arg - * @param $default mixed The default if it doesn't exist + * @param $argId Integer: 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 ) { @@ -179,7 +229,7 @@ abstract class Maintenance { /** * Set the batch size. - * @param $s int The number of operations to do in a batch + * @param $s Integer: the number of operations to do in a batch */ protected function setBatchSize( $s = 0 ) { $this->mBatchSize = $s; @@ -195,33 +245,42 @@ abstract class Maintenance { /** * Return input from stdin. - * @param $length int The number of bytes to read. If null, + * @param $len Integer: the number of bytes to read. If null, * just return the handle. Maintenance::STDIN_ALL returns * the full length - * @return mixed + * @return Mixed */ protected function getStdin( $len = null ) { - if ( $len == Maintenance::STDIN_ALL ) + if ( $len == Maintenance::STDIN_ALL ) { return file_get_contents( 'php://stdin' ); + } $f = fopen( 'php://stdin', 'rt' ); - if( !$len ) + if ( !$len ) { return $f; + } $input = fgets( $f, $len ); fclose( $f ); return rtrim( $input ); } + public function isQuiet() { + return $this->mQuiet; + } + /** * Throw some output to the user. Scripts can call this with no fears, * as we handle all --quiet stuff here - * @param $out String The text to show to the user - * @param $channel Mixed Unique identifier for the channel. See function outputChanneled. + * @param $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 ) { + if ( $this->mQuiet ) { return; } if ( $channel === null ) { + $this->cleanupChanneled(); + $f = fopen( 'php://stdout', 'w' ); fwrite( $f, $out ); fclose( $f ); @@ -235,41 +294,54 @@ abstract class Maintenance { /** * Throw an error to the user. Doesn't respect --quiet, so don't use * this for non-error output - * @param $err String The error to display - * @param $die boolean If true, go ahead and die out. + * @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' ); + $f = fopen( 'php://stderr', 'w' ); fwrite( $f, $err . "\n" ); fclose( $f ); } - if( $die ) die(); + if ( $die ) { + die(); + } } private $atLineStart = true; private $lastChannel = null; - + + /** + * Clean up channeled output. Output a newline if necessary. + */ + public function cleanupChanneled() { + if ( !$this->atLineStart ) { + $handle = fopen( 'php://stdout', 'w' ); + fwrite( $handle, "\n" ); + fclose( $handle ); + $this->atLineStart = true; + } + } + /** * 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 ===. + * @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 ); + $this->cleanupChanneled(); return; } + $handle = fopen( 'php://stdout', 'w' ); + // End the current line if necessary if ( !$this->atLineStart && $channel !== $this->lastChannel ) { fwrite( $handle, "\n" ); @@ -292,12 +364,12 @@ abstract class Maintenance { /** * 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 + * 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 + * @return Integer */ public function getDbType() { return Maintenance::DB_STD; @@ -307,44 +379,41 @@ abstract class Maintenance { * 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" ); - $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( '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' ); + $this->addOption( 'memory-limit', 'Set a specific memory limit for the script, "max" for no limit or "default" to avoid changing it' ); + $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " . + "http://en.wikipedia.org. This is sometimes necessary because " . + "server name detection may fail in command line scripts.", false, true ); // 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 ( $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 ) { + if ( $this->mBatchSize ) { $this->addOption( 'batch-size', 'Run this many operations ' . - 'per batch, default: ' . $this->mBatchSize , false, true ); + '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 + * @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(); - + public function runChild( $maintClass, $classFile = null ) { // Make sure the class is loaded first - if( !class_exists( $maintClass ) ) { - if( $classFile ) { + if ( !class_exists( $maintClass ) ) { + if ( $classFile ) { require_once( $classFile ); } - if( !class_exists( $maintClass ) ) { + if ( !class_exists( $maintClass ) ) { $this->error( "Cannot spawn child: $maintClass" ); } } @@ -355,34 +424,26 @@ abstract class Maintenance { } /** - * 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; + global $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 ); + if ( isset( $_SERVER ) && isset( $_SERVER['REQUEST_METHOD'] ) ) { + $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 ( !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 ) { + 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' ) ) { + if ( ini_get( 'display_errors' ) ) { ini_set( 'display_errors', 'stderr' ); } @@ -393,9 +454,12 @@ abstract class Maintenance { // command-line mode is on, regardless of PHP version. } + $this->loadParamsAndArgs(); + $this->maybeHelp(); + # Set the memory limit # Note we need to set it again later in cache LocalSettings changed it - ini_set( 'memory_limit', $this->memoryLimit() ); + $this->adjustMemoryLimit(); # Set max execution time to 0 (no limit). PHP.net says that # "When running PHP from the command line the default setting is 0." @@ -407,27 +471,38 @@ abstract class Maintenance { # 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. + * blowing up unexpectedly. We also support a --memory-limit option, + * to allow sysadmins to explicitly set one if they'd prefer to override + * defaults (or for people using Suhosin which yells at you for trying + * to disable the limits) */ public function memoryLimit() { - return -1; + $limit = $this->getOption( 'memory-limit', 'max' ); + $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood + return $limit; + } + + /** + * Adjusts PHP's memory limit to better suit our needs, if needed. + */ + protected function adjustMemoryLimit() { + $limit = $this->memoryLimit(); + if ( $limit == 'max' ) { + $limit = -1; // no memory limit + } + if ( $limit != 'default' ) { + ini_set( 'memory_limit', $limit ); + } } /** @@ -450,15 +525,15 @@ abstract class Maintenance { */ public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) { # If we were given opts or args, set those and return early - if( $self ) { + if ( $self ) { $this->mSelf = $self; $this->mInputLoaded = true; } - if( $opts ) { + if ( $opts ) { $this->mOptions = $opts; $this->mInputLoaded = true; } - if( $args ) { + if ( $args ) { $this->mArgs = $args; $this->mInputLoaded = true; } @@ -466,7 +541,7 @@ abstract class Maintenance { # 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 ) { + if ( $this->mInputLoaded ) { $this->loadSpecialVars(); return; } @@ -478,11 +553,11 @@ abstract class Maintenance { $args = array(); # Parse arguments - for( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) { + 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 ) { + while ( $arg !== false ) { $args[] = $arg; $arg = next( $argv ); } @@ -499,7 +574,7 @@ abstract class Maintenance { $options[$option] = $param; } else { $bits = explode( '=', $option, 2 ); - if( count( $bits ) > 1 ) { + if ( count( $bits ) > 1 ) { $option = $bits[0]; $param = $bits[1]; } else { @@ -509,8 +584,8 @@ abstract class Maintenance { } } elseif ( substr( $arg, 0, 1 ) == '-' ) { # Short options - for ( $p=1; $p<strlen( $arg ); $p++ ) { - $option = $arg{$p}; + 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 ) { @@ -539,35 +614,41 @@ abstract class Maintenance { 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 ) ) { + 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!" ); + 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 ); + + if ( $die ) { + $this->maybeHelp( true ); + } } /** * Handle the special variables that are global to all scripts */ protected function loadSpecialVars() { - if( $this->hasOption( 'dbuser' ) ) + if ( $this->hasOption( 'dbuser' ) ) { $this->mDbUser = $this->getOption( 'dbuser' ); - if( $this->hasOption( 'dbpass' ) ) + } + if ( $this->hasOption( 'dbpass' ) ) { $this->mDbPass = $this->getOption( 'dbpass' ); - if( $this->hasOption( 'quiet' ) ) + } + if ( $this->hasOption( 'quiet' ) ) { $this->mQuiet = true; - if( $this->hasOption( 'batch-size' ) ) + } + if ( $this->hasOption( 'batch-size' ) ) { $this->mBatchSize = $this->getOption( 'batch-size' ); + } } /** @@ -575,40 +656,62 @@ abstract class Maintenance { * @param $force boolean Whether to force the help to show, default false */ protected function maybeHelp( $force = false ) { - $screenWidth = 80; // TODO: Caculate this! + if( !$force && !$this->hasOption( 'help' ) ) { + return; + } + + $screenWidth = 80; // TODO: Caculate this! $tab = " "; $descWidth = $screenWidth - ( 2 * strlen( $tab ) ); - + ksort( $this->mParams ); - if( $this->hasOption( 'help' ) || $force ) { - $this->mQuiet = false; + $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 .= " <"; + // Description ... + if ( $this->mDescription ) { + $this->output( "\n" . $this->mDescription . "\n" ); + } + $output = "\nUsage: php " . basename( $this->mSelf ); + + // ... append parameters ... + if ( $this->mParams ) { + $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]"; + } + + // ... and append arguments. + if ( $this->mArgList ) { + $output .= ' '; + foreach ( $this->mArgList as $k => $arg ) { + if ( $arg['require'] ) { + $output .= '<' . $arg['name'] . '>'; + } else { + $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 ); } + $this->output( "$output\n\n" ); + + // Parameters description + foreach ( $this->mParams as $par => $info ) { + $this->output( + wordwrap( "$tab--$par: " . $info['desc'], $descWidth, + "\n$tab$tab" ) . "\n" + ); + } + + // Arguments description + foreach ( $this->mArgList as $info ) { + $openChar = $info['require'] ? '<' : '['; + $closeChar = $info['require'] ? '>' : ']'; + $this->output( + wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " . + $info['desc'], $descWidth, "\n$tab$tab" ) . "\n" + ); + } + + die( 1 ); } /** @@ -616,11 +719,11 @@ abstract class Maintenance { */ public function finalSetup() { global $wgCommandLineMode, $wgShowSQLErrors, $wgServer; - global $wgTitle, $wgProfiling, $IP, $wgDBadminuser, $wgDBadminpassword; + global $wgProfiling, $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() ) { + if ( ob_get_level() ) { ob_end_flush(); } # Same with these @@ -632,45 +735,54 @@ abstract class Maintenance { } # If these were passed, use them - if( $this->mDbUser ) + if ( $this->mDbUser ) { $wgDBadminuser = $this->mDbUser; - if( $this->mDbPass ) + } + if ( $this->mDbPass ) { $wgDBadminpassword = $this->mDbPass; + } if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) { $wgDBuser = $wgDBadminuser; $wgDBpassword = $wgDBadminpassword; - if( $wgDBservers ) { + if ( $wgDBservers ) { foreach ( $wgDBservers as $i => $server ) { $wgDBservers[$i]['user'] = $wgDBuser; $wgDBservers[$i]['password'] = $wgDBpassword; } } - if( isset( $wgLBFactoryConf['serverTemplate'] ) ) { + if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) { $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser; $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword; } + LBFactory::destroyInstance(); } - if ( defined( 'MW_CMDLINE_CALLBACK' ) ) { - $fn = MW_CMDLINE_CALLBACK; - $fn(); - } + $this->afterFinalSetup(); $wgShowSQLErrors = true; @set_time_limit( 0 ); - ini_set( 'memory_limit', $this->memoryLimit() ); + $this->adjustMemoryLimit(); $wgProfiling = false; // only for Profiler.php mode; avoids OOM errors } /** + * Execute a callback function at the end of initialisation + */ + protected function afterFinalSetup() { + if ( defined( 'MW_CMDLINE_CALLBACK' ) ) { + call_user_func( MW_CMDLINE_CALLBACK ); + } + } + + /** * Potentially debug globals. Originally a feature only * for refreshLinks */ public function globals() { - if( $this->hasOption( 'globals' ) ) { + if ( $this->hasOption( 'globals' ) ) { print_r( $GLOBALS ); } } @@ -711,13 +823,12 @@ abstract class Maintenance { # 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 ) { + # 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' ) { @@ -730,11 +841,13 @@ abstract class Maintenance { * @return String */ public function loadSettings() { - global $wgWikiFarm, $wgCommandLineMode, $IP, $DP; + global $wgWikiFarm, $wgCommandLineMode, $IP; $wgWikiFarm = false; if ( isset( $this->mOptions['conf'] ) ) { $settingsFile = $this->mOptions['conf']; + } else if ( defined("MW_CONFIG_FILE") ) { + $settingsFile = MW_CONFIG_FILE; } else { $settingsFile = "$IP/LocalSettings.php"; } @@ -749,19 +862,19 @@ abstract class Maintenance { if ( !is_readable( $settingsFile ) ) { $this->error( "A copy of your installation's LocalSettings.php\n" . - "must exist and be readable in the source directory.", true ); + "must exist and be readable in the source directory.\n" . + "Use --conf to specify it." , 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 + * @param $delete Boolean: whether or not to actually delete the records * @author Rob Church <robchur@gmail.com> */ - protected function purgeRedundantText( $delete = true ) { + public function purgeRedundantText( $delete = true ) { # Data should come off the master, wrapped in a transaction $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); @@ -771,27 +884,27 @@ abstract class Maintenance { $tbl_txt = $dbw->tableName( 'text' ); # Get "active" text records from the revisions table - $this->output( "Searching for active text records in 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 ) { + 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..." ); + $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 ) { + 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..." ); + $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 ) { + foreach ( $res as $row ) { $old[] = $row->old_id; } $this->output( "done.\n" ); @@ -801,8 +914,8 @@ abstract class Maintenance { $this->output( "$count inactive items found.\n" ); # Delete as appropriate - if( $delete && $count ) { - $this->output( "Deleting..." ); + if ( $delete && $count ) { + $this->output( 'Deleting...' ); $set = implode( ', ', $old ); $dbw->query( "DELETE FROM $tbl_txt WHERE old_id IN ( $set )" ); $this->output( "done.\n" ); @@ -823,7 +936,7 @@ abstract class Maintenance { * Get the list of available maintenance scripts. Note * that if you call this _before_ calling doMaintenance * you won't have any extensions in it yet - * @return array + * @return Array */ public static function getMaintenanceScripts() { global $wgMaintenanceScripts; @@ -835,8 +948,7 @@ abstract class Maintenance { * @return array */ protected static function getCoreScripts() { - if( !self::$mCoreScripts ) { - self::disableSetup(); + if ( !self::$mCoreScripts ) { $paths = array( dirname( __FILE__ ), dirname( __FILE__ ) . '/gearman', @@ -844,19 +956,20 @@ abstract class Maintenance { dirname( __FILE__ ) . '/storage', ); self::$mCoreScripts = array(); - foreach( $paths as $p ) { + foreach ( $paths as $p ) { $handle = opendir( $p ); - while( ( $file = readdir( $handle ) ) !== false ) { - if( $file == 'Maintenance.php' ) + while ( ( $file = readdir( $handle ) ) !== false ) { + if ( $file == 'Maintenance.php' ) { continue; + } $file = $p . '/' . $file; - if( is_dir( $file ) || !strpos( $file, '.php' ) || + 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 ) ) { + if ( array_key_exists( 'maintClass', $vars ) ) { self::$mCoreScripts[$vars['maintClass']] = $file; } } @@ -865,4 +978,167 @@ abstract class Maintenance { } return self::$mCoreScripts; } + + /** + * Lock the search index + * @param &$db Database object + */ + private function lockSearchindex( &$db ) { + $write = array( 'searchindex' ); + $read = array( 'page', 'revision', 'text', 'interwiki', 'l10n_cache' ); + $db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ ); + } + + /** + * Unlock the tables + * @param &$db Database object + */ + private function unlockSearchindex( &$db ) { + $db->unlockTables( __CLASS__ . '::' . __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 ); + } + + /** + * Perform a search index update with locking + * @param $maxLockTime Integer: the maximum time to keep the search index locked. + * @param $callback callback String: the function that will update the function. + * @param $dbw Database object + * @param $results + */ + public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) { + $lockTime = time(); + + # 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 ( $results as $row ) { + # Allow reads to be processed + if ( $maxLockTime && time() > $lockTime + $maxLockTime ) { + $this->output( " --- Relocking ---" ); + $this->relockSearchindex( $dbw ); + $lockTime = time(); + $this->output( "\n" ); + } + call_user_func( $callback, $dbw, $row ); + } + + # Unlock searchindex + if ( $maxLockTime ) { + $this->output( " --- Unlocking --" ); + $this->unlockSearchindex( $dbw ); + $this->output( "\n" ); + } + + } + + /** + * Update the searchindex table for a given pageid + * @param $dbw Database: a database write handle + * @param $pageId Integer: the page ID to update. + */ + public function updateSearchIndexForPage( $dbw, $pageId ) { + // Get current revision + $rev = Revision::loadFromPageId( $dbw, $pageId ); + $title = null; + if ( $rev ) { + $titleObj = $rev->getTitle(); + $title = $titleObj->getPrefixedDBkey(); + $this->output( "$title..." ); + # Update searchindex + $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getText() ); + $u->doUpdate(); + $this->output( "\n" ); + } + return $title; + } + + /** + * Prompt the console for input + * @param $prompt String what to begin the line with, like '> ' + * @return String response + */ + public static function readconsole( $prompt = '> ' ) { + static $isatty = null; + if ( is_null( $isatty ) ) { + if ( posix_isatty( 0 /*STDIN*/ ) ) { + $isatty = true; + } else { + $isatty = false; + } + } + + if ( $isatty && function_exists( 'readline' ) ) { + return readline( $prompt ); + } else { + if ( $isatty ) { + $st = self::readlineEmulation( $prompt ); + } else { + if ( feof( STDIN ) ) { + $st = false; + } else { + $st = fgets( STDIN, 1024 ); + } + } + if ( $st === false ) return false; + $resp = trim( $st ); + return $resp; + } + } + + /** + * Emulate readline() + * @param $prompt String what to begin the line with, like '> ' + * @return String + */ + private static function readlineEmulation( $prompt ) { + $bash = Installer::locateExecutableInDefaultPaths( array( 'bash' ) ); + if ( !wfIsWindows() && $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 ); + } } + +class FakeMaintenance extends Maintenance { + protected $mSelf = "FakeMaintenanceScript"; + public function execute() { + return; + } +} + diff --git a/maintenance/Makefile b/maintenance/Makefile index 82476139..a92751c9 100644 --- a/maintenance/Makefile +++ b/maintenance/Makefile @@ -3,7 +3,7 @@ help: @echo "Run 'make doc' to run the doxygen generation." test: - php parserTests.php --quiet + php tests/parserTests.php --quiet doc: php mwdocgen.php --all diff --git a/maintenance/addwiki.php b/maintenance/addwiki.php index 0cb4d74a..e86a8c5d 100644 --- a/maintenance/addwiki.php +++ b/maintenance/addwiki.php @@ -27,15 +27,16 @@ * @ingroup Wikimedia */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); 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' ); + $this->addArg( 'language', 'Language code of new site, e.g. en' ); + $this->addArg( 'site', 'Type of site, e.g. wikipedia' ); + $this->addArg( 'dbname', 'Name of database to create, e.g. enwiki' ); + $this->addArg( 'domain', 'Domain name of the wiki, e.g. en.wikipedia.org' ); } public function getDbType() { @@ -43,17 +44,19 @@ class AddWiki extends Maintenance { } public function execute() { - global $IP, $wgLanguageNames, $wgDefaultExternalStore, $wgNoDBParam; + global $IP, $wgDefaultExternalStore, $wgNoDBParam; $wgNoDBParam = true; - $lang = $this->getArg(0); - $site = $this->getArg(1); - $dbName = $this->getArg(2); + $lang = $this->getArg( 0 ); + $site = $this->getArg( 1 ); + $dbName = $this->getArg( 2 ); + $domain = $this->getArg( 3 ); + $languageNames = Language::getLanguageNames(); - if ( !isset( $wgLanguageNames[$lang] ) ) { + if ( !isset( $languageNames[$lang] ) ) { $this->error( "Language $lang not found in \$wgLanguageNames", true ); } - $name = $wgLanguageNames[$lang]; + $name = $languageNames[$lang]; $dbw = wfGetDB( DB_MASTER ); $common = "/home/wikipedia/common"; @@ -80,13 +83,14 @@ class AddWiki extends Maintenance { $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->sourceFile( "$IP/extensions/UsabilityInitiative/OptIn/OptIn.sql" ); $dbw->query( "INSERT INTO site_stats(ss_row_id) VALUES (1)" ); # Initialise external storage if ( is_array( $wgDefaultExternalStore ) ) { $stores = $wgDefaultExternalStore; - } elseif ( $stores ) { + } elseif ( $wgDefaultExternalStore ) { $stores = array( $wgDefaultExternalStore ); } else { $stores = array(); @@ -138,11 +142,21 @@ class AddWiki extends Maintenance { fclose( $file ); # Update the sublists - shell_exec("cd $common && ./refresh-dblist"); + shell_exec( "cd $common && ./refresh-dblist" ); - #print "Constructing interwiki SQL\n"; + # print "Constructing interwiki SQL\n"; # Rebuild interwiki tables - #passthru( '/home/wikipedia/conf/interwiki/update' ); + # passthru( '/home/wikipedia/conf/interwiki/update' ); + + $time = wfTimestamp( TS_RFC2822 ); + // These arguments need to be escaped twice: once for echo and once for at + $escDbName = wfEscapeShellArg( wfEscapeShellArg( $dbName ) ); + $escTime = wfEscapeShellArg( wfEscapeShellArg( $time ) ); + $escUcsite = wfEscapeShellArg( wfEscapeShellArg( $ucsite ) ); + $escName = wfEscapeShellArg( wfEscapeShellArg( $name ) ); + $escLang = wfEscapeShellArg( wfEscapeShellArg( $lang ) ); + $escDomain = wfEscapeShellArg( wfEscapeShellArg( $domain ) ); + shell_exec( "echo notifyNewProjects $escDbName $escTime $escUcsite $escName $escLang $escDomain | at now + 15 minutes" ); $this->output( "Script ended. You still have to: * Add any required settings in InitialiseSettings.php @@ -150,7 +164,7 @@ class AddWiki extends Maintenance { * 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== @@ -451,4 +465,4 @@ EOT; } $maintClass = "AddWiki"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/archives/patch-archive_ar_revid.sql b/maintenance/archives/patch-archive_ar_revid.sql new file mode 100644 index 00000000..67ee97b1 --- /dev/null +++ b/maintenance/archives/patch-archive_ar_revid.sql @@ -0,0 +1,4 @@ +-- Hopefully temporary index. +-- For https://bugzilla.wikimedia.org/show_bug.cgi?id=21279 +ALTER TABLE /*$wgDBprefix*/archive + ADD INDEX ar_revid ( ar_rev_id );
\ No newline at end of file diff --git a/maintenance/archives/patch-archive_kill_ar_page_revid.sql b/maintenance/archives/patch-archive_kill_ar_page_revid.sql new file mode 100644 index 00000000..2e6fe453 --- /dev/null +++ b/maintenance/archives/patch-archive_kill_ar_page_revid.sql @@ -0,0 +1,4 @@ +-- Used for killing the wrong index added during SVN for 1.17 +-- Won't affect most people, but it doesn't need to exist +ALTER TABLE /*$wgDBprefix*/archive + DROP INDEX ar_page_revid;
\ No newline at end of file diff --git a/maintenance/archives/patch-categorylinks-better-collation.sql b/maintenance/archives/patch-categorylinks-better-collation.sql new file mode 100644 index 00000000..c1499c15 --- /dev/null +++ b/maintenance/archives/patch-categorylinks-better-collation.sql @@ -0,0 +1,19 @@ +-- +-- patch-categorylinks-better-collation.sql +-- +-- Bugs 164, 1211, 23682. This is the second version of this patch; the +-- changes are also incorporated into patch-categorylinks-better-collation2.sql, +-- for the benefit of trunk users who applied the original. +-- +-- Due to bug 25254, the length limit of 255 bytes for cl_sortkey_prefix +-- is also enforced in php. If you change the length of that field, make +-- sure to also change the check in LinksUpdate.php. +ALTER TABLE /*$wgDBprefix*/categorylinks + CHANGE COLUMN cl_sortkey cl_sortkey varbinary(230) NOT NULL default '', + ADD COLUMN cl_sortkey_prefix varchar(255) binary NOT NULL default '', + ADD COLUMN cl_collation varbinary(32) NOT NULL default '', + ADD COLUMN cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page', + ADD INDEX (cl_collation), + DROP INDEX cl_sortkey, + ADD INDEX cl_sortkey (cl_to, cl_type, cl_sortkey, cl_from); +INSERT IGNORE INTO /*$wgDBprefix*/updatelog (ul_key) VALUES ('cl_fields_update'); diff --git a/maintenance/archives/patch-categorylinks-better-collation2.sql b/maintenance/archives/patch-categorylinks-better-collation2.sql new file mode 100644 index 00000000..e9574693 --- /dev/null +++ b/maintenance/archives/patch-categorylinks-better-collation2.sql @@ -0,0 +1,12 @@ +-- +-- patch-categorylinks-better-collation2.sql +-- +-- Bugs 164, 1211, 23682. This patch exists for trunk users who already +-- applied the first patch in its original version. The first patch was +-- updated to incorporate the changes as well, so as not to do two alters on a +-- large table unnecessarily for people upgrading from 1.16, so this will be +-- skipped if unneeded. +ALTER TABLE /*$wgDBprefix*/categorylinks + CHANGE COLUMN cl_sortkey cl_sortkey varbinary(230) NOT NULL default '', + CHANGE COLUMN cl_collation cl_collation varbinary(32) NOT NULL default ''; +INSERT IGNORE INTO /*$wgDBprefix*/updatelog (ul_key) VALUES ('cl_fields_update'); diff --git a/maintenance/archives/patch-iw_api_and_wikiid.sql b/maintenance/archives/patch-iw_api_and_wikiid.sql new file mode 100644 index 00000000..4384a715 --- /dev/null +++ b/maintenance/archives/patch-iw_api_and_wikiid.sql @@ -0,0 +1,9 @@ +-- +-- Add iw_api and iw_wikiid to interwiki table +-- + +ALTER TABLE /*_*/interwiki + ADD iw_api BLOB NOT NULL; +ALTER TABLE /*_*/interwiki + ADD iw_wikiid varchar(64) NOT NULL; + diff --git a/maintenance/archives/patch-iwlinks.sql b/maintenance/archives/patch-iwlinks.sql new file mode 100644 index 00000000..89b34cb1 --- /dev/null +++ b/maintenance/archives/patch-iwlinks.sql @@ -0,0 +1,16 @@ +-- +-- Track inline interwiki links +-- +CREATE TABLE /*_*/iwlinks ( + -- page_id of the referring page + iwl_from int unsigned NOT NULL default 0, + + -- Interwiki prefix code of the target + iwl_prefix varbinary(20) NOT NULL default '', + + -- Title of the target, including namespace + iwl_title varchar(255) binary NOT NULL default '' +) /*$wgDBTableOptions*/; + +CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title); +CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from); diff --git a/maintenance/archives/patch-kill-iwl_pft.sql b/maintenance/archives/patch-kill-iwl_pft.sql new file mode 100644 index 00000000..96e14356 --- /dev/null +++ b/maintenance/archives/patch-kill-iwl_pft.sql @@ -0,0 +1,7 @@ +-- +-- Kill the old iwl_prefix_from_title index, which may be present on some +-- installs if they ran update.php between it being added and being renamed +-- + +DROP INDEX /*i*/iwl_prefix_from_title ON /*_*/iwlinks; + diff --git a/maintenance/archives/patch-kill-iwl_prefix.sql b/maintenance/archives/patch-kill-iwl_prefix.sql new file mode 100644 index 00000000..1cd9b454 --- /dev/null +++ b/maintenance/archives/patch-kill-iwl_prefix.sql @@ -0,0 +1,7 @@ +-- +-- Kill the old iwl_prefix index, which may be present on some +-- installs if they ran update.php between it being added and being renamed +-- + +DROP INDEX /*i*/iwl_prefix ON /*_*/iwlinks; + diff --git a/maintenance/archives/patch-langlinks-ll_lang-20.sql b/maintenance/archives/patch-langlinks-ll_lang-20.sql new file mode 100644 index 00000000..ce026382 --- /dev/null +++ b/maintenance/archives/patch-langlinks-ll_lang-20.sql @@ -0,0 +1,3 @@ +ALTER TABLE /*$wgDBprefix*/langlinks + MODIFY `ll_lang` + VARBINARY(20) NOT NULL DEFAULT '';
\ No newline at end of file diff --git a/maintenance/archives/patch-mime_minor_length.sql b/maintenance/archives/patch-mime_minor_length.sql index 3a3c5c4f..8b63d1f0 100644 --- a/maintenance/archives/patch-mime_minor_length.sql +++ b/maintenance/archives/patch-mime_minor_length.sql @@ -7,4 +7,4 @@ ALTER TABLE /*_*/image 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 +INSERT INTO /*_*/updatelog(ul_key) VALUES ('mime_minor_length'); diff --git a/maintenance/archives/patch-module_deps.sql b/maintenance/archives/patch-module_deps.sql new file mode 100644 index 00000000..ffc94829 --- /dev/null +++ b/maintenance/archives/patch-module_deps.sql @@ -0,0 +1,12 @@ +-- Table for tracking which local files a module depends on that aren't +-- registered directly. +-- Currently only used for tracking images that CSS depends on +CREATE TABLE /*_*/module_deps ( + -- Module name + md_module varbinary(255) NOT NULL, + -- Skin name + md_skin varbinary(32) NOT NULL, + -- JSON blob with file dependencies + md_deps mediumblob NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin); diff --git a/maintenance/archives/patch-msg_resource.sql b/maintenance/archives/patch-msg_resource.sql new file mode 100644 index 00000000..f4f35339 --- /dev/null +++ b/maintenance/archives/patch-msg_resource.sql @@ -0,0 +1,20 @@ +-- Table for storing JSON message blobs for the resource loader +CREATE TABLE /*_*/msg_resource ( + -- Resource name + mr_resource varbinary(255) NOT NULL, + -- Language code + mr_lang varbinary(32) NOT NULL, + -- JSON blob. This is an incomplete JSON object, i.e. without the wrapping {} + mr_blob mediumblob NOT NULL, + -- Timestamp of last update + mr_timestamp binary(14) NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource(mr_resource, mr_lang); + +-- Table for administering which message is contained in which resource +CREATE TABLE /*_*/msg_resource_links ( + mrl_resource varbinary(255) NOT NULL, + -- Message key + mrl_message varbinary(255) NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource); diff --git a/maintenance/archives/patch-profiling.sql b/maintenance/archives/patch-profiling.sql index e748ca31..29663341 100644 --- a/maintenance/archives/patch-profiling.sql +++ b/maintenance/archives/patch-profiling.sql @@ -1,11 +1,12 @@ -- profiling table -- This is optional -CREATE TABLE /*$wgDBprefix*/profiling ( +CREATE TABLE /*_*/profiling ( pf_count int NOT NULL default 0, pf_time float NOT NULL default 0, pf_memory float NOT NULL default 0, pf_name varchar(255) NOT NULL default '', - pf_server varchar(30) NOT NULL default '', - UNIQUE KEY pf_name_server (pf_name, pf_server) + pf_server varchar(30) NOT NULL default '' ) ENGINE=HEAP; + +CREATE UNIQUE INDEX /*i*/pf_name_server ON /*_*/profiling (pf_name, pf_server)
\ No newline at end of file diff --git a/maintenance/archives/patch-rename-iwl_prefix.sql b/maintenance/archives/patch-rename-iwl_prefix.sql new file mode 100644 index 00000000..4b11b36b --- /dev/null +++ b/maintenance/archives/patch-rename-iwl_prefix.sql @@ -0,0 +1,4 @@ +-- +-- Recreates the iwl_prefix index for the iwlinks table +-- +CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from); diff --git a/maintenance/archives/patch-tc-timestamp.sql b/maintenance/archives/patch-tc-timestamp.sql index 4d90cf34..3f7dde41 100644 --- a/maintenance/archives/patch-tc-timestamp.sql +++ b/maintenance/archives/patch-tc-timestamp.sql @@ -1,4 +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'); +INSERT INTO /*_*/updatelog(ul_key) VALUES ('convert transcache field'); diff --git a/maintenance/testRunner.sql b/maintenance/archives/patch-testrun.sql index 8591d81d..8591d81d 100644 --- a/maintenance/testRunner.sql +++ b/maintenance/archives/patch-testrun.sql diff --git a/maintenance/archives/patch-ul_value.sql b/maintenance/archives/patch-ul_value.sql new file mode 100644 index 00000000..50f4e9a8 --- /dev/null +++ b/maintenance/archives/patch-ul_value.sql @@ -0,0 +1,4 @@ +-- Add the ul_value column to updatelog + +ALTER TABLE /*_*/updatelog + add ul_value blob; diff --git a/maintenance/archives/upgradeLogging.php b/maintenance/archives/upgradeLogging.php index f79bbabc..54a82c09 100644 --- a/maintenance/archives/upgradeLogging.php +++ b/maintenance/archives/upgradeLogging.php @@ -6,7 +6,7 @@ * @ingroup MaintenanceArchive */ -require( dirname(__FILE__).'/../commandLine.inc' ); +require( dirname( __FILE__ ) . '/../commandLine.inc' ); class UpdateLogging { var $dbw; @@ -130,7 +130,7 @@ EOT; } else { $conds = array( 'log_timestamp > ' . $this->dbw->addQuotes( $copyPos ) ); } - $srcRes = $this->dbw->select( $srcTable, '*', $conds, __METHOD__, + $srcRes = $this->dbw->select( $srcTable, '*', $conds, __METHOD__, array( 'LIMIT' => $batchSize, 'ORDER BY' => 'log_timestamp' ) ); if ( ! $srcRes->numRows() ) { diff --git a/maintenance/attachLatest.php b/maintenance/attachLatest.php index 67f3088b..e6287f43 100644 --- a/maintenance/attachLatest.php +++ b/maintenance/attachLatest.php @@ -3,7 +3,7 @@ * quick hackjob to fix damages imports on wikisource * page records have page_latest wrong * - * Copyright (C) 2005 Brion Vibber <brion@pobox.com> + * Copyright © 2005 Brion Vibber <brion@pobox.com> * http://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify @@ -21,19 +21,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); 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 ); @@ -43,7 +44,7 @@ class AttachLatest extends Maintenance { __METHOD__ ); $n = 0; - foreach( $result as $row ) { + foreach ( $result as $row ) { $pageId = intval( $row->page_id ); $title = Title::makeTitle( $row->page_namespace, $row->page_title ); $name = $title->getPrefixedText(); @@ -51,31 +52,30 @@ class AttachLatest extends Maintenance { 'MAX(rev_timestamp)', array( 'rev_page' => $pageId ), __METHOD__ ); - if( !$latestTime ) { - $this->output( wfWikiID()." $pageId [[$name]] can't find latest rev time?!\n" ); + 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" ); + 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') ) { + $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') ) { + if ( !$this->hasOption( 'fix' ) ) { $this->output( "This was a dry run; rerun with --fix to update page_latest.\n" ); } } } $maintClass = "AttachLatest"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/backup.inc b/maintenance/backup.inc index 30bd0d88..9ed463c9 100644 --- a/maintenance/backup.inc +++ b/maintenance/backup.inc @@ -1,6 +1,8 @@ <?php /** - * Copyright (C) 2005 Brion Vibber <brion@pobox.com> + * Base classes for database dumpers + * + * Copyright © 2005 Brion Vibber <brion@pobox.com> * http://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify @@ -27,7 +29,7 @@ */ class DumpDBZip2Output extends DumpPipeOutput { function DumpDBZip2Output( $file ) { - parent::DumpPipeOutput( "dbzip2", $file ); + parent::__construct( "dbzip2", $file ); } } @@ -67,16 +69,16 @@ class BackupDumper { } /** - * @param string $name - * @param string $class name of output filter plugin class + * @param $name String + * @param $class String: name of output filter plugin class */ function registerOutput( $name, $class ) { $this->outputTypes[$name] = $class; } /** - * @param string $name - * @param string $class name of filter plugin class + * @param $name String + * @param $class String: name of filter plugin class */ function registerFilter( $name, $class ) { $this->filterTypes[$name] = $class; @@ -84,12 +86,13 @@ class BackupDumper { /** * Load a plugin and register it - * @param string $class Name of plugin class; must have a static 'register' - * method that takes a BackupDumper as a parameter. - * @param string $file Full or relative path to the PHP file to load, or empty + * + * @param $class String: name of plugin class; must have a static 'register' + * method that takes a BackupDumper as a parameter. + * @param $file String: full or relative path to the PHP file to load, or empty */ function loadPlugin( $class, $file ) { - if( $file != '' ) { + if ( $file != '' ) { require_once( $file ); } $register = array( $class, 'register' ); @@ -97,37 +100,36 @@ class BackupDumper { } /** - * @param array $args - * @return array - * @static + * @param $args Array + * @return Array */ function processArgs( $args ) { $sink = null; $sinks = array(); - foreach( $args as $arg ) { + foreach ( $args as $arg ) { $matches = array(); - if( preg_match( '/^--(.+?)(?:=(.+?)(?::(.+?))?)?$/', $arg, $matches ) ) { + if ( preg_match( '/^--(.+?)(?:=(.+?)(?::(.+?))?)?$/', $arg, $matches ) ) { @list( /* $full */ , $opt, $val, $param ) = $matches; switch( $opt ) { case "plugin": $this->loadPlugin( $val, $param ); break; case "output": - if( !is_null( $sink ) ) { + if ( !is_null( $sink ) ) { $sinks[] = $sink; } - if( !isset( $this->outputTypes[$val] ) ) { + if ( !isset( $this->outputTypes[$val] ) ) { wfDie( "Unrecognized output sink type '$val'\n" ); } $type = $this->outputTypes[$val]; $sink = new $type( $param ); break; case "filter": - if( is_null( $sink ) ) { + if ( is_null( $sink ) ) { $this->progress( "Warning: assuming stdout for filter output\n" ); $sink = new DumpOutput(); } - if( !isset( $this->filterTypes[$val] ) ) { + if ( !isset( $this->filterTypes[$val] ) ) { wfDie( "Unrecognized filter type '$val'\n" ); } $type = $this->filterTypes[$val]; @@ -145,9 +147,9 @@ class BackupDumper { $this->server = $val; break; case "force-normal": - if( !function_exists( 'utf8_normalize' ) ) { - dl( "php_utfnormal.so" ); - if( !function_exists( 'utf8_normalize' ) ) { + if ( !function_exists( 'utf8_normalize' ) ) { + wfDl( "php_utfnormal.so" ); + if ( !function_exists( 'utf8_normalize' ) ) { wfDie( "Failed to load UTF-8 normalization extension. " . "Install or remove --force-normal parameter to use slower code.\n" ); } @@ -159,12 +161,12 @@ class BackupDumper { } } - if( is_null( $sink ) ) { + if ( is_null( $sink ) ) { $sink = new DumpOutput(); } $sinks[] = $sink; - if( count( $sinks ) > 1 ) { + if ( count( $sinks ) > 1 ) { return new DumpMultiWriter( $sinks ); } else { return $sink; @@ -178,7 +180,7 @@ class BackupDumper { function dump( $history, $text = WikiExporter::TEXT ) { # Notice messages will foul up your XML output even if they're # relatively harmless. - if( ini_get( 'display_errors' ) ) + if ( ini_get( 'display_errors' ) ) ini_set( 'display_errors', 'stderr' ); $this->initProgress( $history ); @@ -190,18 +192,18 @@ class BackupDumper { $wrapper = new ExportProgressFilter( $this->sink, $this ); $exporter->setOutputSink( $wrapper ); - if( !$this->skipHeader ) + if ( !$this->skipHeader ) $exporter->openStream(); # Log item dumps: all or by range - if( $history & WikiExporter::LOGS ) { - if( $this->startId || $this->endId ) { + if ( $history & WikiExporter::LOGS ) { + if ( $this->startId || $this->endId ) { $exporter->logsByRange( $this->startId, $this->endId ); } else { $exporter->allLogs(); } # Page dumps: all or by page ID range - } else if( is_null( $this->pages ) ) { - if( $this->startId || $this->endId ) { + } else if ( is_null( $this->pages ) ) { + if ( $this->startId || $this->endId ) { $exporter->pagesByRange( $this->startId, $this->endId ); } else { $exporter->allPages(); @@ -211,44 +213,45 @@ class BackupDumper { $exporter->pagesByName( $this->pages ); } - if( !$this->skipFooter ) + if ( !$this->skipFooter ) $exporter->closeStream(); $this->report( true ); } - + /** * Initialise starting time and maximum revision count. * We'll make ETA calculations based an progress, assuming relatively * constant per-revision rate. - * @param int $history WikiExporter::CURRENT or WikiExporter::FULL + * @param $history Integer: WikiExporter::CURRENT or WikiExporter::FULL */ function initProgress( $history = WikiExporter::FULL ) { - $table = ($history == WikiExporter::CURRENT) ? 'page' : 'revision'; - $field = ($history == WikiExporter::CURRENT) ? 'page_id' : 'rev_id'; - + $table = ( $history == WikiExporter::CURRENT ) ? 'page' : 'revision'; + $field = ( $history == WikiExporter::CURRENT ) ? 'page_id' : 'rev_id'; + $dbr = wfGetDB( DB_SLAVE ); - $this->maxCount = $dbr->selectField( $table, "MAX($field)", '', 'BackupDumper::dump' ); + $this->maxCount = $dbr->selectField( $table, "MAX($field)", '', __METHOD__ ); $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. + * @todo 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() { $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. $db->setTimeout( 3600 * 24 ); - + return $db; } - + function __destruct() { - if( isset( $this->lb ) ) { + if ( isset( $this->lb ) ) { $this->lb->closeAll(); } } @@ -270,16 +273,16 @@ class BackupDumper { } function report( $final = false ) { - if( $final xor ( $this->revCount % $this->reportingInterval == 0 ) ) { + if ( $final xor ( $this->revCount % $this->reportingInterval == 0 ) ) { $this->showReport(); } } function showReport() { - if( $this->reporting ) { + if ( $this->reporting ) { $delta = wfTime() - $this->startTime; $now = wfTimestamp( TS_DB ); - if( $delta ) { + if ( $delta ) { $rate = $this->pageCount / $delta; $revrate = $this->revCount / $delta; $portion = $this->revCount / $this->maxCount; @@ -302,7 +305,7 @@ class BackupDumper { class ExportProgressFilter extends DumpFilter { function ExportProgressFilter( &$sink, &$progress ) { - parent::DumpFilter( $sink ); + parent::__construct( $sink ); $this->progress = $progress; } diff --git a/maintenance/backupPrefetch.inc b/maintenance/backupPrefetch.inc index 512af1c7..9d743137 100644 --- a/maintenance/backupPrefetch.inc +++ b/maintenance/backupPrefetch.inc @@ -1,42 +1,10 @@ <?php - -// Some smart guy removed XMLReader's global constants from PHP 5.1 -// and replaced them with class constants. Breaking source compatibility -// is SUPER awesome, and I love languages which do this constantly! -$xmlReaderConstants = array( - "NONE", - "ELEMENT", - "ATTRIBUTE", - "TEXT", - "CDATA", - "ENTITY_REF", - "ENTITY", - "PI", - "COMMENT", - "DOC", - "DOC_TYPE", - "DOC_FRAGMENT", - "NOTATION", - "WHITESPACE", - "SIGNIFICANT_WHITESPACE", - "END_ELEMENT", - "END_ENTITY", - "XML_DECLARATION", - "LOADDTD", - "DEFAULTATTRS", - "VALIDATE", - "SUBST_ENTITIES" ); -foreach( $xmlReaderConstants as $name ) { - $fullName = "XMLREADER_$name"; - $newName = "XMLReader::$name"; - if( !defined( $fullName ) ) { - if( defined( $newName ) ) { - define( $fullName, constant( $newName ) ); - } else { - // broken or missing the extension... - } - } -} +/** + * Helper class for the --prefetch option of dumpTextPass.php + * + * @file + * @ingroup Maintenance + */ /** * Readahead helper for making large MediaWiki data dumps; @@ -51,7 +19,6 @@ foreach( $xmlReaderConstants as $name ) { * - text contents are immutable and should not change once * recorded, so the previous dump is a reliable source * - * Requires PHP 5 and the XMLReader PECL extension. * @ingroup Maintenance */ class BaseDump { @@ -60,9 +27,12 @@ class BaseDump { var $atPageEnd = false; var $lastPage = 0; var $lastRev = 0; + var $infiles = null; function BaseDump( $infile ) { + $this->infiles = explode(';',$infile); $this->reader = new XMLReader(); + $infile = array_shift($this->infiles); $this->reader->open( $infile ); } @@ -71,26 +41,26 @@ class BaseDump { * from the dump stream. May return null if the page is * unavailable. * - * @param int $page ID number of page to read - * @param int $rev ID number of revision to read + * @param $page Integer: ID number of page to read + * @param $rev Integer: ID number of revision to read * @return string or null */ function prefetch( $page, $rev ) { $page = intval( $page ); $rev = intval( $rev ); - while( $this->lastPage < $page && !$this->atEnd ) { + while ( $this->lastPage < $page && !$this->atEnd ) { $this->debug( "BaseDump::prefetch at page $this->lastPage, looking for $page" ); $this->nextPage(); } - if( $this->lastPage > $page || $this->atEnd ) { + if ( $this->lastPage > $page || $this->atEnd ) { $this->debug( "BaseDump::prefetch already past page $page looking for rev $rev [$this->lastPage, $this->lastRev]" ); return null; } - while( $this->lastRev < $rev && !$this->atEnd && !$this->atPageEnd ) { + while ( $this->lastRev < $rev && !$this->atEnd && !$this->atPageEnd ) { $this->debug( "BaseDump::prefetch at page $this->lastPage, rev $this->lastRev, looking for $page, $rev" ); $this->nextRev(); } - if( $this->lastRev == $rev && !$this->atEnd ) { + if ( $this->lastRev == $rev && !$this->atEnd ) { $this->debug( "BaseDump::prefetch hit on $page, $rev [$this->lastPage, $this->lastRev]" ); return $this->nextText(); } else { @@ -101,22 +71,27 @@ class BaseDump { function debug( $str ) { wfDebug( $str . "\n" ); - //global $dumper; - //$dumper->progress( $str ); + // global $dumper; + // $dumper->progress( $str ); } /** * @access private */ function nextPage() { - if( $this->skipTo( 'page', 'mediawiki' ) ) { - if( $this->skipTo( 'id' ) ) { + if ( $this->skipTo( 'page', 'mediawiki' ) ) { + if ( $this->skipTo( 'id' ) ) { $this->lastPage = intval( $this->nodeContents() ); $this->lastRev = 0; $this->atPageEnd = false; } } else { - $this->atEnd = true; + $this->close(); + if (count($this->infiles)) { + $infile = array_shift($this->infiles); + $this->reader->open( $infile ); + $this->atEnd = false; + } } } @@ -124,8 +99,8 @@ class BaseDump { * @access private */ function nextRev() { - if( $this->skipTo( 'revision' ) ) { - if( $this->skipTo( 'id' ) ) { + if ( $this->skipTo( 'revision' ) ) { + if ( $this->skipTo( 'id' ) ) { $this->lastRev = intval( $this->nodeContents() ); } } else { @@ -144,16 +119,16 @@ class BaseDump { /** * @access private */ - function skipTo( $name, $parent='page' ) { - if( $this->atEnd ) { + function skipTo( $name, $parent = 'page' ) { + if ( $this->atEnd ) { return false; } - while( $this->reader->read() ) { - if( $this->reader->nodeType == XMLREADER_ELEMENT && + while ( $this->reader->read() ) { + if ( $this->reader->nodeType == XMLReader::ELEMENT && $this->reader->name == $name ) { return true; } - if( $this->reader->nodeType == XMLREADER_END_ELEMENT && + if ( $this->reader->nodeType == XMLReader::END_ELEMENT && $this->reader->name == $parent ) { $this->debug( "BaseDump::skipTo found </$parent> searching for <$name>" ); return false; @@ -166,25 +141,26 @@ class BaseDump { * Shouldn't something like this be built-in to XMLReader? * Fetches text contents of the current element, assuming * no sub-elements or such scary things. - * @return string + * + * @return String * @access private */ function nodeContents() { - if( $this->atEnd ) { + if ( $this->atEnd ) { return null; } - if( $this->reader->isEmptyElement ) { + if ( $this->reader->isEmptyElement ) { return ""; } $buffer = ""; - while( $this->reader->read() ) { + while ( $this->reader->read() ) { switch( $this->reader->nodeType ) { - case XMLREADER_TEXT: -// case XMLREADER_WHITESPACE: - case XMLREADER_SIGNIFICANT_WHITESPACE: + case XMLReader::TEXT: +// case XMLReader::WHITESPACE: + case XMLReader::SIGNIFICANT_WHITESPACE: $buffer .= $this->reader->value; break; - case XMLREADER_END_ELEMENT: + case XMLReader::END_ELEMENT: return $buffer; } } diff --git a/maintenance/benchmarkPurge.php b/maintenance/benchmarkPurge.php index d167cf92..8360ef85 100644 --- a/maintenance/benchmarkPurge.php +++ b/maintenance/benchmarkPurge.php @@ -17,39 +17,40 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); 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."; } - + public function execute() { - global $wgUseSquid; - if( !$wgUseSquid ) { - $this->error( "Squid purge benchmark doesn't do much without squid support on.". true ); + global $wgUseSquid, $wgSquidServers; + 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') ) ); + if ( $this->hasOption( 'count' ) ) { + $lengths = array( intval( $this->getOption( 'count' ) ) ); } else { $lengths = array( 1, 10, 100 ); } - foreach( $lengths as $length ) { + foreach ( $lengths as $length ) { $urls = $this->randomUrlList( $length ); $trial = $this->benchSquid( $urls ); $this->output( $trial . "\n" ); } } } - - /** + + /** * Run a bunch of URLs through SquidUpdate::purge() * to benchmark Squid response times. * @param $urls array A bunch of URLs to purge @@ -57,7 +58,7 @@ class BenchmarkPurge extends Maintenance { */ private function benchSquid( $urls, $trials = 1 ) { $start = wfTime(); - for( $i = 0; $i < $trials; $i++) { + for ( $i = 0; $i < $trials; $i++ ) { SquidUpdate::purge( $urls ); } $delta = wfTime() - $start; @@ -66,37 +67,37 @@ class BenchmarkPurge extends Maintenance { 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 */ private function randomUrlList( $length ) { $list = array(); - for( $i = 0; $i < $length; $i++ ) { + for ( $i = 0; $i < $length; $i++ ) { $list[] = $this->randomUrl(); } return $list; } - - /** + + /** * Return a random URL of the wiki. Not necessarily an actual title in the - * database, but at least a URL that looks like one. + * 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). + + /** + * 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') ) ); + for ( $i = 0; $i < $length; $i++ ) { + $str .= chr( mt_rand( ord( 'a' ), ord( 'z' ) ) ); } return ucfirst( $str ); } diff --git a/maintenance/benchmarks/Benchmarker.php b/maintenance/benchmarks/Benchmarker.php new file mode 100644 index 00000000..66789ea4 --- /dev/null +++ b/maintenance/benchmarks/Benchmarker.php @@ -0,0 +1,72 @@ +<?php +/** + * Create a doxygen subgroup of Maintenance for benchmarks + * @defgroup Benchmark Benchmark + * @ingroup Maintenance + */ + +/** + * TODO: report PHP version, OS .. + * @file + * @ingroup Benchmark + */ + +require_once( dirname( __FILE__ ) . '/../Maintenance.php' ); +abstract class Benchmarker extends Maintenance { + private $results; + + public function __construct() { + parent::__construct(); + $this->addOption( 'count', "How many time to run a benchmark", false, true ); + } + + public function bench( array $benchs ) { + $bench_number = 0; + $count = $this->getOption( 'count', 100 ); + + foreach( $benchs as $bench ) { + // handle empty args + if(!array_key_exists( 'args', $bench )) { + $bench['args'] = array(); + } + + $bench_number++; + $start = wfTime(); + for( $i=0; $i<$count; $i++ ) { + call_user_func_array( $bench['function'], $bench['args'] ); + } + $delta = wfTime() - $start; + + // function passed as a callback + if( is_array( $bench['function'] ) ) { + $ret = get_class( $bench['function'][0] ). '->' . $bench['function'][1]; + $bench['function'] = $ret; + } + + $this->results[$bench_number] = array( + 'function' => $bench['function'], + 'arguments' => $bench['args'], + 'count' => $count, + 'delta' => $delta, + 'average' => $delta / $count, + ); + } + } + + public function getFormattedResults( ) { + $ret = ''; + foreach( $this->results as $res ) { + // show function with args + $ret .= sprintf( "%s times: function %s(%s) :\n", + $res['count'], + $res['function'], + join( ', ', $res['arguments'] ) + ); + $ret .= sprintf( " %6.2fms (%6.2fms each)\n", + $res['delta'] * 1000, + $res['average'] * 1000 + ); + } + return $ret; + } +} diff --git a/maintenance/benchmarks/bench_HTTP_HTTPS.php b/maintenance/benchmarks/bench_HTTP_HTTPS.php new file mode 100644 index 00000000..13d15fce --- /dev/null +++ b/maintenance/benchmarks/bench_HTTP_HTTPS.php @@ -0,0 +1,38 @@ +<?php +/** + * This come from r75429 message + * @author Platonides + */ + +require_once( dirname( __FILE__ ) . '/Benchmarker.php' ); +class bench_HTTP_HTTPS extends Benchmarker { + + public function __construct() { + parent::__construct(); + } + + public function execute() { + $this->bench( array( + array( 'function' => array( $this, 'getHTTP' ) ), + array( 'function' => array( $this, 'getHTTPS' ) ), + )); + print $this->getFormattedResults(); + } + + static function doRequest( $proto ) { + Http::get( "$proto://localhost/" ); + } + + // bench function 1 + function getHTTP() { + $this->doRequest( 'http' ); + } + + // bench function 2 + function getHTTPS() { + $this->doRequest( 'https' ); + } +} + +$maintClass = 'bench_HTTP_HTTPS'; +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/benchmarks/bench_wfIsWindows.php b/maintenance/benchmarks/bench_wfIsWindows.php new file mode 100644 index 00000000..2f759e07 --- /dev/null +++ b/maintenance/benchmarks/bench_wfIsWindows.php @@ -0,0 +1,42 @@ +<?php +/** + * This come from r75429 message + * @author Platonides + */ + +require_once( dirname( __FILE__ ) . '/Benchmarker.php' ); +class bench_wfIsWindows extends Benchmarker { + + public function __construct() { + parent::__construct(); + } + + public function execute() { + $this->bench( array( + array( 'function' => array( $this, 'wfIsWindows' ) ), + array( 'function' => array( $this, 'wfIsWindowsCached' ) ), + )); + print $this->getFormattedResults(); + } + + static function is_win() { + return substr( php_uname(), 0, 7 ) == 'Windows' ; + } + + // bench function 1 + function wfIsWindows() { + return self::is_win(); + } + + // bench function 2 + function wfIsWindowsCached() { + static $isWindows = null; + if( $isWindows == null ) { + $isWindows = self::is_win(); + } + return $isWindows; + } +} + +$maintClass = 'bench_wfIsWindows'; +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/changePassword.php b/maintenance/changePassword.php index fbc3fa76..568952b9 100644 --- a/maintenance/changePassword.php +++ b/maintenance/changePassword.php @@ -2,6 +2,8 @@ /** * Change the password of a given user * + * Copyright © 2005, Ævar Arnfjörð Bjarmason + * * 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 @@ -17,13 +19,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @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 * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class ChangePassword extends Maintenance { public function __construct() { @@ -32,21 +33,21 @@ class ChangePassword extends Maintenance { $this->addOption( "password", "The password to use", true, true ); $this->mDescription = "Change a user's password"; } - + public function execute() { - $user = User::newFromName( $this->getOption('user') ); - if( !$user->getId() ) { - $this->error( "No such user: " . $this->getOption('user'), true ); + $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->setPassword( $this->getOption( 'password' ) ); $user->saveSettings(); $this->output( "Password set for " . $user->getName() . "\n" ); - } catch( PasswordError $pwe ) { + } catch ( PasswordError $pwe ) { $this->error( $pwe->getText(), true ); } } } $maintClass = "ChangePassword"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/checkAutoLoader.php b/maintenance/checkAutoLoader.php index 9c8f29e3..d199b6fe 100644 --- a/maintenance/checkAutoLoader.php +++ b/maintenance/checkAutoLoader.php @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class CheckAutoLoader extends Maintenance { public function __construct() { @@ -31,8 +32,8 @@ class CheckAutoLoader extends Maintenance { global $wgAutoloadLocalClasses, $IP; $files = array_unique( $wgAutoloadLocalClasses ); - foreach( $files as $file ) { - if( function_exists( 'parsekit_compile_file' ) ){ + foreach ( $files as $file ) { + if ( function_exists( 'parsekit_compile_file' ) ) { $parseInfo = parsekit_compile_file( "$IP/$file" ); $classes = array_keys( $parseInfo['class_table'] ); } else { @@ -43,7 +44,7 @@ class CheckAutoLoader extends Maintenance { } foreach ( $classes as $class ) { if ( !isset( $wgAutoloadLocalClasses[$class] ) ) { - //printf( "%-50s Unlisted, in %s\n", $class, $file ); + // 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" ); @@ -54,4 +55,4 @@ class CheckAutoLoader extends Maintenance { } $maintClass = "CheckAutoLoader"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/checkBadRedirects.php b/maintenance/checkBadRedirects.php index 32f04f45..52bfa65a 100644 --- a/maintenance/checkBadRedirects.php +++ b/maintenance/checkBadRedirects.php @@ -18,10 +18,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ - -require_once( dirname(__FILE__) . '/Maintenance.php' ); + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class CheckBadRedirects extends Maintenance { public function __construct() { @@ -34,19 +35,19 @@ class CheckBadRedirects extends Maintenance { $dbr = wfGetDB( DB_SLAVE ); $result = $dbr->select( array( 'page' ), - array( 'page_namespace','page_title', 'page_latest' ), + 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 ) { + + foreach ( $result as $row ) { $title = Title::makeTitle( $row->page_namespace, $row->page_title ); $rev = Revision::newFromId( $row->page_latest ); - if( $rev ) { + if ( $rev ) { $target = Title::newFromRedirect( $rev->getText() ); - if( !$target ) { + if ( !$target ) { $this->output( $title->getPrefixedText() . "\n" ); } } @@ -56,4 +57,4 @@ class CheckBadRedirects extends Maintenance { } $maintClass = "CheckBadRedirects"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/checkImages.php b/maintenance/checkImages.php index 5dcaac28..96b93f22 100644 --- a/maintenance/checkImages.php +++ b/maintenance/checkImages.php @@ -17,9 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class CheckImages extends Maintenance { @@ -28,16 +29,16 @@ class CheckImages extends Maintenance { $this->mDescription = "Check images to see if they exist, are readable, etc"; $this->setBatchSize( 1000 ); } - + public function execute() { $start = ''; $dbr = wfGetDB( DB_SLAVE ); $numImages = 0; $numGood = 0; - + do { - $res = $dbr->select( 'image', '*', array( 'img_name > ' . $dbr->addQuotes( $start ) ), + $res = $dbr->select( 'image', '*', array( 'img_name > ' . $dbr->addQuotes( $start ) ), __METHOD__, array( 'LIMIT' => $this->mBatchSize ) ); foreach ( $res as $row ) { $numImages++; @@ -53,30 +54,30 @@ class CheckImages extends Maintenance { $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" ); } } $maintClass = "CheckImages"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/checkSyntax.php b/maintenance/checkSyntax.php index 22832dce..396cac5f 100644 --- a/maintenance/checkSyntax.php +++ b/maintenance/checkSyntax.php @@ -17,9 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ - + require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class CheckSyntax extends Maintenance { @@ -33,8 +34,8 @@ class CheckSyntax extends Maintenance { $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); + 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' ); } @@ -49,16 +50,16 @@ class CheckSyntax extends Maintenance { // 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)' ); + $str = 'Checking syntax (using ' . ( $useParseKit ? + 'parsekit' : ' php -l, this can take a long time' ) . ")\n"; $this->output( $str ); - foreach( $this->mFiles as $f ) { - if( $useParseKit ) { + foreach ( $this->mFiles as $f ) { + if ( $useParseKit ) { $this->checkFileWithParsekit( $f ); } else { $this->checkFileWithCli( $f ); } - if( !$this->hasOption( 'syntax-only' ) ) { + if ( !$this->hasOption( 'syntax-only' ) ) { $this->checkForMistakes( $f ); } } @@ -76,18 +77,17 @@ class CheckSyntax extends Maintenance { $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", + "EmailPage/PHPMailer", "FCKeditor/fckeditor/", '\bphplot-', "/svggraph/", "\bjsmin.php$", - "OggHandler/PEAR/", + "PEAR/File_Ogg/", "QPoll/Excel/", "/geshi/", "/smarty/", @@ -105,7 +105,8 @@ class CheckSyntax extends Maintenance { if ( !$f ) { $this->error( "Can't open file $file\n", true ); } - while( $path = trim( fgets( $f ) ) ) { + $path = trim( fgets( $f ) ); + while ( $path ) { $this->addPath( $path ); } fclose( $f ); @@ -113,6 +114,7 @@ class CheckSyntax extends Maintenance { } elseif ( $this->hasOption( 'modified' ) ) { $this->output( "Retrieving list from Subversion... " ); $parentDir = wfEscapeShellArg( dirname( __FILE__ ) . '/..' ); + $retval = null; $output = wfShellExec( "svn status --ignore-externals $parentDir", $retval ); if ( $retval ) { $this->error( "Error retrieving list from Subversion!\n", true ); @@ -122,7 +124,7 @@ class CheckSyntax extends Maintenance { preg_match_all( '/^\s*[AM].{7}(.*?)\r?$/m', $output, $matches ); foreach ( $matches[1] as $file ) { - if ( self::isSuitableFile( $file ) && !is_dir( $file ) ) { + if ( $this->isSuitableFile( $file ) && !is_dir( $file ) ) { $this->mFiles[] = $file; } } @@ -131,20 +133,20 @@ class CheckSyntax extends Maintenance { $this->output( 'Building file list...', 'listfiles' ); - // Only check files in these directories. + // Only check files in these directories. // Don't just put $IP, because the recursive dir thingie goes into all subdirs - $dirs = array( + $dirs = array( $IP . '/includes', $IP . '/config', $IP . '/languages', $IP . '/maintenance', $IP . '/skins', ); - if( $this->hasOption( 'with-extensions' ) ) { + if ( $this->hasOption( 'with-extensions' ) ) { $dirs[] = $IP . '/extensions'; } - foreach( $dirs as $d ) { + foreach ( $dirs as $d ) { $this->addDirectoryContent( $d ); } @@ -158,15 +160,16 @@ class CheckSyntax extends Maintenance { $this->output( 'done.', 'listfiles' ); } - + /** * Returns true if $file is of a type we can check */ private function isSuitableFile( $file ) { + $file = str_replace( '\\', '/', $file ); $ext = pathinfo( $file, PATHINFO_EXTENSION ); if ( $ext != 'php' && $ext != 'inc' && $ext != 'php5' ) return false; - foreach( $this->mIgnorePaths as $regex ) { + foreach ( $this->mIgnorePaths as $regex ) { $m = array(); if ( preg_match( "~{$regex}~", $file, $m ) ) return false; @@ -203,7 +206,7 @@ class CheckSyntax extends Maintenance { */ private function addDirectoryContent( $dir ) { $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator( $dir ), + new RecursiveDirectoryIterator( $dir ), RecursiveIteratorIterator::SELF_FIRST ); foreach ( $iterator as $file ) { @@ -248,8 +251,8 @@ class CheckSyntax extends Maintenance { * @return boolean */ private function checkFileWithCli( $file ) { - $res = exec( 'php -l ' . wfEscapeShellArg( $file ) ); - if( strpos( $res, 'No syntax errors detected' ) === false ) { + $res = exec( 'php -l ' . wfEscapeShellArg( $file ) ); + if ( strpos( $res, 'No syntax errors detected' ) === false ) { $this->mFailures[$file] = $res; $this->output( $res . "\n" ); return false; @@ -265,7 +268,7 @@ class CheckSyntax extends Maintenance { * @return boolean */ private function checkForMistakes( $file ) { - foreach( $this->mNoStyleCheckPaths as $regex ) { + foreach ( $this->mNoStyleCheckPaths as $regex ) { $m = array(); if ( preg_match( "~{$regex}~", $file, $m ) ) return; @@ -292,5 +295,5 @@ class CheckSyntax extends Maintenance { } $maintClass = "CheckSyntax"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/checkUsernames.php b/maintenance/checkUsernames.php index 85a3d157..9b98721d 100644 --- a/maintenance/checkUsernames.php +++ b/maintenance/checkUsernames.php @@ -19,11 +19,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class CheckUsernames extends Maintenance { @@ -44,11 +45,11 @@ class CheckUsernames extends Maintenance { foreach ( $res as $row ) { if ( ! User::isValidUserName( $row->user_name ) ) { $this->error( sprintf( "%s: %6d: '%s'\n", wfWikiID(), $row->user_id, $row->user_name ) ); - wfDebugLog( 'checkUsernames', $out ); + wfDebugLog( 'checkUsernames', $row->user_name ); } } } } $maintClass = "CheckUsernames"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/cleanupCaps.php b/maintenance/cleanupCaps.php index 6a48ea83..2d945a52 100644 --- a/maintenance/cleanupCaps.php +++ b/maintenance/cleanupCaps.php @@ -1,12 +1,12 @@ <?php -/* +/** * Script to clean up broken page links when somebody turns on $wgCapitalLinks. * * Usage: php cleanupCaps.php [--dry-run] * Options: * --dry-run don't actually try moving them * - * Copyright (C) 2005 Brion Vibber <brion@pobox.com> + * Copyright © 2005 Brion Vibber <brion@pobox.com> * http://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify @@ -24,11 +24,12 @@ * 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 + * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/cleanupTable.inc' ); +require_once( dirname( __FILE__ ) . '/cleanupTable.inc' ); class CapsCleanup extends TableCleanup { public function __construct() { @@ -42,7 +43,7 @@ class CapsCleanup extends TableCleanup { $this->namespace = intval( $this->getOption( 'namespace', 0 ) ); $this->dryrun = $this->hasOption( 'dry-run' ); $wgUser->setName( 'Conversion script' ); - if( $wgCapitalLinks ) + if ( $wgCapitalLinks ) $this->error( "\$wgCapitalLinks is on -- no need for caps links cleanup.", true ); $this->runTable( array( @@ -59,31 +60,31 @@ class CapsCleanup extends TableCleanup { $display = $current->getPrefixedText(); $upper = $row->page_title; $lower = $wgContLang->lcfirst( $row->page_title ); - if( $upper == $lower ) { + if ( $upper == $lower ) { $this->output( "\"$display\" already lowercase.\n" ); return $this->progress( 0 ); } $target = Title::makeTitle( $row->page_namespace, $lower ); $targetDisplay = $target->getPrefixedText(); - if( $target->exists() ) { + if ( $target->exists() ) { $this->output( "\"$display\" skipped; \"$targetDisplay\" already exists\n" ); return $this->progress( 0 ); } - if( $this->dryrun ) { + if ( $this->dryrun ) { $this->output( "\"$display\" -> \"$targetDisplay\": DRY RUN, NOT MOVED\n" ); $ok = true; } else { $ok = $current->moveTo( $target, false, 'Converting page titles to lowercase' ); $this->output( "\"$display\" -> \"$targetDisplay\": $ok\n" ); } - if( $ok === true ) { + if ( $ok === true ) { $this->progress( 1 ); - if( $row->page_namespace == $this->namespace ) { + if ( $row->page_namespace == $this->namespace ) { $talk = $target->getTalkPage(); $row->page_namespace = $talk->getNamespace(); - if( $talk->exists() ) { + if ( $talk->exists() ) { return $this->processRow( $row ); } } @@ -94,4 +95,4 @@ class CapsCleanup extends TableCleanup { } $maintClass = "CapsCleanup"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/cleanupDupes.inc b/maintenance/cleanupDupes.inc deleted file mode 100644 index bb408007..00000000 --- a/maintenance/cleanupDupes.inc +++ /dev/null @@ -1,128 +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 - -/** - * If on the old non-unique indexes, check the cur table for duplicate - * entries and remove them... - * - * @file - * @ingroup Maintenance - */ - -function fixDupes( $fixthem = false) { - $dbw = wfGetDB( DB_MASTER ); - $cur = $dbw->tableName( 'cur' ); - $old = $dbw->tableName( 'old' ); - $dbw->query( "LOCK TABLES $cur WRITE, $old WRITE" ); - echo "Checking for duplicate cur table entries... (this may take a while on a large wiki)\n"; - $res = $dbw->query( <<<END -SELECT cur_namespace,cur_title,count(*) as c,min(cur_id) as id - FROM $cur - GROUP BY cur_namespace,cur_title -HAVING c > 1 -END - ); - $n = $dbw->numRows( $res ); - echo "Found $n titles with duplicate entries.\n"; - if( $n > 0 ) { - if( $fixthem ) { - echo "Correcting...\n"; - } else { - echo "Just a demo...\n"; - } - while( $row = $dbw->fetchObject( $res ) ) { - $ns = intval( $row->cur_namespace ); - $title = $dbw->addQuotes( $row->cur_title ); - - # Get the first responding ID; that'll be the one we keep. - $id = $dbw->selectField( 'cur', 'cur_id', array( - 'cur_namespace' => $row->cur_namespace, - 'cur_title' => $row->cur_title ) ); - - echo "$ns:$row->cur_title (canonical ID $id)\n"; - if( $id != $row->id ) { - echo " ** minimum ID $row->id; "; - $timeMin = $dbw->selectField( 'cur', 'cur_timestamp', array( - 'cur_id' => $row->id ) ); - $timeFirst = $dbw->selectField( 'cur', 'cur_timestamp', array( - 'cur_id' => $id ) ); - if( $timeMin == $timeFirst ) { - echo "timestamps match at $timeFirst; ok\n"; - } else { - echo "timestamps don't match! min: $timeMin, first: $timeFirst; "; - if( $timeMin > $timeFirst ) { - $id = $row->id; - echo "keeping minimum: $id\n"; - } else { - echo "keeping first: $id\n"; - } - } - } - - if( $fixthem ) { - $dbw->query( <<<END -INSERT - INTO $old - (old_namespace, old_title, old_text, - old_comment, old_user, old_user_text, - old_timestamp, old_minor_edit, old_flags, - inverse_timestamp) -SELECT cur_namespace, cur_title, cur_text, - cur_comment, cur_user, cur_user_text, - cur_timestamp, cur_minor_edit, '', - inverse_timestamp - FROM $cur - WHERE cur_namespace=$ns - AND cur_title=$title - AND cur_id != $id -END - ); - $dbw->query( <<<END -DELETE - FROM $cur - WHERE cur_namespace=$ns - AND cur_title=$title - AND cur_id != $id -END - ); - } - } - } - $dbw->query( 'UNLOCK TABLES' ); - if( $fixthem ) { - echo "Done.\n"; - } else { - echo "Run again with --fix option to delete the duplicates.\n"; - } -} - -function checkDupes( $fixthem = false, $indexonly = false ) { - $dbw = wfGetDB( DB_MASTER ); - if( $dbw->indexExists( 'cur', 'name_title' ) && - $dbw->indexUnique( 'cur', 'name_title' ) ) { - echo wfWikiID().": cur table has the current unique index; no duplicate entries.\n"; - } elseif( $dbw->indexExists( 'cur', 'name_title_dup_prevention' ) ) { - echo wfWikiID().": cur table has a temporary name_title_dup_prevention unique index; no duplicate entries.\n"; - } else { - echo wfWikiID().": cur table has the old non-unique index and may have duplicate entries.\n"; - if( !$indexonly ) { - fixDupes( $fixthem ); - } - } -} diff --git a/maintenance/cleanupImages.php b/maintenance/cleanupImages.php index db13f4c9..b25b9bbe 100644 --- a/maintenance/cleanupImages.php +++ b/maintenance/cleanupImages.php @@ -1,5 +1,5 @@ <?php -/* +/** * Script to clean up broken, unparseable upload filenames. * * Usage: php cleanupImages.php [--fix] @@ -24,11 +24,12 @@ * 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( dirname(__FILE__) . '/cleanupTable.inc' ); +require_once( dirname( __FILE__ ) . '/cleanupTable.inc' ); class ImageCleanup extends TableCleanup { protected $defaultParams = array( @@ -47,38 +48,38 @@ class ImageCleanup extends TableCleanup { global $wgContLang; $source = $row->img_name; - if( $source == '' ) { + if ( $source == '' ) { // Ye olde empty rows. Just kill them. $this->killRow( $source ); return $this->progress( 1 ); } - + $cleaned = $source; - + // About half of old bad image names have percent-codes $cleaned = rawurldecode( $cleaned ); // We also have some HTML entities there $cleaned = Sanitizer::decodeCharReferences( $cleaned ); - + // Some are old latin-1 $cleaned = $wgContLang->checkTitleEncoding( $cleaned ); - + // Many of remainder look like non-normalized unicode $cleaned = $wgContLang->normalize( $cleaned ); - + $title = Title::makeTitleSafe( NS_FILE, $cleaned ); - - if( is_null( $title ) ) { + + if ( is_null( $title ) ) { $this->output( "page $source ($cleaned) is illegal.\n" ); $safe = $this->buildSafeTitle( $cleaned ); - if( $safe === false ) + if ( $safe === false ) return $this->progress( 0 ); $this->pokeFile( $source, $safe ); return $this->progress( 1 ); } - if( $title->getDBkey() !== $source ) { + if ( $title->getDBkey() !== $source ) { $munged = $title->getDBkey(); $this->output( "page $source ($munged) doesn't match self.\n" ); $this->pokeFile( $source, $munged ); @@ -89,7 +90,7 @@ class ImageCleanup extends TableCleanup { } private function killRow( $name ) { - if( $this->dryrun ) { + if ( $this->dryrun ) { $this->output( "DRY RUN: would delete bogus row '$name'\n" ); } else { $this->output( "deleting bogus row '$name'\n" ); @@ -99,7 +100,7 @@ class ImageCleanup extends TableCleanup { __METHOD__ ); } } - + private function filePath( $name ) { if ( !isset( $this->repo ) ) { $this->repo = RepoGroup::singleton()->getLocalRepo(); @@ -114,14 +115,14 @@ class ImageCleanup extends TableCleanup { private function pageExists( $name, $db ) { return $db->selectField( 'page', '1', array( 'page_namespace' => NS_FILE, 'page_title' => $name ), __METHOD__ ); } - + private function pokeFile( $orig, $new ) { $path = $this->filePath( $orig ); - if( !file_exists( $path ) ) { + if ( !file_exists( $path ) ) { $this->output( "missing file: $path\n" ); return $this->killRow( $orig ); } - + $db = wfGetDB( DB_MASTER ); /* @@ -134,18 +135,18 @@ class ImageCleanup extends TableCleanup { $version = 0; $final = $new; $conflict = ( $this->imageExists( $final, $db ) || - ( $this->pageExists( $orig, $db ) && $this->pageExists( $final, $db ) ) ); - - while( $conflict ) { + ( $this->pageExists( $orig, $db ) && $this->pageExists( $final, $db ) ) ); + + 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 ) { + + if ( $this->dryrun ) { $this->output( "DRY RUN: would rename $path to $finalPath\n" ); } else { $this->output( "renaming $path to $finalPath\n" ); @@ -164,14 +165,14 @@ class ImageCleanup extends TableCleanup { array( 'page_title' => $orig, 'page_namespace' => NS_FILE ), __METHOD__ ); $dir = dirname( $finalPath ); - if( !file_exists( $dir ) ) { - if( !wfMkdirParents( $dir ) ) { + if ( !file_exists( $dir ) ) { + if ( !wfMkdirParents( $dir ) ) { $this->log( "RENAME FAILED, COULD NOT CREATE $dir" ); $db->rollback(); return; } } - if( rename( $path, $finalPath ) ) { + if ( rename( $path, $finalPath ) ) { $db->commit(); } else { $this->error( "RENAME FAILED" ); @@ -191,16 +192,16 @@ class ImageCleanup extends TableCleanup { "/([^$wgLegalTitleChars]|~)/", array( $this, 'hexChar' ), $name ); - + $test = Title::makeTitleSafe( NS_FILE, $x ); - if( is_null( $test ) || $test->getDBkey() !== $x ) { + if ( is_null( $test ) || $test->getDBkey() !== $x ) { $this->error( "Unable to generate safe title from '$name', got '$x'" ); return false; } - + return $x; } } $maintClass = "ImageCleanup"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/cleanupRemovedModules.php b/maintenance/cleanupRemovedModules.php new file mode 100644 index 00000000..fb8afd2d --- /dev/null +++ b/maintenance/cleanupRemovedModules.php @@ -0,0 +1,89 @@ +<?php +/** + * Maintenance script to remove cache entries for removed ResourceLoader modules + * from the database + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + * @author Roan Kattouw + */ + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); + +class CleanupRemovedModules extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = 'Remove cache entries for removed ResourceLoader modules from the database'; + $this->addOption( 'batchsize', 'Delete rows in batches of this size. Default: 500', false, true ); + $this->addOption( 'max-slave-lag', 'If the slave lag exceeds this many seconds, wait until it drops below this value. Default: 5', false, true ); + } + + public function execute() { + $dbw = wfGetDB( DB_MASTER ); + $rl = new ResourceLoader(); + $moduleNames = $rl->getModuleNames(); + $moduleList = implode( ', ', array_map( array( $dbw, 'addQuotes' ), $moduleNames ) ); + $limit = max( 1, intval( $this->getOption( 'batchsize', 500 ) ) ); + $maxlag = intval( $this->getOption( 'max-slave-lag', 5 ) ); + + $this->output( "Cleaning up module_deps table...\n" ); + $i = 1; + $modDeps = $dbw->tableName( 'module_deps' ); + do { + // $dbw->delete() doesn't support LIMIT :( + $where = $moduleList ? "md_module NOT IN ($moduleList)" : '1=1'; + $dbw->query( "DELETE FROM $modDeps WHERE $where LIMIT $limit", __METHOD__ ); + $numRows = $dbw->affectedRows(); + $this->output( "Batch $i: $numRows rows\n" ); + $i++; + wfWaitForSlaves( $maxlag ); + } while( $numRows > 0 ); + $this->output( "done\n" ); + + $this->output( "Cleaning up msg_resource table...\n" ); + $i = 1; + + $mrRes = $dbw->tableName( 'msg_resource' ); + do { + $where = $moduleList ? "mr_resource NOT IN ($moduleList)" : '1=1'; + $dbw->query( "DELETE FROM $mrRes WHERE $where LIMIT $limit", __METHOD__ ); + $numRows = $dbw->affectedRows(); + $this->output( "Batch $i: $numRows rows\n" ); + $i++; + wfWaitForSlaves( $maxlag ); + } while( $numRows > 0 ); + $this->output( "done\n" ); + + $this->output( "Cleaning up msg_resource_links table...\n" ); + $i = 1; + $msgResLinks = $dbw->tableName( 'msg_resource_links' ); + do { + $where = $moduleList ? "mrl_resource NOT IN ($moduleList)" : '1=1'; + $dbw->query( "DELETE FROM $msgResLinks WHERE $where LIMIT $limit", __METHOD__ ); + $numRows = $dbw->affectedRows(); + $this->output( "Batch $i: $numRows rows\n" ); + $i++; + wfWaitForSlaves( $maxlag ); + } while( $numRows > 0 ); + $this->output( "done\n" ); + } +} + +$maintClass = "CleanupRemovedModules"; +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/cleanupSpam.php b/maintenance/cleanupSpam.php index e78ffe41..39abe4c5 100644 --- a/maintenance/cleanupSpam.php +++ b/maintenance/cleanupSpam.php @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class CleanupSpam extends Maintenance { public function __construct() { @@ -31,7 +32,7 @@ class CleanupSpam extends Maintenance { } public function execute() { - global $wgLocalDatabases; + global $wgLocalDatabases, $wgUser; $username = wfMsg( 'spambot_username' ); $wgUser = User::newFromName( $username ); @@ -44,15 +45,15 @@ class CleanupSpam extends Maintenance { if ( !$like ) { $this->error( "Not a valid hostname specification: $spec", true ); } - - if ( $this->hasOption('all') ) { + + 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(*)', + $count = $dbr->selectField( 'externallinks', 'COUNT(*)', array( 'el_index' . $dbr->buildLike( $like ) ), __METHOD__ ); if ( $count ) { $found = true; @@ -68,7 +69,7 @@ class CleanupSpam extends Maintenance { // Clean up spam on this wiki $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'externallinks', array( 'DISTINCT el_from' ), + $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" ); @@ -87,15 +88,15 @@ class CleanupSpam extends Maintenance { $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(); + # $rev = $rev->getPrevious(); $revId = $title->getPreviousRevisionID( $revId ); if ( $revId ) { $rev = Revision::newFromTitle( $title, $revId ); @@ -114,14 +115,12 @@ class CleanupSpam extends Maintenance { // 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 ); - + $article->doEdit( '', wfMsg( 'spam_blanking', $domain ) ); } else { // Revert to this revision $this->output( "reverting\n" ); $article = new Article( $title ); - $article->updateArticle( $rev->getText(), wfMsg( 'spam_reverting', $domain ), false, false ); + $article->doEdit( $rev->getText(), wfMsg( 'spam_reverting', $domain ), EDIT_UPDATE ); } $dbw->commit(); wfDoUpdates(); @@ -130,4 +129,4 @@ class CleanupSpam extends Maintenance { } $maintClass = "CleanupSpam"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/cleanupTable.inc b/maintenance/cleanupTable.inc index 3549a9a1..67a32510 100644 --- a/maintenance/cleanupTable.inc +++ b/maintenance/cleanupTable.inc @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class TableCleanup extends Maintenance { protected $defaultParams = array( @@ -44,7 +45,7 @@ class TableCleanup extends Maintenance { global $wgUser; $wgUser->setName( 'Conversion script' ); $this->dryrun = $this->hasOption( 'dry-run' ); - if( $this->dryrun ) { + if ( $this->dryrun ) { $this->output( "Checking for bad titles...\n" ); } else { $this->output( "Checking and fixing bad titles...\n" ); @@ -63,7 +64,7 @@ class TableCleanup extends Maintenance { protected function progress( $updated ) { $this->updated += $updated; $this->processed++; - if( $this->processed % $this->reportInterval != 0 ) { + if ( $this->processed % $this->reportInterval != 0 ) { return; } $portion = $this->processed / $this->count; @@ -74,7 +75,7 @@ class TableCleanup extends Maintenance { $estimatedTotalTime = $delta / $portion; $eta = $this->startTime + $estimatedTotalTime; - $this->output( + $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 ) ), @@ -84,7 +85,7 @@ class TableCleanup extends Maintenance { $this->processed, $this->count, $this->processed / $delta, - $updateRate * 100.0 + $updateRate * 100.0 ) ); flush(); @@ -94,9 +95,9 @@ class TableCleanup extends Maintenance { $dbr = wfGetDB( DB_SLAVE ); if ( array_diff( array_keys( $params ), - array( 'table', 'conds', 'index', 'callback' ) ) ) + array( 'table', 'conds', 'index', 'callback' ) ) ) { - throw new MWException( __METHOD__.': Missing parameter ' . implode( ', ', $params ) ); + throw new MWException( __METHOD__ . ': Missing parameter ' . implode( ', ', $params ) ); } $table = $params['table']; diff --git a/maintenance/cleanupTitles.php b/maintenance/cleanupTitles.php index ed714b2d..f03b7957 100644 --- a/maintenance/cleanupTitles.php +++ b/maintenance/cleanupTitles.php @@ -1,12 +1,12 @@ <?php -/* +/** * Script to clean up broken, unparseable titles. * * Usage: php cleanupTitles.php [--fix] * Options: * --fix Actually clean up titles; otherwise just checks for them * - * Copyright (C) 2005 Brion Vibber <brion@pobox.com> + * Copyright © 2005 Brion Vibber <brion@pobox.com> * http://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify @@ -24,11 +24,12 @@ * 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( dirname(__FILE__) . '/cleanupTable.inc' ); +require_once( dirname( __FILE__ ) . '/cleanupTable.inc' ); class TitleCleanup extends TableCleanup { public function __construct() { @@ -42,7 +43,7 @@ class TitleCleanup extends TableCleanup { $verified = $wgContLang->normalize( $display ); $title = Title::newFromText( $verified ); - if( !is_null( $title ) + if ( !is_null( $title ) && $title->canExist() && $title->getNamespace() == $row->page_namespace && $title->getDBkey() === $row->page_title ) @@ -50,10 +51,10 @@ class TitleCleanup extends TableCleanup { return $this->progress( 0 ); // all is fine } - if( $row->page_namespace == NS_FILE && $this->fileExists( $row->page_title ) ) { + if ( $row->page_namespace == NS_FILE && $this->fileExists( $row->page_title ) ) { $this->output( "file $row->page_title needs cleanup, please run cleanupImages.php.\n" ); return $this->progress( 0 ); - } elseif( is_null( $title ) ) { + } elseif ( is_null( $title ) ) { $this->output( "page $row->page_id ($display) is illegal.\n" ); $this->moveIllegalPage( $row ); return $this->progress( 1 ); @@ -77,23 +78,23 @@ class TitleCleanup extends TableCleanup { $legalized = preg_replace_callback( "!([^$legal])!", array( &$this, 'hexChar' ), $row->page_title ); - if( $legalized == '.' ) $legalized = '(dot)'; - if( $legalized == '_' ) $legalized = '(space)'; + if ( $legalized == '.' ) $legalized = '(dot)'; + if ( $legalized == '_' ) $legalized = '(space)'; $legalized = 'Broken/' . $legalized; $title = Title::newFromText( $legalized ); - if( is_null( $title ) ) { + if ( is_null( $title ) ) { $clean = 'Broken/id:' . $row->page_id; $this->output( "Couldn't legalize; form '$legalized' still invalid; using '$clean'\n" ); $title = Title::newFromText( $clean ); - } elseif( $title->exists() ) { + } elseif ( $title->exists() ) { $clean = 'Broken/id:' . $row->page_id; $this->output( "Legalized for '$legalized' exists; using '$clean'\n" ); $title = Title::newFromText( $clean ); } $dest = $title->getDBkey(); - if( $this->dryrun ) { + if ( $this->dryrun ) { $this->output( "DRY RUN: would rename $row->page_id ($row->page_namespace,'$row->page_title') to ($row->page_namespace,'$dest')\n" ); } else { $this->output( "renaming $row->page_id ($row->page_namespace,'$row->page_title') to ($row->page_namespace,'$dest')\n" ); @@ -106,28 +107,34 @@ class TitleCleanup extends TableCleanup { } protected function moveInconsistentPage( $row, $title ) { - if( $title->exists() || $title->getInterwiki() ) { - if( $title->getInterwiki() ) { + if ( $title->exists() || $title->getInterwiki() || !$title->canExist() ) { + if ( $title->getInterwiki() || !$title->canExist() ) { $prior = $title->getPrefixedDbKey(); } else { $prior = $title->getDBkey(); } + + # Old cleanupTitles could move articles there. See bug 23147. + $ns = $row->page_namespace; + if ( $ns < 0 ) $ns = 0; + $clean = 'Broken/' . $prior; - $verified = Title::makeTitleSafe( $row->page_namespace, $clean ); - if( $verified->exists() ) { + $verified = Title::makeTitleSafe( $ns, $clean ); + if ( $verified->exists() ) { $blah = "Broken/id:" . $row->page_id; $this->output( "Couldn't legalize; form '$clean' exists; using '$blah'\n" ); - $verified = Title::makeTitleSafe( $row->page_namespace, $blah ); + $verified = Title::makeTitleSafe( $ns, $blah ); } $title = $verified; } - if( is_null( $title ) ) { + if ( is_null( $title ) ) { $this->error( "Something awry; empty title.", true ); } $ns = $title->getNamespace(); $dest = $title->getDBkey(); - if( $this->dryrun ) { - $this->output( "DRY RUN: would rename $row->page_id ($row->page_namespace,'$row->page_title') to ($row->page_namespace,'$dest')\n" ); + + if ( $this->dryrun ) { + $this->output( "DRY RUN: would rename $row->page_id ($row->page_namespace,'$row->page_title') to ($ns,'$dest')\n" ); } else { $this->output( "renaming $row->page_id ($row->page_namespace,'$row->page_title') to ($ns,'$dest')\n" ); $dbw = wfGetDB( DB_MASTER ); @@ -145,4 +152,4 @@ class TitleCleanup extends TableCleanup { } $maintClass = "TitleCleanup"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/cleanupWatchlist.php b/maintenance/cleanupWatchlist.php index ed84b268..a9b20fea 100644 --- a/maintenance/cleanupWatchlist.php +++ b/maintenance/cleanupWatchlist.php @@ -1,5 +1,5 @@ <?php -/* +/** * Script to remove broken, unparseable titles in the Watchlist. * * Usage: php cleanupWatchlist.php [--fix] @@ -24,11 +24,12 @@ * 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( dirname(__FILE__) . '/cleanupTable.inc' ); +require_once( dirname( __FILE__ ) . '/cleanupTable.inc' ); class WatchlistCleanup extends TableCleanup { protected $defaultParams = array( @@ -58,7 +59,7 @@ class WatchlistCleanup extends TableCleanup { $verified = $wgContLang->normalize( $display ); $title = Title::newFromText( $verified ); - if( $row->wl_user == 0 || is_null( $title ) || !$title->equals( $current ) ) { + if ( $row->wl_user == 0 || is_null( $title ) || !$title->equals( $current ) ) { $this->output( "invalid watch by {$row->wl_user} for ({$row->wl_namespace}, \"{$row->wl_title}\")\n" ); $updated = $this->removeWatch( $row ); $this->progress( $updated ); @@ -68,7 +69,7 @@ class WatchlistCleanup extends TableCleanup { } private function removeWatch( $row ) { - if( !$this->dryrun && $this->hasOption( 'fix' ) ) { + if ( !$this->dryrun && $this->hasOption( 'fix' ) ) { $dbw = wfGetDB( DB_MASTER ); $dbw->delete( 'watchlist', array( 'wl_user' => $row->wl_user, @@ -84,4 +85,4 @@ class WatchlistCleanup extends TableCleanup { } $maintClass = "WatchlistCleanup"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/clear_interwiki_cache.php b/maintenance/clear_interwiki_cache.php index a3510a06..953bd4ce 100644 --- a/maintenance/clear_interwiki_cache.php +++ b/maintenance/clear_interwiki_cache.php @@ -18,10 +18,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class ClearInterwikiCache extends Maintenance { @@ -42,7 +43,7 @@ class ClearInterwikiCache extends Maintenance { foreach ( $wgLocalDatabases as $db ) { $this->output( "$db..." ); foreach ( $prefixes as $prefix ) { - $wgMemc->delete("$db:interwiki:$prefix"); + $wgMemc->delete( "$db:interwiki:$prefix" ); } $this->output( "done\n" ); } @@ -50,4 +51,4 @@ class ClearInterwikiCache extends Maintenance { } $maintClass = "ClearInterwikiCache"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/clear_stats.php b/maintenance/clear_stats.php index 6a6a4981..8f91864e 100644 --- a/maintenance/clear_stats.php +++ b/maintenance/clear_stats.php @@ -1,7 +1,7 @@ <?php /** * This script remove all statistics tracking 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 @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class clear_stats extends Maintenance { @@ -32,22 +33,22 @@ class clear_stats extends Maintenance { 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"); + $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 ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/commandLine.inc b/maintenance/commandLine.inc index 332527ba..4ae753ba 100644 --- a/maintenance/commandLine.inc +++ b/maintenance/commandLine.inc @@ -3,10 +3,11 @@ /** * Backwards-compatibility wrapper for old-style maintenance scripts */ -require( dirname(__FILE__) . '/Maintenance.php' ); +require( dirname( __FILE__ ) . '/Maintenance.php' ); +global $optionsWithArgs; if ( !isset( $optionsWithArgs ) ) { - $optionsWithArgs = array(); + $optionsWithArgs = array(); } class CommandLineInc extends Maintenance { @@ -42,5 +43,5 @@ class CommandLineInc extends Maintenance { } $maintClass = 'CommandLineInc'; -require( DO_MAINTENANCE ); +require( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/convertLinks.inc b/maintenance/convertLinks.inc deleted file mode 100644 index 7c7b8aff..00000000 --- a/maintenance/convertLinks.inc +++ /dev/null @@ -1,218 +0,0 @@ -<?php -/** - * @file - * @todo document - * @ingroup Maintenance - */ - -/** */ -function convertLinks() { - global $wgDBtype; - if( $wgDBtype == 'postgres' ) { - wfOut( "Links table already ok on Postgres.\n" ); - return; - } - - wfOut( "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' ); - - // 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; - } - - $res = $dbw->query( "SELECT COUNT(*) AS count FROM $links" ); - $row = $dbw->fetchObject($res); - $numRows = $row->count; - $dbw->freeResult( $res ); - - if ( $numRows == 0 ) { - wfOut( "Updating schema (no rows to convert)...\n" ); - createTempTable(); - } else { - if ( $logPerformance ) { $fh = fopen ( $perfLogFilename, "w" ); } - $baseTime = $startTime = getMicroTime(); - # Create a title -> cur_id map - wfOut( "Loading IDs from $cur table...\n" ); - performanceLog ( "Reading $numRows rows from cur table...\n" ); - 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) { - performanceLog( $curRowsRead . " " . (getMicroTime() - $baseTime) . "\n" ); - wfOut( "\t$curRowsRead rows of $cur table read.\n" ); - } - } - } - $dbw->freeResult( $res ); - $dbw->bufferResults( true ); - wfOut( "Finished loading IDs.\n\n" ); - performanceLog( "Took " . (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. - createTempTable(); - performanceLog( "Resetting timer.\n\n" ); - $baseTime = getMicroTime(); - wfOut( "Processing $numRows rows from $links table...\n" ); - performanceLog( "Processing $numRows rows from $links table...\n" ); - 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); - #wfOut( "rowOffset: $rowOffset\ttuplesAdded: $tuplesAdded\tnumBadLinks: $numBadLinks\n" ); - if ( $tuplesAdded != 0 ) { - if ($reportLinksConvProgress) { - wfOut( "Inserting $tuplesAdded tuples into $links_temp..." ); - } - $dbw->query( implode("",$sqlWrite) ); - $totalTuplesInserted += $tuplesAdded; - if ($reportLinksConvProgress) - wfOut( " done. Total $totalTuplesInserted tuples inserted.\n" ); - performanceLog( $totalTuplesInserted . " " . (getMicroTime() - $baseTime) . "\n" ); - } - } - wfOut( "$totalTuplesInserted valid titles and $numBadLinks invalid titles were processed.\n\n" ); - performanceLog( "$totalTuplesInserted valid titles and $numBadLinks invalid titles were processed.\n" ); - performanceLog( "Total execution time: " . (getMicroTime() - $startTime) . " seconds.\n" ); - if ( $logPerformance ) { fclose ( $fh ); } - } - #-------------------------------------------------------------------- - - if ( $overwriteLinksTable ) { - $dbConn = Database::newFromParams( $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname ); - if (!($dbConn->isOpen())) { - wfOut( "Opening connection to database failed.\n" ); - return; - } - # Check for existing links_backup, and delete it if it exists. - wfOut( "Dropping backup links table if it exists..." ); - $dbConn->query( "DROP TABLE IF EXISTS $links_backup", DB_MASTER); - wfOut( " done.\n" ); - - # Swap in the new table, and move old links table to links_backup - wfOut( "Swapping tables '$links' to '$links_backup'; '$links_temp' to '$links'..." ); - $dbConn->query( "RENAME TABLE links TO $links_backup, $links_temp TO $links", DB_MASTER ); - wfOut( " done.\n\n" ); - - $dbConn->close(); - wfOut( "Conversion complete. The old table remains at $links_backup;\n" ); - wfOut( "delete at your leisure.\n" ); - } else { - wfOut( "Conversion complete. The converted table is at $links_temp;\n" ); - wfOut( "the original links table is unchanged.\n" ); - } -} - -#-------------------------------------------------------------------- - -function createTempTable() { - global $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname; - global $noKeys; - $dbConn = Database::newFromParams( $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname ); - - if (!($dbConn->isOpen())) { - wfOut( "Opening connection to database failed.\n" ); - return; - } - $links_temp = $dbConn->tableName( 'links_temp' ); - - wfOut( "Dropping temporary links table if it exists..." ); - $dbConn->query( "DROP TABLE IF EXISTS $links_temp"); - wfOut( " done.\n" ); - - wfOut( "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))"); - } - wfOut( " done.\n\n" ); -} - -function performanceLog( $text ) { - global $logPerformance, $fh; - if ( $logPerformance ) { - fwrite( $fh, $text ); - } -} - -function getMicroTime() { # return time in seconds, with microsecond accuracy - list($usec, $sec) = explode(" ", microtime()); - return ((float)$usec + (float)$sec); -} diff --git a/maintenance/convertLinks.php b/maintenance/convertLinks.php index 415662a0..b7a55d57 100644 --- a/maintenance/convertLinks.php +++ b/maintenance/convertLinks.php @@ -18,95 +18,111 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class ConvertLinks extends Maintenance { + private $logPerformance; public function __construct() { parent::__construct(); $this->mDescription = "Convert from the old links schema (string->ID) to the new schema (ID->ID) The wiki should be put into read-only mode while this script executes"; + + $this->addArg( 'logperformance', "Log performance to perfLogFilename.", false ); + $this->addArg( 'perfLogFilename', "Filename where performance is logged if --logperformance was set (defaults to 'convLinksPerf.txt').", false ); + $this->addArg( 'keep-links-table', "Don't overwrite the old links table with the new one, leave the new table at links_temp.", false ); + $this->addArg( 'nokeys', "Don't create keys, and so allow duplicates in the new links table.\n +This gives a huge speed improvement for very large links tables which are MyISAM." /* (What about InnoDB?) */, false ); + } + + public function getDbType() { + return Maintenance::DB_ADMIN; } public function execute() { - global $wgDBtype; - if( $wgDBtype == 'postgres' ) { - $this->output( "Links table already ok on Postgres.\n" ); + $dbw = wfGetDB( DB_MASTER ); + + $type = $dbw->getType(); + if ( $type != 'mysql' ) { + $this->output( "Link table conversion not necessary for $type\n" ); return; } - $this->output( "Converting links table to ID-ID...\n" ); + global $wgContLang; - global $wgLang, $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname; - global $noKeys, $logPerformance, $fh; - - $tuplesAdded = $numBadLinks = $curRowsRead = 0; #counters etc + $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 - + + $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' ); - + # $finalRowOffset = 0; # not used yet; highest row number from links table to process + + $overwriteLinksTable = !$this->hasOption( 'keep-links-table' ); + $noKeys = $this->hasOption( 'noKeys' ); + $this->logPerformance = $this->hasOption( 'logperformance' ); + $perfLogFilename = $this->getArg( 'perfLogFilename', "convLinksPerf.txt" ); + + # -------------------------------------------------------------------- + + list ( $cur, $links, $links_temp, $links_backup ) = $dbw->tableNamesN( 'cur', 'links', 'links_temp', 'links_backup' ); + + if( $dbw->tableExists( 'pagelinks' ) ) { + $this->output( "...have pagelinks; skipping old links table updates\n" ); + return; + } + $res = $dbw->query( "SELECT l_from FROM $links LIMIT 1" ); if ( $dbw->fieldType( $res, 0 ) == "int" ) { $this->output( "Schema already converted\n" ); return; } - + $res = $dbw->query( "SELECT COUNT(*) AS count FROM $links" ); - $row = $dbw->fetchObject($res); + $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" ); } + $fh = false; + if ( $this->logPerformance ) { + $fh = fopen ( $perfLogFilename, "w" ); + if ( !$fh ) { + $this->error( "Couldn't open $perfLogFilename" ); + $this->logPerformance = false; + } + } $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" ); + $this->performanceLog ( $fh, "Reading $numRows rows from cur table...\n" ); + $this->performanceLog ( $fh, "rows read vs seconds elapsed:\n" ); $dbw->bufferResults( false ); $res = $dbw->query( "SELECT cur_namespace,cur_title,cur_id FROM $cur" ); $ids = array(); - while ( $row = $dbw->fetchObject( $res ) ) { + foreach ( $res as $row ) { $title = $row->cur_title; if ( $row->cur_namespace ) { - $title = $wgLang->getNsText( $row->cur_namespace ) . ":$title"; + $title = $wgContLang->getNsText( $row->cur_namespace ) . ":$title"; } $ids[$title] = $row->cur_id; $curRowsRead++; - if ($reportCurReadProgress) { - if (($curRowsRead % $curReadReportInterval) == 0) { - $this->performanceLog( $curRowsRead . " " . ($this->getMicroTime() - $baseTime) . "\n" ); + if ( $reportCurReadProgress ) { + if ( ( $curRowsRead % $curReadReportInterval ) == 0 ) { + $this->performanceLog( $fh, $curRowsRead . " " . ( $this->getMicroTime() - $baseTime ) . "\n" ); $this->output( "\t$curRowsRead rows of $cur table read.\n" ); } } @@ -114,30 +130,31 @@ The wiki should be put into read-only mode while this script executes"; $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" ); - #-------------------------------------------------------------------- - + $this->performanceLog( $fh, "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" ); + $this->performanceLog( $fh, "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) { + $this->performanceLog( $fh, "Processing $numRows rows from $links table...\n" ); + $this->performanceLog( $fh, "rows inserted vs seconds elapsed:\n" ); + + for ( $rowOffset = $initialRowOffset; $rowOffset < $numRows; $rowOffset += $linksConvInsertInterval ) { $sqlRead = "SELECT * FROM $links "; - $sqlRead = $dbw->limitResult($sqlRead, $linksConvInsertInterval,$rowOffset); - $res = $dbw->query($sqlRead); + $sqlRead = $dbw->limitResult( $sqlRead, $linksConvInsertInterval, $rowOffset ); + $res = $dbw->query( $sqlRead ); if ( $noKeys ) { - $sqlWrite = array("INSERT INTO $links_temp (l_from,l_to) VALUES "); + $sqlWrite = array( "INSERT INTO $links_temp (l_from,l_to) VALUES " ); } else { - $sqlWrite = array("INSERT IGNORE INTO $links_temp (l_from,l_to) VALUES "); + $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) ) { + foreach ( $res as $row ) { $fromTitle = $row->l_from; if ( array_key_exists( $fromTitle, $ids ) ) { # valid title $from = $ids[$fromTitle]; @@ -151,43 +168,40 @@ The wiki should be put into read-only mode while this script executes"; $numBadLinks++; } } - $dbw->freeResult($res); - #$this->output( "rowOffset: $rowOffset\ttuplesAdded: $tuplesAdded\tnumBadLinks: $numBadLinks\n" ); + $dbw->freeResult( $res ); + # $this->output( "rowOffset: $rowOffset\ttuplesAdded: $tuplesAdded\tnumBadLinks: $numBadLinks\n" ); if ( $tuplesAdded != 0 ) { - if ($reportLinksConvProgress) { + if ( $reportLinksConvProgress ) { $this->output( "Inserting $tuplesAdded tuples into $links_temp..." ); } - $dbw->query( implode("",$sqlWrite) ); + $dbw->query( implode( "", $sqlWrite ) ); $totalTuplesInserted += $tuplesAdded; - if ($reportLinksConvProgress) + if ( $reportLinksConvProgress ) $this->output( " done. Total $totalTuplesInserted tuples inserted.\n" ); - $this->performanceLog( $totalTuplesInserted . " " . ($this->getMicroTime() - $baseTime) . "\n" ); + $this->performanceLog( $fh, $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 ); } + $this->performanceLog( $fh, "$totalTuplesInserted valid titles and $numBadLinks invalid titles were processed.\n" ); + $this->performanceLog( $fh, "Total execution time: " . ( $this->getMicroTime() - $startTime ) . " seconds.\n" ); + if ( $this->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); + $dbw->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 ); + $dbw->query( "RENAME TABLE links TO $links_backup, $links_temp TO $links", DB_MASTER ); $this->output( " done.\n\n" ); - - $dbConn->close(); + + $dbw->close(); $this->output( "Conversion complete. The old table remains at $links_backup;\n" ); $this->output( "delete at your leisure.\n" ); } else { @@ -197,47 +211,44 @@ The wiki should be put into read-only mode while this script executes"; } private function createTempTable() { - global $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname; - global $noKeys; - $dbConn = Database::newFromParams( $wgDBserver, $wgDBadminuser, $wgDBadminpassword, $wgDBname ); + $dbConn = wfGetDB( DB_MASTER ); - if (!($dbConn->isOpen())) { + 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"); + $dbConn->query( "DROP TABLE IF EXISTS $links_temp" ); $this->output( " done.\n" ); $this->output( "Creating temporary links table..." ); - if ( $noKeys ) { + if ( $this->hasOption( 'noKeys' ) ) { $dbConn->query( "CREATE TABLE $links_temp ( " . "l_from int(8) unsigned NOT NULL default '0', " . - "l_to int(8) unsigned NOT NULL default '0')"); + "l_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))"); + "KEY (l_to))" ); } $this->output( " done.\n\n" ); } - private function performanceLog( $text ) { - global $logPerformance, $fh; - if ( $logPerformance ) { + private function performanceLog( $fh, $text ) { + if ( $this->logPerformance ) { fwrite( $fh, $text ); } } private function getMicroTime() { # return time in seconds, with microsecond accuracy - list($usec, $sec) = explode(" ", microtime()); - return ((float)$usec + (float)$sec); + list( $usec, $sec ) = explode( " ", microtime() ); + return ( (float)$usec + (float)$sec ); } } $maintClass = "ConvertLinks"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/convertUserOptions.php b/maintenance/convertUserOptions.php index 657a82c1..278d40ff 100644 --- a/maintenance/convertUserOptions.php +++ b/maintenance/convertUserOptions.php @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class ConvertUserOptions extends Maintenance { @@ -30,24 +31,24 @@ class ConvertUserOptions extends Maintenance { 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 + 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) + + if ( $id ) $this->output( "--Converted to ID $id\n" ); } $this->output( "Conversion done. Converted " . $this->mConversionCount . " user records.\n" ); @@ -57,16 +58,16 @@ class ConvertUserOptions extends Maintenance { $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 ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/createAndPromote.php b/maintenance/createAndPromote.php index 391d1226..8bff284a 100644 --- a/maintenance/createAndPromote.php +++ b/maintenance/createAndPromote.php @@ -1,5 +1,4 @@ <?php - /** * Maintenance script to create an account and grant it administrator rights * @@ -18,11 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance * @author Rob Church <robchur@gmail.com> */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class CreateAndPromote extends Maintenance { @@ -35,41 +35,41 @@ class CreateAndPromote extends Maintenance { } public function execute() { - $username = $this->getArg(0); - $password = $this->getArg(1); - + $username = $this->getArg( 0 ); + $password = $this->getArg( 1 ); + $this->output( wfWikiID() . ": Creating and promoting User:{$username}..." ); - + $user = User::newFromName( $username ); - if( !is_object( $user ) ) { + if ( !is_object( $user ) ) { $this->error( "invalid username.", true ); - } elseif( 0 != $user->idForName() ) { + } elseif ( 0 != $user->idForName() ) { $this->error( "account exists.", true ); } # Try to set the password try { $user->setPassword( $password ); - } catch( PasswordError $pwe ) { + } catch ( PasswordError $pwe ) { $this->error( $pwe->getText(), true ); } # Insert the account into the database $user->addToDatabase(); $user->saveSettings(); - + # Promote user $user->addGroup( 'sysop' ); - if( $this->hasOption( 'bureaucrat' ) ) + 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" ); } } $maintClass = "CreateAndPromote"; -require_once( DO_MAINTENANCE );
\ No newline at end of file +require_once( RUN_MAINTENANCE_IF_MAIN );
\ No newline at end of file diff --git a/maintenance/cssjanus/COPYING b/maintenance/cssjanus/COPYING new file mode 100644 index 00000000..3f2c8953 --- /dev/null +++ b/maintenance/cssjanus/COPYING @@ -0,0 +1,13 @@ + Copyright 2008 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/maintenance/cssjanus/LICENSE b/maintenance/cssjanus/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/maintenance/cssjanus/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/maintenance/cssjanus/README b/maintenance/cssjanus/README new file mode 100644 index 00000000..9b922156 --- /dev/null +++ b/maintenance/cssjanus/README @@ -0,0 +1,91 @@ +=CSSJanus= + +_Flips CSS from LTR to an RTL orienation and vice-versa_ + +Author: `Lindsey Simon <elsigh@google.com>` + +==Introduction== + +CSSJanus is CSS parser utility designed to aid the conversion of a website's +layout from left-to-right(LTR) to right-to-left(RTL). The script was born out of +a need to convert CSS for RTL languages when tables are not being used for layout (since tables will automatically reorder TD's in RTL). +CSSJanus will change most of the obvious CSS property names and their values as +well as some not-so-obvious ones (cursor, background-position %, etc...). +The script is designed to offer flexibility to account for cases when you do +not want to change certain rules which exist to account for bidirectional text +display bugs, as well as situations where you may or may not want to flip annotations inside of the background url string. +Note that you can disable CSSJanus from running on an entire class or any +rule within a class by prepending a /* @noflip */ comment before the rule(s) +you want CSSJanus to ignore. + +CSSJanus itself is not always enough to make a website that works in a LTR +language context work in a RTL language all the way, but it is a start. + +==Getting the code== + +View the trunk at: + + http://cssjanus.googlecode.com/svn/trunk/ + +Check out the latest development version anonymously with: + +{{{ + $ svn checkout http://cssjanus.googlecode.com/svn/trunk/ cssjanus +}}} + +==Using== + +Usage: + ./cssjanus.py < file.css > file-rtl.css +Flags: + --swap_left_right_in_url: Fixes "left"/"right" string within urls. + Ex: ./cssjanus.py --swap_left_right_in_url < file.css > file_rtl.css + --swap_ltr_rtl_in_url: Fixes "ltr"/"rtl" string within urls. + Ex: ./cssjanus.py --swap_ltr_rtl_in_url < file.css > file_rtl.css + +If you'd like to make use of the webapp version of cssjanus, you'll need to +download the Google App Engine SDK + http://code.google.com/appengine/downloads.html +and also drop a "django" directory into this directory, with the latest svn +from django. You should be good to go with that setup. Please let me know +otherwise. + +==Bugs, Patches== + +Patches and bug reports are welcome, just please keep the style +consistent with the original source. If you find a bug, please include a diff +of cssjanus_test.py with the bug included as a new unit test which fails. It +will make understanding and fixing the bug easier. + +==Todo== + +* Include some helpers for some typical bidi text solutions? +* Aural CSS (azimuth) swapping? + +==Contributors== + +Additional thanks to Mike Samuel for his work on csslex.py, Andy Perelson for +his help coding and reviewing, Stephen Zabel for his help with i18n and my sanity, +and to Eric Meyer for his thoughtful input. +Thanks to Junyu Wang for the Chinese translation. +Thanks to Masashi Kawashima for the Japanese translation. +Thanks to Taaryk Taar and Tariq Al-Omaireeni for an updated Arabic translation. +Thanks to Jens Meiert for the German translation. + +==License== + +{{{ + Copyright 2008 Google Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the 'License'); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an 'AS IS' BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +}}} diff --git a/maintenance/cssjanus/cssjanus.py b/maintenance/cssjanus/cssjanus.py new file mode 100644 index 00000000..dd14bd58 --- /dev/null +++ b/maintenance/cssjanus/cssjanus.py @@ -0,0 +1,574 @@ +#!/usr/bin/python +# +# Copyright 2008 Google Inc. All Rights Reserved. + +"""Converts a LeftToRight Cascading Style Sheet into a RightToLeft one. + + This is a utility script for replacing "left" oriented things in a CSS file + like float, padding, margin with "right" oriented values. + It also does the opposite. + The goal is to be able to conditionally serve one large, cat'd, compiled CSS + file appropriate for LeftToRight oriented languages and RightToLeft ones. + This utility will hopefully help your structural layout done in CSS in + terms of its RTL compatibility. It will not help with some of the more + complicated bidirectional text issues. +""" + +__author__ = 'elsigh@google.com (Lindsey Simon)' +__version__ = '0.1' + +import logging +import re +import sys +import getopt +import os + +import csslex + +logging.getLogger().setLevel(logging.INFO) + +# Global for the command line flags. +SWAP_LTR_RTL_IN_URL_DEFAULT = False +SWAP_LEFT_RIGHT_IN_URL_DEFAULT = False +FLAGS = {'swap_ltr_rtl_in_url': SWAP_LTR_RTL_IN_URL_DEFAULT, + 'swap_left_right_in_url': SWAP_LEFT_RIGHT_IN_URL_DEFAULT} + +# Generic token delimiter character. +TOKEN_DELIMITER = '~' + +# This is a temporary match token we use when swapping strings. +TMP_TOKEN = '%sTMP%s' % (TOKEN_DELIMITER, TOKEN_DELIMITER) + +# Token to be used for joining lines. +TOKEN_LINES = '%sJ%s' % (TOKEN_DELIMITER, TOKEN_DELIMITER) + +# Global constant text strings for CSS value matches. +LTR = 'ltr' +RTL = 'rtl' +LEFT = 'left' +RIGHT = 'right' + +# This is a lookbehind match to ensure that we don't replace instances +# of our string token (left, rtl, etc...) if there's a letter in front of it. +# Specifically, this prevents replacements like 'background: url(bright.png)'. +LOOKBEHIND_NOT_LETTER = r'(?<![a-zA-Z])' + +# This is a lookahead match to make sure we don't replace left and right +# in actual classnames, so that we don't break the HTML/CSS dependencies. +# Read literally, it says ignore cases where the word left, for instance, is +# directly followed by valid classname characters and a curly brace. +# ex: .column-left {float: left} will become .column-left {float: right} +LOOKAHEAD_NOT_OPEN_BRACE = (r'(?!(?:%s|%s|%s|#|\:|\.|\,|\+|>)*?{)' % + (csslex.NMCHAR, TOKEN_LINES, csslex.SPACE)) + + +# These two lookaheads are to test whether or not we are within a +# background: url(HERE) situation. +# Ref: http://www.w3.org/TR/CSS21/syndata.html#uri +VALID_AFTER_URI_CHARS = r'[\'\"]?%s' % csslex.WHITESPACE +LOOKAHEAD_NOT_CLOSING_PAREN = r'(?!%s?%s\))' % (csslex.URL_CHARS, + VALID_AFTER_URI_CHARS) +LOOKAHEAD_FOR_CLOSING_PAREN = r'(?=%s?%s\))' % (csslex.URL_CHARS, + VALID_AFTER_URI_CHARS) + +# Compile a regex to swap left and right values in 4 part notations. +# We need to match negatives and decimal numeric values. +# ex. 'margin: .25em -2px 3px 0' becomes 'margin: .25em 0 3px -2px'. +POSSIBLY_NEGATIVE_QUANTITY = r'((?:-?%s)|(?:inherit|auto))' % csslex.QUANTITY +POSSIBLY_NEGATIVE_QUANTITY_SPACE = r'%s%s%s' % (POSSIBLY_NEGATIVE_QUANTITY, + csslex.SPACE, + csslex.WHITESPACE) +FOUR_NOTATION_QUANTITY_RE = re.compile(r'%s%s%s%s' % + (POSSIBLY_NEGATIVE_QUANTITY_SPACE, + POSSIBLY_NEGATIVE_QUANTITY_SPACE, + POSSIBLY_NEGATIVE_QUANTITY_SPACE, + POSSIBLY_NEGATIVE_QUANTITY), + re.I) +COLOR = r'(%s|%s)' % (csslex.NAME, csslex.HASH) +COLOR_SPACE = r'%s%s' % (COLOR, csslex.SPACE) +FOUR_NOTATION_COLOR_RE = re.compile(r'(-color%s:%s)%s%s%s(%s)' % + (csslex.WHITESPACE, + csslex.WHITESPACE, + COLOR_SPACE, + COLOR_SPACE, + COLOR_SPACE, + COLOR), + re.I) + +# Compile the cursor resize regexes +CURSOR_EAST_RE = re.compile(LOOKBEHIND_NOT_LETTER + '([ns]?)e-resize') +CURSOR_WEST_RE = re.compile(LOOKBEHIND_NOT_LETTER + '([ns]?)w-resize') + +# Matches the condition where we need to replace the horizontal component +# of a background-position value when expressed in horizontal percentage. +# Had to make two regexes because in the case of position-x there is only +# one quantity, and otherwise we don't want to match and change cases with only +# one quantity. +BG_HORIZONTAL_PERCENTAGE_RE = re.compile(r'background(-position)?(%s:%s)' + '([^%%]*?)(%s)%%' + '(%s(?:%s|%s))' % (csslex.WHITESPACE, + csslex.WHITESPACE, + csslex.NUM, + csslex.WHITESPACE, + csslex.QUANTITY, + csslex.IDENT)) + +BG_HORIZONTAL_PERCENTAGE_X_RE = re.compile(r'background-position-x(%s:%s)' + '(%s)%%' % (csslex.WHITESPACE, + csslex.WHITESPACE, + csslex.NUM)) + +# Matches the opening of a body selector. +BODY_SELECTOR = r'body%s{%s' % (csslex.WHITESPACE, csslex.WHITESPACE) + +# Matches anything up until the closing of a selector. +CHARS_WITHIN_SELECTOR = r'[^\}]*?' + +# Matches the direction property in a selector. +DIRECTION_RE = r'direction%s:%s' % (csslex.WHITESPACE, csslex.WHITESPACE) + +# These allow us to swap "ltr" with "rtl" and vice versa ONLY within the +# body selector and on the same line. +BODY_DIRECTION_LTR_RE = re.compile(r'(%s)(%s)(%s)(ltr)' % + (BODY_SELECTOR, CHARS_WITHIN_SELECTOR, + DIRECTION_RE), + re.I) +BODY_DIRECTION_RTL_RE = re.compile(r'(%s)(%s)(%s)(rtl)' % + (BODY_SELECTOR, CHARS_WITHIN_SELECTOR, + DIRECTION_RE), + re.I) + + +# Allows us to swap "direction:ltr" with "direction:rtl" and +# vice versa anywhere in a line. +DIRECTION_LTR_RE = re.compile(r'%s(ltr)' % DIRECTION_RE) +DIRECTION_RTL_RE = re.compile(r'%s(rtl)' % DIRECTION_RE) + +# We want to be able to switch left with right and vice versa anywhere +# we encounter left/right strings, EXCEPT inside the background:url(). The next +# two regexes are for that purpose. We have alternate IN_URL versions of the +# regexes compiled in case the user passes the flag that they do +# actually want to have left and right swapped inside of background:urls. +LEFT_RE = re.compile('%s(%s)%s%s' % (LOOKBEHIND_NOT_LETTER, + LEFT, + LOOKAHEAD_NOT_CLOSING_PAREN, + LOOKAHEAD_NOT_OPEN_BRACE), + re.I) +RIGHT_RE = re.compile('%s(%s)%s%s' % (LOOKBEHIND_NOT_LETTER, + RIGHT, + LOOKAHEAD_NOT_CLOSING_PAREN, + LOOKAHEAD_NOT_OPEN_BRACE), + re.I) +LEFT_IN_URL_RE = re.compile('%s(%s)%s' % (LOOKBEHIND_NOT_LETTER, + LEFT, + LOOKAHEAD_FOR_CLOSING_PAREN), + re.I) +RIGHT_IN_URL_RE = re.compile('%s(%s)%s' % (LOOKBEHIND_NOT_LETTER, + RIGHT, + LOOKAHEAD_FOR_CLOSING_PAREN), + re.I) +LTR_IN_URL_RE = re.compile('%s(%s)%s' % (LOOKBEHIND_NOT_LETTER, + LTR, + LOOKAHEAD_FOR_CLOSING_PAREN), + re.I) +RTL_IN_URL_RE = re.compile('%s(%s)%s' % (LOOKBEHIND_NOT_LETTER, + RTL, + LOOKAHEAD_FOR_CLOSING_PAREN), + re.I) + +COMMENT_RE = re.compile('(%s)' % csslex.COMMENT, re.I) + +NOFLIP_TOKEN = r'\@noflip' +# The NOFLIP_TOKEN inside of a comment. For now, this requires that comments +# be in the input, which means users of a css compiler would have to run +# this script first if they want this functionality. +NOFLIP_ANNOTATION = r'/\*%s%s%s\*/' % (csslex.WHITESPACE, + NOFLIP_TOKEN, + csslex. WHITESPACE) + +# After a NOFLIP_ANNOTATION, and within a class selector, we want to be able +# to set aside a single rule not to be flipped. We can do this by matching +# our NOFLIP annotation and then using a lookahead to make sure there is not +# an opening brace before the match. +NOFLIP_SINGLE_RE = re.compile(r'(%s%s[^;}]+;?)' % (NOFLIP_ANNOTATION, + LOOKAHEAD_NOT_OPEN_BRACE), + re.I) + +# After a NOFLIP_ANNOTATION, we want to grab anything up until the next } which +# means the entire following class block. This will prevent all of its +# declarations from being flipped. +NOFLIP_CLASS_RE = re.compile(r'(%s%s})' % (NOFLIP_ANNOTATION, + CHARS_WITHIN_SELECTOR), + re.I) + + +class Tokenizer: + """Replaces any CSS comments with string tokens and vice versa.""" + + def __init__(self, token_re, token_string): + """Constructor for the Tokenizer. + + Args: + token_re: A regex for the string to be replace by a token. + token_string: The string to put between token delimiters when tokenizing. + """ + logging.debug('Tokenizer::init token_string=%s' % token_string) + self.token_re = token_re + self.token_string = token_string + self.originals = [] + + def Tokenize(self, line): + """Replaces any string matching token_re in line with string tokens. + + By passing a function as an argument to the re.sub line below, we bypass + the usual rule where re.sub will only replace the left-most occurrence of + a match by calling the passed in function for each occurrence. + + Args: + line: A line to replace token_re matches in. + + Returns: + line: A line with token_re matches tokenized. + """ + line = self.token_re.sub(self.TokenizeMatches, line) + logging.debug('Tokenizer::Tokenize returns: %s' % line) + return line + + def DeTokenize(self, line): + """Replaces tokens with the original string. + + Args: + line: A line with tokens. + + Returns: + line with any tokens replaced by the original string. + """ + + # Put all of the comments back in by their comment token. + for i, original in enumerate(self.originals): + token = '%s%s_%s%s' % (TOKEN_DELIMITER, self.token_string, i + 1, + TOKEN_DELIMITER) + line = line.replace(token, original) + logging.debug('Tokenizer::DeTokenize i:%s w/%s' % (i, token)) + logging.debug('Tokenizer::DeTokenize returns: %s' % line) + return line + + def TokenizeMatches(self, m): + """Replaces matches with tokens and stores the originals. + + Args: + m: A match object. + + Returns: + A string token which replaces the CSS comment. + """ + logging.debug('Tokenizer::TokenizeMatches %s' % m.group(1)) + self.originals.append(m.group(1)) + return '%s%s_%s%s' % (TOKEN_DELIMITER, + self.token_string, + len(self.originals), + TOKEN_DELIMITER) + + +def FixBodyDirectionLtrAndRtl(line): + """Replaces ltr with rtl and vice versa ONLY in the body direction. + + Args: + line: A string to replace instances of ltr with rtl. + Returns: + line with direction: ltr and direction: rtl swapped only in body selector. + line = FixBodyDirectionLtrAndRtl('body { direction:ltr }') + line will now be 'body { direction:rtl }'. + """ + + line = BODY_DIRECTION_LTR_RE.sub('\\1\\2\\3%s' % TMP_TOKEN, line) + line = BODY_DIRECTION_RTL_RE.sub('\\1\\2\\3%s' % LTR, line) + line = line.replace(TMP_TOKEN, RTL) + logging.debug('FixBodyDirectionLtrAndRtl returns: %s' % line) + return line + + +def FixLeftAndRight(line): + """Replaces left with right and vice versa in line. + + Args: + line: A string in which to perform the replacement. + + Returns: + line with left and right swapped. For example: + line = FixLeftAndRight('padding-left: 2px; margin-right: 1px;') + line will now be 'padding-right: 2px; margin-left: 1px;'. + """ + + line = LEFT_RE.sub(TMP_TOKEN, line) + line = RIGHT_RE.sub(LEFT, line) + line = line.replace(TMP_TOKEN, RIGHT) + logging.debug('FixLeftAndRight returns: %s' % line) + return line + + +def FixLeftAndRightInUrl(line): + """Replaces left with right and vice versa ONLY within background urls. + + Args: + line: A string in which to replace left with right and vice versa. + + Returns: + line with left and right swapped in the url string. For example: + line = FixLeftAndRightInUrl('background:url(right.png)') + line will now be 'background:url(left.png)'. + """ + + line = LEFT_IN_URL_RE.sub(TMP_TOKEN, line) + line = RIGHT_IN_URL_RE.sub(LEFT, line) + line = line.replace(TMP_TOKEN, RIGHT) + logging.debug('FixLeftAndRightInUrl returns: %s' % line) + return line + + +def FixLtrAndRtlInUrl(line): + """Replaces ltr with rtl and vice versa ONLY within background urls. + + Args: + line: A string in which to replace ltr with rtl and vice versa. + + Returns: + line with left and right swapped. For example: + line = FixLtrAndRtlInUrl('background:url(rtl.png)') + line will now be 'background:url(ltr.png)'. + """ + + line = LTR_IN_URL_RE.sub(TMP_TOKEN, line) + line = RTL_IN_URL_RE.sub(LTR, line) + line = line.replace(TMP_TOKEN, RTL) + logging.debug('FixLtrAndRtlInUrl returns: %s' % line) + return line + + +def FixCursorProperties(line): + """Fixes directional CSS cursor properties. + + Args: + line: A string to fix CSS cursor properties in. + + Returns: + line reformatted with the cursor properties substituted. For example: + line = FixCursorProperties('cursor: ne-resize') + line will now be 'cursor: nw-resize'. + """ + + line = CURSOR_EAST_RE.sub('\\1' + TMP_TOKEN, line) + line = CURSOR_WEST_RE.sub('\\1e-resize', line) + line = line.replace(TMP_TOKEN, 'w-resize') + logging.debug('FixCursorProperties returns: %s' % line) + return line + + +def FixFourPartNotation(line): + """Fixes the second and fourth positions in 4 part CSS notation. + + Args: + line: A string to fix 4 part CSS notation in. + + Returns: + line reformatted with the 4 part notations swapped. For example: + line = FixFourPartNotation('padding: 1px 2px 3px 4px') + line will now be 'padding: 1px 4px 3px 2px'. + """ + line = FOUR_NOTATION_QUANTITY_RE.sub('\\1 \\4 \\3 \\2', line) + line = FOUR_NOTATION_COLOR_RE.sub('\\1\\2 \\5 \\4 \\3', line) + logging.debug('FixFourPartNotation returns: %s' % line) + return line + + +def FixBackgroundPosition(line): + """Fixes horizontal background percentage values in line. + + Args: + line: A string to fix horizontal background position values in. + + Returns: + line reformatted with the 4 part notations swapped. + """ + line = BG_HORIZONTAL_PERCENTAGE_RE.sub(CalculateNewBackgroundPosition, line) + line = BG_HORIZONTAL_PERCENTAGE_X_RE.sub(CalculateNewBackgroundPositionX, + line) + logging.debug('FixBackgroundPosition returns: %s' % line) + return line + + +def CalculateNewBackgroundPosition(m): + """Fixes horizontal background-position percentages. + + This function should be used as an argument to re.sub since it needs to + perform replacement specific calculations. + + Args: + m: A match object. + + Returns: + A string with the horizontal background position percentage fixed. + BG_HORIZONTAL_PERCENTAGE_RE.sub(FixBackgroundPosition, + 'background-position: 75% 50%') + will return 'background-position: 25% 50%'. + """ + + # The flipped value is the offset from 100% + new_x = str(100-int(m.group(4))) + + # Since m.group(1) may very well be None type and we need a string.. + if m.group(1): + position_string = m.group(1) + else: + position_string = '' + + return 'background%s%s%s%s%%%s' % (position_string, m.group(2), m.group(3), + new_x, m.group(5)) + + +def CalculateNewBackgroundPositionX(m): + """Fixes percent based background-position-x. + + This function should be used as an argument to re.sub since it needs to + perform replacement specific calculations. + + Args: + m: A match object. + + Returns: + A string with the background-position-x percentage fixed. + BG_HORIZONTAL_PERCENTAGE_X_RE.sub(CalculateNewBackgroundPosition, + 'background-position-x: 75%') + will return 'background-position-x: 25%'. + """ + + # The flipped value is the offset from 100% + new_x = str(100-int(m.group(2))) + + return 'background-position-x%s%s%%' % (m.group(1), new_x) + + +def ChangeLeftToRightToLeft(lines, + swap_ltr_rtl_in_url=None, + swap_left_right_in_url=None): + """Turns lines into a stream and runs the fixing functions against it. + + Args: + lines: An list of CSS lines. + swap_ltr_rtl_in_url: Overrides this flag if param is set. + swap_left_right_in_url: Overrides this flag if param is set. + + Returns: + The same lines, but with left and right fixes. + """ + + global FLAGS + + # Possibly override flags with params. + logging.debug('ChangeLeftToRightToLeft swap_ltr_rtl_in_url=%s, ' + 'swap_left_right_in_url=%s' % (swap_ltr_rtl_in_url, + swap_left_right_in_url)) + if swap_ltr_rtl_in_url is None: + swap_ltr_rtl_in_url = FLAGS['swap_ltr_rtl_in_url'] + if swap_left_right_in_url is None: + swap_left_right_in_url = FLAGS['swap_left_right_in_url'] + + # Turns the array of lines into a single line stream. + logging.debug('LINES COUNT: %s' % len(lines)) + line = TOKEN_LINES.join(lines) + + # Tokenize any single line rules with the /* noflip */ annotation. + noflip_single_tokenizer = Tokenizer(NOFLIP_SINGLE_RE, 'NOFLIP_SINGLE') + line = noflip_single_tokenizer.Tokenize(line) + + # Tokenize any class rules with the /* noflip */ annotation. + noflip_class_tokenizer = Tokenizer(NOFLIP_CLASS_RE, 'NOFLIP_CLASS') + line = noflip_class_tokenizer.Tokenize(line) + + # Tokenize the comments so we can preserve them through the changes. + comment_tokenizer = Tokenizer(COMMENT_RE, 'C') + line = comment_tokenizer.Tokenize(line) + + # Here starteth the various left/right orientation fixes. + line = FixBodyDirectionLtrAndRtl(line) + + if swap_left_right_in_url: + line = FixLeftAndRightInUrl(line) + + if swap_ltr_rtl_in_url: + line = FixLtrAndRtlInUrl(line) + + line = FixLeftAndRight(line) + line = FixCursorProperties(line) + line = FixFourPartNotation(line) + line = FixBackgroundPosition(line) + + # DeTokenize the single line noflips. + line = noflip_single_tokenizer.DeTokenize(line) + + # DeTokenize the class-level noflips. + line = noflip_class_tokenizer.DeTokenize(line) + + # DeTokenize the comments. + line = comment_tokenizer.DeTokenize(line) + + # Rejoin the lines back together. + lines = line.split(TOKEN_LINES) + + return lines + +def usage(): + """Prints out usage information.""" + + print 'Usage:' + print ' ./cssjanus.py < file.css > file-rtl.css' + print 'Flags:' + print ' --swap_left_right_in_url: Fixes "left"/"right" string within urls.' + print ' Ex: ./cssjanus.py --swap_left_right_in_url < file.css > file_rtl.css' + print ' --swap_ltr_rtl_in_url: Fixes "ltr"/"rtl" string within urls.' + print ' Ex: ./cssjanus --swap_ltr_rtl_in_url < file.css > file_rtl.css' + +def setflags(opts): + """Parse the passed in command line arguments and set the FLAGS global. + + Args: + opts: getopt iterable intercepted from argv. + """ + + global FLAGS + + # Parse the arguments. + for opt, arg in opts: + logging.debug('opt: %s, arg: %s' % (opt, arg)) + if opt in ("-h", "--help"): + usage() + sys.exit() + elif opt in ("-d", "--debug"): + logging.getLogger().setLevel(logging.DEBUG) + elif opt == '--swap_ltr_rtl_in_url': + FLAGS['swap_ltr_rtl_in_url'] = True + elif opt == '--swap_left_right_in_url': + FLAGS['swap_left_right_in_url'] = True + + +def main(argv): + """Sends stdin lines to ChangeLeftToRightToLeft and writes to stdout.""" + + # Define the flags. + try: + opts, args = getopt.getopt(argv, 'hd', ['help', 'debug', + 'swap_left_right_in_url', + 'swap_ltr_rtl_in_url']) + except getopt.GetoptError: + usage() + sys.exit(2) + + # Parse and set the flags. + setflags(opts) + + # Call the main routine with all our functionality. + fixed_lines = ChangeLeftToRightToLeft(sys.stdin.readlines()) + sys.stdout.write(''.join(fixed_lines)) + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/maintenance/cssjanus/csslex.py b/maintenance/cssjanus/csslex.py new file mode 100644 index 00000000..1fc7304e --- /dev/null +++ b/maintenance/cssjanus/csslex.py @@ -0,0 +1,114 @@ +#!/usr/bin/python +# +# Copyright 2007 Google Inc. All Rights Reserved. + +"""CSS Lexical Grammar rules. + +CSS lexical grammar from http://www.w3.org/TR/CSS21/grammar.html +""" + +__author__ = ['elsigh@google.com (Lindsey Simon)', + 'msamuel@google.com (Mike Samuel)'] + +# public symbols +__all__ = [ "NEWLINE", "HEX", "NON_ASCII", "UNICODE", "ESCAPE", "NMSTART", "NMCHAR", "STRING1", "STRING2", "IDENT", "NAME", "HASH", "NUM", "STRING", "URL", "SPACE", "WHITESPACE", "COMMENT", "QUANTITY", "PUNC" ] + +# The comments below are mostly copied verbatim from the grammar. + +# "@import" {return IMPORT_SYM;} +# "@page" {return PAGE_SYM;} +# "@media" {return MEDIA_SYM;} +# "@charset" {return CHARSET_SYM;} +KEYWORD = r'(?:\@(?:import|page|media|charset))' + +# nl \n|\r\n|\r|\f ; a newline +NEWLINE = r'\n|\r\n|\r|\f' + +# h [0-9a-f] ; a hexadecimal digit +HEX = r'[0-9a-f]' + +# nonascii [\200-\377] +NON_ASCII = r'[\200-\377]' + +# unicode \\{h}{1,6}(\r\n|[ \t\r\n\f])? +UNICODE = r'(?:(?:\\' + HEX + r'{1,6})(?:\r\n|[ \t\r\n\f])?)' + +# escape {unicode}|\\[^\r\n\f0-9a-f] +ESCAPE = r'(?:' + UNICODE + r'|\\[^\r\n\f0-9a-f])' + +# nmstart [_a-z]|{nonascii}|{escape} +NMSTART = r'(?:[_a-z]|' + NON_ASCII + r'|' + ESCAPE + r')' + +# nmchar [_a-z0-9-]|{nonascii}|{escape} +NMCHAR = r'(?:[_a-z0-9-]|' + NON_ASCII + r'|' + ESCAPE + r')' + +# ident -?{nmstart}{nmchar}* +IDENT = r'-?' + NMSTART + NMCHAR + '*' + +# name {nmchar}+ +NAME = NMCHAR + r'+' + +# hash +HASH = r'#' + NAME + +# string1 \"([^\n\r\f\\"]|\\{nl}|{escape})*\" ; "string" +STRING1 = r'"(?:[^\"\\]|\\.)*"' + +# string2 \'([^\n\r\f\\']|\\{nl}|{escape})*\' ; 'string' +STRING2 = r"'(?:[^\'\\]|\\.)*'" + +# string {string1}|{string2} +STRING = '(?:' + STRING1 + r'|' + STRING2 + ')' + +# num [0-9]+|[0-9]*"."[0-9]+ +NUM = r'(?:[0-9]*\.[0-9]+|[0-9]+)' + +# s [ \t\r\n\f] +SPACE = r'[ \t\r\n\f]' + +# w {s}* +WHITESPACE = '(?:' + SPACE + r'*)' + +# url special chars +URL_SPECIAL_CHARS = r'[!#$%&*-~]' + +# url chars ({url_special_chars}|{nonascii}|{escape})* +URL_CHARS = r'(?:%s|%s|%s)*' % (URL_SPECIAL_CHARS, NON_ASCII, ESCAPE) + +# url +URL = r'url\(%s(%s|%s)%s\)' % (WHITESPACE, STRING, URL_CHARS, WHITESPACE) + +# comments +# see http://www.w3.org/TR/CSS21/grammar.html +COMMENT = r'/\*[^*]*\*+([^/*][^*]*\*+)*/' + +# {E}{M} {return EMS;} +# {E}{X} {return EXS;} +# {P}{X} {return LENGTH;} +# {C}{M} {return LENGTH;} +# {M}{M} {return LENGTH;} +# {I}{N} {return LENGTH;} +# {P}{T} {return LENGTH;} +# {P}{C} {return LENGTH;} +# {D}{E}{G} {return ANGLE;} +# {R}{A}{D} {return ANGLE;} +# {G}{R}{A}{D} {return ANGLE;} +# {M}{S} {return TIME;} +# {S} {return TIME;} +# {H}{Z} {return FREQ;} +# {K}{H}{Z} {return FREQ;} +# % {return PERCENTAGE;} +UNIT = r'(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)' + +# {num}{UNIT|IDENT} {return NUMBER;} +QUANTITY = '%s(?:%s%s|%s)?' % (NUM, WHITESPACE, UNIT, IDENT) + +# "<!--" {return CDO;} +# "-->" {return CDC;} +# "~=" {return INCLUDES;} +# "|=" {return DASHMATCH;} +# {w}"{" {return LBRACE;} +# {w}"+" {return PLUS;} +# {w}">" {return GREATER;} +# {w}"," {return COMMA;} +PUNC = r'<!--|-->|~=|\|=|[\{\+>,:;]' diff --git a/maintenance/deleteArchivedFiles.inc b/maintenance/deleteArchivedFiles.inc new file mode 100644 index 00000000..e0ac225e --- /dev/null +++ b/maintenance/deleteArchivedFiles.inc @@ -0,0 +1,62 @@ +<?php +/** + * Core functions for deleteArchivedFiles.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 + */ + +class DeleteArchivedFilesImplementation { + static public function doDelete( $output, $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 + $output->handleOutput( "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 { + $output->handleOutput( "Notice - file '$key' not found in group '$group'\n" ); + if ( $force ) { + $output->handleOutput( "Got --force, deleting DB entry\n" ); + $dbw->query( "DELETE FROM $tbl_arch WHERE fa_id = $id" ); + } + } + } + $dbw->commit(); + $output->handleOutput( "Done! [$count file(s)]\n" ); + } +}
\ No newline at end of file diff --git a/maintenance/deleteArchivedFiles.php b/maintenance/deleteArchivedFiles.php index af4bbb74..6067c807 100644 --- a/maintenance/deleteArchivedFiles.php +++ b/maintenance/deleteArchivedFiles.php @@ -23,7 +23,8 @@ * Based on deleteOldRevisions.php by Rob Church */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/deleteArchivedFiles.inc' ); class DeleteArchivedFiles extends Maintenance { public function __construct() { @@ -33,50 +34,19 @@ class DeleteArchivedFiles extends Maintenance { $this->addOption( 'force', 'Force deletion of rows from filearchive' ); } + public function handleOutput( $str ) { + return $this->output( $str ); + } + public function execute() { - if( !$this->hasOption('delete') ) { + 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" ); + DeleteArchivedFilesImplementation::doDelete( $this, $force ); } } $maintClass = "DeleteArchivedFiles"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/deleteArchivedRevisions.inc b/maintenance/deleteArchivedRevisions.inc new file mode 100644 index 00000000..10bd4cff --- /dev/null +++ b/maintenance/deleteArchivedRevisions.inc @@ -0,0 +1,57 @@ +<?php + +/** + * Delete archived (deleted from public) revisions from the database + * + * 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 + */ + +class DeleteArchivedRevisionsImplementation { + + /** + * Perform the delete on archived revisions. + + * @param $maint Object An object (typically of class Maintenance) + * that implements two methods: handleOutput() and + * purgeRedundantText(). See Maintenance for a description of + * those methods. + */ + static public function doDelete( $maint ) { + $dbw = wfGetDB( DB_MASTER ); + + $dbw->begin(); + + $tbl_arch = $dbw->tableName( 'archive' ); + + # Delete as appropriate + $maint->handleOutput( "Deleting archived revisions... " ); + $dbw->query( "DELETE FROM $tbl_arch" ); + + $count = $dbw->affectedRows(); + $deletedRows = $count != 0; + + $maint->handleOutput( "done. $count revisions deleted.\n" ); + + # This bit's done + # Purge redundant text records + $dbw->commit(); + if ( $deletedRows ) { + $maint->purgeRedundantText( true ); + } + } +}
\ No newline at end of file diff --git a/maintenance/deleteArchivedRevisions.php b/maintenance/deleteArchivedRevisions.php index c3f8bf11..0faa0abb 100644 --- a/maintenance/deleteArchivedRevisions.php +++ b/maintenance/deleteArchivedRevisions.php @@ -23,7 +23,8 @@ * Shamelessly stolen from deleteOldRevisions.php by Rob Church :) */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/deleteArchivedRevisions.inc' ); class DeleteArchivedRevisions extends Maintenance { public function __construct() { @@ -32,31 +33,17 @@ class DeleteArchivedRevisions extends Maintenance { $this->addOption( 'delete', 'Performs the deletion' ); } + public function handleOutput( $str ) { + $this->output( $str ); + } + 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 ); - } + if ( $this->hasOption( 'delete' ) ) { + DeleteArchivedRevisionsImplementation::doDelete( $this ); } else { + $dbw = wfGetDB( DB_MASTER ); $res = $dbw->selectRow( 'archive', 'COUNT(*) as count', array(), __FUNCTION__ ); $this->output( "Found {$res->count} revisions to delete.\n" ); $this->output( "Please run the script again with the --delete option to really delete the revisions.\n" ); @@ -65,4 +52,4 @@ class DeleteArchivedRevisions extends Maintenance { } $maintClass = "DeleteArchivedRevisions"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/deleteBatch.php b/maintenance/deleteBatch.php index 56afd86c..c8bb4803 100644 --- a/maintenance/deleteBatch.php +++ b/maintenance/deleteBatch.php @@ -26,39 +26,40 @@ * * @ingroup Maintenance */ - -require_once( dirname(__FILE__) . '/Maintenance.php' ); + +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 ); + $this->addArg( 'listfile', 'File with titles to delete, separated by newlines. ' . + 'If not given, stdin will be used.', false ); } - + 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() ) { + if ( $this->hasArg() ) { $file = fopen( $this->getArg(), 'r' ); } else { $file = $this->getStdin(); } # Setup - if( !$file ) { + if ( !$file ) { $this->error( "Unable to read file, exiting", true ); } $wgUser = User::newFromName( $user ); @@ -75,18 +76,18 @@ class DeleteBatch extends Maintenance { $this->output( "Invalid title '$line' on line $linenum\n" ); continue; } - if( !$page->exists() ) { + if ( !$page->exists() ) { $this->output( "Skipping nonexistent page '$line'\n" ); continue; } - - + + $this->output( $page->getPrefixedText() ); $dbw->begin(); - if( $page->getNamespace() == NS_FILE ) { + if ( $page->getNamespace() == NS_FILE ) { $art = new ImagePage( $page ); $img = wfFindFile( $art->mTitle ); - if( !$img || !$img->delete( $reason ) ) { + if ( !$img || !$img->delete( $reason ) ) { $this->output( "FAILED to delete image file... " ); } } else { @@ -99,7 +100,7 @@ class DeleteBatch extends Maintenance { } else { $this->output( " FAILED to delete article\n" ); } - + if ( $interval ) { sleep( $interval ); } @@ -109,4 +110,4 @@ class DeleteBatch extends Maintenance { } $maintClass = "DeleteBatch"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/deleteDefaultMessages.php b/maintenance/deleteDefaultMessages.php index 3f0e1b1c..fc482ac0 100644 --- a/maintenance/deleteDefaultMessages.php +++ b/maintenance/deleteDefaultMessages.php @@ -1,6 +1,6 @@ <?php /** - * Deletes all pages in the MediaWiki namespace which were last edited by + * Deletes all pages in the MediaWiki namespace which were last edited by * "MediaWiki default". * * This program is free software; you can redistribute it and/or modify @@ -21,7 +21,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class DeleteDefaultMessages extends Maintenance { public function __construct() { @@ -31,17 +31,11 @@ class DeleteDefaultMessages extends Maintenance { } public function execute() { - self::reallyExecute(); - } - - public static function reallyExecute() { + $user = 'MediaWiki default'; $reason = 'No longer required'; - global $wgUser; - $wgUser = User::newFromName( $user ); - $wgUser->addGroup( 'bot' ); - + $this->output( "Checking existence of old default messages..." ); $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( array( 'page', 'revision' ), array( 'page_namespace', 'page_title' ), @@ -52,6 +46,20 @@ class DeleteDefaultMessages extends Maintenance { ) ); + if( $dbr->numRows( $res ) == 0 ) { + # No more messages left + $this->output( "done.\n" ); + return; + } + + # Deletions will be made by $user temporarly added to the bot group + # in order to hide it in RecentChanges. + global $wgUser; + $wgUser = User::newFromName( $user ); + $wgUser->addGroup( 'bot' ); + + # Handle deletion + $this->output( "\n...deleting old default messages (this may take a long time!)...", 'msg' ); $dbw = wfGetDB( DB_MASTER ); foreach ( $res as $row ) { @@ -65,8 +73,10 @@ class DeleteDefaultMessages extends Maintenance { $article->doDeleteArticle( $reason ); $dbw->commit(); } + + $this->output( 'done!', 'msg' ); } } $maintClass = "DeleteDefaultMessages"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/deleteImageMemcached.php b/maintenance/deleteImageMemcached.php index 9becddb8..007f0d17 100644 --- a/maintenance/deleteImageMemcached.php +++ b/maintenance/deleteImageMemcached.php @@ -23,7 +23,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class DeleteImageCache extends Maintenance { public function __construct() { @@ -36,8 +36,8 @@ class DeleteImageCache extends Maintenance { public function execute() { global $wgMemc; - $until = preg_replace( "/[^\d]/", '', $this->getOption('until') ); - $sleep = (int)$this->getOption('sleep') * 1000; // milliseconds + $until = preg_replace( "/[^\d]/", '', $this->getOption( 'until' ) ); + $sleep = (int)$this->getOption( 'sleep' ) * 1000; // milliseconds ini_set( 'display_errors', false ); @@ -53,12 +53,12 @@ class DeleteImageCache extends Maintenance { $total = $this->getImageCount(); foreach ( $res as $row ) { - if ($i % $this->report == 0) - $this->output( sprintf("%s: %13s done (%s)\n", wfWikiID(), "$i/$total", wfPercent( $i / $total * 100 ) ) ); + if ( $i % $this->report == 0 ) + $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 ($sleep != 0) + if ( $sleep != 0 ) usleep( $sleep ); ++$i; @@ -72,4 +72,4 @@ class DeleteImageCache extends Maintenance { } $maintClass = "DeleteImageCache"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/deleteOldRevisions.php b/maintenance/deleteOldRevisions.php index 1f4dc4c9..ba76e9e9 100644 --- a/maintenance/deleteOldRevisions.php +++ b/maintenance/deleteOldRevisions.php @@ -22,7 +22,7 @@ * @author Rob Church <robchur@gmail.com> */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class DeleteOldRevisions extends Maintenance { public function __construct() { @@ -31,24 +31,24 @@ class DeleteOldRevisions extends Maintenance { $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() ) { # 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 ); @@ -56,46 +56,46 @@ class DeleteOldRevisions extends Maintenance { $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 ) { + 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 ) { + 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 ) { + 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 ) { + if ( $delete ) { $this->purgeRedundantText( true ); } } } $maintClass = "DeleteOldRevisions"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/deleteOrphanedRevisions.php b/maintenance/deleteOrphanedRevisions.php index 1146befb..e972d1fa 100644 --- a/maintenance/deleteOrphanedRevisions.php +++ b/maintenance/deleteOrphanedRevisions.php @@ -24,7 +24,7 @@ * @todo More efficient cleanup of text records */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class DeleteOrphanedRevisions extends Maintenance { public function __construct() { @@ -36,7 +36,7 @@ class DeleteOrphanedRevisions extends Maintenance { public function execute() { $this->output( "Delete Orphaned Revisions\n" ); - $report = $this->hasOption('report'); + $report = $this->hasOption( 'report' ); $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); @@ -46,45 +46,44 @@ class DeleteOrphanedRevisions extends Maintenance { $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 ) + 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 ) { + if ( $report || $count == 0 ) { $dbw->commit(); - exit(0); + 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) + * @param $dbw Database class (needs to be a master) */ private function deleteRevs( $id, &$dbw ) { - if( !is_array( $id ) ) + if ( !is_array( $id ) ) $id = array( $id ); $dbw->delete( 'revision', array( 'rev_id' => $id ), __METHOD__ ); } } $maintClass = "DeleteOrphanedRevisions"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/deleteRevision.php b/maintenance/deleteRevision.php index 5dc0b59f..5e8ecaac 100644 --- a/maintenance/deleteRevision.php +++ b/maintenance/deleteRevision.php @@ -20,24 +20,24 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); 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 ) { + if ( count( $this->mArgs ) == 0 ) { $this->error( "No revisions specified", true ); } - $this->output( "Deleting revision(s) " . implode( ',', $this->mArgs ) . + $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' ), @@ -78,4 +78,4 @@ class DeleteRevision extends Maintenance { } $maintClass = "DeleteRevision"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/deleteSelfExternals.php b/maintenance/deleteSelfExternals.php index 1ab2839e..db23e92c 100644 --- a/maintenance/deleteSelfExternals.php +++ b/maintenance/deleteSelfExternals.php @@ -2,7 +2,7 @@ /** * 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 + * 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 * @@ -24,7 +24,7 @@ * @ingroup Maintenance */ -require_once( "Maintenance.php" ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class DeleteSelfExternals extends Maintenance { @@ -33,22 +33,22 @@ class DeleteSelfExternals extends Maintenance { $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) { + $db = wfGetDB( DB_MASTER ); + while ( 1 ) { wfWaitForSlaves( 2 ); $db->commit(); - $q = $db->limitResult( "DELETE /* deleteSelfExternals */ FROM externallinks WHERE el_to" + $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); + $db->query( $q ); + if ( !$db->affectedRows() ) return; } } } $maintClass = "DeleteSelfExternals"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/doMaintenance.php b/maintenance/doMaintenance.php index 008c5b87..a9f5fae7 100644 --- a/maintenance/doMaintenance.php +++ b/maintenance/doMaintenance.php @@ -2,7 +2,7 @@ /** * 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 + * 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 * @@ -26,18 +26,21 @@ * @ingroup Maintenance */ -if ( !defined( 'DO_MAINTENANCE' ) ) { +if ( !defined( 'RUN_MAINTENANCE_IF_MAIN' ) ) { 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 ); +// Wasn't included from the file scope, halt execution (probably wanted the class) +// If a class is using commandLine.inc (old school maintenance), they definitely +// cannot be included and will proceed with execution +if( !Maintenance::shouldExecute() && $maintClass != 'CommandLineInc' ) { + return; } -if( defined( 'MW_NO_SETUP' ) ) { - return; +if ( !$maintClass || !class_exists( $maintClass ) ) { + echo "\$maintClass is not set or is set to a non-existent class.\n"; + exit( 1 ); } // Get an object to start us off @@ -51,6 +54,7 @@ $maintenance->setup(); $self = $maintenance->getName(); # Setup the profiler +global $IP; if ( file_exists( "$IP/StartProfiler.php" ) ) { require_once( "$IP/StartProfiler.php" ); } else { @@ -60,10 +64,19 @@ if ( file_exists( "$IP/StartProfiler.php" ) ) { // Some other requires require_once( "$IP/includes/AutoLoader.php" ); require_once( "$IP/includes/Defines.php" ); +require_once( "$IP/includes/DefaultSettings.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" ) ) { +if ( defined( 'MW_CONFIG_CALLBACK' ) ) { + # Use a callback function to configure MediaWiki + $callback = MW_CONFIG_CALLBACK; + # PHP 5.1 doesn't support "class::method" for call_user_func, so split it + if ( strpos( $callback, '::' ) !== false ) { + $callback = explode( '::', $callback, 2 ); + } + call_user_func( $callback ); +} elseif ( file_exists( "$IP/wmf-config/wikimedia-mode" ) ) { + // Load settings, using wikimedia-mode if needed + // Fixme: replace this hack with general farm-friendly code # TODO FIXME! Wikimedia-specific stuff needs to go away to an ext # Maybe a hook? global $cluster; @@ -72,10 +85,11 @@ if( file_exists( "$IP/wmf-config/wikimedia-mode" ) ) { require_once( "$IP/includes/SiteConfiguration.php" ); require( "$IP/wmf-config/wgConf.php" ); $maintenance->loadWikimediaSettings(); - require( $IP.'/wmf-config/CommonSettings.php' ); + require( $IP . '/wmf-config/CommonSettings.php' ); } else { require_once( $maintenance->loadSettings() ); } + if ( $maintenance->getDbType() === Maintenance::DB_ADMIN && is_readable( "$IP/AdminSettings.php" ) ) { @@ -87,7 +101,7 @@ require_once( "$IP/includes/Setup.php" ); require_once( "$IP/maintenance/install-utils.inc" ); // Much much faster startup than creating a title object -$wgTitle = null; +$wgTitle = null; // Do the work try { @@ -95,7 +109,7 @@ try { // Potentially debug globals $maintenance->globals(); -} catch( MWException $mwe ) { +} catch ( MWException $mwe ) { echo( $mwe->getText() ); exit( 1 ); } diff --git a/maintenance/dumpBackup.php b/maintenance/dumpBackup.php index 3f4530ed..90e8f72f 100644 --- a/maintenance/dumpBackup.php +++ b/maintenance/dumpBackup.php @@ -1,6 +1,9 @@ <?php /** - * Copyright (C) 2005 Brion Vibber <brion@pobox.com> + * Script that dumps wiki pages or logging database into an XML interchange + * wrapper format for export or backup + * + * Copyright © 2005 Brion Vibber <brion@pobox.com> * http://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify @@ -26,12 +29,12 @@ $originalDir = getcwd(); $optionsWithArgs = array( 'pagelist', 'start', 'end' ); -require_once( dirname(__FILE__) . '/commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/commandLine.inc' ); require_once( 'backup.inc' ); $dumper = new BackupDumper( $argv ); -if( isset( $options['quiet'] ) ) { +if ( isset( $options['quiet'] ) ) { $dumper->reporting = false; } @@ -47,10 +50,10 @@ if ( isset( $options['pagelist'] ) ) { $dumper->pages = array_filter( $pages, create_function( '$x', 'return $x !== "";' ) ); } -if( isset( $options['start'] ) ) { +if ( isset( $options['start'] ) ) { $dumper->startId = intval( $options['start'] ); } -if( isset( $options['end'] ) ) { +if ( isset( $options['end'] ) ) { $dumper->endId = intval( $options['end'] ); } $dumper->skipHeader = isset( $options['skip-header'] ); @@ -59,13 +62,13 @@ $dumper->dumpUploads = isset( $options['uploads'] ); $textMode = isset( $options['stub'] ) ? WikiExporter::STUB : WikiExporter::TEXT; -if( isset( $options['full'] ) ) { +if ( isset( $options['full'] ) ) { $dumper->dump( WikiExporter::FULL, $textMode ); -} elseif( isset( $options['current'] ) ) { +} elseif ( isset( $options['current'] ) ) { $dumper->dump( WikiExporter::CURRENT, $textMode ); -} elseif( isset( $options['stable'] ) ) { +} elseif ( isset( $options['stable'] ) ) { $dumper->dump( WikiExporter::STABLE, $textMode ); -} elseif( isset( $options['logs'] ) ) { +} elseif ( isset( $options['logs'] ) ) { $dumper->dump( WikiExporter::LOGS ); } else { $dumper->progress( <<<ENDS @@ -79,11 +82,14 @@ Actions: --full Dump all revisions of every page. --current Dump only the latest revision of every page. --logs Dump all log events. + --stable Stable versions of pages? + --pagelist=<file> + Where <file> is a list of page titles to be dumped Options: --quiet Don't dump status reports to stderr. --report=n Report position and speed after every n pages processed. - (Default: 100) + (Default: 100) --server=h Force reading from MySQL server h --start=n Start from page_id or log_id n --end=n Stop before page_id or log_id n (exclusive) @@ -91,11 +97,14 @@ Options: --skip-footer Don't output the </mediawiki> footer --stub Don't perform old_text lookups; for 2-pass dump --uploads Include upload records (experimental) + --conf=<file> Use the specified configuration file (LocalSettings.php) + + --wiki=<wiki> Only back up the specified <wiki> 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 + <type>s: file, gzip, bzip2, 7zip --filter=<type>[:<options>] Add a filter on an output branch ENDS diff --git a/maintenance/dumpInterwiki.inc b/maintenance/dumpInterwiki.inc deleted file mode 100644 index c366b08c..00000000 --- a/maintenance/dumpInterwiki.inc +++ /dev/null @@ -1,209 +0,0 @@ -<?php -/** - * Rebuild interwiki table using the file on meta and the language list - * Wikimedia specific! - * - * @file - * @todo document - * @ingroup Maintenance - * @ingroup Wikimedia - */ - -/** - * @todo document - * @ingroup Maintenance - */ -class Site { - var $suffix, $lateral, $url; - - function __construct( $s, $l, $u ) { - $this->suffix = $s; - $this->lateral = $l; - $this->url = $u; - } - - function getURL( $lang ) { - $xlang = str_replace( '_', '-', $lang ); - return "http://$xlang.{$this->url}/wiki/\$1"; - } -} - -function getRebuildInterwikiDump() { - global $langlist, $languageAliases, $prefixRewrites; - - # Multi-language sites - # db suffix => db suffix, iw prefix, hostname - $sites = array( - 'wiki' => new Site( 'wiki', 'w', 'wikipedia.org' ), - 'wiktionary' => new Site( 'wiktionary', 'wikt', 'wiktionary.org' ), - 'wikiquote' => new Site( 'wikiquote', 'q', 'wikiquote.org' ), - 'wikibooks' => new Site( 'wikibooks', 'b', 'wikibooks.org' ), - 'wikinews' => new Site( 'wikinews', 'n', 'wikinews.org' ), - 'wikisource' => new Site( 'wikisource', 's', 'wikisource.org' ), - 'wikimedia' => new Site( 'wikimedia', 'chapter', 'wikimedia.org' ), - 'wikiversity' => new Site( 'wikiversity', 'v', 'wikiversity.org' ), - ); - - # List of language prefixes likely to be found in multi-language sites - $langlist = array_map( "trim", file( "/home/wikipedia/common/langlist" ) ); - - # List of all database names - $dblist = array_map( "trim", file( "/home/wikipedia/common/all.dblist" ) ); - - # Special-case databases - $specials = array_flip( - array_map( "trim", - file( "/home/wikipedia/common/special.dblist" ) ) ); - - # Extra interwiki links that can't be in the intermap for some reason - $extraLinks = array( - array( 'm', 'http://meta.wikimedia.org/wiki/$1', 1 ), - array( 'meta', 'http://meta.wikimedia.org/wiki/$1', 1 ), - array( 'sep11', 'http://sep11.wikipedia.org/wiki/$1', 1 ), - ); - - # Language aliases, usually configured as redirects to the real wiki in apache - # Interlanguage links are made directly to the real wiki - # Something horrible happens if you forget to list an alias here, I can't - # remember what - $languageAliases = array( - 'zh-cn' => 'zh', - 'zh-tw' => 'zh', - 'dk' => 'da', - 'nb' => 'no', - ); - - # Special case prefix rewrites, for the benefit of Swedish which uses s:t - # as an abbreviation for saint - $prefixRewrites = array( - 'svwiki' => array ( 's' => 'src'), - ); - - # Construct a list of reserved prefixes - $reserved = array(); - foreach ( $langlist as $lang ) { - $reserved[$lang] = 1; - } - foreach ( $languageAliases as $alias => $lang ) { - $reserved[$alias] = 1; - } - foreach( $sites as $site ) { - $reserved[$site->lateral] = 1; - } - - # Extract the intermap from meta - $intermap = Http::get( 'http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw', 30 ); - $lines = array_map( 'trim', explode( "\n", trim( $intermap ) ) ); - - if ( !$lines || count( $lines ) < 2 ) { - wfDie( "m:Interwiki_map not found" ); - } - - # Global iterwiki map - foreach ( $lines as $line ) { - if ( preg_match( '/^\|\s*(.*?)\s*\|\|\s*(.*?)\s*$/', $line, $matches ) ) { - $prefix = strtolower( $matches[1] ); - $url = $matches[2]; - if ( preg_match( '/(wikipedia|wiktionary|wikisource|wikiquote|wikibooks|wikimedia)\.org/', $url ) ) { - $local = 1; - } else { - $local = 0; - } - - if ( empty( $reserved[$prefix] ) ) { - $imap = array( "iw_prefix" => $prefix, "iw_url" => $url, "iw_local" => $local ); - makeLink ($imap, "__global"); - } - } - } - - # Exclude Wikipedia for Wikipedia - makeLink ( array ('iw_prefix' => 'wikipedia', 'is_url' => null ), "_wiki" ); - - #Multilanguage sites - foreach ($sites as $site) - makeLanguageLinks ( $site, "_".$site->suffix ); - - - foreach ( $dblist as $db ) { - if ( isset( $specials[$db] ) ) { - # Special wiki - # Has interwiki links and interlanguage links to wikipedia - - makeLink( array( 'iw_prefix' => $db, 'iw_url' => "wiki"), "__sites" ); - # Links to multilanguage sites - foreach ( $sites as $targetSite ) { - makeLink( array( 'iw_prefix' => $targetSite->lateral, - 'iw_url' =>$targetSite->getURL( 'en' ), - 'iw_local' => 1 ), $db ); - } - - } else { - # Find out which site this DB belongs to - $site = false; - foreach( $sites as $candidateSite ) { - $suffix = $candidateSite->suffix; - if ( preg_match( "/(.*)$suffix$/", $db, $matches ) ) { - $site = $candidateSite; - break; - } - } - makeLink( array( 'iw_prefix' => $db, 'iw_url' => $site->suffix), "__sites" ); - if ( !$site ) { - print "Invalid database $db\n"; - continue; - } - $lang = $matches[1]; - - # Lateral links - foreach ( $sites as $targetSite ) { - if ( $targetSite->suffix != $site->suffix ) { - makeLink( array( 'iw_prefix' => $targetSite->lateral, - 'iw_url' => $targetSite->getURL( $lang ), - 'iw_local' => 1 ), $db ); - } - } - - if ( $site->suffix == "wiki" ) { - makeLink( array('iw_prefix' => 'w', - 'iw_url' => "http://en.wikipedia.org/wiki/$1", - 'iw_local' => 1), $db ); - } - - } - } - foreach ( $extraLinks as $link ) - makeLink( $link, "__global" ); -} - -# ------------------------------------------------------------------------------------------ - -# Executes part of an INSERT statement, corresponding to all interlanguage links to a particular site -function makeLanguageLinks( &$site, $source ) { - global $langlist, $languageAliases; - # Actual languages with their own databases - foreach ( $langlist as $targetLang ) { - makeLink( array( $targetLang, $site->getURL( $targetLang ), 1 ), $source ); - } - - # Language aliases - foreach ( $languageAliases as $alias => $lang ) { - makeLink( array( $alias, $site->getURL( $lang ), 1 ), $source ); - } -} - -function makeLink( $entry, $source ) { - global $prefixRewrites, $dbFile; - if ( isset( $prefixRewrites[$source] ) && isset( $prefixRewrites[$source][$entry[0]] ) ) - $entry[0] = $prefixRewrites[$source][$entry[0]]; - if (!array_key_exists("iw_prefix",$entry)) - $entry = array("iw_prefix" => $entry[0], "iw_url" => $entry[1], "iw_local" => $entry[2]); - if ( array_key_exists($source,$prefixRewrites) && - array_key_exists($entry['iw_prefix'],$prefixRewrites[$source])) - $entry['iw_prefix'] = $prefixRewrites[$source][$entry['iw_prefix']]; - if ($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 045e393b..4a4b6791 100644 --- a/maintenance/dumpInterwiki.php +++ b/maintenance/dumpInterwiki.php @@ -1,6 +1,6 @@ <?php /** - * Rebuild interwiki table using the file on meta and the language list + * Build constant slightly compact database of interwiki prefixes * Wikimedia specific! * * @file @@ -9,19 +9,231 @@ * @ingroup Wikimedia */ -/** */ -$oldCwd = getcwd(); +/** + * @todo document + * @ingroup Maintenance + */ +class Site { + var $suffix, $lateral, $url; + + function __construct( $s, $l, $u ) { + $this->suffix = $s; + $this->lateral = $l; + $this->url = $u; + } + + function getURL( $lang ) { + $xlang = str_replace( '_', '-', $lang ); + return "http://$xlang.{$this->url}/wiki/\$1"; + } +} + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); + +class DumpInterwiki extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = "Build constant slightly compact database of interwiki prefixes."; + $this->addOption( 'langlist', 'File with one language code per line', false, true ); + $this->addOption( 'dblist', 'File with one db per line', false, true ); + $this->addOption( 'specialdbs', "File with one 'special' db per line", false, true ); + $this->addOption( 'o', 'Cdb output file', false, true ); + } + + function execute() { + # List of language prefixes likely to be found in multi-language sites + $this->langlist = array_map( "trim", file( $this->getOption( 'langlist', "/home/wikipedia/common/langlist" ) ) ); + + # List of all database names + $this->dblist = array_map( "trim", file( $this->getOption( 'dblist', "/home/wikipedia/common/all.dblist" ) ) ); + + # Special-case databases + $this->specials = array_flip( array_map( "trim", file( $this->getOption( 'specialdbs', "/home/wikipedia/common/special.dblist" ) ) ) ); + + if ( $this->hasOption( 'o' ) ) { + $this->dbFile = CdbWriter::open( $this->getOption( 'o' ) ) ; + } else { + $this->dbFile = false; + } + + $this->getRebuildInterwikiDump(); + } + + function getRebuildInterwikiDump() { + global $wgContLang; + + # Multi-language sites + # db suffix => db suffix, iw prefix, hostname + $sites = array( + 'wiki' => new Site( 'wiki', 'w', 'wikipedia.org' ), + 'wiktionary' => new Site( 'wiktionary', 'wikt', 'wiktionary.org' ), + 'wikiquote' => new Site( 'wikiquote', 'q', 'wikiquote.org' ), + 'wikibooks' => new Site( 'wikibooks', 'b', 'wikibooks.org' ), + 'wikinews' => new Site( 'wikinews', 'n', 'wikinews.org' ), + 'wikisource' => new Site( 'wikisource', 's', 'wikisource.org' ), + 'wikimedia' => new Site( 'wikimedia', 'chapter', 'wikimedia.org' ), + 'wikiversity' => new Site( 'wikiversity', 'v', 'wikiversity.org' ), + ); + + # Extra interwiki links that can't be in the intermap for some reason + $extraLinks = array( + array( 'm', 'http://meta.wikimedia.org/wiki/$1', 1 ), + array( 'meta', 'http://meta.wikimedia.org/wiki/$1', 1 ), + array( 'sep11', 'http://sep11.wikipedia.org/wiki/$1', 1 ), + ); + + # Language aliases, usually configured as redirects to the real wiki in apache + # Interlanguage links are made directly to the real wiki + # Something horrible happens if you forget to list an alias here, I can't + # remember what + $this->languageAliases = array( + 'zh-cn' => 'zh', + 'zh-tw' => 'zh', + 'dk' => 'da', + 'nb' => 'no', + ); + + # Special case prefix rewrites, for the benefit of Swedish which uses s:t + # as an abbreviation for saint + $this->prefixRewrites = array( + 'svwiki' => array( 's' => 'src' ), + ); + + # Construct a list of reserved prefixes + $reserved = array(); + foreach ( $this->langlist as $lang ) { + $reserved[$lang] = 1; + } + foreach ( $this->languageAliases as $alias => $lang ) { + $reserved[$alias] = 1; + } + foreach ( $sites as $site ) { + $reserved[$site->lateral] = 1; + } + + # Extract the intermap from meta + $intermap = Http::get( 'http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw', 30 ); + $lines = array_map( 'trim', explode( "\n", trim( $intermap ) ) ); + + if ( !$lines || count( $lines ) < 2 ) { + $this->error( "m:Interwiki_map not found", true ); + } + + # Global iterwiki map + foreach ( $lines as $line ) { + if ( preg_match( '/^\|\s*(.*?)\s*\|\|\s*(.*?)\s*$/', $line, $matches ) ) { + $prefix = $wgContLang->lc( $matches[1] ); + $prefix = str_replace( ' ', '_', $prefix ); + + $url = $matches[2]; + if ( preg_match( '/(wikipedia|wiktionary|wikisource|wikiquote|wikibooks|wikimedia)\.org/', $url ) ) { + $local = 1; + } else { + $local = 0; + } + + if ( empty( $reserved[$prefix] ) ) { + $imap = array( "iw_prefix" => $prefix, "iw_url" => $url, "iw_local" => $local ); + $this->makeLink ( $imap, "__global" ); + } + } + } + + # Exclude Wikipedia for Wikipedia + $this->makeLink ( array ( 'iw_prefix' => 'wikipedia', 'is_url' => null ), "_wiki" ); + + # Multilanguage sites + foreach ( $sites as $site ) { + $this->makeLanguageLinks ( $site, "_" . $site->suffix ); + } + + foreach ( $this->dblist as $db ) { + if ( isset( $this->specials[$db] ) ) { + # Special wiki + # Has interwiki links and interlanguage links to wikipedia + + $this->makeLink( array( 'iw_prefix' => $db, 'iw_url' => "wiki" ), "__sites" ); + # Links to multilanguage sites + foreach ( $sites as $targetSite ) { + $this->makeLink( array( 'iw_prefix' => $targetSite->lateral, + 'iw_url' => $targetSite->getURL( 'en' ), + 'iw_local' => 1 ), $db ); + } + } else { + # Find out which site this DB belongs to + $site = false; + foreach ( $sites as $candidateSite ) { + $suffix = $candidateSite->suffix; + if ( preg_match( "/(.*)$suffix$/", $db, $matches ) ) { + $site = $candidateSite; + break; + } + } + $this->makeLink( array( 'iw_prefix' => $db, 'iw_url' => $site->suffix ), "__sites" ); + if ( !$site ) { + $this->error( "Invalid database $db\n" ); + continue; + } + $lang = $matches[1]; + + # Lateral links + foreach ( $sites as $targetSite ) { + if ( $targetSite->suffix != $site->suffix ) { + $this->makeLink( array( 'iw_prefix' => $targetSite->lateral, + 'iw_url' => $targetSite->getURL( $lang ), + 'iw_local' => 1 ), $db ); + } + } + + if ( $site->suffix == "wiki" ) { + $this->makeLink( array( 'iw_prefix' => 'w', + 'iw_url' => "http://en.wikipedia.org/wiki/$1", + 'iw_local' => 1 ), $db ); + } + + } + } + foreach ( $extraLinks as $link ) { + $this->makeLink( $link, "__global" ); + } + } + + # ------------------------------------------------------------------------------------------ + + # Executes part of an INSERT statement, corresponding to all interlanguage links to a particular site + function makeLanguageLinks( &$site, $source ) { + # Actual languages with their own databases + foreach ( $this->langlist as $targetLang ) { + $this->makeLink( array( $targetLang, $site->getURL( $targetLang ), 1 ), $source ); + } + + # Language aliases + foreach ( $this->languageAliases as $alias => $lang ) { + $this->makeLink( array( $alias, $site->getURL( $lang ), 1 ), $source ); + } + } + + function makeLink( $entry, $source ) { + if ( isset( $this->prefixRewrites[$source] ) && isset( $this->prefixRewrites[$source][$entry[0]] ) ) + $entry[0] = $this->prefixRewrites[$source][$entry[0]]; -$optionsWithArgs = array( "o" ); -require_once( dirname(__FILE__) . '/commandLine.inc' ); -require( dirname(__FILE__)."/dumpInterwiki.inc" ); -chdir( $oldCwd ); + if ( !array_key_exists( "iw_prefix", $entry ) ) { + $entry = array( "iw_prefix" => $entry[0], "iw_url" => $entry[1], "iw_local" => $entry[2] ); + } + if ( array_key_exists( $source, $this->prefixRewrites ) && + array_key_exists( $entry['iw_prefix'], $this->prefixRewrites[$source] ) ) { + $entry['iw_prefix'] = $this->prefixRewrites[$source][$entry['iw_prefix']]; + } -# Output -if ( isset( $options['o'] ) ) { - # To database specified with -o - $dbFile = CdbWriter::open( $options['o'] ); -} + if ( $this->dbFile ) { + $this->dbFile->set( "{$source}:{$entry['iw_prefix']}", trim( "{$entry['iw_local']} {$entry['iw_url']}" ) ); + } else { + $this->output( "{$source}:{$entry['iw_prefix']} {$entry['iw_url']} {$entry['iw_local']}\n" ); + } + } +} -getRebuildInterwikiDump(); +$maintClass = "DumpInterwiki"; +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/dumpLinks.php b/maintenance/dumpLinks.php index 529cd1aa..39a9e955 100644 --- a/maintenance/dumpLinks.php +++ b/maintenance/dumpLinks.php @@ -29,7 +29,7 @@ * @ingroup Mainatenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class DumpLinks extends Maintenance { public function __construct() { @@ -49,11 +49,11 @@ class DumpLinks extends Maintenance { 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 ) ) { + foreach ( $result as $row ) { + if ( $lastPage != $row->page_id ) { + if ( isset( $lastPage ) ) { $this->output( "\n" ); } $page = Title::makeTitle( $row->page_namespace, $row->page_title ); @@ -63,11 +63,11 @@ class DumpLinks extends Maintenance { $link = Title::makeTitle( $row->pl_namespace, $row->pl_title ); $this->output( " " . $link->getPrefixedUrl() ); } - if( isset( $lastPage ) ) + if ( isset( $lastPage ) ) $this->output( "\n" ); } } $maintClass = "DumpLinks"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/dumpSisterSites.php b/maintenance/dumpSisterSites.php index d9fd28a6..f5abcd1b 100644 --- a/maintenance/dumpSisterSites.php +++ b/maintenance/dumpSisterSites.php @@ -24,14 +24,14 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); 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 ); @@ -42,15 +42,14 @@ class DumpSisterSites extends Maintenance { ), __METHOD__ ); - foreach( $result as $row ) { + 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 ); } } $maintClass = "DumpSisterSites"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/dumpTextPass.php b/maintenance/dumpTextPass.php index 2e639e68..98d4af0e 100644 --- a/maintenance/dumpTextPass.php +++ b/maintenance/dumpTextPass.php @@ -1,6 +1,8 @@ <?php /** - * Copyright (C) 2005 Brion Vibber <brion@pobox.com> + * Script that postprocesses XML dumps from dumpBackup.php to add page text + * + * Copyright © 2005 Brion Vibber <brion@pobox.com>, 2010 Alexandre Emsenhuber * http://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify @@ -24,7 +26,7 @@ $originalDir = getcwd(); -require_once( dirname(__FILE__) . '/commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/commandLine.inc' ); require_once( 'backup.inc' ); /** @@ -33,14 +35,15 @@ require_once( 'backup.inc' ); class TextPassDumper extends BackupDumper { var $prefetch = null; var $input = "php://stdin"; - var $history = WikiExporter::FULL; var $fetchCount = 0; var $prefetchCount = 0; - + var $failures = 0; - var $maxFailures = 200; + var $maxFailures = 5; + var $failedTextRetrievals = 0; + var $maxConsecutiveFailedTextRetrievals = 200; var $failureTimeout = 5; // Seconds to sleep after db failure - + var $php = "php"; var $spawn = false; var $spawnProc = false; @@ -48,29 +51,22 @@ class TextPassDumper extends BackupDumper { var $spawnRead = false; var $spawnErr = false; - function dump() { + function dump( $history, $text = WikiExporter::TEXT ) { # This shouldn't happen if on console... ;) header( 'Content-type: text/html; charset=UTF-8' ); # Notice messages will foul up your XML output even if they're # relatively harmless. - if( ini_get( 'display_errors' ) ) + if ( ini_get( 'display_errors' ) ) ini_set( 'display_errors', 'stderr' ); - $this->initProgress( $this->history ); + $this->initProgress( $history ); $this->db = $this->backupDb(); - $this->egress = new ExportProgressFilter( $this->sink, $this ); + $this->readDump(); - $input = fopen( $this->input, "rt" ); - $result = $this->readDump( $input ); - - if( WikiError::isError( $result ) ) { - wfDie( $result->getMessage() ); - } - - if( $this->spawnProc ) { + if ( $this->spawnProc ) { $this->closeSpawn(); } @@ -78,59 +74,63 @@ class TextPassDumper extends BackupDumper { } function processOption( $opt, $val, $param ) { + global $IP; $url = $this->processFileOpt( $val, $param ); - + switch( $opt ) { case 'prefetch': - global $IP; require_once "$IP/maintenance/backupPrefetch.inc"; $this->prefetch = new BaseDump( $url ); break; case 'stub': $this->input = $url; break; - case 'current': - $this->history = WikiExporter::CURRENT; - break; - case 'full': - $this->history = WikiExporter::FULL; - break; case 'spawn': $this->spawn = true; - if( $val ) { + if ( $val ) { $this->php = $val; } break; } } - + function processFileOpt( $val, $param ) { - switch( $val ) { - case "file": - return $param; - case "gzip": - return "compress.zlib://$param"; - case "bzip2": - return "compress.bzip2://$param"; - case "7zip": - return "mediawiki.compress.7z://$param"; - default: - return $val; + $fileURIs = explode(';',$param); + foreach ( $fileURIs as $URI ) { + switch( $val ) { + case "file": + $newURI = $URI; + break; + case "gzip": + $newURI = "compress.zlib://$URI"; + break; + case "bzip2": + $newURI = "compress.bzip2://$URI"; + break; + case "7zip": + $newURI = "mediawiki.compress.7z://$URI"; + break; + default: + $newURI = $URI; + } + $newFileURIs[] = $newURI; } + $val = implode( ';', $newFileURIs ); + return $val; } /** * Overridden to include prefetch ratio if enabled. */ function showReport() { - if( !$this->prefetch ) { + if ( !$this->prefetch ) { return parent::showReport(); } - - if( $this->reporting ) { + + if ( $this->reporting ) { $delta = wfTime() - $this->startTime; $now = wfTimestamp( TS_DB ); - if( $delta ) { + if ( $delta ) { $rate = $this->pageCount / $delta; $revrate = $this->revCount / $delta; $portion = $this->revCount / $this->maxCount; @@ -148,150 +148,207 @@ class TextPassDumper extends BackupDumper { } } - function readDump( $input ) { - $this->buffer = ""; - $this->openElement = false; - $this->atStart = true; - $this->state = ""; - $this->lastName = ""; + function readDump() { + $state = ''; + $lastName = ''; $this->thisPage = 0; $this->thisRev = 0; - $parser = xml_parser_create( "UTF-8" ); - xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); + $reader = new XMLReader(); + $reader->open( $this->input ); + $writer = new XMLWriter(); + $writer->openMemory(); + - xml_set_element_handler( $parser, array( &$this, 'startElement' ), array( &$this, 'endElement' ) ); - xml_set_character_data_handler( $parser, array( &$this, 'characterData' ) ); + while ( $reader->read() ) { + $tag = $reader->name; + $type = $reader->nodeType; - $offset = 0; // for context extraction on error reporting - $bufferSize = 512 * 1024; - do { - $chunk = fread( $input, $bufferSize ); - if( !xml_parse( $parser, $chunk, feof( $input ) ) ) { - wfDebug( "TextDumpPass::readDump encountered XML parsing error\n" ); - return new WikiXmlError( $parser, 'XML import parse failure', $chunk, $offset ); + if ( $type == XmlReader::END_ELEMENT ) { + $writer->endElement(); + + if ( $tag == 'revision' ) { + $this->revCount(); + $this->thisRev = ''; + } elseif ( $tag == 'page' ) { + $this->reportPage(); + $this->thisPage = ''; + } + } elseif ( $type == XmlReader::ELEMENT ) { + $attribs = array(); + if ( $reader->hasAttributes ) { + for ( $i = 0; $reader->moveToAttributeNo( $i ); $i++ ) { + $attribs[$reader->name] = $reader->value; + } + } + + if ( $reader->isEmptyElement && $tag == 'text' && isset( $attribs['id'] ) ) { + $writer->startElement( 'text' ); + $writer->writeAttribute( 'xml:space', 'preserve' ); + $text = $this->getText( $attribs['id'] ); + if ( strlen( $text ) ) { + $writer->text( $text ); + } + $writer->endElement(); + } else { + $writer->startElement( $tag ); + foreach( $attribs as $name => $val ) { + $writer->writeAttribute( $name, $val ); + } + if ( $reader->isEmptyElement ) { + $writer->endElement(); + } + } + + $lastName = $tag; + if ( $tag == 'revision' ) { + $state = 'revision'; + } elseif ( $tag == 'page' ) { + $state = 'page'; + } + } elseif ( $type == XMLReader::SIGNIFICANT_WHITESPACE || $type = XMLReader::TEXT ) { + if ( $lastName == 'id' ) { + if ( $state == 'revision' ) { + $this->thisRev .= $reader->value; + } elseif ( $state == 'page' ) { + $this->thisPage .= $reader->value; + } + } + $writer->text( $reader->value ); } - $offset += strlen( $chunk ); - } while( $chunk !== false && !feof( $input ) ); - xml_parser_free( $parser ); - - return true; + $this->sink->write( $writer->outputMemory() ); + } } function getText( $id ) { $this->fetchCount++; - if( isset( $this->prefetch ) ) { + if ( isset( $this->prefetch ) ) { $text = $this->prefetch->prefetch( $this->thisPage, $this->thisRev ); - if( $text === null ) { - // Entry missing from prefetch dump - } elseif( $text === "" ) { - // Blank entries may indicate that the prior dump was broken. - // To be safe, reload it. - } else { - $this->prefetchCount++; - return $text; + if ( $text !== null ) { // Entry missing from prefetch dump + $dbr = wfGetDB( DB_SLAVE ); + $revID = intval( $this->thisRev ); + $revLength = $dbr->selectField( 'revision', 'rev_len', array( 'rev_id' => $revID ) ); + // if length of rev text in file doesn't match length in db, we reload + // this avoids carrying forward broken data from previous xml dumps + if( strlen( $text ) == $revLength ) { + $this->prefetchCount++; + return $text; + } } } return $this->doGetText( $id ); } - + private function doGetText( $id ) { - if( $this->spawn ) { - return $this->getTextSpawned( $id ); - } else { - return $this->getTextDbSafe( $id ); + $id = intval( $id ); + $this->failures = 0; + $ex = new MWException( "Graceful storage failure" ); + while (true) { + if ( $this->spawn ) { + if ($this->failures) { + // we don't know why it failed, could be the child process + // borked, could be db entry busted, could be db server out to lunch, + // so cover all bases + $this->closeSpawn(); + $this->openSpawn(); + } + $text = $this->getTextSpawned( $id ); + } else { + $text = $this->getTextDbSafe( $id ); + } + if ( $text === false ) { + $this->failures++; + if ( $this->failures > $this->maxFailures) { + $this->progress( "Failed to retrieve revision text for text id ". + "$id after $this->maxFailures tries, giving up" ); + // were there so many bad retrievals in a row we want to bail? + // at some point we have to declare the dump irretrievably broken + $this->failedTextRetrievals++; + if ($this->failedTextRetrievals > $this->maxConsecutiveFailedTextRetrievals) { + throw $ex; + } + else { + // would be nice to return something better to the caller someday, + // log what we know about the failure and about the revision + return(""); + } + } else { + $this->progress( "Error $this->failures " . + "of allowed $this->maxFailures retrieving revision text for text id $id! " . + "Pausing $this->failureTimeout seconds before retry..." ); + sleep( $this->failureTimeout ); + } + } else { + $this->failedTextRetrievals= 0; + return( $text ); + } } + } - + /** * Fetch a text revision from the database, retrying in case of failure. * This may survive some transitory errors by reconnecting, but * may not survive a long-term server outage. */ private function getTextDbSafe( $id ) { - while( true ) { + while ( true ) { try { $text = $this->getTextDb( $id ); - $ex = new MWException("Graceful storage failure"); - } catch (DBQueryError $ex) { + } catch ( DBQueryError $ex ) { $text = false; } - if( $text === false ) { - $this->failures++; - if( $this->failures > $this->maxFailures ) { - throw $ex; - } else { - $this->progress( "Database failure $this->failures " . - "of allowed $this->maxFailures for revision $id! " . - "Pausing $this->failureTimeout seconds..." ); - sleep( $this->failureTimeout ); - } - } else { - return $text; - } + return $text; } } - + /** * 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' ), array( 'old_id' => $id ), - 'TextPassDumper::getText' ); + __METHOD__ ); $text = Revision::getRevisionText( $row ); - if( $text === false ) { + if ( $text === false ) { return false; } $stripped = str_replace( "\r", "", $text ); $normalized = $wgContLang->normalize( $stripped ); return $normalized; } - + private function getTextSpawned( $id ) { wfSuppressWarnings(); - if( !$this->spawnProc ) { + if ( !$this->spawnProc ) { // First time? $this->openSpawn(); } - while( true ) { - - $text = $this->getTextSpawnedOnce( $id ); - if( !is_string( $text ) ) { - $this->progress("Database subprocess failed. Respawning..."); - - $this->closeSpawn(); - sleep( $this->failureTimeout ); - $this->openSpawn(); - - continue; - } - wfRestoreWarnings(); - return $text; - } + $text = $this->getTextSpawnedOnce( $id ); + wfRestoreWarnings(); + return $text; } - + function openSpawn() { - global $IP, $wgDBname; - + global $IP; + $cmd = implode( " ", array_map( 'wfEscapeShellArg', array( $this->php, "$IP/maintenance/fetchText.php", - $wgDBname ) ) ); + '--wiki', wfWikiID() ) ) ); $spec = array( 0 => array( "pipe", "r" ), 1 => array( "pipe", "w" ), 2 => array( "file", "/dev/null", "a" ) ); $pipes = array(); - + $this->progress( "Spawning database subprocess: $cmd" ); $this->spawnProc = proc_open( $cmd, $spec, $pipes ); - if( !$this->spawnProc ) { + if ( !$this->spawnProc ) { // shit $this->progress( "Subprocess spawn failed." ); return false; @@ -300,138 +357,83 @@ class TextPassDumper extends BackupDumper { $this->spawnWrite, // -> stdin $this->spawnRead, // <- stdout ) = $pipes; - + return true; } - + private function closeSpawn() { wfSuppressWarnings(); - if( $this->spawnRead ) + if ( $this->spawnRead ) fclose( $this->spawnRead ); $this->spawnRead = false; - if( $this->spawnWrite ) + if ( $this->spawnWrite ) fclose( $this->spawnWrite ); $this->spawnWrite = false; - if( $this->spawnErr ) + if ( $this->spawnErr ) fclose( $this->spawnErr ); $this->spawnErr = false; - if( $this->spawnProc ) + if ( $this->spawnProc ) pclose( $this->spawnProc ); $this->spawnProc = false; wfRestoreWarnings(); } - + private function getTextSpawnedOnce( $id ) { global $wgContLang; $ok = fwrite( $this->spawnWrite, "$id\n" ); - //$this->progress( ">> $id" ); - if( !$ok ) return false; - + // $this->progress( ">> $id" ); + if ( !$ok ) return false; + $ok = fflush( $this->spawnWrite ); - //$this->progress( ">> [flush]" ); - if( !$ok ) return false; - + // $this->progress( ">> [flush]" ); + if ( !$ok ) return false; + + // check that the text id they are sending is the one we asked for + // this avoids out of sync revision text errors we have encountered in the past + $newId = fgets( $this->spawnRead ); + if ( $newId === false ) { + return false; + } + if ( $id != intval( $newId ) ) { + return false; + } + $len = fgets( $this->spawnRead ); - //$this->progress( "<< " . trim( $len ) ); - if( $len === false ) return false; - + // $this->progress( "<< " . trim( $len ) ); + if ( $len === false ) return false; + $nbytes = intval( $len ); + // actual error, not zero-length text + if ($nbytes < 0 ) return false; + $text = ""; - + // Subprocess may not send everything at once, we have to loop. - while( $nbytes > strlen( $text ) ) { + while ( $nbytes > strlen( $text ) ) { $buffer = fread( $this->spawnRead, $nbytes - strlen( $text ) ); - if( $buffer === false ) break; + if ( $buffer === false ) break; $text .= $buffer; } - + $gotbytes = strlen( $text ); - if( $gotbytes != $nbytes ) { - $this->progress( "Expected $nbytes bytes from database subprocess, got $gotbytes "); + if ( $gotbytes != $nbytes ) { + $this->progress( "Expected $nbytes bytes from database subprocess, got $gotbytes " ); return false; } - + // Do normalization in the dump thread... $stripped = str_replace( "\r", "", $text ); $normalized = $wgContLang->normalize( $stripped ); return $normalized; } - - function startElement( $parser, $name, $attribs ) { - $this->clearOpenElement( null ); - $this->lastName = $name; - - if( $name == 'revision' ) { - $this->state = $name; - $this->egress->writeOpenPage( null, $this->buffer ); - $this->buffer = ""; - } elseif( $name == 'page' ) { - $this->state = $name; - if( $this->atStart ) { - $this->egress->writeOpenStream( $this->buffer ); - $this->buffer = ""; - $this->atStart = false; - } - } - - if( $name == "text" && isset( $attribs['id'] ) ) { - $text = $this->getText( $attribs['id'] ); - $this->openElement = array( $name, array( 'xml:space' => 'preserve' ) ); - if( strlen( $text ) > 0 ) { - $this->characterData( $parser, $text ); - } - } else { - $this->openElement = array( $name, $attribs ); - } - } - - function endElement( $parser, $name ) { - if( $this->openElement ) { - $this->clearOpenElement( "" ); - } else { - $this->buffer .= "</$name>"; - } - - if( $name == 'revision' ) { - $this->egress->writeRevision( null, $this->buffer ); - $this->buffer = ""; - $this->thisRev = ""; - } elseif( $name == 'page' ) { - $this->egress->writeClosePage( $this->buffer ); - $this->buffer = ""; - $this->thisPage = ""; - } elseif( $name == 'mediawiki' ) { - $this->egress->writeCloseStream( $this->buffer ); - $this->buffer = ""; - } - } - - function characterData( $parser, $data ) { - $this->clearOpenElement( null ); - if( $this->lastName == "id" ) { - if( $this->state == "revision" ) { - $this->thisRev .= $data; - } elseif( $this->state == "page" ) { - $this->thisPage .= $data; - } - } - $this->buffer .= htmlspecialchars( $data ); - } - - function clearOpenElement( $style ) { - if( $this->openElement ) { - $this->buffer .= Xml::element( $this->openElement[0], $this->openElement[1], $style ); - $this->openElement = false; - } - } } $dumper = new TextPassDumper( $argv ); -if( true ) { - $dumper->dump(); +if ( !isset( $options['help'] ) ) { + $dumper->dump( WikiExporter::FULL ); } else { $dumper->progress( <<<ENDS This script postprocesses XML dumps from dumpBackup.php to add @@ -444,14 +446,16 @@ Usage: php dumpTextPass.php [<options>] Options: --stub=<type>:<file> To load a compressed stub dump instead of stdin --prefetch=<type>:<file> Use a prior dump file as a text source, to save - pressure on the database. - (Requires PHP 5.0+ and the XMLReader PECL extension) - --quiet Don't dump status reports to stderr. + pressure on the database. + --quiet Don't dump status reports to stderr. --report=n Report position and speed after every n pages processed. - (Default: 100) + (Default: 100) --server=h Force reading from MySQL server h - --current Base ETA on number of pages in database instead of all revisions - --spawn Spawn a subprocess for loading text records + --output=<type>:<file> Write to a file instead of stdout + <type>s: file, gzip, bzip2, 7zip + --current Base ETA on number of pages in database instead of all revisions + --spawn Spawn a subprocess for loading text records + --help Display this help message ENDS ); } diff --git a/maintenance/dumpUploads.php b/maintenance/dumpUploads.php index c8f1667b..74c0cb0b 100644 --- a/maintenance/dumpUploads.php +++ b/maintenance/dumpUploads.php @@ -20,7 +20,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class UploadDumper extends Maintenance { public function __construct() { @@ -34,22 +34,22 @@ By default, outputs relative paths against the parent directory of \$wgUploadDir } public function execute() { - global $IP, $wgUseSharedUploads; + global $IP; $this->mAction = 'fetchLocal'; $this->mBasePath = $this->getOption( 'base', $IP ); $this->mShared = false; $this->mSharedSupplement = false; - if( $this->hasOption('local') ) { + if ( $this->hasOption( 'local' ) ) { $this->mAction = 'fetchLocal'; } - - if( $this->hasOption('used') ) { + + if ( $this->hasOption( 'used' ) ) { $this->mAction = 'fetchUsed'; } - - if( $this->hasOption('shared') ) { - if( $this->hasOption('used') ) { + + if ( $this->hasOption( 'shared' ) ) { + if ( $this->hasOption( 'used' ) ) { // Include shared-repo files in the used check $this->mShared = true; } else { @@ -57,51 +57,53 @@ By default, outputs relative paths against the parent directory of \$wgUploadDir $this->mSharedSupplement = true; } } - $this->{$this->mAction}( $this->mShared ); - if( $this->mSharedSupplement ) { + $this-> { $this->mAction } ( $this->mShared ); + if ( $this->mSharedSupplement ) { $this->fetchUsed( true ); } } /** - * Fetch a list of all or used images from a particular image source. - * @param string $table - * @param string $directory Base directory where files are located - * @param bool $shared true to pass shared-dir settings to hash func + * Fetch a list of used images from a particular image source. + * + * @param $shared Boolean: true to pass shared-dir settings to hash func */ function fetchUsed( $shared ) { $dbr = wfGetDB( DB_SLAVE ); $image = $dbr->tableName( 'image' ); $imagelinks = $dbr->tableName( 'imagelinks' ); - + $sql = "SELECT DISTINCT il_to, img_name FROM $imagelinks LEFT OUTER JOIN $image ON il_to=img_name"; $result = $dbr->query( $sql ); - - foreach( $result as $row ) { + + foreach ( $result as $row ) { $this->outputItem( $row->il_to, $shared ); } - $dbr->freeResult( $result ); } + /** + * Fetch a list of all images from a particular image source. + * + * @param $shared Boolean: true to pass shared-dir settings to hash func + */ function fetchLocal( $shared ) { $dbr = wfGetDB( DB_SLAVE ); $result = $dbr->select( 'image', array( 'img_name' ), '', __METHOD__ ); - - foreach( $result as $row ) { + + foreach ( $result as $row ) { $this->outputItem( $row->img_name, $shared ); } - $dbr->freeResult( $result ); } - + function outputItem( $name, $shared ) { $file = wfFindFile( $name ); - if( $file && $this->filterItem( $file, $shared ) ) { + if ( $file && $this->filterItem( $file, $shared ) ) { $filename = $file->getFullPath(); $rel = wfRelativePath( $filename, $this->mBasePath ); $this->output( "$rel\n" ); @@ -116,4 +118,4 @@ By default, outputs relative paths against the parent directory of \$wgUploadDir } $maintClass = "UploadDumper"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/edit.php b/maintenance/edit.php index 8d0068c3..40623afb 100644 --- a/maintenance/edit.php +++ b/maintenance/edit.php @@ -20,7 +20,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class EditCLI extends Maintenance { public function __construct() { @@ -36,7 +36,7 @@ class EditCLI extends Maintenance { } public function execute() { - global $wgUser, $wgTitle, $wgArticle; + global $wgUser, $wgTitle; $userName = $this->getOption( 'u', 'Maintenance script' ); $summary = $this->getOption( 's', '' ); @@ -44,7 +44,7 @@ class EditCLI extends Maintenance { $bot = $this->hasOption( 'b' ); $autoSummary = $this->hasOption( 'a' ); $noRC = $this->hasOption( 'no-rc' ); - + $wgUser = User::newFromName( $userName ); if ( !$wgUser ) { $this->error( "Invalid username", true ); @@ -52,22 +52,22 @@ class EditCLI extends Maintenance { if ( $wgUser->isAnon() ) { $wgUser->addToDatabase(); } - + $wgTitle = Title::newFromText( $this->getArg() ); if ( !$wgTitle ) { $this->error( "Invalid title", true ); } - - $wgArticle = new Article( $wgTitle ); - + + $article = new Article( $wgTitle ); + # Read the text $text = $this->getStdin( Maintenance::STDIN_ALL ); - + # Do the edit $this->output( "Saving... " ); - $status = $wgArticle->doEdit( $text, $summary, + $status = $article->doEdit( $text, $summary, ( $minor ? EDIT_MINOR : 0 ) | - ( $bot ? EDIT_FORCE_BOT : 0 ) | + ( $bot ? EDIT_FORCE_BOT : 0 ) | ( $autoSummary ? EDIT_AUTOSUMMARY : 0 ) | ( $noRC ? EDIT_SUPPRESS_RC : 0 ) ); if ( $status->isOK() ) { @@ -85,5 +85,5 @@ class EditCLI extends Maintenance { } $maintClass = "EditCLI"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/eval.php b/maintenance/eval.php index a990a4d8..3cc1d16a 100644 --- a/maintenance/eval.php +++ b/maintenance/eval.php @@ -16,7 +16,7 @@ * @ingroup Maintenance */ -$wgUseNormalUser = (bool)getenv('MW_WIKIUSER'); +$wgUseNormalUser = (bool)getenv( 'MW_WIKIUSER' ); $optionsWithArgs = array( 'd' ); @@ -39,8 +39,8 @@ if ( isset( $options['d'] ) ) { } } -if ( function_exists( 'readline_add_history' ) - && function_exists( 'posix_isatty' ) && posix_isatty( 0 /*STDIN*/ ) ) +if ( function_exists( 'readline_add_history' ) + && posix_isatty( 0 /*STDIN*/ ) ) { $useReadline = true; } else { @@ -48,19 +48,20 @@ if ( function_exists( 'readline_add_history' ) } if ( $useReadline ) { - $historyFile = "{$_ENV['HOME']}/.mweval_history"; + $historyFile = isset( $_ENV['HOME'] ) ? + "{$_ENV['HOME']}/.mweval_history" : "$IP/maintenance/.mweval_history"; readline_read_history( $historyFile ); } -while ( ( $line = readconsole( '> ' ) ) !== false ) { +while ( ( $line = Maintenance::readconsole() ) !== false ) { if ( $useReadline ) { readline_add_history( $line ); readline_write_history( $historyFile ); } $val = eval( $line . ";" ); - if( is_null( $val ) ) { + if ( is_null( $val ) ) { echo "\n"; - } elseif( is_string( $val ) || is_numeric( $val ) ) { + } elseif ( is_string( $val ) || is_numeric( $val ) ) { echo "$val\n"; } else { var_dump( $val ); diff --git a/maintenance/fetchText.php b/maintenance/fetchText.php index 746ef8ad..ea56535d 100644 --- a/maintenance/fetchText.php +++ b/maintenance/fetchText.php @@ -20,7 +20,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class FetchText extends Maintenance { public function __construct() { @@ -28,35 +28,52 @@ class FetchText extends Maintenance { $this->mDescription = "Fetch the revision text from an old_id"; } - public function execute() { + /* + * returns a string containing the following in order: + * textid + * \n + * length of text (-1 on error = failure to retrieve/unserialize/gunzip/etc) + * \n + * text (may be empty) + * + * note that that the text string itself is *not* followed by newline + */ + public function execute() { $db = wfGetDB( DB_SLAVE ); $stdin = $this->getStdin(); - while( !feof( $stdin ) ) { + while ( !feof( $stdin ) ) { $line = fgets( $stdin ); - if( $line === false ) { + if ( $line === false ) { // We appear to have lost contact... break; } $textId = intval( $line ); $text = $this->doGetText( $db, $textId ); - $this->output( strlen( $text ) . "\n". $text ); + if ($text === false) { + # actual error, not zero-length text + $textLen = "-1"; + } + else { + $textLen = strlen($text); + } + $this->output( $textId . "\n" . $textLen . "\n" . $text ); } } - + /** - * May throw a database error if, say, the server dies during query. + * 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' ); + __METHOD__ ); $text = Revision::getRevisionText( $row ); - if( $text === false ) { + if ( $text === false ) { return false; } return $text; @@ -64,4 +81,4 @@ class FetchText extends Maintenance { } $maintClass = "FetchText"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/findhooks.php b/maintenance/findhooks.php index 13236b6b..04a5faef 100644 --- a/maintenance/findhooks.php +++ b/maintenance/findhooks.php @@ -2,7 +2,7 @@ /** * Simple script that try to find documented hook and hooks actually * in the code and show what's missing. - * + * * This script assumes that: * - hooks names in hooks.txt are at the beginning of a line and single quoted. * - hooks names in code are the first parameter of wfRunHooks. @@ -12,6 +12,8 @@ * * Any instance of wfRunHooks that doesn't meet these parameters will be noted. * + * Copyright © Ashar Voultoiz + * * 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 @@ -27,14 +29,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance - * - * @author Ashar Voultoiz <hashar@altern.org> - * @copyright Copyright © Ashar voultoiz - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public Licence 2.0 or later + * @author Ashar Voultoiz <hashar at free dot fr> */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class FindHooks extends Maintenance { public function __construct() { @@ -54,37 +54,42 @@ class FindHooks extends Maintenance { $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/', + $IP . '/', + $IP . '/includes/', + $IP . '/includes/api/', + $IP . '/includes/db/', + $IP . '/includes/diff/', + $IP . '/includes/filerepo/', + $IP . '/includes/installer/', + $IP . '/includes/parser/', + $IP . '/includes/resourceloader/', + $IP . '/includes/revisiondelete/', + $IP . '/includes/search/', + $IP . '/includes/specials/', + $IP . '/includes/upload/', + $IP . '/languages/', + $IP . '/maintenance/', + $IP . '/maintenance/tests/', + $IP . '/maintenance/tests/parser/', + $IP . '/skins/', ); - foreach( $pathinc as $dir ) { + 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->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" ); } @@ -93,14 +98,14 @@ class FindHooks extends Maintenance { * @return array of documented hooks */ private function getHooksFromDoc( $doc ) { - if( $this->hasOption( 'online' ) ){ + 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 ) { + foreach ( $allhookdata['query']['categorymembers'] as $page ) { $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches ); - if( $found ) { + if ( $found ) { $hook = str_replace( ' ', '_', $matches[1] ); $allhooks[] = $hook; } @@ -109,9 +114,9 @@ class FindHooks extends Maintenance { $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 ) { + foreach ( $oldhookdata['query']['categorymembers'] as $page ) { $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches ); - if( $found ) { + if ( $found ) { $hook = str_replace( ' ', '_', $matches[1] ); $removed[] = $hook; } @@ -133,7 +138,7 @@ class FindHooks extends Maintenance { private function getHooksFromFile( $file ) { $content = file_get_contents( $file ); $m = array(); - preg_match_all( '/wfRunHooks\(\s*([\'"])(.*?)\1/', $content, $m); + preg_match_all( '/wfRunHooks\(\s*([\'"])(.*?)\1/', $content, $m ); return $m[2]; } @@ -144,13 +149,14 @@ class FindHooks extends Maintenance { */ 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) ); + $dh = opendir( $path ); + if ( $dh ) { + while ( ( $file = readdir( $dh ) ) !== false ) { + if ( filetype( $path . $file ) == 'file' ) { + $hooks = array_merge( $hooks, $this->getHooksFromFile( $path . $file ) ); } } - closedir($dh); + closedir( $dh ); } return $hooks; } @@ -164,9 +170,9 @@ class FindHooks extends Maintenance { $content = file_get_contents( $file ); $m = array(); # We want to skip the "function wfRunHooks()" one. :) - preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m); + preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m ); $list = array(); - foreach( $m[0] as $match ){ + foreach ( $m[0] as $match ) { $list[] = $match . "(" . $file . ")"; } return $list; @@ -179,14 +185,15 @@ class FindHooks extends Maintenance { */ private function getBadHooksFromPath( $path ) { $hooks = array(); - if( $dh = opendir($path) ) { - while(($file = readdir($dh)) !== false) { + $dh = opendir( $path ); + if ( $dh ) { + 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) ); + if ( filetype( $path . $file ) == 'file' && !$path . $file == __FILE__ ) { + $hooks = array_merge( $hooks, $this->getBadHooksFromFile( $path . $file ) ); } } - closedir($dh); + closedir( $dh ); } return $hooks; } @@ -198,10 +205,10 @@ class FindHooks extends Maintenance { * @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" ); + if ( $sort ) asort( $arr ); + foreach ( $arr as $v ) $this->output( "$msg: $v\n" ); } } $maintClass = "FindHooks"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/fixSlaveDesync.php b/maintenance/fixSlaveDesync.php index c585beb1..fe892944 100644 --- a/maintenance/fixSlaveDesync.php +++ b/maintenance/fixSlaveDesync.php @@ -18,32 +18,29 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class FixSlaveDesync extends Maintenance { public function __construct() { - global $wgUseRootUser; - $wgUseRootUser = true; - parent::__construct(); $this->mDescription = ""; - } - + + public function getDbType() { + return Maintenance::DB_ADMIN; + } + public function execute() { - global $slaveIndexes, $wgDBservers; - $slaveIndexes = array(); - for ( $i = 1; $i < count( $wgDBservers ); $i++ ) { + $this->slaveIndexes = array(); + for ( $i = 1; $i < wfGetLB()->getServerCount(); $i++ ) { if ( wfGetLB()->isNonZeroLoad( $i ) ) { - $slaveIndexes[] = $i; + $this->slaveIndexes[] = $i; } } 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 ); @@ -69,10 +66,8 @@ class FixSlaveDesync extends Maintenance { } } $this->output( "\n" ); - $dbw->freeResult( $res ); - global $slaveIndexes; - foreach ( $slaveIndexes as $i ) { + foreach ( $this->slaveIndexes as $i ) { $db = wfGetDB( $i ); $res = $db->select( 'page', array( 'page_id', 'page_latest' ), array( 'page_id<6054123' ), __METHOD__ ); foreach ( $res as $row ) { @@ -81,7 +76,6 @@ class FixSlaveDesync extends Maintenance { $this->output( $row->page_id . "\t" ); } } - $db->freeResult( $res ); } $this->output( "\n" ); return $desync; @@ -92,16 +86,14 @@ class FixSlaveDesync extends Maintenance { * @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 ), + $realLatest = $dbw->selectField( 'page', 'page_latest', array( 'page_id' => $pageID ), __METHOD__, 'FOR UPDATE' ); - #list( $masterFile, $masterPos ) = $dbw->getMasterPos(); + # list( $masterFile, $masterPos ) = $dbw->getMasterPos(); $found = false; - foreach ( $slaveIndexes as $i ) { + foreach ( $this->slaveIndexes as $i ) { $db = wfGetDB( $i ); /* if ( !$db->masterPosWait( $masterFile, $masterPos, 10 ) ) { @@ -109,7 +101,7 @@ class FixSlaveDesync extends Maintenance { $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 ) { @@ -125,20 +117,18 @@ class FixSlaveDesync extends Maintenance { } # Find the missing revisions - $res = $dbw->select( 'revision', array( 'rev_id' ), array( 'rev_page' => $pageID ), + $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 ), __METHOD__ ); $slaveIDs = array(); foreach ( $res as $row ) { $slaveIDs[] = $row->rev_id; } - $db->freeResult( $res ); if ( count( $masterIDs ) < count( $slaveIDs ) ) { $missingIDs = array_diff( $slaveIDs, $masterIDs ); if ( count( $missingIDs ) ) { @@ -167,7 +157,7 @@ class FixSlaveDesync extends Maintenance { # Revision $row = $dbFrom->selectRow( 'revision', '*', array( 'rev_id' => $rid ), __METHOD__ ); if ( $toMaster ) { - $id = $dbw->selectField( 'revision', 'rev_id', array( 'rev_id' => $rid ), + $id = $dbw->selectField( 'revision', 'rev_id', array( 'rev_id' => $rid ), __METHOD__, 'FOR UPDATE' ); if ( $id ) { $this->output( "Revision already exists\n" ); @@ -177,7 +167,7 @@ class FixSlaveDesync extends Maintenance { $dbw->insert( 'revision', get_object_vars( $row ), __METHOD__, 'IGNORE' ); } } else { - foreach ( $slaveIndexes as $i ) { + foreach ( $this->slaveIndexes as $i ) { $db = wfGetDB( $i ); $db->insert( 'revision', get_object_vars( $row ), __METHOD__, 'IGNORE' ); } @@ -188,7 +178,7 @@ class FixSlaveDesync extends Maintenance { if ( $toMaster ) { $dbw->insert( 'text', get_object_vars( $row ), __METHOD__, 'IGNORE' ); } else { - foreach ( $slaveIndexes as $i ) { + foreach ( $this->slaveIndexes as $i ) { $db = wfGetDB( $i ); $db->insert( 'text', get_object_vars( $row ), __METHOD__, 'IGNORE' ); } @@ -200,9 +190,9 @@ class FixSlaveDesync extends Maintenance { if ( $found ) { $this->output( "Fixing page_latest... " ); if ( $toMaster ) { - #$dbw->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), __METHOD__ ); + # $dbw->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), __METHOD__ ); } else { - foreach ( $slaveIndexes as $i ) { + foreach ( $this->slaveIndexes as $i ) { $db = wfGetDB( $i ); $db->update( 'page', array( 'page_latest' => $realLatest ), array( 'page_id' => $pageID ), __METHOD__ ); } @@ -214,4 +204,4 @@ class FixSlaveDesync extends Maintenance { } $maintClass = "FixSlaveDesync"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/fixTimestamps.php b/maintenance/fixTimestamps.php index ea102fb8..3e3bd0a5 100644 --- a/maintenance/fixTimestamps.php +++ b/maintenance/fixTimestamps.php @@ -1,9 +1,9 @@ <?php /** - * This script fixes timestamp corruption caused by one or more webservers + * This script fixes timestamp corruption caused by one or more webservers * temporarily being set to the wrong time. The time offset must be known and - * consistent. Start and end times (in 14-character format) restrict the search, - * and must bracket the damage. There must be a majority of good timestamps in the + * consistent. Start and end times (in 14-character format) restrict the search, + * and must bracket the damage. There must be a majority of good timestamps in the * search period. * * This program is free software; you can redistribute it and/or modify @@ -23,8 +23,8 @@ * * @ingroup Maintenance */ - -require_once( dirname(__FILE__) . '/Maintenance.php' ); + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class FixTimestamps extends Maintenance { public function __construct() { @@ -36,25 +36,25 @@ class FixTimestamps extends Maintenance { } public function execute() { - $offset = $this->getArg(0) * 3600; - $start = $this->getArg(1); - $end = $this->getArg(2); + $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"; @@ -64,13 +64,13 @@ class FixTimestamps extends Maintenance { } 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; @@ -89,26 +89,25 @@ class FixTimestamps extends Maintenance { $badRevs[] = $row->rev_id; } } - $dbw->freeResult( $res ); - + $numBadRevs = count( $badRevs ); if ( $numBadRevs > $numGoodRevs ) { - $this->error( + $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 + 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 + 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); + exit( 0 ); } - - $this->output( sprintf( "Fixing %d revisions (%.2f%% of revisions in search interval)\n", - $numBadRevs, $numBadRevs / ($numGoodRevs + $numBadRevs) * 100 ) ); - + + $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') " . @@ -119,4 +118,4 @@ class FixTimestamps extends Maintenance { } $maintClass = "FixTimestamps"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/fixUserRegistration.php b/maintenance/fixUserRegistration.php index d3305358..d4ff7c23 100644 --- a/maintenance/fixUserRegistration.php +++ b/maintenance/fixUserRegistration.php @@ -21,7 +21,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class FixUserRegistration extends Maintenance { public function __construct() { @@ -52,4 +52,4 @@ class FixUserRegistration extends Maintenance { } $maintClass = "FixUserRegistration"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/fuzz-tester.php b/maintenance/fuzz-tester.php index 6d8c57f2..a78522cd 100644 --- a/maintenance/fuzz-tester.php +++ b/maintenance/fuzz-tester.php @@ -13,9 +13,9 @@ Description: How: - Generate lots of nasty wiki text. - - Ask the Parser to render that wiki text to HTML, or ask MediaWiki's forms - to deal with that wiki text. - - Check MediaWiki's output for problems. + - Ask the Parser to render that wiki text to HTML, or ask MediaWiki's forms + to deal with that wiki text. + - Check MediaWiki's output for problems. - Repeat. Why: @@ -32,7 +32,7 @@ What type of problems are being checked for: - Optionally checking for malformed HTML using the W3C validator. Background: - Many of the wikiFuzz class methods are a modified PHP port, + Many of the wikiFuzz class methods are a modified PHP port, of a "shameless" Python port, of LCAMTUF'S MANGELME: - http://www.securiteam.com/tools/6Z00N1PBFK.html - http://www.securityfocus.com/archive/1/378632/2004-10-15/2004-10-21/0 @@ -43,15 +43,15 @@ Video: Requirements: To run this, you will need: - - Command-line PHP5, with PHP-curl enabled (not all installations have this - enabled - try "apt-get install php5-curl" if you're on Debian to install). + - Command-line PHP5, with PHP-curl enabled (not all installations have this + enabled - try "apt-get install php5-curl" if you're on Debian to install). - the Tidy standalone executable. ("apt-get install tidy"). Optional: - If you want to run the curl scripts, you'll need standalone curl installed - ("apt-get install curl") + ("apt-get install curl") - For viewing the W3C validator output on a command line, the "html2text" - program may be useful ("apt-get install html2text") + program may be useful ("apt-get install html2text") Saving tests and test results: Any of the fuzz tests which find problems are saved for later review. @@ -65,7 +65,7 @@ Saving tests and test results: Wiki configuration for testing: You should make some additions to LocalSettings.php in order to catch the most errors. Note this configuration is for **TESTING PURPOSES ONLY**, and is IN NO - WAY, SHAPE, OR FORM suitable for deployment on a hostile network. That said, + WAY, SHAPE, OR FORM suitable for deployment on a hostile network. That said, personally I find these additions to be the most helpful for testing purposes: // --------- Start --------- @@ -99,7 +99,7 @@ Wiki configuration for testing: $wgGroupPermissions['*']['makesysop'] = true; // Enable weird and wonderful options: - // Increase default error reporting level. + // Increase default error reporting level. error_reporting (E_ALL); // At a later date could be increased to E_ALL | E_STRICT $wgBlockOpenProxies = true; // Some block pages require this to be true in order to test. $wgEnableUploads = true; // enable uploads. @@ -127,14 +127,14 @@ Wiki configuration for testing: require_once("extensions/Renameuser/SpecialRenameuser.php"); require_once("extensions/LinkSearch/LinkSearch.php"); // --------- End --------- - + If you want to try E_STRICT error logging, add this to the above: // --------- Start --------- error_reporting (E_ALL | E_STRICT); set_error_handler( 'error_handler' ); function error_handler ($type, $message, $file=__FILE__, $line=__LINE__) { - if ($message == "var: Deprecated. Please use the public/private/protected modifiers") return; - print "<br />\n<b>Strict Standards:</b> Type: <b>$type</b>: $message in <b>$file</b> on line <b>$line</b><br />\n"; + if ($message == "var: Deprecated. Please use the public/private/protected modifiers") return; + print "<br />\n<b>Strict Standards:</b> Type: <b>$type</b>: $message in <b>$file</b> on line <b>$line</b><br />\n"; } // --------- End --------- @@ -152,62 +152,62 @@ Usage: Console output: - If requested, first any previously failed tests will be rerun. - Then new tests will be generated and run. Any tests that fail will be saved, - and a brief message about why they failed will be printed on the console. + and a brief message about why they failed will be printed on the console. - The console will show the number of tests run, time run, number of tests - failed, number of tests being done per minute, and the name of the current test. + failed, number of tests being done per minute, and the name of the current test. TODO: Some known things that could improve this script: - - Logging in with cookie jar storage needed for some tests (as there are some - pages that cannot be tested without being logged in, and which are currently - untested - e.g. Special:Emailuser, Special:Preferences, adding to Watchist). + - Logging in with cookie jar storage needed for some tests (as there are some + pages that cannot be tested without being logged in, and which are currently + untested - e.g. Special:Emailuser, Special:Preferences, adding to Watchist). - Testing of Timeline extension (I cannot test as ploticus has/had issues on - my architecture). + my architecture). */ -/////////////////////////// COMMAND LINE HELP //////////////////////////////////// +// ///////////////////////// COMMAND LINE HELP //////////////////////////////////// // This is a command line script, load MediaWiki env (gives command line options); -require_once( dirname(__FILE__) . '/commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/commandLine.inc' ); // if the user asked for an explanation of command line options. if ( isset( $options["help"] ) ) { - print <<<ENDS + print <<<ENDS MediaWiki $wgVersion fuzz tester Usage: php {$_SERVER["SCRIPT_NAME"]} [--quiet] [--base-url=<url-to-test-wiki>] - [--directory=<failed-test-path>] [--include-binary] - [--w3c-validate] [--delete-passed-retests] [--help] - [--user=<username>] [--password=<password>] - [--rerun-failed-tests] [--max-errors=<int>] - [--max-runtime=<num-minutes>] - [--specific-test=<test-name>] + [--directory=<failed-test-path>] [--include-binary] + [--w3c-validate] [--delete-passed-retests] [--help] + [--user=<username>] [--password=<password>] + [--rerun-failed-tests] [--max-errors=<int>] + [--max-runtime=<num-minutes>] + [--specific-test=<test-name>] Options: --quiet : Hides passed tests, shows only failed tests. - --base-url : URL to a wiki on which to run the tests. - The "http://" is optional and can be omitted. + --base-url : URL to a wiki on which to run the tests. + The "http://" is optional and can be omitted. --directory : Full path to directory for storing failed tests. - Will be created if it does not exist. + Will be created if it does not exist. --include-binary : Includes non-alphanumeric characters in the tests. - --w3c-validate : Validates pages using the W3C's web validator. - Slow. Currently many pages fail validation. + --w3c-validate : Validates pages using the W3C's web validator. + Slow. Currently many pages fail validation. --user : Login name of a valid user on your test wiki. - --password : Password for the valid user on your test wiki. + --password : Password for the valid user on your test wiki. --delete-passed-retests : Will delete retests that now pass. - Requires --rerun-failed-tests to be meaningful. + Requires --rerun-failed-tests to be meaningful. --rerun-failed-tests : Whether to rerun any previously failed tests. --max-errors : Maximum number of errors to report before exiting. - Does not include errors from --rerun-failed-tests + Does not include errors from --rerun-failed-tests --max-runtime : Maximum runtime, in minutes, to run before exiting. - Only applies to new tests, not --rerun-failed-tests - --specific-test : Runs only the specified fuzz test. - Only applies to new tests, not --rerun-failed-tests + Only applies to new tests, not --rerun-failed-tests + --specific-test : Runs only the specified fuzz test. + Only applies to new tests, not --rerun-failed-tests --keep-passed-tests : Saves all test files, even those that pass. --help : Show this help message. Example: - If you wanted to fuzz test a nightly MediaWiki checkout using cron for 1 hour, + If you wanted to fuzz test a nightly MediaWiki checkout using cron for 1 hour, and only wanted to be informed of errors, and did not want to redo previously failed tests, and wanted a maximum of 100 errors, then you could do: php {$_SERVER["SCRIPT_NAME"]} --quiet --max-errors=100 --max-runtime=60 @@ -215,659 +215,660 @@ Example: ENDS; - exit( 0 ); + exit( 0 ); } // if we got command line options, check they look valid. -$validOptions = array ("quiet", "base-url", "directory", "include-binary", - "w3c-validate", "user", "password", "delete-passed-retests", - "rerun-failed-tests", "max-errors", - "max-runtime", "specific-test", "keep-passed-tests", "help" ); -if (!empty($options)) { - $unknownArgs = array_diff (array_keys($options), $validOptions); - foreach ($unknownArgs as $invalidArg) { - print "Ignoring invalid command-line option: --$invalidArg\n"; - } +$validOptions = array ( "quiet", "base-url", "directory", "include-binary", + "w3c-validate", "user", "password", "delete-passed-retests", + "rerun-failed-tests", "max-errors", + "max-runtime", "specific-test", "keep-passed-tests", "help" ); +if ( !empty( $options ) ) { + $unknownArgs = array_diff ( array_keys( $options ), $validOptions ); + foreach ( $unknownArgs as $invalidArg ) { + print "Ignoring invalid command-line option: --$invalidArg\n"; + } } -///////////////////////////// CONFIGURATION //////////////////////////////////// +// /////////////////////////// CONFIGURATION //////////////////////////////////// // URL to some wiki on which we can run our tests. -if (!empty($options["base-url"])) { - define("WIKI_BASE_URL", $options["base-url"]); +if ( !empty( $options["base-url"] ) ) { + define( "WIKI_BASE_URL", $options["base-url"] ); } else { - define("WIKI_BASE_URL", $wgServer . $wgScriptPath . '/'); + define( "WIKI_BASE_URL", $wgServer . $wgScriptPath . '/' ); } // The directory name where we store the output. // Example for Windows: "c:\\temp\\wiki-fuzz" -if (!empty($options["directory"])) { - define("DIRECTORY", $options["directory"] ); +if ( !empty( $options["directory"] ) ) { + define( "DIRECTORY", $options["directory"] ); } else { - define("DIRECTORY", "{$wgUploadDirectory}/fuzz-tests"); + define( "DIRECTORY", "{$wgUploadDirectory}/fuzz-tests" ); } // Should our test fuzz data include binary strings? -define("INCLUDE_BINARY", isset($options["include-binary"]) ); +define( "INCLUDE_BINARY", isset( $options["include-binary"] ) ); // Whether we want to validate HTML output on the web. // At the moment very few generated pages will validate, so not recommended. -define("VALIDATE_ON_WEB", isset($options["w3c-validate"]) ); +define( "VALIDATE_ON_WEB", isset( $options["w3c-validate"] ) ); // URL to use to validate our output: -define("VALIDATOR_URL", "http://validator.w3.org/check"); +define( "VALIDATOR_URL", "http://validator.w3.org/check" ); // Location of Tidy standalone executable. -define("PATH_TO_TIDY", "/usr/bin/tidy"); +define( "PATH_TO_TIDY", "/usr/bin/tidy" ); -// The name of a user who has edited on your wiki. Used +// The name of a user who has edited on your wiki. Used // when testing the Special:Contributions and Special:Userlogin page. -if (!empty($options["user"])) { - define("USER_ON_WIKI", $options["user"] ); +if ( !empty( $options["user"] ) ) { + define( "USER_ON_WIKI", $options["user"] ); } else { - define("USER_ON_WIKI", "nickj"); + define( "USER_ON_WIKI", "nickj" ); } // The password of the above user. Used when testing the login page, -// and to do this we sometimes need to login successfully. -if (!empty($options["password"])) { - define("USER_PASSWORD", $options["password"] ); +// and to do this we sometimes need to login successfully. +if ( !empty( $options["password"] ) ) { + define( "USER_PASSWORD", $options["password"] ); } else { - // And no, this is not a valid password on any public wiki. - define("USER_PASSWORD", "nickj"); + // And no, this is not a valid password on any public wiki. + define( "USER_PASSWORD", "nickj" ); } // If we have a test that failed, and then we run it again, and it passes, // do you want to delete it or keep it? -define("DELETE_PASSED_RETESTS", isset($options["delete-passed-retests"]) ); +define( "DELETE_PASSED_RETESTS", isset( $options["delete-passed-retests"] ) ); // Do we want to rerun old saved tests at script startup? // Set to true to help catch regressions, or false if you only want new stuff. -define("RERUN_OLD_TESTS", isset($options["rerun-failed-tests"]) ); +define( "RERUN_OLD_TESTS", isset( $options["rerun-failed-tests"] ) ); // File where the database errors are logged. Should be defined in LocalSettings.php. -define("DB_ERROR_LOG_FILE", $wgDBerrorLog ); +define( "DB_ERROR_LOG_FILE", $wgDBerrorLog ); // Run in chatty mode (all output, default), or run in quiet mode (only prints out details of failed tests)? -define("QUIET", isset($options["quiet"]) ); +define( "QUIET", isset( $options["quiet"] ) ); // Keep all test files, even those that pass. Potentially useful to tracking input that causes something // unusual to happen, if you don't know what "unusual" is until later. -define("KEEP_PASSED_TESTS", isset($options["keep-passed-tests"]) ); +define( "KEEP_PASSED_TESTS", isset( $options["keep-passed-tests"] ) ); // The maximum runtime, if specified. -if (!empty($options["max-runtime"]) && intval($options["max-runtime"])>0) { - define("MAX_RUNTIME", intval($options["max-runtime"]) ); +if ( !empty( $options["max-runtime"] ) && intval( $options["max-runtime"] ) > 0 ) { + define( "MAX_RUNTIME", intval( $options["max-runtime"] ) ); } // The maximum number of problems to find, if specified. Excludes retest errors. -if (!empty($options["max-errors"]) && intval($options["max-errors"])>0) { - define("MAX_ERRORS", intval($options["max-errors"]) ); +if ( !empty( $options["max-errors"] ) && intval( $options["max-errors"] ) > 0 ) { + define( "MAX_ERRORS", intval( $options["max-errors"] ) ); } // if the user has requested a specific test (instead of all tests), and the test they asked for looks valid. -if (!empty($options["specific-test"])) { - if (class_exists($options["specific-test"]) && get_parent_class($options["specific-test"])=="pageTest") { - define("SPECIFIC_TEST", $options["specific-test"] ); - } - else { - print "Ignoring invalid --specific-test\n"; - } +if ( !empty( $options["specific-test"] ) ) { + if ( class_exists( $options["specific-test"] ) && get_parent_class( $options["specific-test"] ) == "pageTest" ) { + define( "SPECIFIC_TEST", $options["specific-test"] ); + } + else { + print "Ignoring invalid --specific-test\n"; + } } // Define the file extensions we'll use: -define("PHP_TEST" , ".test.php"); -define("CURL_TEST", ".curl.sh" ); -define("DATA_FILE", ".data.bin"); -define("INFO_FILE", ".info.txt"); -define("HTML_FILE", ".wiki_preview.html"); +define( "PHP_TEST" , ".test.php" ); +define( "CURL_TEST", ".curl.sh" ); +define( "DATA_FILE", ".data.bin" ); +define( "INFO_FILE", ".info.txt" ); +define( "HTML_FILE", ".wiki_preview.html" ); // If it goes wrong, we want to know about it. -error_reporting(E_ALL | E_STRICT); +error_reporting( E_ALL | E_STRICT ); -//////////////// A CLASS THAT GENERATES RANDOM NASTY WIKI & HTML STRINGS ////////////////////// +// ////////////// A CLASS THAT GENERATES RANDOM NASTY WIKI & HTML STRINGS ////////////////////// class wikiFuzz { - // Only some HTML tags are understood with params by MediaWiki, the rest are ignored. - // List the tags that accept params below, as well as what those params are. - public static $data = array( - "B" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "CAPTION" => array("CLASS", "ID", "STYLE", "align", "lang", "dir", "title"), - "CENTER" => array("CLASS", "STYLE", "ID", "lang", "dir", "title"), - "DIV" => array("CLASS", "STYLE", "ID", "align", "lang", "dir", "title"), - "FONT" => array("CLASS", "STYLE", "ID", "lang", "dir", "title", "face", "size", "color"), - "H1" => array("STYLE", "CLASS", "ID", "align", "lang", "dir", "title"), - "H2" => array("STYLE", "CLASS", "ID", "align", "lang", "dir", "title"), - "HR" => array("STYLE", "CLASS", "ID", "WIDTH", "lang", "dir", "title", "size", "noshade"), - "LI" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", "type", "value"), - "TABLE" => array("STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "BORDER", "CELLPADDING", - "CELLSPACING", "lang", "dir", "title", "summary", "frame", "rules"), - "TD" => array("STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "COLSPAN", "ROWSPAN", - "VALIGN", "abbr", "axis", "headers", "scope", "nowrap", "height", "lang", - "dir", "title", "char", "charoff"), - "TH" => array("STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "COLSPAN", "ROWSPAN", - "VALIGN", "abbr", "axis", "headers", "scope", "nowrap", "height", "lang", - "dir", "title", "char", "charoff"), - "TR" => array("CLASS", "STYLE", "ID", "BGCOLOR", "ALIGN", "VALIGN", "lang", "dir", "title", "char", "charoff"), - "UL" => array("CLASS", "STYLE", "ID", "lang", "dir", "title", "type"), - "P" => array("style", "class", "id", "align", "lang", "dir", "title"), - "blockquote" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", "cite"), - "span" => array("CLASS", "ID", "STYLE", "align", "lang", "dir", "title"), - "code" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "tt" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "small" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "big" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "s" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "u" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "del" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", "datetime", "cite"), - "ins" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", "datetime", "cite"), - "sub" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "sup" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "ol" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", "type", "start"), - "br" => array("CLASS", "ID", "STYLE", "title", "clear"), - "cite" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "var" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "dl" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "ruby" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "rt" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "rp" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "dt" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "dl" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "em" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "strong" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "i" => array("CLASS", "ID", "STYLE", "lang", "dir", "title"), - "thead" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign'), - "tfoot" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign'), - "tbody" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign'), - "colgroup" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign', 'span', 'width'), - "col" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign', 'span', 'width'), - "pre" => array("CLASS", "ID", "STYLE", "lang", "dir", "title", "width"), - - // extension tags that accept parameters: - "sort" => array("order", "class"), - "ref" => array("name"), - "categorytree" => array("hideroot", "mode", "style"), - "chemform" => array("link", "wikilink", "query"), - "section" => array("begin", "new"), - - // older MW transclusion. - "transclude" => array("page"), - ); - - // The types of the HTML that we will be testing were defined above - // Note: this needs to be initialized later to be equal to: array_keys(wikiFuzz::$data); - // as such, it also needs to also be publicly modifiable. - public static $types; - - - // Some attribute values. - static private $other = array("&","=",":","?","\"","\n","%n%n%n%n%n%n%n%n%n%n%n%n","\\"); - static private $ints = array( - // various numbers - "0","-1","127","-7897","89000","808080","90928345", - "0xfffffff","ffff", - - // Different ways of saying: ' - "'", // Long UTF-8 Unicode encoding - "'", // dec version. - "'", // hex version. - "§", // malformed hex variant, MSB not zero. - - // Different ways of saying: " - """, // Long UTF-8 Unicode encoding - """, - """, // hex version. - "¢", // malformed hex variant, MSB not zero. - - // Different ways of saying: < - "<", - "<", // Long UTF-8 Unicode encoding without semicolon (Mediawiki wants the colon) - "<", // Long UTF-8 Unicode encoding with semicolon - "<", - "<", // hex version. - "¼", // malformed hex variant, MSB not zero. - "<", // mid-length hex version - "<", // slightly longer hex version, with capital "X" - - // Different ways of saying: > - ">", - ">", // Long UTF-8 Unicode encoding - ">", - ">", // hex version. - "¾", // malformed variant, MSB not zero. - - // Different ways of saying: [ - "[", // Long UTF-8 Unicode encoding - "[", - "[", // hex version. - - // Different ways of saying: {{ - "{{", // Long UTF-8 Unicode encoding - "{{", - "{{", // hex version. - - // Different ways of saying: | - "|", // Long UTF-8 Unicode encoding - "|", - "|", // hex version. - "ü", // malformed hex variant, MSB not zero. - - // a "lignature" - http://www.robinlionheart.com/stds/html4/spchars#ligature - "‌" - ); - - // Defines various wiki-related bits of syntax, that can potentially cause - // MediaWiki to do something other than just print that literal text. - static private $ext = array( - // links, templates, parameters. - "[[", "]]", "{{", "}}", "|", "[", "]", "{{{", "}}}", "|]]", - - // wiki tables. - "\n{|", "\n|}", - "!", - "\n!", - "!!", - "||", - "\n|-", "| ", "\n|", - - // section headings. - "=", "==", "===", "====", "=====", "======", - - // lists (ordered and unordered) and indentation. - "\n*", "*", "\n:", ":", - "\n#", "#", - - // definition lists (dl, dt, dd), newline, and newline with pre, and a tab. - "\n;", ";", "\n ", - - // Whitespace: newline, tab, space. - "\n", "\t", " ", - - // Some XSS attack vectors from http://ha.ckers.org/xss.html - "	", // tab - "
", // newline - "
", // carriage return - "\0", // null character - "  ", // spaces and meta characters - "'';!--\"<XSS>=&{()}", // compact injection of XSS & SQL tester - - // various NULL fields - "%00", - "�", - "\0", - - // horizontal rule. - "-----", "\n-----", - - // signature, redirect, bold, italics. - "~~~~", "#REDIRECT [[", "'''", "''", - - // comments. - "<!--", "-->", - - // quotes. - "\"", "'", - - // tag start and tag end. - "<", ">", - - // implicit link creation on URIs. - "http://", - "https://", - "ftp://", - "irc://", - "news:", - 'gopher://', - 'telnet://', - 'nntp://', - 'worldwind://', - 'mailto:', - - // images. - "[[image:", - ".gif", - ".png", - ".jpg", - ".jpeg", - 'thumbnail=', - 'thumbnail', - 'thumb=', - 'thumb', - 'right', - 'none', - 'left', - 'framed', - 'frame', - 'enframed', - 'centre', - 'center', - "Image:", - "[[:Image", - 'px', - 'upright=', - 'border', - - // misc stuff to throw at the Parser. - '%08X', - '/', - ":x{|", - "\n|+", - "<noinclude>", - "</noinclude>", - " \302\273", - " :", - " !", - " ;", - "\302\253", - "[[category:", - "?=", - "(", - ")", - "]]]", - "../", - "{{{{", - "}}}}", - "[[Special:", - "<includeonly>", - "</includeonly>", - "<!--MWTEMPLATESECTION=", - '<!--MWTOC-->', - - // implicit link creation on booknum, RFC, and PubMed ID usage (both with and without IDs) - "ISBN 2", - "RFC 000", - "PMID 000", - "ISBN ", - "RFC ", - "PMID ", - - // magic words: - '__NOTOC__', - '__FORCETOC__', - '__NOEDITSECTION__', - '__START__', - '__NOTITLECONVERT__', - '__NOCONTENTCONVERT__', - '__END__', - '__TOC__', - '__NOTC__', - '__NOCC__', - "__FORCETOC__", - "__NEWSECTIONLINK__", - "__NOGALLERY__", - - // more magic words / internal templates. - '{{PAGENAME}}', - '{{PAGENAMEE}}', - '{{NAMESPACE}}', - "{{MSG:", - "}}", - "{{MSGNW:", - "}}", - "{{INT:", - "}}", - '{{SITENAME}}', - "{{NS:", - "}}", - "{{LOCALURL:", - "}}", - "{{LOCALURLE:", - "}}", - "{{SCRIPTPATH}}", - "{{GRAMMAR:gentiv|", - "}}", - "{{REVISIONID}}", - "{{SUBPAGENAME}}", - "{{SUBPAGENAMEE}}", - "{{ns:0}}", - "{{fullurle:", - "}}", - "{{subst::", - "}}", - "{{UCFIRST:", - "}}", - "{{UC:", - '{{SERVERNAME}}', - '{{SERVER}}', - "{{RAW:", - "}}", - "{{PLURAL:", - "}}", - "{{LCFIRST:", - "}}", - "{{LC:", - "}}", - '{{CURRENTWEEK}}', - '{{CURRENTDOW}}', - "{{INT:{{LC:contribs-showhideminor}}|", - "}}", - "{{INT:googlesearch|", - "}}", - "{{BASEPAGENAME}}", - "{{CONTENTLANGUAGE}}", - "{{PAGESINNAMESPACE:}}", - "{{#language:", - "}}", - "{{#special:", - "}}", - "{{#special:emailuser", - "}}", - - // Some raw link for magic words. - "{{NUMBEROFPAGES:R", - "}}", - "{{NUMBEROFUSERS:R", - "}}", - "{{NUMBEROFARTICLES:R", - "}}", - "{{NUMBEROFFILES:R", - "}}", - "{{NUMBEROFADMINS:R", - "}}", - "{{padleft:", - "}}", - "{{padright:", - "}}", - "{{DEFAULTSORT:", - "}}", - - // internal Math "extension": - "<math>", - "</math>", - - // Parser extension functions: - "{{#expr:", - "{{#if:", - "{{#ifeq:", - "{{#ifexist:", - "{{#ifexpr:", - "{{#switch:", - "{{#time:", - "}}", - - // references table for the Cite extension. - "<references/>", - - // Internal Parser tokens - try inserting some of these. - "UNIQ25f46b0524f13e67NOPARSE", - "UNIQ17197916557e7cd6-HTMLCommentStrip46238afc3bb0cf5f00000002", - "\x07UNIQ17197916557e7cd6-HTMLCommentStrip46238afc3bb0cf5f00000002-QINU", - - // Inputbox extension: - "<inputbox>\ntype=search\nsearchbuttonlabel=\n", - "</inputbox>", - - // charInsert extension: - "<charInsert>", - "</charInsert>", - - // wikiHiero extension: - "<hiero>", - "</hiero>", - - // Image gallery: - "<gallery>", - "</gallery>", - - // FixedImage extension. - "<fundraising/>", - - // Timeline extension: currently untested. - - // Nowiki: - "<nOwIkI>", - "</nowiki>", - - // an external image to test the external image displaying code - "http://debian.org/Pics/debian.png", - - // LabeledSectionTransclusion extension. - "{{#lstx:", - "}}", - "{{#lst:", - "}}", - "{{#lst:Main Page|", - "}}" - ); - - /** - ** Randomly returns one element of the input array. - */ - static public function chooseInput(array $input) { - $randindex = wikiFuzz::randnum(count($input) - 1); - return $input[$randindex]; - } - - // Max number of parameters for HTML attributes. - static private $maxparams = 10; - - /** - ** Returns random number between finish and start. - */ - static public function randnum($finish,$start=0) { - return mt_rand($start,$finish); - } - - /** - ** Returns a mix of random text and random wiki syntax. - */ - static private function randstring() { - $thestring = ""; - - for ($i=0; $i<40; $i++) { - $what = wikiFuzz::randnum(1); - - if ($what == 0) { // include some random wiki syntax - $which = wikiFuzz::randnum(count(wikiFuzz::$ext) - 1); - $thestring .= wikiFuzz::$ext[$which]; - } - else { // include some random text - $char = INCLUDE_BINARY - // Decimal version: - // "&#" . wikiFuzz::randnum(255) . ";" - // Hex version: - ? "&#x" . str_pad(dechex(wikiFuzz::randnum(255)), wikiFuzz::randnum(2, 7), "0", STR_PAD_LEFT) . ";" - // A truly binary version: - // ? chr(wikiFuzz::randnum(0,255)) - : chr(wikiFuzz::randnum(126,32)); - - $length = wikiFuzz::randnum(8); - $thestring .= str_repeat ($char, $length); - } - } - return $thestring; - } - - /** - ** Returns either random text, or random wiki syntax, or random data from "ints", - ** or random data from "other". - */ - static private function makestring() { - $what = wikiFuzz::randnum(2); - if ($what == 0) { - return wikiFuzz::randstring(); - } - elseif ($what == 1) { - return wikiFuzz::$ints[wikiFuzz::randnum(count(wikiFuzz::$ints) - 1)]; - } - else { - return wikiFuzz::$other[wikiFuzz::randnum(count(wikiFuzz::$other) - 1)]; - } - } - - - /** - ** Strips out the stuff that Mediawiki balks at in a page's title. - ** Implementation copied/pasted from cleanupTable.inc & cleanupImages.php - */ - static public function makeTitleSafe($str) { - $legalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF"; - return preg_replace_callback( - "/([^$legalTitleChars])/", - create_function( - // single quotes are essential here, - // or alternative escape all $ as \$ - '$matches', - 'return sprintf( "\\x%02x", ord( $matches[1] ) );' - ), - $str ); - } - - /** - ** Returns a string of fuzz text. - */ - static private function loop() { - switch ( wikiFuzz::randnum(3) ) { - case 1: // an opening tag, with parameters. - $string = ""; - $i = wikiFuzz::randnum(count(wikiFuzz::$types) - 1); - $t = wikiFuzz::$types[$i]; - $arr = wikiFuzz::$data[$t]; - $string .= "<" . $t . " "; - $num_params = min(wikiFuzz::$maxparams, count($arr)); - for ($z=0; $z<$num_params; $z++) { - $badparam = $arr[wikiFuzz::randnum(count($arr) - 1)]; - $badstring = wikiFuzz::makestring(); - $string .= $badparam . "=" . wikiFuzz::getRandQuote() . $badstring . wikiFuzz::getRandQuote() . " "; - } - $string .= ">\n"; - return $string; - case 2: // a closing tag. - $i = wikiFuzz::randnum(count(wikiFuzz::$types) - 1); - return "</". wikiFuzz::$types[$i] . ">"; - case 3: // a random string, between tags. - return wikiFuzz::makeString(); - } - return ""; // catch-all, should never be called. - } - - /** - ** Returns one of the three styles of random quote: ', ", and nothing. - */ - static private function getRandQuote() { - switch ( wikiFuzz::randnum(3) ) { - case 1 : return "'"; - case 2 : return "\""; - default: return ""; - } - } - - /** - ** Returns fuzz text, with the parameter indicating approximately how many lines of text you want. - */ - static public function makeFuzz($maxtypes = 2) { - $page = ""; - for ($k=0; $k<$maxtypes; $k++) { - $page .= wikiFuzz::loop(); - } - return $page; - } -} - - -//////// MEDIAWIKI PAGES TO TEST, AND HOW TO TEST THEM /////// + // Only some HTML tags are understood with params by MediaWiki, the rest are ignored. + // List the tags that accept params below, as well as what those params are. + public static $data = array( + "B" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "CAPTION" => array( "CLASS", "ID", "STYLE", "align", "lang", "dir", "title" ), + "CENTER" => array( "CLASS", "STYLE", "ID", "lang", "dir", "title" ), + "DIV" => array( "CLASS", "STYLE", "ID", "align", "lang", "dir", "title" ), + "FONT" => array( "CLASS", "STYLE", "ID", "lang", "dir", "title", "face", "size", "color" ), + "H1" => array( "STYLE", "CLASS", "ID", "align", "lang", "dir", "title" ), + "H2" => array( "STYLE", "CLASS", "ID", "align", "lang", "dir", "title" ), + "HR" => array( "STYLE", "CLASS", "ID", "WIDTH", "lang", "dir", "title", "size", "noshade" ), + "LI" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "type", "value" ), + "TABLE" => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "BORDER", "CELLPADDING", + "CELLSPACING", "lang", "dir", "title", "summary", "frame", "rules" ), + "TD" => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "COLSPAN", "ROWSPAN", + "VALIGN", "abbr", "axis", "headers", "scope", "nowrap", "height", "lang", + "dir", "title", "char", "charoff" ), + "TH" => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "COLSPAN", "ROWSPAN", + "VALIGN", "abbr", "axis", "headers", "scope", "nowrap", "height", "lang", + "dir", "title", "char", "charoff" ), + "TR" => array( "CLASS", "STYLE", "ID", "BGCOLOR", "ALIGN", "VALIGN", "lang", "dir", "title", "char", "charoff" ), + "UL" => array( "CLASS", "STYLE", "ID", "lang", "dir", "title", "type" ), + "P" => array( "style", "class", "id", "align", "lang", "dir", "title" ), + "blockquote" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "cite" ), + "span" => array( "CLASS", "ID", "STYLE", "align", "lang", "dir", "title" ), + "code" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "tt" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "small" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "big" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "s" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "u" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "del" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "datetime", "cite" ), + "ins" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "datetime", "cite" ), + "sub" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "sup" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "ol" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "type", "start" ), + "br" => array( "CLASS", "ID", "STYLE", "title", "clear" ), + "cite" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "var" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "dl" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "ruby" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "rt" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "rp" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "dt" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "dl" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "em" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "strong" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "i" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ), + "thead" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign' ), + "tfoot" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign' ), + "tbody" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign' ), + "colgroup" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign', 'span', 'width' ), + "col" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", 'align', 'char', 'charoff', 'valign', 'span', 'width' ), + "pre" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "width" ), + + // extension tags that accept parameters: + "sort" => array( "order", "class" ), + "ref" => array( "name" ), + "categorytree" => array( "hideroot", "mode", "style" ), + "chemform" => array( "link", "wikilink", "query" ), + "section" => array( "begin", "new" ), + + // older MW transclusion. + "transclude" => array( "page" ), + ); + + // The types of the HTML that we will be testing were defined above + // Note: this needs to be initialized later to be equal to: array_keys(wikiFuzz::$data); + // as such, it also needs to also be publicly modifiable. + public static $types; + + + // Some attribute values. + static private $other = array( "&", "=", ":", "?", "\"", "\n", "%n%n%n%n%n%n%n%n%n%n%n%n", "\\" ); + static private $ints = array( + // various numbers + "0", "-1", "127", "-7897", "89000", "808080", "90928345", + "0xfffffff", "ffff", + + // Different ways of saying: ' + "'", // Long UTF-8 Unicode encoding + "'", // dec version. + "'", // hex version. + "§", // malformed hex variant, MSB not zero. + + // Different ways of saying: " + """, // Long UTF-8 Unicode encoding + """, + """, // hex version. + "¢", // malformed hex variant, MSB not zero. + + // Different ways of saying: < + "<", + "<", // Long UTF-8 Unicode encoding without semicolon (Mediawiki wants the colon) + "<", // Long UTF-8 Unicode encoding with semicolon + "<", + "<", // hex version. + "¼", // malformed hex variant, MSB not zero. + "<", // mid-length hex version + "<", // slightly longer hex version, with capital "X" + + // Different ways of saying: > + ">", + ">", // Long UTF-8 Unicode encoding + ">", + ">", // hex version. + "¾", // malformed variant, MSB not zero. + + // Different ways of saying: [ + "[", // Long UTF-8 Unicode encoding + "[", + "[", // hex version. + + // Different ways of saying: {{ + "{{", // Long UTF-8 Unicode encoding + "{{", + "{{", // hex version. + + // Different ways of saying: | + "|", // Long UTF-8 Unicode encoding + "|", + "|", // hex version. + "ü", // malformed hex variant, MSB not zero. + + // a "lignature" - http://www.robinlionheart.com/stds/html4/spchars#ligature + // ‌ == ‌ + "‌" + ); + + // Defines various wiki-related bits of syntax, that can potentially cause + // MediaWiki to do something other than just print that literal text. + static private $ext = array( + // links, templates, parameters. + "[[", "]]", "{{", "}}", "|", "[", "]", "{{{", "}}}", "|]]", + + // wiki tables. + "\n{|", "\n|}", + "!", + "\n!", + "!!", + "||", + "\n|-", "| ", "\n|", + + // section headings. + "=", "==", "===", "====", "=====", "======", + + // lists (ordered and unordered) and indentation. + "\n*", "*", "\n:", ":", + "\n#", "#", + + // definition lists (dl, dt, dd), newline, and newline with pre, and a tab. + "\n;", ";", "\n ", + + // Whitespace: newline, tab, space. + "\n", "\t", " ", + + // Some XSS attack vectors from http://ha.ckers.org/xss.html + "	", // tab + "
", // newline + "
", // carriage return + "\0", // null character + "  ", // spaces and meta characters + "'';!--\"<XSS>=&{()}", // compact injection of XSS & SQL tester + + // various NULL fields + "%00", + "�", + "\0", + + // horizontal rule. + "-----", "\n-----", + + // signature, redirect, bold, italics. + "~~~~", "#REDIRECT [[", "'''", "''", + + // comments. + "<!--", "-->", + + // quotes. + "\"", "'", + + // tag start and tag end. + "<", ">", + + // implicit link creation on URIs. + "http://", + "https://", + "ftp://", + "irc://", + "news:", + 'gopher://', + 'telnet://', + 'nntp://', + 'worldwind://', + 'mailto:', + + // images. + "[[image:", + ".gif", + ".png", + ".jpg", + ".jpeg", + 'thumbnail=', + 'thumbnail', + 'thumb=', + 'thumb', + 'right', + 'none', + 'left', + 'framed', + 'frame', + 'enframed', + 'centre', + 'center', + "Image:", + "[[:Image", + 'px', + 'upright=', + 'border', + + // misc stuff to throw at the Parser. + '%08X', + '/', + ":x{|", + "\n|+", + "<noinclude>", + "</noinclude>", + " \302\273", + " :", + " !", + " ;", + "\302\253", + "[[category:", + "?=", + "(", + ")", + "]]]", + "../", + "{{{{", + "}}}}", + "[[Special:", + "<includeonly>", + "</includeonly>", + "<!--MWTEMPLATESECTION=", + '<!--MWTOC-->', + + // implicit link creation on booknum, RFC, and PubMed ID usage (both with and without IDs) + "ISBN 2", + "RFC 000", + "PMID 000", + "ISBN ", + "RFC ", + "PMID ", + + // magic words: + '__NOTOC__', + '__FORCETOC__', + '__NOEDITSECTION__', + '__START__', + '__NOTITLECONVERT__', + '__NOCONTENTCONVERT__', + '__END__', + '__TOC__', + '__NOTC__', + '__NOCC__', + "__FORCETOC__", + "__NEWSECTIONLINK__", + "__NOGALLERY__", + + // more magic words / internal templates. + '{{PAGENAME}}', + '{{PAGENAMEE}}', + '{{NAMESPACE}}', + "{{MSG:", + "}}", + "{{MSGNW:", + "}}", + "{{INT:", + "}}", + '{{SITENAME}}', + "{{NS:", + "}}", + "{{LOCALURL:", + "}}", + "{{LOCALURLE:", + "}}", + "{{SCRIPTPATH}}", + "{{GRAMMAR:gentiv|", + "}}", + "{{REVISIONID}}", + "{{SUBPAGENAME}}", + "{{SUBPAGENAMEE}}", + "{{ns:0}}", + "{{fullurle:", + "}}", + "{{subst::", + "}}", + "{{UCFIRST:", + "}}", + "{{UC:", + '{{SERVERNAME}}', + '{{SERVER}}', + "{{RAW:", + "}}", + "{{PLURAL:", + "}}", + "{{LCFIRST:", + "}}", + "{{LC:", + "}}", + '{{CURRENTWEEK}}', + '{{CURRENTDOW}}', + "{{INT:{{LC:contribs-showhideminor}}|", + "}}", + "{{INT:googlesearch|", + "}}", + "{{BASEPAGENAME}}", + "{{CONTENTLANGUAGE}}", + "{{PAGESINNAMESPACE:}}", + "{{#language:", + "}}", + "{{#special:", + "}}", + "{{#special:emailuser", + "}}", + + // Some raw link for magic words. + "{{NUMBEROFPAGES:R", + "}}", + "{{NUMBEROFUSERS:R", + "}}", + "{{NUMBEROFARTICLES:R", + "}}", + "{{NUMBEROFFILES:R", + "}}", + "{{NUMBEROFADMINS:R", + "}}", + "{{padleft:", + "}}", + "{{padright:", + "}}", + "{{DEFAULTSORT:", + "}}", + + // internal Math "extension": + "<math>", + "</math>", + + // Parser extension functions: + "{{#expr:", + "{{#if:", + "{{#ifeq:", + "{{#ifexist:", + "{{#ifexpr:", + "{{#switch:", + "{{#time:", + "}}", + + // references table for the Cite extension. + "<references/>", + + // Internal Parser tokens - try inserting some of these. + "UNIQ25f46b0524f13e67NOPARSE", + "UNIQ17197916557e7cd6-HTMLCommentStrip46238afc3bb0cf5f00000002", + "\x07UNIQ17197916557e7cd6-HTMLCommentStrip46238afc3bb0cf5f00000002-QINU", + + // Inputbox extension: + "<inputbox>\ntype=search\nsearchbuttonlabel=\n", + "</inputbox>", + + // charInsert extension: + "<charInsert>", + "</charInsert>", + + // wikiHiero extension: + "<hiero>", + "</hiero>", + + // Image gallery: + "<gallery>", + "</gallery>", + + // FixedImage extension. + "<fundraising/>", + + // Timeline extension: currently untested. + + // Nowiki: + "<nOwIkI>", + "</nowiki>", + + // an external image to test the external image displaying code + "http://debian.org/Pics/debian.png", + + // LabeledSectionTransclusion extension. + "{{#lstx:", + "}}", + "{{#lst:", + "}}", + "{{#lst:Main Page|", + "}}" + ); + + /** + ** Randomly returns one element of the input array. + */ + static public function chooseInput( array $input ) { + $randindex = wikiFuzz::randnum( count( $input ) - 1 ); + return $input[$randindex]; + } + + // Max number of parameters for HTML attributes. + static private $maxparams = 10; + + /** + ** Returns random number between finish and start. + */ + static public function randnum( $finish, $start = 0 ) { + return mt_rand( $start, $finish ); + } + + /** + ** Returns a mix of random text and random wiki syntax. + */ + static private function randstring() { + $thestring = ""; + + for ( $i = 0; $i < 40; $i++ ) { + $what = wikiFuzz::randnum( 1 ); + + if ( $what == 0 ) { // include some random wiki syntax + $which = wikiFuzz::randnum( count( wikiFuzz::$ext ) - 1 ); + $thestring .= wikiFuzz::$ext[$which]; + } + else { // include some random text + $char = INCLUDE_BINARY + // Decimal version: + // "&#" . wikiFuzz::randnum(255) . ";" + // Hex version: + ? "&#x" . str_pad( dechex( wikiFuzz::randnum( 255 ) ), wikiFuzz::randnum( 2, 7 ), "0", STR_PAD_LEFT ) . ";" + // A truly binary version: + // ? chr(wikiFuzz::randnum(0,255)) + : chr( wikiFuzz::randnum( 126, 32 ) ); + + $length = wikiFuzz::randnum( 8 ); + $thestring .= str_repeat ( $char, $length ); + } + } + return $thestring; + } + + /** + ** Returns either random text, or random wiki syntax, or random data from "ints", + ** or random data from "other". + */ + static private function makestring() { + $what = wikiFuzz::randnum( 2 ); + if ( $what == 0 ) { + return wikiFuzz::randstring(); + } + elseif ( $what == 1 ) { + return wikiFuzz::$ints[wikiFuzz::randnum( count( wikiFuzz::$ints ) - 1 )]; + } + else { + return wikiFuzz::$other[wikiFuzz::randnum( count( wikiFuzz::$other ) - 1 )]; + } + } + + + /** + ** Strips out the stuff that Mediawiki balks at in a page's title. + ** Implementation copied/pasted from cleanupTable.inc & cleanupImages.php + */ + static public function makeTitleSafe( $str ) { + $legalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF"; + return preg_replace_callback( + "/([^$legalTitleChars])/", + create_function( + // single quotes are essential here, + // or alternative escape all $ as \$ + '$matches', + 'return sprintf( "\\x%02x", ord( $matches[1] ) );' + ), + $str ); + } + + /** + ** Returns a string of fuzz text. + */ + static private function loop() { + switch ( wikiFuzz::randnum( 3 ) ) { + case 1: // an opening tag, with parameters. + $string = ""; + $i = wikiFuzz::randnum( count( wikiFuzz::$types ) - 1 ); + $t = wikiFuzz::$types[$i]; + $arr = wikiFuzz::$data[$t]; + $string .= "<" . $t . " "; + $num_params = min( wikiFuzz::$maxparams, count( $arr ) ); + for ( $z = 0; $z < $num_params; $z++ ) { + $badparam = $arr[wikiFuzz::randnum( count( $arr ) - 1 )]; + $badstring = wikiFuzz::makestring(); + $string .= $badparam . "=" . wikiFuzz::getRandQuote() . $badstring . wikiFuzz::getRandQuote() . " "; + } + $string .= ">\n"; + return $string; + case 2: // a closing tag. + $i = wikiFuzz::randnum( count( wikiFuzz::$types ) - 1 ); + return "</" . wikiFuzz::$types[$i] . ">"; + case 3: // a random string, between tags. + return wikiFuzz::makeString(); + } + return ""; // catch-all, should never be called. + } + + /** + ** Returns one of the three styles of random quote: ', ", and nothing. + */ + static private function getRandQuote() { + switch ( wikiFuzz::randnum( 3 ) ) { + case 1 : return "'"; + case 2 : return "\""; + default: return ""; + } + } + + /** + ** Returns fuzz text, with the parameter indicating approximately how many lines of text you want. + */ + static public function makeFuzz( $maxtypes = 2 ) { + $page = ""; + for ( $k = 0; $k < $maxtypes; $k++ ) { + $page .= wikiFuzz::loop(); + } + return $page; + } +} + + +// ////// MEDIAWIKI PAGES TO TEST, AND HOW TO TEST THEM /////// /** ** A page test has just these things: @@ -875,30 +876,30 @@ class wikiFuzz { ** 2) the URL we are going to test those parameters on. ** 3) Any cookies required for the test. ** 4) Whether Tidy should validate the page. Defaults to true, but can be turned off. - ** Declared abstract because it should be extended by a class + ** Declared abstract because it should be extended by a class ** that supplies these parameters. */ abstract class pageTest { - protected $params; - protected $pagePath; - protected $cookie = ""; - protected $tidyValidate = true; + protected $params; + protected $pagePath; + protected $cookie = ""; + protected $tidyValidate = true; + + public function getParams() { + return $this->params; + } - public function getParams() { - return $this->params; - } + public function getPagePath() { + return $this->pagePath; + } - public function getPagePath() { - return $this->pagePath; - } + public function getCookie() { + return $this->cookie; + } - public function getCookie() { - return $this->cookie; - } - - public function tidyValidate() { - return $this->tidyValidate; - } + public function tidyValidate() { + return $this->tidyValidate; + } } @@ -906,31 +907,31 @@ abstract class pageTest { ** a page test for the "Edit" page. Tests Parser.php and Sanitizer.php. */ class editPageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=WIKIFUZZ"; - - $this->params = array ( - "action" => "submit", - "wpMinoredit" => wikiFuzz::makeFuzz(2), - "wpPreview" => wikiFuzz::makeFuzz(2), - "wpSection" => wikiFuzz::makeFuzz(2), - "wpEdittime" => wikiFuzz::makeFuzz(2), - "wpSummary" => wikiFuzz::makeFuzz(2), - "wpScrolltop" => wikiFuzz::makeFuzz(2), - "wpStarttime" => wikiFuzz::makeFuzz(2), - "wpAutoSummary" => wikiFuzz::makeFuzz(2), - "wpTextbox1" => wikiFuzz::makeFuzz(40) // the main wiki text, need lots of this. - ); - - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpSection"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpEdittime"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpSummary"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpScrolltop"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpStarttime"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpAutoSummary"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpTextbox1"]); - } + function __construct() { + $this->pagePath = "index.php?title=WIKIFUZZ"; + + $this->params = array ( + "action" => "submit", + "wpMinoredit" => wikiFuzz::makeFuzz( 2 ), + "wpPreview" => wikiFuzz::makeFuzz( 2 ), + "wpSection" => wikiFuzz::makeFuzz( 2 ), + "wpEdittime" => wikiFuzz::makeFuzz( 2 ), + "wpSummary" => wikiFuzz::makeFuzz( 2 ), + "wpScrolltop" => wikiFuzz::makeFuzz( 2 ), + "wpStarttime" => wikiFuzz::makeFuzz( 2 ), + "wpAutoSummary" => wikiFuzz::makeFuzz( 2 ), + "wpTextbox1" => wikiFuzz::makeFuzz( 40 ) // the main wiki text, need lots of this. + ); + + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpSection"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpEdittime"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpSummary"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpScrolltop"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpStarttime"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpAutoSummary"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpTextbox1"] ); + } } @@ -938,18 +939,18 @@ class editPageTest extends pageTest { ** a page test for "Special:Listusers". */ class listusersTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Listusers"; + function __construct() { + $this->pagePath = "index.php?title=Special:Listusers"; - $this->params = array ( - "title" => wikiFuzz::makeFuzz(2), - "group" => wikiFuzz::makeFuzz(2), - "username" => wikiFuzz::makeFuzz(2), - "Go" => wikiFuzz::makeFuzz(2), - "limit" => wikiFuzz::chooseInput( array("0", "-1", "---'----------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - "offset" => wikiFuzz::chooseInput( array("0", "-1", "--------'-----0", "+1", "81343242346234234", wikiFuzz::makeFuzz(2)) ) - ); - } + $this->params = array ( + "title" => wikiFuzz::makeFuzz( 2 ), + "group" => wikiFuzz::makeFuzz( 2 ), + "username" => wikiFuzz::makeFuzz( 2 ), + "Go" => wikiFuzz::makeFuzz( 2 ), + "limit" => wikiFuzz::chooseInput( array( "0", "-1", "---'----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + "offset" => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1", "81343242346234234", wikiFuzz::makeFuzz( 2 ) ) ) + ); + } } @@ -957,34 +958,34 @@ class listusersTest extends pageTest { ** a page test for "Special:Search". */ class searchTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Search"; - - $this->params = array ( - "action" => "index.php?title=Special:Search", - "ns0" => wikiFuzz::makeFuzz(2), - "ns1" => wikiFuzz::makeFuzz(2), - "ns2" => wikiFuzz::makeFuzz(2), - "ns3" => wikiFuzz::makeFuzz(2), - "ns4" => wikiFuzz::makeFuzz(2), - "ns5" => wikiFuzz::makeFuzz(2), - "ns6" => wikiFuzz::makeFuzz(2), - "ns7" => wikiFuzz::makeFuzz(2), - "ns8" => wikiFuzz::makeFuzz(2), - "ns9" => wikiFuzz::makeFuzz(2), - "ns10" => wikiFuzz::makeFuzz(2), - "ns11" => wikiFuzz::makeFuzz(2), - "ns12" => wikiFuzz::makeFuzz(2), - "ns13" => wikiFuzz::makeFuzz(2), - "ns14" => wikiFuzz::makeFuzz(2), - "ns15" => wikiFuzz::makeFuzz(2), - "redirs" => wikiFuzz::makeFuzz(2), - "search" => wikiFuzz::makeFuzz(2), - "offset" => wikiFuzz::chooseInput( array("", "0", "-1", "--------'-----0", "+1", "81343242346234234", wikiFuzz::makeFuzz(2)) ), - "fulltext" => wikiFuzz::chooseInput( array("", "0", "1", "--------'-----0", "+1", wikiFuzz::makeFuzz(2)) ), - "searchx" => wikiFuzz::chooseInput( array("", "0", "1", "--------'-----0", "+1", wikiFuzz::makeFuzz(2)) ) - ); - } + function __construct() { + $this->pagePath = "index.php?title=Special:Search"; + + $this->params = array ( + "action" => "index.php?title=Special:Search", + "ns0" => wikiFuzz::makeFuzz( 2 ), + "ns1" => wikiFuzz::makeFuzz( 2 ), + "ns2" => wikiFuzz::makeFuzz( 2 ), + "ns3" => wikiFuzz::makeFuzz( 2 ), + "ns4" => wikiFuzz::makeFuzz( 2 ), + "ns5" => wikiFuzz::makeFuzz( 2 ), + "ns6" => wikiFuzz::makeFuzz( 2 ), + "ns7" => wikiFuzz::makeFuzz( 2 ), + "ns8" => wikiFuzz::makeFuzz( 2 ), + "ns9" => wikiFuzz::makeFuzz( 2 ), + "ns10" => wikiFuzz::makeFuzz( 2 ), + "ns11" => wikiFuzz::makeFuzz( 2 ), + "ns12" => wikiFuzz::makeFuzz( 2 ), + "ns13" => wikiFuzz::makeFuzz( 2 ), + "ns14" => wikiFuzz::makeFuzz( 2 ), + "ns15" => wikiFuzz::makeFuzz( 2 ), + "redirs" => wikiFuzz::makeFuzz( 2 ), + "search" => wikiFuzz::makeFuzz( 2 ), + "offset" => wikiFuzz::chooseInput( array( "", "0", "-1", "--------'-----0", "+1", "81343242346234234", wikiFuzz::makeFuzz( 2 ) ) ), + "fulltext" => wikiFuzz::chooseInput( array( "", "0", "1", "--------'-----0", "+1", wikiFuzz::makeFuzz( 2 ) ) ), + "searchx" => wikiFuzz::chooseInput( array( "", "0", "1", "--------'-----0", "+1", wikiFuzz::makeFuzz( 2 ) ) ) + ); + } } @@ -992,28 +993,28 @@ class searchTest extends pageTest { ** a page test for "Special:Recentchanges". */ class recentchangesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Recentchanges"; - - $this->params = array ( - "action" => wikiFuzz::makeFuzz(2), - "title" => wikiFuzz::makeFuzz(2), - "namespace" => wikiFuzz::chooseInput( range(-1, 15) ), - "Go" => wikiFuzz::makeFuzz(2), - "invert" => wikiFuzz::chooseInput( array("-1", "---'----------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - "hideanons" => wikiFuzz::chooseInput( array("-1", "------'-------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - 'limit' => wikiFuzz::chooseInput( array("0", "-1", "---------'----0", "+1", "81340909772349234", wikiFuzz::makeFuzz(2)) ), - "days" => wikiFuzz::chooseInput( array("-1", "----------'---0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - "hideminor" => wikiFuzz::chooseInput( array("-1", "-----------'--0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - "hidebots" => wikiFuzz::chooseInput( array("-1", "---------'----0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - "hideliu" => wikiFuzz::chooseInput( array("-1", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - "hidepatrolled" => wikiFuzz::chooseInput( array("-1", "-----'--------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - "hidemyself" => wikiFuzz::chooseInput( array("-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - 'categories_any'=> wikiFuzz::chooseInput( array("-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - 'categories' => wikiFuzz::chooseInput( array("-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - 'feed' => wikiFuzz::chooseInput( array("-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ) - ); - } + function __construct() { + $this->pagePath = "index.php?title=Special:Recentchanges"; + + $this->params = array ( + "action" => wikiFuzz::makeFuzz( 2 ), + "title" => wikiFuzz::makeFuzz( 2 ), + "namespace" => wikiFuzz::chooseInput( range( -1, 15 ) ), + "Go" => wikiFuzz::makeFuzz( 2 ), + "invert" => wikiFuzz::chooseInput( array( "-1", "---'----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + "hideanons" => wikiFuzz::chooseInput( array( "-1", "------'-------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + 'limit' => wikiFuzz::chooseInput( array( "0", "-1", "---------'----0", "+1", "81340909772349234", wikiFuzz::makeFuzz( 2 ) ) ), + "days" => wikiFuzz::chooseInput( array( "-1", "----------'---0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + "hideminor" => wikiFuzz::chooseInput( array( "-1", "-----------'--0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + "hidebots" => wikiFuzz::chooseInput( array( "-1", "---------'----0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + "hideliu" => wikiFuzz::chooseInput( array( "-1", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + "hidepatrolled" => wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + "hidemyself" => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + 'categories_any' => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + 'categories' => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + 'feed' => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ) + ); + } } @@ -1021,25 +1022,25 @@ class recentchangesTest extends pageTest { ** a page test for "Special:Prefixindex". */ class prefixindexTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Prefixindex"; + function __construct() { + $this->pagePath = "index.php?title=Special:Prefixindex"; - $this->params = array ( - "title" => "Special:Prefixindex", - "namespace" => wikiFuzz::randnum(-10,101), - "Go" => wikiFuzz::makeFuzz(2) - ); + $this->params = array ( + "title" => "Special:Prefixindex", + "namespace" => wikiFuzz::randnum( -10, 101 ), + "Go" => wikiFuzz::makeFuzz( 2 ) + ); - // sometimes we want 'prefix', sometimes we want 'from', and sometimes we want nothing. - if (wikiFuzz::randnum(3) == 0) { - $this->params["prefix"] = wikiFuzz::chooseInput( array("-1", "-----'--------0", "+++--+1", - wikiFuzz::randnum(-10,8134), wikiFuzz::makeFuzz(2)) ); - } - if (wikiFuzz::randnum(3) == 0) { - $this->params["from"] = wikiFuzz::chooseInput( array("-1", "-----'--------0", "+++--+1", - wikiFuzz::randnum(-10,8134), wikiFuzz::makeFuzz(2)) ); - } - } + // sometimes we want 'prefix', sometimes we want 'from', and sometimes we want nothing. + if ( wikiFuzz::randnum( 3 ) == 0 ) { + $this->params["prefix"] = wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+++--+1", + wikiFuzz::randnum( -10, 8134 ), wikiFuzz::makeFuzz( 2 ) ) ); + } + if ( wikiFuzz::randnum( 3 ) == 0 ) { + $this->params["from"] = wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+++--+1", + wikiFuzz::randnum( -10, 8134 ), wikiFuzz::makeFuzz( 2 ) ) ); + } + } } @@ -1047,16 +1048,16 @@ class prefixindexTest extends pageTest { ** a page test for "Special:MIMEsearch". */ class mimeSearchTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:MIMEsearch"; + function __construct() { + $this->pagePath = "index.php?title=Special:MIMEsearch"; - $this->params = array ( - "action" => "index.php?title=Special:MIMEsearch", - "mime" => wikiFuzz::makeFuzz(3), - 'limit' => wikiFuzz::chooseInput( array("0", "-1", "-------'------0", "+1", "81342321351235325", wikiFuzz::makeFuzz(2)) ), - 'offset' => wikiFuzz::chooseInput( array("0", "-1", "-----'--------0", "+1", "81341231235365252234324", wikiFuzz::makeFuzz(2)) ) - ); - } + $this->params = array ( + "action" => "index.php?title=Special:MIMEsearch", + "mime" => wikiFuzz::makeFuzz( 3 ), + 'limit' => wikiFuzz::chooseInput( array( "0", "-1", "-------'------0", "+1", "81342321351235325", wikiFuzz::makeFuzz( 2 ) ) ), + 'offset' => wikiFuzz::chooseInput( array( "0", "-1", "-----'--------0", "+1", "81341231235365252234324", wikiFuzz::makeFuzz( 2 ) ) ) + ); + } } @@ -1064,19 +1065,19 @@ class mimeSearchTest extends pageTest { ** a page test for "Special:Log". */ class specialLogTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Log"; + function __construct() { + $this->pagePath = "index.php?title=Special:Log"; - $this->params = array ( - "type" => wikiFuzz::chooseInput( array("", wikiFuzz::makeFuzz(2)) ), - "par" => wikiFuzz::makeFuzz(2), - "user" => wikiFuzz::makeFuzz(2), - "page" => wikiFuzz::makeFuzz(2), - "from" => wikiFuzz::makeFuzz(2), - "until" => wikiFuzz::makeFuzz(2), - "title" => wikiFuzz::makeFuzz(2) - ); - } + $this->params = array ( + "type" => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ), + "par" => wikiFuzz::makeFuzz( 2 ), + "user" => wikiFuzz::makeFuzz( 2 ), + "page" => wikiFuzz::makeFuzz( 2 ), + "from" => wikiFuzz::makeFuzz( 2 ), + "until" => wikiFuzz::makeFuzz( 2 ), + "title" => wikiFuzz::makeFuzz( 2 ) + ); + } } @@ -1084,18 +1085,18 @@ class specialLogTest extends pageTest { ** a page test for "Special:Userlogin", with a successful login. */ class successfulUserLoginTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Userlogin&action=submitlogin&type=login&returnto=" . wikiFuzz::makeFuzz(2); + function __construct() { + $this->pagePath = "index.php?title=Special:Userlogin&action=submitlogin&type=login&returnto=" . wikiFuzz::makeFuzz( 2 ); - $this->params = array ( - "wpName" => USER_ON_WIKI, - // sometimes real password, sometimes not: - 'wpPassword' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz(2), USER_PASSWORD ) ), - 'wpRemember' => wikiFuzz::makeFuzz(2) - ); + $this->params = array ( + "wpName" => USER_ON_WIKI, + // sometimes real password, sometimes not: + 'wpPassword' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), USER_PASSWORD ) ), + 'wpRemember' => wikiFuzz::makeFuzz( 2 ) + ); - $this->cookie = "wikidb_session=" . wikiFuzz::chooseInput( array("1" , wikiFuzz::makeFuzz(2) ) ); - } + $this->cookie = "wikidb_session=" . wikiFuzz::chooseInput( array( "1" , wikiFuzz::makeFuzz( 2 ) ) ); + } } @@ -1103,30 +1104,30 @@ class successfulUserLoginTest extends pageTest { ** a page test for "Special:Userlogin". */ class userLoginTest extends pageTest { - function __construct() { + function __construct() { - $this->pagePath = "index.php?title=Special:Userlogin"; + $this->pagePath = "index.php?title=Special:Userlogin"; - $this->params = array ( - 'wpRetype' => wikiFuzz::makeFuzz(2), - 'wpRemember' => wikiFuzz::makeFuzz(2), - 'wpRealName' => wikiFuzz::makeFuzz(2), - 'wpPassword' => wikiFuzz::makeFuzz(2), - 'wpName' => wikiFuzz::makeFuzz(2), - 'wpMailmypassword'=> wikiFuzz::makeFuzz(2), - 'wpLoginattempt' => wikiFuzz::makeFuzz(2), - 'wpEmail' => wikiFuzz::makeFuzz(2), - 'wpDomain' => wikiFuzz::chooseInput( array("", "local", wikiFuzz::makeFuzz(2)) ), - 'wpCreateaccountMail' => wikiFuzz::chooseInput( array("", wikiFuzz::makeFuzz(2)) ), - 'wpCreateaccount' => wikiFuzz::chooseInput( array("", wikiFuzz::makeFuzz(2)) ), - 'wpCookieCheck' => wikiFuzz::chooseInput( array("", wikiFuzz::makeFuzz(2)) ), - 'type' => wikiFuzz::chooseInput( array("signup", "login", "", wikiFuzz::makeFuzz(2)) ), - 'returnto' => wikiFuzz::makeFuzz(2), - 'action' => wikiFuzz::chooseInput( array("", "submitlogin", wikiFuzz::makeFuzz(2)) ) - ); + $this->params = array ( + 'wpRetype' => wikiFuzz::makeFuzz( 2 ), + 'wpRemember' => wikiFuzz::makeFuzz( 2 ), + 'wpRealName' => wikiFuzz::makeFuzz( 2 ), + 'wpPassword' => wikiFuzz::makeFuzz( 2 ), + 'wpName' => wikiFuzz::makeFuzz( 2 ), + 'wpMailmypassword' => wikiFuzz::makeFuzz( 2 ), + 'wpLoginattempt' => wikiFuzz::makeFuzz( 2 ), + 'wpEmail' => wikiFuzz::makeFuzz( 2 ), + 'wpDomain' => wikiFuzz::chooseInput( array( "", "local", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpCreateaccountMail' => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpCreateaccount' => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpCookieCheck' => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ), + 'type' => wikiFuzz::chooseInput( array( "signup", "login", "", wikiFuzz::makeFuzz( 2 ) ) ), + 'returnto' => wikiFuzz::makeFuzz( 2 ), + 'action' => wikiFuzz::chooseInput( array( "", "submitlogin", wikiFuzz::makeFuzz( 2 ) ) ) + ); - $this->cookie = "wikidb_session=" . wikiFuzz::chooseInput( array("1" , wikiFuzz::makeFuzz(2) ) ); - } + $this->cookie = "wikidb_session=" . wikiFuzz::chooseInput( array( "1" , wikiFuzz::makeFuzz( 2 ) ) ); + } } @@ -1134,32 +1135,32 @@ class userLoginTest extends pageTest { ** a page test for "Special:Ipblocklist" (also includes unblocking) */ class ipblocklistTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Ipblocklist"; - - $this->params = array ( - 'wpUnblockAddress'=> wikiFuzz::makeFuzz(2), - 'ip' => wikiFuzz::chooseInput( array("20398702394", "", "Nickj2", wikiFuzz::makeFuzz(2), - // something like an IP address, sometimes invalid: - ( wikiFuzz::randnum(300,-20) . "." . wikiFuzz::randnum(300,-20) . "." - . wikiFuzz::randnum(300,-20) . "." .wikiFuzz::randnum(300,-20) ) ) ), - 'id' => wikiFuzz::makeFuzz(2), - 'wpUnblockReason' => wikiFuzz::makeFuzz(2), - 'action' => wikiFuzz::chooseInput( array(wikiFuzz::makeFuzz(2), "success", "submit", "unblock") ), - 'wpEditToken' => wikiFuzz::makeFuzz(2), - 'wpBlock' => wikiFuzz::chooseInput( array(wikiFuzz::makeFuzz(2), "") ), - 'limit' => wikiFuzz::chooseInput( array("0", "-1", "--------'-----0", "+1", - "09700982312351132098234", wikiFuzz::makeFuzz(2)) ), - 'offset' => wikiFuzz::chooseInput( array("0", "-1", "------'-------0", "+1", - "09700980982341535324234234", wikiFuzz::makeFuzz(2)) ) - ); - - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(4) == 0) unset($this->params["action"]); - if (wikiFuzz::randnum(3) == 0) unset($this->params["ip"]); - if (wikiFuzz::randnum(2) == 0) unset($this->params["id"]); - if (wikiFuzz::randnum(3) == 0) unset($this->params["wpUnblockAddress"]); - } + function __construct() { + $this->pagePath = "index.php?title=Special:Ipblocklist"; + + $this->params = array ( + 'wpUnblockAddress' => wikiFuzz::makeFuzz( 2 ), + 'ip' => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ), + // something like an IP address, sometimes invalid: + ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "." + . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ), + 'id' => wikiFuzz::makeFuzz( 2 ), + 'wpUnblockReason' => wikiFuzz::makeFuzz( 2 ), + 'action' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "success", "submit", "unblock" ) ), + 'wpEditToken' => wikiFuzz::makeFuzz( 2 ), + 'wpBlock' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "" ) ), + 'limit' => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1", + "09700982312351132098234", wikiFuzz::makeFuzz( 2 ) ) ), + 'offset' => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", + "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) ) + ); + + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["ip"] ); + if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["id"] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpUnblockAddress"] ); + } } @@ -1167,20 +1168,20 @@ class ipblocklistTest extends pageTest { ** a page test for "Special:Newimages". */ class newImagesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Newimages"; + function __construct() { + $this->pagePath = "index.php?title=Special:Newimages"; - $this->params = array ( - 'hidebots' => wikiFuzz::chooseInput( array(wikiFuzz::makeFuzz(2), "1", "", "-1") ), - 'wpIlMatch' => wikiFuzz::makeFuzz(2), - 'until' => wikiFuzz::makeFuzz(2), - 'from' => wikiFuzz::makeFuzz(2) - ); + $this->params = array ( + 'hidebots' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "1", "", "-1" ) ), + 'wpIlMatch' => wikiFuzz::makeFuzz( 2 ), + 'until' => wikiFuzz::makeFuzz( 2 ), + 'from' => wikiFuzz::makeFuzz( 2 ) + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(6) == 0) unset($this->params["until"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["from"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["until"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["from"] ); + } } @@ -1188,16 +1189,16 @@ class newImagesTest extends pageTest { ** a page test for the "Special:Imagelist" page. */ class imagelistTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Imagelist"; + function __construct() { + $this->pagePath = "index.php?title=Special:Imagelist"; - $this->params = array ( - 'sort' => wikiFuzz::chooseInput( array("bysize", "byname" , "bydate", wikiFuzz::makeFuzz(2)) ), - 'limit' => wikiFuzz::chooseInput( array("0", "-1", "--------'-----0", "+1", "09700982312351132098234", wikiFuzz::makeFuzz(2)) ), - 'offset' => wikiFuzz::chooseInput( array("0", "-1", "------'-------0", "+1", "09700980982341535324234234", wikiFuzz::makeFuzz(2)) ), - 'wpIlMatch' => wikiFuzz::makeFuzz(2) - ); - } + $this->params = array ( + 'sort' => wikiFuzz::chooseInput( array( "bysize", "byname" , "bydate", wikiFuzz::makeFuzz( 2 ) ) ), + 'limit' => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1", "09700982312351132098234", wikiFuzz::makeFuzz( 2 ) ) ), + 'offset' => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpIlMatch' => wikiFuzz::makeFuzz( 2 ) + ); + } } @@ -1205,27 +1206,27 @@ class imagelistTest extends pageTest { ** a page test for "Special:Export". */ class specialExportTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Export"; + function __construct() { + $this->pagePath = "index.php?title=Special:Export"; - $this->params = array ( - 'action' => wikiFuzz::chooseInput( array("submit", "", wikiFuzz::makeFuzz(2)) ), - 'pages' => wikiFuzz::makeFuzz(2), - 'curonly' => wikiFuzz::chooseInput( array("", "0", "-1", wikiFuzz::makeFuzz(2)) ), - 'listauthors' => wikiFuzz::chooseInput( array("", "0", "-1", wikiFuzz::makeFuzz(2)) ), - 'history' => wikiFuzz::chooseInput( array("0", "-1", "------'-------0", "+1", "09700980982341535324234234", wikiFuzz::makeFuzz(2)) ), + $this->params = array ( + 'action' => wikiFuzz::chooseInput( array( "submit", "", wikiFuzz::makeFuzz( 2 ) ) ), + 'pages' => wikiFuzz::makeFuzz( 2 ), + 'curonly' => wikiFuzz::chooseInput( array( "", "0", "-1", wikiFuzz::makeFuzz( 2 ) ) ), + 'listauthors' => wikiFuzz::chooseInput( array( "", "0", "-1", wikiFuzz::makeFuzz( 2 ) ) ), + 'history' => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) ), - ); + ); - // For the time being, need to disable "submit" action as Tidy barfs on MediaWiki's XML export. - if ($this->params['action'] == 'submit') $this->params['action'] = ''; + // For the time being, need to disable "submit" action as Tidy barfs on MediaWiki's XML export. + if ( $this->params['action'] == 'submit' ) $this->params['action'] = ''; - // Sometimes remove the history field. - if (wikiFuzz::randnum(2) == 0) unset($this->params["history"]); - - // page does not produce HTML. - $this->tidyValidate = false; - } + // Sometimes remove the history field. + if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["history"] ); + + // page does not produce HTML. + $this->tidyValidate = false; + } } @@ -1233,15 +1234,15 @@ class specialExportTest extends pageTest { ** a page test for "Special:Booksources". */ class specialBooksourcesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Booksources"; + function __construct() { + $this->pagePath = "index.php?title=Special:Booksources"; - $this->params = array ( - 'go' => wikiFuzz::makeFuzz(2), - // ISBN codes have to contain some semi-numeric stuff or will be ignored: - 'isbn' => "0X0" . wikiFuzz::makeFuzz(2) - ); - } + $this->params = array ( + 'go' => wikiFuzz::makeFuzz( 2 ), + // ISBN codes have to contain some semi-numeric stuff or will be ignored: + 'isbn' => "0X0" . wikiFuzz::makeFuzz( 2 ) + ); + } } @@ -1249,15 +1250,15 @@ class specialBooksourcesTest extends pageTest { ** a page test for "Special:Allpages". */ class specialAllpagesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special%3AAllpages"; + function __construct() { + $this->pagePath = "index.php?title=Special%3AAllpages"; - $this->params = array ( - 'from' => wikiFuzz::makeFuzz(2), - 'namespace' => wikiFuzz::chooseInput( range(-1, 15) ), - 'go' => wikiFuzz::makeFuzz(2) - ); - } + $this->params = array ( + 'from' => wikiFuzz::makeFuzz( 2 ), + 'namespace' => wikiFuzz::chooseInput( range( -1, 15 ) ), + 'go' => wikiFuzz::makeFuzz( 2 ) + ); + } } @@ -1265,19 +1266,19 @@ class specialAllpagesTest extends pageTest { ** a page test for the page History. */ class pageHistoryTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Main_Page&action=history"; + function __construct() { + $this->pagePath = "index.php?title=Main_Page&action=history"; - $this->params = array ( - 'limit' => wikiFuzz::chooseInput( array("-1", "0", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - 'offset' => wikiFuzz::chooseInput( array("-1", "0", "------'-------0", "+1", "9823412312312412435", wikiFuzz::makeFuzz(2)) ), - "go" => wikiFuzz::chooseInput( array("first", "last", wikiFuzz::makeFuzz(2)) ), - "dir" => wikiFuzz::chooseInput( array("prev", "next", wikiFuzz::makeFuzz(2)) ), - "diff" => wikiFuzz::chooseInput( array("-1", "--------'-----0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - "oldid" => wikiFuzz::chooseInput( array("prev", "-1", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - "feed" => wikiFuzz::makeFuzz(2) - ); - } + $this->params = array ( + 'limit' => wikiFuzz::chooseInput( array( "-1", "0", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + 'offset' => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) ), + "go" => wikiFuzz::chooseInput( array( "first", "last", wikiFuzz::makeFuzz( 2 ) ) ), + "dir" => wikiFuzz::chooseInput( array( "prev", "next", wikiFuzz::makeFuzz( 2 ) ) ), + "diff" => wikiFuzz::chooseInput( array( "-1", "--------'-----0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + "oldid" => wikiFuzz::chooseInput( array( "prev", "-1", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + "feed" => wikiFuzz::makeFuzz( 2 ) + ); + } } @@ -1285,17 +1286,17 @@ class pageHistoryTest extends pageTest { ** a page test for the Special:Contributions". */ class contributionsTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Contributions/" . USER_ON_WIKI; + function __construct() { + $this->pagePath = "index.php?title=Special:Contributions/" . USER_ON_WIKI; - $this->params = array ( - 'target' => wikiFuzz::chooseInput( array(wikiFuzz::makeFuzz(2), "newbies", USER_ON_WIKI) ), - 'namespace' => wikiFuzz::chooseInput( array(-1, 15, 1, wikiFuzz::makeFuzz(2)) ), - 'offset' => wikiFuzz::chooseInput( array("0", "-1", "------'-------0", "+1", "982342131232131231241", wikiFuzz::makeFuzz(2)) ), - 'bot' => wikiFuzz::chooseInput( array("", "-1", "0", "1", wikiFuzz::makeFuzz(2)) ), - 'go' => wikiFuzz::chooseInput( array("-1", 'prev', 'next', wikiFuzz::makeFuzz(2)) ) - ); - } + $this->params = array ( + 'target' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "newbies", USER_ON_WIKI ) ), + 'namespace' => wikiFuzz::chooseInput( array( -1, 15, 1, wikiFuzz::makeFuzz( 2 ) ) ), + 'offset' => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "982342131232131231241", wikiFuzz::makeFuzz( 2 ) ) ), + 'bot' => wikiFuzz::chooseInput( array( "", "-1", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), + 'go' => wikiFuzz::chooseInput( array( "-1", 'prev', 'next', wikiFuzz::makeFuzz( 2 ) ) ) + ); + } } @@ -1303,66 +1304,66 @@ class contributionsTest extends pageTest { ** a page test for viewing a normal page, whilst posting various params. */ class viewPageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Main_Page"; - - $this->params = array ( - "useskin" => wikiFuzz::chooseInput( array("chick", "cologneblue", "myskin", - "nostalgia", "simple", "standard", wikiFuzz::makeFuzz(2)) ), - "uselang" => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz(2), - "ab", "af", "an", "ar", "arc", "as", "ast", "av", "ay", "az", "ba", - "bat-smg", "be", "bg", "bm", "bn", "bo", "bpy", "br", "bs", "ca", - "ce", "cs", "csb", "cv", "cy", "da", "de", "dv", "dz", "el", "en", - "eo", "es", "et", "eu", "fa", "fi", "fo", "fr", "fur", "fy", "ga", - "gn", "gsw", "gu", "he", "hi", "hr", "hu", "ia", "id", "ii", "is", - "it", "ja", "jv", "ka", "km", "kn", "ko", "ks", "ku", "kv", "la", - "li", "lo", "lt", "lv", "mk", "ml", "ms", "nah", "nap", "nds", - "nds-nl", "nl", "nn", "no", "non", "nv", "oc", "or", "os", "pa", - "pl", "pms", "ps", "pt", "pt-br", "qu", "rmy", "ro", "ru", "sc", - "sd", "sk", "sl", "sq", "sr", "sr-ec", "sr-el", - "su", "sv", "ta", "te", "th", "tr", "tt", "ty", "tyv", "udm", - "ug", "uk", "ur", "utf8", "vec", "vi", "wa", "xal", "yi", "za", - "zh", "zh-cn", "zh-hk", "zh-sg", "zh-tw", "zh-tw") ), - "returnto" => wikiFuzz::makeFuzz(2), - "feed" => wikiFuzz::chooseInput( array("atom", "rss", wikiFuzz::makeFuzz(2)) ), - "rcid" => wikiFuzz::makeFuzz(2), - "action" => wikiFuzz::chooseInput( array("view", "raw", "render", wikiFuzz::makeFuzz(2), "markpatrolled") ), - "printable" => wikiFuzz::makeFuzz(2), - "oldid" => wikiFuzz::makeFuzz(2), - "redirect" => wikiFuzz::makeFuzz(2), - "diff" => wikiFuzz::makeFuzz(2), - "search" => wikiFuzz::makeFuzz(2), - "rdfrom" => wikiFuzz::makeFuzz(2), // things from Article.php from here on: - "token" => wikiFuzz::makeFuzz(2), - "tbid" => wikiFuzz::makeFuzz(2), - "action" => wikiFuzz::chooseInput( array("purge", wikiFuzz::makeFuzz(2)) ), - "wpReason" => wikiFuzz::makeFuzz(2), - "wpEditToken" => wikiFuzz::makeFuzz(2), - "from" => wikiFuzz::makeFuzz(2), - "bot" => wikiFuzz::makeFuzz(2), - "summary" => wikiFuzz::makeFuzz(2), - "direction" => wikiFuzz::chooseInput( array("next", "prev", wikiFuzz::makeFuzz(2)) ), - "section" => wikiFuzz::makeFuzz(2), - "preload" => wikiFuzz::makeFuzz(2), - - ); - - // Tidy does not know how to valid atom or rss, so exclude from testing for the time being. - if ($this->params["feed"] == "atom") { unset($this->params["feed"]); } - else if ($this->params["feed"] == "rss") { unset($this->params["feed"]); } - - // Raw pages cannot really be validated - if ($this->params["action"] == "raw") unset($this->params["action"]); - - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(6) == 0) unset($this->params["rcid"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["diff"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["rdfrom"]); - if (wikiFuzz::randnum(3) == 0) unset($this->params["oldid"]); - - // usually don't want action == purge. - if (wikiFuzz::randnum(6) > 1) unset($this->params["action"]); - } + function __construct() { + $this->pagePath = "index.php?title=Main_Page"; + + $this->params = array ( + "useskin" => wikiFuzz::chooseInput( array( "chick", "cologneblue", "myskin", + "nostalgia", "simple", "standard", wikiFuzz::makeFuzz( 2 ) ) ), + "uselang" => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), + "ab", "af", "an", "ar", "arc", "as", "ast", "av", "ay", "az", "ba", + "bat-smg", "be", "bg", "bm", "bn", "bo", "bpy", "br", "bs", "ca", + "ce", "cs", "csb", "cv", "cy", "da", "de", "dv", "dz", "el", "en", + "eo", "es", "et", "eu", "fa", "fi", "fo", "fr", "fur", "fy", "ga", + "gn", "gsw", "gu", "he", "hi", "hr", "hu", "ia", "id", "ii", "is", + "it", "ja", "jv", "ka", "km", "kn", "ko", "ks", "ku", "kv", "la", + "li", "lo", "lt", "lv", "mk", "ml", "ms", "nah", "nap", "nds", + "nds-nl", "nl", "nn", "no", "non", "nv", "oc", "or", "os", "pa", + "pl", "pms", "ps", "pt", "pt-br", "qu", "rmy", "ro", "ru", "sc", + "sd", "sk", "sl", "sq", "sr", "sr-ec", "sr-el", + "su", "sv", "ta", "te", "th", "tr", "tt", "ty", "tyv", "udm", + "ug", "uk", "ur", "utf8", "vec", "vi", "wa", "xal", "yi", "za", + "zh", "zh-cn", "zh-hk", "zh-sg", "zh-tw", "zh-tw" ) ), + "returnto" => wikiFuzz::makeFuzz( 2 ), + "feed" => wikiFuzz::chooseInput( array( "atom", "rss", wikiFuzz::makeFuzz( 2 ) ) ), + "rcid" => wikiFuzz::makeFuzz( 2 ), + "action" => wikiFuzz::chooseInput( array( "view", "raw", "render", wikiFuzz::makeFuzz( 2 ), "markpatrolled" ) ), + "printable" => wikiFuzz::makeFuzz( 2 ), + "oldid" => wikiFuzz::makeFuzz( 2 ), + "redirect" => wikiFuzz::makeFuzz( 2 ), + "diff" => wikiFuzz::makeFuzz( 2 ), + "search" => wikiFuzz::makeFuzz( 2 ), + "rdfrom" => wikiFuzz::makeFuzz( 2 ), // things from Article.php from here on: + "token" => wikiFuzz::makeFuzz( 2 ), + "tbid" => wikiFuzz::makeFuzz( 2 ), + "action" => wikiFuzz::chooseInput( array( "purge", wikiFuzz::makeFuzz( 2 ) ) ), + "wpReason" => wikiFuzz::makeFuzz( 2 ), + "wpEditToken" => wikiFuzz::makeFuzz( 2 ), + "from" => wikiFuzz::makeFuzz( 2 ), + "bot" => wikiFuzz::makeFuzz( 2 ), + "summary" => wikiFuzz::makeFuzz( 2 ), + "direction" => wikiFuzz::chooseInput( array( "next", "prev", wikiFuzz::makeFuzz( 2 ) ) ), + "section" => wikiFuzz::makeFuzz( 2 ), + "preload" => wikiFuzz::makeFuzz( 2 ), + + ); + + // Tidy does not know how to valid atom or rss, so exclude from testing for the time being. + if ( $this->params["feed"] == "atom" ) { unset( $this->params["feed"] ); } + else if ( $this->params["feed"] == "rss" ) { unset( $this->params["feed"] ); } + + // Raw pages cannot really be validated + if ( $this->params["action"] == "raw" ) unset( $this->params["action"] ); + + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["rcid"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["diff"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["rdfrom"] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["oldid"] ); + + // usually don't want action == purge. + if ( wikiFuzz::randnum( 6 ) > 1 ) unset( $this->params["action"] ); + } } @@ -1370,50 +1371,50 @@ class viewPageTest extends pageTest { ** a page test for "Special:Allmessages". */ class specialAllmessagesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Allmessages"; + function __construct() { + $this->pagePath = "index.php?title=Special:Allmessages"; - // only really has one parameter - $this->params = array ( - "ot" => wikiFuzz::chooseInput( array("php", "html", wikiFuzz::makeFuzz(2)) ) - ); - } + // only really has one parameter + $this->params = array ( + "ot" => wikiFuzz::chooseInput( array( "php", "html", wikiFuzz::makeFuzz( 2 ) ) ) + ); + } } /** ** a page test for "Special:Newpages". */ class specialNewpages extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Newpages"; + function __construct() { + $this->pagePath = "index.php?title=Special:Newpages"; - $this->params = array ( - "namespace" => wikiFuzz::chooseInput( range(-1, 15) ), - "feed" => wikiFuzz::chooseInput( array("atom", "rss", wikiFuzz::makeFuzz(2)) ), - 'limit' => wikiFuzz::chooseInput( array("-1", "0", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz(2)) ), - 'offset' => wikiFuzz::chooseInput( array("-1", "0", "------'-------0", "+1", "9823412312312412435", wikiFuzz::makeFuzz(2)) ) - ); + $this->params = array ( + "namespace" => wikiFuzz::chooseInput( range( -1, 15 ) ), + "feed" => wikiFuzz::chooseInput( array( "atom", "rss", wikiFuzz::makeFuzz( 2 ) ) ), + 'limit' => wikiFuzz::chooseInput( array( "-1", "0", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ), + 'offset' => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) ) + ); - // Tidy does not know how to valid atom or rss, so exclude from testing for the time being. - if ($this->params["feed"] == "atom") { unset($this->params["feed"]); } - else if ($this->params["feed"] == "rss") { unset($this->params["feed"]); } - } + // Tidy does not know how to valid atom or rss, so exclude from testing for the time being. + if ( $this->params["feed"] == "atom" ) { unset( $this->params["feed"] ); } + else if ( $this->params["feed"] == "rss" ) { unset( $this->params["feed"] ); } + } } /** ** a page test for "redirect.php" */ class redirectTest extends pageTest { - function __construct() { - $this->pagePath = "redirect.php"; + function __construct() { + $this->pagePath = "redirect.php"; - $this->params = array ( - "wpDropdown" => wikiFuzz::makeFuzz(2) - ); + $this->params = array ( + "wpDropdown" => wikiFuzz::makeFuzz( 2 ) + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpDropdown"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpDropdown"] ); + } } @@ -1421,14 +1422,14 @@ class redirectTest extends pageTest { ** a page test for "Special:Confirmemail" */ class confirmEmail extends pageTest { - function __construct() { - // sometimes we send a bogus confirmation code, and sometimes we don't. - $this->pagePath = "index.php?title=Special:Confirmemail" . wikiFuzz::chooseInput( array("", "/" . wikiFuzz::makeTitleSafe(wikiFuzz::makeFuzz(1)) ) ); + function __construct() { + // sometimes we send a bogus confirmation code, and sometimes we don't. + $this->pagePath = "index.php?title=Special:Confirmemail" . wikiFuzz::chooseInput( array( "", "/" . wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 1 ) ) ) ); - $this->params = array ( - "token" => wikiFuzz::makeFuzz(2) - ); - } + $this->params = array ( + "token" => wikiFuzz::makeFuzz( 2 ) + ); + } } @@ -1437,24 +1438,24 @@ class confirmEmail extends pageTest { ** Note: this test would be better if we were logged in. */ class watchlistTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Watchlist"; + function __construct() { + $this->pagePath = "index.php?title=Special:Watchlist"; - $this->params = array ( - "remove" => wikiFuzz::chooseInput( array("Remove checked items from watchlist", wikiFuzz::makeFuzz(2))), - 'days' => wikiFuzz::chooseInput( array(0, -1, -230, "--", 3, 9, wikiFuzz::makeFuzz(2)) ), - 'hideOwn' => wikiFuzz::chooseInput( array("", "0", "1", wikiFuzz::makeFuzz(2)) ), - 'hideBots' => wikiFuzz::chooseInput( array("", "0", "1", wikiFuzz::makeFuzz(2)) ), - 'namespace'=> wikiFuzz::chooseInput( array("", "0", "1", wikiFuzz::makeFuzz(2)) ), - 'action' => wikiFuzz::chooseInput( array("submit", "clear", wikiFuzz::makeFuzz(2)) ), - 'id[]' => wikiFuzz::makeFuzz(2), - 'edit' => wikiFuzz::makeFuzz(2), - 'token' => wikiFuzz::chooseInput( array("", "1243213", wikiFuzz::makeFuzz(2)) ) - ); + $this->params = array ( + "remove" => wikiFuzz::chooseInput( array( "Remove checked items from watchlist", wikiFuzz::makeFuzz( 2 ) ) ), + 'days' => wikiFuzz::chooseInput( array( 0, -1, -230, "--", 3, 9, wikiFuzz::makeFuzz( 2 ) ) ), + 'hideOwn' => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), + 'hideBots' => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), + 'namespace' => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), + 'action' => wikiFuzz::chooseInput( array( "submit", "clear", wikiFuzz::makeFuzz( 2 ) ) ), + 'id[]' => wikiFuzz::makeFuzz( 2 ), + 'edit' => wikiFuzz::makeFuzz( 2 ), + 'token' => wikiFuzz::chooseInput( array( "", "1243213", wikiFuzz::makeFuzz( 2 ) ) ) + ); - // sometimes we specifiy "reset", and sometimes we don't. - if (wikiFuzz::randnum(3) == 0) $this->params["reset"] = wikiFuzz::chooseInput( array("", "0", "1", wikiFuzz::makeFuzz(2)) ); - } + // sometimes we specifiy "reset", and sometimes we don't. + if ( wikiFuzz::randnum( 3 ) == 0 ) $this->params["reset"] = wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ); + } } @@ -1462,16 +1463,16 @@ class watchlistTest extends pageTest { ** a page test for "Special:Blockme" */ class specialBlockmeTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Blockme"; + function __construct() { + $this->pagePath = "index.php?title=Special:Blockme"; - $this->params = array ( ); + $this->params = array ( ); - // sometimes we specify "ip", and sometimes we don't. - if (wikiFuzz::randnum(1) == 0) { - $this->params["ip"] = wikiFuzz::chooseInput( array("10.12.41.213", wikiFuzz::randnum(-10,8134), wikiFuzz::makeFuzz(2)) ); - } - } + // sometimes we specify "ip", and sometimes we don't. + if ( wikiFuzz::randnum( 1 ) == 0 ) { + $this->params["ip"] = wikiFuzz::chooseInput( array( "10.12.41.213", wikiFuzz::randnum( -10, 8134 ), wikiFuzz::makeFuzz( 2 ) ) ); + } + } } @@ -1479,32 +1480,32 @@ class specialBlockmeTest extends pageTest { ** a page test for "Special:Movepage" */ class specialMovePage extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Movepage"; - - $this->params = array ( - "action" => wikiFuzz::chooseInput( array("success", "submit", "", wikiFuzz::makeFuzz(2)) ), - 'wpEditToken' => wikiFuzz::chooseInput( array('', 0, 34987987, wikiFuzz::makeFuzz(2)) ), - 'target' => wikiFuzz::chooseInput( array("x", wikiFuzz::makeTitleSafe(wikiFuzz::makeFuzz(2)) ) ), - 'wpOldTitle' => wikiFuzz::chooseInput( array("z", wikiFuzz::makeTitleSafe(wikiFuzz::makeFuzz(2)), wikiFuzz::makeFuzz(2) ) ), - 'wpNewTitle' => wikiFuzz::chooseInput( array("y", wikiFuzz::makeTitleSafe(wikiFuzz::makeFuzz(2)), wikiFuzz::makeFuzz(2) ) ), - 'wpReason' => wikiFuzz::chooseInput( array(wikiFuzz::makeFuzz(2)) ), - 'wpMovetalk' => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - 'wpDeleteAndMove' => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - 'wpConfirm' => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - 'talkmoved' => wikiFuzz::chooseInput( array("1", wikiFuzz::makeFuzz(2), "articleexists", 'notalkpage') ), - 'oldtitle' => wikiFuzz::makeFuzz(2), - 'newtitle' => wikiFuzz::makeFuzz(2), - 'wpMovetalk' => wikiFuzz::chooseInput( array("1", "0", wikiFuzz::makeFuzz(2)) ) - ); - - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(2) == 0) unset($this->params["wpEditToken"]); - if (wikiFuzz::randnum(3) == 0) unset($this->params["target"]); - if (wikiFuzz::randnum(3) == 0) unset($this->params["wpNewTitle"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpReason"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpOldTitle"]); - } + function __construct() { + $this->pagePath = "index.php?title=Special:Movepage"; + + $this->params = array ( + "action" => wikiFuzz::chooseInput( array( "success", "submit", "", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpEditToken' => wikiFuzz::chooseInput( array( '', 0, 34987987, wikiFuzz::makeFuzz( 2 ) ) ), + 'target' => wikiFuzz::chooseInput( array( "x", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ) ) ), + 'wpOldTitle' => wikiFuzz::chooseInput( array( "z", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ), wikiFuzz::makeFuzz( 2 ) ) ), + 'wpNewTitle' => wikiFuzz::chooseInput( array( "y", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ), wikiFuzz::makeFuzz( 2 ) ) ), + 'wpReason' => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ) ) ), + 'wpMovetalk' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpDeleteAndMove' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpConfirm' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + 'talkmoved' => wikiFuzz::chooseInput( array( "1", wikiFuzz::makeFuzz( 2 ), "articleexists", 'notalkpage' ) ), + 'oldtitle' => wikiFuzz::makeFuzz( 2 ), + 'newtitle' => wikiFuzz::makeFuzz( 2 ), + 'wpMovetalk' => wikiFuzz::chooseInput( array( "1", "0", wikiFuzz::makeFuzz( 2 ) ) ) + ); + + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["wpEditToken"] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["target"] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpNewTitle"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpReason"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpOldTitle"] ); + } } @@ -1512,26 +1513,26 @@ class specialMovePage extends pageTest { ** a page test for "Special:Undelete" */ class specialUndelete extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Undelete"; + function __construct() { + $this->pagePath = "index.php?title=Special:Undelete"; - $this->params = array ( - "action" => wikiFuzz::chooseInput( array("submit", "", wikiFuzz::makeFuzz(2)) ), - 'wpEditToken' => wikiFuzz::chooseInput( array('', 0, 34987987, wikiFuzz::makeFuzz(2)) ), - 'target' => wikiFuzz::chooseInput( array("x", wikiFuzz::makeTitleSafe(wikiFuzz::makeFuzz(2)) ) ), - 'timestamp' => wikiFuzz::chooseInput( array("125223", wikiFuzz::makeFuzz(2) ) ), - 'file' => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - 'restore' => wikiFuzz::chooseInput( array("0", "1", wikiFuzz::makeFuzz(2)) ), - 'preview' => wikiFuzz::chooseInput( array("0", "1", wikiFuzz::makeFuzz(2)) ), - 'wpComment' => wikiFuzz::makeFuzz(2) - ); + $this->params = array ( + "action" => wikiFuzz::chooseInput( array( "submit", "", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpEditToken' => wikiFuzz::chooseInput( array( '', 0, 34987987, wikiFuzz::makeFuzz( 2 ) ) ), + 'target' => wikiFuzz::chooseInput( array( "x", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ) ) ), + 'timestamp' => wikiFuzz::chooseInput( array( "125223", wikiFuzz::makeFuzz( 2 ) ) ), + 'file' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + 'restore' => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), + 'preview' => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpComment' => wikiFuzz::makeFuzz( 2 ) + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(2) == 0) unset($this->params["wpEditToken"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["target"]); - if (wikiFuzz::randnum(1) == 0) unset($this->params["restore"]); - if (wikiFuzz::randnum(1) == 0) unset($this->params["preview"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["wpEditToken"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["target"] ); + if ( wikiFuzz::randnum( 1 ) == 0 ) unset( $this->params["restore"] ); + if ( wikiFuzz::randnum( 1 ) == 0 ) unset( $this->params["preview"] ); + } } @@ -1539,20 +1540,20 @@ class specialUndelete extends pageTest { ** a page test for "Special:Unlockdb" */ class specialUnlockdb extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Unlockdb"; + function __construct() { + $this->pagePath = "index.php?title=Special:Unlockdb"; - $this->params = array ( - "action" => wikiFuzz::chooseInput( array("submit", "success", "", wikiFuzz::makeFuzz(2)) ), - 'wpEditToken' => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - 'wpLockConfirm' => wikiFuzz::chooseInput( array("0", "1", wikiFuzz::makeFuzz(2)) ) - ); + $this->params = array ( + "action" => wikiFuzz::chooseInput( array( "submit", "success", "", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpEditToken' => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpLockConfirm' => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) ) + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpEditToken"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["action"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpLockConfirm"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpEditToken"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpLockConfirm"] ); + } } @@ -1560,21 +1561,21 @@ class specialUnlockdb extends pageTest { ** a page test for "Special:Lockdb" */ class specialLockdb extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Lockdb"; + function __construct() { + $this->pagePath = "index.php?title=Special:Lockdb"; - $this->params = array ( - "action" => wikiFuzz::chooseInput( array("submit", "success", "", wikiFuzz::makeFuzz(2)) ), - 'wpEditToken' => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - 'wpLockReason' => wikiFuzz::makeFuzz(2), - 'wpLockConfirm'=> wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ) - ); + $this->params = array ( + "action" => wikiFuzz::chooseInput( array( "submit", "success", "", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpEditToken' => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpLockReason' => wikiFuzz::makeFuzz( 2 ), + 'wpLockConfirm' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ) + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpEditToken"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["action"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpLockConfirm"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpEditToken"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpLockConfirm"] ); + } } @@ -1582,22 +1583,22 @@ class specialLockdb extends pageTest { ** a page test for "Special:Userrights" */ class specialUserrights extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Userrights"; + function __construct() { + $this->pagePath = "index.php?title=Special:Userrights"; - $this->params = array ( - 'wpEditToken' => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - 'user-editname' => wikiFuzz::chooseInput( array("Nickj2", "Nickj2\n<xyz>", wikiFuzz::makeFuzz(2)) ), - 'ssearchuser' => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - 'saveusergroups'=> wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)), "Save User Groups"), - 'member[]' => wikiFuzz::chooseInput( array("0", "bot", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - "available[]" => wikiFuzz::chooseInput( array("0", "sysop", "bureaucrat", "1", "++--34234", wikiFuzz::makeFuzz(2)) ) - ); + $this->params = array ( + 'wpEditToken' => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + 'user-editname' => wikiFuzz::chooseInput( array( "Nickj2", "Nickj2\n<xyz>", wikiFuzz::makeFuzz( 2 ) ) ), + 'ssearchuser' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + 'saveusergroups' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ), "Save User Groups" ), + 'member[]' => wikiFuzz::chooseInput( array( "0", "bot", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "available[]" => wikiFuzz::chooseInput( array( "0", "sysop", "bureaucrat", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ) + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(3) == 0) unset($this->params['ssearchuser']); - if (wikiFuzz::randnum(3) == 0) unset($this->params['saveusergroups']); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['ssearchuser'] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['saveusergroups'] ); + } } @@ -1605,23 +1606,23 @@ class specialUserrights extends pageTest { ** a test for page protection and unprotection. */ class pageProtectionForm extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Main_Page"; + function __construct() { + $this->pagePath = "index.php?title=Main_Page"; - $this->params = array ( - "action" => "protect", - 'wpEditToken' => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - "mwProtect-level-edit" => wikiFuzz::chooseInput( array('', 'autoconfirmed', 'sysop', wikifuzz::makeFuzz(2)) ), - "mwProtect-level-move" => wikiFuzz::chooseInput( array('', 'autoconfirmed', 'sysop', wikifuzz::makeFuzz(2)) ), - "mwProtectUnchained" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - 'mwProtect-reason' => wikiFuzz::chooseInput( array("because it was there", wikifuzz::makeFuzz(2)) ) - ); + $this->params = array ( + "action" => "protect", + 'wpEditToken' => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + "mwProtect-level-edit" => wikiFuzz::chooseInput( array( '', 'autoconfirmed', 'sysop', wikiFuzz::makeFuzz( 2 ) ) ), + "mwProtect-level-move" => wikiFuzz::chooseInput( array( '', 'autoconfirmed', 'sysop', wikiFuzz::makeFuzz( 2 ) ) ), + "mwProtectUnchained" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + 'mwProtect-reason' => wikiFuzz::chooseInput( array( "because it was there", wikiFuzz::makeFuzz( 2 ) ) ) + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(3) == 0) unset($this->params["mwProtectUnchained"]); - if (wikiFuzz::randnum(3) == 0) unset($this->params['mwProtect-reason']); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["mwProtectUnchained"] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['mwProtect-reason'] ); + } } @@ -1629,38 +1630,38 @@ class pageProtectionForm extends pageTest { ** a page test for "Special:Blockip". */ class specialBlockip extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Blockip"; - - $this->params = array ( - "action" => wikiFuzz::chooseInput( array("submit", "", wikiFuzz::makeFuzz(2)) ), - 'wpEditToken' => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - "wpBlockAddress" => wikiFuzz::chooseInput( array("20398702394", "", "Nickj2", wikiFuzz::makeFuzz(2), - // something like an IP address, sometimes invalid: - ( wikiFuzz::randnum(300,-20) . "." . wikiFuzz::randnum(300,-20) . "." - . wikiFuzz::randnum(300,-20) . "." .wikiFuzz::randnum(300,-20) ) ) ), - "ip" => wikiFuzz::chooseInput( array("20398702394", "", "Nickj2", wikiFuzz::makeFuzz(2), - // something like an IP address, sometimes invalid: - ( wikiFuzz::randnum(300,-20) . "." . wikiFuzz::randnum(300,-20) . "." - . wikiFuzz::randnum(300,-20) . "." .wikiFuzz::randnum(300,-20) ) ) ), - "wpBlockOther" => wikiFuzz::chooseInput( array('', 'Nickj2', wikifuzz::makeFuzz(2)) ), - "wpBlockExpiry" => wikiFuzz::chooseInput( array("other", "2 hours", "1 day", "3 days", "1 week", "2 weeks", - "1 month", "3 months", "6 months", "1 year", "infinite", wikiFuzz::makeFuzz(2)) ), - "wpBlockReason" => wikiFuzz::chooseInput( array("because it was there", wikifuzz::makeFuzz(2)) ), - "wpAnonOnly" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - "wpCreateAccount" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - "wpBlock" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ) - ); - - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpBlockOther"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpBlockExpiry"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpBlockReason"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpAnonOnly"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpCreateAccount"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["wpBlockAddress"]); - if (wikiFuzz::randnum(4) == 0) unset($this->params["ip"]); - } + function __construct() { + $this->pagePath = "index.php?title=Special:Blockip"; + + $this->params = array ( + "action" => wikiFuzz::chooseInput( array( "submit", "", wikiFuzz::makeFuzz( 2 ) ) ), + 'wpEditToken' => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + "wpBlockAddress" => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ), + // something like an IP address, sometimes invalid: + ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "." + . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ), + "ip" => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ), + // something like an IP address, sometimes invalid: + ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "." + . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ), + "wpBlockOther" => wikiFuzz::chooseInput( array( '', 'Nickj2', wikiFuzz::makeFuzz( 2 ) ) ), + "wpBlockExpiry" => wikiFuzz::chooseInput( array( "other", "2 hours", "1 day", "3 days", "1 week", "2 weeks", + "1 month", "3 months", "6 months", "1 year", "infinite", wikiFuzz::makeFuzz( 2 ) ) ), + "wpBlockReason" => wikiFuzz::chooseInput( array( "because it was there", wikiFuzz::makeFuzz( 2 ) ) ), + "wpAnonOnly" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "wpCreateAccount" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "wpBlock" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ) + ); + + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockOther"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockExpiry"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockReason"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpAnonOnly"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpCreateAccount"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockAddress"] ); + if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["ip"] ); + } } @@ -1668,22 +1669,22 @@ class specialBlockip extends pageTest { ** a test for the imagepage. */ class imagepageTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Image:Small-email.png"; + function __construct() { + $this->pagePath = "index.php?title=Image:Small-email.png"; - $this->params = array ( - "image" => wikiFuzz::chooseInput( array("Small-email.png", wikifuzz::makeFuzz(2)) ), - "wpReason" => wikifuzz::makeFuzz(2), - "oldimage" => wikiFuzz::chooseInput( array("Small-email.png", wikifuzz::makeFuzz(2)) ), - "wpEditToken" => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - ); + $this->params = array ( + "image" => wikiFuzz::chooseInput( array( "Small-email.png", wikiFuzz::makeFuzz( 2 ) ) ), + "wpReason" => wikiFuzz::makeFuzz( 2 ), + "oldimage" => wikiFuzz::chooseInput( array( "Small-email.png", wikiFuzz::makeFuzz( 2 ) ) ), + "wpEditToken" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(6) == 0) unset($this->params["image"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpReason"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["oldimage"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpEditToken"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["image"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpReason"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldimage"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpEditToken"] ); + } } @@ -1691,20 +1692,20 @@ class imagepageTest extends pageTest { ** a test for page deletion form. */ class pageDeletion extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Main_Page&action=delete"; + function __construct() { + $this->pagePath = "index.php?title=Main_Page&action=delete"; - $this->params = array ( - "wpEditToken" => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - "wpReason" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - "wpConfirm" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - ); + $this->params = array ( + "wpEditToken" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + "wpReason" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "wpConfirm" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(5) == 0) unset($this->params["wpReason"]); - if (wikiFuzz::randnum(5) == 0) unset($this->params["wpEditToken"]); - if (wikiFuzz::randnum(5) == 0) unset($this->params["wpConfirm"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpReason"] ); + if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpEditToken"] ); + if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpConfirm"] ); + } } @@ -1713,30 +1714,30 @@ class pageDeletion extends pageTest { ** a test for Revision Deletion. */ class specialRevisionDelete extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Revisiondelete"; - - $this->params = array ( - "target" => wikiFuzz::chooseInput( array("Main Page", wikifuzz::makeFuzz(2)) ), - "oldid" => wikifuzz::makeFuzz(2), - "oldid[]" => wikifuzz::makeFuzz(2), - "wpReason" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - "revdelete-hide-text" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - "revdelete-hide-comment" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - "revdelete-hide-user" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - "revdelete-hide-restricted" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - ); - - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(3) == 0) unset($this->params["target"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["oldid"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["oldid[]"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["wpReason"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["revdelete-hide-text"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["revdelete-hide-comment"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["revdelete-hide-user"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["revdelete-hide-restricted"]); - } + function __construct() { + $this->pagePath = "index.php?title=Special:Revisiondelete"; + + $this->params = array ( + "target" => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ), + "oldid" => wikiFuzz::makeFuzz( 2 ), + "oldid[]" => wikiFuzz::makeFuzz( 2 ), + "wpReason" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "revdelete-hide-text" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "revdelete-hide-comment" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "revdelete-hide-user" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "revdelete-hide-restricted" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + ); + + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["target"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldid"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldid[]"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpReason"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-text"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-comment"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-user"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-restricted"] ); + } } @@ -1744,31 +1745,31 @@ class specialRevisionDelete extends pageTest { ** a test for Special:Import. */ class specialImport extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Import"; + function __construct() { + $this->pagePath = "index.php?title=Special:Import"; - $this->params = array ( - "action" => "submit", - "source" => wikiFuzz::chooseInput( array("upload", "interwiki", wikifuzz::makeFuzz(2)) ), - "MAX_FILE_SIZE" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikifuzz::makeFuzz(2)) ), - "xmlimport" => wikiFuzz::chooseInput( array("/var/www/hosts/mediawiki/wiki/AdminSettings.php", "1", "++--34234", wikiFuzz::makeFuzz(2)) ), - "namespace" => wikiFuzz::chooseInput( array(wikiFuzz::randnum(30,-6), wikiFuzz::makeFuzz(2)) ), - "interwiki" => wikiFuzz::makeFuzz(2), - "interwikiHistory" => wikiFuzz::makeFuzz(2), - "frompage" => wikiFuzz::makeFuzz(2), - ); + $this->params = array ( + "action" => "submit", + "source" => wikiFuzz::chooseInput( array( "upload", "interwiki", wikiFuzz::makeFuzz( 2 ) ) ), + "MAX_FILE_SIZE" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "xmlimport" => wikiFuzz::chooseInput( array( "/var/www/hosts/mediawiki/wiki/AdminSettings.php", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "namespace" => wikiFuzz::chooseInput( array( wikiFuzz::randnum( 30, -6 ), wikiFuzz::makeFuzz( 2 ) ) ), + "interwiki" => wikiFuzz::makeFuzz( 2 ), + "interwikiHistory" => wikiFuzz::makeFuzz( 2 ), + "frompage" => wikiFuzz::makeFuzz( 2 ), + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(6) == 0) unset($this->params["action"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["source"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["MAX_FILE_SIZE"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["xmlimport"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["interwiki"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["interwikiHistory"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["frompage"]); + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["action"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["source"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["MAX_FILE_SIZE"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["xmlimport"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["interwiki"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["interwikiHistory"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["frompage"] ); - // Note: Need to do a file upload to fully test this Special page. - } + // Note: Need to do a file upload to fully test this Special page. + } } @@ -1776,20 +1777,20 @@ class specialImport extends pageTest { ** a test for thumb.php */ class thumbTest extends pageTest { - function __construct() { - $this->pagePath = "thumb.php"; + function __construct() { + $this->pagePath = "thumb.php"; - $this->params = array ( - "f" => wikiFuzz::chooseInput( array("..", "\\", "small-email.png", wikifuzz::makeFuzz(2)) ), - "w" => wikiFuzz::chooseInput( array("80", wikiFuzz::randnum(6000,-200), wikifuzz::makeFuzz(2)) ), - "r" => wikiFuzz::chooseInput( array("0", wikifuzz::makeFuzz(2)) ), - ); + $this->params = array ( + "f" => wikiFuzz::chooseInput( array( "..", "\\", "small-email.png", wikiFuzz::makeFuzz( 2 ) ) ), + "w" => wikiFuzz::chooseInput( array( "80", wikiFuzz::randnum( 6000, -200 ), wikiFuzz::makeFuzz( 2 ) ) ), + "r" => wikiFuzz::chooseInput( array( "0", wikiFuzz::makeFuzz( 2 ) ) ), + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(6) == 0) unset($this->params["f"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["w"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["r"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["f"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["w"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["r"] ); + } } @@ -1797,24 +1798,24 @@ class thumbTest extends pageTest { ** a test for trackback.php */ class trackbackTest extends pageTest { - function __construct() { - $this->pagePath = "trackback.php"; + function __construct() { + $this->pagePath = "trackback.php"; + + $this->params = array ( + "url" => wikiFuzz::makeFuzz( 2 ), + "blog_name" => wikiFuzz::chooseInput( array( "80", wikiFuzz::randnum( 6000, -200 ), wikiFuzz::makeFuzz( 2 ) ) ), + "article" => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ), + "title" => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ), + "excerpt" => wikiFuzz::makeFuzz( 2 ), + ); - $this->params = array ( - "url" => wikifuzz::makeFuzz(2), - "blog_name" => wikiFuzz::chooseInput( array("80", wikiFuzz::randnum(6000,-200), wikifuzz::makeFuzz(2)) ), - "article" => wikiFuzz::chooseInput( array("Main Page", wikifuzz::makeFuzz(2)) ), - "title" => wikiFuzz::chooseInput( array("Main Page", wikifuzz::makeFuzz(2)) ), - "excerpt" => wikifuzz::makeFuzz(2), - ); + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["title"] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["excerpt"] ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(3) == 0) unset($this->params["title"]); - if (wikiFuzz::randnum(3) == 0) unset($this->params["excerpt"]); - - // page does not produce HTML. - $this->tidyValidate = false; - } + // page does not produce HTML. + $this->tidyValidate = false; + } } @@ -1822,19 +1823,19 @@ class trackbackTest extends pageTest { ** a test for profileinfo.php */ class profileInfo extends pageTest { - function __construct() { - $this->pagePath = "profileinfo.php"; + function __construct() { + $this->pagePath = "profileinfo.php"; - $this->params = array ( - "expand" => wikifuzz::makeFuzz(2), - "sort" => wikiFuzz::chooseInput( array("time", "count", "name", wikifuzz::makeFuzz(2)) ), - "filter" => wikiFuzz::chooseInput( array("Main Page", wikifuzz::makeFuzz(2)) ), - ); + $this->params = array ( + "expand" => wikiFuzz::makeFuzz( 2 ), + "sort" => wikiFuzz::chooseInput( array( "time", "count", "name", wikiFuzz::makeFuzz( 2 ) ) ), + "filter" => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ), + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(3) == 0) unset($this->params["sort"]); - if (wikiFuzz::randnum(3) == 0) unset($this->params["filter"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["sort"] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["filter"] ); + } } @@ -1842,18 +1843,18 @@ class profileInfo extends pageTest { ** a test for Special:Cite (extension Special page). */ class specialCite extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Cite"; + function __construct() { + $this->pagePath = "index.php?title=Special:Cite"; - $this->params = array ( - "page" => wikiFuzz::chooseInput( array("\" onmouseover=\"alert(1);\"", "Main Page", wikifuzz::makeFuzz(2)) ), - "id" => wikiFuzz::chooseInput( array("-1", "0", "------'-------0", "+1", "-9823412312312412435", wikiFuzz::makeFuzz(2)) ), - ); + $this->params = array ( + "page" => wikiFuzz::chooseInput( array( "\" onmouseover=\"alert(1);\"", "Main Page", wikiFuzz::makeFuzz( 2 ) ) ), + "id" => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "-9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) ), + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(6) == 0) unset($this->params["page"]); - if (wikiFuzz::randnum(6) == 0) unset($this->params["id"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["page"] ); + if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["id"] ); + } } @@ -1861,13 +1862,13 @@ class specialCite extends pageTest { ** a test for Special:Filepath (extension Special page). */ class specialFilepath extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Filepath"; + function __construct() { + $this->pagePath = "index.php?title=Special:Filepath"; - $this->params = array ( - "file" => wikiFuzz::chooseInput( array("Small-email.png", "Small-email.png" . wikifuzz::makeFuzz(1), wikiFuzz::makeFuzz(2)) ), - ); - } + $this->params = array ( + "file" => wikiFuzz::chooseInput( array( "Small-email.png", "Small-email.png" . wikiFuzz::makeFuzz( 1 ), wikiFuzz::makeFuzz( 2 ) ) ), + ); + } } @@ -1875,22 +1876,22 @@ class specialFilepath extends pageTest { ** a test for Special:Makebot (extension Special page). */ class specialMakebot extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Makebot"; + function __construct() { + $this->pagePath = "index.php?title=Special:Makebot"; - $this->params = array ( - "username" => wikiFuzz::chooseInput( array("Nickj2", "192.168.0.2", wikifuzz::makeFuzz(1) ) ), - "dosearch" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikifuzz::makeFuzz(2)) ), - "grant" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikifuzz::makeFuzz(2)) ), - "comment" => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - "token" => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - ); + $this->params = array ( + "username" => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ), + "dosearch" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "grant" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "comment" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + "token" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(2) == 0) unset($this->params["dosearch"]); - if (wikiFuzz::randnum(2) == 0) unset($this->params["grant"]); - if (wikiFuzz::randnum(5) == 0) unset($this->params["token"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["dosearch"] ); + if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["grant"] ); + if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["token"] ); + } } @@ -1898,22 +1899,22 @@ class specialMakebot extends pageTest { ** a test for Special:Makesysop (extension Special page). */ class specialMakesysop extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Makesysop"; + function __construct() { + $this->pagePath = "index.php?title=Special:Makesysop"; - $this->params = array ( - "wpMakesysopUser" => wikiFuzz::chooseInput( array("Nickj2", "192.168.0.2", wikifuzz::makeFuzz(1) ) ), - "action" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikifuzz::makeFuzz(2)) ), - "wpMakesysopSubmit" => wikiFuzz::chooseInput( array("0", "1", "++--34234", wikifuzz::makeFuzz(2)) ), - "wpEditToken" => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - "wpSetBureaucrat" => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - ); + $this->params = array ( + "wpMakesysopUser" => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ), + "action" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "wpMakesysopSubmit" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ), + "wpEditToken" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + "wpSetBureaucrat" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(3) == 0) unset($this->params["wpMakesysopSubmit"]); - if (wikiFuzz::randnum(3) == 0) unset($this->params["wpEditToken"]); - if (wikiFuzz::randnum(3) == 0) unset($this->params["wpSetBureaucrat"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpMakesysopSubmit"] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpEditToken"] ); + if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpSetBureaucrat"] ); + } } @@ -1921,15 +1922,15 @@ class specialMakesysop extends pageTest { ** a test for Special:Renameuser (extension Special page). */ class specialRenameuser extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Renameuser"; + function __construct() { + $this->pagePath = "index.php?title=Special:Renameuser"; - $this->params = array ( - "oldusername" => wikiFuzz::chooseInput( array("Nickj2", "192.168.0.2", wikifuzz::makeFuzz(1) ) ), - "newusername" => wikiFuzz::chooseInput( array("Nickj2", "192.168.0.2", wikifuzz::makeFuzz(1) ) ), - "token" => wikiFuzz::chooseInput( array("20398702394", "", wikiFuzz::makeFuzz(2)) ), - ); - } + $this->params = array ( + "oldusername" => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ), + "newusername" => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ), + "token" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ), + ); + } } @@ -1937,16 +1938,16 @@ class specialRenameuser extends pageTest { ** a test for Special:Linksearch (extension Special page). */ class specialLinksearch extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special%3ALinksearch"; + function __construct() { + $this->pagePath = "index.php?title=Special%3ALinksearch"; - $this->params = array ( - "target" => wikifuzz::makeFuzz(2), - ); + $this->params = array ( + "target" => wikiFuzz::makeFuzz( 2 ), + ); - // sometimes we don't want to specify certain parameters. - if (wikiFuzz::randnum(10) == 0) unset($this->params["target"]); - } + // sometimes we don't want to specify certain parameters. + if ( wikiFuzz::randnum( 10 ) == 0 ) unset( $this->params["target"] ); + } } @@ -1954,20 +1955,20 @@ class specialLinksearch extends pageTest { ** a test for Special:CategoryTree (extension Special page). */ class specialCategoryTree extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:CategoryTree"; + function __construct() { + $this->pagePath = "index.php?title=Special:CategoryTree"; - $this->params = array ( - "target" => wikifuzz::makeFuzz(2), - "from" => wikifuzz::makeFuzz(2), - "until" => wikifuzz::makeFuzz(2), - "showas" => wikifuzz::makeFuzz(2), - "mode" => wikiFuzz::chooseInput( array("pages", "categories", "all", wikifuzz::makeFuzz(2)) ), - ); + $this->params = array ( + "target" => wikiFuzz::makeFuzz( 2 ), + "from" => wikiFuzz::makeFuzz( 2 ), + "until" => wikiFuzz::makeFuzz( 2 ), + "showas" => wikiFuzz::makeFuzz( 2 ), + "mode" => wikiFuzz::chooseInput( array( "pages", "categories", "all", wikiFuzz::makeFuzz( 2 ) ) ), + ); - // sometimes we do want to specify certain parameters. - if (wikiFuzz::randnum(5) == 0) $this->params["notree"] = wikiFuzz::chooseInput( array("1", 0, "", wikiFuzz::makeFuzz(2)) ); - } + // sometimes we do want to specify certain parameters. + if ( wikiFuzz::randnum( 5 ) == 0 ) $this->params["notree"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) ); + } } @@ -1975,40 +1976,40 @@ class specialCategoryTree extends pageTest { ** a test for "Special:Chemicalsources" (extension Special page). */ class specialChemicalsourcesTest extends pageTest { - function __construct() { - $this->pagePath = "index.php?title=Special:Chemicalsources"; - - // choose an input format to use. - $format = wikiFuzz::chooseInput( - array( 'go', - 'CAS', - 'EINECS', - 'CHEBI', - 'PubChem', - 'SMILES', - 'InChI', - 'ATCCode', - 'KEGG', - 'RTECS', - 'ECNumber', - 'DrugBank', - 'Formula', - 'Name' - ) - ); - - // values for different formats usually start with either letters or numbers. - switch ($format) { - case 'Name' : $value = "A"; break; - case 'InChI' : - case 'SMILES' : - case 'Formula': $value = "C"; break; - default : $value = "0"; break; - } - - // and then we append the fuzz input. - $this->params = array ($format => $value . wikifuzz::makeFuzz(2) ); - } + function __construct() { + $this->pagePath = "index.php?title=Special:Chemicalsources"; + + // choose an input format to use. + $format = wikiFuzz::chooseInput( + array( 'go', + 'CAS', + 'EINECS', + 'CHEBI', + 'PubChem', + 'SMILES', + 'InChI', + 'ATCCode', + 'KEGG', + 'RTECS', + 'ECNumber', + 'DrugBank', + 'Formula', + 'Name' + ) + ); + + // values for different formats usually start with either letters or numbers. + switch ( $format ) { + case 'Name' : $value = "A"; break; + case 'InChI' : + case 'SMILES' : + case 'Formula': $value = "C"; break; + default : $value = "0"; break; + } + + // and then we append the fuzz input. + $this->params = array ( $format => $value . wikiFuzz::makeFuzz( 2 ) ); + } } @@ -2023,143 +2024,143 @@ class specialChemicalsourcesTest extends pageTest { */ class api extends pageTest { - // API login mode. - private static function loginMode() { - $arr = array ( "lgname" => wikifuzz::makeFuzz(2), - "lgpassword" => wikifuzz::makeFuzz(2), - ); - // sometimes we want to specify the extra "lgdomain" parameter. - if (wikiFuzz::randnum(3) == 0) { - $arr["lgdomain"] = wikiFuzz::chooseInput( array("1", 0, "", wikiFuzz::makeFuzz(2)) ); - } - - return $arr; - } - - // API OpenSearch mode. - private static function opensearchMode() { - return array ("search" => wikifuzz::makeFuzz(2)); - } - - // API watchlist feed mode. - private static function feedwatchlistMode() { - // FIXME: add "wikifuzz::makeFuzz(2)" as possible value below? - return array ("feedformat" => wikiFuzz::chooseInput( array("rss", "atom") ) ); - } - - // API query mode. - private static function queryMode() { - // FIXME: add "wikifuzz::makeFuzz(2)" as possible params for the elements below? - // Suspect this will stuff up the tests more, but need to check. - $params = array ( - // FIXME: More titles. - "titles" => wikiFuzz::chooseInput( array("Main Page")), - // FIXME: More pageids. - "pageids" => 1, - "prop" => wikiFuzz::chooseInput( array("info", "revisions", "watchlist")), - "list" => wikiFuzz::chooseInput( array("allpages", "logevents", "watchlist", "usercontribs", "recentchanges", "backlinks", "embeddedin", "imagelinks") ), - "meta" => wikiFuzz::chooseInput( array("siteinfo")), - "generator" => wikiFuzz::chooseInput( array("allpages", "logevents", "watchlist", "info", "revisions") ), - "siprop" => wikiFuzz::chooseInput( array("general", "namespaces", "general|namespaces") ), - ); - - // Add extra parameters based on what list choice we got. - switch ($params["list"]) { - case "usercontribs" : self::addListParams ($params, "uc", array("limit", "start", "end", "user", "dir") ); break; - case "allpages" : self::addListParams ($params, "ap", array("from", "prefix", "namespace", "filterredir", "limit") ); break; - case "watchlist" : self::addListParams ($params, "wl", array("allrev", "start", "end", "namespace", "dir", "limit", "prop") ); break; - case "logevents" : self::addListParams ($params, "le", array("limit", "type", "start", "end", "user", "dir") ); break; - case "recentchanges": self::addListParams ($params, "rc", array("limit", "prop", "show", "namespace", "start", "end", "dir") ); break; - case "backlinks" : self::addListParams ($params, "bl", array("continue", "namespace", "redirect", "limit") ); break; - case "embeddedin" : self::addListParams ($params, "ei", array("continue", "namespace", "redirect", "limit") ); break; - case "imagelinks" : self::addListParams ($params, "il", array("continue", "namespace", "redirect", "limit") ); break; - } - - if ($params["prop"] == "revisions") { - self::addListParams ($params, "rv", array("prop", "limit", "startid", "endid", "end", "dir") ); - } - - // Sometimes we want redirects, sometimes we don't. - if (wikiFuzz::randnum(3) == 0) { - $params["redirects"] = wikiFuzz::chooseInput( array("1", 0, "", wikiFuzz::makeFuzz(2)) ); - } - - return $params; - } - - // Adds all the elements to the array, using the specified prefix. - private static function addListParams(&$array, $prefix, $elements) { - foreach ($elements as $element) { - $array[$prefix . $element] = self::getParamDetails($element); - } - } - - // For a given element name, returns the data for that element. - private static function getParamDetails($element) { - switch ($element) { - case 'startid' : - case 'endid' : - case 'start' : - case 'end' : - case 'limit' : return wikiFuzz::chooseInput( array("0", "-1", "---'----------0", "+1", "8134", "320742734234235", "20060230121212", wikiFuzz::randnum(9000, -100), wikiFuzz::makeFuzz(2)) ); - case 'dir' : return wikiFuzz::chooseInput( array("newer", "older", wikifuzz::makeFuzz(2) ) ); - case 'user' : return wikiFuzz::chooseInput( array(USER_ON_WIKI, wikifuzz::makeFuzz(2) ) ); - case 'namespace' : return wikiFuzz::chooseInput( array(-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 200000, wikifuzz::makeFuzz(2)) ); - case 'filterredir': return wikiFuzz::chooseInput( array("all", "redirects", "nonredirectsallpages", wikifuzz::makeFuzz(2)) ); - case 'allrev' : return wikiFuzz::chooseInput( array("1", 0, "", wikiFuzz::makeFuzz(2)) ); - case 'prop' : return wikiFuzz::chooseInput( array("user", "comment", "timestamp", "patrol", "flags", "user|user|comment|flags", wikifuzz::makeFuzz(2) ) ); - case 'type' : return wikiFuzz::chooseInput( array("block", "protect", "rights", "delete", "upload", "move", "import", "renameuser", "newusers", "makebot", wikifuzz::makeFuzz(2) ) ); - case 'hide' : return wikiFuzz::chooseInput( array("minor", "bots", "anons", "liu", "liu|bots|", wikifuzz::makeFuzz(2) ) ); - case 'show' : return wikiFuzz::chooseInput( array('minor', '!minor', 'bot', '!bot', 'anon', '!anon', wikifuzz::makeFuzz(2) ) ); - default : return wikifuzz::makeFuzz(2); - } - } - - // Entry point. - function __construct() { - $this->pagePath = "api.php"; - - $modes = array ("help", - "login", - "opensearch", - "feedwatchlist", - "query"); - $action = wikiFuzz::chooseInput( array_merge ($modes, array(wikifuzz::makeFuzz(2))) ); - - switch ($action) { - case "login" : $this->params = self::loginMode(); - break; - case "opensearch" : $this->params = self::opensearchMode(); - break; - case "feedwatchlist" : $this->params = self::feedwatchlistMode(); - break; - case "query" : $this->params = self::queryMode(); - break; - case "help" : - default : // Do something random - "Crazy Ivan" mode. - $random_mode = wikiFuzz::chooseInput( $modes ) . "Mode"; - // There is no "helpMode". - if ($random_mode == "helpMode") $random_mode = "queryMode"; - $this->params = self::$random_mode(); - break; - } - - // Save the selected action. - $this->params["action"] = $action; - - // Set the cookie: - // FIXME: need to get this cookie dynamically set, rather than hard-coded. - $this->cookie = "wikidbUserID=10001; wikidbUserName=Test; wikidb_session=178df0fe68c75834643af65dec9ec98a; wikidbToken=1adc6753d62c44aec950c024d7ae0540"; - - // Output format - $this->params["format"] = wikiFuzz::chooseInput( array("json", "jsonfm", "php", "phpfm", - "wddx", "wddxfm", "xml", "xmlfm", - "yaml", "yamlfm", "raw", "rawfm", - wikifuzz::makeFuzz(2) ) ); - - // Page does not produce HTML (sometimes). - $this->tidyValidate = false; - } + // API login mode. + private static function loginMode() { + $arr = array ( "lgname" => wikiFuzz::makeFuzz( 2 ), + "lgpassword" => wikiFuzz::makeFuzz( 2 ), + ); + // sometimes we want to specify the extra "lgdomain" parameter. + if ( wikiFuzz::randnum( 3 ) == 0 ) { + $arr["lgdomain"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) ); + } + + return $arr; + } + + // API OpenSearch mode. + private static function opensearchMode() { + return array ( "search" => wikiFuzz::makeFuzz( 2 ) ); + } + + // API watchlist feed mode. + private static function feedwatchlistMode() { + // FIXME: add "wikiFuzz::makeFuzz(2)" as possible value below? + return array ( "feedformat" => wikiFuzz::chooseInput( array( "rss", "atom" ) ) ); + } + + // API query mode. + private static function queryMode() { + // FIXME: add "wikiFuzz::makeFuzz(2)" as possible params for the elements below? + // Suspect this will stuff up the tests more, but need to check. + $params = array ( + // FIXME: More titles. + "titles" => wikiFuzz::chooseInput( array( "Main Page" ) ), + // FIXME: More pageids. + "pageids" => 1, + "prop" => wikiFuzz::chooseInput( array( "info", "revisions", "watchlist" ) ), + "list" => wikiFuzz::chooseInput( array( "allpages", "logevents", "watchlist", "usercontribs", "recentchanges", "backlinks", "embeddedin", "imagelinks" ) ), + "meta" => wikiFuzz::chooseInput( array( "siteinfo" ) ), + "generator" => wikiFuzz::chooseInput( array( "allpages", "logevents", "watchlist", "info", "revisions" ) ), + "siprop" => wikiFuzz::chooseInput( array( "general", "namespaces", "general|namespaces" ) ), + ); + + // Add extra parameters based on what list choice we got. + switch ( $params["list"] ) { + case "usercontribs" : self::addListParams ( $params, "uc", array( "limit", "start", "end", "user", "dir" ) ); break; + case "allpages" : self::addListParams ( $params, "ap", array( "from", "prefix", "namespace", "filterredir", "limit" ) ); break; + case "watchlist" : self::addListParams ( $params, "wl", array( "allrev", "start", "end", "namespace", "dir", "limit", "prop" ) ); break; + case "logevents" : self::addListParams ( $params, "le", array( "limit", "type", "start", "end", "user", "dir" ) ); break; + case "recentchanges": self::addListParams ( $params, "rc", array( "limit", "prop", "show", "namespace", "start", "end", "dir" ) ); break; + case "backlinks" : self::addListParams ( $params, "bl", array( "continue", "namespace", "redirect", "limit" ) ); break; + case "embeddedin" : self::addListParams ( $params, "ei", array( "continue", "namespace", "redirect", "limit" ) ); break; + case "imagelinks" : self::addListParams ( $params, "il", array( "continue", "namespace", "redirect", "limit" ) ); break; + } + + if ( $params["prop"] == "revisions" ) { + self::addListParams ( $params, "rv", array( "prop", "limit", "startid", "endid", "end", "dir" ) ); + } + + // Sometimes we want redirects, sometimes we don't. + if ( wikiFuzz::randnum( 3 ) == 0 ) { + $params["redirects"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) ); + } + + return $params; + } + + // Adds all the elements to the array, using the specified prefix. + private static function addListParams( &$array, $prefix, $elements ) { + foreach ( $elements as $element ) { + $array[$prefix . $element] = self::getParamDetails( $element ); + } + } + + // For a given element name, returns the data for that element. + private static function getParamDetails( $element ) { + switch ( $element ) { + case 'startid' : + case 'endid' : + case 'start' : + case 'end' : + case 'limit' : return wikiFuzz::chooseInput( array( "0", "-1", "---'----------0", "+1", "8134", "320742734234235", "20060230121212", wikiFuzz::randnum( 9000, -100 ), wikiFuzz::makeFuzz( 2 ) ) ); + case 'dir' : return wikiFuzz::chooseInput( array( "newer", "older", wikiFuzz::makeFuzz( 2 ) ) ); + case 'user' : return wikiFuzz::chooseInput( array( USER_ON_WIKI, wikiFuzz::makeFuzz( 2 ) ) ); + case 'namespace' : return wikiFuzz::chooseInput( array( -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 200000, wikiFuzz::makeFuzz( 2 ) ) ); + case 'filterredir': return wikiFuzz::chooseInput( array( "all", "redirects", "nonredirectsallpages", wikiFuzz::makeFuzz( 2 ) ) ); + case 'allrev' : return wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) ); + case 'prop' : return wikiFuzz::chooseInput( array( "user", "comment", "timestamp", "patrol", "flags", "user|user|comment|flags", wikiFuzz::makeFuzz( 2 ) ) ); + case 'type' : return wikiFuzz::chooseInput( array( "block", "protect", "rights", "delete", "upload", "move", "import", "renameuser", "newusers", "makebot", wikiFuzz::makeFuzz( 2 ) ) ); + case 'hide' : return wikiFuzz::chooseInput( array( "minor", "bots", "anons", "liu", "liu|bots|", wikiFuzz::makeFuzz( 2 ) ) ); + case 'show' : return wikiFuzz::chooseInput( array( 'minor', '!minor', 'bot', '!bot', 'anon', '!anon', wikiFuzz::makeFuzz( 2 ) ) ); + default : return wikiFuzz::makeFuzz( 2 ); + } + } + + // Entry point. + function __construct() { + $this->pagePath = "api.php"; + + $modes = array ( "help", + "login", + "opensearch", + "feedwatchlist", + "query" ); + $action = wikiFuzz::chooseInput( array_merge ( $modes, array( wikiFuzz::makeFuzz( 2 ) ) ) ); + + switch ( $action ) { + case "login" : $this->params = self::loginMode(); + break; + case "opensearch" : $this->params = self::opensearchMode(); + break; + case "feedwatchlist" : $this->params = self::feedwatchlistMode(); + break; + case "query" : $this->params = self::queryMode(); + break; + case "help" : + default : // Do something random - "Crazy Ivan" mode. + $random_mode = wikiFuzz::chooseInput( $modes ) . "Mode"; + // There is no "helpMode". + if ( $random_mode == "helpMode" ) $random_mode = "queryMode"; + $this->params = self::$random_mode(); + break; + } + + // Save the selected action. + $this->params["action"] = $action; + + // Set the cookie: + // FIXME: need to get this cookie dynamically set, rather than hard-coded. + $this->cookie = "wikidbUserID=10001; wikidbUserName=Test; wikidb_session=178df0fe68c75834643af65dec9ec98a; wikidbToken=1adc6753d62c44aec950c024d7ae0540"; + + // Output format + $this->params["format"] = wikiFuzz::chooseInput( array( "json", "jsonfm", "php", "phpfm", + "wddx", "wddxfm", "xml", "xmlfm", + "yaml", "yamlfm", "raw", "rawfm", + wikiFuzz::makeFuzz( 2 ) ) ); + + // Page does not produce HTML (sometimes). + $this->tidyValidate = false; + } } @@ -2167,152 +2168,152 @@ class api extends pageTest { ** a page test for the GeSHi extension. */ class GeSHi_Test extends pageTest { - - private function getGeSHiContent() { - return "<source lang=\"" . $this->getLang() . "\" " - . (wikiFuzz::randnum(2) == 0 ? "line " : "") - . (wikiFuzz::randnum(2) == 0 ? "strict " : "") - . "start=" . wikiFuzz::chooseInput( array(wikiFuzz::randnum(-6000,6000), wikifuzz::makeFuzz(2)) ) - . ">" - . wikiFuzz::makeFuzz(2) - . "</source>"; - } - - private function getLang() { + + private function getGeSHiContent() { + return "<source lang=\"" . $this->getLang() . "\" " + . ( wikiFuzz::randnum( 2 ) == 0 ? "line " : "" ) + . ( wikiFuzz::randnum( 2 ) == 0 ? "strict " : "" ) + . "start=" . wikiFuzz::chooseInput( array( wikiFuzz::randnum( -6000, 6000 ), wikiFuzz::makeFuzz( 2 ) ) ) + . ">" + . wikiFuzz::makeFuzz( 2 ) + . "</source>"; + } + + private function getLang() { return wikiFuzz::chooseInput( array( "actionscript", "ada", "apache", "applescript", "asm", "asp", "autoit", "bash", "blitzbasic", "bnf", "c", "c_mac", "caddcl", "cadlisp", - "cfdg", "cfm", "cpp", "cpp-qt", "csharp", "css", "d", "delphi", "diff", "div", "dos", "eiffel", "fortran", "freebasic", "gml", "groovy", "html4strict", "idl", - "ini", "inno", "io", "java", "java5", "javascript", "latex", "lisp", "lua", "matlab", "mirc", "mpasm", "mysql", "nsis", "objc", "ocaml", "ocaml-brief", "oobas", - "oracle8", "pascal", "perl", "php", "php-brief", "plsql", "python", "qbasic", "rails", "reg", "robots", "ruby", "sas", "scheme", "sdlbasic", "smalltalk", "smarty", - "sql", "tcl", "text", "thinbasic", "tsql", "vb", "vbnet", "vhdl", "visualfoxpro", "winbatch", "xml", "xpp", "z80", wikifuzz::makeFuzz(1) ) ); - } - - function __construct() { - $this->pagePath = "index.php?title=WIKIFUZZ"; - - $this->params = array ( - "action" => "submit", - "wpMinoredit" => "test", - "wpPreview" => "test", - "wpSection" => "test", - "wpEdittime" => "test", - "wpSummary" => "test", - "wpScrolltop" => "test", - "wpStarttime" => "test", - "wpAutoSummary" => "test", - "wpTextbox1" => $this->getGeSHiContent() // the main wiki text, contains fake GeSHi content. - ); - } + "cfdg", "cfm", "cpp", "cpp-qt", "csharp", "css", "d", "delphi", "diff", "div", "dos", "eiffel", "fortran", "freebasic", "gml", "groovy", "html4strict", "idl", + "ini", "inno", "io", "java", "java5", "javascript", "latex", "lisp", "lua", "matlab", "mirc", "mpasm", "mysql", "nsis", "objc", "ocaml", "ocaml-brief", "oobas", + "oracle8", "pascal", "perl", "php", "php-brief", "plsql", "python", "qbasic", "rails", "reg", "robots", "ruby", "sas", "scheme", "sdlbasic", "smalltalk", "smarty", + "sql", "tcl", "text", "thinbasic", "tsql", "vb", "vbnet", "vhdl", "visualfoxpro", "winbatch", "xml", "xpp", "z80", wikiFuzz::makeFuzz( 1 ) ) ); + } + + function __construct() { + $this->pagePath = "index.php?title=WIKIFUZZ"; + + $this->params = array ( + "action" => "submit", + "wpMinoredit" => "test", + "wpPreview" => "test", + "wpSection" => "test", + "wpEdittime" => "test", + "wpSummary" => "test", + "wpScrolltop" => "test", + "wpStarttime" => "test", + "wpAutoSummary" => "test", + "wpTextbox1" => $this->getGeSHiContent() // the main wiki text, contains fake GeSHi content. + ); + } } /** ** selects a page test to run. */ -function selectPageTest($count) { - - // if the user only wants a specific test, then only ever give them that. - if (defined("SPECIFIC_TEST")) { - $testType = SPECIFIC_TEST; - return new $testType (); - } - - // Some of the time we test Special pages, the remaining - // time we test using the standard edit page. - switch ($count % 100) { - case 0 : return new successfulUserLoginTest(); - case 1 : return new listusersTest(); - case 2 : return new searchTest(); - case 3 : return new recentchangesTest(); - case 4 : return new prefixindexTest(); - case 5 : return new mimeSearchTest(); - case 6 : return new specialLogTest(); - case 7 : return new userLoginTest(); - case 8 : return new ipblocklistTest(); - case 9 : return new newImagesTest(); - case 10: return new imagelistTest(); - case 11: return new specialExportTest(); - case 12: return new specialBooksourcesTest(); - case 13: return new specialAllpagesTest(); - case 14: return new pageHistoryTest(); - case 15: return new contributionsTest(); - case 16: return new viewPageTest(); - case 17: return new specialAllmessagesTest(); - case 18: return new specialNewpages(); - case 19: return new searchTest(); - case 20: return new redirectTest(); - case 21: return new confirmEmail(); - case 22: return new watchlistTest(); - case 23: return new specialBlockmeTest(); - case 24: return new specialUndelete(); - case 25: return new specialMovePage(); - case 26: return new specialUnlockdb(); - case 27: return new specialLockdb(); - case 28: return new specialUserrights(); - case 29: return new pageProtectionForm(); - case 30: return new specialBlockip(); - case 31: return new imagepageTest(); - case 32: return new pageDeletion(); - case 33: return new specialRevisionDelete(); - case 34: return new specialImport(); - case 35: return new thumbTest(); - case 36: return new trackbackTest(); - case 37: return new profileInfo(); - case 38: return new specialCite(); - case 39: return new specialFilepath(); - case 40: return new specialMakebot(); - case 41: return new specialMakesysop(); - case 42: return new specialRenameuser(); - case 43: return new specialLinksearch(); - case 44: return new specialCategoryTree(); - case 45: return new api(); - case 45: return new specialChemicalsourcesTest(); - default: return new editPageTest(); - } -} - - -/////////////////////// SAVING OUTPUT ///////////////////////// +function selectPageTest( $count ) { + + // if the user only wants a specific test, then only ever give them that. + if ( defined( "SPECIFIC_TEST" ) ) { + $testType = SPECIFIC_TEST; + return new $testType (); + } + + // Some of the time we test Special pages, the remaining + // time we test using the standard edit page. + switch ( $count % 100 ) { + case 0 : return new successfulUserLoginTest(); + case 1 : return new listusersTest(); + case 2 : return new searchTest(); + case 3 : return new recentchangesTest(); + case 4 : return new prefixindexTest(); + case 5 : return new mimeSearchTest(); + case 6 : return new specialLogTest(); + case 7 : return new userLoginTest(); + case 8 : return new ipblocklistTest(); + case 9 : return new newImagesTest(); + case 10: return new imagelistTest(); + case 11: return new specialExportTest(); + case 12: return new specialBooksourcesTest(); + case 13: return new specialAllpagesTest(); + case 14: return new pageHistoryTest(); + case 15: return new contributionsTest(); + case 16: return new viewPageTest(); + case 17: return new specialAllmessagesTest(); + case 18: return new specialNewpages(); + case 19: return new searchTest(); + case 20: return new redirectTest(); + case 21: return new confirmEmail(); + case 22: return new watchlistTest(); + case 23: return new specialBlockmeTest(); + case 24: return new specialUndelete(); + case 25: return new specialMovePage(); + case 26: return new specialUnlockdb(); + case 27: return new specialLockdb(); + case 28: return new specialUserrights(); + case 29: return new pageProtectionForm(); + case 30: return new specialBlockip(); + case 31: return new imagepageTest(); + case 32: return new pageDeletion(); + case 33: return new specialRevisionDelete(); + case 34: return new specialImport(); + case 35: return new thumbTest(); + case 36: return new trackbackTest(); + case 37: return new profileInfo(); + case 38: return new specialCite(); + case 39: return new specialFilepath(); + case 40: return new specialMakebot(); + case 41: return new specialMakesysop(); + case 42: return new specialRenameuser(); + case 43: return new specialLinksearch(); + case 44: return new specialCategoryTree(); + case 45: return new api(); + case 45: return new specialChemicalsourcesTest(); + default: return new editPageTest(); + } +} + + +// ///////////////////// SAVING OUTPUT ///////////////////////// /** ** Utility function for saving a file. Currently has no error checking. */ -function saveFile($data, $name) { - file_put_contents($name, $data); +function saveFile( $data, $name ) { + file_put_contents( $name, $data ); } /** ** Returns a test as an experimental GET-to-POST URL. - ** This doesn't seem to always work though, and sometimes the output is too long + ** This doesn't seem to always work though, and sometimes the output is too long ** to be a valid GET URL, so we also save in other formats. */ -function getAsURL(pageTest $test) { - $used_question_mark = (strpos($test->getPagePath(), "?") !== false); - $retval = "http://get-to-post.nickj.org/?" . WIKI_BASE_URL . $test->getPagePath(); - foreach ($test->getParams() as $param => $value) { - if (!$used_question_mark) { - $retval .= "?"; - $used_question_mark = true; - } - else { - $retval .= "&"; - } - $retval .= $param . "=" . urlencode($value); - } - return $retval; +function getAsURL( pageTest $test ) { + $used_question_mark = ( strpos( $test->getPagePath(), "?" ) !== false ); + $retval = "http://get-to-post.nickj.org/?" . WIKI_BASE_URL . $test->getPagePath(); + foreach ( $test->getParams() as $param => $value ) { + if ( !$used_question_mark ) { + $retval .= "?"; + $used_question_mark = true; + } + else { + $retval .= "&"; + } + $retval .= $param . "=" . urlencode( $value ); + } + return $retval; } /** ** Saves a plain-text human-readable version of a test. */ -function saveTestAsText(pageTest $test, $filename) { - $str = "Test: " . $test->getPagePath(); - foreach ($test->getParams() as $param => $value) { - $str .= "\n$param: $value"; - } - $str .= "\nGet-to-post URL: " . getAsURL($test) . "\n"; - saveFile($str, $filename); +function saveTestAsText( pageTest $test, $filename ) { + $str = "Test: " . $test->getPagePath(); + foreach ( $test->getParams() as $param => $value ) { + $str .= "\n$param: $value"; + } + $str .= "\nGet-to-post URL: " . getAsURL( $test ) . "\n"; + saveFile( $str, $filename ); } @@ -2320,37 +2321,37 @@ function saveTestAsText(pageTest $test, $filename) { ** Saves a test as a standalone basic PHP script that shows this one problem. ** Resulting script requires PHP-Curl be installed in order to work. */ -function saveTestAsPHP(pageTest $test, $filename) { - $str = "<?php\n" - . "\$params = " . var_export(escapeForCurl($test->getParams()), true) . ";\n" - . "\$ch = curl_init();\n" - . "curl_setopt(\$ch, CURLOPT_POST, 1);\n" - . "curl_setopt(\$ch, CURLOPT_POSTFIELDS, \$params );\n" - . "curl_setopt(\$ch, CURLOPT_URL, " . var_export(WIKI_BASE_URL . $test->getPagePath(), true) . ");\n" - . "curl_setopt(\$ch, CURLOPT_RETURNTRANSFER,1);\n" - . ($test->getCookie() ? "curl_setopt(\$ch, CURLOPT_COOKIE, " . var_export($test->getCookie(), true) . ");\n" : "") - . "\$result=curl_exec(\$ch);\n" - . "curl_close (\$ch);\n" - . "print \$result;\n" - . "?>\n"; - saveFile($str, $filename); +function saveTestAsPHP( pageTest $test, $filename ) { + $str = "<?php\n" + . "\$params = " . var_export( escapeForCurl( $test->getParams() ), true ) . ";\n" + . "\$ch = curl_init();\n" + . "curl_setopt(\$ch, CURLOPT_POST, 1);\n" + . "curl_setopt(\$ch, CURLOPT_POSTFIELDS, \$params );\n" + . "curl_setopt(\$ch, CURLOPT_URL, " . var_export( WIKI_BASE_URL . $test->getPagePath(), true ) . ");\n" + . "curl_setopt(\$ch, CURLOPT_RETURNTRANSFER,1);\n" + . ( $test->getCookie() ? "curl_setopt(\$ch, CURLOPT_COOKIE, " . var_export( $test->getCookie(), true ) . ");\n" : "" ) + . "\$result=curl_exec(\$ch);\n" + . "curl_close (\$ch);\n" + . "print \$result;\n" + . "?>\n"; + saveFile( $str, $filename ); } /** ** Escapes a value so that it can be used on the command line by Curl. - ** Specifically, "<" and "@" need to be escaped if they are the first character, + ** Specifically, "<" and "@" need to be escaped if they are the first character, ** otherwise curl interprets these as meaning that we want to insert a file. */ -function escapeForCurl(array $input_params) { - $output_params = array(); - foreach ($input_params as $param => $value) { - if (strlen($value) > 0 && ( $value[0] == "@" || $value[0] == "<")) { - $value = "\\" . $value; - } - $output_params[$param] = $value; - } - return $output_params; +function escapeForCurl( array $input_params ) { + $output_params = array(); + foreach ( $input_params as $param => $value ) { + if ( strlen( $value ) > 0 && ( $value[0] == "@" || $value[0] == "<" ) ) { + $value = "\\" . $value; + } + $output_params[$param] = $value; + } + return $output_params; } @@ -2358,124 +2359,124 @@ function escapeForCurl(array $input_params) { ** Saves a test as a standalone CURL shell script that shows this one problem. ** Resulting script requires standalone Curl be installed in order to work. */ -function saveTestAsCurl(pageTest $test, $filename) { - $str = "#!/bin/bash\n" - . "curl --silent --include --globoff \\\n" - . ($test->getCookie() ? " --cookie " . escapeshellarg($test->getCookie()) . " \\\n" : ""); - foreach (escapeForCurl($test->getParams()) as $param => $value) { - $str .= " -F " . escapeshellarg($param) . "=" . escapeshellarg($value) . " \\\n"; - } - $str .= " " . escapeshellarg(WIKI_BASE_URL . $test->getPagePath()); // beginning space matters. - $str .= "\n"; - saveFile($str, $filename); - chmod($filename, 0755); // make executable +function saveTestAsCurl( pageTest $test, $filename ) { + $str = "#!/bin/bash\n" + . "curl --silent --include --globoff \\\n" + . ( $test->getCookie() ? " --cookie " . escapeshellarg( $test->getCookie() ) . " \\\n" : "" ); + foreach ( escapeForCurl( $test->getParams() ) as $param => $value ) { + $str .= " -F " . escapeshellarg( $param ) . "=" . escapeshellarg( $value ) . " \\\n"; + } + $str .= " " . escapeshellarg( WIKI_BASE_URL . $test->getPagePath() ); // beginning space matters. + $str .= "\n"; + saveFile( $str, $filename ); + chmod( $filename, 0755 ); // make executable } /** ** Saves the internal data structure to file. */ -function saveTestData (pageTest $test, $filename) { - saveFile(serialize($test), $filename); +function saveTestData ( pageTest $test, $filename ) { + saveFile( serialize( $test ), $filename ); } /** ** saves a test in the various formats. */ -function saveTest(pageTest $test, $testname) { - $base_name = DIRECTORY . "/" . $testname; - saveTestAsText($test, $base_name . INFO_FILE); - saveTestAsPHP ($test, $base_name . PHP_TEST ); - saveTestAsCurl($test, $base_name . CURL_TEST); - saveTestData ($test, $base_name . DATA_FILE); +function saveTest( pageTest $test, $testname ) { + $base_name = DIRECTORY . "/" . $testname; + saveTestAsText( $test, $base_name . INFO_FILE ); + saveTestAsPHP ( $test, $base_name . PHP_TEST ); + saveTestAsCurl( $test, $base_name . CURL_TEST ); + saveTestData ( $test, $base_name . DATA_FILE ); } -//////////////////// MEDIAWIKI OUTPUT ///////////////////////// +// ////////////////// MEDIAWIKI OUTPUT ///////////////////////// /** ** Asks MediaWiki for the HTML output of a test. */ -function wikiTestOutput(pageTest $test) { +function wikiTestOutput( pageTest $test ) { - $ch = curl_init(); + $ch = curl_init(); - // specify the cookie, if required. - if ($test->getCookie()) curl_setopt($ch, CURLOPT_COOKIE, $test->getCookie()); - curl_setopt($ch, CURLOPT_POST, 1); // save form using a POST + // specify the cookie, if required. + if ( $test->getCookie() ) curl_setopt( $ch, CURLOPT_COOKIE, $test->getCookie() ); + curl_setopt( $ch, CURLOPT_POST, 1 ); // save form using a POST - $params = escapeForCurl($test->getParams()); - curl_setopt($ch, CURLOPT_POSTFIELDS, $params ); // load the POST variables + $params = escapeForCurl( $test->getParams() ); + curl_setopt( $ch, CURLOPT_POSTFIELDS, $params ); // load the POST variables - curl_setopt($ch, CURLOPT_URL, WIKI_BASE_URL . $test->getPagePath() ); // set url to post to - curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // return into a variable + curl_setopt( $ch, CURLOPT_URL, WIKI_BASE_URL . $test->getPagePath() ); // set url to post to + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); // return into a variable - $result=curl_exec ($ch); + $result = curl_exec ( $ch ); - // if we encountered an error, then say so, and return an empty string. - if (curl_error($ch)) { - print "\nCurl error #: " . curl_errno($ch) . " - " . curl_error ($ch); - $result = ""; - } + // if we encountered an error, then say so, and return an empty string. + if ( curl_error( $ch ) ) { + print "\nCurl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch ); + $result = ""; + } - curl_close ($ch); + curl_close ( $ch ); - return $result; + return $result; } -//////////////////// HTML VALIDATION ///////////////////////// +// ////////////////// HTML VALIDATION ///////////////////////// /* ** Asks the validator whether this is valid HTML, or not. */ -function validateHTML($text) { +function validateHTML( $text ) { - $params = array ("fragment" => $text); + $params = array ( "fragment" => $text ); - $ch = curl_init(); + $ch = curl_init(); - curl_setopt($ch, CURLOPT_POST, 1); // save form using a POST - curl_setopt($ch, CURLOPT_POSTFIELDS, $params); // load the POST variables - curl_setopt($ch, CURLOPT_URL, VALIDATOR_URL); // set url to post to - curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // return into a variable + curl_setopt( $ch, CURLOPT_POST, 1 ); // save form using a POST + curl_setopt( $ch, CURLOPT_POSTFIELDS, $params ); // load the POST variables + curl_setopt( $ch, CURLOPT_URL, VALIDATOR_URL ); // set url to post to + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); // return into a variable - $result=curl_exec ($ch); + $result = curl_exec ( $ch ); - // if we encountered an error, then log it, and exit. - if (curl_error($ch)) { - trigger_error("Curl error #: " . curl_errno($ch) . " - " . curl_error ($ch) ); - print "Curl error #: " . curl_errno($ch) . " - " . curl_error ($ch) . " - exiting.\n"; - exit(1); - } + // if we encountered an error, then log it, and exit. + if ( curl_error( $ch ) ) { + trigger_error( "Curl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch ) ); + print "Curl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch ) . " - exiting.\n"; + exit( 1 ); + } - curl_close ($ch); + curl_close ( $ch ); - $valid = (strpos($result, "Failed validation") === false ? true : false); + $valid = ( strpos( $result, "Failed validation" ) === false ? true : false ); - return array($valid, $result); + return array( $valid, $result ); } /** ** Get tidy to check for no HTML errors in the output file (e.g. unescaped strings). */ -function tidyCheckFile($name) { - $file = DIRECTORY . "/" . $name; - $command = PATH_TO_TIDY . " -output /tmp/out.html -quiet $file 2>&1"; - $x = `$command`; +function tidyCheckFile( $name ) { + $file = DIRECTORY . "/" . $name; + $command = PATH_TO_TIDY . " -output /tmp/out.html -quiet $file 2>&1"; + $x = `$command`; - // Look for the most interesting Tidy errors and warnings. - if ( strpos($x,"end of file while parsing attributes") !== false - || strpos($x,"attribute with missing trailing quote mark") !== false - || strpos($x,"missing '>' for end of tag") !== false - || strpos($x,"Error:") !== false) { - print "\nTidy found something - view details with: $command"; - return false; - } else { - return true; - } + // Look for the most interesting Tidy errors and warnings. + if ( strpos( $x, "end of file while parsing attributes" ) !== false + || strpos( $x, "attribute with missing trailing quote mark" ) !== false + || strpos( $x, "missing '>' for end of tag" ) !== false + || strpos( $x, "Error:" ) !== false ) { + print "\nTidy found something - view details with: $command"; + return false; + } else { + return true; + } } @@ -2484,267 +2485,267 @@ function tidyCheckFile($name) { ** the last time this was run. This is used to tell if a test caused a DB error. */ function dbErrorLogged() { - static $filesize; + static $filesize; - // first time running this function - if (!isset($filesize)) { - // create log if it does not exist - if (!file_exists(DB_ERROR_LOG_FILE)) { - saveFile("", DB_ERROR_LOG_FILE); - } - $filesize = filesize(DB_ERROR_LOG_FILE); - return false; - } + // first time running this function + if ( !isset( $filesize ) ) { + // create log if it does not exist + if ( !file_exists( DB_ERROR_LOG_FILE ) ) { + saveFile( "", DB_ERROR_LOG_FILE ); + } + $filesize = filesize( DB_ERROR_LOG_FILE ); + return false; + } - $newsize = filesize(DB_ERROR_LOG_FILE); - // if the log has grown, then assume the current test caused it. - if ($newsize != $filesize) { - $filesize = $newsize; - return true; - } + $newsize = filesize( DB_ERROR_LOG_FILE ); + // if the log has grown, then assume the current test caused it. + if ( $newsize != $filesize ) { + $filesize = $newsize; + return true; + } - return false; + return false; } -////////////////// TOP-LEVEL PROBLEM-FINDING FUNCTION //////////////////////// +// //////////////// TOP-LEVEL PROBLEM-FINDING FUNCTION //////////////////////// /** ** takes a page test, and runs it and tests it for problems in the output. ** Returns: False on finding a problem, or True on no problems being found. */ -function runWikiTest(pageTest $test, &$testname, $can_overwrite = false) { - - // by default don't overwrite a previous test of the same name. - while ( ! $can_overwrite && file_exists(DIRECTORY . "/" . $testname . DATA_FILE)) { - $testname .= "-" . mt_rand(0,9); - } - - $filename = DIRECTORY . "/" . $testname . DATA_FILE; - - // Store the time before and after, to find slow pages. - $before = microtime(true); - - // Get MediaWiki to give us the output of this test. - $wiki_preview = wikiTestOutput($test); - - $after = microtime(true); - - // if we received no response, then that's interesting. - if ($wiki_preview == "") { - print "\nNo response received for: $filename"; - return false; - } - - // save output HTML to file. - $html_file = DIRECTORY . "/" . $testname . HTML_FILE; - saveFile($wiki_preview, $html_file); - - // if there were PHP errors in the output, then that's interesting too. - if ( strpos($wiki_preview, "<b>Warning</b>: " ) !== false - || strpos($wiki_preview, "<b>Fatal error</b>: " ) !== false - || strpos($wiki_preview, "<b>Notice</b>: " ) !== false - || strpos($wiki_preview, "<b>Error</b>: " ) !== false - || strpos($wiki_preview, "<b>Strict Standards:</b>") !== false - ) { - $error = substr($wiki_preview, strpos($wiki_preview, "</b>:") + 7, 50); - // Avoid probable PHP bug with bad session ids; http://bugs.php.net/bug.php?id=38224 - if ($error != "Unknown: The session id contains illegal character") { - print "\nPHP error/warning/notice in HTML output: $html_file ; $error"; - return false; - } - } - - // if there was a MediaWiki Backtrace message in the output, then that's also interesting. - if( strpos($wiki_preview, "Backtrace:") !== false ) { - print "\nInternal MediaWiki error in HTML output: $html_file"; - return false; - } - - // if there was a Parser error comment in the output, then that's potentially interesting. - if( strpos($wiki_preview, "!-- ERR") !== false ) { - print "\nParser Error comment in HTML output: $html_file"; - return false; - } - - // if a database error was logged, then that's definitely interesting. - if( dbErrorLogged() ) { - print "\nDatabase Error logged for: $filename"; - return false; - } - - // validate result - $valid = true; - if( VALIDATE_ON_WEB ) { - list ($valid, $validator_output) = validateHTML($wiki_preview); - if (!$valid) print "\nW3C web validation failed - view details with: html2text " . DIRECTORY . "/" . $testname . ".validator_output.html"; - } - - // Get tidy to check the page, unless we already know it produces non-XHTML output. - if( $test->tidyValidate() ) { - $valid = tidyCheckFile( $testname . HTML_FILE ) && $valid; - } - - // if it took more than 2 seconds to render, then it may be interesting too. (Possible DoS attack?) - if (($after - $before) >= 2) { - print "\nParticularly slow to render (" . round($after - $before, 2) . " seconds): $filename"; - return false; - } - - if( $valid ) { - // Remove temp HTML file if test was valid: - unlink( $html_file ); - } elseif( VALIDATE_ON_WEB ) { - saveFile($validator_output, DIRECTORY . "/" . $testname . ".validator_output.html"); - } - - return $valid; -} - - -/////////////////// RERUNNING OLD TESTS /////////////////// +function runWikiTest( pageTest $test, &$testname, $can_overwrite = false ) { + + // by default don't overwrite a previous test of the same name. + while ( ! $can_overwrite && file_exists( DIRECTORY . "/" . $testname . DATA_FILE ) ) { + $testname .= "-" . mt_rand( 0, 9 ); + } + + $filename = DIRECTORY . "/" . $testname . DATA_FILE; + + // Store the time before and after, to find slow pages. + $before = microtime( true ); + + // Get MediaWiki to give us the output of this test. + $wiki_preview = wikiTestOutput( $test ); + + $after = microtime( true ); + + // if we received no response, then that's interesting. + if ( $wiki_preview == "" ) { + print "\nNo response received for: $filename"; + return false; + } + + // save output HTML to file. + $html_file = DIRECTORY . "/" . $testname . HTML_FILE; + saveFile( $wiki_preview, $html_file ); + + // if there were PHP errors in the output, then that's interesting too. + if ( strpos( $wiki_preview, "<b>Warning</b>: " ) !== false + || strpos( $wiki_preview, "<b>Fatal error</b>: " ) !== false + || strpos( $wiki_preview, "<b>Notice</b>: " ) !== false + || strpos( $wiki_preview, "<b>Error</b>: " ) !== false + || strpos( $wiki_preview, "<b>Strict Standards:</b>" ) !== false + ) { + $error = substr( $wiki_preview, strpos( $wiki_preview, "</b>:" ) + 7, 50 ); + // Avoid probable PHP bug with bad session ids; http://bugs.php.net/bug.php?id=38224 + if ( $error != "Unknown: The session id contains illegal character" ) { + print "\nPHP error/warning/notice in HTML output: $html_file ; $error"; + return false; + } + } + + // if there was a MediaWiki Backtrace message in the output, then that's also interesting. + if ( strpos( $wiki_preview, "Backtrace:" ) !== false ) { + print "\nInternal MediaWiki error in HTML output: $html_file"; + return false; + } + + // if there was a Parser error comment in the output, then that's potentially interesting. + if ( strpos( $wiki_preview, "!-- ERR" ) !== false ) { + print "\nParser Error comment in HTML output: $html_file"; + return false; + } + + // if a database error was logged, then that's definitely interesting. + if ( dbErrorLogged() ) { + print "\nDatabase Error logged for: $filename"; + return false; + } + + // validate result + $valid = true; + if ( VALIDATE_ON_WEB ) { + list ( $valid, $validator_output ) = validateHTML( $wiki_preview ); + if ( !$valid ) print "\nW3C web validation failed - view details with: html2text " . DIRECTORY . "/" . $testname . ".validator_output.html"; + } + + // Get tidy to check the page, unless we already know it produces non-XHTML output. + if ( $test->tidyValidate() ) { + $valid = tidyCheckFile( $testname . HTML_FILE ) && $valid; + } + + // if it took more than 2 seconds to render, then it may be interesting too. (Possible DoS attack?) + if ( ( $after - $before ) >= 2 ) { + print "\nParticularly slow to render (" . round( $after - $before, 2 ) . " seconds): $filename"; + return false; + } + + if ( $valid ) { + // Remove temp HTML file if test was valid: + unlink( $html_file ); + } elseif ( VALIDATE_ON_WEB ) { + saveFile( $validator_output, DIRECTORY . "/" . $testname . ".validator_output.html" ); + } + + return $valid; +} + + +// ///////////////// RERUNNING OLD TESTS /////////////////// /** ** We keep our failed tests so that they can be rerun. ** This function does that retesting. */ function rerunPreviousTests() { - print "Retesting previously found problems.\n"; + print "Retesting previously found problems.\n"; - $dir_contents = scandir (DIRECTORY); + $dir_contents = scandir ( DIRECTORY ); - // sort file into the order a normal person would use. - natsort ($dir_contents); + // sort file into the order a normal person would use. + natsort ( $dir_contents ); - foreach ($dir_contents as $file) { + foreach ( $dir_contents as $file ) { - // if file is not a test, then skip it. - // Note we need to escape any periods or will be treated as "any character". - $matches = array(); - if (!ereg("(.*)" . str_replace(".", "\.", DATA_FILE) . "$", $file, $matches)) continue; + // if file is not a test, then skip it. + // Note we need to escape any periods or will be treated as "any character". + $matches = array(); + if ( !preg_match( "/(.*)" . str_replace( ".", "\.", DATA_FILE ) . "$/", $file, $matches ) ) continue; - // reload the test. - $full_path = DIRECTORY . "/" . $file; - $test = unserialize(file_get_contents($full_path)); + // reload the test. + $full_path = DIRECTORY . "/" . $file; + $test = unserialize( file_get_contents( $full_path ) ); - // if this is not a valid test, then skip it. - if (! $test instanceof pageTest) { - print "\nSkipping invalid test - $full_path"; - continue; - } + // if this is not a valid test, then skip it. + if ( ! $test instanceof pageTest ) { + print "\nSkipping invalid test - $full_path"; + continue; + } - // The date format is in Apache log format, which makes it easier to locate - // which retest caused which error in the Apache logs (only happens usually if - // apache segfaults). - if (!QUIET) print "[" . date ("D M d H:i:s Y") . "] Retesting $file (" . get_class($test) . ")"; + // The date format is in Apache log format, which makes it easier to locate + // which retest caused which error in the Apache logs (only happens usually if + // apache segfaults). + if ( !QUIET ) print "[" . date ( "D M d H:i:s Y" ) . "] Retesting $file (" . get_class( $test ) . ")"; - // run test - $testname = $matches[1]; - $valid = runWikiTest($test, $testname, true); + // run test + $testname = $matches[1]; + $valid = runWikiTest( $test, $testname, true ); - if (!$valid) { - saveTest($test, $testname); - if (QUIET) { - print "\nTest: " . get_class($test) . " ; Testname: $testname\n------"; - } else { - print "\n"; - } - } - else { - if (!QUIET) print "\r"; - if (DELETE_PASSED_RETESTS) { - $prefix = DIRECTORY . "/" . $testname; - if (is_file($prefix . DATA_FILE)) unlink($prefix . DATA_FILE); - if (is_file($prefix . PHP_TEST )) unlink($prefix . PHP_TEST ); - if (is_file($prefix . CURL_TEST)) unlink($prefix . CURL_TEST); - if (is_file($prefix . INFO_FILE)) unlink($prefix . INFO_FILE); - } - } - } + if ( !$valid ) { + saveTest( $test, $testname ); + if ( QUIET ) { + print "\nTest: " . get_class( $test ) . " ; Testname: $testname\n------"; + } else { + print "\n"; + } + } + else { + if ( !QUIET ) print "\r"; + if ( DELETE_PASSED_RETESTS ) { + $prefix = DIRECTORY . "/" . $testname; + if ( is_file( $prefix . DATA_FILE ) ) unlink( $prefix . DATA_FILE ); + if ( is_file( $prefix . PHP_TEST ) ) unlink( $prefix . PHP_TEST ); + if ( is_file( $prefix . CURL_TEST ) ) unlink( $prefix . CURL_TEST ); + if ( is_file( $prefix . INFO_FILE ) ) unlink( $prefix . INFO_FILE ); + } + } + } - print "\nDone retesting.\n"; + print "\nDone retesting.\n"; } -////////////////////// MAIN LOOP //////////////////////// +// //////////////////// MAIN LOOP //////////////////////// // first check whether CURL is installed, because sometimes it's not. -if( ! function_exists('curl_init') ) { - die("Could not find 'curl_init' function. Is the curl extension compiled into PHP?\n"); +if ( ! function_exists( 'curl_init' ) ) { + die( "Could not find 'curl_init' function. Is the curl extension compiled into PHP?\n" ); } -// Initialization of types. wikiFuzz doesn't have a constructor because we want to +// Initialization of types. wikiFuzz doesn't have a constructor because we want to // access it staticly and not have any globals. -wikiFuzz::$types = array_keys(wikiFuzz::$data); +wikiFuzz::$types = array_keys( wikiFuzz::$data ); // Make directory if doesn't exist -if (!is_dir(DIRECTORY)) { - mkdir (DIRECTORY, 0700 ); +if ( !is_dir( DIRECTORY ) ) { + mkdir ( DIRECTORY, 0700 ); } // otherwise, we first retest the things that we have found in previous runs -else if (RERUN_OLD_TESTS) { - rerunPreviousTests(); +else if ( RERUN_OLD_TESTS ) { + rerunPreviousTests(); } // main loop. -$start_time = date("U"); +$start_time = date( "U" ); $num_errors = 0; -if (!QUIET) { - print "Beginning main loop. Results are stored in the " . DIRECTORY . " directory.\n"; - print "Press CTRL+C to stop testing.\n"; -} - -for ($count=0; true; $count++) { - if (!QUIET) { - // spinning progress indicator. - switch( $count % 4 ) { - case '0': print "\r/"; break; - case '1': print "\r-"; break; - case '2': print "\r\\"; break; - case '3': print "\r|"; break; - } - print " $count"; - } - - // generate a page test to run. - $test = selectPageTest($count); - - $mins = ( date("U") - $start_time ) / 60; - if (!QUIET && $mins > 0) { - print ". $num_errors poss errors. " - . floor($mins) . " mins. " - . round ($count / $mins, 0) . " tests/min. " - . get_class($test); // includes the current test name. - } - - // run this test against MediaWiki, and see if the output was valid. - $testname = $count; - $valid = runWikiTest($test, $testname, false); - - // save the failed test - if ( ! $valid ) { - if (QUIET) { - print "\nTest: " . get_class($test) . " ; Testname: $testname\n------"; - } else { - print "\n"; - } - saveTest($test, $testname); - $num_errors += 1; - } else if ( KEEP_PASSED_TESTS ) { - // print current time, with microseconds (matches "strace" format), and the test name. - print " " . date("H:i:s.") . substr(current(explode(" ", microtime())), 2) . " " . $testname; - saveTest($test, $testname); - } - - // stop if we have reached max number of errors. - if (defined("MAX_ERRORS") && $num_errors>=MAX_ERRORS) { - break; - } - - // stop if we have reached max number of mins runtime. - if (defined("MAX_RUNTIME") && $mins>=MAX_RUNTIME) { - break; - } +if ( !QUIET ) { + print "Beginning main loop. Results are stored in the " . DIRECTORY . " directory.\n"; + print "Press CTRL+C to stop testing.\n"; +} + +for ( $count = 0; true; $count++ ) { + if ( !QUIET ) { + // spinning progress indicator. + switch( $count % 4 ) { + case '0': print "\r/"; break; + case '1': print "\r-"; break; + case '2': print "\r\\"; break; + case '3': print "\r|"; break; + } + print " $count"; + } + + // generate a page test to run. + $test = selectPageTest( $count ); + + $mins = ( date( "U" ) - $start_time ) / 60; + if ( !QUIET && $mins > 0 ) { + print ". $num_errors poss errors. " + . floor( $mins ) . " mins. " + . round ( $count / $mins, 0 ) . " tests/min. " + . get_class( $test ); // includes the current test name. + } + + // run this test against MediaWiki, and see if the output was valid. + $testname = $count; + $valid = runWikiTest( $test, $testname, false ); + + // save the failed test + if ( ! $valid ) { + if ( QUIET ) { + print "\nTest: " . get_class( $test ) . " ; Testname: $testname\n------"; + } else { + print "\n"; + } + saveTest( $test, $testname ); + $num_errors += 1; + } else if ( KEEP_PASSED_TESTS ) { + // print current time, with microseconds (matches "strace" format), and the test name. + print " " . date( "H:i:s." ) . substr( current( explode( " ", microtime() ) ), 2 ) . " " . $testname; + saveTest( $test, $testname ); + } + + // stop if we have reached max number of errors. + if ( defined( "MAX_ERRORS" ) && $num_errors >= MAX_ERRORS ) { + break; + } + + // stop if we have reached max number of mins runtime. + if ( defined( "MAX_RUNTIME" ) && $mins >= MAX_RUNTIME ) { + break; + } } diff --git a/maintenance/gearman/gearman.inc b/maintenance/gearman/gearman.inc index 514b9bac..15f80e62 100644 --- a/maintenance/gearman/gearman.inc +++ b/maintenance/gearman/gearman.inc @@ -72,7 +72,7 @@ class NonScaryGearmanWorker extends Net_Gearman_Worker { if (isset($resp['data']['arg']) && Net_Gearman_Connection::stringLength($resp['data']['arg'])) { $arg = json_decode($resp['data']['arg'], true); - } + } ### START MW DIFFERENT BIT if ( $name != 'mw_job' ) { diff --git a/maintenance/gearman/gearmanRefreshLinks.php b/maintenance/gearman/gearmanRefreshLinks.php index eb3104eb..730db96b 100644 --- a/maintenance/gearman/gearmanRefreshLinks.php +++ b/maintenance/gearman/gearmanRefreshLinks.php @@ -2,8 +2,8 @@ $optionsWithArgs = array( 'fake-job' ); -require( dirname(__FILE__).'/../commandLine.inc' ); -require( dirname(__FILE__).'/gearman.inc' ); +require( dirname( __FILE__ ) . '/../commandLine.inc' ); +require( dirname( __FILE__ ) . '/gearman.inc' ); if ( !$args ) { $args = array( 'localhost' ); @@ -15,12 +15,12 @@ $dbr = wfGetDB( DB_SLAVE ); $startId = 0; $endId = $dbr->selectField( 'page', 'MAX(page_id)', false, __METHOD__ ); while ( true ) { - $res = $dbr->select( - 'page', + $res = $dbr->select( + 'page', array( 'page_namespace', 'page_title', 'page_id' ), - array( 'page_id > ' . intval( $startId ) ), + array( 'page_id > ' . intval( $startId ) ), __METHOD__, - array( 'LIMIT' => $batchSize ) + array( 'LIMIT' => $batchSize ) ); if ( $res->numRows() == 0 ) { diff --git a/maintenance/gearman/gearmanWorker.php b/maintenance/gearman/gearmanWorker.php index d6f3949f..aea126a7 100644 --- a/maintenance/gearman/gearmanWorker.php +++ b/maintenance/gearman/gearmanWorker.php @@ -1,10 +1,10 @@ <?php $optionsWithArgs = array( 'fake-job', 'procs' ); -require( dirname(__FILE__).'/../commandLine.inc' ); -require( dirname(__FILE__).'/gearman.inc' ); +require( dirname( __FILE__ ) . '/../commandLine.inc' ); +require( dirname( __FILE__ ) . '/gearman.inc' ); -ini_set('memory_limit', '150M' ); +ini_set( 'memory_limit', '150M' ); if ( isset( $options['procs'] ) ) { $procs = $options['procs']; diff --git a/maintenance/generateSitemap.php b/maintenance/generateSitemap.php index 04dbbc4d..e483f7c9 100644 --- a/maintenance/generateSitemap.php +++ b/maintenance/generateSitemap.php @@ -4,6 +4,9 @@ define( 'GS_TALK', -1 ); /** * Creates a sitemap for the site * + * Copyright © 2005, Ævar Arnfjörð Bjarmason, Jens Frank <jeluf@gmx.de> and + * Brion Vibber <brion@pobox.com> + * * 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 @@ -19,19 +22,13 @@ define( 'GS_TALK', -1 ); * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance - * - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @copyright Copyright © 2005, Jens Frank <jeluf@gmx.de> - * @copyright Copyright © 2005, Brion Vibber <brion@pobox.com> - * * @see http://www.sitemaps.org/ * @see http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd - * - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class GenerateSitemap extends Maintenance { /** @@ -60,11 +57,11 @@ class GenerateSitemap extends Maintenance { var $fspath; /** - * The path to append to the domain name + * The URL path to prepend to filenames in the index; should resolve to the same directory as $fspath * * @var string */ - var $path; + var $urlpath; /** * Whether or not to use compression @@ -129,8 +126,8 @@ class GenerateSitemap extends Maintenance { public function __construct() { parent::__construct(); $this->mDescription = "Creates a sitemap for the site"; - $this->addOption( 'fspath', 'The file system path to save to, e.g. /tmp/sitemap' . - "\n\t\tdefaults to current directory", false, true ); + $this->addOption( 'fspath', 'The file system path to save to, e.g. /tmp/sitemap; defaults to current directory', false, true ); + $this->addOption( 'urlpath', 'The URL path corresponding to --fspath, prepended to filenames in the index; defaults to an empty string', false, true ); $this->addOption( 'compress', 'Compress the sitemap files, can take value yes|no, default yes', false, true ); } @@ -138,11 +135,14 @@ class GenerateSitemap extends Maintenance { * Execute */ public function execute() { - global $wgScriptPath; $this->setNamespacePriorities(); $this->url_limit = 50000; $this->size_limit = pow( 2, 20 ) * 10; $this->fspath = self::init_path( $this->getOption( 'fspath', getcwd() ) ); + $this->urlpath = $this->getOption( 'urlpath', "" ); + if ( $this->urlpath !== "" && substr( $this->urlpath, -1 ) !== '/' ) { + $this->urlpath .= '/'; + } $this->compress = $this->getOption( 'compress', 'yes' ) !== 'no'; $this->dbr = wfGetDB( DB_SLAVE ); $this->generateNamespaces(); @@ -179,15 +179,15 @@ class GenerateSitemap extends Maintenance { * Create directory if it does not exist and return pathname with a trailing slash */ private static function init_path( $fspath ) { - if( !isset( $fspath ) ) { + if ( !isset( $fspath ) ) { return null; } # Create directory if needed - if( $fspath && !is_dir( $fspath ) ) { - wfMkdirParents( $fspath ) or die("Can not create directory $fspath.\n"); + if ( $fspath && !is_dir( $fspath ) ) { + wfMkdirParents( $fspath ) or die( "Can not create directory $fspath.\n" ); } - return realpath( $fspath ). DIRECTORY_SEPARATOR ; + return realpath( $fspath ) . DIRECTORY_SEPARATOR ; } /** @@ -196,7 +196,7 @@ class GenerateSitemap extends Maintenance { function generateNamespaces() { // Only generate for specific namespaces if $wgSitemapNamespaces is an array. global $wgSitemapNamespaces; - if( is_array( $wgSitemapNamespaces ) ) { + if ( is_array( $wgSitemapNamespaces ) ) { $this->namespaces = $wgSitemapNamespaces; return; } @@ -218,11 +218,9 @@ class GenerateSitemap extends Maintenance { /** * Get the priority of a given namespace * - * @param int $namespace The namespace to get the priority for - + - * @return string + * @param $namespace Integer: the namespace to get the priority for + * @return String */ - function priority( $namespace ) { return isset( $this->priorities[$namespace] ) ? $this->priorities[$namespace] : $this->guessPriority( $namespace ); } @@ -232,9 +230,8 @@ class GenerateSitemap extends Maintenance { * default priority for the namespace, varies depending on whether it's * a talkpage or not. * - * @param int $namespace The namespace to get the priority for - * - * @return string + * @param $namespace Integer: the namespace to get the priority for + * @return String */ function guessPriority( $namespace ) { return MWNamespace::isMain( $namespace ) ? $this->priorities[GS_MAIN] : $this->priorities[GS_TALK]; @@ -243,9 +240,8 @@ class GenerateSitemap extends Maintenance { /** * Return a database resolution of all the pages in a given namespace * - * @param int $namespace Limit the query to this namespace - * - * @return resource + * @param $namespace Integer: limit the query to this namespace + * @return Resource */ function getPageRes( $namespace ) { return $this->dbr->select( 'page', @@ -261,10 +257,8 @@ class GenerateSitemap extends Maintenance { /** * Main loop - * - * @access public */ - function main() { + public function main() { global $wgContLang; fwrite( $this->findex, $this->openIndex() ); @@ -298,11 +292,11 @@ class GenerateSitemap extends Maintenance { $length += strlen( $entry ); $this->write( $this->file, $entry ); // generate pages for language variants - if($wgContLang->hasVariants()){ + if ( $wgContLang->hasVariants() ) { $variants = $wgContLang->getVariants(); - foreach($variants as $vCode){ - if($vCode==$wgContLang->getCode()) continue; // we don't want default variant - $entry = $this->fileEntry( $title->getFullURL('',$vCode), $date, $this->priority( $namespace ) ); + foreach ( $variants as $vCode ) { + if ( $vCode == $wgContLang->getCode() ) continue; // we don't want default variant + $entry = $this->fileEntry( $title->getFullURL( '', $vCode ), $date, $this->priority( $namespace ) ); $length += strlen( $entry ); $this->write( $this->file, $entry ); } @@ -320,7 +314,7 @@ class GenerateSitemap extends Maintenance { /** * gzopen() / fopen() wrapper * - * @return resource + * @return Resource */ function open( $file, $flags ) { return $this->compress ? gzopen( $file, $flags ) : fopen( $file, $flags ); @@ -349,23 +343,18 @@ class GenerateSitemap extends Maintenance { /** * Get a sitemap filename * - * @static - * - * @param int $namespace The namespace - * @param int $count The count - * - * @return string + * @param $namespace Integer: the namespace + * @param $count Integer: the count + * @return String */ function sitemapFilename( $namespace, $count ) { $ext = $this->compress ? '.gz' : ''; - return "sitemap-".wfWikiID()."-NS_$namespace-$count.xml$ext"; + return "sitemap-" . wfWikiID() . "-NS_$namespace-$count.xml$ext"; } /** * Return the XML required to open an XML file * - * @static - * * @return string */ function xmlHead() { @@ -375,9 +364,7 @@ class GenerateSitemap extends Maintenance { /** * Return the XML schema being used * - * @static - * - * @returns string + * @return String */ function xmlSchema() { return 'http://www.sitemaps.org/schemas/sitemap/0.9'; @@ -386,7 +373,7 @@ class GenerateSitemap extends Maintenance { /** * Return the XML required to open a sitemap index file * - * @return string + * @return String */ function openIndex() { return $this->xmlHead() . '<sitemapindex xmlns="' . $this->xmlSchema() . '">' . "\n"; @@ -395,16 +382,13 @@ class GenerateSitemap extends Maintenance { /** * Return the XML for a single sitemap indexfile entry * - * @static - * - * @param string $filename The filename of the sitemap file - * - * @return string + * @param $filename String: the filename of the sitemap file + * @return String */ function indexEntry( $filename ) { return "\t<sitemap>\n" . - "\t\t<loc>$filename</loc>\n" . + "\t\t<loc>{$this->urlpath}$filename</loc>\n" . "\t\t<lastmod>{$this->timestamp}</lastmod>\n" . "\t</sitemap>\n"; } @@ -412,9 +396,7 @@ class GenerateSitemap extends Maintenance { /** * Return the XML required to close a sitemap index file * - * @static - * - * @return string + * @return String */ function closeIndex() { return "</sitemapindex>\n"; @@ -423,7 +405,7 @@ class GenerateSitemap extends Maintenance { /** * Return the XML required to open a sitemap file * - * @return string + * @return String */ function openFile() { return $this->xmlHead() . '<urlset xmlns="' . $this->xmlSchema() . '">' . "\n"; @@ -432,13 +414,10 @@ class GenerateSitemap extends Maintenance { /** * Return the XML for a single sitemap entry * - * @static - * - * @param string $url An RFC 2396 compliant URL - * @param string $date A ISO 8601 date - * @param string $priority A priority indicator, 0.0 - 1.0 inclusive with a 0.1 stepsize - * - * @return string + * @param $url String: an RFC 2396 compliant URL + * @param $date String: a ISO 8601 date + * @param $priority String: a priority indicator, 0.0 - 1.0 inclusive with a 0.1 stepsize + * @return String */ function fileEntry( $url, $date, $priority ) { return @@ -452,8 +431,7 @@ class GenerateSitemap extends Maintenance { /** * Return the XML required to close sitemap file * - * @static - * @return string + * @return String */ function closeFile() { return "</urlset>\n"; @@ -474,4 +452,4 @@ class GenerateSitemap extends Maintenance { } $maintClass = "GenerateSitemap"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/getLagTimes.php b/maintenance/getLagTimes.php index bc14ae71..0322fa2d 100644 --- a/maintenance/getLagTimes.php +++ b/maintenance/getLagTimes.php @@ -18,7 +18,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class GetLagTimes extends Maintenance { public function __construct() { @@ -29,14 +29,14 @@ class GetLagTimes extends Maintenance { public function execute() { $lb = wfGetLB(); - if( $lb->getServerCount() == 1 ) { + 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." ); + . "a multi-host db server configuration." ); } else { $lags = $lb->getLagTimes(); - foreach( $lags as $n => $lag ) { + foreach ( $lags as $n => $lag ) { $host = $lb->getServerName( $n ); - if( IP::isValid( $host ) ) { + if ( IP::isValid( $host ) ) { $ip = $host; $host = gethostbyaddr( $host ); } else { @@ -51,4 +51,4 @@ class GetLagTimes extends Maintenance { } $maintClass = "GetLagTimes"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/getSlaveServer.php b/maintenance/getSlaveServer.php index eac97a59..a9d93f1d 100644 --- a/maintenance/getSlaveServer.php +++ b/maintenance/getSlaveServer.php @@ -19,8 +19,8 @@ * * @ingroup Maintenance */ - -require_once( dirname(__FILE__) . '/Maintenance.php' ); + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class GetSlaveServer extends Maintenance { public function __construct() { @@ -30,11 +30,11 @@ class GetSlaveServer extends Maintenance { } public function execute() { global $wgAllDBsAreLocalhost; - if( $wgAllDBsAreLocalhost ) { + if ( $wgAllDBsAreLocalhost ) { $host = 'localhost'; } else { - if( $this->hasOption('group') ) { - $db = wfGetDB( DB_SLAVE, $this->getOption('group') ); + if ( $this->hasOption( 'group' ) ) { + $db = wfGetDB( DB_SLAVE, $this->getOption( 'group' ) ); $host = $db->getServer(); } else { $lb = wfGetLB(); @@ -47,4 +47,4 @@ class GetSlaveServer extends Maintenance { } $maintClass = "GetSlaveServer"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/getText.php b/maintenance/getText.php index 6326267d..eb044117 100644 --- a/maintenance/getText.php +++ b/maintenance/getText.php @@ -21,7 +21,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class GetTextMaint extends Maintenance { public function __construct() { @@ -45,7 +45,7 @@ class GetTextMaint extends Maintenance { $titleText = $title->getPrefixedText(); $this->error( "Page $titleText does not exist.\n", true ); } - $text = $rev->getText( $this->hasOption('show-private') ? Revision::RAW : Revision::FOR_PUBLIC ); + $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 ); @@ -55,4 +55,4 @@ class GetTextMaint extends Maintenance { } $maintClass = "GetTextMaint"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/httpSessionDownload.php b/maintenance/httpSessionDownload.php index cab6e872..3c4f16a0 100644 --- a/maintenance/httpSessionDownload.php +++ b/maintenance/httpSessionDownload.php @@ -23,7 +23,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class HttpSessionDownload extends Maintenance { public function __construct() { @@ -34,10 +34,10 @@ class HttpSessionDownload extends Maintenance { } public function execute() { - wfProfileIn(__METHOD__); + wfProfileIn( __METHOD__ ); - //run the download: - Http::doSessionIdDownload( $this->getOption('sid'), $this->getOption('usk') ); + // run the download: + Http::doSessionIdDownload( $this->getOption( 'sid' ), $this->getOption( 'usk' ) ); // close up shop: // Execute any deferred updates @@ -49,9 +49,9 @@ class HttpSessionDownload extends Maintenance { // Shut down the database before exit wfGetLBFactory()->shutdown(); - wfProfileOut(__METHOD__); + wfProfileOut( __METHOD__ ); } } $maintClass = "HttpSessionDownload"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/ibm_db2/README b/maintenance/ibm_db2/README deleted file mode 100644 index 3c3f381c..00000000 --- a/maintenance/ibm_db2/README +++ /dev/null @@ -1,3 +0,0 @@ -== 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/foreignkeys.sql b/maintenance/ibm_db2/foreignkeys.sql new file mode 100644 index 00000000..81a88eb9 --- /dev/null +++ b/maintenance/ibm_db2/foreignkeys.sql @@ -0,0 +1,107 @@ +-- good +ALTER TABLE user_groups ADD CONSTRAINT USER_GROUPS_FK1 FOREIGN KEY (ug_user) REFERENCES user(user_id) ON DELETE CASCADE +; + +-- good +ALTER TABLE user_newtalk ADD CONSTRAINT USER_NEWTALK_FK1 FOREIGN KEY (user_id) REFERENCES user(user_id) ON DELETE CASCADE +; + +-- referenced value not found +ALTER TABLE revision ADD CONSTRAINT REVISION_PAGE_FK FOREIGN KEY (rev_page) REFERENCES page(page_id) ON DELETE CASCADE +; +-- referenced value not found +ALTER TABLE revision ADD CONSTRAINT REVISION_USER_FK FOREIGN KEY (rev_user) REFERENCES user(user_id) ON DELETE RESTRICT +; + +-- good +ALTER TABLE page_restrictions ADD CONSTRAINT PAGE_RESTRICTIONS_PAGE_FK FOREIGN KEY (pr_page) REFERENCES page(page_id) ON DELETE CASCADE +; + +-- good +ALTER TABLE page_props ADD CONSTRAINT PAGE_PROPS_PAGE_FK FOREIGN KEY (pp_page) REFERENCES page(page_id) ON DELETE CASCADE +; + +-- cannot contain null values +-- ALTER TABLE archive ADD CONSTRAINT ARCHIVE_USER_FK FOREIGN KEY (ar_user) REFERENCES user(user_id) ON DELETE SET NULL +--; + +-- referenced value not found +ALTER TABLE redirect ADD CONSTRAINT REDIRECT_FROM_FK FOREIGN KEY (rd_from) REFERENCES page(page_id) ON DELETE CASCADE +; + +-- referenced value not found +ALTER TABLE pagelinks ADD CONSTRAINT PAGELINKS_FROM_FK FOREIGN KEY (pl_from) REFERENCES page(page_id) ON DELETE CASCADE +; + +-- good +ALTER TABLE templatelinks ADD CONSTRAINT TEMPLATELINKS_FROM_FK FOREIGN KEY (tl_from) REFERENCES page(page_id) ON DELETE CASCADE +; + +-- good +ALTER TABLE imagelinks ADD CONSTRAINT IMAGELINKS_FROM_FK FOREIGN KEY (il_from) REFERENCES page(page_id) ON DELETE CASCADE +; + +-- good +ALTER TABLE categorylinks ADD CONSTRAINT CATEGORYLINKS_FROM_FK FOREIGN KEY (cl_from) REFERENCES page(page_id) ON DELETE CASCADE +; + +-- good +ALTER TABLE externallinks ADD CONSTRAINT EXTERNALLINKS_FROM_FK FOREIGN KEY (el_from) REFERENCES page(page_id) ON DELETE CASCADE +; + +-- good +ALTER TABLE langlinks ADD CONSTRAINT LANGLINKS_FROM_FK FOREIGN KEY (ll_from) REFERENCES page(page_id) ON DELETE CASCADE +; + +-- cannot contain null values +-- ALTER TABLE ipblocks ADD CONSTRAINT IPBLOCKS_USER_FK FOREIGN KEY (ipb_user) REFERENCES user(user_id) ON DELETE SET NULL +--; + +-- good +ALTER TABLE ipblocks ADD CONSTRAINT IPBLOCKS_BY_FK FOREIGN KEY (ipb_by) REFERENCES user(user_id) ON DELETE CASCADE +; + +-- cannot contain null values +-- ALTER TABLE image ADD CONSTRAINT IMAGE_USER_FK FOREIGN KEY (img_user) REFERENCES user(user_id) ON DELETE SET NULL +--; + +-- cannot contain null values +-- ALTER TABLE oldimage ADD CONSTRAINT OLDIMAGE_USER_FK FOREIGN KEY (oi_user) REFERENCES user(user_id) ON DELETE SET NULL +--; + +-- good +ALTER TABLE oldimage ADD CONSTRAINT OLDIMAGE_NAME_FK FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE +; + +-- cannot contain null values +-- ALTER TABLE filearchive ADD CONSTRAINT FILEARCHIVE_DELETED_USER_FK FOREIGN KEY (fa_deleted_user) REFERENCES user(user_id) ON DELETE SET NULL +--; + +-- cannot contain null values +-- ALTER TABLE filearchive ADD CONSTRAINT FILEARCHIVE_USER_FK FOREIGN KEY (fa_user) REFERENCES user(user_id) ON DELETE SET NULL +--; + +-- cannot contain null values +-- ALTER TABLE recentchanges ADD CONSTRAINT RECENTCHANGES_USER_FK FOREIGN KEY (rc_user) REFERENCES user(user_id) ON DELETE SET NULL +--; + +-- cannot contain null values +-- ALTER TABLE recentchanges ADD CONSTRAINT RECENTCHANGES_CUR_ID_FK FOREIGN KEY (rc_cur_id) REFERENCES page(page_id) ON DELETE SET NULL +--; + +-- good +ALTER TABLE watchlist ADD CONSTRAINT WATCHLIST_USER_FK FOREIGN KEY (wl_user) REFERENCES user(user_id) ON DELETE CASCADE +; + +-- good +-- already in MySQL schema +ALTER TABLE trackbacks ADD CONSTRAINT TRACKBACKS_PAGE_FK FOREIGN KEY (tb_page) REFERENCES page(page_id) ON DELETE CASCADE +; + +-- cannot contain null values +-- ALTER TABLE protected_titles ADD CONSTRAINT PROTECTED_TITLES_USER_FK FOREIGN KEY (pt_user) REFERENCES user(user_id) ON DELETE SET NULL +--; + +-- cannot contain null values +-- ALTER TABLE logging ADD CONSTRAINT LOGGING_USER_FK FOREIGN KEY (log_user) REFERENCES user(user_id) ON DELETE SET NULL +--;
\ No newline at end of file diff --git a/maintenance/ibm_db2/tables.sql b/maintenance/ibm_db2/tables.sql index 71c161c6..546c871d 100644 --- a/maintenance/ibm_db2/tables.sql +++ b/maintenance/ibm_db2/tables.sql @@ -8,6 +8,7 @@ CREATE TABLE user ( + -- Needs to start with 0 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), @@ -47,11 +48,7 @@ CREATE TABLE user_groups ( -- 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 INDEX user_groups_unique ON user_groups (ug_user, ug_group); CREATE TABLE user_newtalk ( @@ -71,7 +68,7 @@ CREATE UNIQUE INDEX user_newtalk_include_idx CREATE TABLE page ( - page_id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 0), + page_id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), page_namespace SMALLINT NOT NULL, page_title VARCHAR(255) NOT NULL, page_restrictions VARCHAR(1024), @@ -96,7 +93,7 @@ CREATE UNIQUE INDEX page_name_include CREATE TABLE revision ( - rev_id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 0), + rev_id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), rev_page BIGINT NOT NULL DEFAULT 0, -- REFERENCES page (page_id) ON DELETE CASCADE, rev_text_id BIGINT, -- FK @@ -119,9 +116,7 @@ CREATE INDEX rev_user_text_idx ON revision (rev_user_text); 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_id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), old_text CLOB(16M) INLINE LENGTH 4096, old_flags VARCHAR(1024) ); @@ -129,8 +124,8 @@ CREATE TABLE text ( -- replaces reserved word 'text' CREATE TABLE page_restrictions ( --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_id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), + pr_id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), pr_page INTEGER NOT NULL DEFAULT 0, --(used to be nullable) -- REFERENCES page (page_id) ON DELETE CASCADE, @@ -184,7 +179,7 @@ CREATE INDEX archive_user_text ON archive (ar_user_text); CREATE TABLE redirect ( - rd_from BIGINT NOT NULL PRIMARY KEY, + rd_from BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), --REFERENCES page(page_id) ON DELETE CASCADE, rd_namespace SMALLINT NOT NULL DEFAULT 0, rd_title VARCHAR(255) NOT NULL DEFAULT '', @@ -248,7 +243,7 @@ CREATE INDEX externallinks_index ON externallinks (el_index); -- CREATE TABLE external_user ( -- Foreign key to user_id - eu_local_id BIGINT NOT NULL PRIMARY KEY, + eu_local_id BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), -- Some opaque identifier provided by the external database eu_external_id VARCHAR(255) NOT NULL @@ -289,7 +284,7 @@ CREATE TABLE hitcounter ( ); CREATE TABLE ipblocks ( - ipb_id INTEGER NOT NULL PRIMARY KEY, + ipb_id INTEGER NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), --DEFAULT nextval('ipblocks_ipb_id_val'), ipb_address VARCHAR(1024), ipb_user BIGINT NOT NULL DEFAULT 0, @@ -358,7 +353,6 @@ CREATE TABLE oldimage ( 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); CREATE INDEX oi_name_archive_name ON oldimage (oi_name,oi_archive_name); CREATE INDEX oi_sha1 ON oldimage (oi_sha1); @@ -366,7 +360,7 @@ CREATE INDEX oi_sha1 ON oldimage (oi_sha1); CREATE TABLE filearchive ( - fa_id INTEGER NOT NULL PRIMARY KEY, + fa_id INTEGER NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), --PRIMARY KEY DEFAULT nextval('filearchive_fa_id_seq'), fa_name VARCHAR(255) NOT NULL, fa_archive_name VARCHAR(255), @@ -398,7 +392,7 @@ CREATE INDEX fa_nouser ON filearchive (fa_deleted_user); CREATE TABLE recentchanges ( - rc_id INTEGER NOT NULL PRIMARY KEY, + rc_id INTEGER NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), --PRIMARY KEY DEFAULT nextval('rc_rc_id_seq'), rc_timestamp TIMESTAMP(3) NOT NULL, rc_cur_time TIMESTAMP(3) NOT NULL, @@ -509,7 +503,7 @@ CREATE TABLE transcache ( CREATE TABLE logging ( - log_id BIGINT NOT NULL PRIMARY KEY, + log_id BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), --PRIMARY KEY DEFAULT nextval('log_log_id_seq'), log_type VARCHAR(32) NOT NULL, log_action VARCHAR(32) NOT NULL, @@ -534,10 +528,11 @@ CREATE INDEX log_page_id_time ON logging (log_page,log_timestamp); CREATE TABLE trackbacks ( - tb_id INTEGER NOT NULL PRIMARY KEY, + tb_id INTEGER NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), --PRIMARY KEY DEFAULT nextval('trackbacks_tb_id_seq'), -- foreign key also in MySQL - tb_page INTEGER REFERENCES page(page_id) ON DELETE CASCADE, + tb_page INTEGER, + -- REFERENCES page(page_id) ON DELETE CASCADE, tb_title VARCHAR(255) NOT NULL, tb_url CLOB(64K) INLINE LENGTH 4096 NOT NULL, tb_ex CLOB(64K) INLINE LENGTH 4096, @@ -548,7 +543,7 @@ CREATE INDEX trackback_page ON trackbacks (tb_page); CREATE TABLE job ( - job_id BIGINT NOT NULL PRIMARY KEY, + job_id BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), --PRIMARY KEY DEFAULT nextval('job_job_id_seq'), job_cmd VARCHAR(255) NOT NULL, job_namespace SMALLINT NOT NULL, @@ -558,47 +553,6 @@ CREATE TABLE job ( CREATE INDEX job_cmd_namespace_title ON job (job_cmd, job_namespace, job_title); - --- Postgres' Tsearch2 dropped ---ALTER TABLE page ADD titlevector tsvector; ---CREATE FUNCTION ts2_page_title() RETURNS TRIGGER LANGUAGE plpgsql AS ---$mw$ ---BEGIN ---IF TG_OP = 'INSERT' THEN --- NEW.titlevector = to_tsvector('default',REPLACE(NEW.page_title,'/',' ')); ---ELSIF NEW.page_title != OLD.page_title THEN --- NEW.titlevector := to_tsvector('default',REPLACE(NEW.page_title,'/',' ')); ---END IF; ---RETURN NEW; ---END; ---$mw$; - ---CREATE TRIGGER ts2_page_title BEFORE INSERT OR UPDATE ON page --- FOR EACH ROW EXECUTE PROCEDURE ts2_page_title(); - - ---ALTER TABLE text ADD textvector tsvector; ---CREATE FUNCTION ts2_page_text() RETURNS TRIGGER LANGUAGE plpgsql AS ---$mw$ ---BEGIN ---IF TG_OP = 'INSERT' THEN --- NEW.textvector = to_tsvector('default',NEW.old_text); ---ELSIF NEW.old_text != OLD.old_text THEN --- NEW.textvector := to_tsvector('default',NEW.old_text); ---END IF; ---RETURN NEW; ---END; ---$mw$; - ---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 text USING gin(textvector); - --TODO --CREATE FUNCTION add_interwiki (TEXT,INT,SMALLINT) RETURNS INT LANGUAGE SQL AS --$mw$ @@ -644,7 +598,7 @@ CREATE TABLE updatelog ( CREATE TABLE category ( - cat_id INTEGER NOT NULL PRIMARY KEY, + cat_id INTEGER NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (START WITH 1), --PRIMARY KEY DEFAULT nextval('category_id_seq'), cat_title VARCHAR(255) NOT NULL, cat_pages INTEGER NOT NULL DEFAULT 0, @@ -721,27 +675,6 @@ CREATE TABLE log_search ( 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(1024) NOT NULL, - mw_version VARCHAR(1024) NOT NULL, - notes VARCHAR(1024) , - - 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(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 diff --git a/maintenance/importDump.php b/maintenance/importDump.php index 714d76d8..5f47635e 100644 --- a/maintenance/importDump.php +++ b/maintenance/importDump.php @@ -24,7 +24,7 @@ $optionsWithArgs = array( 'report' ); -require_once( dirname(__FILE__) . '/commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/commandLine.inc' ); /** * @ingroup Maintenance @@ -38,7 +38,7 @@ class BackupReader { var $debug = false; var $uploads = false; - function BackupReader() { + function __construct() { $this->stderr = fopen( "php://stderr", "wt" ); } @@ -48,7 +48,7 @@ class BackupReader { function handleRevision( $rev ) { $title = $rev->getTitle(); - if( !$title ) { + if ( !$title ) { $this->progress( "Got bogus revision with null title!" ); return; } @@ -56,20 +56,20 @@ class BackupReader { $this->revCount++; $this->report(); - if( !$this->dryRun ) { + if ( !$this->dryRun ) { call_user_func( $this->importCallback, $rev ); } } - + function handleUpload( $revision ) { - if( $this->uploads ) { + if ( $this->uploads ) { $this->uploadCount++; - //$this->report(); + // $this->report(); $this->progress( "upload: " . $revision->getFilename() ); - - if( !$this->dryRun ) { + + if ( !$this->dryRun ) { // bluuuh hack - //call_user_func( $this->uploadCallback, $revision ); + // call_user_func( $this->uploadCallback, $revision ); $dbw = wfGetDB( DB_MASTER ); return $dbw->deadlockLoop( array( $revision, 'importUpload' ) ); } @@ -80,34 +80,34 @@ class BackupReader { $this->revCount++; $this->report(); - if( !$this->dryRun ) { + if ( !$this->dryRun ) { call_user_func( $this->logItemCallback, $rev ); } } function report( $final = false ) { - if( $final xor ( $this->pageCount % $this->reportingInterval == 0 ) ) { + if ( $final xor ( $this->pageCount % $this->reportingInterval == 0 ) ) { $this->showReport(); } } function showReport() { - if( $this->reporting ) { + if ( $this->reporting ) { $delta = wfTime() - $this->startTime; - if( $delta ) { - $rate = sprintf("%.2f", $this->pageCount / $delta); - $revrate = sprintf("%.2f", $this->revCount / $delta); + if ( $delta ) { + $rate = sprintf( "%.2f", $this->pageCount / $delta ); + $revrate = sprintf( "%.2f", $this->revCount / $delta ); } else { $rate = '-'; $revrate = '-'; } # Logs dumps don't have page tallies - if( $this->pageCount ) + if ( $this->pageCount ) $this->progress( "$this->pageCount ($rate pages/sec $revrate revs/sec)" ); else $this->progress( "$this->revCount ($revrate revs/sec)" ); } - wfWaitForSlaves(5); + wfWaitForSlaves( 5 ); } function progress( $string ) { @@ -115,19 +115,17 @@ class BackupReader { } function importFromFile( $filename ) { - $t = true; - if( preg_match( '/\.gz$/', $filename ) ) { + if ( preg_match( '/\.gz$/', $filename ) ) { $filename = 'compress.zlib://' . $filename; } - elseif( preg_match( '/\.bz2$/', $filename ) ) { + elseif ( preg_match( '/\.bz2$/', $filename ) ) { $filename = 'compress.bzip2://' . $filename; } - elseif( preg_match( '/\.7z$/', $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 + $file = fopen( $filename, 'rt' ); return $this->importFromHandle( $file ); } @@ -151,43 +149,41 @@ class BackupReader { $this->logItemCallback = $importer->setLogItemCallback( array( &$this, 'handleLogItem' ) ); + if ( $this->dryRun ) { + $importer->setPageOutCallback( null ); + } + return $importer->doImport(); } } -if( wfReadOnly() ) { +if ( wfReadOnly() ) { wfDie( "Wiki is in read-only mode; you'll need to disable it for import to work.\n" ); } $reader = new BackupReader(); -if( isset( $options['quiet'] ) ) { +if ( isset( $options['quiet'] ) ) { $reader->reporting = false; } -if( isset( $options['report'] ) ) { +if ( isset( $options['report'] ) ) { $reader->reportingInterval = intval( $options['report'] ); } -if( isset( $options['dry-run'] ) ) { +if ( isset( $options['dry-run'] ) ) { $reader->dryRun = true; } -if( isset( $options['debug'] ) ) { +if ( isset( $options['debug'] ) ) { $reader->debug = true; } -if( isset( $options['uploads'] ) ) { +if ( isset( $options['uploads'] ) ) { $reader->uploads = true; // experimental! } -if( isset( $args[0] ) ) { +if ( isset( $args[0] ) ) { $result = $reader->importFromFile( $args[0] ); } else { $result = $reader->importFromStdin(); } -if( WikiError::isError( $result ) ) { - echo $result->getMessage() . "\n"; -} else { - echo "Done!\n"; - echo "You might want to run rebuildrecentchanges.php to regenerate\n"; - echo "the recentchanges page.\n"; -} - - +echo "Done!\n"; +echo "You might want to run rebuildrecentchanges.php to regenerate\n"; +echo "the recentchanges page.\n"; diff --git a/maintenance/importImages.inc b/maintenance/importImages.inc index 7bb50eb8..ad88b07c 100644 --- a/maintenance/importImages.inc +++ b/maintenance/importImages.inc @@ -17,13 +17,14 @@ * @return mixed Array of filenames on success, or false on failure */ function findFiles( $dir, $exts ) { - if( is_dir( $dir ) ) { - if( $dhl = opendir( $dir ) ) { + if ( is_dir( $dir ) ) { + $dhl = opendir( $dir ); + if ( $dhl ) { $files = array(); - while( ( $file = readdir( $dhl ) ) !== false ) { - if( is_file( $dir . '/' . $file ) ) { + while ( ( $file = readdir( $dhl ) ) !== false ) { + if ( is_file( $dir . '/' . $file ) ) { list( /* $name */, $ext ) = splitFilename( $dir . '/' . $file ); - if( array_search( strtolower( $ext ), $exts ) !== false ) + if ( array_search( strtolower( $ext ), $exts ) !== false ) $files[] = $dir . '/' . $file; } } @@ -51,10 +52,10 @@ function splitFilename( $filename ) { } /** - * Find an auxilliary file with the given extension, matching - * the give base file path. $maxStrip determines how many extensions + * Find an auxilliary file with the given extension, matching + * the give base file path. $maxStrip determines how many extensions * may be stripped from the original file name before appending the - * new extension. For example, with $maxStrip = 1 (the default), + * new extension. For example, with $maxStrip = 1 (the default), * file files acme.foo.bar.txt and acme.foo.txt would be auxilliary * files for acme.foo.bar and the extension ".txt". With $maxStrip = 2, * acme.txt would also be acceptable. @@ -90,23 +91,23 @@ function findAuxFile( $file, $auxExtension, $maxStrip = 1 ) { } # 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; - } +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] ); + 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; - } +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] ); + return html_entity_decode( $matches[1] ); } diff --git a/maintenance/importImages.php b/maintenance/importImages.php index f0dd388a..befbe64d 100644 --- a/maintenance/importImages.php +++ b/maintenance/importImages.php @@ -18,23 +18,23 @@ */ $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' ); +require_once( dirname( __FILE__ ) . '/commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/importImages.inc' ); $processed = $added = $ignored = $skipped = $overwritten = $failed = 0; echo( "Import Images\n\n" ); # Need a path -if( count( $args ) > 0 ) { +if ( count( $args ) > 0 ) { $dir = $args[0]; # Check Protection - if (isset($options['protect']) && isset($options['unprotect'])) - die("Cannot specify both protect and unprotect. Only 1 is allowed.\n"); + if ( isset( $options['protect'] ) && isset( $options['unprotect'] ) ) + die( "Cannot specify both protect and unprotect. Only 1 is allowed.\n" ); -if (isset($options['protect']) && $options['protect'] == 1) - die("You must specify a protection option.\n"); + if ( isset( $options['protect'] ) && $options['protect'] == 1 ) + die( "You must specify a protection option.\n" ); # Prepare the list of allowed extensions global $wgFileExtensions; @@ -49,31 +49,31 @@ if (isset($options['protect']) && $options['protect'] == 1) $user = isset( $options['user'] ) ? User::newFromName( $options['user'] ) : User::newFromName( 'Maintenance script' ); - if( !$user instanceof User ) + if ( !$user instanceof User ) $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 = (int)$options['check-userblock']; } else { $checkUserBlock = false; } - # Get --from + # Get --from $from = @$options['from']; - # Get sleep time. + # Get sleep time. $sleep = @$options['sleep']; - if ( $sleep ) $sleep = (int)$sleep; + if ( $sleep ) $sleep = (int)$sleep; # Get limit number $limit = @$options['limit']; - if ( $limit ) $limit = (int)$limit; + if ( $limit ) $limit = (int)$limit; - # Get the upload comment - $comment = NULL; + # Get the upload comment. Provide a default one in case there's no comment given. + $comment = 'Importing image file'; if ( isset( $options['comment-file'] ) ) { $comment = file_get_contents( $options['comment-file'] ); @@ -91,18 +91,18 @@ if (isset($options['protect']) && $options['protect'] == 1) $license = isset( $options['license'] ) ? $options['license'] : ''; # Batch "upload" operation - if( ( $count = count( $files ) ) > 0 ) { - - foreach( $files as $file ) { + if ( ( $count = count( $files ) ) > 0 ) { + + foreach ( $files as $file ) { $base = wfBaseName( $file ); - + # Validate a title $title = Title::makeTitleSafe( NS_FILE, $base ); - if( !is_object( $title ) ) { + if ( !is_object( $title ) ) { echo( "{$base} could not be imported; a valid title cannot be produced\n" ); continue; } - + if ( $from ) { if ( $from == $title->getDBkey() ) { $from = NULL; @@ -113,7 +113,7 @@ if (isset($options['protect']) && $options['protect'] == 1) } if ( $checkUserBlock && ( ( $processed % $checkUserBlock ) == 0 ) ) { - $user->clearInstanceCache( 'name' ); //reload from DB! + $user->clearInstanceCache( 'name' ); // reload from DB! if ( $user->isBlocked() ) { echo( $user->getName() . " was blocked! Aborting.\n" ); break; @@ -122,8 +122,8 @@ if (isset($options['protect']) && $options['protect'] == 1) # Check existence $image = wfLocalFile( $title ); - if( $image->exists() ) { - if( isset( $options['overwrite'] ) ) { + if ( $image->exists() ) { + if ( isset( $options['overwrite'] ) ) { echo( "{$base} exists, overwriting..." ); $svar = 'overwritten'; } else { @@ -134,7 +134,7 @@ if (isset($options['protect']) && $options['protect'] == 1) } else { if ( isset( $options['skip-dupes'] ) ) { $repo = $image->getRepo(); - $sha1 = File::sha1Base36( $file ); #XXX: we end up calculating this again when actually uploading. that sucks. + $sha1 = File::sha1Base36( $file ); # XXX: we end up calculating this again when actually uploading. that sucks. $dupes = $repo->findBySha1( $sha1 ); @@ -149,73 +149,73 @@ if (isset($options['protect']) && $options['protect'] == 1) $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['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} by '" . $wgUser->getName() . "', comment '$commentText'... " ); } else { $archive = $image->publish( $file ); - if( WikiError::isError( $archive ) || !$archive->isGood() ) { + if ( !$archive->isGood() ) { echo( "failed.\n" ); $failed++; continue; } } - + $doProtect = false; $restrictions = array(); - + global $wgRestrictionLevels; - - $protectLevel = isset($options['protect']) ? $options['protect'] : null; - + + $protectLevel = isset( $options['protect'] ) ? $options['protect'] : null; + if ( $protectLevel && in_array( $protectLevel, $wgRestrictionLevels ) ) { $restrictions['move'] = $protectLevel; $restrictions['edit'] = $protectLevel; $doProtect = true; } - if (isset($options['unprotect'])) { + if ( isset( $options['unprotect'] ) ) { $restrictions['move'] = ''; $restrictions['edit'] = ''; $doProtect = true; @@ -227,26 +227,26 @@ if (isset($options['protect']) && $options['protect'] == 1) } else if ( $image->recordUpload( $archive->value, $commentText, $license ) ) { # We're done! echo( "done.\n" ); - if ($doProtect) { + if ( $doProtect ) { # Protect the file $article = new Article( $title ); echo "\nWaiting for slaves...\n"; // Wait for slaves. - sleep(2.0); + sleep( 2.0 ); wfWaitForSlaves( 1.0 ); - + echo( "\nSetting image restrictions ... " ); - if ( $article->updateRestrictions($restrictions) ) - echo( "done.\n" ); + if ( $article->updateRestrictions( $restrictions ) ) + echo( "done.\n" ); else - echo( "failed.\n" ); + echo( "failed.\n" ); } } else { echo( "failed.\n" ); $svar = 'failed'; } - + $$svar++; $processed++; @@ -256,16 +256,16 @@ if (isset($options['protect']) && $options['protect'] == 1) if ( $sleep ) sleep( $sleep ); } - + # Print out some statistics echo( "\n" ); - foreach( array( 'count' => 'Found', 'limit' => 'Limit', 'ignored' => 'Ignored', - 'added' => 'Added', 'skipped' => 'Skipped', 'overwritten' => 'Overwritten', + foreach ( array( 'count' => 'Found', 'limit' => 'Limit', 'ignored' => 'Ignored', + 'added' => 'Added', 'skipped' => 'Skipped', 'overwritten' => 'Overwritten', 'failed' => 'Failed' ) as $var => $desc ) { - if( $$var > 0 ) + if ( $$var > 0 ) echo( "{$desc}: {$$var}\n" ); } - + } else { echo( "No suitable files could be found for import.\n" ); } @@ -274,10 +274,10 @@ if (isset($options['protect']) && $options['protect'] == 1) showUsage(); } -exit(0); +exit( 0 ); function showUsage( $reason = false ) { - if( $reason ) { + if ( $reason ) { echo( $reason . "\n" ); } @@ -292,7 +292,7 @@ Options: --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. + 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' @@ -306,8 +306,8 @@ Options: --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/" + For example, --source-wiki-url="http://en.wikipedia.org/" TEXT; - exit(1); + exit( 1 ); } diff --git a/maintenance/importTextFile.php b/maintenance/importTextFile.php index 955d01f4..3b77eb5f 100644 --- a/maintenance/importTextFile.php +++ b/maintenance/importTextFile.php @@ -11,30 +11,30 @@ $options = array( 'help', 'nooverwrite', 'norc' ); $optionsWithArgs = array( 'title', 'user', 'comment' ); -require_once( dirname(__FILE__) . '/commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/commandLine.inc' ); echo( "Import Text File\n\n" ); -if( count( $args ) < 1 || isset( $options['help'] ) ) { +if ( count( $args ) < 1 || isset( $options['help'] ) ) { showHelp(); } else { $filename = $args[0]; echo( "Using {$filename}..." ); - if( is_file( $filename ) ) { + if ( is_file( $filename ) ) { $title = isset( $options['title'] ) ? $options['title'] : titleFromFilename( $filename ); $title = Title::newFromURL( $title ); - if( is_object( $title ) ) { + if ( is_object( $title ) ) { echo( "\nUsing title '" . $title->getPrefixedText() . "'..." ); - if( !$title->exists() || !isset( $options['nooverwrite'] ) ) { + if ( !$title->exists() || !isset( $options['nooverwrite'] ) ) { $text = file_get_contents( $filename ); $user = isset( $options['user'] ) ? $options['user'] : 'Maintenance script'; $user = User::newFromName( $user ); - if( is_object( $user ) ) { + if ( is_object( $user ) ) { echo( "\nUsing username '" . $user->getName() . "'..." ); $wgUser =& $user; diff --git a/maintenance/importUseModWiki.php b/maintenance/importUseModWiki.php index 0d014145..bff4cd02 100644 --- a/maintenance/importUseModWiki.php +++ b/maintenance/importUseModWiki.php @@ -26,7 +26,7 @@ * @ingroup Maintenance */ -if( php_sapi_name() != 'cli' ) { +if ( php_sapi_name() != 'cli' ) { echo "Please customize the settings and run me from the command line."; die( -1 ); } @@ -41,9 +41,9 @@ $wgRootDirectory = "/kalman/Projects/wiki2002/wiki/lib-http/db/wiki"; /* globals */ $wgFieldSeparator = "\xb3"; # Some wikis may use different char $FS = $wgFieldSeparator ; - $FS1 = $FS."1" ; - $FS2 = $FS."2" ; - $FS3 = $FS."3" ; + $FS1 = $FS . "1" ; + $FS2 = $FS . "2" ; + $FS3 = $FS . "3" ; # Unicode sanitization tools require_once( dirname( dirname( __FILE__ ) ) . '/includes/normal/UtfNormal.php' ); @@ -60,13 +60,13 @@ function importPages() $gt = '>'; echo <<<XML -<?xml version="1.0" encoding="UTF-8" ?$gt +<?xml version="1.0" encoding="UTF-8" ?> <mediawiki xmlns="http://www.mediawiki.org/xml/export-0.1/" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.1/ - http://www.mediawiki.org/xml/export-0.1.xsd" - version="0.1" - xml:lang="en"> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.1/ + http://www.mediawiki.org/xml/export-0.1.xsd" + version="0.1" + xml:lang="en"> <!-- generated by importUseModWiki.php --> XML; @@ -74,9 +74,9 @@ XML; 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'other' ); - foreach( $letters as $letter ) { + foreach ( $letters as $letter ) { $dir = "$wgRootDirectory/page/$letter"; - if( is_dir( $dir ) ) + if ( is_dir( $dir ) ) importPageDirectory( $dir ); } echo <<<XML @@ -89,13 +89,13 @@ function importPageDirectory( $dir, $prefix = "" ) { echo "\n<!-- Checking page directory " . xmlCommentSafe( $dir ) . " -->\n"; $mydir = opendir( $dir ); - while( $entry = readdir( $mydir ) ) { + while ( $entry = readdir( $mydir ) ) { $m = array(); - if( preg_match( '/^(.+)\.db$/', $entry, $m ) ) { + if ( preg_match( '/^(.+)\.db$/', $entry, $m ) ) { echo importPage( $prefix . $m[1] ); } else { - if( is_dir( "$dir/$entry" ) ) { - if( $entry != '.' && $entry != '..' ) { + if ( is_dir( "$dir/$entry" ) ) { + if ( $entry != '.' && $entry != '..' ) { importPageDirectory( "$dir/$entry", "$entry/" ); } } else { @@ -114,7 +114,7 @@ function importPageDirectory( $dir, $prefix = "" ) function useModFilename( $title ) { $c = substr( $title, 0, 1 ); - if(preg_match( '/[A-Z]/i', $c ) ) { + if ( preg_match( '/[A-Z]/i', $c ) ) { return strtoupper( $c ) . "/$title"; } return "other/$title"; @@ -122,10 +122,10 @@ function useModFilename( $title ) { function fetchPage( $title ) { - global $FS1,$FS2,$FS3, $wgRootDirectory; + global $FS1, $FS2, $FS3, $wgRootDirectory; $fname = $wgRootDirectory . "/page/" . useModFilename( $title ) . ".db"; - if( !file_exists( $fname ) ) { + if ( !file_exists( $fname ) ) { echo "Couldn't open file '$fname' for page '$title'.\n"; die( -1 ); } @@ -141,19 +141,19 @@ function fetchPage( $title ) function fetchKeptPages( $title ) { - global $FS1,$FS2,$FS3, $wgRootDirectory; + global $FS1, $FS2, $FS3, $wgRootDirectory; $fname = $wgRootDirectory . "/keep/" . useModFilename( $title ) . ".kp"; - if( !file_exists( $fname ) ) return array(); + if ( !file_exists( $fname ) ) return array(); $keptlist = explode( $FS1, file_get_contents( $fname ) ); array_shift( $keptlist ); # Drop the junk at beginning of file $revisions = array(); - foreach( $keptlist as $rev ) { + foreach ( $keptlist as $rev ) { $section = splitHash( $FS2, $rev ); $text = splitHash( $FS3, $section["data"] ); - if ( $text["text"] && $text["minor"] != "" && ( $section["ts"]*1 > 0 ) ) { + if ( $text["text"] && $text["minor"] != "" && ( $section["ts"] * 1 > 0 ) ) { array_push( $revisions, array2object( array ( "text" => $text["text"] , "summary" => $text["summary"] , "minor" => $text["minor"] , "ts" => $section["ts"] , "username" => $section["username"] , "host" => $section["host"] ) ) ); @@ -167,7 +167,7 @@ function fetchKeptPages( $title ) function splitHash ( $sep , $str ) { $temp = explode ( $sep , $str ) ; $ret = array () ; - for ( $i = 0; $i+1 < count ( $temp ) ; $i++ ) { + for ( $i = 0; $i + 1 < count ( $temp ) ; $i++ ) { $ret[$temp[$i]] = $temp[++$i] ; } return $ret ; @@ -182,8 +182,8 @@ function checkUserCache( $name, $host ) { global $usercache; - if( $name ) { - if( in_array( $name, $usercache ) ) { + if ( $name ) { + if ( in_array( $name, $usercache ) ) { $userid = $usercache[$name]; } else { # If we haven't imported user accounts @@ -199,15 +199,13 @@ function checkUserCache( $name, $host ) function importPage( $title ) { - global $usercache; - echo "\n<!-- Importing page " . xmlCommentSafe( $title ) . " -->\n"; $page = fetchPage( $title ); $newtitle = xmlsafe( str_replace( '_', ' ', recodeText( $title ) ) ); $munged = mungeFormat( $page->text ); - if( $munged != $page->text ) { + if ( $munged != $page->text ) { /** * Save a *new* revision with the conversion, and put the * previous last version into the history. @@ -235,13 +233,13 @@ XML; # History $revisions = array_merge( $revisions, fetchKeptPages( $title ) ); - if(count( $revisions ) == 0 ) { + if ( count( $revisions ) == 0 ) { return NULL; // Was "$sql", which does not appear to be defined. } - foreach( $revisions as $rev ) { + foreach ( $revisions as $rev ) { $text = xmlsafe( recodeText( $rev->text ) ); - $minor = ($rev->minor ? '<minor/>' : ''); + $minor = ( $rev->minor ? '<minor/>' : '' ); list( /* $userid */ , $username ) = checkUserCache( $rev->username, $rev->host ); $username = xmlsafe( recodeText( $username ) ); $timestamp = xmlsafe( timestamp2ISO8601( $rev->ts ) ); @@ -272,22 +270,22 @@ function recodeText( $string ) { return $string; } -function wfUtf8Sequence($codepoint) { - if($codepoint < 0x80) return chr($codepoint); - if($codepoint < 0x800) return chr($codepoint >> 6 & 0x3f | 0xc0) . - chr($codepoint & 0x3f | 0x80); - if($codepoint < 0x10000) return chr($codepoint >> 12 & 0x0f | 0xe0) . - chr($codepoint >> 6 & 0x3f | 0x80) . - chr($codepoint & 0x3f | 0x80); - if($codepoint < 0x100000) return chr($codepoint >> 18 & 0x07 | 0xf0) . # Double-check this - chr($codepoint >> 12 & 0x3f | 0x80) . - chr($codepoint >> 6 & 0x3f | 0x80) . - chr($codepoint & 0x3f | 0x80); +function wfUtf8Sequence( $codepoint ) { + if ( $codepoint < 0x80 ) return chr( $codepoint ); + if ( $codepoint < 0x800 ) return chr( $codepoint >> 6 & 0x3f | 0xc0 ) . + chr( $codepoint & 0x3f | 0x80 ); + if ( $codepoint < 0x10000 ) return chr( $codepoint >> 12 & 0x0f | 0xe0 ) . + chr( $codepoint >> 6 & 0x3f | 0x80 ) . + chr( $codepoint & 0x3f | 0x80 ); + if ( $codepoint < 0x100000 ) return chr( $codepoint >> 18 & 0x07 | 0xf0 ) . # Double-check this + chr( $codepoint >> 12 & 0x3f | 0x80 ) . + chr( $codepoint >> 6 & 0x3f | 0x80 ) . + chr( $codepoint & 0x3f | 0x80 ); # Doesn't yet handle outside the BMP return "&#$codepoint;"; } -function wfMungeToUtf8($string) { +function wfMungeToUtf8( $string ) { $string = preg_replace ( '/&#([0-9]+);/e', 'wfUtf8Sequence($1)', $string ); $string = preg_replace ( '/&#x([0-9a-f]+);/ie', 'wfUtf8Sequence(0x$1)', $string ); # Should also do named entities here @@ -295,7 +293,7 @@ function wfMungeToUtf8($string) { } function timestamp2ISO8601( $ts ) { - #2003-08-05T18:30:02Z + # 2003-08-05T18:30:02Z return gmdate( 'Y-m-d', $ts ) . 'T' . gmdate( 'H:i:s', $ts ) . 'Z'; } @@ -318,7 +316,7 @@ function xmlCommentSafe( $text ) { function array2object( $arr ) { $o = (object)0; - foreach( $arr as $x => $y ) { + foreach ( $arr as $x => $y ) { $o->$x = $y; } return $o; diff --git a/maintenance/initEditCount.php b/maintenance/initEditCount.php index b7301643..e421e29b 100644 --- a/maintenance/initEditCount.php +++ b/maintenance/initEditCount.php @@ -1,5 +1,8 @@ <?php /** + * Init the user_editcount database field based on the number of rows in the + * revision 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 @@ -15,10 +18,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class InitEditCount extends Maintenance { public function __construct() { @@ -30,12 +34,11 @@ class InitEditCount extends Maintenance { 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 the load balancer, usually indicating a replication environment.' ); $this->mDescription = "Batch-recalculate user_editcount fields from the revision table"; } public function execute() { - global $wgDBservers; $dbw = wfGetDB( DB_MASTER ); $user = $dbw->tableName( 'user' ); $revision = $dbw->tableName( 'revision' ); @@ -43,16 +46,16 @@ in $wgDBservers, usually indicating a replication environment.' ); $dbver = $dbw->getServerVersion(); // Autodetect mode... - $backgroundMode = count( $wgDBservers ) > 1 || - ($dbw instanceof DatabaseMySql && version_compare( $dbver, '4.1' ) < 0); - - if( $this->hasOption('background') ) { + $backgroundMode = wfGetLB()->getServerCount() > 1 || + ( $dbw instanceof DatabaseMysql && version_compare( $dbver, '4.1' ) < 0 ); + + if ( $this->hasOption( 'background' ) ) { $backgroundMode = true; - } elseif( $this->hasOption('quick') ) { + } elseif ( $this->hasOption( 'quick' ) ) { $backgroundMode = false; } - if( $backgroundMode ) { + if ( $backgroundMode ) { $this->output( "Using replication-friendly background mode...\n" ); $dbr = wfGetDB( DB_SLAVE ); @@ -61,7 +64,7 @@ in $wgDBservers, usually indicating a replication environment.' ); $start = microtime( true ); $migrated = 0; - for( $min = 0; $min <= $lastUser; $min += $chunkSize ) { + for ( $min = 0; $min <= $lastUser; $min += $chunkSize ) { $max = $min + $chunkSize; $result = $dbr->query( "SELECT @@ -73,17 +76,16 @@ in $wgDBservers, usually indicating a replication environment.' ); GROUP BY user_id", __METHOD__ ); - foreach( $result as $row ) { + 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; + $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, @@ -105,4 +107,4 @@ in $wgDBservers, usually indicating a replication environment.' ); } $maintClass = "InitEditCount"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/initStats.php b/maintenance/initStats.php index b92d46c5..1ec5c925 100644 --- a/maintenance/initStats.php +++ b/maintenance/initStats.php @@ -1,5 +1,4 @@ <?php - /** * Maintenance script to re-initialise or update the site statistics table * @@ -18,13 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance * @author Brion Vibber * @author Rob Church <robchur@gmail.com> - * @licence GNU General Public Licence 2.0 or later */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class InitStats extends Maintenance { public function __construct() { @@ -56,13 +55,13 @@ class InitStats extends Maintenance { $image = $counter->files(); $this->output( "{$image}\n" ); - if( !$this->hasOption('noviews') ) { + if ( !$this->hasOption( 'noviews' ) ) { $this->output( "Counting total page views..." ); $views = $counter->views(); $this->output( "{$views}\n" ); } - if( $this->hasOption( 'active' ) ) { + if ( $this->hasOption( 'active' ) ) { $this->output( "Counting active users..." ); $active = SiteStatsUpdate::cacheUpdate(); $this->output( "{$active}\n" ); @@ -70,7 +69,7 @@ class InitStats extends Maintenance { $this->output( "\nUpdating site statistics..." ); - if( $this->hasOption( 'update' ) ) { + if ( $this->hasOption( 'update' ) ) { $counter->update(); } else { $counter->refresh(); @@ -81,4 +80,4 @@ class InitStats extends Maintenance { } $maintClass = "InitStats"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/install-utils.inc b/maintenance/install-utils.inc index f2d48e9a..93ca0b58 100644 --- a/maintenance/install-utils.inc +++ b/maintenance/install-utils.inc @@ -1,219 +1,31 @@ <?php - /** - * This file contains functions used by the install script (config/index.php) - * and maintenance scripts. It is not loaded in normal web requests. + * This file contains ancient db-related functions that have been deprecated. Do + * not use them. Please find the appropriate replacements. * * @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 + * @deprecated Use DatabaseBase::sourceFile(). Will probably be removed in 1.19 */ -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 ); - } + $db = wfGetDB( DB_MASTER ); } $error = $db->sourceFile( $fname ); if ( $error !== true ) { print $error; - exit(1); + 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 + * @deprecated Use DatabaseBase::patchPath(). Will probably be removed in 1.18 */ -function mw_have_dl() { - return function_exists( 'dl' ) - && is_callable( 'dl' ) - && wfIniGetBool( 'enable_dl' ) - && !wfIniGetBool( 'safe_mode' ); +function archive( $name ) { + wfDeprecated( __METHOD__ ); + $dbr = wfGetDB( DB_SLAVE ); + return $dbr->patchPath( $name ); } diff --git a/maintenance/install.php b/maintenance/install.php new file mode 100644 index 00000000..34e3ea85 --- /dev/null +++ b/maintenance/install.php @@ -0,0 +1,95 @@ +<?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 + * @see wfWaitForSlaves() + */ + +if ( !function_exists( 'version_compare' ) || ( version_compare( phpversion(), '5.2.3' ) < 0 ) ) { + echo "You are using PHP version " . phpversion() . " but MediaWiki needs PHP 5.2.3 or higher. ABORTING.\n" . + "Check if you have a newer php executable with a different name, such as php5.\n"; + die( 1 ); +} + +define( 'MW_CONFIG_CALLBACK', 'Installer::overrideConfig' ); +define( 'MEDIAWIKI_INSTALL', true ); + +require_once( dirname( dirname( __FILE__ ) )."/maintenance/Maintenance.php" ); + +class CommandLineInstaller extends Maintenance { + function __construct() { + parent::__construct(); + global $IP; + + $this->addArg( 'name', 'The name of the wiki', true); + + $this->addArg( 'admin', 'The username of the wiki administrator (WikiSysop)', true ); + $this->addOption( 'pass', 'The password for the wiki administrator. You will be prompted for this if it isn\'t provided', false, true ); + $this->addOption( 'email', 'The email for the wiki administrator', false, true ); + $this->addOption( 'scriptpath', 'The relative path of the wiki in the web server (/wiki)', false, true ); + + $this->addOption( 'lang', 'The language to use (en)', false, true ); + /* $this->addOption( 'cont-lang', 'The content language (en)', false, true ); */ + + $this->addOption( 'dbtype', 'The type of database (mysql)', false, true ); + $this->addOption( 'dbserver', 'The database host (localhost)', false, true ); + $this->addOption( 'dbport', 'The database port; only for PostgreSQL (5432)', false, true ); + $this->addOption( 'dbname', 'The database name (my_wiki)', false, true ); + $this->addOption( 'dbpath', 'The path for the SQLite DB (/var/data)', false, true ); + $this->addOption( 'installdbuser', 'The user to use for installing (root)', false, true ); + $this->addOption( 'installdbpass', 'The pasword for the DB user to install as.', false, true ); + $this->addOption( 'dbuser', 'The user to use for normal operations (wikiuser)', false, true ); + $this->addOption( 'dbpass', 'The pasword for the DB user for normal operations', false, true ); + $this->addOption( 'confpath', "Path to write LocalSettings.php to, default $IP", false, true ); + /* $this->addOption( 'dbschema', 'The schema for the MediaWiki DB in pg (mediawiki)', false, true ); */ + /* $this->addOption( 'namespace', 'The project namespace (same as the name)', false, true ); */ + $this->addOption( 'env-checks', "Run environment checks only, don't change anything" ); + } + + function execute() { + global $IP, $wgTitle; + $siteName = isset( $this->mArgs[0] ) ? $this->mArgs[0] : "Don't care"; // Will not be set if used with --env-checks + $adminName = isset( $this->mArgs[1] ) ? $this->mArgs[1] : null; + $wgTitle = Title::newFromText( 'Installer script' ); + + $installer = + new CliInstaller( $siteName, $adminName, $this->mOptions ); + + $status = $installer->doEnvironmentChecks(); + if( $status->isGood() ) { + $installer->showMessage( 'config-env-good' ); + } else { + $installer->showStatusMessage( $status ); + return; + } + if( !$this->hasOption( 'env-checks' ) ) { + $installer->execute(); + $installer->writeConfigurationFile( $this->getOption( 'confpath', $IP ) ); + } + } + + function validateParamsAndArgs() { + if ( !$this->hasOption( 'env-checks' ) ) { + parent::validateParamsAndArgs(); + } + } +} + +$maintClass = "CommandLineInstaller"; + +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/installExtension.php b/maintenance/installExtension.php deleted file mode 100644 index ea4c191c..00000000 --- a/maintenance/installExtension.php +++ /dev/null @@ -1,683 +0,0 @@ -<?php -/** - * Copyright (C) 2006 Daniel Kinzler, brightbyte.de - * - * 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( 'target', 'repository', 'repos' ); - -require_once( dirname(__FILE__) . '/commandLine.inc' ); - -define('EXTINST_NOPATCH', 0); -define('EXTINST_WRITEPATCH', 6); -define('EXTINST_HOTPATCH', 10); - -/** - * @ingroup Maintenance - */ -class InstallerRepository { - var $path; - - function InstallerRepository( $path ) { - $this->path = $path; - } - - function printListing( ) { - trigger_error( 'override InstallerRepository::printListing()', E_USER_ERROR ); - } - - function getResource( $name ) { - trigger_error( 'override InstallerRepository::getResource()', E_USER_ERROR ); - } - - static function makeRepository( $path, $type = NULL ) { - if ( !$type ) { - $m = array(); - preg_match( '!(([-+\w]+)://)?.*?(\.[-\w\d.]+)?$!', $path, $m ); - $proto = @$m[2]; - - if ( !$proto ) { - $type = 'dir'; - } else if ( ( $proto == 'http' || $proto == 'https' ) && preg_match( '!([^\w]svn|svn[^\w])!i', $path) ) { - $type = 'svn'; #HACK! - } else { - $type = $proto; - } - } - - if ( $type == 'dir' || $type == 'file' ) { return new LocalInstallerRepository( $path ); } - else if ( $type == 'http' || $type == 'http' ) { return new WebInstallerRepository( $path ); } - else { return new SVNInstallerRepository( $path ); } - } -} - -/** - * @ingroup Maintenance - */ -class LocalInstallerRepository extends InstallerRepository { - - function LocalInstallerRepository ( $path ) { - InstallerRepository::InstallerRepository( $path ); - } - - function printListing( ) { - $ff = glob( "{$this->path}/*" ); - if ( $ff === false || $ff === NULL ) { - ExtensionInstaller::error( "listing directory {$this->path} failed!" ); - return false; - } - - foreach ( $ff as $f ) { - $n = basename($f); - - if ( !is_dir( $f ) ) { - $m = array(); - if ( !preg_match( '/(.*)\.(tgz|tar\.gz|zip)/', $n, $m ) ) continue; - $n = $m[1]; - } - - print "\t$n\n"; - } - } - - function getResource( $name ) { - $path = $this->path . '/' . $name; - - if ( !file_exists( $path ) || !is_dir( $path ) ) $path = $this->path . '/' . $name . '.tgz'; - if ( !file_exists( $path ) ) $path = $this->path . '/' . $name . '.tar.gz'; - if ( !file_exists( $path ) ) $path = $this->path . '/' . $name . '.zip'; - - return new LocalInstallerResource( $path ); - } -} - -/** - * @ingroup Maintenance - */ -class WebInstallerRepository extends InstallerRepository { - - function WebInstallerRepository ( $path ) { - InstallerRepository::InstallerRepository( $path ); - } - - function printListing( ) { - ExtensionInstaller::note( "listing index from {$this->path}..." ); - - $txt = @file_get_contents( $this->path . '/index.txt' ); - if ( $txt ) { - print $txt; - print "\n"; - } - else { - $txt = file_get_contents( $this->path ); - if ( !$txt ) { - ExtensionInstaller::error( "listing index from {$this->path} failed!" ); - print ( $txt ); - return false; - } - - $m = array(); - $ok = preg_match_all( '!<a\s[^>]*href\s*=\s*['."'".'"]([^/'."'".'"]+)\.tgz['."'".'"][^>]*>.*?</a>!si', $txt, $m, PREG_SET_ORDER ); - if ( !$ok ) { - ExtensionInstaller::error( "listing index from {$this->path} does not match!" ); - print ( $txt ); - return false; - } - - foreach ( $m as $l ) { - $n = $l[1]; - print "\t$n\n"; - } - } - } - - function getResource( $name ) { - $path = $this->path . '/' . $name . '.tgz'; - return new WebInstallerResource( $path ); - } -} - -/** - * @ingroup Maintenance - */ -class SVNInstallerRepository extends InstallerRepository { - - function SVNInstallerRepository ( $path ) { - InstallerRepository::InstallerRepository( $path ); - } - - function printListing( ) { - ExtensionInstaller::note( "SVN list {$this->path}..." ); - $code = null; // Shell Exec return value. - $txt = wfShellExec( 'svn ls ' . escapeshellarg( $this->path ), $code ); - if ( $code !== 0 ) { - ExtensionInstaller::error( "svn list for {$this->path} failed!" ); - return false; - } - - $ll = preg_split('/(\s*[\r\n]\s*)+/', $txt); - - foreach ( $ll as $line ) { - $m = array(); - if ( !preg_match('!^(.*)/$!', $line, $m) ) continue; - $n = $m[1]; - - print "\t$n\n"; - } - } - - function getResource( $name ) { - $path = $this->path . '/' . $name; - return new SVNInstallerResource( $path ); - } -} - -/** - * @ingroup Maintenance - */ -class InstallerResource { - var $path; - var $isdir; - var $islocal; - - function InstallerResource( $path, $isdir, $islocal ) { - $this->path = $path; - - $this->isdir= $isdir; - $this->islocal = $islocal; - - $m = array(); - preg_match( '!([-+\w]+://)?.*?(\.[-\w\d.]+)?$!', $path, $m ); - - $this->protocol = @$m[1]; - $this->extensions = @$m[2]; - - if ( $this->extensions ) $this->extensions = strtolower( $this->extensions ); - } - - function fetch( $target ) { - trigger_error( 'override InstallerResource::fetch()', E_USER_ERROR ); - } - - function extract( $file, $target ) { - - if ( $this->extensions == '.tgz' || $this->extensions == '.tar.gz' ) { #tgz file - ExtensionInstaller::note( "extracting $file..." ); - $code = null; // shell Exec return value. - wfShellExec( 'tar zxvf ' . escapeshellarg( $file ) . ' -C ' . escapeshellarg( $target ), $code ); - - if ( $code !== 0 ) { - ExtensionInstaller::error( "failed to extract $file!" ); - return false; - } - } - else if ( $this->extensions == '.zip' ) { #zip file - ExtensionInstaller::note( "extracting $file..." ); - $code = null; // shell Exec return value. - wfShellExec( 'unzip ' . escapeshellarg( $file ) . ' -d ' . escapeshellarg( $target ) , $code ); - - if ( $code !== 0 ) { - ExtensionInstaller::error( "failed to extract $file!" ); - return false; - } - } - else { - ExtensionInstaller::error( "unknown extension {$this->extensions}!" ); - return false; - } - - return true; - } - - /*static*/ function makeResource( $url ) { - $m = array(); - preg_match( '!(([-+\w]+)://)?.*?(\.[-\w\d.]+)?$!', $url, $m ); - $proto = @$m[2]; - $ext = @$m[3]; - if ( $ext ) $ext = strtolower( $ext ); - - if ( !$proto ) { return new LocalInstallerResource( $url, $ext ? false : true ); } - else if ( $ext && ( $proto == 'http' || $proto == 'http' || $proto == 'ftp' ) ) { return new WebInstallerResource( $url ); } - else { return new SVNInstallerResource( $url ); } - } -} - -/** - * @ingroup Maintenance - */ -class LocalInstallerResource extends InstallerResource { - function LocalInstallerResource( $path ) { - InstallerResource::InstallerResource( $path, is_dir( $path ), true ); - } - - function fetch( $target ) { - if ( $this->isdir ) return ExtensionInstaller::copyDir( $this->path, dirname( $target ) ); - else return $this->extract( $this->path, dirname( $target ) ); - } - -} - -/** - * @ingroup Maintenance - */ -class WebInstallerResource extends InstallerResource { - function WebInstallerResource( $path ) { - InstallerResource::InstallerResource( $path, false, false ); - } - - function fetch( $target ) { - $tmp = wfTempDir() . '/' . basename( $this->path ); - - ExtensionInstaller::note( "downloading {$this->path}..." ); - $ok = copy( $this->path, $tmp ); - - if ( !$ok ) { - ExtensionInstaller::error( "failed to download {$this->path}" ); - return false; - } - - $this->extract( $tmp, dirname( $target ) ); - unlink($tmp); - - return true; - } -} - -/** - * @ingroup Maintenance - */ -class SVNInstallerResource extends InstallerResource { - function SVNInstallerResource( $path ) { - InstallerResource::InstallerResource( $path, true, false ); - } - - function fetch( $target ) { - ExtensionInstaller::note( "SVN checkout of {$this->path}..." ); - $code = null; // shell exec return val. - wfShellExec( 'svn co ' . escapeshellarg( $this->path ) . ' ' . escapeshellarg( $target ), $code ); - - if ( $code !== 0 ) { - ExtensionInstaller::error( "checkout failed for {$this->path}!" ); - return false; - } - - return true; - } -} - -/** - * @ingroup Maintenance - */ -class ExtensionInstaller { - var $source; - var $target; - var $name; - var $dir; - var $tasks; - - function ExtensionInstaller( $name, $source, $target ) { - if ( !is_object( $source ) ) $source = InstallerResource::makeResource( $source ); - - $this->name = $name; - $this->source = $source; - $this->target = realpath( $target ); - $this->extdir = "$target/extensions"; - $this->dir = "{$this->extdir}/$name"; - $this->incpath = "extensions/$name"; - $this->tasks = array(); - - #TODO: allow a subdir different from "extensions" - #TODO: allow a config file different from "LocalSettings.php" - } - - static function note( $msg ) { - print "$msg\n"; - } - - static function warn( $msg ) { - print "WARNING: $msg\n"; - } - - static function error( $msg ) { - print "ERROR: $msg\n"; - } - - function prompt( $msg ) { - if ( function_exists( 'readline' ) ) { - $s = readline( $msg ); - } - else { - if ( !@$this->stdin ) $this->stdin = fopen( 'php://stdin', 'r' ); - if ( !$this->stdin ) die( "Failed to open stdin for user interaction!\n" ); - - print $msg; - flush(); - - $s = fgets( $this->stdin ); - } - - $s = trim( $s ); - return $s; - } - - function confirm( $msg ) { - while ( true ) { - $s = $this->prompt( $msg . " [yes/no]: "); - $s = strtolower( trim($s) ); - - if ( $s == 'yes' || $s == 'y' ) { return true; } - else if ( $s == 'no' || $s == 'n' ) { return false; } - else { print "bad response: $s\n"; } - } - } - - function deleteContents( $dir ) { - $ff = glob( $dir . "/*" ); - if ( !$ff ) return; - - foreach ( $ff as $f ) { - if ( is_dir( $f ) && !is_link( $f ) ) $this->deleteContents( $f ); - unlink( $f ); - } - } - - function copyDir( $dir, $tgt ) { - $d = $tgt . '/' . basename( $dir ); - - if ( !file_exists( $d ) ) { - $ok = mkdir( $d ); - if ( !$ok ) { - ExtensionInstaller::error( "failed to create director $d" ); - return false; - } - } - - $ff = glob( $dir . "/*" ); - if ( $ff === false || $ff === NULL ) return false; - - foreach ( $ff as $f ) { - if ( is_dir( $f ) && !is_link( $f ) ) { - $ok = ExtensionInstaller::copyDir( $f, $d ); - if ( !$ok ) return false; - } - else { - $t = $d . '/' . basename( $f ); - $ok = copy( $f, $t ); - - if ( !$ok ) { - ExtensionInstaller::error( "failed to copy $f to $t" ); - return false; - } - } - } - - return true; - } - - function setPermissions( $dir, $dirbits, $filebits ) { - if ( !chmod( $dir, $dirbits ) ) ExtensionInstaller::warn( "faield to set permissions for $dir" ); - - $ff = glob( $dir . "/*" ); - if ( $ff === false || $ff === NULL ) return false; - - foreach ( $ff as $f ) { - $n= basename( $f ); - if ( $n{0} == '.' ) continue; #HACK: skip dot files - - if ( is_link( $f ) ) continue; #skip link - - if ( is_dir( $f ) ) { - ExtensionInstaller::setPermissions( $f, $dirbits, $filebits ); - } - else { - if ( !chmod( $f, $filebits ) ) ExtensionInstaller::warn( "faield to set permissions for $f" ); - } - } - - return true; - } - - function fetchExtension( ) { - if ( $this->source->islocal && $this->source->isdir && realpath( $this->source->path ) === $this->dir ) { - $this->note( "files are already in the extension dir" ); - return true; - } - - if ( file_exists( $this->dir ) && glob( $this->dir . "/*" ) ) { - if ( $this->confirm( "{$this->dir} exists and is not empty.\nDelete all files in that directory?" ) ) { - $this->deleteContents( $this->dir ); - } - else { - return false; - } - } - - $ok = $this->source->fetch( $this->dir ); - if ( !$ok ) return false; - - if ( !file_exists( $this->dir ) && glob( $this->dir . "/*" ) ) { - $this->error( "{$this->dir} does not exist or is empty. Something went wrong, sorry." ); - return false; - } - - if ( file_exists( $this->dir . '/README' ) ) $this->tasks[] = "read the README file in {$this->dir}"; - if ( file_exists( $this->dir . '/INSTALL' ) ) $this->tasks[] = "read the INSTALL file in {$this->dir}"; - if ( file_exists( $this->dir . '/RELEASE-NOTES' ) ) $this->tasks[] = "read the RELEASE-NOTES file in {$this->dir}"; - - #TODO: configure this smartly...? - $this->setPermissions( $this->dir, 0755, 0644 ); - - $this->note( "fetched extension to {$this->dir}" ); - return true; - } - - function patchLocalSettings( $mode ) { - #NOTE: if we get a better way to hook up extensions, that should be used instead. - - $f = $this->dir . '/install.settings'; - $t = $this->target . '/LocalSettings.php'; - - #TODO: assert version ?! - #TODO: allow custom installer scripts + sql patches - - if ( !file_exists( $f ) ) { - self::warn( "No install.settings file provided!" ); - $this->tasks[] = "Please read the instructions and edit LocalSettings.php manually to activate the extension."; - return '?'; - } - else { - self::note( "applying settings patch..." ); - } - - $settings = file_get_contents( $f ); - - if ( !$settings ) { - self::error( "failed to read settings from $f!" ); - return false; - } - - $settings = str_replace( '{{path}}', $this->incpath, $settings ); - - if ( $mode == EXTINST_NOPATCH ) { - $this->tasks[] = "Please put the following into your LocalSettings.php:" . "\n$settings\n"; - self::note( "Skipping patch phase, automatic patching is off." ); - return true; - } - - if ( $mode == EXTINST_HOTPATCH ) { - #NOTE: keep php extension for backup file! - $bak = $this->target . '/LocalSettings.install-' . $this->name . '-' . wfTimestamp(TS_MW) . '.bak.php'; - - $ok = copy( $t, $bak ); - - if ( !$ok ) { - self::warn( "failed to create backup of LocalSettings.php!" ); - return false; - } - else { - self::note( "created backup of LocalSettings.php at $bak" ); - } - } - - $localsettings = file_get_contents( $t ); - - if ( !$settings ) { - self::error( "failed to read $t for patching!" ); - return false; - } - - $marker = "<@< extension {$this->name} >@>"; - $blockpattern = "/\n\s*#\s*BEGIN\s*$marker.*END\s*$marker\s*/smi"; - - if ( preg_match( $blockpattern, $localsettings ) ) { - $localsettings = preg_replace( $blockpattern, "\n", $localsettings ); - $this->warn( "removed old configuration block for extension {$this->name}!" ); - } - - $newblock= "\n# BEGIN $marker\n$settings\n# END $marker\n"; - - $localsettings = preg_replace( "/\?>\s*$/si", "$newblock?>", $localsettings ); - - if ( $mode != EXTINST_HOTPATCH ) { - $t = $this->target . '/LocalSettings.install-' . $this->name . '-' . wfTimestamp(TS_MW) . '.php'; - } - - $ok = file_put_contents( $t, $localsettings ); - - if ( !$ok ) { - self::error( "failed to patch $t!" ); - return false; - } - else if ( $mode == EXTINST_HOTPATCH ) { - self::note( "successfully patched $t" ); - } - else { - self::note( "created patched settings file $t" ); - $this->tasks[] = "Replace your current LocalSettings.php with ".basename($t); - } - - return true; - } - - function printNotices( ) { - if ( !$this->tasks ) { - $this->note( "Installation is complete, no pending tasks" ); - } - else { - $this->note( "" ); - $this->note( "PENDING TASKS:" ); - $this->note( "" ); - - foreach ( $this->tasks as $t ) { - $this->note ( "* " . $t ); - } - - $this->note( "" ); - } - - return true; - } - -} - -$tgt = isset ( $options['target'] ) ? $options['target'] : $IP; - -$repos = @$options['repository']; -if ( !$repos ) $repos = @$options['repos']; -if ( !$repos ) $repos = @$wgExtensionInstallerRepository; - -if ( !$repos && file_exists("$tgt/.svn") && is_dir("$tgt/.svn") ) { - $svn = file_get_contents( "$tgt/.svn/entries" ); - - $m = array(); - if ( preg_match( '!url="(.*?)"!', $svn, $m ) ) { - $repos = dirname( $m[1] ) . '/extensions'; - } -} - -if ( !$repos ) $repos = 'http://svn.wikimedia.org/svnroot/mediawiki/trunk/extensions'; - -if( !isset( $args[0] ) && !@$options['list'] ) { - die( "USAGE: installExtension.php [options] <name> [source]\n" . - "OPTIONS: \n" . - " --list list available extensions. <name> is ignored / may be omitted.\n" . - " --repository <n> repository to fetch extensions from. May be a local directoy,\n" . - " an SVN repository or a HTTP directory\n" . - " --target <dir> mediawiki installation directory to use\n" . - " --nopatch don't create a patched LocalSettings.php\n" . - " --hotpatch patched LocalSettings.php directly (creates a backup)\n" . - "SOURCE: specifies the package source directly. If given, the repository is ignored.\n" . - " The source my be a local file (tgz or zip) or directory, the URL of a\n" . - " remote file (tgz or zip), or a SVN path.\n" - ); -} - -$repository = InstallerRepository::makeRepository( $repos ); - -if ( isset( $options['list'] ) ) { - $repository->printListing(); - exit(0); -} - -$name = $args[0]; - -$src = isset( $args[1] ) ? $args[1] : $repository->getResource( $name ); - -#TODO: detect $source mismatching $name !! - -$mode = EXTINST_WRITEPATCH; -if ( isset( $options['nopatch'] ) || @$wgExtensionInstallerNoPatch ) { $mode = EXTINST_NOPATCH; } -else if ( isset( $options['hotpatch'] ) || @$wgExtensionInstallerHotPatch ) { $mode = EXTINST_HOTPATCH; } - -if ( !file_exists( "$tgt/LocalSettings.php" ) ) { - die("can't find $tgt/LocalSettings.php\n"); -} - -if ( $mode == EXTINST_HOTPATCH && !is_writable( "$tgt/LocalSettings.php" ) ) { - die("can't write to $tgt/LocalSettings.php\n"); -} - -if ( !file_exists( "$tgt/extensions" ) ) { - die("can't find $tgt/extensions\n"); -} - -if ( !is_writable( "$tgt/extensions" ) ) { - die("can't write to $tgt/extensions\n"); -} - -$installer = new ExtensionInstaller( $name, $src, $tgt ); - -$installer->note( "Installing extension {$installer->name} from {$installer->source->path} to {$installer->dir}" ); - -print "\n"; -print "\tTHIS TOOL IS EXPERIMENTAL!\n"; -print "\tEXPECT THE UNEXPECTED!\n"; -print "\n"; - -if ( !$installer->confirm("continue") ) die("aborted\n"); - -$ok = $installer->fetchExtension(); - -if ( $ok ) $ok = $installer->patchLocalSettings( $mode ); - -if ( $ok ) $ok = $installer->printNotices(); - -if ( $ok ) $installer->note( "$name extension installed." ); - diff --git a/maintenance/interwiki.list b/maintenance/interwiki.list new file mode 100644 index 00000000..d6abd1a7 --- /dev/null +++ b/maintenance/interwiki.list @@ -0,0 +1,97 @@ +# Based more or less on the public interwiki map from MeatballWiki +# Default interwiki prefixes... +acronym|http://www.acronymfinder.com/af-query.asp?String=exact&Acronym=$1|0 +advogato|http://www.advogato.org/$1|0 +annotationwiki|http://www.seedwiki.com/page.cfm?wikiid=368&doc=$1|0 +arxiv|http://www.arxiv.org/abs/$1|0 +c2find|http://c2.com/cgi/wiki?FindPage&value=$1|0 +cache|http://www.google.com/search?q=cache:$1|0 +commons|http://commons.wikimedia.org/wiki/$1|0 +corpknowpedia|http://corpknowpedia.org/wiki/index.php/$1|0 +dictionary|http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query=$1|0 +disinfopedia|http://www.disinfopedia.org/wiki.phtml?title=$1|0 +docbook|http://wiki.docbook.org/topic/$1|0 +doi|http://dx.doi.org/$1|0 +drumcorpswiki|http://www.drumcorpswiki.com/index.php/$1|0 +dwjwiki|http://www.suberic.net/cgi-bin/dwj/wiki.cgi?$1|0 +emacswiki|http://www.emacswiki.org/cgi-bin/wiki.pl?$1|0 +elibre|http://enciclopedia.us.es/index.php/$1|0 +foldoc|http://foldoc.org/?$1|0 +foxwiki|http://fox.wikis.com/wc.dll?Wiki~$1|0 +freebsdman|http://www.FreeBSD.org/cgi/man.cgi?apropos=1&query=$1|0 +gej|http://www.esperanto.de/cgi-bin/aktivikio/wiki.pl?$1|0 +gentoo-wiki|http://gentoo-wiki.com/$1|0 +google|http://www.google.com/search?q=$1|0 +googlegroups|http://groups.google.com/groups?q=$1|0 +hammondwiki|http://www.dairiki.org/HammondWiki/$1|0 +hewikisource|http://he.wikisource.org/wiki/$1|1 +hrwiki|http://www.hrwiki.org/index.php/$1|0 +imdb|http://us.imdb.com/Title?$1|0 +jargonfile|http://sunir.org/apps/meta.pl?wiki=JargonFile&redirect=$1|0 +jspwiki|http://www.jspwiki.org/wiki/$1|0 +keiki|http://kei.ki/en/$1|0 +kmwiki|http://kmwiki.wikispaces.com/$1|0 +linuxwiki|http://linuxwiki.de/$1|0 +lojban|http://www.lojban.org/tiki/tiki-index.php?page=$1|0 +lqwiki|http://wiki.linuxquestions.org/wiki/$1|0 +lugkr|http://lug-kr.sourceforge.net/cgi-bin/lugwiki.pl?$1|0 +mathsongswiki|http://SeedWiki.com/page.cfm?wikiid=237&doc=$1|0 +meatball|http://www.usemod.com/cgi-bin/mb.pl?$1|0 +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 +# patwiki|http://gauss.ffii.org/$1|0 # 2008-02-27: lots of spambots +pmeg|http://www.bertilow.com/pmeg/$1.php|0 +ppr|http://c2.com/cgi/wiki?$1|0 +pythoninfo|http://wiki.python.org/moin/$1|0 +rfc|http://www.rfc-editor.org/rfc/rfc$1.txt|0 +s23wiki|http://is-root.de/wiki/index.php/$1|0 +seattlewiki|http://seattle.wikia.com/wiki/$1|0 +seattlewireless|http://seattlewireless.net/?$1|0 +senseislibrary|http://senseis.xmp.net/?$1|0 +# slashdot|http://slashdot.org/article.pl?sid=$1|0 # 2008-02-27: update me +sourceforge|http://sourceforge.net/$1|0 +squeak|http://wiki.squeak.org/squeak/$1|0 +susning|http://www.susning.nu/$1|0 +svgwiki|http://wiki.svg.org/$1|0 +tavi|http://tavi.sourceforge.net/$1|0 +tejo|http://www.tejo.org/vikio/$1|0 +tmbw|http://www.tmbw.net/wiki/$1|0 +tmnet|http://www.technomanifestos.net/?$1|0 +tmwiki|http://www.EasyTopicMaps.com/?page=$1|0 +theopedia|http://www.theopedia.com/$1|0 +twiki|http://twiki.org/cgi-bin/view/$1|0 +uea|http://www.tejo.org/uea/$1|0 +unreal|http://wiki.beyondunreal.com/wiki/$1|0 +usemod|http://www.usemod.com/cgi-bin/wiki.pl?$1|0 +vinismo|http://vinismo.com/en/$1|0 +webseitzwiki|http://webseitz.fluxent.com/wiki/$1|0 +why|http://clublet.com/c/c/why?$1|0 +wiki|http://c2.com/cgi/wiki?$1|0 +wikia|http://www.wikia.com/wiki/$1|0 +wikibooks|http://en.wikibooks.org/wiki/$1|1 +wikicities|http://www.wikia.com/wiki/$1|0 +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 +wikinews|http://en.wikinews.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 +wlug|http://www.wlug.org.nz/$1|0 +zwiki|http://zwiki.org/$1|0 +zzz wiki|http://wiki.zzz.ee/index.php/$1|0 diff --git a/maintenance/interwiki.sql b/maintenance/interwiki.sql index 2ce0e23f..6efc1e0e 100644 --- a/maintenance/interwiki.sql +++ b/maintenance/interwiki.sql @@ -87,7 +87,7 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local) VALUES ('wikimedia','http://wikimediafoundation.org/wiki/$1',0), ('wikinews','http://en.wikinews.org/wiki/$1',1), ('wikiquote','http://en.wikiquote.org/wiki/$1',1), -('wikipedia', 'http://en.wikipedia.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), diff --git a/maintenance/lag.php b/maintenance/lag.php index 47b4c47b..fdc74293 100644 --- a/maintenance/lag.php +++ b/maintenance/lag.php @@ -6,7 +6,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class DatabaseLag extends Maintenance { public function __construct() { @@ -19,18 +19,18 @@ class DatabaseLag extends Maintenance { if ( $this->hasOption( 'r' ) ) { $lb = wfGetLB(); echo 'time '; - for( $i = 1; $i < $lb->getServerCount(); $i++ ) { + for ( $i = 1; $i < $lb->getServerCount(); $i++ ) { $hostname = $lb->getServerName( $i ); printf( "%-12s ", $hostname ); } echo "\n"; - while( 1 ) { + while ( 1 ) { $lb->clearLagTimeCache(); $lags = $lb->getLagTimes(); unset( $lags[0] ); echo gmdate( 'H:i:s' ) . ' '; - foreach( $lags as $i => $lag ) { + foreach ( $lags as $lag ) { printf( "%-12s " , $lag === false ? 'false' : $lag ); } echo "\n"; @@ -39,7 +39,7 @@ class DatabaseLag extends Maintenance { } else { $lb = wfGetLB(); $lags = $lb->getLagTimes(); - foreach( $lags as $i => $lag ) { + foreach ( $lags as $i => $lag ) { $name = $lb->getServerName( $i ); $this->output( sprintf( "%-20s %s\n" , $name, $lag === false ? 'false' : $lag ) ); } @@ -48,4 +48,4 @@ class DatabaseLag extends Maintenance { } $maintClass = "DatabaseLag"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/language/StatOutputs.php b/maintenance/language/StatOutputs.php index 169a4d41..b8e28302 100644 --- a/maintenance/language/StatOutputs.php +++ b/maintenance/language/StatOutputs.php @@ -1,12 +1,12 @@ <?php -if (!defined('MEDIAWIKI')) die(); +if ( !defined( 'MEDIAWIKI' ) ) die(); /** * Statistic output classes. * * @file * @ingroup MaintenanceLanguage * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @author Ashar Voultoiz <thoane@altern.org> + * @author Ashar Voultoiz <hashar at free dot fr> */ /** A general output object. Need to be overriden */ @@ -31,12 +31,11 @@ class statsOutput { /** Outputs WikiText */ class wikiStatsOutput extends statsOutput { function heading() { - global $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"; - echo '{| class="sortable wikitable" border="2" cellpadding="4" cellspacing="0" style="background-color: #F9F9F9; border: 1px #AAAAAA solid; border-collapse: collapse; clear:both;" width="100%"'."\n"; + echo '{| class="sortable wikitable" border="2" cellpadding="4" cellspacing="0" style="background-color: #F9F9F9; border: 1px #AAAAAA solid; border-collapse: collapse; clear:both;" width="100%"' . "\n"; } function footer() { echo "|}\n"; @@ -48,11 +47,15 @@ class wikiStatsOutput extends statsOutput { echo ''; } function element( $in, $heading = false ) { - echo ($heading ? '!' : '|') . "$in\n"; + echo ( $heading ? '!' : '|' ) . "$in\n"; } function formatPercent( $subset, $total, $revert = false, $accuracy = 2 ) { - $v = @round(255 * $subset / $total); + $v = @round( 255 * $subset / $total ); if ( $revert ) { + # Weigh reverse with factor 20 so coloring takes effect more quickly as + # this option is used solely for reporting 'bad' percentages. + $v = $v * 20; + if ( $v > 255 ) $v = 255; $v = 255 - $v; } if ( $v < 128 ) { @@ -61,21 +64,21 @@ class wikiStatsOutput extends statsOutput { $green = sprintf( '%02X', 2 * $v ); } else { # Yellow to Green - $red = sprintf('%02X', 2 * ( 255 - $v ) ); + $red = sprintf( '%02X', 2 * ( 255 - $v ) ); $green = 'FF'; } $blue = '00'; $color = $red . $green . $blue; - $percent = statsOutput::formatPercent( $subset, $total, $revert, $accuracy ); - return 'bgcolor="#'. $color .'"|'. $percent; + $percent = parent::formatPercent( $subset, $total, $revert, $accuracy ); + return 'bgcolor="#' . $color . '"|' . $percent; } } /** Output text. To be used on a terminal for example. */ class textStatsOutput extends statsOutput { function element( $in, $heading = false ) { - echo $in."\t"; + echo $in . "\t"; } function blockend() { echo "\n"; diff --git a/maintenance/language/alltrans.php b/maintenance/language/alltrans.php index 420386fd..f872e6a6 100644 --- a/maintenance/language/alltrans.php +++ b/maintenance/language/alltrans.php @@ -20,7 +20,7 @@ * @ingroup MaintenanceLanguage */ -require_once( dirname(__FILE__) . '/../Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/../Maintenance.php' ); class AllTrans extends Maintenance { public function __construct() { @@ -29,12 +29,12 @@ class AllTrans extends Maintenance { } public function execute() { - $wgEnglishMessages = array_keys( Language::getMessagesFor( 'en' ) ); - foreach( $wgEnglishMessages as $key ) { + $englishMessages = array_keys( Language::getMessagesFor( 'en' ) ); + foreach ( $englishMessages as $key ) { $this->output( "$key\n" ); } } } $maintClass = "AllTrans"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/language/checkDupeMessages.php b/maintenance/language/checkDupeMessages.php index 81eafccf..ea9d5fb7 100644 --- a/maintenance/language/checkDupeMessages.php +++ b/maintenance/language/checkDupeMessages.php @@ -5,22 +5,22 @@ * @ingroup MaintenanceLanguage */ -require_once( dirname(__FILE__).'/../commandLine.inc' ); -$messagesDir = dirname(__FILE__).'/../../languages/messages/'; +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'] )) { +if ( isset( $options['lang'] ) && isset( $options['clang'] ) ) { + if ( !isset( $options['mode'] ) ) { $runMode = 'text'; } else { - if (!strcmp($options['mode'],'wiki')) { + if ( !strcmp( $options['mode'], 'wiki' ) ) { $runMode = 'wiki'; - } else if (!strcmp($options['mode'],'php')) { + } else if ( !strcmp( $options['mode'], 'php' ) ) { $runMode = 'php'; - } else if (!strcmp($options['mode'],'raw')) { + } else if ( !strcmp( $options['mode'], 'raw' ) ) { $runMode = 'raw'; } else { } @@ -45,11 +45,11 @@ TEXT; 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)) { + $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 { @@ -59,60 +59,60 @@ if ( $runTest ) { // Run to check the dupes if ( $run ) { - if (!strcmp($runMode,'wiki')) { + if ( !strcmp( $runMode, 'wiki' ) ) { $runMode = 'wiki'; - } else if (!strcmp($runMode,'raw')) { + } else if ( !strcmp( $runMode, 'raw' ) ) { $runMode = 'raw'; } include( $messagesFile ); - $messageExist = isset($messages); - if ($messageExist) + $messageExist = isset( $messages ); + if ( $messageExist ) $wgMessages[$langCode] = $messages; include( $messagesFileC ); - $messageCExist = isset($messages); - if ($messageCExist) + $messageCExist = isset( $messages ); + if ( $messageCExist ) $wgMessages[$langCodeC] = $messages; $count = 0; - if (($messageExist) && ($messageCExist)) { + if ( ( $messageExist ) && ( $messageCExist ) ) { - if (!strcmp($runMode,'php')) { - print("<?php\n"); - print('$dupeMessages = array('."\n"); + 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"); + 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"); + print( "* $key\n" ); } $count++; } } } } - if (!strcmp($runMode,'php')) { - print(");\n"); + if ( !strcmp( $runMode, 'php' ) ) { + print( ");\n" ); } - if (!strcmp($runMode,'text')) { - if ($count == 1) { + 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) + if ( !$messageExist ) echo "There are no messages defined in $langCode.\n"; - if (!$messageCExist) + if ( !$messageCExist ) echo "There are no messages defined in $langCodeC.\n"; - } + } } diff --git a/maintenance/language/checkExtensions.php b/maintenance/language/checkExtensions.php index ed1855c1..c05cf193 100644 --- a/maintenance/language/checkExtensions.php +++ b/maintenance/language/checkExtensions.php @@ -6,11 +6,11 @@ * @ingroup MaintenanceLanguage */ -require_once( dirname(__FILE__).'/../commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/../commandLine.inc' ); require_once( 'languages.inc' ); require_once( 'checkLanguage.inc' ); -if( !class_exists( 'MessageGroups' ) || !class_exists( 'PremadeMediawikiExtensionGroups' ) ) { +if ( !class_exists( 'MessageGroups' ) || !class_exists( 'PremadeMediawikiExtensionGroups' ) ) { echo <<<TEXT Please add the Translate extension to LocalSettings.php, and enable the extension groups: require_once( 'extensions/Translate/Translate.php' ); @@ -18,7 +18,7 @@ Please add the Translate extension to LocalSettings.php, and enable the extensio If you still get this message, update Translate to its latest version. TEXT; - exit(-1); + exit( -1 ); } $cli = new CheckExtensionsCLI( $options, $argv[0] ); diff --git a/maintenance/language/checkLanguage.inc b/maintenance/language/checkLanguage.inc index fc77aad3..d8480c0f 100644 --- a/maintenance/language/checkLanguage.inc +++ b/maintenance/language/checkLanguage.inc @@ -1,8 +1,8 @@ <?php + /** * @ingroup MaintenanceLanguage */ - class CheckLanguageCLI { protected $code = null; protected $level = 2; @@ -190,17 +190,23 @@ class CheckLanguageCLI { Run this script to check a specific language file, or all of them. Command line settings are in form --parameter[=value]. Parameters: - * lang: Language code (default: the installation default language). - * all: Check all customized languages. - * 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). - * easy: Do only the easy checks, which can be treated by non-speakers of the language. - * noexif: Don't check for EXIF messages (a bit hard and boring to translate), if you know that they are currently not translated and want to focus on other problems (default off). + --help: Show this help. + --lang: Language code (default: the installation default language). + --all: Check all customized languages. + --level: Show the following display level (default: 2): + * 0: Skip the checks (useful for checking syntax). + * 1: Show only the stub headers and number of wrong messages, without list of messages. + * 2: Show only the headers and the message keys, without the message values. + * 3: Show both the headers and the complete messages, with both keys and values. + --links: Link the message values (default off). + --prefix: prefix to add to links. + --wikilang: For the links, what is the content language of the wiki to display the output in (default en). + --noexif: Don't check for EXIF messages (a bit hard and boring to translate), if you know + that they are currently not translated and want to focus on other problems (default off). + --whitelist: Do only the following checks (form: code,code). + --blacklist: Don't do the following checks (form: code,code). + --easy: Do only the easy checks, which can be treated by non-speakers of the language. + Check codes (ideally, all of them should result 0; all the checks are executed by default (except language-specific check blacklists in checkLanguage.inc): * untranslated: Messages which are required to translate, but are not translated. * duplicate: Messages which translation equal to fallback @@ -220,11 +226,6 @@ Check codes (ideally, all of them should result 0; all the checks are executed b * magic-case: Magic words whose translation changes the case-sensitivity of the original English word. * special: Special page names that were not translated. * special-old: Special page names which do not exist. -Display levels (default: 2): - * 0: Skip the checks (useful for checking syntax). - * 1: Show only the stub headers and number of wrong messages, without list of messages. - * 2: Show only the headers and the message keys, without the message values. - * 3: Show both the headers and the complete messages, with both keys and values. ENDS; } @@ -382,7 +383,7 @@ ENDS; * @return The checks results as wiki text. */ function outputWiki() { - global $wgContLang, $IP; + global $wgContLang; $detailText = ''; $rows[] = '! Language !! Code !! Total !! ' . implode( ' !! ', array_diff( $this->checks, $this->nonMessageChecks() ) ); foreach ( $this->results as $code => $results ) { @@ -443,8 +444,8 @@ EOL; * @return True if there are any results, false if not. */ protected function isEmpty() { - foreach( $this->results as $code => $results ) { - foreach( $results as $check => $messages ) { + foreach( $this->results as $results ) { + foreach( $results as $messages ) { if( !empty( $messages ) ) { return false; } @@ -454,6 +455,9 @@ EOL; } } +/** + * @ingroup MaintenanceLanguage + */ class CheckExtensionsCLI extends CheckLanguageCLI { private $extensions; diff --git a/maintenance/language/checkLanguage.php b/maintenance/language/checkLanguage.php index 7a4d3dd2..9396e8c1 100644 --- a/maintenance/language/checkLanguage.php +++ b/maintenance/language/checkLanguage.php @@ -6,7 +6,7 @@ * @ingroup MaintenanceLanguage */ -require_once( dirname(__FILE__).'/../commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/../commandLine.inc' ); require_once( 'checkLanguage.inc' ); require_once( 'languages.inc' ); @@ -14,6 +14,6 @@ $cli = new CheckLanguageCLI( $options ); try { $cli->execute(); -} catch( MWException $e ) { +} catch ( MWException $e ) { print 'Error: ' . $e->getMessage() . "\n"; } diff --git a/maintenance/language/countMessages.php b/maintenance/language/countMessages.php index 826c43cb..f949ddc2 100644 --- a/maintenance/language/countMessages.php +++ b/maintenance/language/countMessages.php @@ -20,7 +20,7 @@ * @ingroup MaintenanceLanguage */ -require_once( dirname(__FILE__) . '/../Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/../Maintenance.php' ); class CountMessages extends Maintenance { public function __construct() { @@ -35,12 +35,12 @@ class CountMessages extends Maintenance { $nonZero = 0; foreach ( glob( "$dir/*.php" ) as $file ) { $baseName = basename( $file ); - if( !preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $baseName, $m ) ) { + 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"; + // print "$code: $numMessages\n"; $total += $numMessages; if ( $numMessages > 0 ) { $nonZero ++; @@ -62,4 +62,4 @@ class CountMessages extends Maintenance { } $maintClass = "CountMessages"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/language/date-formats.php b/maintenance/language/date-formats.php index 54a6a26d..04f5e8ba 100644 --- a/maintenance/language/date-formats.php +++ b/maintenance/language/date-formats.php @@ -20,7 +20,7 @@ * @ingroup MaintenanceLanguage */ -require_once( dirname(__FILE__) . '/../Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/../Maintenance.php' ); class DateFormats extends Maintenance { @@ -60,7 +60,7 @@ class DateFormats extends Maintenance { } $this->output( $lang->time( $this->ts, false, $pref ) ); } - $this->output( "\n$code both: " ); + $this->output( "\n$code both: " ); foreach ( $prefs as $index => $pref ) { if ( $index > 0 ) { $this->output( ' | ' ); @@ -73,4 +73,4 @@ class DateFormats extends Maintenance { } $maintClass = "DateFormats"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/language/diffLanguage.php b/maintenance/language/diffLanguage.php deleted file mode 100644 index bbdb8653..00000000 --- a/maintenance/language/diffLanguage.php +++ /dev/null @@ -1,159 +0,0 @@ -<?php -# MediaWiki web-based config/installation -# Copyright (C) 2004 Ashar Voultoiz <thoane@altern.org> and others -# 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 - -/** - * Usage: php DiffLanguage.php [lang [file]] - * - * lang: Enter the language code following "Language" of the LanguageXX.php you - * want to check. If using linux you might need to follow case aka Zh and not - * zh. - * - * file: A php language file you want to include to compare mediawiki - * Language{Lang}.php against (for example Special:Allmessages PHP output). - * - * The goal is to get a list of messages not yet localised in a languageXX.php - * file using the language.php file as reference. - * - * The script then print a list of wgAllMessagesXX keys that aren't localised, a - * percentage of messages correctly localised and the number of messages to be - * translated. - * - * @file - * @ingroup MaintenanceLanguage - */ - -/** This script run from the commandline */ -require_once( dirname(__FILE__).'/../parserTests.inc' ); -require_once( dirname(__FILE__).'/../commandLine.inc' ); - -if( isset($options['help']) ) { usage(); wfDie(); } - -$wgLanguageCode = ucfirstlcrest($wgLanguageCode); -/** Language messages we will use as reference. By default 'en' */ -$referenceMessages = $wgAllMessagesEn; -$referenceLanguage = 'En'; -$referenceFilename = 'Language'.$referenceLanguage.'.php'; -/** Language messages we will test. */ -$testMessages = array(); -$testLanguage = ''; -/** whereas we use an external language file */ -$externalRef = false; - -# FUNCTIONS -/** @todo more informations !! */ -function usage() { -echo 'php DiffLanguage.php [lang [file]] [--color=(yes|no|light)]'."\n"; -} - -/** Return a given string with first letter upper case, the rest lowercase */ -function ucfirstlcrest($string) { - return strtoupper(substr($string,0,1)).strtolower(substr($string,1)); -} - -/** - * Return a $wgAllmessages array shipped in MediaWiki - * @param $languageCode String: formated language code - * @return array The MediaWiki default $wgAllMessages array requested - */ -function getMediawikiMessages($languageCode = 'En') { - - $foo = "wgAllMessages$languageCode"; - global $$foo; - global $wgSkinNamesEn; // potentially unused global declaration? - - // it might already be loaded in LocalSettings.php - if(!isset($$foo)) { - global $IP; - $langFile = $IP.'/languages/classes/Language'.$languageCode.'.php'; - if (file_exists( $langFile ) ) { - print "Including $langFile\n"; - include($langFile); - } else wfDie("ERROR: The file $langFile does not exist !\n"); - } - return $$foo; -} - -/** - * 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 $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) { - print "Including external file $filename.\n"; - include($filename); - $foo = "wgAllMessages$languageCode"; - return $$foo; -} - -# MAIN ENTRY -if ( isset($args[0]) ) { - $lang = ucfirstlcrest($args[0],1); - - // eventually against another language file we will use as reference instead - // of the default english language. - if( isset($args[1])) { - // we assume the external file contain an array of messages for the - // lang we are testing - $referenceMessages = getExternalMessages( $args[1], $lang ); - $referenceLanguage = $lang; - $referenceFilename = $args[1]; - $externalRef = true; - } - - // Load datas from MediaWiki - $testMessages = getMediawikiMessages($lang); - $testLanguage = $lang; -} else { - usage(); - wfDie(); -} - -/** parsertest is used to do differences */ -$myParserTest = new ParserTest(); - -# Get all references messages and check if they exist in the tested language -$i = 0; - -$msg = "MW Language{$testLanguage}.php against "; -if($externalRef) { $msg .= 'external file '; } -else { $msg .= 'internal file '; } -$msg .= $referenceFilename.' ('.$referenceLanguage."):\n----\n"; -echo $msg; - -// process messages -foreach($referenceMessages as $index => $ref) -{ - // message is not localized - if(!(isset($testMessages[$index]))) { - $i++; - print "'$index' => \"$ref\",\n"; - // Messages in the same language differs - } elseif( ($lang == $referenceLanguage) AND ($testMessages[$index] != $ref)) { - print "\n$index differs:\n"; - print $myParserTest->quickDiff($testMessages[$index],$ref,'tested','reference'); - } -} - -echo "\n----\n".$msg; -echo "$referenceLanguage language is complete at ".number_format((100 - $i/count($wgAllMessagesEn) * 100),2)."%\n"; -echo "$i unlocalised messages of the ".count($wgAllMessagesEn)." messages available.\n"; - diff --git a/maintenance/language/digit2html.php b/maintenance/language/digit2html.php index 54630af0..a80ac014 100644 --- a/maintenance/language/digit2html.php +++ b/maintenance/language/digit2html.php @@ -18,17 +18,17 @@ * @ingroup MaintenanceLanguage */ -require_once( dirname(__FILE__).'/../Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/../Maintenance.php' ); class Digit2Html extends Maintenance { # 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' + 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() { @@ -37,18 +37,18 @@ class Digit2Html extends Maintenance { } public function execute() { - foreach( $this->mLangs as $code ) { + foreach ( $this->mLangs as $code ) { $filename = Language::getMessagesFileName( $code ); $this->output( "Loading language [$code] ... " ); unset( $digitTransformTable ); require_once( $filename ); - if( !isset( $digitTransformTable ) ) { + if ( !isset( $digitTransformTable ) ) { $this->error( "\$digitTransformTable not found for lang: $code" ); continue; } $this->output( "OK\n\$digitTransformTable = array(\n" ); - foreach( $digitTransformTable as $latin => $translation ) { + foreach ( $digitTransformTable as $latin => $translation ) { $htmlent = utf8ToHexSequence( $translation ); $this->output( "'$latin' => '$translation', # &#x$htmlent;\n" ); } @@ -58,4 +58,4 @@ class Digit2Html extends Maintenance { } $maintClass = "Digit2Html"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/language/dumpMessages.php b/maintenance/language/dumpMessages.php index a0f0a9ab..9bdda09d 100644 --- a/maintenance/language/dumpMessages.php +++ b/maintenance/language/dumpMessages.php @@ -22,7 +22,7 @@ * @todo Make this more useful, right now just dumps $wgContentLang */ -require_once( dirname(__FILE__) . '/../Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/../Maintenance.php' ); class DumpMessages extends Maintenance { public function __construct() { @@ -31,6 +31,8 @@ class DumpMessages extends Maintenance { } public function execute() { + global $wgVersion; + $messages = array(); foreach ( array_keys( Language::getMessagesFor( 'en' ) ) as $key ) { $messages[$key] = wfMsg( $key ); @@ -41,4 +43,4 @@ class DumpMessages extends Maintenance { } $maintClass = "DumpMessages"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/language/function-list.php b/maintenance/language/function-list.php index f0c398a6..7985f37d 100644 --- a/maintenance/language/function-list.php +++ b/maintenance/language/function-list.php @@ -7,15 +7,15 @@ define( 'MEDIAWIKI', 1 ); define( 'NOT_REALLY_MEDIAWIKI', 1 ); -class Language {} +class Language { } foreach ( glob( 'Language*.php' ) as $file ) { if ( $file != 'Language.php' ) { require_once( $file ); } } -$removedFunctions = array( 'date', 'time', 'timeanddate', 'formatMonth', 'formatDay', - 'getMonthName', 'getMonthNameGen', 'getMonthAbbreviation', 'getWeekdayName', +$removedFunctions = array( 'date', 'time', 'timeanddate', 'formatMonth', 'formatDay', + 'getMonthName', 'getMonthNameGen', 'getMonthAbbreviation', 'getWeekdayName', 'userAdjust', 'dateFormat', 'timeSeparator', 'timeDateSeparator', 'timeBeforeDate', 'monthByLatinNumber', 'getSpecialMonthName', diff --git a/maintenance/language/generateCollationData.php b/maintenance/language/generateCollationData.php new file mode 100644 index 00000000..68ad2ddf --- /dev/null +++ b/maintenance/language/generateCollationData.php @@ -0,0 +1,381 @@ +<?php + +require_once( dirname( __FILE__ ) .'/../Maintenance.php' ); + +/** + * Generate first letter data files for Collation.php + */ +class GenerateCollationData extends Maintenance { + /** The directory with source data files in it */ + var $dataDir; + + /** The primary weights, indexed by codepoint */ + var $weights; + + /** + * A hashtable keyed by codepoint, where presence indicates that a character + * has a decomposition mapping. This makes it non-preferred for group header + * selection. + */ + var $mappedChars; + + var $debugOutFile; + + /** + * Important tertiary weights from UTS #10 section 7.2 + */ + const NORMAL_UPPERCASE = 0x08; + const NORMAL_HIRAGANA = 0X0E; + + public function __construct() { + parent::__construct(); + $this->addOption( 'data-dir', 'A directory on the local filesystem ' . + 'containing allkeys.txt and ucd.all.grouped.xml from unicode.org', + false, true ); + $this->addOption( 'debug-output', 'Filename for sending debug output to', + false, true ); + } + + public function execute() { + $this->dataDir = $this->getOption( 'data-dir', '.' ); + if ( !file_exists( "{$this->dataDir}/allkeys.txt" ) ) { + $this->error( "Unable to find allkeys.txt. Please download it from " . + "http://www.unicode.org/Public/UCA/latest/allkeys.txt and specify " . + "its location with --data-dir=<DIR>" ); + exit( 1 ); + } + if ( !file_exists( "{$this->dataDir}/ucd.all.grouped.xml" ) ) { + $this->error( "Unable to find ucd.all.grouped.xml. Please download it " . + "from http://www.unicode.org/Public/6.0.0/ucdxml/ucd.all.grouped.zip " . + "and specify its location with --data-dir=<DIR>" ); + exit( 1 ); + } + $debugOutFileName = $this->getOption( 'debug-output' ); + if ( $debugOutFileName ) { + $this->debugOutFile = fopen( $debugOutFileName, 'w' ); + if ( !$this->debugOutFile ) { + $this->error( "Unable to open debug output file for writing" ); + exit( 1 ); + } + } + $this->loadUcd(); + $this->generateFirstChars(); + } + + function loadUcd() { + $uxr = new UcdXmlReader( "{$this->dataDir}/ucd.all.grouped.xml" ); + $uxr->readChars( array( $this, 'charCallback' ) ); + } + + function charCallback( $data ) { + // Skip non-printable characters + $category = substr( $data['gc'], 0, 1 ); + if ( strpos( 'LNPS', $category ) === false ) { + return; + } + $cp = hexdec( $data['cp'] ); + + // Skip the CJK ideograph blocks, as an optimisation measure. + // UCA doesn't sort them properly anyway, without tailoring. + if ( IcuCollation::isCjk( $cp ) ) { + return; + } + + // Skip the composed Hangul syllables, we will use the bare Jamo + // as first letters + if ( $data['block'] == 'Hangul Syllables' ) { + return; + } + + // Calculate implicit weight per UTS #10 v6.0.0, sec 7.1.3 + if ( $data['UIdeo'] === 'Y' ) { + if ( $data['block'] == 'CJK Unified Ideographs' + || $data['block'] == 'CJK Compatibility Ideographs' ) + { + $base = 0xFB40; + } else { + $base = 0xFB80; + } + } else { + $base = 0xFBC0; + } + $a = $base + ( $cp >> 15 ); + $b = ( $cp & 0x7fff ) | 0x8000; + + $this->weights[$cp] = sprintf( ".%04X.%04X", $a, $b ); + + if ( $data['dm'] !== '#' ) { + $this->mappedChars[$cp] = true; + } + + if ( $cp % 4096 == 0 ) { + print "{$data['cp']}\n"; + } + } + + function generateFirstChars() { + $file = fopen( "{$this->dataDir}/allkeys.txt", 'r' ); + if ( !$file ) { + $this->error( "Unable to open allkeys.txt" ); + exit( 1 ); + } + global $IP; + $outFile = fopen( "$IP/serialized/first-letters-root.ser", 'w' ); + if ( !$outFile ) { + $this->error( "Unable to open output file first-letters-root.ser" ); + exit( 1 ); + } + + $goodTertiaryChars = array(); + + // For each character with an entry in allkeys.txt, overwrite the implicit + // entry in $this->weights that came from the UCD. + // Also gather a list of tertiary weights, for use in selecting the group header + while ( false !== ( $line = fgets( $file ) ) ) { + // We're only interested in single-character weights, pick them out with a regex + $line = trim( $line ); + if ( !preg_match( '/^([0-9A-F]+)\s*;\s*([^#]*)/', $line, $m ) ) { + continue; + } + + $cp = hexdec( $m[1] ); + $allWeights = trim( $m[2] ); + $primary = ''; + $tertiary = ''; + + if ( !isset( $this->weights[$cp] ) ) { + // Non-printable, ignore + continue; + } + foreach ( StringUtils::explode( '[', $allWeights ) as $weightStr ) { + preg_match_all( '/[*.]([0-9A-F]+)/', $weightStr, $m ); + if ( !empty( $m[1] ) ) { + if ( $m[1][0] !== '0000' ) { + $primary .= '.' . $m[1][0]; + } + if ( $m[1][2] !== '0000' ) { + $tertiary .= '.' . $m[1][2]; + } + } + } + $this->weights[$cp] = $primary; + if ( $tertiary === '.0008' + || $tertiary === '.000E' ) + { + $goodTertiaryChars[$cp] = true; + } + } + fclose( $file ); + + // Identify groups of characters with the same primary weight + $this->groups = array(); + asort( $this->weights, SORT_STRING ); + $prevWeight = reset( $this->weights ); + $group = array(); + foreach ( $this->weights as $cp => $weight ) { + if ( $weight !== $prevWeight ) { + $this->groups[$prevWeight] = $group; + $prevWeight = $weight; + if ( isset( $this->groups[$weight] ) ) { + $group = $this->groups[$weight]; + } else { + $group = array(); + } + } + $group[] = $cp; + } + if ( $group ) { + $this->groups[$prevWeight] = $group; + } + + // If one character has a given primary weight sequence, and a second + // character has a longer primary weight sequence with an initial + // portion equal to the first character, then remove the second + // character. This avoids having characters like U+A732 (double A) + // polluting the basic latin sort area. + $prevWeights = array(); + foreach ( $this->groups as $weight => $group ) { + if ( preg_match( '/(\.[0-9A-F]*)\./', $weight, $m ) ) { + if ( isset( $this->groups[$m[1]] ) ) { + unset( $this->groups[$weight] ); + } + } + } + + ksort( $this->groups, SORT_STRING ); + + // Identify the header character in each group + $headerChars = array(); + $prevChar = "\000"; + $tertiaryCollator = new Collator( 'root' ); + $primaryCollator = new Collator( 'root' ); + $primaryCollator->setStrength( Collator::PRIMARY ); + $numOutOfOrder = 0; + foreach ( $this->groups as $weight => $group ) { + $uncomposedChars = array(); + $goodChars = array(); + foreach ( $group as $cp ) { + if ( isset( $goodTertiaryChars[$cp] ) ) { + $goodChars[] = $cp; + } + if ( !isset( $this->mappedChars[$cp] ) ) { + $uncomposedChars[] = $cp; + } + } + $x = array_intersect( $goodChars, $uncomposedChars ); + if ( !$x ) { + $x = $uncomposedChars; + if ( !$x ) { + $x = $group; + } + } + + // Use ICU to pick the lowest sorting character in the selection + $tertiaryCollator->sort( $x ); + $cp = $x[0]; + + $char = codepointToUtf8( $cp ); + $headerChars[] = $char; + if ( $primaryCollator->compare( $char, $prevChar ) <= 0 ) { + $numOutOfOrder ++; + /* + printf( "Out of order: U+%05X > U+%05X\n", + utf8ToCodepoint( $prevChar ), + utf8ToCodepoint( $char ) ); + */ + } + $prevChar = $char; + + if ( $this->debugOutFile ) { + fwrite( $this->debugOutFile, sprintf( "%05X %s %s (%s)\n", $cp, $weight, $char, + implode( ' ', array_map( 'codepointToUtf8', $group ) ) ) ); + } + } + + print "Out of order: $numOutOfOrder / " . count( $headerChars ) . "\n"; + + fwrite( $outFile, serialize( $headerChars ) ); + } +} + +class UcdXmlReader { + var $fileName; + var $callback; + var $groupAttrs; + var $xml; + var $blocks = array(); + var $currentBlock; + + function __construct( $fileName ) { + $this->fileName = $fileName; + } + + public function readChars( $callback ) { + $this->getBlocks(); + $this->currentBlock = reset( $this->blocks ); + $xml = $this->open(); + $this->callback = $callback; + + while ( $xml->name !== 'repertoire' && $xml->next() ); + + while ( $xml->read() ) { + if ( $xml->nodeType == XMLReader::ELEMENT ) { + if ( $xml->name === 'group' ) { + $this->groupAttrs = $this->readAttributes(); + } elseif ( $xml->name === 'char' ) { + $this->handleChar(); + } + } elseif ( $xml->nodeType === XMLReader::END_ELEMENT ) { + if ( $xml->name === 'group' ) { + $this->groupAttrs = array(); + } + } + } + $xml->close(); + } + + protected function open() { + $this->xml = new XMLReader; + $this->xml->open( $this->fileName ); + if ( !$this->xml ) { + throw new MWException( __METHOD__.": unable to open {$this->fileName}" ); + } + while ( $this->xml->name !== 'ucd' && $this->xml->read() ); + $this->xml->read(); + return $this->xml; + } + + /** + * Read the attributes of the current element node and return them + * as an array + */ + protected function readAttributes() { + $attrs = array(); + while ( $this->xml->moveToNextAttribute() ) { + $attrs[$this->xml->name] = $this->xml->value; + } + return $attrs; + } + + protected function handleChar() { + $attrs = $this->readAttributes() + $this->groupAttrs; + if ( isset( $attrs['cp'] ) ) { + $first = $last = hexdec( $attrs['cp'] ); + } else { + $first = hexdec( $attrs['first-cp'] ); + $last = hexdec( $attrs['last-cp'] ); + unset( $attrs['first-cp'] ); + unset( $attrs['last-cp'] ); + } + + for ( $cp = $first; $cp <= $last; $cp++ ) { + $hexCp = sprintf( "%04X", $cp ); + foreach ( array( 'na', 'na1' ) as $nameProp ) { + if ( isset( $attrs[$nameProp] ) ) { + $attrs[$nameProp] = str_replace( '#', $hexCp, $attrs[$nameProp] ); + } + } + + while ( $this->currentBlock ) { + if ( $cp < $this->currentBlock[0] ) { + break; + } elseif ( $cp <= $this->currentBlock[1] ) { + $attrs['block'] = key( $this->blocks ); + break; + } else { + $this->currentBlock = next( $this->blocks ); + } + } + + $attrs['cp'] = $hexCp; + call_user_func( $this->callback, $attrs ); + } + } + + public function getBlocks() { + if ( $this->blocks ) { + return $this->blocks; + } + + $xml = $this->open(); + while ( $xml->name !== 'blocks' && $xml->read() ); + + while ( $xml->read() ) { + if ( $xml->nodeType == XMLReader::ELEMENT ) { + if ( $xml->name === 'block' ) { + $attrs = $this->readAttributes(); + $first = hexdec( $attrs['first-cp'] ); + $last = hexdec( $attrs['last-cp'] ); + $this->blocks[$attrs['name']] = array( $first, $last ); + } + } + } + $xml->close(); + return $this->blocks; + } + +} + +$maintClass = 'GenerateCollationData'; +require_once( DO_MAINTENANCE ); + diff --git a/maintenance/language/generateNormalizerData.php b/maintenance/language/generateNormalizerData.php index d6b7aaa6..cb9910f3 100644 --- a/maintenance/language/generateNormalizerData.php +++ b/maintenance/language/generateNormalizerData.php @@ -87,8 +87,8 @@ class GenerateNormalizerData extends Maintenance { // No decomposition continue; } - if ( !preg_match( '/^ *(<\w*>) +([0-9A-F ]*)$/', - $data['Decomposition_Type_Mapping'], $m ) ) + 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 ); @@ -133,5 +133,5 @@ class GenerateNormalizerData extends Maintenance { } $maintClass = 'GenerateNormalizerData'; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/language/lang2po.php b/maintenance/language/lang2po.php index c7484d63..7e5dc472 100644 --- a/maintenance/language/lang2po.php +++ b/maintenance/language/lang2po.php @@ -25,18 +25,18 @@ */ /** This is a command line script */ -require_once(dirname(__FILE__) . '/../Maintenance.php' ); -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'); -define('MSGMERGE_BIN', 'msgmerge'); +define( 'ALL_LANGUAGES', true ); +define( 'XGETTEXT_BIN', 'xgettext' ); +define( 'MSGMERGE_BIN', 'msgmerge' ); // used to generate the .pot -define('XGETTEXT_OPTIONS', '-n --keyword=wfMsg --keyword=wfMsgForContent --keyword=wfMsgHtml --keyword=wfMsgWikiHtml '); -define('MSGMERGE_OPTIONS', ' -v '); +define( 'XGETTEXT_OPTIONS', '-n --keyword=wfMsg --keyword=wfMsgForContent --keyword=wfMsgHtml --keyword=wfMsgWikiHtml ' ); +define( 'MSGMERGE_OPTIONS', ' -v ' ); -define('LOCALE_OUTPUT_DIR', $IP.'/locale'); +define( 'LOCALE_OUTPUT_DIR', $IP . '/locale' ); class Lang2Po extends Maintenance { public function __construct() { @@ -53,27 +53,28 @@ class Lang2Po extends Maintenance { $langTool = new languages(); - if( $this->getOption( 'lang', ALL_LANGUAGES ) === ALL_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) { + foreach ( $codes as $langcode ) { $this->output( "Loading messages for $langcode:\n" ); - if( !$this->generatePo($langcode, $langTool->getMessages($langcode) ) ) { + if ( !$this->generatePo( $langcode, $langTool->getMessages( $langcode ) ) ) { $this->error( "ERROR: Failed to write file." ); } else { $this->output( "Applying template:" ); - $this->applyPot($langcode); + $this->applyPot( $langcode ); } } } /** * Return a dummy header for later edition. - * @return string A dummy header + * + * @return String: a dummy header */ private function poHeader() { return '# SOME DESCRIPTIVE TITLE. @@ -99,34 +100,34 @@ msgstr "" /** * 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. + * @param $langcode String: code of a language it will process. + * @param $messages Array containing the various messages. * @return string Filename where stuff got saved or false. */ - private function generatePo($langcode, $messages) { + private function generatePo( $langcode, $messages ) { $data = $this->poHeader(); // Generate .po entries - foreach( $messages['all'] as $identifier => $content ) { + foreach ( $messages['all'] as $identifier => $content ) { $data .= "msgid \"$identifier\"\n"; // Escape backslashes - $tmp = str_replace('\\', '\\\\', $content); + $tmp = str_replace( '\\', '\\\\', $content ); // Escape doublelquotes - $tmp = preg_replace( "/(?<!\\\\)\"/", '\"', $tmp); + $tmp = preg_replace( "/(?<!\\\\)\"/", '\"', $tmp ); // Rewrite multilines to gettext format - $tmp = str_replace("\n", "\"\n\"", $tmp); + $tmp = str_replace( "\n", "\"\n\"", $tmp ); - $data .= 'msgstr "'. $tmp . "\"\n\n"; + $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'; + $dir = LOCALE_OUTPUT_DIR . '/' . $langcode; + if ( !is_dir( $dir ) ) { mkdir( $dir, 0770 ); } + $filename = $dir . '/fromlanguagefile.po'; $file = fopen( $filename , 'wb' ); - if( fwrite( $file, $data ) ) { + if ( fwrite( $file, $data ) ) { fclose( $file ); return $filename; } else { @@ -138,28 +139,28 @@ msgstr "" private function generatePot() { global $IP; $curdir = getcwd(); - chdir($IP); + chdir( $IP ); exec( XGETTEXT_BIN - .' '.XGETTEXT_OPTIONS - .' -o '.LOCALE_OUTPUT_DIR.'/wfMsg.pot' - .' includes/*php' + . ' ' . XGETTEXT_OPTIONS + . ' -o ' . LOCALE_OUTPUT_DIR . '/wfMsg.pot' + . ' includes/*php' ); - chdir($curdir); + chdir( $curdir ); } - private function applyPot($langcode) { - $langdir = LOCALE_OUTPUT_DIR.'/'.$langcode; + private function applyPot( $langcode ) { + $langdir = LOCALE_OUTPUT_DIR . '/' . $langcode; - $from = $langdir.'/fromlanguagefile.po'; - $pot = LOCALE_OUTPUT_DIR.'/wfMsg.pot'; - $dest = $langdir.'/messages.po'; + $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 "); + exec( MSGMERGE_BIN . MSGMERGE_OPTIONS . " $from $pot -o $dest " ); // delete no more needed file // unlink($from); } } $maintClass = "Lang2Po"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/language/langmemusage.php b/maintenance/language/langmemusage.php index 71135474..28fe120e 100644 --- a/maintenance/language/langmemusage.php +++ b/maintenance/language/langmemusage.php @@ -22,8 +22,8 @@ */ /** This is a command line script */ -require_once( dirname(__FILE__) . '/../Maintenance.php' ); -require_once( dirname(__FILE__) . '/languages.inc' ); +require_once( dirname( __FILE__ ) . '/../Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/languages.inc' ); class LangMemUsage extends Maintenance { @@ -45,15 +45,15 @@ class LangMemUsage extends Maintenance { foreach ( $langtool->getLanguages() as $langcode ) { Language::factory( $langcode ); $memstep = memory_get_usage(); - $this->output( sprintf( "%12s: %d\n", $langcode, ($memstep- $memlast) ) ); + $this->output( sprintf( "%12s: %d\n", $langcode, ( $memstep - $memlast ) ) ); $memlast = $memstep; } $memend = memory_get_usage(); - $this->output( ' Total Usage: '.($memend - $memstart)."\n" ); + $this->output( ' Total Usage: ' . ( $memend - $memstart ) . "\n" ); } } $maintClass = "LangMemUsage"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/language/languages.inc b/maintenance/language/languages.inc index 98464292..c5c9f650 100644 --- a/maintenance/language/languages.inc +++ b/maintenance/language/languages.inc @@ -547,7 +547,9 @@ class languages { public function getUntranslatedNamespaces( $code ) { $this->loadFile( 'en' ); $this->loadFile( $code ); - return array_flip( array_diff_key( $this->mNamespaceNames['en'], $this->mNamespaceNames[$code] ) ); + $namespacesDiff = array_diff_key( $this->mNamespaceNames['en'], $this->mNamespaceNames[$code] ); + if ( isset( $namespacesDiff[NS_MAIN] ) ) unset( $namespacesDiff[NS_MAIN] ); + return $namespacesDiff; } /** diff --git a/maintenance/language/messageTypes.inc b/maintenance/language/messageTypes.inc index 9b979b19..9baf7e76 100644 --- a/maintenance/language/messageTypes.inc +++ b/maintenance/language/messageTypes.inc @@ -69,11 +69,18 @@ $wgIgnoredMessages = array( 'accesskey-compareselectedversions', 'accesskey-watch', 'accesskey-upload', + 'accesskey-preferences-save', + 'accesskey-summary', + 'accesskey-userrights-set', + 'accesskey-blockip-block', + 'accesskey-export', + 'accesskey-import', 'addsection', 'talkpageheader', 'anonnotice', 'autoblock_whitelist', 'searchmenu-help', + 'searchmenu-new-nocreate', 'googlesearch', 'opensearch-desc', 'exif-make-value', @@ -84,6 +91,8 @@ $wgIgnoredMessages = array( 'loginstart', 'loginend', 'loginlanguagelinks', + 'pear-mail-error', + 'php-mail-error', 'markaspatrolledlink', 'newarticletextanon', 'newsectionheaderdefaultlevel', @@ -94,13 +103,13 @@ $wgIgnoredMessages = array( 'pubmedurl', 'randompage-url', 'recentchanges-url', - 'cantcreateaccount-nonblock-text', 'revision-info-current', 'revision-nav', 'rfcurl', 'shareddescriptionfollows', 'signature', 'signature-anon', + 'signupstart', 'signupend', 'sitenotice', 'sitesubtitle', @@ -160,6 +169,7 @@ $wgIgnoredMessages = array( 'editpage-tos-summary', 'addsection-preload', 'addsection-editintro', + 'longpage-hint', ); /** Optional messages, which may be translated only if changed in the target language. */ @@ -257,7 +267,6 @@ $wgOptionalMessages = array( 'exif-lightsource-21', 'exif-lightsource-22', 'exif-lightsource-23', - 'exif-filesource-3', 'booksources-isbn', 'sp-contributions-explain', 'sorbs', @@ -353,6 +362,8 @@ $wgOptionalMessages = array( 'prefs-registration-date-time', 'prefs-memberingroups-type', 'shared-repo-name-wikimediacommons', + 'usermessage-template', + 'filepage.css', ); /** EXIF messages, which may be set as optional in several checks, but are generally mandatory */ @@ -589,4 +600,5 @@ $wgEXIFMessages = array( 'exif-gpsdestdistance-n', 'exif-gpsdirection-t', 'exif-gpsdirection-m', + 'exif-objectname', ); diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index 4c907a16..a0b19ac6 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -27,7 +27,6 @@ $wgMessageStructure = array( 'tog-editsectiononrightclick', 'tog-showtoc', 'tog-rememberpassword', - 'tog-editwidth', 'tog-watchcreations', 'tog-watchdefault', 'tog-watchmoves', @@ -181,17 +180,7 @@ $wgMessageStructure = array( '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-simplesearch-preference', 'vector-view-create', 'vector-view-edit', 'vector-view-history', @@ -259,6 +248,9 @@ $wgMessageStructure = array( 'jumptonavigation', 'jumptosearch', 'view-pool-error', + 'pool-timeout', + 'pool-queuefull', + 'pool-errorunknown', ), 'links' => array( 'aboutsite', @@ -405,6 +397,7 @@ $wgMessageStructure = array( 'yourpassword', 'yourpasswordagain', 'remembermypassword', + 'securelogin-stick-https', 'yourdomainname', 'externaldberror', 'login', @@ -421,6 +414,7 @@ $wgMessageStructure = array( 'gotaccount', 'gotaccountlink', 'createaccountmail', + 'createaccountreason', 'badretype', 'userexists', 'loginerror', @@ -438,6 +432,7 @@ $wgMessageStructure = array( 'wrongpasswordempty', 'passwordtooshort', 'password-name-match', + 'password-login-forbidden', 'mailmypassword', 'passwordremindertitle', 'passwordremindertext', @@ -449,6 +444,7 @@ $wgMessageStructure = array( 'throttled-mailpassword', 'loginstart', 'loginend', + 'signupstart', 'signupend', 'mailerror', 'acct_creation_throttle_hit', @@ -467,6 +463,11 @@ $wgMessageStructure = array( 'loginlanguagelinks', 'suspicious-userlogout', ), + 'mail' => array( + 'pear-mail-error', + 'php-mail-error', + 'php-mail-error-unknown', + ), 'resetpass' => array( 'resetpass', 'resetpass_announce', @@ -517,6 +518,7 @@ $wgMessageStructure = array( 'showlivepreview', 'showdiff', 'anoneditwarning', + 'anonpreviewwarning', 'missingsummary', 'missingcommenttext', 'missingcommentheader', @@ -554,6 +556,8 @@ $wgMessageStructure = array( 'userjsyoucanpreview', 'usercsspreview', 'userjspreview', + 'sitecsspreview', + 'sitejspreview', 'userinvalidcssjstitle', 'updated', 'note', @@ -575,7 +579,7 @@ $wgMessageStructure = array( 'copyrightwarning', 'copyrightwarning2', 'editpage-tos-summary', - 'longpagewarning', + 'longpage-hint', 'longpageerror', 'readonlywarning', 'protectedpagewarning', @@ -628,7 +632,6 @@ $wgMessageStructure = array( 'cantcreateaccount' => array( 'cantcreateaccounttitle', 'cantcreateaccount-text', - 'cantcreateaccount-nonblock-text', ), 'history' => array( 'viewpagelogs', @@ -718,6 +721,8 @@ $wgMessageStructure = array( 'logdelete-success', 'logdelete-failure', 'revdel-restore', + 'revdel-restore-deleted', + 'revdel-restore-visible', 'pagehist', 'deletedhist', 'revdelete-content', @@ -777,11 +782,13 @@ $wgMessageStructure = array( 'diffs' => array( 'history-title', 'difference', + 'difference-multipage', 'lineno', 'compareselectedversions', 'showhideselectedversions', 'editundo', 'diff-multi', + 'diff-multi-manyusers', ), 'search' => array( 'search-summary', @@ -804,6 +811,7 @@ $wgMessageStructure = array( 'searchmenu-legend', 'searchmenu-exists', 'searchmenu-new', + 'searchmenu-new-nocreate', 'searchhelp-url', 'searchmenu-prefix', 'searchmenu-help', @@ -818,6 +826,7 @@ $wgMessageStructure = array( 'searchprofile-everything-tooltip', 'searchprofile-advanced-tooltip', 'search-result-size', + 'search-result-category-size', 'search-result-score', 'search-redirect', 'search-section', @@ -898,6 +907,7 @@ $wgMessageStructure = array( 'contextlines', 'contextchars', 'stub-threshold', + 'stub-threshold-disabled', 'recentchangesdays', 'recentchangesdays-max', 'recentchangescount', @@ -930,6 +940,7 @@ $wgMessageStructure = array( 'prefs-files', 'prefs-custom-css', 'prefs-custom-js', + 'prefs-common-css-js', 'prefs-reset-intro', 'prefs-emailconfirm-label', 'prefs-textboxsize', @@ -966,9 +977,15 @@ $wgMessageStructure = array( 'prefs-advancedrendering', 'prefs-advancedsearchoptions', 'prefs-advancedwatchlist', - 'prefs-display', + 'prefs-displayrc', + 'prefs-displaysearchoptions', + 'prefs-displaywatchlist', 'prefs-diffs', ), + 'preferences-email' => array( + 'email-address-validity-valid', + 'email-address-validity-invalid', + ), 'userrights' => array( 'userrights', 'userrights-summary', @@ -1053,6 +1070,7 @@ $wgMessageStructure = array( 'right-hideuser', 'right-ipblock-exempt', 'right-proxyunbannable', + 'right-unblockself', 'right-protect', 'right-editprotected', 'right-editinterface', @@ -1075,7 +1093,6 @@ $wgMessageStructure = array( 'right-siteadmin', 'right-reset-passwords', 'right-override-export-depth', - 'right-versiondetail', 'right-sendemail', ), 'rightslog' => array( @@ -1127,14 +1144,9 @@ $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', @@ -1186,6 +1198,7 @@ $wgMessageStructure = array( 'upload_directory_read_only', 'uploaderror', 'upload-summary', + 'upload-recreate-warning', 'uploadtext', 'upload-permitted', 'upload-preferred', @@ -1212,6 +1225,17 @@ $wgMessageStructure = array( 'filetype-unwanted-type', 'filetype-banned-type', 'filetype-missing', + 'empty-file', + 'file-too-large', + 'filename-tooshort', + 'filetype-banned', + 'verification-error', + 'hookaborted', + 'illegal-filename', + 'overwrite', + 'unknown-error', + 'tmp-create-error', + 'tmp-write-error', 'large-file', 'largefileserver', 'emptyfile', @@ -1224,13 +1248,14 @@ $wgMessageStructure = array( 'fileexists-shared-forbidden', 'file-exists-duplicate', 'file-deleted-duplicate', - 'successfulupload', 'uploadwarning', 'uploadwarning-text', 'savefile', 'uploadedimage', 'overwroteimage', 'uploaddisabled', + 'copyuploaddisabled', + 'uploadfromurl-queued', 'uploaddisabledtext', 'php-uploaddisabledtext', 'uploadscripted', @@ -1247,6 +1272,12 @@ $wgMessageStructure = array( 'upload-wasdeleted', 'filename-bad-prefix', 'filename-prefix-blacklist', + 'upload-success-subj', + 'upload-success-msg', + 'upload-failure-subj', + 'upload-failure-msg', + 'upload-warning-subj', + 'upload-warning-msg', ), 'upload-errors' => array( 'upload-proto-error', @@ -1306,6 +1337,7 @@ $wgMessageStructure = array( 'listfiles_search_for', 'imgfile', 'listfiles', + 'listfiles_thumb', 'listfiles_date', 'listfiles_name', 'listfiles_user', @@ -1347,6 +1379,7 @@ $wgMessageStructure = array( 'shared-repo-from', 'shared-repo', 'shared-repo-name-wikimediacommons', + 'filepage.css', ), 'filerevert' => array( 'filerevert', @@ -1421,8 +1454,8 @@ $wgMessageStructure = array( 'statistics-edits', 'statistics-edits-average', 'statistics-views-total', + 'statistics-views-total-desc', 'statistics-views-peredit', - 'statistics-jobqueue', 'statistics-users', 'statistics-users-active', 'statistics-users-active-desc', @@ -1467,6 +1500,8 @@ $wgMessageStructure = array( 'nrevisions', 'nviews', 'nchanges', + 'nimagelinks', + 'ntransclusions', 'specialpage-empty', 'lonelypages', 'lonelypages-summary', @@ -1662,6 +1697,8 @@ $wgMessageStructure = array( 'emailpagetext', 'usermailererror', 'defemailsubject', + 'usermaildisabled', + 'usermaildisabledtext', 'noemailtitle', 'noemailtext', 'nowikiemailtitle', @@ -1678,10 +1715,15 @@ $wgMessageStructure = array( 'emailsenttext', 'emailuserfooter', ), + 'usermessage' => array( + 'usermessage-summary', + 'usermessage-editor', + 'usermessage-template', + ), 'watchlist' => array( 'watchlist', 'mywatchlist', - 'watchlistfor', + 'watchlistfor2', 'nowatchlist', 'watchlistanontext', 'watchnologin', @@ -1765,6 +1807,9 @@ $wgMessageStructure = array( 'revertpage', 'revertpage-nouser', 'rollback-success', + ), + 'edittokens' => array( + 'sessionfailure-title', 'sessionfailure', ), 'protect' => array( @@ -1884,12 +1929,15 @@ $wgMessageStructure = array( 'sp-contributions-newbies-title', 'sp-contributions-blocklog', 'sp-contributions-deleted', + 'sp-contributions-uploads', 'sp-contributions-logs', 'sp-contributions-talk', 'sp-contributions-userrights', 'sp-contributions-blocked-notice', + 'sp-contributions-blocked-notice-anon', 'sp-contributions-search', 'sp-contributions-username', + 'sp-contributions-toponly', 'sp-contributions-submit', 'sp-contributions-explain', 'sp-contributions-footer', @@ -1946,7 +1994,6 @@ $wgMessageStructure = array( 'ipb-edit-dropdown', 'ipb-unblock-addr', 'ipb-unblock', - 'ipb-blocklist-addr', 'ipb-blocklist', 'ipb-blocklist-contribs', 'unblockip', @@ -2013,7 +2060,9 @@ $wgMessageStructure = array( 'sorbsreason', 'sorbs_create_account_reason', 'cant-block-while-blocked', - 'cant-see-hidden-user' + 'cant-see-hidden-user', + 'ipbblocked', + 'ipbnounblockself', ), 'developertools' => array( 'lockdb', @@ -2037,6 +2086,7 @@ $wgMessageStructure = array( 'move-page-backlink', 'move-page-legend', 'movepagetext', + 'movepagetext-noredirectfixer', 'movepagetalktext', 'movearticle', 'moveuserpage-warning', @@ -2086,6 +2136,7 @@ $wgMessageStructure = array( 'immobile-target-page', 'immobile_namespace', 'imagenocrossnamespace', + 'nonfile-cannot-move-to-file', 'imagetypemismatch', 'imageinvalidfilename', 'fix-double-redirects', @@ -2152,6 +2203,7 @@ $wgMessageStructure = array( 'importstart', 'import-revision-count', 'importnopages', + 'imported-log-entries', 'importfailed', 'importunknownsource', 'importcantopen', @@ -2242,6 +2294,12 @@ $wgMessageStructure = array( 'accesskey-compareselectedversions', 'accesskey-watch', 'accesskey-upload', + 'accesskey-preferences-save', + 'accesskey-summary', + 'accesskey-userrights-set', + 'accesskey-blockip-block', + 'accesskey-export', + 'accesskey-import', ), 'tooltips' => array( 'tooltip-pt-userpage', @@ -2308,6 +2366,8 @@ $wgMessageStructure = array( 'tooltip-upload', 'tooltip-rollback', 'tooltip-undo', + 'tooltip-preferences-save', + 'tooltip-summary', ), 'stylesheets' => array( 'common.css', @@ -2445,6 +2505,9 @@ $wgMessageStructure = array( 'show-big-image-thumb', 'file-info-gif-looped', 'file-info-gif-frames', + 'file-info-png-looped', + 'file-info-png-repeat', + 'file-info-png-frames', ), 'newfiles' => array( 'newimages', @@ -2635,6 +2698,7 @@ $wgMessageStructure = array( 'exif-gpsareainformation', 'exif-gpsdatestamp', 'exif-gpsdifferential', + 'exif-objectname', ), 'exif-values' => array( 'exif-make-value', @@ -2860,6 +2924,8 @@ $wgMessageStructure = array( 'confirmemail_error', 'confirmemail_subject', 'confirmemail_body', + 'confirmemail_body_changed', + 'confirmemail_body_set', 'confirmemail_invalidated', 'invalidateemail', ), @@ -2915,6 +2981,7 @@ $wgMessageStructure = array( 'table_pager_first', 'table_pager_last', 'table_pager_limit', + 'table_pager_limit_label', 'table_pager_limit_submit', 'table_pager_empty', ), @@ -3038,6 +3105,7 @@ $wgMessageStructure = array( 'version-specialpages', 'version-parserhooks', 'version-variables', + 'version-skins', 'version-other', 'version-mediahandlers', 'version-hooks', @@ -3050,6 +3118,9 @@ $wgMessageStructure = array( 'version-version', 'version-svn-revision', 'version-license', + 'version-poweredby-credits', + 'version-poweredby-others', + 'version-license-info', 'version-software', 'version-software-product', 'version-software-version', @@ -3107,6 +3178,15 @@ $wgMessageStructure = array( 'tags-edit', 'tags-hitcount', ), + 'comparepages' => array( + 'comparepages', + 'compare-selector', + 'compare-page1', + 'compare-page2', + 'compare-rev1', + 'compare-rev2', + 'compare-submit', + ), 'db-error-messages' => array( 'dberr-header', 'dberr-problems', @@ -3123,10 +3203,15 @@ $wgMessageStructure = array( 'htmlform-float-invalid', 'htmlform-int-toolow', 'htmlform-int-toohigh', + 'htmlform-required', 'htmlform-submit', 'htmlform-reset', 'htmlform-selectorother-other', ), + 'sqlite' => array( + 'sqlite-has-fts', + 'sqlite-no-fts', + ), ); /** Comments for each block */ @@ -3156,6 +3241,8 @@ XHTML id names.", 'errors' => 'General errors', 'virus' => 'Virus scanner', 'login' => 'Login and logout pages', + 'mail' => 'E-mail sending', + 'passwordstrength' => 'JavaScript password checks', 'resetpass' => 'Password reset dialog', 'toolbar' => 'Edit page toolbar', 'edit' => 'Edit pages', @@ -3173,6 +3260,7 @@ XHTML id names.", 'opensearch' => 'OpenSearch description', 'quickbar' => 'Quickbar', 'preferences' => 'Preferences page', + 'preferences-email' => 'User preference: e-mail validation using jQuery', 'userrights' => 'User rights', 'group' => 'Groups', 'group-member' => '', @@ -3217,11 +3305,13 @@ XHTML id names.", 'newuserlog' => 'Special:Log/newusers', 'listgrouprights' => 'Special:ListGroupRights', 'emailuser' => 'E-mail user', + 'usermessage' => 'User Messenger', 'watchlist' => 'Watchlist', 'watching' => 'Displayed when you click the "watch" button and it is in the process of watching', 'enotif' => '', 'delete' => 'Delete', 'rollback' => 'Rollback', + 'edittokens' => 'Edit tokens', 'protect' => 'Protect', 'restrictions' => 'Restrictions (nouns)', 'restriction-levels' => 'Restriction levels', @@ -3331,6 +3421,8 @@ Variants for Chinese language", 'special-blank' => 'Special:BlankPage', 'external_images' => 'External image whitelist', 'special-tags' => 'Special:Tags', + 'comparepages' => 'Special:ComparePages', 'db-error-messages' => 'Database error messages', 'html-forms' => 'HTML forms', + 'sqlite' => 'SQLite database support', ); diff --git a/maintenance/language/rebuildLanguage.php b/maintenance/language/rebuildLanguage.php index 6c624ca3..fd8d62ee 100644 --- a/maintenance/language/rebuildLanguage.php +++ b/maintenance/language/rebuildLanguage.php @@ -7,7 +7,7 @@ * @defgroup MaintenanceLanguage MaintenanceLanguage */ -require_once( dirname(__FILE__).'/../commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/../commandLine.inc' ); require_once( 'languages.inc' ); require_once( 'writeMessagesArray.inc' ); @@ -21,11 +21,10 @@ require_once( 'writeMessagesArray.inc' ); * @param $removeDupes Remove the duplicated messages? * @param $dupeMsgSource The source file intended to remove from the array. */ -function rebuildLanguage( $code, $write, $listUnknown, $removeUnknown, $removeDupes, $dupeMsgSource ) { - global $wgLanguages; - $messages = $wgLanguages->getMessages( $code ); +function rebuildLanguage( $languages, $code, $write, $listUnknown, $removeUnknown, $removeDupes, $dupeMsgSource ) { + $messages = $languages->getMessages( $code ); $messages = $messages['all']; - if ($removeDupes) { + if ( $removeDupes ) { $messages = removeDupes( $messages, $dupeMsgSource ); } MessageWriter::writeMessagesToFile( $messages, $code, $write, $listUnknown, $removeUnknown ); @@ -39,20 +38,20 @@ function rebuildLanguage( $code, $write, $listUnknown, $removeUnknown, $removeDu * @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); + 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); + echo ( "The specified file $dupeMsgSource cannot be found." ); + exit( 1 ); } $newMsgArray = $oldMsgArray; - foreach ($oldMsgArray as $key => $value) { + foreach ( $oldMsgArray as $key => $value ) { if ( array_key_exists( $key, $dupeMessages ) ) { - unset($newMsgArray[$key]); + unset( $newMsgArray[$key] ); } } return $newMsgArray; @@ -72,7 +71,7 @@ Options: * remove-duplicates: Remove duplicated messages based on a PHP source file. TEXT; - exit(1); + exit( 1 ); } # Get the language code @@ -96,13 +95,13 @@ $wgRemoveUnknownMessages = isset( $options['remove-unknown'] ); $wgRemoveDuplicateMessages = isset( $options['remove-duplicates'] ); # Get language objects -$wgLanguages = new languages(); +$languages = new languages(); # Write all the language if ( $wgCode == 'all' ) { - foreach ( $wgLanguages->getLanguages() as $language ) { - rebuildLanguage( $language, $wgWriteToFile, $wgListUnknownMessages, $wgRemoveUnknownMessages, $wgRemoveDuplicateMessages, $wgDupeMessageSource ); + foreach ( $languages->getLanguages() as $languageCode ) { + rebuildLanguage( $languages, $languageCode, $wgWriteToFile, $wgListUnknownMessages, $wgRemoveUnknownMessages, $wgRemoveDuplicateMessages, $wgDupeMessageSource ); } } else { - rebuildLanguage( $wgCode, $wgWriteToFile, $wgListUnknownMessages, $wgRemoveUnknownMessages, $wgRemoveDuplicateMessages, $wgDupeMessageSource ); + rebuildLanguage( $languages, $wgCode, $wgWriteToFile, $wgListUnknownMessages, $wgRemoveUnknownMessages, $wgRemoveDuplicateMessages, $wgDupeMessageSource ); } diff --git a/maintenance/language/transstat.php b/maintenance/language/transstat.php index eeded34e..c2144eb6 100644 --- a/maintenance/language/transstat.php +++ b/maintenance/language/transstat.php @@ -6,16 +6,16 @@ * @ingroup MaintenanceLanguage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @author Ashar Voultoiz <thoane@altern.org> + * @author Ashar Voultoiz <hashar at free dot fr> * * Output is posted from time to time on: * http://www.mediawiki.org/wiki/Localisation_statistics */ $optionsWithArgs = array( 'output' ); -require_once( dirname(__FILE__).'/../commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/../commandLine.inc' ); require_once( 'languages.inc' ); -require_once( dirname(__FILE__).'/StatOutputs.php' ); +require_once( dirname( __FILE__ ) . '/StatOutputs.php' ); if ( isset( $options['help'] ) ) { @@ -39,7 +39,7 @@ Usage: php transstat.php [--help] [--output=csv|text|wiki] Example: php maintenance/transstat.php --output=text TEXT; - exit(1); + exit( 1 ); } diff --git a/maintenance/language/validate.php b/maintenance/language/validate.php index e84aa29d..d897e467 100644 --- a/maintenance/language/validate.php +++ b/maintenance/language/validate.php @@ -29,7 +29,7 @@ foreach ( $files as $filename ) { $keys = array_keys( $vars ); $diff = array_diff( $keys, Language::$mLocalisationKeys ); if ( $diff ) { - print "\nWarning: unrecognised variable(s): " . implode( ', ', $diff ) ."\n"; + print "\nWarning: unrecognised variable(s): " . implode( ', ', $diff ) . "\n"; } else { print " ok\n"; } diff --git a/maintenance/language/writeMessagesArray.inc b/maintenance/language/writeMessagesArray.inc index e28a5c04..e3a48abd 100644 --- a/maintenance/language/writeMessagesArray.inc +++ b/maintenance/language/writeMessagesArray.inc @@ -21,10 +21,11 @@ class MessageWriter { /** * Write a messages array as a PHP text and write it to the messages file. * - * @param $messages The messages array. - * @param $code The language code. - * @param $write Write to the messages file? - * @param $listUnknown List the unknown messages? + * @param $messages Array: the messages array. + * @param $code String: the language code. + * @param $write Boolean: write to the messages file? + * @param $listUnknown Boolean: list the unknown messages? + * @param $removeUnknown Boolean: whether to remove unkown messages */ public static function writeMessagesToFile( $messages, $code, $write, $listUnknown, $removeUnknown ) { # Rewrite the messages array @@ -66,8 +67,12 @@ class MessageWriter { /** * Write a messages array as a PHP text. * - * @param $messages The messages array. - * @param $ignoredComments Show comments about ignored and optional messages? (For English.) + * @param $messages Array: the messages array. + * @param $ignoredComments Boolean: show comments about ignored and optional + * messages? (For English.) + * @param $prefix String: base path for messages.inc and messageTypes.inc files + * or false for default path (this directory) + * @param $removeUnknown Boolean: whether to remove unkown messages * * @return Array of the PHP text and the sorted messages array. */ @@ -130,9 +135,9 @@ class MessageWriter { /** * Generates an array of comments for messages. * - * @param $messages Key of messages. - * @param $ignored List of ingored message keys. - * @param $optional List of optional message keys. + * @param $messages Array: key of messages. + * @param $ignored Array: list of ingored message keys. + * @param $optional Array: list of optional message keys. */ public static function makeComments( $messages, $ignored, $optional ) { # Comment collector @@ -153,10 +158,10 @@ class MessageWriter { /** * Write a block of messages to PHP. * - * @param $blockComment The comment of whole block. - * @param $messages The block messages. - * @param $messageComments Optional comments for messages in this block. - * @param $prefix Prefix for every line, for indenting purposes. + * @param $blockComment String: the comment of whole block. + * @param $messages Array: the block messages. + * @param $messageComments Array: optional comments for messages in this block. + * @param $prefix String: prefix for every line, for indenting purposes. * * @return The block, formatted in PHP. */ diff --git a/maintenance/mcc.php b/maintenance/mcc.php index e90266ae..a8c79a72 100644 --- a/maintenance/mcc.php +++ b/maintenance/mcc.php @@ -8,14 +8,14 @@ */ /** */ -require_once( dirname(__FILE__) . '/commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/commandLine.inc' ); -$mcc = new MWMemcached( array('persistant' => true/*, 'debug' => true*/) ); +$mcc = new MWMemcached( array( 'persistant' => true/*, 'debug' => true*/ ) ); $mcc->set_servers( $wgMemCachedServers ); -#$mcc->set_debug( true ); +# $mcc->set_debug( true ); -function mccShowHelp($command) { - $commandList = array( +function mccShowHelp( $command ) { + $commandList = array( 'get' => 'grabs something', 'getsock' => 'lists sockets', 'set' => 'changes something', @@ -27,14 +27,15 @@ function mccShowHelp($command) { 'quit' => 'exit mcc', 'help' => 'help about a command', ); - if( !$command ) { + if ( !$command ) { $command = 'fullhelp'; } - if( $command === 'fullhelp' ) { - foreach( $commandList as $cmd => $desc ) { - print "$cmd: $desc\n"; + if ( $command === 'fullhelp' ) { + $max_cmd_len = max( array_map( 'strlen', array_keys( $commandList ) ) ); + foreach ( $commandList as $cmd => $desc ) { + printf( "%-{$max_cmd_len}s: %s\n", $cmd, $desc ); } - } elseif( isset( $commandList[$command] ) ) { + } elseif ( isset( $commandList[$command] ) ) { print "$command: $commandList[$command]\n"; } else { print "$command: command does not exist or no help for it\n"; @@ -46,8 +47,8 @@ do { $showhelp = false; $quit = false; - $line = readconsole( '> ' ); - if ($line === false) exit; + $line = Maintenance::readconsole(); + if ( $line === false ) exit; $args = explode( ' ', $line ); $command = array_shift( $args ); @@ -56,7 +57,7 @@ do { switch ( $command ) { case 'help': // show an help message - mccShowHelp(array_shift($args)); + mccShowHelp( array_shift( $args ) ); break; case 'get': @@ -70,7 +71,7 @@ do { $res = $res[$args[1]]; } if ( $res === false ) { - #print 'Error: ' . $mcc->error_string() . "\n"; + # print 'Error: ' . $mcc->error_string() . "\n"; print "MemCached error\n"; } elseif ( is_string( $res ) ) { print "$res\n"; @@ -106,7 +107,7 @@ do { $value = implode( ' ', $args ); } if ( !$mcc->set( $key, $value, 0 ) ) { - #print 'Error: ' . $mcc->error_string() . "\n"; + # print 'Error: ' . $mcc->error_string() . "\n"; print "MemCached error\n"; } break; @@ -114,14 +115,14 @@ do { case 'delete': $key = implode( ' ', $args ); if ( !$mcc->delete( $key ) ) { - #print 'Error: ' . $mcc->error_string() . "\n"; + # print 'Error: ' . $mcc->error_string() . "\n"; print "MemCached error\n"; } break; case 'history': if ( function_exists( 'readline_list_history' ) ) { - foreach( readline_list_history() as $num => $line) { + foreach ( readline_list_history() as $num => $line ) { print "$num: $line\n"; } } else { diff --git a/maintenance/mctest.php b/maintenance/mctest.php index 3667cb93..651b2958 100644 --- a/maintenance/mctest.php +++ b/maintenance/mctest.php @@ -21,7 +21,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class mcTest extends Maintenance { public function __construct() { @@ -36,36 +36,36 @@ class mcTest extends Maintenance { global $wgMemCachedServers; $iterations = $this->getOption( 'i', 100 ); - if( $this->hasArg() ) + if ( $this->hasArg() ) $wgMemCachedServers = array( $this->getArg() ); foreach ( $wgMemCachedServers as $server ) { - $this->output( $server . " " ); - $mcc = new MemCachedClientforWiki( array('persistant' => true) ); + $this->output( $server . " ", $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++ ) { + for ( $i = 1; $i <= $iterations; $i++ ) { if ( !is_null( $mcc->set( "test$i", $i ) ) ) { $set++; } } - for ( $i=1; $i<=$iterations; $i++ ) { + for ( $i = 1; $i <= $iterations; $i++ ) { if ( !is_null( $mcc->incr( "test$i", $i ) ) ) { $incr++; } } - for ( $i=1; $i<=$iterations; $i++ ) { + for ( $i = 1; $i <= $iterations; $i++ ) { $value = $mcc->get( "test$i" ); - if ( $value == $i*2 ) { + if ( $value == $i * 2 ) { $get++; } } $exectime = $this->microtime_float() - $time_start; - - $this->output( "set: $set incr: $incr get: $get time: $exectime\n" ); + + $this->output( "set: $set incr: $incr get: $get time: $exectime", $server ); } } @@ -74,10 +74,10 @@ class mcTest extends Maintenance { * @return float */ private function microtime_float() { - list($usec, $sec) = explode(" ", microtime()); - return ((float)$usec + (float)$sec); + list( $usec, $sec ) = explode( " ", microtime() ); + return ( (float)$usec + (float)$sec ); } } $maintClass = "mcTest"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/mergeMessageFileList.php b/maintenance/mergeMessageFileList.php index 57d9ebb0..00c8cae5 100644 --- a/maintenance/mergeMessageFileList.php +++ b/maintenance/mergeMessageFileList.php @@ -3,7 +3,7 @@ # Start from scratch define( 'MW_NO_EXTENSION_MESSAGES', 1 ); -require_once( dirname( __FILE__ ).'/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); $maintClass = 'MergeMessageFileList'; $mmfl = false; class MergeMessageFileList extends Maintenance { @@ -11,12 +11,12 @@ 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 ' . + $this->mDescription = 'Merge $wgExtensionMessagesFiles from various extensions to produce a ' . 'single array containing all message files.'; } public function execute() { - global $IP, $mmfl; + global $mmfl; if ( !$this->hasOption( 'list-file' ) ) { $this->error( 'The --list-file option must be specified.' ); return; @@ -33,7 +33,7 @@ class MergeMessageFileList extends Maintenance { } } -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); foreach ( $mmfl['setupFiles'] as $fileName ) { if ( strval( $fileName ) === '' ) { @@ -44,8 +44,8 @@ foreach ( $mmfl['setupFiles'] as $fileName ) { include_once( $fileName ); } fwrite( STDERR, "\n" ); -$s = - "<" . "?php\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" . @@ -53,14 +53,14 @@ $s = $dirs = array( $IP, - dirname( dirname( __FILE__ ) ), + dirname( dirname( __FILE__ ) ), realpath( $IP ) ); foreach ( $dirs as $dir ) { - $s = preg_replace( + $s = preg_replace( "/'" . preg_quote( $dir, '/' ) . "([^']*)'/", - '"$IP\1"', + '"$IP\1"', $s ); } diff --git a/maintenance/migrateUserGroup.php b/maintenance/migrateUserGroup.php index 6d584f7d..c4163208 100644 --- a/maintenance/migrateUserGroup.php +++ b/maintenance/migrateUserGroup.php @@ -20,7 +20,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class MigrateUserGroup extends Maintenance { public function __construct() { @@ -37,10 +37,10 @@ class MigrateUserGroup extends Maintenance { $newGroup = $this->getArg( 1 ); $dbw = wfGetDB( DB_MASTER ); $start = $dbw->selectField( 'user_groups', 'MIN(ug_user)', - array('ug_group' => $oldGroup), __FUNCTION__ ); + array( 'ug_group' => $oldGroup ), __FUNCTION__ ); $end = $dbw->selectField( 'user_groups', 'MAX(ug_user)', - array('ug_group' => $oldGroup), __FUNCTION__ ); - if( $start === null ) { + array( 'ug_group' => $oldGroup ), __FUNCTION__ ); + if ( $start === null ) { $this->error( "Nothing to do - no users in the '$oldGroup' group", true ); } # Do remaining chunk @@ -48,12 +48,12 @@ class MigrateUserGroup extends Maintenance { $blockStart = $start; $blockEnd = $start + $this->mBatchSize - 1; // Migrate users over in batches... - while( $blockEnd <= $end ) { + 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, + array( 'ug_group' => $newGroup ), + array( 'ug_group' => $oldGroup, "ug_user BETWEEN $blockStart AND $blockEnd" ) ); $count += $dbw->affectedRows(); @@ -67,4 +67,4 @@ class MigrateUserGroup extends Maintenance { } $maintClass = "MigrateUserGroup"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/minify.php b/maintenance/minify.php index 601a4d67..2884fd7b 100644 --- a/maintenance/minify.php +++ b/maintenance/minify.php @@ -10,15 +10,21 @@ class MinifyScript extends Maintenance { public function __construct() { parent::__construct(); - $this->addOption( 'outfile', - 'File for output. Only a single file may be specified for input.', + $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" . + "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->addOption( 'js-statements-on-own-line', + "Boolean value for putting statements on their own line when minifying JavaScript.", + false, true ); + $this->addOption( 'js-max-line-length', + "Maximum line length for JavaScript minification.", + 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" . + "If --outfile is not specified, then the output file names will have a .min extension\n" . "added, e.g. jquery.js -> jquery.min.js."; } @@ -48,13 +54,12 @@ class MinifyScript extends Maintenance { $inDir = dirname( $inPath ); if ( strpos( $inName, '.min.' ) !== false ) { - echo "Skipping $inName\n"; + $this->error( "Skipping $inName\n" ); continue; } if ( !file_exists( $inPath ) ) { - $this->error( "File does not exist: $arg" ); - exit( 1 ); + $this->error( "File does not exist: $arg", true ); } $extension = $this->getExtension( $inName ); @@ -72,15 +77,17 @@ class MinifyScript extends Maintenance { public function getExtension( $fileName ) { $dotPos = strrpos( $fileName, '.' ); if ( $dotPos === false ) { - $this->error( "No file extension, cannot determine type: $arg" ); + $this->error( "No file extension, cannot determine type: $fileName" ); exit( 1 ); } return substr( $fileName, $dotPos + 1 ); } public function minify( $inPath, $outPath ) { + global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength; + $extension = $this->getExtension( $inPath ); - echo basename( $inPath ) . ' -> ' . basename( $outPath ) . '...'; + $this->output( basename( $inPath ) . ' -> ' . basename( $outPath ) . '...' ); $inText = file_get_contents( $inPath ); if ( $inText === false ) { @@ -95,7 +102,13 @@ class MinifyScript extends Maintenance { switch ( $extension ) { case 'js': - $outText = JSMin::minify( $inText ); + $outText = JavaScriptMinifier::minify( $inText, + $this->getOption( 'js-statements-on-own-line', $wgResourceLoaderMinifierStatementsOnOwnLine ), + $this->getOption( 'js-max-line-length', $wgResourceLoaderMinifierMaxLineLength ) + ); + break; + case 'css': + $outText = CSSMin::minify( $inText ); break; default: $this->error( "No minifier defined for extension \"$extension\"" ); @@ -103,9 +116,9 @@ class MinifyScript extends Maintenance { fwrite( $outFile, $outText ); fclose( $outFile ); - echo " ok\n"; + $this->output( " ok\n" ); } } $maintClass = 'MinifyScript'; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/moveBatch.php b/maintenance/moveBatch.php index d1d3193b..c7495338 100644 --- a/maintenance/moveBatch.php +++ b/maintenance/moveBatch.php @@ -33,7 +33,7 @@ * e.g. immobile_namespace for namespaces which can't be moved */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class MoveBatch extends Maintenance { public function __construct() { @@ -44,7 +44,7 @@ class MoveBatch extends Maintenance { $this->addOption( 'i', "Interval to sleep between moves" ); $this->addArg( 'listfile', 'List of pages to move, newline delimited', false ); } - + public function execute() { global $wgUser; @@ -56,18 +56,18 @@ class MoveBatch extends Maintenance { $user = $this->getOption( 'u', 'Move page script' ); $reason = $this->getOption( 'r', '' ); $interval = $this->getOption( 'i', 0 ); - if( $this->hasArg() ) { + if ( $this->hasArg() ) { $file = fopen( $this->getArg(), 'r' ); } else { $file = $this->getStdin(); } # Setup - if( !$file ) { + 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++ ) { @@ -86,18 +86,18 @@ class MoveBatch extends Maintenance { $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 ) { + if ( $err !== true ) { $msg = array_shift( $err[0] ); $this->output( "\nFAILED: " . wfMsg( $msg, $err[0] ) ); } $dbw->commit(); $this->output( "\n" ); - + if ( $interval ) { sleep( $interval ); } @@ -107,4 +107,4 @@ class MoveBatch extends Maintenance { } $maintClass = "MoveBatch"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/mssql/README b/maintenance/mssql/README deleted file mode 100644 index bcdeb82a..00000000 --- a/maintenance/mssql/README +++ /dev/null @@ -1,78 +0,0 @@ -== Syntax differences between MySQL and MSSQL == -{| border cellspacing=0 cellpadding=4 -!MySQL!!MSSQL -|- - -|AUTO_INCREMENT -|IDENTITY(1,1) -|- - -|binary -|varchar NULL ''(MSSQL doesn't allow setting of binary's to string values, and won't implicitly allow NULL's)'' -|- - -|bool -|bit -|- - -|[UN]SIGNED -|''not valid'' -|- - -|SELECT * FROM foo LIMIT x -|SELECT TOP x * FROM foo -|- - -|SELECT * FROM foo LIMIT x,y| -''not sure how to implement yet because it must be applied from within '''DatabaseMssql::limitResult''''' -|- - -|INSERT IGNORE INTO foo (foo_id,bar) VALUES ('1','xyz') -|IF NOT EXISTS (SELECT * FROM foo WHERE foo_id = '1') INSERT INTO foo (foo_id,bar) VALUES ('1','xyz') -|- - -|IF(cond,trueVal,falseVal) -|CASE WHEN cond THEN trueVal ELSE falseVal END -|- - -|SHOW TABLES -|SELECT * FROM INFORMATION_SCHEMA.TABLES -|- - -|ENUM -|''not natively supported, change to text'' -|} - -== MSSQL Variables == -{| border cellspacing=0 cellpadding=4 - -|@@VERSION -|Server version information -|- - -|@@IDENTITY -|Last inserted row -|- - -|@@ERROR -|Last error number -|} - -== Changes to INSERT wrapper == -=== AUTOINCREMENT vs IDENTITY === -MySQL style ''AUTOINCREMENT'' columns are inplemented in MSSQL using ''IDENTITY(x,y)'' where ''x'' is the initial value and ''y'' is the amount to add on each insert. The last value resulting from an insert into an IDENTITY column is stored in the ''@@IDENTITY'' variable. These kinds of columns are usually used as primary keys and are therefore assigned the ''NOT NULL'' property. - -In MySQL the standard way of inserting data into rows exhibiting AUTOINCREMENT columns is simply to use a ''NULL'' value which will be ignored. In MSSQL however assigning a ''NULL'' to an ''IDENTITY'' column is not allowed, instead the best way is not to include those items in the list of columns to be updated at all. - -To get round this in the MediaWiki MSSQL layer, I've modified the insert wrapper in the ''DatabaseMssql'' class to check if the primary key is used in the insert and remove it if so. It checks this by assuming that the primary key will be of the same name as the table but with ''_id'' on the end, and that it will the first item in the list of columns to update. - -=== IGNORE === -As you can see from the comparison table above, the MySQL ''INSERT IGNORE'' option takes quite a different form in MSSQL. This is handled in the ''insert'' wrapper method. In the case of multiple row inserts, a separate conditional insert query is performed for each item. - -== NULL values and NOT NULL columns == -MySQL implicitly casts NULL assignments to NOT NULL columns to an empty string or zero value accordingly, but MSSQL raises an error instead. This is a big problem within the MediaWiki environment because the code relies heavily on this implicit NULL casting. I've tried to get round the problem by replacing NULL's with empty strings from update and insert queries, and MSSQL is happy to cast the empty string to a numeric zero if necessary. - -== See also == -*[http://msdn.microsoft.com/en-us/library/ms188783.aspx MSSQL reference] -*[http://doc.ddart.net/mssql/sql70/ca-co_1.htm Type casting] -*[http://msdn.microsoft.com/en-us/library/ms187752.aspx TransactSQL datatypes] diff --git a/maintenance/mssql/tables.sql b/maintenance/mssql/tables.sql index 24847330..3d3d3592 100644 --- a/maintenance/mssql/tables.sql +++ b/maintenance/mssql/tables.sql @@ -1,395 +1,764 @@ +-- Experimental table definitions for Microsoft SQL Server with +-- content-holding fields switched to explicit BINARY charset. +-- ------------------------------------------------------------ + +-- 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. + +-- +-- General notes: +-- +-- The comments in this and other files are +-- replaced with the defined table prefix by the installer +-- and updater scripts. If you are installing or running +-- updates manually, you will need to manually insert the +-- table prefix if any when running these scripts. +-- + + +-- +-- The user table contains basic account information, +-- authentication keys, etc. +-- +-- Some multi-wiki sites may share a single central user table +-- between separate wikis using the $wgSharedDB setting. +-- +-- Note that when a external authentication plugin is used, +-- user table entries still need to be created to store +-- preferences and to key tracking information in the other +-- tables. + +-- LINE:53 CREATE TABLE /*$wgDBprefix*/user ( - user_id int NOT NULL IDENTITY(1,1), - user_name varchar(255) NOT NULL default '', - user_real_name varchar(255) NOT NULL default '', - user_password text NOT NULL, - user_newpassword text NOT NULL, - user_newpass_time varchar(5) NULL, - user_email text NOT NULL, - user_options text NOT NULL, - user_touched varchar(5) NOT NULL default '', - user_token varchar(10) NOT NULL default '', - user_email_authenticated varchar(5) NULL, - user_email_token varchar(10) NULL, - user_email_token_expires varchar(5) NULL, - user_registration varchar(5) NULL, - user_editcount int, - PRIMARY KEY (user_id) -); - + user_id INT NOT NULL PRIMARY KEY IDENTITY(0,1), + user_name NVARCHAR(255) NOT NULL UNIQUE DEFAULT '', + user_real_name NVARCHAR(255) NOT NULL DEFAULT '', + user_password NVARCHAR(255) NOT NULL DEFAULT '', + user_newpassword NVARCHAR(255) NOT NULL DEFAULT '', + user_newpass_time DATETIME NULL, + user_email NVARCHAR(255) NOT NULL DEFAULT '', + user_options NVARCHAR(MAX) NOT NULL DEFAULT '', + user_touched DATETIME NOT NULL DEFAULT GETDATE(), + user_token NCHAR(32) NOT NULL DEFAULT '', + user_email_authenticated DATETIME DEFAULT NULL, + user_email_token NCHAR(32) DEFAULT '', + user_email_token_expires DATETIME DEFAULT NULL, + user_registration DATETIME DEFAULT NULL, + user_editcount INT NULL +); +CREATE INDEX /*$wgDBprefix*/user_email_token ON /*$wgDBprefix*/[user](user_email_token); +CREATE UNIQUE INDEX /*$wgDBprefix*/[user_name] ON /*$wgDBprefix*/[user]([user_name]); +; + +-- +-- User permissions have been broken out to a separate table; +-- this allows sites with a shared user table to have different +-- permissions assigned to a user in each project. +-- +-- This table replaces the old user_rights field which used a +-- comma-separated blob. CREATE TABLE /*$wgDBprefix*/user_groups ( - ug_user int NOT NULL default '0', - ug_group varchar(5) NOT NULL default '', - PRIMARY KEY (ug_user,ug_group) + ug_user INT NOT NULL REFERENCES /*$wgDBprefix*/[user](user_id) ON DELETE CASCADE, + ug_group NVARCHAR(16) NOT NULL DEFAULT '', ); +CREATE UNIQUE clustered INDEX /*$wgDBprefix*/user_groups_unique ON /*$wgDBprefix*/user_groups(ug_user, ug_group); +CREATE INDEX /*$wgDBprefix*/user_group ON /*$wgDBprefix*/user_groups(ug_group); +-- Stores notifications of user talk page changes, for the display +-- of the "you have new messages" box +-- Changed user_id column to mwuser_id to avoid clashing with user_id function CREATE TABLE /*$wgDBprefix*/user_newtalk ( - user_id int NOT NULL default '0', - user_ip varchar(13) NOT NULL default '', - user_last_timestamp varchar(5) NOT NULL default '' -); - + user_id INT NOT NULL DEFAULT 0 REFERENCES /*$wgDBprefix*/[user](user_id) ON DELETE CASCADE, + user_ip NVARCHAR(40) NOT NULL DEFAULT '', + user_last_timestamp DATETIME NOT NULL DEFAULT '', +); +CREATE INDEX /*$wgDBprefix*/user_group_id ON /*$wgDBprefix*/user_newtalk([user_id]); +CREATE INDEX /*$wgDBprefix*/user_ip ON /*$wgDBprefix*/user_newtalk(user_ip); + +-- +-- User preferences and other fun stuff +-- replaces old user.user_options BLOB +-- +CREATE TABLE /*$wgDBprefix*/user_properties ( + up_user INT NOT NULL, + up_property NVARCHAR(32) NOT NULL, + up_value NVARCHAR(MAX), +); +CREATE UNIQUE clustered INDEX /*$wgDBprefix*/user_props_user_prop ON /*$wgDBprefix*/user_properties(up_user, up_property); +CREATE INDEX /*$wgDBprefix*/user_props_prop ON /*$wgDBprefix*/user_properties(up_property); + + +-- +-- Core of the wiki: each page has an entry here which identifies +-- it by title and contains some essential metadata. +-- CREATE TABLE /*$wgDBprefix*/page ( - page_id int NOT NULL IDENTITY(1,1), - page_namespace int NOT NULL, - page_title varchar(255) NOT NULL, - page_restrictions text NOT NULL, - page_counter bigint NOT NULL default '0', - page_is_redirect tinyint NOT NULL default '0', - page_is_new tinyint NOT NULL default '0', - page_random real NOT NULL, - page_touched varchar(5) NOT NULL default '', - page_latest int NOT NULL, - page_len int NOT NULL, - PRIMARY KEY (page_id) -); - + page_id INT NOT NULL PRIMARY KEY clustered IDENTITY, + page_namespace INT NOT NULL, + page_title NVARCHAR(255) NOT NULL, + page_restrictions NVARCHAR(255) NULL, + page_counter BIGINT NOT NULL DEFAULT 0, + page_is_redirect BIT NOT NULL DEFAULT 0, + page_is_new BIT NOT NULL DEFAULT 0, + page_random NUMERIC(15,14) NOT NULL DEFAULT RAND(), + page_touched DATETIME NOT NULL DEFAULT GETDATE(), + page_latest INT NOT NULL, + page_len INT NOT NULL, +); +CREATE UNIQUE INDEX /*$wgDBprefix*/page_unique_name ON /*$wgDBprefix*/page(page_namespace, page_title); +CREATE INDEX /*$wgDBprefix*/page_random_idx ON /*$wgDBprefix*/page(page_random); +CREATE INDEX /*$wgDBprefix*/page_len_idx ON /*$wgDBprefix*/page(page_len); +; + +-- +-- Every edit of a page creates also a revision row. +-- This stores metadata about the revision, and a reference +-- to the TEXT storage backend. +-- CREATE TABLE /*$wgDBprefix*/revision ( - rev_id int NOT NULL IDENTITY(1,1), - rev_page int NOT NULL, - rev_text_id int NOT NULL, - rev_comment text NOT NULL, - rev_user int NOT NULL default '0', - rev_user_text varchar(255) NOT NULL default '', - rev_timestamp varchar(5) NOT NULL default '', - rev_minor_edit tinyint NOT NULL default '0', - rev_deleted tinyint NOT NULL default '0', - rev_len int, - rev_parent_id int default NULL, - PRIMARY KEY (rev_page, rev_id) -); - + rev_id INT NOT NULL UNIQUE IDENTITY, + rev_page INT NOT NULL, + rev_text_id INT NOT NULL, + rev_comment NVARCHAR(max) NOT NULL, + rev_user INT NOT NULL DEFAULT 0 /*REFERENCES [user](user_id)*/, + rev_user_text NVARCHAR(255) NOT NULL DEFAULT '', + rev_timestamp DATETIME NOT NULL DEFAULT GETDATE(), + rev_minor_edit BIT NOT NULL DEFAULT 0, + rev_deleted BIT NOT NULL DEFAULT 0, + rev_len INT, + rev_parent_id INT DEFAULT NULL, + +); +CREATE UNIQUE clustered INDEX /*$wgDBprefix*/revision_unique ON /*$wgDBprefix*/revision(rev_page, rev_id); +CREATE UNIQUE INDEX /*$wgDBprefix*/rev_id ON /*$wgDBprefix*/revision(rev_id); +CREATE INDEX /*$wgDBprefix*/rev_timestamp ON /*$wgDBprefix*/revision(rev_timestamp); +CREATE INDEX /*$wgDBprefix*/page_timestamp ON /*$wgDBprefix*/revision(rev_page, rev_timestamp); +CREATE INDEX /*$wgDBprefix*/user_timestamp ON /*$wgDBprefix*/revision(rev_user, rev_timestamp); +CREATE INDEX /*$wgDBprefix*/usertext_timestamp ON /*$wgDBprefix*/revision(rev_user_text, rev_timestamp); +; + +-- +-- Holds TEXT of individual page revisions. +-- +-- Field names are a holdover from the 'old' revisions table in +-- MediaWiki 1.4 and earlier: an upgrade will transform that +-- table INTo the 'text' table to minimize unnecessary churning +-- and downtime. If upgrading, the other fields will be left unused. CREATE TABLE /*$wgDBprefix*/text ( - old_id int NOT NULL IDENTITY(1,1), - old_text text NOT NULL, - old_flags text NOT NULL, - PRIMARY KEY (old_id) -); - + old_id INT NOT NULL PRIMARY KEY clustered IDENTITY, + old_text TEXT NOT NULL, + old_flags NVARCHAR(255) NOT NULL, +); + +-- +-- Holding area for deleted articles, which may be viewed +-- or restored by admins through the Special:Undelete interface. +-- The fields generally correspond to the page, revision, and text +-- fields, with several caveats. +-- Cannot reasonably create views on this table, due to the presence of TEXT +-- columns. CREATE TABLE /*$wgDBprefix*/archive ( - ar_namespace int NOT NULL default '0', - ar_title varchar(255) NOT NULL default '', - ar_text text NOT NULL, - ar_comment text NOT NULL, - ar_user int NOT NULL default '0', - ar_user_text varchar(255) NOT NULL, - ar_timestamp varchar(5) NOT NULL default '', - ar_minor_edit tinyint NOT NULL default '0', - ar_flags text NOT NULL, - ar_rev_id int, - ar_text_id int, - ar_deleted tinyint NOT NULL default '0', - ar_len int, - ar_page_id int, - ar_parent_id int default NULL -); - + ar_namespace SMALLINT NOT NULL DEFAULT 0, + ar_title NVARCHAR(255) NOT NULL DEFAULT '', + ar_text NVARCHAR(MAX) NOT NULL, + ar_comment NVARCHAR(255) NOT NULL, + ar_user INT NULL REFERENCES /*$wgDBprefix*/[user](user_id) ON DELETE SET NULL, + ar_user_text NVARCHAR(255) NOT NULL, + ar_timestamp DATETIME NOT NULL DEFAULT GETDATE(), + ar_minor_edit BIT NOT NULL DEFAULT 0, + ar_flags NVARCHAR(255) NOT NULL, + ar_rev_id INT, + ar_text_id INT, + ar_deleted BIT NOT NULL DEFAULT 0, + ar_len INT DEFAULT NULL, + ar_page_id INT NULL, + ar_parent_id INT NULL, +); +CREATE INDEX /*$wgDBprefix*/ar_name_title_timestamp ON /*$wgDBprefix*/archive(ar_namespace,ar_title,ar_timestamp); +CREATE INDEX /*$wgDBprefix*/ar_usertext_timestamp ON /*$wgDBprefix*/archive(ar_user_text,ar_timestamp); +CREATE INDEX /*$wgDBprefix*/ar_user_text ON /*$wgDBprefix*/archive(ar_user_text); + + +-- +-- Track page-to-page hyperlinks within the wiki. +-- CREATE TABLE /*$wgDBprefix*/pagelinks ( - pl_from int NOT NULL default '0', - pl_namespace int NOT NULL default '0', - pl_title varchar(255) NOT NULL default '' + pl_from INT NOT NULL DEFAULT 0 REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, + pl_namespace SMALLINT NOT NULL DEFAULT 0, + pl_title NVARCHAR(255) NOT NULL DEFAULT '', ); +CREATE UNIQUE INDEX /*$wgDBprefix*/pl_from ON /*$wgDBprefix*/pagelinks(pl_from,pl_namespace,pl_title); +CREATE UNIQUE INDEX /*$wgDBprefix*/pl_namespace ON /*$wgDBprefix*/pagelinks(pl_namespace,pl_title,pl_from); +-- +-- Track template inclusions. +-- CREATE TABLE /*$wgDBprefix*/templatelinks ( - tl_from int NOT NULL default '0', - tl_namespace int NOT NULL default '0', - tl_title varchar(255) NOT NULL default '' -); - + tl_from INT NOT NULL DEFAULT 0 REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, + tl_namespace SMALLINT NOT NULL DEFAULT 0, + tl_title NVARCHAR(255) NOT NULL DEFAULT '', +); +CREATE UNIQUE INDEX /*$wgDBprefix*/tl_from ON /*$wgDBprefix*/templatelinks(tl_from,tl_namespace,tl_title); +CREATE UNIQUE INDEX /*$wgDBprefix*/tl_namespace ON /*$wgDBprefix*/templatelinks(tl_namespace,tl_title,tl_from); + +-- +-- Track links to images *used inline* +-- We don't distinguish live from broken links here, so +-- they do not need to be changed ON upload/removal. +-- CREATE TABLE /*$wgDBprefix*/imagelinks ( - il_from int NOT NULL default '0', - il_to varchar(255) NOT NULL default '' -); - + il_from INT NOT NULL DEFAULT 0 REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, + il_to NVARCHAR(255) NOT NULL DEFAULT '', + CONSTRAINT /*$wgDBprefix*/il_from PRIMARY KEY(il_from,il_to), +); +CREATE UNIQUE INDEX /*$wgDBprefix*/il_from_to ON /*$wgDBprefix*/imagelinks(il_from,il_to); +CREATE UNIQUE INDEX /*$wgDBprefix*/il_to_from ON /*$wgDBprefix*/imagelinks(il_to,il_from); + +-- +-- Track category inclusions *used inline* +-- This tracks a single level of category membership +-- (folksonomic tagging, really). +-- CREATE TABLE /*$wgDBprefix*/categorylinks ( - cl_from int NOT NULL default '0', - cl_to varchar(255) NOT NULL default '', - cl_sortkey varchar(70) NOT NULL default '', - cl_timestamp timestamp NOT NULL -); - + cl_from INT NOT NULL DEFAULT 0, + cl_to NVARCHAR(255) NOT NULL DEFAULT '', + cl_sortkey NVARCHAR(150) NOT NULL DEFAULT '', + cl_timestamp DATETIME NOT NULL DEFAULT GETDATE(), + CONSTRAINT /*$wgDBprefix*/cl_from PRIMARY KEY(cl_from, cl_to), +); +CREATE UNIQUE INDEX /*$wgDBprefix*/cl_from_to ON /*$wgDBprefix*/categorylinks(cl_from,cl_to); +-- We always sort within a given category... +CREATE INDEX /*$wgDBprefix*/cl_sortkey ON /*$wgDBprefix*/categorylinks(cl_to,cl_sortkey); +-- Not really used? +CREATE INDEX /*$wgDBprefix*/cl_timestamp ON /*$wgDBprefix*/categorylinks(cl_to,cl_timestamp); +--; + +-- +-- Track all existing categories. Something is a category if 1) it has an en- +-- try somewhere in categorylinks, or 2) it once did. Categories might not +-- have corresponding pages, so they need to be tracked separately. +-- CREATE TABLE /*$wgDBprefix*/category ( cat_id int NOT NULL IDENTITY(1,1), - cat_title varchar(255) NOT NULL, + cat_title nvarchar(255) NOT NULL, cat_pages int NOT NULL default 0, cat_subcats int NOT NULL default 0, cat_files int NOT NULL default 0, cat_hidden tinyint NOT NULL default 0, - PRIMARY KEY (cat_id) ); -CREATE TABLE /*$wgDBprefix*/externallinks ( - el_from int NOT NULL default '0', - el_to text NOT NULL, - el_index text NOT NULL -); +CREATE UNIQUE INDEX /*$wgDBprefix*/cat_title ON /*$wgDBprefix*/category(cat_title); +-- For Special:Mostlinkedcategories +CREATE INDEX /*$wgDBprefix*/cat_pages ON /*$wgDBprefix*/category(cat_pages); -CREATE TABLE /*$wgDBprefix*/langlinks ( - ll_from int NOT NULL default '0', - ll_lang varchar(7) NOT NULL default '', - ll_title varchar(255) NOT NULL default '' -); -CREATE TABLE /*$wgDBprefix*/site_stats ( - ss_row_id int NOT NULL, - ss_total_views bigint default '0', - ss_total_edits bigint default '0', - ss_good_articles bigint default '0', - ss_total_pages bigint default '-1', - ss_users bigint default '-1', - ss_admins int default '-1', - ss_images int default '0' +CREATE TABLE /*$wgDBprefix*/change_tag ( + ct_rc_id int NOT NULL default 0, + ct_log_id int NOT NULL default 0, + ct_rev_id int NOT NULL default 0, + ct_tag varchar(255) NOT NULL, + ct_params varchar(255) NOT NULL, ); +CREATE UNIQUE INDEX /*$wgDBprefix*/change_tag_rc_tag ON /*$wgDBprefix*/change_tag(ct_rc_id,ct_tag); +CREATE UNIQUE INDEX /*$wgDBprefix*/change_tag_log_tag ON /*$wgDBprefix*/change_tag(ct_log_id,ct_tag); +CREATE UNIQUE INDEX /*$wgDBprefix*/change_tag_rev_tag ON /*$wgDBprefix*/change_tag(ct_rev_id,ct_tag); +CREATE INDEX /*$wgDBprefix*/change_tag_tag_id ON /*$wgDBprefix*/change_tag(ct_tag,ct_rc_id,ct_rev_id,ct_log_id); -CREATE TABLE /*$wgDBprefix*/hitcounter ( - hc_id int NOT NULL +CREATE TABLE /*$wgDBprefix*/tag_summary ( + ts_rc_id INT NOT NULL default 0, + ts_log_id INT NOT NULL default 0, + ts_rev_id INT NOT NULL default 0, + ts_tags varchar(255) NOT NULL ); +CREATE UNIQUE INDEX /*$wgDBprefix*/tag_summary_rc_id ON /*$wgDBprefix*/tag_summary(ts_rc_id); +CREATE UNIQUE INDEX /*$wgDBprefix*/tag_summary_log_id ON /*$wgDBprefix*/tag_summary(ts_log_id); +CREATE UNIQUE INDEX /*$wgDBprefix*/tag_summary_rev_id ON /*$wgDBprefix*/tag_summary(ts_rev_id); -CREATE TABLE /*$wgDBprefix*/ipblocks ( - ipb_id int NOT NULL IDENTITY(1,1), - ipb_address text NOT NULL, - ipb_user int NOT NULL default '0', - ipb_by int NOT NULL default '0', - ipb_by_text varchar(255) NOT NULL default '', - ipb_reason text NOT NULL, - ipb_timestamp varchar(5) NOT NULL default '', - ipb_auto bit NOT NULL default 0, - ipb_anon_only bit NOT NULL default 0, - ipb_create_account bit NOT NULL default 1, - ipb_enable_autoblock bit NOT NULL default '1', - ipb_expiry varchar(5) NOT NULL default '', - ipb_range_start text NOT NULL, - ipb_range_end text NOT NULL, - ipb_deleted bit NOT NULL default 0, - ipb_block_email bit NOT NULL default 0, - PRIMARY KEY (ipb_id) +CREATE TABLE /*$wgDBprefix*/valid_tag ( + vt_tag varchar(255) NOT NULL PRIMARY KEY ); -CREATE TABLE /*$wgDBprefix*/image ( - img_name varchar(255) NOT NULL default '', - img_size int NOT NULL default '0', - img_width int NOT NULL default '0', - img_height int NOT NULL default '0', - img_metadata text NOT NULL, - img_bits int NOT NULL default '0', - img_media_type TEXT default NULL, - img_major_mime TEXT NOT NULL default "unknown", - img_minor_mime varchar(10) NOT NULL default "unknown", - img_description text NOT NULL, - img_user int NOT NULL default '0', - img_user_text varchar(255) NOT NULL, - img_timestamp varchar(5) NOT NULL default '', - img_sha1 varchar(10) NOT NULL default '', - PRIMARY KEY (img_name) +-- +-- Table for storing localisation data +-- +CREATE TABLE /*$wgDBprefix*/l10n_cache ( + -- language code + lc_lang NVARCHAR(32) NOT NULL, + + -- cache key + lc_key NVARCHAR(255) NOT NULL, + + -- Value + lc_value TEXT NOT NULL DEFAULT '', ); +CREATE INDEX /*$wgDBprefix*/lc_lang_key ON /*$wgDBprefix*/l10n_cache (lc_lang, lc_key); -CREATE TABLE /*$wgDBprefix*/oldimage ( - oi_name varchar(255) NOT NULL default '', - oi_archive_name varchar(255) NOT NULL default '', - oi_size int NOT NULL default 0, - oi_width int NOT NULL default 0, - oi_height int NOT NULL default 0, - oi_bits int NOT NULL default 0, - oi_description text NOT NULL, - oi_user int NOT NULL default '0', - oi_user_text varchar(255) NOT NULL, - oi_timestamp varchar(5) NOT NULL default '', - oi_metadata text NOT NULL, - oi_media_type TEXT default NULL, - oi_major_mime TEXT NOT NULL default "unknown", - oi_minor_mime varchar(10) NOT NULL default "unknown", - oi_deleted tinyint NOT NULL default '0', - oi_sha1 varchar(10) NOT NULL default '' +-- +-- Track links to external URLs +-- IE >= 4 supports no more than 2083 characters in a URL +CREATE TABLE /*$wgDBprefix*/externallinks ( + el_from INT NOT NULL DEFAULT '0', + el_to VARCHAR(2083) NOT NULL, + el_index VARCHAR(896) NOT NULL, +); +-- Maximum key length ON SQL Server is 900 bytes +CREATE INDEX /*$wgDBprefix*/externallinks_index ON /*$wgDBprefix*/externallinks(el_index); + +-- +-- Track external user accounts, if ExternalAuth is used +-- +CREATE TABLE /*$wgDBprefix*/external_user ( + -- Foreign key to user_id + eu_local_id INT NOT NULL PRIMARY KEY, + -- opaque identifier provided by the external database + eu_external_id NVARCHAR(255) NOT NULL, +); +CREATE UNIQUE INDEX /*$wgDBprefix*/eu_external_idx ON /*$wgDBprefix*/external_user(eu_external_id); + +-- +-- Track INTerlanguage links +-- +CREATE TABLE /*$wgDBprefix*/langlinks ( + ll_from INT NOT NULL DEFAULT 0, + ll_lang NVARCHAR(20) NOT NULL DEFAULT '', + ll_title NVARCHAR(255) NOT NULL DEFAULT '', + CONSTRAINT /*$wgDBprefix*/langlinks_pk PRIMARY KEY(ll_from, ll_lang), +); +CREATE UNIQUE INDEX /*$wgDBprefix*/langlinks_reverse_key ON /*$wgDBprefix*/langlinks(ll_lang,ll_title); + +-- +-- Track inline interwiki links +-- +CREATE TABLE /*$wgDBprefix*/iwlinks ( + -- page_id of the referring page + iwl_from INT NOT NULL DEFAULT 0, + + -- Interwiki prefix code of the target + iwl_prefix NVARCHAR(20) NOT NULL DEFAULT '', + + -- Title of the target, including namespace + iwl_title NVARCHAR(255) NOT NULL DEFAULT '', +); + +CREATE UNIQUE INDEX /*$wgDBprefix*/iwl_from ON /*$wgDBprefix*/iwlinks(iwl_from,iwl_prefix,iwl_title); +CREATE UNIQUE INDEX /*$wgDBprefix*/iwl_prefix ON /*$wgDBprefix*/iwlinks(iwl_prefix,iwl_title); + + +-- +-- Contains a single row with some aggregate info +-- ON the state of the site. +-- +CREATE TABLE /*$wgDBprefix*/site_stats ( + ss_row_id INT NOT NULL DEFAULT 1 PRIMARY KEY, + ss_total_views BIGINT DEFAULT 0, + ss_total_edits BIGINT DEFAULT 0, + ss_good_articles BIGINT DEFAULT 0, + ss_total_pages BIGINT DEFAULT -1, + ss_users BIGINT DEFAULT -1, + ss_active_users BIGINT DEFAULT -1, + ss_admins INT DEFAULT -1, + ss_images INT DEFAULT 0, +); + +-- INSERT INTO site_stats DEFAULT VALUES; + +-- +-- Stores an ID for every time any article is visited; +-- depending ON $wgHitcounterUpdateFreq, it is +-- periodically cleared and the page_counter column +-- in the page table updated for the all articles +-- that have been visited.) +-- +CREATE TABLE /*$wgDBprefix*/hitcounter ( + hc_id BIGINT NOT NULL ); +-- +-- The Internet is full of jerks, alas. Sometimes it's handy +-- to block a vandal or troll account. +-- +CREATE TABLE /*$wgDBprefix*/ipblocks ( + ipb_id INT NOT NULL PRIMARY KEY, + ipb_address NVARCHAR(255) NOT NULL, + ipb_user INT NOT NULL DEFAULT 0, + ipb_by INT NOT NULL DEFAULT 0, + ipb_by_text NVARCHAR(255) NOT NULL DEFAULT '', + ipb_reason NVARCHAR(255) NOT NULL, + ipb_timestamp DATETIME NOT NULL DEFAULT GETDATE(), + ipb_auto BIT NOT NULL DEFAULT 0, + ipb_anon_only BIT NOT NULL DEFAULT 0, + ipb_create_account BIT NOT NULL DEFAULT 1, + ipb_enable_autoblock BIT NOT NULL DEFAULT 1, + ipb_expiry DATETIME NOT NULL DEFAULT GETDATE(), + ipb_range_start NVARCHAR(32) NOT NULL DEFAULT '', + ipb_range_end NVARCHAR(32) NOT NULL DEFAULT '', + ipb_deleted BIT NOT NULL DEFAULT 0, + ipb_block_email BIT NOT NULL DEFAULT 0, + ipb_allow_usertalk BIT NOT NULL DEFAULT 0, +); +-- Unique index to support "user already blocked" messages +-- Any new options which prevent collisions should be included +--UNIQUE INDEX ipb_address (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only), +CREATE UNIQUE INDEX /*$wgDBprefix*/ipb_address ON /*$wgDBprefix*/ipblocks(ipb_address, ipb_user, ipb_auto, ipb_anon_only); +CREATE INDEX /*$wgDBprefix*/ipb_user ON /*$wgDBprefix*/ipblocks(ipb_user); +CREATE INDEX /*$wgDBprefix*/ipb_range ON /*$wgDBprefix*/ipblocks(ipb_range_start, ipb_range_end); +CREATE INDEX /*$wgDBprefix*/ipb_timestamp ON /*$wgDBprefix*/ipblocks(ipb_timestamp); +CREATE INDEX /*$wgDBprefix*/ipb_expiry ON /*$wgDBprefix*/ipblocks(ipb_expiry); +; + +-- +-- Uploaded images and other files. +CREATE TABLE /*$wgDBprefix*/image ( + img_name varchar(255) NOT NULL default '', + img_size INT NOT NULL DEFAULT 0, + img_width INT NOT NULL DEFAULT 0, + img_height INT NOT NULL DEFAULT 0, + img_metadata TEXT NOT NULL, -- was MEDIUMBLOB + img_bits SMALLINT NOT NULL DEFAULT 0, + img_media_type NVARCHAR(MAX) DEFAULT 'UNKNOWN', + img_major_mime NVARCHAR(MAX) DEFAULT 'UNKNOWN', + img_minor_mime NVARCHAR(MAX) NOT NULL DEFAULT 'unknown', + img_description NVARCHAR(MAX) NOT NULL, + img_user INT NOT NULL DEFAULT 0, + img_user_text VARCHAR(255) NOT NULL DEFAULT '', + img_timestamp DATETIME NOT NULL DEFAULT GETDATE(), + img_sha1 VARCHAR(255) NOT NULL default '', +); +-- Used by Special:Imagelist for sort-by-size +CREATE INDEX /*$wgDBprefix*/img_size ON /*$wgDBprefix*/[image](img_size); +-- Used by Special:Newimages and Special:Imagelist +CREATE INDEX /*$wgDBprefix*/img_timestamp ON /*$wgDBprefix*/[image](img_timestamp) +CREATE INDEX /*$wgDBprefix*/[img_sha1] ON /*wgDBprefix*/[image](img_sha1) + +-- +-- Previous revisions of uploaded files. +-- Awkwardly, image rows have to be moved into +-- this table at re-upload time. +-- +CREATE TABLE /*$wgDBprefix*/oldimage ( + oi_name VARCHAR(255) NOT NULL DEFAULT '', + oi_archive_name VARCHAR(255) NOT NULL DEFAULT '', + oi_size INT NOT NULL DEFAULT 0, + oi_width INT NOT NULL DEFAULT 0, + oi_height INT NOT NULL DEFAULT 0, + oi_bits SMALLINT NOT NULL DEFAULT 0, + oi_description NVARCHAR(MAX) NOT NULL, + oi_user INT NOT NULL DEFAULT 0, + oi_user_text VARCHAR(255) NOT NULL DEFAULT '', + oi_timestamp DATETIME NOT NULL DEFAULT GETDATE(), + oi_metadata TEXT NOT NULL, + oi_media_type NVARCHAR(MAX) DEFAULT 'UNKNOWN', + oi_major_mime NVARCHAR(MAX) NOT NULL DEFAULT 'UNKNOWN', + oi_minor_mime NVARCHAR(MAX) NOT NULL DEFAULT 'unknown', + oi_deleted BIT NOT NULL default 0, + oi_sha1 VARCHAR(255) NOT NULL default '', +); +CREATE INDEX /*$wgDBprefix*/oi_usertext_timestamp ON /*$wgDBprefix*/oldimage(oi_user_text,oi_timestamp); +CREATE INDEX /*$wgDBprefix*/oi_name_timestamp ON /*$wgDBprefix*/oldimage(oi_name, oi_timestamp); +CREATE INDEX /*$wgDBprefix*/oi_name_archive_name ON /*$wgDBprefix*/oldimage(oi_name,oi_archive_name); +CREATE INDEX /*$wgDBprefix*/[oi_sha1] ON /*$wgDBprefix*/oldimage(oi_sha1); + +-- +-- Record of deleted file data +-- CREATE TABLE /*$wgDBprefix*/filearchive ( - fa_id int NOT NULL IDENTITY(1,1), - fa_name varchar(255) NOT NULL default '', - fa_archive_name varchar(255) NULL default '', - fa_storage_group varchar(5) NULL, - fa_storage_key varchar(17) NULL default '', - fa_deleted_user int, - fa_deleted_timestamp varchar(5) NULL default '', - fa_deleted_reason text, - fa_size int default '0', - fa_width int default '0', - fa_height int default '0', - fa_metadata text, - fa_bits int default '0', - fa_media_type TEXT default NULL, - fa_major_mime TEXT default "unknown", - fa_minor_mime varchar(10) NULL default "unknown", - fa_description text, - fa_user int default '0', - fa_user_text varchar(255) NULL, - fa_timestamp varchar(5) NULL default '', - fa_deleted tinyint NOT NULL default '0', - PRIMARY KEY (fa_id) -); - + fa_id INT NOT NULL PRIMARY KEY, + fa_name NVARCHAR(255) NOT NULL DEFAULT '', + fa_archive_name NVARCHAR(255) DEFAULT '', + fa_storage_group NVARCHAR(16), + fa_storage_key NVARCHAR(64) DEFAULT '', + fa_deleted_user INT, + fa_deleted_timestamp NVARCHAR(14) DEFAULT NULL, + fa_deleted_reason NVARCHAR(255), + fa_size SMALLINT DEFAULT 0, + fa_width SMALLINT DEFAULT 0, + fa_height SMALLINT DEFAULT 0, + fa_metadata NVARCHAR(MAX), -- was mediumblob + fa_bits SMALLINT DEFAULT 0, + fa_media_type NVARCHAR(11) DEFAULT NULL, + fa_major_mime NVARCHAR(11) DEFAULT 'unknown', + fa_minor_mime NVARCHAR(32) DEFAULT 'unknown', + fa_description NVARCHAR(255), + fa_user INT DEFAULT 0, + fa_user_text NVARCHAR(255) DEFAULT '', + fa_timestamp DATETIME DEFAULT GETDATE(), + fa_deleted BIT NOT NULL DEFAULT 0, +); +-- Pick by image name +CREATE INDEX /*$wgDBprefix*/filearchive_name ON /*$wgDBprefix*/filearchive(fa_name,fa_timestamp); +-- Pick by dupe files +CREATE INDEX /*$wgDBprefix*/filearchive_dupe ON /*$wgDBprefix*/filearchive(fa_storage_group,fa_storage_key); +-- Pick by deletion time +CREATE INDEX /*$wgDBprefix*/filearchive_time ON /*$wgDBprefix*/filearchive(fa_deleted_timestamp); +-- Pick by deleter +CREATE INDEX /*$wgDBprefix*/filearchive_user ON /*$wgDBprefix*/filearchive(fa_deleted_user); + +-- +-- Primarily a summary table for Special:Recentchanges, +-- this table contains some additional info on edits from +-- the last few days, see Article::editUpdates() +-- CREATE TABLE /*$wgDBprefix*/recentchanges ( - rc_id int NOT NULL IDENTITY(1,1), - rc_timestamp varchar(5) NOT NULL default '', - rc_cur_time varchar(5) NOT NULL default '', - rc_user int NOT NULL default '0', - rc_user_text varchar(255) NOT NULL, - rc_namespace int NOT NULL default '0', - rc_title varchar(255) NOT NULL default '', - rc_comment varchar(255) NOT NULL default '', - rc_minor tinyint NOT NULL default '0', - rc_bot tinyint NOT NULL default '0', - rc_new tinyint NOT NULL default '0', - rc_cur_id int NOT NULL default '0', - rc_this_oldid int NOT NULL default '0', - rc_last_oldid int NOT NULL default '0', - rc_type tinyint NOT NULL default '0', - rc_moved_to_ns tinyint NOT NULL default '0', - rc_moved_to_title varchar(255) NOT NULL default '', - rc_patrolled tinyint NOT NULL default '0', - rc_ip varchar(13) NOT NULL default '', - rc_old_len int, - rc_new_len int, - rc_deleted tinyint NOT NULL default '0', - rc_logid int NOT NULL default '0', - rc_log_type varchar(17) NULL default NULL, - rc_log_action varchar(17) NULL default NULL, - rc_params text NULL, - PRIMARY KEY (rc_id) -); + rc_id INT NOT NULL, + rc_timestamp DATETIME DEFAULT GETDATE(), + rc_cur_time DATETIME DEFAULT GETDATE(), + rc_user INT DEFAULT 0, + rc_user_text NVARCHAR(255) DEFAULT '', + rc_namespace SMALLINT DEFAULT 0, + rc_title NVARCHAR(255) DEFAULT '', + rc_comment NVARCHAR(255) DEFAULT '', + rc_minor BIT DEFAULT 0, + rc_bot BIT DEFAULT 0, + rc_new BIT DEFAULT 0, + rc_cur_id INT DEFAULT 0, + rc_this_oldid INT DEFAULT 0, + rc_last_oldid INT DEFAULT 0, + rc_type tinyint DEFAULT 0, + rc_moved_to_ns BIT DEFAULT 0, + rc_moved_to_title NVARCHAR(255) DEFAULT '', + rc_patrolled BIT DEFAULT 0, + rc_ip NCHAR(40) DEFAULT '', + rc_old_len INT DEFAULT 0, + rc_new_len INT DEFAULT 0, + rc_deleted BIT DEFAULT 0, + rc_logid INT DEFAULT 0, + rc_log_type NVARCHAR(255) NULL DEFAULT NULL, + rc_log_action NVARCHAR(255) NULL DEFAULT NULL, + rc_params NVARCHAR(MAX) DEFAULT '', +); +CREATE INDEX /*$wgDBprefix*/rc_timestamp ON /*$wgDBprefix*/recentchanges(rc_timestamp); +CREATE INDEX /*$wgDBprefix*/rc_namespace_title ON /*$wgDBprefix*/recentchanges(rc_namespace, rc_title); +CREATE INDEX /*$wgDBprefix*/rc_cur_id ON /*$wgDBprefix*/recentchanges(rc_cur_id); +CREATE INDEX /*$wgDBprefix*/new_name_timestamp ON /*$wgDBprefix*/recentchanges(rc_new,rc_namespace,rc_timestamp); +CREATE INDEX /*$wgDBprefix*/rc_ip ON /*$wgDBprefix*/recentchanges(rc_ip); +CREATE INDEX /*$wgDBprefix*/rc_ns_usertext ON /*$wgDBprefix*/recentchanges(rc_namespace, rc_user_text); +CREATE INDEX /*$wgDBprefix*/rc_user_text ON /*$wgDBprefix*/recentchanges(rc_user_text, rc_timestamp); +; CREATE TABLE /*$wgDBprefix*/watchlist ( - wl_user int NOT NULL, - wl_namespace int NOT NULL default '0', - wl_title varchar(255) NOT NULL default '', - wl_notificationtimestamp varchar(5) NULL + wl_user INT NOT NULL, + wl_namespace SMALLINT NOT NULL DEFAULT 0, + wl_title NVARCHAR(255) NOT NULL DEFAULT '', + wl_notificationtimestamp NVARCHAR(14) DEFAULT NULL, + ); +CREATE UNIQUE INDEX /*$wgDBprefix*/namespace_title ON /*$wgDBprefix*/watchlist(wl_namespace,wl_title); +-- +-- Used by the math module to keep track +-- of previously-rendered items. +-- CREATE TABLE /*$wgDBprefix*/math ( - math_inputhash varchar(5) NOT NULL, - math_outputhash varchar(5) NOT NULL, - math_html_conservativeness tinyint NOT NULL, - math_html text, - math_mathml text + math_inputhash varbinary(16) NOT NULL PRIMARY KEY, + math_outputhash varbinary(16) NOT NULL, + math_html_conservativeness tinyint NOT NULL, + math_html NVARCHAR(MAX), + math_mathml NVARCHAR(MAX), ); +-- Needs fulltext index. CREATE TABLE /*$wgDBprefix*/searchindex ( - si_page int NOT NULL, - si_title varchar(255) NOT NULL default '', - si_text text NOT NULL -); - + si_page INT NOT NULL unique REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, + si_title varbinary(max) NOT NULL, + si_text varbinary(max) NOT NULL, + si_ext CHAR(4) NOT NULL DEFAULT '.txt', +); +CREATE FULLTEXT CATALOG wikidb AS DEFAULT; +CREATE UNIQUE CLUSTERED INDEX searchindex_page ON searchindex (si_page); +CREATE FULLTEXT INDEX on searchindex (si_title TYPE COLUMN si_ext, si_text TYPE COLUMN si_ext) +KEY INDEX searchindex_page +; + +-- This table is not used unless profiling is turned on +CREATE TABLE profiling ( + pf_count INTEGER NOT NULL DEFAULT 0, + pf_time NUMERIC(18,10) NOT NULL DEFAULT 0, + pf_name NVARCHAR(200) NOT NULL, + pf_server NVARCHAR(200) NULL +); +CREATE UNIQUE INDEX pf_name_server ON profiling (pf_name, pf_server); + +-- +-- Recognized INTerwiki link prefixes +-- CREATE TABLE /*$wgDBprefix*/interwiki ( - iw_prefix varchar(32) NOT NULL, - iw_url text NOT NULL, - iw_local bit NOT NULL, - iw_trans tinyint NOT NULL default 0 + iw_prefix NCHAR(32) NOT NULL PRIMARY KEY, + iw_url NCHAR(127) NOT NULL, + iw_api TEXT NOT NULL DEFAULT '', + iw_wikiid NVARCHAR(64) NOT NULL DEFAULT '', + iw_local BIT NOT NULL, + iw_trans BIT NOT NULL DEFAULT 0, ); +-- +-- Used for caching expensive grouped queries +-- CREATE TABLE /*$wgDBprefix*/querycache ( - qc_type varchar(10) NOT NULL, - qc_value int NOT NULL default '0', - qc_namespace int NOT NULL default '0', - qc_title varchar(255) NOT NULL default '' + qc_type NCHAR(32) NOT NULL, + qc_value INT NOT NULL DEFAULT '0', + qc_namespace SMALLINT NOT NULL DEFAULT 0, + qc_title NCHAR(255) NOT NULL DEFAULT '', + CONSTRAINT /*$wgDBprefix*/qc_pk PRIMARY KEY (qc_type,qc_value) ); +-- +-- For a few generic cache operations if not using Memcached +-- CREATE TABLE /*$wgDBprefix*/objectcache ( - keyname varchar(17) NOT NULL default '', - value text, - exptime datetime -); - + keyname NCHAR(255) NOT NULL DEFAULT '', + [value] NVARCHAR(MAX), -- IMAGE, + exptime DATETIME, -- This is treated as a DATETIME +); +CREATE CLUSTERED INDEX /*$wgDBprefix*/[objectcache_time] ON /*$wgDBprefix*/objectcache(exptime); +CREATE UNIQUE INDEX /*$wgDBprefix*/[objectcache_PK] ON /*wgDBprefix*/objectcache(keyname); +-- +-- Cache of INTerwiki transclusion +-- CREATE TABLE /*$wgDBprefix*/transcache ( - tc_url varchar(17) NOT NULL, - tc_contents text, - tc_time int NOT NULL + tc_url NVARCHAR(255) NOT NULL PRIMARY KEY, + tc_contents NVARCHAR(MAX), + tc_time INT NOT NULL, ); CREATE TABLE /*$wgDBprefix*/logging ( - log_id int NOT NULL IDENTITY(1,1), - log_type varchar(4) NOT NULL default '', - log_action varchar(4) NOT NULL default '', - log_timestamp varchar(5) NOT NULL default '19700101000000', - log_user int NOT NULL default 0, - log_namespace int NOT NULL default 0, - log_title varchar(255) NOT NULL default '', - log_comment varchar(255) NOT NULL default '', - log_params text NOT NULL, - log_deleted tinyint NOT NULL default '0', - PRIMARY KEY (log_id) -); + log_id INT PRIMARY KEY IDENTITY, + log_type NCHAR(10) NOT NULL DEFAULT '', + log_action NCHAR(10) NOT NULL DEFAULT '', + log_timestamp DATETIME NOT NULL DEFAULT GETDATE(), + log_user INT NOT NULL DEFAULT 0, + log_user_text NVARCHAR(255) NOT NULL DEFAULT '', + log_namespace INT NOT NULL DEFAULT 0, + log_title NVARCHAR(255) NOT NULL DEFAULT '', + log_page INT NULL DEFAULT NULL, + log_comment NVARCHAR(255) NOT NULL DEFAULT '', + log_params NVARCHAR(MAX) NOT NULL, + log_deleted BIT NOT NULL DEFAULT 0, +); +CREATE INDEX /*$wgDBprefix*/type_time ON /*$wgDBprefix*/logging (log_type, log_timestamp); +CREATE INDEX /*$wgDBprefix*/user_time ON /*$wgDBprefix*/logging (log_user, log_timestamp); +CREATE INDEX /*$wgDBprefix*/page_time ON /*$wgDBprefix*/logging (log_namespace, log_title, log_timestamp); +CREATE INDEX /*$wgDBprefix*/times ON /*$wgDBprefix*/logging (log_timestamp); +CREATE INDEX /*$wgDBprefix*/log_user_type_time ON /*$wgDBprefix*/logging (log_user, log_type, log_timestamp); +CREATE INDEX /*$wgDBprefix*/log_page_id_time ON /*$wgDBprefix*/logging (log_page,log_timestamp); + +CREATE TABLE /*$wgDBprefix*/log_search ( + -- The type of ID (rev ID, log ID, rev timestamp, username) + ls_field NVARCHAR(32) NOT NULL, + -- The value of the ID + ls_value NVARCHAR(255) NOT NULL, + -- Key to log_id + ls_log_id INT NOT NULL default 0, +); +CREATE UNIQUE INDEX /*$wgDBprefix*/ls_field_val ON /*$wgDBprefix*/log_search (ls_field,ls_value,ls_log_id); +CREATE INDEX /*$wgDBprefix*/ls_log_id ON /*$wgDBprefix*/log_search (ls_log_id); + CREATE TABLE /*$wgDBprefix*/trackbacks ( - tb_id int IDENTITY(1,1), - tb_page int REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, - tb_title varchar(255) NOT NULL, - tb_url text NOT NULL, - tb_ex text, - tb_name varchar(255) NULL, - PRIMARY KEY (tb_id) + tb_id INT PRIMARY KEY, + tb_page INT REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, + tb_title NVARCHAR(255) NOT NULL, + tb_url NVARCHAR(255) NOT NULL, + tb_ex NVARCHAR(MAX), + tb_name NVARCHAR(255), ); +CREATE INDEX /*$wgDBprefix*/trackbacks_page ON /*$wgDBprefix*/trackbacks(tb_page); +-- Jobs performed by parallel apache threads or a command-line daemon CREATE TABLE /*$wgDBprefix*/job ( - job_id int NOT NULL IDENTITY(1,1), - job_cmd varchar(17) NOT NULL default '', - job_namespace int NOT NULL, - job_title varchar(255) NOT NULL, - job_params text NOT NULL, - PRIMARY KEY (job_id) + job_id INT NOT NULL PRIMARY KEY, + job_cmd NVARCHAR(200) NOT NULL DEFAULT '', + job_namespace INT NOT NULL, + job_title NVARCHAR(200) NOT NULL, + job_params NVARCHAR(255) NOT NULL, ); +CREATE INDEX /*$wgDBprefix*/job_idx ON /*$wgDBprefix*/job(job_cmd,job_namespace,job_title); +-- Details of updates to cached special pages CREATE TABLE /*$wgDBprefix*/querycache_info ( - qci_type varchar(10) NOT NULL default '', - qci_timestamp varchar(5) NOT NULL default '19700101000000' + qci_type NVARCHAR(32) NOT NULL DEFAULT '' PRIMARY KEY, + qci_timestamp NVARCHAR(14) NOT NULL DEFAULT '19700101000000', ); +-- For each redirect, this table contains exactly one row defining its target CREATE TABLE /*$wgDBprefix*/redirect ( - rd_from int NOT NULL default '0', - rd_namespace int NOT NULL default '0', - rd_title varchar(255) NOT NULL default '', - PRIMARY KEY (rd_from) + rd_from INT NOT NULL DEFAULT 0 REFERENCES /*$wgDBprefix*/[page](page_id) ON DELETE CASCADE, + rd_namespace SMALLINT NOT NULL DEFAULT '0', + rd_title NVARCHAR(255) NOT NULL DEFAULT '', + rd_interwiki NVARCHAR(32) DEFAULT NULL, + rd_fragment NVARCHAR(255) DEFAULT NULL, ); +CREATE UNIQUE INDEX /*$wgDBprefix*/rd_ns_title ON /*$wgDBprefix*/redirect(rd_namespace,rd_title,rd_from); +-- Used for caching expensive grouped queries that need two links (for example double-redirects) CREATE TABLE /*$wgDBprefix*/querycachetwo ( - qcc_type varchar(10) NOT NULL, - qcc_value int NOT NULL default '0', - qcc_namespace int NOT NULL default '0', - qcc_title varchar(255) NOT NULL default '', - qcc_namespacetwo int NOT NULL default '0', - qcc_titletwo varchar(255) NOT NULL default '' + qcc_type NCHAR(32) NOT NULL, + qcc_value INT NOT NULL DEFAULT 0, + qcc_namespace INT NOT NULL DEFAULT 0, + qcc_title NCHAR(255) NOT NULL DEFAULT '', + qcc_namespacetwo INT NOT NULL DEFAULT 0, + qcc_titletwo NCHAR(255) NOT NULL DEFAULT '', + CONSTRAINT /*$wgDBprefix*/qcc_type PRIMARY KEY(qcc_type,qcc_value), ); +CREATE UNIQUE INDEX /*$wgDBprefix*/qcc_title ON /*$wgDBprefix*/querycachetwo(qcc_type,qcc_namespace,qcc_title); +CREATE UNIQUE INDEX /*$wgDBprefix*/qcc_titletwo ON /*$wgDBprefix*/querycachetwo(qcc_type,qcc_namespacetwo,qcc_titletwo); -CREATE TABLE /*$wgDBprefix*/page_restrictions ( - pr_page int NOT NULL, - pr_type varchar(17) NOT NULL, - pr_level varchar(17) NOT NULL, - pr_cascade tinyint NOT NULL, - pr_user int NULL, - pr_expiry varchar(5) NULL, - pr_id int NOT NULL IDENTITY(1,1), - PRIMARY KEY (pr_page,pr_type) -); +--- Used for storing page restrictions (i.e. protection levels) +CREATE TABLE /*$wgDBprefix*/page_restrictions ( + pr_page INT NOT NULL REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE, + pr_type NVARCHAR(200) NOT NULL, + pr_level NVARCHAR(200) NOT NULL, + pr_cascade SMALLINT NOT NULL, + pr_user INT NULL, + pr_expiry DATETIME NULL, + pr_id INT UNIQUE IDENTITY, + CONSTRAINT /*$wgDBprefix*/pr_pagetype PRIMARY KEY(pr_page,pr_type), +); +CREATE INDEX /*$wgDBprefix*/pr_page ON /*$wgDBprefix*/page_restrictions(pr_page); +CREATE INDEX /*$wgDBprefix*/pr_typelevel ON /*$wgDBprefix*/page_restrictions(pr_type,pr_level); +CREATE INDEX /*$wgDBprefix*/pr_pagelevel ON /*$wgDBprefix*/page_restrictions(pr_level); +CREATE INDEX /*$wgDBprefix*/pr_cascade ON /*$wgDBprefix*/page_restrictions(pr_cascade); +; + +-- Protected titles - nonexistent pages that have been protected CREATE TABLE /*$wgDBprefix*/protected_titles ( pt_namespace int NOT NULL, - pt_title varchar(255) NOT NULL, + pt_title NVARCHAR(255) NOT NULL, pt_user int NOT NULL, - pt_reason text, - pt_timestamp varchar(5) NOT NULL, - pt_expiry varchar(5) NOT NULL default '', - pt_create_perm varchar(17) NOT NULL, - PRIMARY KEY (pt_namespace,pt_title) + pt_reason NVARCHAR(3555), + pt_timestamp DATETIME NOT NULL, + pt_expiry DATETIME NOT NULL default '', + pt_create_perm NVARCHAR(60) NOT NULL, + PRIMARY KEY (pt_namespace,pt_title), ); +CREATE INDEX /*$wgDBprefix*/pt_timestamp ON /*$wgDBprefix*/protected_titles(pt_timestamp); +; +-- Name/value pairs indexed by page_id CREATE TABLE /*$wgDBprefix*/page_props ( pp_page int NOT NULL, - pp_propname varchar(17) NOT NULL, - pp_value text NOT NULL, + pp_propname NVARCHAR(60) NOT NULL, + pp_value NVARCHAR(MAX) NOT NULL, PRIMARY KEY (pp_page,pp_propname) ); +-- A table to log updates, one text key row per update. CREATE TABLE /*$wgDBprefix*/updatelog ( - ul_key varchar(255) NOT NULL, + ul_key NVARCHAR(255) NOT NULL, PRIMARY KEY (ul_key) ); +-- NOTE To enable full text indexing on SQL 2008 you need to create an account FDH$MSSQLSERVER +-- AND assign a password for the FDHOST process to run under +-- Once you have assigned a password to that account, you need to run the following stored procedure +-- replacing XXXXX with the password you used. +-- EXEC sp_fulltext_resetfdhostaccount @username = 'FDH$MSSQLSERVER', @password = 'XXXXXX' ; + +--- Add the full-text capabilities, depricated in SQL Server 2005, FTS is enabled on all user created tables by default unless you are using SQL Server 2005 Express +--sp_fulltext_database 'enable'; +--sp_fulltext_catalog 'WikiCatalog', 'create' +--sp_fulltext_table +--sp_fulltext_column +--sp_fulltext_table 'Articles', 'activate' diff --git a/maintenance/mwdocgen.php b/maintenance/mwdocgen.php index b91922c3..92311521 100644 --- a/maintenance/mwdocgen.php +++ b/maintenance/mwdocgen.php @@ -21,7 +21,9 @@ * @todo document * @ingroup Maintenance * - * @author Ashar Voultoiz <thoane@altern.org> + * @author Ashar Voultoiz <hashar at free dot fr> + * @author Brion Vibber + * @author Alexandre Emsenhuber * @version first release */ @@ -29,7 +31,7 @@ # Variables / Configuration # -if( php_sapi_name() != 'cli' ) { +if ( php_sapi_name() != 'cli' ) { echo 'Run me from the command line.'; die( -1 ); } @@ -37,9 +39,6 @@ if( php_sapi_name() != 'cli' ) { /** Figure out the base directory for MediaWiki location */ $mwPath = dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR; -/** Global variable: temporary directory */ -$tmpPath = '/tmp/'; - /** doxygen binary script */ $doxygenBin = 'doxygen'; @@ -50,14 +49,14 @@ $doxygenTemplate = $mwPath . 'maintenance/Doxyfile'; $svnstat = $mwPath . 'bin/svnstat'; /** where Phpdoc should output documentation */ -#$doxyOutput = '/var/www/mwdoc/'; +# $doxyOutput = '/var/www/mwdoc/'; $doxyOutput = $mwPath . 'docs' . DIRECTORY_SEPARATOR ; /** MediaWiki subpaths */ -$mwPathI = $mwPath.'includes/'; -$mwPathL = $mwPath.'languages/'; -$mwPathM = $mwPath.'maintenance/'; -$mwPathS = $mwPath.'skins/'; +$mwPathI = $mwPath . 'includes/'; +$mwPathL = $mwPath . 'languages/'; +$mwPathM = $mwPath . 'maintenance/'; +$mwPathS = $mwPath . 'skins/'; /** Variable to get user input */ $input = ''; @@ -67,11 +66,14 @@ $exclude = ''; # Functions # +define( 'MEDIAWIKI', true ); +require_once( "$mwPath/includes/GlobalFunctions.php" ); + /** * Read a line from the shell * @param $prompt String */ -function readaline( $prompt = '' ){ +function readaline( $prompt = '' ) { print $prompt; $fp = fopen( "php://stdin", "r" ); $resp = trim( fgets( $fp, 1024 ) ); @@ -88,27 +90,27 @@ function getSvnRevision( $dir ) { // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html $entries = $dir . '/.svn/entries'; - if( !file_exists( $entries ) ) { + if ( !file_exists( $entries ) ) { return false; } $content = file( $entries ); // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4) - if( preg_match( '/^<\?xml/', $content[0] ) ) { + if ( preg_match( '/^<\?xml/', $content[0] ) ) { // subversion is release <= 1.3 - if( !function_exists( 'simplexml_load_file' ) ) { + if ( !function_exists( 'simplexml_load_file' ) ) { // We could fall back to expat... YUCK return false; } $xml = simplexml_load_file( $entries ); - if( $xml ) { - foreach( $xml->entry as $entry ) { - if( $xml->entry[0]['name'] == '' ) { + if ( $xml ) { + foreach ( $xml->entry as $entry ) { + if ( $xml->entry[0]['name'] == '' ) { // The directory entry should always have a revision marker. - if( $entry['revision'] ) { + if ( $entry['revision'] ) { return intval( $entry['revision'] ); } } @@ -129,15 +131,14 @@ 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) + * @param $exclude String: Additionals path regex to exlcude + * (LocalSettings.php, AdminSettings.php, .svn and .git directories are always excluded) */ -function generateConfigFile( $doxygenTemplate, $outputDirectory, $stripFromPath, $currentVersion, $svnstat, $input, $exclude ){ - global $tmpPath; +function generateConfigFile( $doxygenTemplate, $outputDirectory, $stripFromPath, $currentVersion, $svnstat, $input, $exclude ) { $template = file_get_contents( $doxygenTemplate ); - // Replace template placeholders by correct values. + // Replace template placeholders by correct values. $replacements = array( '{{OUTPUT_DIRECTORY}}' => $outputDirectory, '{{STRIP_FROM_PATH}}' => $stripFromPath, @@ -147,8 +148,8 @@ function generateConfigFile( $doxygenTemplate, $outputDirectory, $stripFromPath, '{{EXCLUDE}}' => $exclude, ); $tmpCfg = str_replace( array_keys( $replacements ), array_values( $replacements ), $template ); - $tmpFileName = $tmpPath . 'mwdocgen'. rand() .'.tmp'; - file_put_contents( $tmpFileName , $tmpCfg ) or die("Could not write doxygen configuration to file $tmpFileName\n"); + $tmpFileName = tempnam( wfTempDir(), 'mwdocgen-' ); + file_put_contents( $tmpFileName , $tmpCfg ) or die( "Could not write doxygen configuration to file $tmpFileName\n" ); return $tmpFileName; } @@ -159,7 +160,7 @@ function generateConfigFile( $doxygenTemplate, $outputDirectory, $stripFromPath, unset( $file ); -if( is_array( $argv ) && isset( $argv[1] ) ) { +if ( is_array( $argv ) && isset( $argv[1] ) ) { switch( $argv[1] ) { case '--all': $input = 0; break; case '--includes': $input = 1; break; @@ -168,7 +169,7 @@ if( is_array( $argv ) && isset( $argv[1] ) ) { case '--skins': $input = 4; break; case '--file': $input = 5; - if( isset( $argv[2] ) ) { + if ( isset( $argv[2] ) ) { $file = $argv[2]; } break; @@ -178,7 +179,7 @@ if( is_array( $argv ) && isset( $argv[1] ) ) { // TODO : generate a list of paths )) -if( $input === '' ) { +if ( $input === '' ) { echo <<<OPTIONS Several documentation possibilities: 0 : whole documentation (1 + 2 + 3 + 4) @@ -189,33 +190,33 @@ Several documentation possibilities: 5 : only a given file 6 : all but the extensions directory OPTIONS; - while ( !is_numeric($input) ) + while ( !is_numeric( $input ) ) { $input = readaline( "\nEnter your choice [0]:" ); - if($input == '') { + if ( $input == '' ) { $input = 0; } } } -switch ($input) { +switch ( $input ) { case 0: $input = $mwPath; break; case 1: $input = $mwPathI; break; case 2: $input = $mwPathL; break; case 3: $input = $mwPathM; break; case 4: $input = $mwPathS; break; case 5: - if( !isset( $file ) ) { + if ( !isset( $file ) ) { $file = readaline( "Enter file name $mwPath" ); } - $input = $mwPath.$file; + $input = $mwPath . $file; case 6: $input = $mwPath; $exclude = 'extensions'; } $versionNumber = getSvnRevision( $input ); -if( $versionNumber === false ){ #Not using subversion ? +if ( $versionNumber === false ) { # Not using subversion ? $svnstat = ''; # Not really useful if subversion not available $version = 'trunk'; # FIXME } else { diff --git a/maintenance/namespaceDupes.php b/maintenance/namespaceDupes.php index dd29558c..dbb16bcd 100644 --- a/maintenance/namespaceDupes.php +++ b/maintenance/namespaceDupes.php @@ -23,7 +23,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class NamespaceConflictChecker extends Maintenance { public function __construct() { @@ -47,13 +47,13 @@ class NamespaceConflictChecker extends Maintenance { $prefix = $this->getOption( 'prefix', '' ); $key = intval( $this->getOption( 'key', 0 ) ); - if( $prefix ) { + if ( $prefix ) { $retval = $this->checkPrefix( $key, $prefix, $fix, $suffix ); } else { $retval = $this->checkAll( $fix, $suffix ); } - - if( $retval ) { + + if ( $retval ) { $this->output( "\nLooks good!\n" ); } else { $this->output( "\nOh noeees\n" ); @@ -62,44 +62,44 @@ class NamespaceConflictChecker extends Maintenance { /** * @todo Document - * @param $fix bool Whether or not to fix broken entries - * @param $suffix String Suffix to append to renamed articles + * @param $fix Boolean: 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; - + $spaces = array(); - + // List interwikis first, so they'll be overridden // by any conflicting local namespaces. - foreach( $this->getInterwikiList() as $prefix ) { + foreach ( $this->getInterwikiList() as $prefix ) { $name = $wgContLang->ucfirst( $prefix ); $spaces[$name] = 0; } // Now pull in all canonical and alias namespaces... - foreach( $wgCanonicalNamespaceNames as $ns => $name ) { + foreach ( $wgCanonicalNamespaceNames as $ns => $name ) { // This includes $wgExtraNamespaces - if( $name !== '' ) { + if ( $name !== '' ) { $spaces[$name] = $ns; } } - foreach( $wgContLang->getNamespaces() as $ns => $name ) { - if( $name !== '' ) { + foreach ( $wgContLang->getNamespaces() as $ns => $name ) { + if ( $name !== '' ) { $spaces[$name] = $ns; } } - foreach( $wgNamespaceAliases as $name => $ns ) { + foreach ( $wgNamespaceAliases as $name => $ns ) { $spaces[$name] = $ns; } - foreach( $wgContLang->getNamespaceAliases() as $name => $ns ) { + foreach ( $wgContLang->getNamespaceAliases() as $name => $ns ) { $spaces[$name] = $ns; } - + // We'll need to check for lowercase keys as well, // since we're doing case-sensitive searches in the db. - foreach( $spaces as $name => $ns ) { + foreach ( $spaces as $name => $ns ) { $moreNames = array(); $moreNames[] = $wgContLang->uc( $name ); $moreNames[] = $wgContLang->ucfirst( $wgContLang->lc( $name ) ); @@ -107,24 +107,24 @@ class NamespaceConflictChecker extends Maintenance { $moreNames[] = $wgContLang->ucwords( $wgContLang->lc( $name ) ); $moreNames[] = $wgContLang->ucwordbreaks( $name ); $moreNames[] = $wgContLang->ucwordbreaks( $wgContLang->lc( $name ) ); - if( !$wgCapitalLinks ) { - foreach( $moreNames as $altName ) { + if ( !$wgCapitalLinks ) { + foreach ( $moreNames as $altName ) { $moreNames[] = $wgContLang->lcfirst( $altName ); } $moreNames[] = $wgContLang->lcfirst( $name ); } - foreach( array_unique( $moreNames ) as $altName ) { - if( $altName !== $name ) { + foreach ( array_unique( $moreNames ) as $altName ) { + if ( $altName !== $name ) { $spaces[$altName] = $ns; } } } - + ksort( $spaces ); asort( $spaces ); - + $ok = true; - foreach( $spaces as $name => $ns ) { + foreach ( $spaces as $name => $ns ) { $ok = $this->checkNamespace( $ns, $name, $fix, $suffix ) && $ok; } return $ok; @@ -132,44 +132,44 @@ class NamespaceConflictChecker extends Maintenance { /** * Get the interwiki list + * * @todo Needs to respect interwiki cache! - * @return array + * @return Array */ private function getInterwikiList() { $result = $this->db->select( 'interwiki', array( 'iw_prefix' ) ); $prefixes = array(); - foreach( $result as $row ) { + foreach ( $result as $row ) { $prefixes[] = $row->iw_prefix; } - $this->db->freeResult( $result ); return $prefixes; } /** * @todo Document - * @param $ns int A namespace id + * @param $ns Integer: a namespace id * @param $name String - * @param $fix bool Whether to fix broken entries - * @param $suffix String Suffix to append to renamed articles + * @param $fix Boolean: 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 ( $count == 0 ) { return true; } $ok = true; - foreach( $conflicts as $row ) { + foreach ( $conflicts as $row ) { $resolvable = $this->reportConflict( $row, $suffix ); $ok = $ok && $resolvable; - if( $fix && ( $resolvable || $suffix != '' ) ) { + if ( $fix && ( $resolvable || $suffix != '' ) ) { $ok = $this->resolveConflict( $row, $resolvable, $suffix ) && $ok; } } return $ok; } - + /** * @todo: do this for reals */ @@ -181,8 +181,9 @@ class NamespaceConflictChecker extends Maintenance { /** * Find pages in mainspace that have a prefix of the new namespace * so we know titles that will need migrating - * @param $ns int Namespace id (id for new namespace?) - * @param $name String Prefix that is being made a namespace + * + * @param $ns Integer: namespace id (id for new namespace?) + * @param $name String: prefix that is being made a namespace */ private function getConflicts( $ns, $name ) { $page = 'page'; @@ -192,27 +193,26 @@ class NamespaceConflictChecker extends Maintenance { $encNamespace = $this->db->addQuotes( $ns ); $titleSql = "TRIM(LEADING '$prefix:' FROM {$page}_title)"; - if( $ns == 0 ) { + if ( $ns == 0 ) { // An interwiki; try an alternate encoding with '-' for ':' $titleSql = $this->db->buildConcat( array( "'$prefix-'", $titleSql ) ); } - + $sql = "SELECT {$page}_id AS id, - {$page}_title AS oldtitle, - $encNamespace AS namespace, - $titleSql AS title - FROM {$table} - WHERE {$page}_namespace=0 - AND {$page}_title " . $this->db->buildLike( $name . ':', $this->db->anyString() ); + {$page}_title AS oldtitle, + $encNamespace + {$page}_namespace AS namespace, + $titleSql AS title, + {$page}_namespace AS oldnamespace + FROM {$table} + WHERE ( {$page}_namespace=0 OR {$page}_namespace=1 ) + AND {$page}_title " . $this->db->buildLike( $name . ':', $this->db->anyString() ); $result = $this->db->query( $sql, __METHOD__ ); $set = array(); - foreach( $result as $row ) { + foreach ( $result as $row ) { $set[] = $row; } - $this->db->freeResult( $result ); - return $set; } @@ -221,25 +221,27 @@ class NamespaceConflictChecker extends Maintenance { */ private function reportConflict( $row, $suffix ) { $newTitle = Title::makeTitleSafe( $row->namespace, $row->title ); - if( is_null($newTitle) || !$newTitle->canExist() ) { + if ( is_null( $newTitle ) || !$newTitle->canExist() ) { // Title is also an illegal title... // For the moment we'll let these slide to cleanupTitles or whoever. - $this->output( sprintf( "... %d (0,\"%s\")\n", + $this->output( sprintf( "... %d (%d,\"%s\")\n", $row->id, + $row->oldnamespace, $row->oldtitle ) ); $this->output( "... *** cannot resolve automatically; illegal title ***\n" ); return false; } - $this->output( sprintf( "... %d (0,\"%s\") -> (%d,\"%s\") [[%s]]\n", + $this->output( sprintf( "... %d (%d,\"%s\") -> (%d,\"%s\") [[%s]]\n", $row->id, + $row->oldnamespace, $row->oldtitle, $newTitle->getNamespace(), $newTitle->getDBkey(), $newTitle->getPrefixedText() ) ); $id = $newTitle->getArticleId(); - if( $id ) { + if ( $id ) { $this->output( "... *** cannot resolve automatically; page exists with ID $id ***\n" ); return false; } else { @@ -249,24 +251,26 @@ class NamespaceConflictChecker extends Maintenance { /** * 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 + * + * @param $row Object: row from the page table to fix + * @param $resolvable Boolean + * @param $suffix String: suffix to append to the fixed page */ private function resolveConflict( $row, $resolvable, $suffix ) { - if( !$resolvable ) { + if ( !$resolvable ) { $this->output( "... *** old title {$row->title}\n" ); - while( true ) { + while ( true ) { $row->title .= $suffix; $this->output( "... *** new title {$row->title}\n" ); $title = Title::makeTitleSafe( $row->namespace, $row->title ); - if ( ! $title ) { + if ( !$title ) { $this->output( "... !!! invalid title\n" ); return false; } - if ( $id = $title->getArticleId() ) { + $id = $title->getArticleId(); + if ( $id ) { $this->output( "... *** page exists with ID $id ***\n" ); - } else { + } else { break; } } @@ -278,9 +282,10 @@ class NamespaceConflictChecker extends Maintenance { /** * 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 + * + * @param $row Object: row from the old broken entry + * @param $table String: table to update + * @param $prefix String: prefix for column name, like page or ar */ private function resolveConflictOn( $row, $table, $prefix ) { $this->output( "... resolving on $table... " ); @@ -291,8 +296,9 @@ class NamespaceConflictChecker extends Maintenance { "{$prefix}_title" => $newTitle->getDBkey(), ), array( - "{$prefix}_namespace" => 0, - "{$prefix}_title" => $row->oldtitle, + // "{$prefix}_namespace" => 0, + // "{$prefix}_title" => $row->oldtitle, + "{$prefix}_id" => $row->id, ), __METHOD__ ); $this->output( "ok.\n" ); @@ -301,4 +307,4 @@ class NamespaceConflictChecker extends Maintenance { } $maintClass = "NamespaceConflictChecker"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/nextJobDB.php b/maintenance/nextJobDB.php index 75855bb3..8d8229e2 100644 --- a/maintenance/nextJobDB.php +++ b/maintenance/nextJobDB.php @@ -21,7 +21,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class nextJobDB extends Maintenance { public function __construct() { @@ -39,13 +39,13 @@ class nextJobDB extends Maintenance { $pendingDBs = $wgMemc->get( $mckey ); # If we didn't get it from the cache - if( !$pendingDBs ) { + if ( !$pendingDBs ) { $pendingDBs = $this->getPendingDbs( $type ); - $wgMemc->get( $mckey, $pendingDBs, 300 ); + $wgMemc->set( $mckey, $pendingDBs, 300 ); } # If we've got a pending job in a db, display it. if ( $pendingDBs ) { - $this->output( $pendingDBs[mt_rand(0, count( $pendingDBs ) - 1)] ); + $this->output( $pendingDBs[mt_rand( 0, count( $pendingDBs ) - 1 )] ); } } @@ -61,10 +61,10 @@ class nextJobDB extends Maintenance { $dbsByMaster = array(); foreach ( $wgLocalDatabases as $db ) { $lb = wfGetLB( $db ); - $dbsByMaster[$lb->getServerName(0)][] = $db; + $dbsByMaster[$lb->getServerName( 0 )][] = $db; } - foreach ( $dbsByMaster as $master => $dbs ) { + foreach ( $dbsByMaster as $dbs ) { $dbConn = wfGetDB( DB_MASTER, array(), $dbs[0] ); $stype = $dbConn->addQuotes( $type ); @@ -100,4 +100,4 @@ class nextJobDB extends Maintenance { } $maintClass = "nextJobDb"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/nukeNS.php b/maintenance/nukeNS.php index 21e921fe..c89fa94b 100644 --- a/maintenance/nukeNS.php +++ b/maintenance/nukeNS.php @@ -33,7 +33,7 @@ * based on nukePage by Rob Church */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class NukeNS extends Maintenance { public function __construct() { @@ -55,8 +55,8 @@ class NukeNS extends Maintenance { $n_deleted = 0; - foreach( $res as $row ) { - //echo "$ns_name:".$row->page_title, "\n"; + foreach ( $res as $row ) { + // echo "$ns_name:".$row->page_title, "\n"; $title = Title::makeTitle( $ns, $row->page_title ); $id = $title->getArticleID(); @@ -64,19 +64,19 @@ class NukeNS extends Maintenance { $res2 = $dbw->query( "SELECT rev_id FROM $tbl_rev WHERE rev_page = $id" ); $revs = array(); - foreach( $res2 as $row2 ) { + foreach ( $res2 as $row2 ) { $revs[] = $row2->rev_id; } $count = count( $revs ); - //skip anything that looks modified (i.e. multiple revs) + // skip anything that looks modified (i.e. multiple revs) if ( $count == 1 ) { - #echo $title->getPrefixedText(), "\t", $count, "\n"; + # echo $title->getPrefixedText(), "\t", $count, "\n"; $this->output( "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 ) { + // 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 @@ -92,18 +92,18 @@ class NukeNS extends Maintenance { $dbw->commit(); if ( $n_deleted > 0 ) { - #update statistics - better to decrement existing count, or just count - #the page table? + # 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 ), + '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" ); } @@ -111,4 +111,4 @@ class NukeNS extends Maintenance { } $maintClass = "NukeNS"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/nukePage.php b/maintenance/nukePage.php index 16ff2e40..4a073a5e 100644 --- a/maintenance/nukePage.php +++ b/maintenance/nukePage.php @@ -22,7 +22,7 @@ * @author Rob Church <robchur@gmail.com> */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class NukePage extends Maintenance { public function __construct() { @@ -47,7 +47,7 @@ class NukePage extends Maintenance { # Get page ID $this->output( "Searching for \"$name\"..." ); $title = Title::newFromText( $name ); - if( $title ) { + if ( $title ) { $id = $title->getArticleID(); $real = $title->getPrefixedText(); $isGoodArticle = $title->isContentPage(); @@ -56,14 +56,14 @@ class NukePage extends Maintenance { # 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 ) { + 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 ) { + if ( $delete ) { $this->output( "Deleting page record..." ); $dbw->query( "DELETE FROM $tbl_pag WHERE page_id = $id" ); $this->output( "done.\n" ); @@ -75,7 +75,7 @@ class NukePage extends Maintenance { $dbw->commit(); # Delete revisions as appropriate - if( $delete && $count ) { + if ( $delete && $count ) { $this->output( "Deleting revisions..." ); $this->deleteRevisions( $revs ); $this->output( "done.\n" ); @@ -105,9 +105,9 @@ class NukePage extends Maintenance { $set = implode( ', ', $ids ); $dbw->query( "DELETE FROM $tbl_rev WHERE rev_id IN ( $set )" ); - $dbw->commit(); + $dbw->commit(); } } $maintClass = "NukePage"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/testRunner.ora.sql b/maintenance/oracle/archives/patch-testrun.sql index 6e3e1b7c..6e3e1b7c 100644 --- a/maintenance/testRunner.ora.sql +++ b/maintenance/oracle/archives/patch-testrun.sql diff --git a/maintenance/oracle/archives/patch_16_17_schema_changes.sql b/maintenance/oracle/archives/patch_16_17_schema_changes.sql new file mode 100644 index 00000000..64c28481 --- /dev/null +++ b/maintenance/oracle/archives/patch_16_17_schema_changes.sql @@ -0,0 +1,98 @@ +define mw_prefix='{$wgDBprefix}'; + +ALTER TABLE &mw_prefix.archive MODIFY ar_user DEFAULT 0 NOT NULL; +ALTER TABLE &mw_prefix.archive MODIFY ar_deleted CHAR(1); +CREATE INDEX &mw_prefix.archive_i03 ON &mw_prefix.archive (ar_rev_id); + +ALTER TABLE &mw_prefix.page MODIFY page_is_redirect default '0'; +ALTER TABLE &mw_prefix.page MODIFY page_is_new default '0'; +ALTER TABLE &mw_prefix.page MODIFY page_latest default 0; +ALTER TABLE &mw_prefix.page MODIFY page_len default 0; + +ALTER TABLE &mw_prefix.categorylinks MODIFY cl_sortkey VARCHAR2(230); +ALTER TABLE &mw_prefix.categorylinks ADD cl_sortkey_prefix VARCHAR2(255) DEFAULT '' NOT NULL; +ALTER TABLE &mw_prefix.categorylinks ADD cl_collation VARCHAR2(32) DEFAULT '' NOT NULL; +ALTER TABLE &mw_prefix.categorylinks ADD cl_type VARCHAR2(6) DEFAULT 'page' NOT NULL; +DROP INDEX &mw_prefix.categorylinks_i01; +CREATE INDEX &mw_prefix.categorylinks_i01 ON &mw_prefix.categorylinks (cl_to,cl_type,cl_sortkey,cl_from); +CREATE INDEX &mw_prefix.categorylinks_i03 ON &mw_prefix.categorylinks (cl_collation); + +ALTER TABLE &mw_prefix.filearchive MODIFY fa_deleted_user DEFAULT 0 NOT NULL; +ALTER TABLE &mw_prefix.filearchive MODIFY fa_size DEFAULT 0; +ALTER TABLE &mw_prefix.filearchive MODIFY fa_width DEFAULT 0; +ALTER TABLE &mw_prefix.filearchive MODIFY fa_height DEFAULT 0; +ALTER TABLE &mw_prefix.filearchive MODIFY fa_bits DEFAULT 0 NOT NULL; +ALTER TABLE &mw_prefix.filearchive MODIFY fa_user DEFAULT 0 NOT NULL; +ALTER TABLE &mw_prefix.filearchive MODIFY fa_deleted DEFAULT 0; + +ALTER TABLE &mw_prefix.image MODIFY img_size DEFAULT 0; +ALTER TABLE &mw_prefix.image MODIFY img_width DEFAULT 0; +ALTER TABLE &mw_prefix.image MODIFY img_height DEFAULT 0; +ALTER TABLE &mw_prefix.image MODIFY img_bits DEFAULT 0 NOT NULL; +ALTER TABLE &mw_prefix.image MODIFY img_user DEFAULT 0 NOT NULL; + +ALTER TABLE &mw_prefix.interwiki ADD iw_api BLOB DEFAULT EMPTY_BLOB(); +ALTER TABLE &mw_prefix.interwiki MODIFY iw_api DEFAULT NULL NOT NULL; +ALTER TABLE &mw_prefix.interwiki ADD iw_wikiid VARCHAR2(64); + +ALTER TABLE &mw_prefix.ipblocks MODIFY ipb_user DEFAULT 0 NOT NULL; +ALTER TABLE &mw_prefix.ipblocks MODIFY ipb_by DEFAULT 0; + +CREATE TABLE &mw_prefix.iwlinks ( + iwl_from NUMBER DEFAULT 0 NOT NULL, + iwl_prefix VARCHAR2(20) DEFAULT '' NOT NULL, + iwl_title VARCHAR2(255) DEFAULT '' NOT NULL +); +CREATE UNIQUE INDEX &mw_prefix.iwlinks_ui01 ON &mw_prefix.iwlinks (iwl_from, iwl_prefix, iwl_title); +CREATE UNIQUE INDEX &mw_prefix.iwlinks_ui02 ON &mw_prefix.iwlinks (iwl_prefix, iwl_title, iwl_from); + +ALTER TABLE &mw_prefix.logging MODIFY log_user DEFAULT 0 NOT NULL; +ALTER TABLE &mw_prefix.logging MODIFY log_deleted CHAR(1); + +CREATE TABLE &mw_prefix.module_deps ( + md_module VARCHAR2(255) NOT NULL, + md_skin VARCHAR2(32) NOT NULL, + md_deps BLOB NOT NULL +); +CREATE UNIQUE INDEX &mw_prefix.module_deps_u01 ON &mw_prefix.module_deps (md_module, md_skin); + +CREATE TABLE &mw_prefix.msg_resource_links ( + mrl_resource VARCHAR2(255) NOT NULL, + mrl_message VARCHAR2(255) NOT NULL +); +CREATE UNIQUE INDEX &mw_prefix.msg_resource_links_u01 ON &mw_prefix.msg_resource_links (mrl_message, mrl_resource); + +CREATE TABLE &mw_prefix.msg_resource ( + mr_resource VARCHAR2(255) NOT NULL, + mr_lang varchar2(32) NOT NULL, + mr_blob BLOB NOT NULL, + mr_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL +) ; +CREATE UNIQUE INDEX &mw_prefix.msg_resource_u01 ON &mw_prefix.msg_resource (mr_resource, mr_lang); + +ALTER TABLE &mw_prefix.oldimage MODIFY oi_name DEFAULT 0; +ALTER TABLE &mw_prefix.oldimage MODIFY oi_size DEFAULT 0; +ALTER TABLE &mw_prefix.oldimage MODIFY oi_width DEFAULT 0; +ALTER TABLE &mw_prefix.oldimage MODIFY oi_height DEFAULT 0; +ALTER TABLE &mw_prefix.oldimage MODIFY oi_bits DEFAULT 0; +ALTER TABLE &mw_prefix.oldimage MODIFY oi_user DEFAULT 0 NOT NULL; + +ALTER TABLE &mw_prefix.querycache MODIFY qc_value DEFAULT 0; + +ALTER TABLE &mw_prefix.recentchanges MODIFY rc_user DEFAULT 0 NOT NULL; +ALTER TABLE &mw_prefix.recentchanges MODIFY rc_cur_id DEFAULT 0 NOT NULL; +ALTER TABLE &mw_prefix.recentchanges MODIFY rc_this_oldid DEFAULT 0; +ALTER TABLE &mw_prefix.recentchanges MODIFY rc_last_oldid DEFAULT 0; +ALTER TABLE &mw_prefix.recentchanges MODIFY rc_moved_to_ns DEFAULT 0 NOT NULL; +ALTER TABLE &mw_prefix.recentchanges MODIFY rc_deleted CHAR(1); +ALTER TABLE &mw_prefix.recentchanges MODIFY rc_logid DEFAULT 0; + +ALTER TABLE &mw_prefix.revision MODIFY rev_page NOT NULL; +ALTER TABLE &mw_prefix.revision MODIFY rev_user DEFAULT 0; + +ALTER TABLE &mw_prefix.updatelog ADD ul_value BLOB; + +ALTER TABLE &mw_prefix.user_groups MODIFY ug_user DEFAULT 0 NOT NULL; + +ALTER TABLE &mw_prefix.user_newtalk MODIFY user_id DEFAULT 0; + diff --git a/maintenance/oracle/archives/patch_create_17_functions.sql b/maintenance/oracle/archives/patch_create_17_functions.sql new file mode 100644 index 00000000..6c9c9542 --- /dev/null +++ b/maintenance/oracle/archives/patch_create_17_functions.sql @@ -0,0 +1,125 @@ +define mw_prefix='{$wgDBprefix}'; + +/*$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); + l_temp_ei_sql VARCHAR2(2000); +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 + l_temp_ei_sql := SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'PCTFREE') - 1); + l_temp_ei_sql := SUBSTR(l_temp_ei_sql, 1, INSTR(l_temp_ei_sql, ')', INSTR(l_temp_ei_sql, 'PRIMARY KEY')+1)+1); + EXECUTE IMMEDIATE l_temp_ei_sql; + END LOOP; + IF (NOT p_temporary) THEN + 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; + END IF; + 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, + index_type + FROM user_indexes ui + WHERE table_name = p_oldprefix || p_tabname + AND index_type NOT IN ('LOB', 'DOMAIN') + AND NOT EXISTS + (SELECT NULL + FROM user_constraints + WHERE table_name = ui.table_name + AND constraint_name = ui.index_name)) LOOP + l_temp_ei_sql := SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'PCTFREE') - 1); + l_temp_ei_sql := SUBSTR(l_temp_ei_sql, 1, INSTR(l_temp_ei_sql, ')', INSTR(l_temp_ei_sql, '"' || USER || '"."' || p_newprefix || '"')+1)+1); + EXECUTE IMMEDIATE l_temp_ei_sql; + 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 + l_temp_ei_sql := SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'ALTER ') - 1); + dbms_output.put_line(l_temp_ei_sql); + EXECUTE IMMEDIATE l_temp_ei_sql; + END LOOP; +END; +/*$mw$*/ + +CREATE OR REPLACE TYPE GET_OUTPUT_TYPE IS TABLE OF VARCHAR2(255); + +/*$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$*/ + diff --git a/maintenance/oracle/archives/patch_fk_rename_deferred.sql b/maintenance/oracle/archives/patch_fk_rename_deferred.sql new file mode 100644 index 00000000..ce5be9af --- /dev/null +++ b/maintenance/oracle/archives/patch_fk_rename_deferred.sql @@ -0,0 +1,41 @@ +define mw_prefix='{$wgDBprefix}'; + +/*$mw$*/ +BEGIN +-- drop all, recreate manual in case anyone was missing + FOR cc1 IN (SELECT uc.table_name, + uc.constraint_name + FROM user_constraints uc + WHERE uc.constraint_type = 'R') LOOP + EXECUTE IMMEDIATE 'ALTER TABLE &mw_prefix.' || cc1.table_name || + ' DROP CONSTRAINT ' || cc1.constraint_name; + END LOOP; +END; +/*$mw$*/ + +ALTER TABLE &mw_prefix.user_groups ADD CONSTRAINT &mw_prefix.user_groups_fk1 FOREIGN KEY (ug_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.user_newtalk ADD CONSTRAINT &mw_prefix.user_newtalk_fk1 FOREIGN KEY (user_id) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.revision ADD CONSTRAINT &mw_prefix.revision_fk1 FOREIGN KEY (rev_page) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.revision ADD CONSTRAINT &mw_prefix.revision_fk2 FOREIGN KEY (rev_user) REFERENCES &mw_prefix.mwuser(user_id) DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.archive ADD CONSTRAINT &mw_prefix.archive_fk1 FOREIGN KEY (ar_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.pagelinks ADD CONSTRAINT &mw_prefix.pagelinks_fk1 FOREIGN KEY (pl_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.templatelinks ADD CONSTRAINT &mw_prefix.templatelinks_fk1 FOREIGN KEY (tl_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.imagelinks ADD CONSTRAINT &mw_prefix.imagelinks_fk1 FOREIGN KEY (il_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.categorylinks ADD CONSTRAINT &mw_prefix.categorylinks_fk1 FOREIGN KEY (cl_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.externallinks ADD CONSTRAINT &mw_prefix.externallinks_fk1 FOREIGN KEY (el_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.langlinks ADD CONSTRAINT &mw_prefix.langlinks_fk1 FOREIGN KEY (ll_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.ipblocks ADD CONSTRAINT &mw_prefix.ipblocks_fk1 FOREIGN KEY (ipb_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.ipblocks ADD CONSTRAINT &mw_prefix.ipblocks_fk2 FOREIGN KEY (ipb_by) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.image ADD CONSTRAINT &mw_prefix.image_fk1 FOREIGN KEY (img_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.oldimage ADD CONSTRAINT &mw_prefix.oldimage_fk1 FOREIGN KEY (oi_name) REFERENCES &mw_prefix.image(img_name) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.oldimage ADD CONSTRAINT &mw_prefix.oldimage_fk2 FOREIGN KEY (oi_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.filearchive ADD CONSTRAINT &mw_prefix.filearchive_fk1 FOREIGN KEY (fa_deleted_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.filearchive ADD CONSTRAINT &mw_prefix.filearchive_fk2 FOREIGN KEY (fa_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.recentchanges ADD CONSTRAINT &mw_prefix.recentchanges_fk1 FOREIGN KEY (rc_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.recentchanges ADD CONSTRAINT &mw_prefix.recentchanges_fk2 FOREIGN KEY (rc_cur_id) REFERENCES &mw_prefix.page(page_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.watchlist ADD CONSTRAINT &mw_prefix.watchlist_fk1 FOREIGN KEY (wl_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.logging ADD CONSTRAINT &mw_prefix.logging_fk1 FOREIGN KEY (log_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.trackbacks ADD CONSTRAINT &mw_prefix.trackbacks_fk1 FOREIGN KEY (tb_page) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.redirect ADD CONSTRAINT &mw_prefix.redirect_fk1 FOREIGN KEY (rd_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.page_restrictions ADD CONSTRAINT &mw_prefix.page_restrictions_fk1 FOREIGN KEY (pr_page) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; + diff --git a/maintenance/oracle/archives/patch_namespace_defaults.sql b/maintenance/oracle/archives/patch_namespace_defaults.sql new file mode 100644 index 00000000..24c95643 --- /dev/null +++ b/maintenance/oracle/archives/patch_namespace_defaults.sql @@ -0,0 +1,17 @@ +define mw_prefix='{$wgDBprefix}'; + +ALTER TABLE &mw_prefix.page MODIFY page_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.archive MODIFY ar_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.pagelinks MODIFY pl_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.templatelinks MODIFY tl_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.recentchanges MODIFY rc_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.querycache MODIFY qc_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.logging MODIFY log_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.job MODIFY job_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.redirect MODIFY rd_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.protected_titles MODIFY pt_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.archive MODIFY ar_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.archive MODIFY ar_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.archive MODIFY ar_namespace DEFAULT 0; +ALTER TABLE &mw_prefix.archive MODIFY ar_namespace DEFAULT 0; + diff --git a/maintenance/ora/patch_seq_names_pre1.16.sql b/maintenance/oracle/patch_seq_names_pre1.16.sql index 5346b141..5346b141 100644 --- a/maintenance/ora/patch_seq_names_pre1.16.sql +++ b/maintenance/oracle/patch_seq_names_pre1.16.sql diff --git a/maintenance/ora/tables.sql b/maintenance/oracle/tables.sql index d2d1a21b..b8ecc2b9 100644 --- a/maintenance/ora/tables.sql +++ b/maintenance/oracle/tables.sql @@ -29,17 +29,19 @@ 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 &mw_prefix.user_groups ( - ug_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE, + ug_user NUMBER DEFAULT 0 NOT NULL, ug_group VARCHAR2(16) NOT NULL ); +ALTER TABLE &mw_prefix.user_groups ADD CONSTRAINT &mw_prefix.user_groups_fk1 FOREIGN KEY (ug_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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 &mw_prefix.user_newtalk ( - user_id NUMBER NOT NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE, + user_id NUMBER DEFAULT 0 NOT NULL, user_ip VARCHAR2(40) NULL, user_last_timestamp TIMESTAMP(6) WITH TIME ZONE ); +ALTER TABLE &mw_prefix.user_newtalk ADD CONSTRAINT &mw_prefix.user_newtalk_fk1 FOREIGN KEY (user_id) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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); @@ -51,26 +53,29 @@ CREATE TABLE &mw_prefix.user_properties ( 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 page_page_id_seq; CREATE TABLE &mw_prefix.page ( page_id NUMBER NOT NULL, - page_namespace NUMBER NOT NULL, + page_namespace NUMBER DEFAULT 0 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_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 + page_latest NUMBER DEFAULT 0 NOT NULL, -- FK? + page_len NUMBER DEFAULT 0 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); +-- Create a dummy page to satisfy fk contraints especially with revisions +INSERT INTO &mw_prefix.page + VALUES (0, 0, ' ', NULL, 0, 0, 0, 0, current_timestamp, 0, 0); + /*$mw$*/ CREATE TRIGGER &mw_prefix.page_set_random BEFORE INSERT ON &mw_prefix.page FOR EACH ROW WHEN (new.page_random IS NULL) @@ -82,10 +87,10 @@ END; 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_page NUMBER NOT NULL, rev_text_id NUMBER NULL, rev_comment VARCHAR2(255), - rev_user NUMBER NOT NULL REFERENCES &mw_prefix.mwuser(user_id), + rev_user NUMBER DEFAULT 0 NOT NULL, 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, @@ -94,6 +99,8 @@ CREATE TABLE &mw_prefix.revision ( rev_parent_id NUMBER DEFAULT NULL ); ALTER TABLE &mw_prefix.revision ADD CONSTRAINT &mw_prefix.revision_pk PRIMARY KEY (rev_id); +ALTER TABLE &mw_prefix.revision ADD CONSTRAINT &mw_prefix.revision_fk1 FOREIGN KEY (rev_page) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.revision ADD CONSTRAINT &mw_prefix.revision_fk2 FOREIGN KEY (rev_user) REFERENCES &mw_prefix.mwuser(user_id) DEFERRABLE INITIALLY DEFERRED; 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); @@ -109,59 +116,68 @@ CREATE TABLE &mw_prefix.pagecontent ( -- replaces reserved word 'text' ALTER TABLE &mw_prefix.pagecontent ADD CONSTRAINT &mw_prefix.pagecontent_pk PRIMARY KEY (old_id); CREATE TABLE &mw_prefix.archive ( - ar_namespace NUMBER NOT NULL, + ar_namespace NUMBER DEFAULT 0 NOT NULL, ar_title VARCHAR2(255) NOT NULL, ar_text CLOB, ar_comment VARCHAR2(255), - ar_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL, + ar_user NUMBER DEFAULT 0 NOT 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_deleted CHAR(1) DEFAULT '0' NOT NULL, ar_len NUMBER, ar_page_id NUMBER, ar_parent_id NUMBER ); +ALTER TABLE &mw_prefix.archive ADD CONSTRAINT &mw_prefix.archive_fk1 FOREIGN KEY (ar_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; 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 INDEX &mw_prefix.archive_i03 ON &mw_prefix.archive (ar_rev_id); 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_from NUMBER NOT NULL, + pl_namespace NUMBER DEFAULT 0 NOT NULL, pl_title VARCHAR2(255) NOT NULL ); +ALTER TABLE &mw_prefix.pagelinks ADD CONSTRAINT &mw_prefix.pagelinks_fk1 FOREIGN KEY (pl_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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_from NUMBER NOT NULL, + tl_namespace NUMBER DEFAULT 0 NOT NULL, tl_title VARCHAR2(255) NOT NULL ); +ALTER TABLE &mw_prefix.templatelinks ADD CONSTRAINT &mw_prefix.templatelinks_fk1 FOREIGN KEY (tl_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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_from NUMBER NOT NULL, il_to VARCHAR2(255) NOT NULL ); +ALTER TABLE &mw_prefix.imagelinks ADD CONSTRAINT &mw_prefix.imagelinks_fk1 FOREIGN KEY (il_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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_from NUMBER NOT NULL, cl_to VARCHAR2(255) NOT NULL, - cl_sortkey VARCHAR2(255), - cl_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL + cl_sortkey VARCHAR2(230), + cl_sortkey_prefix VARCHAR2(255) DEFAULT '' NOT NULL, + cl_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL, + cl_collation VARCHAR2(32) DEFAULT '' NOT NULL, + cl_type VARCHAR2(6) DEFAULT 'page' NOT NULL ); +ALTER TABLE &mw_prefix.categorylinks ADD CONSTRAINT &mw_prefix.categorylinks_fk1 FOREIGN KEY (cl_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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_i01 ON &mw_prefix.categorylinks (cl_to,cl_type,cl_sortkey,cl_from); CREATE INDEX &mw_prefix.categorylinks_i02 ON &mw_prefix.categorylinks (cl_to,cl_timestamp); +CREATE INDEX &mw_prefix.categorylinks_i03 ON &mw_prefix.categorylinks (cl_collation); CREATE SEQUENCE category_cat_id_seq; CREATE TABLE &mw_prefix.category ( @@ -177,10 +193,11 @@ 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_from NUMBER NOT NULL, el_to VARCHAR2(2048) NOT NULL, el_index VARCHAR2(2048) NOT NULL ); +ALTER TABLE &mw_prefix.externallinks ADD CONSTRAINT &mw_prefix.externallinks_fk1 FOREIGN KEY (el_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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); @@ -193,13 +210,22 @@ ALTER TABLE &mw_prefix.external_user ADD CONSTRAINT &mw_prefix.external_user_pk 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_from NUMBER NOT NULL, ll_lang VARCHAR2(20), ll_title VARCHAR2(255) ); +ALTER TABLE &mw_prefix.langlinks ADD CONSTRAINT &mw_prefix.langlinks_fk1 FOREIGN KEY (ll_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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.iwlinks ( + iwl_from NUMBER DEFAULT 0 NOT NULL, + iwl_prefix VARCHAR2(20) DEFAULT '' NOT NULL, + iwl_title VARCHAR2(255) DEFAULT '' NOT NULL +); +CREATE UNIQUE INDEX &mw_prefix.iwlinks_ui01 ON &mw_prefix.iwlinks (iwl_from, iwl_prefix, iwl_title); +CREATE UNIQUE INDEX &mw_prefix.iwlinks_ui02 ON &mw_prefix.iwlinks (iwl_prefix, iwl_title, iwl_from); + CREATE TABLE &mw_prefix.site_stats ( ss_row_id NUMBER NOT NULL , ss_total_views NUMBER DEFAULT 0, @@ -221,8 +247,8 @@ 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_user NUMBER DEFAULT 0 NOT NULL, + ipb_by NUMBER DEFAULT 0 NOT NULL, ipb_by_text VARCHAR2(255) NOT NULL, ipb_reason VARCHAR2(255) NOT NULL, ipb_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL, @@ -238,6 +264,8 @@ CREATE TABLE &mw_prefix.ipblocks ( ipb_allow_usertalk CHAR(1) DEFAULT '0' NOT NULL ); ALTER TABLE &mw_prefix.ipblocks ADD CONSTRAINT &mw_prefix.ipblocks_pk PRIMARY KEY (ipb_id); +ALTER TABLE &mw_prefix.ipblocks ADD CONSTRAINT &mw_prefix.ipblocks_fk1 FOREIGN KEY (ipb_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.ipblocks ADD CONSTRAINT &mw_prefix.ipblocks_fk2 FOREIGN KEY (ipb_by) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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); @@ -246,21 +274,22 @@ 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_size NUMBER DEFAULT 0 NOT NULL, + img_width NUMBER DEFAULT 0 NOT NULL, + img_height NUMBER DEFAULT 0 NOT NULL, img_metadata CLOB, - img_bits NUMBER, + img_bits NUMBER DEFAULT 0 NOT NULL, 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 NUMBER DEFAULT 0 NOT NULL, img_user_text VARCHAR2(255) NOT NULL, img_timestamp TIMESTAMP(6) WITH TIME ZONE, - img_sha1 VARCHAR2(32) + img_sha1 VARCHAR2(32) ); ALTER TABLE &mw_prefix.image ADD CONSTRAINT &mw_prefix.image_pk PRIMARY KEY (img_name); +ALTER TABLE &mw_prefix.image ADD CONSTRAINT &mw_prefix.image_fk1 FOREIGN KEY (img_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; 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); @@ -268,14 +297,14 @@ 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_name VARCHAR2(255) DEFAULT 0 NOT NULL, 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_size NUMBER DEFAULT 0 NOT NULL, + oi_width NUMBER DEFAULT 0 NOT NULL, + oi_height NUMBER DEFAULT 0 NOT NULL, + oi_bits NUMBER DEFAULT 0 NOT NULL, oi_description VARCHAR2(255), - oi_user NUMBER NULL REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL, + oi_user NUMBER DEFAULT 0 NOT NULL, oi_user_text VARCHAR2(255) NOT NULL, oi_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL, oi_metadata CLOB, @@ -285,6 +314,8 @@ CREATE TABLE &mw_prefix.oldimage ( oi_deleted NUMBER DEFAULT 0 NOT NULL, oi_sha1 VARCHAR2(32) ); +ALTER TABLE &mw_prefix.oldimage ADD CONSTRAINT &mw_prefix.oldimage_fk1 FOREIGN KEY (oi_name) REFERENCES &mw_prefix.image(img_name) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.oldimage ADD CONSTRAINT &mw_prefix.oldimage_fk2 FOREIGN KEY (oi_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; 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); @@ -298,24 +329,26 @@ CREATE TABLE &mw_prefix.filearchive ( 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_user NUMBER DEFAULT 0 NOT NULL, fa_deleted_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL, fa_deleted_reason CLOB, - fa_size NUMBER NOT NULL, - fa_width NUMBER NOT NULL, - fa_height NUMBER NOT NULL, + fa_size NUMBER DEFAULT 0 NOT NULL, + fa_width NUMBER DEFAULT 0 NOT NULL, + fa_height NUMBER DEFAULT 0 NOT NULL, fa_metadata CLOB, - fa_bits NUMBER, + fa_bits NUMBER DEFAULT 0 NOT NULL, 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 NUMBER DEFAULT 0 NOT NULL, fa_user_text VARCHAR2(255) NOT NULL, fa_timestamp TIMESTAMP(6) WITH TIME ZONE, - fa_deleted NUMBER DEFAULT '0' NOT NULL + fa_deleted NUMBER DEFAULT 0 NOT NULL ); ALTER TABLE &mw_prefix.filearchive ADD CONSTRAINT &mw_prefix.filearchive_pk PRIMARY KEY (fa_id); +ALTER TABLE &mw_prefix.filearchive ADD CONSTRAINT &mw_prefix.filearchive_fk1 FOREIGN KEY (fa_deleted_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.filearchive ADD CONSTRAINT &mw_prefix.filearchive_fk2 FOREIGN KEY (fa_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; 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); @@ -326,31 +359,33 @@ 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 NUMBER DEFAULT 0 NOT NULL, rc_user_text VARCHAR2(255) NOT NULL, - rc_namespace NUMBER NOT NULL, + rc_namespace NUMBER DEFAULT 0 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_cur_id NUMBER DEFAULT 0 NOT NULL, + rc_this_oldid NUMBER DEFAULT 0 NOT NULL, + rc_last_oldid NUMBER DEFAULT 0 NOT NULL, rc_type CHAR(1) DEFAULT '0' NOT NULL, - rc_moved_to_ns NUMBER, + rc_moved_to_ns NUMBER DEFAULT 0 NOT NULL, 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_deleted CHAR(1) 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); +ALTER TABLE &mw_prefix.recentchanges ADD CONSTRAINT &mw_prefix.recentchanges_fk1 FOREIGN KEY (rc_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE &mw_prefix.recentchanges ADD CONSTRAINT &mw_prefix.recentchanges_fk2 FOREIGN KEY (rc_cur_id) REFERENCES &mw_prefix.page(page_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; 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); @@ -360,11 +395,12 @@ CREATE INDEX &mw_prefix.recentchanges_i06 ON &mw_prefix.recentchanges (rc_namesp 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_user NUMBER NOT NULL, wl_namespace NUMBER DEFAULT 0 NOT NULL, wl_title VARCHAR2(255) NOT NULL, wl_notificationtimestamp TIMESTAMP(6) WITH TIME ZONE ); +ALTER TABLE &mw_prefix.watchlist ADD CONSTRAINT &mw_prefix.watchlist_fk1 FOREIGN KEY (wl_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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); @@ -388,6 +424,8 @@ CREATE UNIQUE INDEX &mw_prefix.searchindex_u01 ON &mw_prefix.searchindex (si_pag CREATE TABLE &mw_prefix.interwiki ( iw_prefix VARCHAR2(32) NOT NULL, iw_url VARCHAR2(127) NOT NULL, + iw_api BLOB NOT NULL, + iw_wikiid VARCHAR2(64), iw_local CHAR(1) NOT NULL, iw_trans CHAR(1) DEFAULT '0' NOT NULL ); @@ -395,8 +433,8 @@ CREATE UNIQUE INDEX &mw_prefix.interwiki_u01 ON &mw_prefix.interwiki (iw_prefix) CREATE TABLE &mw_prefix.querycache ( qc_type VARCHAR2(32) NOT NULL, - qc_value NUMBER NOT NULL, - qc_namespace NUMBER NOT NULL, + qc_value NUMBER DEFAULT 0 NOT NULL, + qc_namespace NUMBER DEFAULT 0 NOT NULL, qc_title VARCHAR2(255) NOT NULL ); CREATE INDEX &mw_prefix.querycache_u01 ON &mw_prefix.querycache (qc_type,qc_value); @@ -422,16 +460,17 @@ CREATE TABLE &mw_prefix.logging ( 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 NUMBER DEFAULT 0 NOT NULL, log_user_text VARCHAR2(255), - log_namespace NUMBER NOT NULL, + log_namespace NUMBER DEFAULT 0 NOT NULL, log_title VARCHAR2(255) NOT NULL, - log_page NUMBER, + log_page NUMBER, log_comment VARCHAR2(255), log_params CLOB, - log_deleted NUMBER DEFAULT '0' NOT NULL + log_deleted CHAR(1) DEFAULT '0' NOT NULL ); ALTER TABLE &mw_prefix.logging ADD CONSTRAINT &mw_prefix.logging_pk PRIMARY KEY (log_id); +ALTER TABLE &mw_prefix.logging ADD CONSTRAINT &mw_prefix.logging_fk1 FOREIGN KEY (log_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED; 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); @@ -448,20 +487,21 @@ CREATE INDEX &mw_prefix.log_search_i01 ON &mw_prefix.log_search (ls_log_id); CREATE SEQUENCE trackbacks_tb_id_seq; CREATE TABLE &mw_prefix.trackbacks ( tb_id NUMBER NOT NULL, - tb_page NUMBER REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE, + tb_page NUMBER, tb_title VARCHAR2(255) NOT NULL, tb_url VARCHAR2(255) NOT NULL, tb_ex CLOB, tb_name VARCHAR2(255) ); ALTER TABLE &mw_prefix.trackbacks ADD CONSTRAINT &mw_prefix.trackbacks_pk PRIMARY KEY (tb_id); +ALTER TABLE &mw_prefix.trackbacks ADD CONSTRAINT &mw_prefix.trackbacks_fk1 FOREIGN KEY (tb_page) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; CREATE INDEX &mw_prefix.trackbacks_i01 ON &mw_prefix.trackbacks (tb_page); CREATE SEQUENCE job_job_id_seq; CREATE TABLE &mw_prefix.job ( job_id NUMBER NOT NULL, job_cmd VARCHAR2(60) NOT NULL, - job_namespace NUMBER NOT NULL, + job_namespace NUMBER DEFAULT 0 NOT NULL, job_title VARCHAR2(255) NOT NULL, job_params CLOB NOT NULL ); @@ -475,12 +515,13 @@ CREATE TABLE &mw_prefix.querycache_info ( 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_from NUMBER NOT NULL, + rd_namespace NUMBER DEFAULT 0 NOT NULL, rd_title VARCHAR2(255) NOT NULL, rd_interwiki VARCHAR2(32), rd_fragment VARCHAR2(255) ); +ALTER TABLE &mw_prefix.redirect ADD CONSTRAINT &mw_prefix.redirect_fk1 FOREIGN KEY (rd_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; CREATE INDEX &mw_prefix.redirect_i01 ON &mw_prefix.redirect (rd_namespace,rd_title,rd_from); CREATE TABLE &mw_prefix.querycachetwo ( @@ -498,7 +539,7 @@ CREATE INDEX &mw_prefix.querycachetwo_i03 ON &mw_prefix.querycachetwo (qcc_type, 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_page NUMBER NOT NULL, pr_type VARCHAR2(255) NOT NULL, pr_level VARCHAR2(255) NOT NULL, pr_cascade NUMBER NOT NULL, @@ -506,12 +547,13 @@ CREATE TABLE &mw_prefix.page_restrictions ( 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); +ALTER TABLE &mw_prefix.page_restrictions ADD CONSTRAINT &mw_prefix.page_restrictions_fk1 FOREIGN KEY (pr_page) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; 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_namespace NUMBER DEFAULT 0 NOT NULL, pt_title VARCHAR2(255) NOT NULL, pt_user NUMBER NOT NULL, pt_reason VARCHAR2(255), @@ -531,7 +573,8 @@ CREATE UNIQUE INDEX &mw_prefix.page_props_u01 ON &mw_prefix.page_props (pp_page, CREATE TABLE &mw_prefix.updatelog ( - ul_key VARCHAR2(255) NOT NULL + ul_key VARCHAR2(255) NOT NULL, + ul_value BLOB ); ALTER TABLE &mw_prefix.updatelog ADD CONSTRAINT &mw_prefix.updatelog_pk PRIMARY KEY (ul_key); @@ -571,8 +614,8 @@ ALTER TABLE &mw_prefix.valid_tag ADD CONSTRAINT &mw_prefix.valid_tag_pk PRIMARY --); --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 INDEX &mw_prefix.si_title_idx ON &mw_prefix.searchindex(si_title) INDEXTYPE IS ctxsys.context; +CREATE INDEX &mw_prefix.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, @@ -581,6 +624,27 @@ CREATE TABLE &mw_prefix.l10n_cache ( ); CREATE INDEX &mw_prefix.l10n_cache_u01 ON &mw_prefix.l10n_cache (lc_lang, lc_key); +CREATE TABLE &mw_prefix.msg_resource ( + mr_resource VARCHAR2(255) NOT NULL, + mr_lang varchar2(32) NOT NULL, + mr_blob BLOB NOT NULL, + mr_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL +) ; +CREATE UNIQUE INDEX &mw_prefix.msg_resource_u01 ON &mw_prefix.msg_resource (mr_resource, mr_lang); + +CREATE TABLE &mw_prefix.msg_resource_links ( + mrl_resource VARCHAR2(255) NOT NULL, + mrl_message VARCHAR2(255) NOT NULL +); +CREATE UNIQUE INDEX &mw_prefix.msg_resource_links_u01 ON &mw_prefix.msg_resource_links (mrl_message, mrl_resource); + +CREATE TABLE &mw_prefix.module_deps ( + md_module VARCHAR2(255) NOT NULL, + md_skin VARCHAR2(32) NOT NULL, + md_deps BLOB NOT NULL +); +CREATE UNIQUE INDEX &mw_prefix.module_deps_u01 ON &mw_prefix.module_deps (md_module, md_skin); + -- do not prefix this table as it breaks parserTests CREATE TABLE wiki_field_info_full ( table_name VARCHAR2(35) NOT NULL, @@ -653,6 +717,7 @@ CREATE OR REPLACE PROCEDURE duplicate_table(p_tabname IN VARCHAR2, p_temporary IN BOOLEAN) IS e_table_not_exist EXCEPTION; PRAGMA EXCEPTION_INIT(e_table_not_exist, -00942); + l_temp_ei_sql VARCHAR2(2000); BEGIN BEGIN EXECUTE IMMEDIATE 'DROP TABLE ' || p_newprefix || p_tabname || @@ -676,7 +741,7 @@ BEGIN 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); + SUBSTR(rc.data_default, 1, 2000); END LOOP; FOR rc IN (SELECT REPLACE(REPLACE(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('CONSTRAINT', constraint_name), @@ -690,11 +755,11 @@ BEGIN 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); + l_temp_ei_sql := SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'PCTFREE') - 1); + l_temp_ei_sql := SUBSTR(l_temp_ei_sql, 1, INSTR(l_temp_ei_sql, ')', INSTR(l_temp_ei_sql, 'PRIMARY KEY')+1)+1); + EXECUTE IMMEDIATE l_temp_ei_sql; END LOOP; + IF (NOT p_temporary) THEN FOR rc IN (SELECT REPLACE(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('REF_CONSTRAINT', constraint_name), 32767, @@ -707,6 +772,7 @@ BEGIN AND constraint_type = 'R') LOOP EXECUTE IMMEDIATE rc.ddlvc2; END LOOP; + END IF; FOR rc IN (SELECT REPLACE(REPLACE(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('INDEX', index_name), 32767, @@ -715,16 +781,19 @@ BEGIN USER || '"."' || p_newprefix), '"' || index_name || '"', '"' || p_newprefix || index_name || '"') DDLVC2, - index_name + index_name, + index_type FROM user_indexes ui WHERE table_name = p_oldprefix || p_tabname - AND index_type != 'LOB' + AND index_type NOT IN ('LOB', 'DOMAIN') 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); + l_temp_ei_sql := SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'PCTFREE') - 1); + l_temp_ei_sql := SUBSTR(l_temp_ei_sql, 1, INSTR(l_temp_ei_sql, ')', INSTR(l_temp_ei_sql, '"' || USER || '"."' || p_newprefix || '"')+1)+1); + EXECUTE IMMEDIATE l_temp_ei_sql; END LOOP; FOR rc IN (SELECT REPLACE(REPLACE(UPPER(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('TRIGGER', trigger_name), @@ -737,18 +806,13 @@ BEGIN trigger_name FROM user_triggers WHERE table_name = p_oldprefix || p_tabname) LOOP - EXECUTE IMMEDIATE SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'ALTER ') - 1); + l_temp_ei_sql := SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'ALTER ') - 1); + EXECUTE IMMEDIATE l_temp_ei_sql; 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)); @@ -762,9 +826,7 @@ BEGIN 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 diff --git a/maintenance/ora/user.sql b/maintenance/oracle/user.sql index d54acf39..57688eae 100644 --- a/maintenance/ora/user.sql +++ b/maintenance/oracle/user.sql @@ -1,8 +1,8 @@ -- 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}'; +define def_ts='{$_OracleDefTS}'; +define temp_ts='{$_OracleTempTS}'; 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.; diff --git a/maintenance/orphans.php b/maintenance/orphans.php index 67403e43..dbbddb9c 100644 --- a/maintenance/orphans.php +++ b/maintenance/orphans.php @@ -27,7 +27,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class Orphans extends Maintenance { public function __construct() { @@ -55,7 +55,7 @@ class Orphans extends Maintenance { */ private function lockTables( &$db, $extraTable = null ) { $tbls = array( 'page', 'revision', 'redirect' ); - if( $extraTable ) + if ( $extraTable ) $tbls[] = $extraTable; $db->lockTables( array(), $tbls, __METHOD__, false ); } @@ -68,23 +68,23 @@ class Orphans extends Maintenance { $dbw = wfGetDB( DB_MASTER ); $page = $dbw->tableName( 'page' ); $revision = $dbw->tableName( 'revision' ); - - if( $fix ) { + + 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 ) { + 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 ) { + foreach ( $result as $row ) { $comment = ( $row->rev_comment == '' ) ? '' : '(' . $wgContLang->truncate( $row->rev_comment, 40 ) . ')'; @@ -94,24 +94,24 @@ class Orphans extends Maintenance { $row->rev_timestamp, $wgContLang->truncate( $row->rev_user_text, 17 ), $comment ) ); - if( $fix ) { + if ( $fix ) { $dbw->delete( 'revision', array( 'rev_id' => $row->rev_id ) ); } } - if( !$fix ) { + if ( !$fix ) { $this->output( "Run again with --fix to remove these entries automatically.\n" ); } } else { $this->output( "No orphans! Yay!\n" ); } - - if( $fix ) { - $dbw->unlockTables(); + + if ( $fix ) { + $dbw->unlockTables( __METHOD__ ); } } /** - * @param $fix bool + * @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) @@ -121,7 +121,7 @@ class Orphans extends Maintenance { $page = $dbw->tableName( 'page' ); $revision = $dbw->tableName( 'revision' ); - if( $fix ) { + if ( $fix ) { $this->lockTables( $dbw ); } @@ -130,31 +130,30 @@ class Orphans extends Maintenance { 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; + if ( $widows > 0 ) { $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 ) { + foreach ( $result as $row ) { printf( "%10d %11d %2d %s\n", $row->page_id, $row->page_latest, $row->page_namespace, $row->page_title ); - if( $fix ) { + if ( $fix ) { $dbw->delete( 'page', array( 'page_id' => $row->page_id ) ); } } - if( !$fix ) { + if ( !$fix ) { $this->output( "Run again with --fix to remove these entries automatically.\n" ); } } else { $this->output( "No childless pages! Yay!\n" ); } - - if( $fix ) { - $dbw->unlockTables(); + + if ( $fix ) { + $dbw->unlockTables( __METHOD__ ); } } @@ -166,29 +165,27 @@ class Orphans extends Maintenance { $dbw = wfGetDB( DB_MASTER ); $page = $dbw->tableName( 'page' ); $revision = $dbw->tableName( 'revision' ); - $text = $dbw->tableName( 'text' ); - - if( $fix ) { - $dbw->lockTables( $dbw, 'text' ); + + if ( $fix ) { + $dbw->lockTables( $dbw, 'text', __METHOD__ ); } - + $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 ) { + 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 ) { + 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' ) ); } @@ -198,7 +195,7 @@ class Orphans extends Maintenance { $row->page_latest, $row->rev_timestamp, $row2->max_timestamp ) ); - if( $fix ) { + if ( $fix ) { # ... $maxId = $dbw->selectField( 'revision', @@ -217,21 +214,21 @@ class Orphans extends Maintenance { $this->output( "wtf\n" ); } } - - if( $found ) { + + 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 ) { + if ( !$fix && $found > 0 ) { $this->output( "Run again with --fix to remove these entries automatically.\n" ); } - - if( $fix ) { - $dbw->unlockTables(); + + if ( $fix ) { + $dbw->unlockTables( __METHOD__ ); } } } $maintClass = "Orphans"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/ourusers.php b/maintenance/ourusers.php index 3b5da447..499da5cf 100644 --- a/maintenance/ourusers.php +++ b/maintenance/ourusers.php @@ -32,12 +32,12 @@ $databases = array( print "/*!40100 set old_passwords=1 */;\n"; print "/*!40100 set global old_passwords=1 */;\n"; -foreach( $hosts as $host ) { +foreach ( $hosts as $host ) { print "--\n-- $host\n--\n"; print "\n-- wikiuser\n\n"; print "GRANT REPLICATION CLIENT,PROCESS ON *.* TO 'wikiuser'@'$host' IDENTIFIED BY '$wikiuser_pass';\n"; print "GRANT ALL PRIVILEGES ON `boardvote%`.* TO 'wikiuser'@'$host' IDENTIFIED BY '$wikiuser_pass';\n"; - foreach( $databases as $db ) { + foreach ( $databases as $db ) { print "GRANT SELECT, INSERT, UPDATE, DELETE ON `$db`.* TO 'wikiuser'@'$host' IDENTIFIED BY '$wikiuser_pass';\n"; } diff --git a/maintenance/parserTests.inc b/maintenance/parserTests.inc deleted file mode 100644 index 6526da90..00000000 --- a/maintenance/parserTests.inc +++ /dev/null @@ -1,1719 +0,0 @@ -<?php -# 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 -# 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 more independent of the configuration (and if possible the database) - * @todo document - * @file - * @ingroup Maintenance - */ - -/** */ -$options = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' ); -$optionsWithArgs = array( 'regex', 'seed', 'setversion' ); - -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" ); - -/** - * @ingroup Maintenance - */ -class ParserTest { - /** - * boolean $color whereas output should be colorized - */ - private $color; - - /** - * boolean $showOutput Show test output - */ - private $showOutput; - - /** - * boolean $useTemporaryTables Use temporary tables for the temporary database - */ - private $useTemporaryTables = true; - - /** - * boolean $databaseSetupDone True if the database has been set up - */ - private $databaseSetupDone = false; - - /** - * string $oldTablePrefix Original table prefix - */ - private $oldTablePrefix; - - private $maxFuzzTestLength = 300; - private $fuzzSeed = 0; - private $memoryLimit = 50; - - /** - * Sets terminal colorization and diff/quick modes depending on OS and - * command-line options (--color and --quick). - */ - public function ParserTest() { - global $options; - - # Only colorize output if stdout is a terminal. - $this->color = !wfIsWindows() && posix_isatty(1); - - if( isset( $options['color'] ) ) { - switch( $options['color'] ) { - case 'no': - $this->color = false; - break; - case 'yes': - default: - $this->color = true; - break; - } - } - $this->term = $this->color - ? new AnsiTermColorer() - : new DummyTermColorer(); - - $this->showDiffs = !isset( $options['quick'] ); - $this->showProgress = !isset( $options['quiet'] ); - $this->showFailure = !( - isset( $options['quiet'] ) - && ( isset( $options['record'] ) - || isset( $options['compare'] ) ) ); // redundant output - - $this->showOutput = isset( $options['show-output'] ); - - - if (isset($options['regex'])) { - if ( isset( $options['record'] ) ) { - echo "Warning: --record cannot be used with --regex, disabling --record\n"; - unset( $options['record'] ); - } - $this->regex = $options['regex']; - } else { - # Matches anything - $this->regex = ''; - } - - if( isset( $options['record'] ) ) { - $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 ); - } - $this->keepUploads = isset( $options['keep-uploads'] ); - - if ( isset( $options['seed'] ) ) { - $this->fuzzSeed = intval( $options['seed'] ) - 1; - } - - $this->runDisabled = isset( $options['run-disabled'] ); - - $this->hooks = array(); - $this->functionHooks = array(); - } - - /** - * Remove last character if it is a newline - */ - public function chomp($s) { - if (substr($s, -1) === "\n") { - return substr($s, 0, -1); - } - else { - return $s; - } - } - - /** - * Run a fuzz test series - * Draw input from a set of test files - */ - function fuzzTest( $filenames ) { - $dict = $this->getFuzzInput( $filenames ); - $dictSize = strlen( $dict ); - $logMaxLength = log( $this->maxFuzzTestLength ); - $this->setupDatabase(); - ini_set( 'memory_limit', $this->memoryLimit * 1048576 ); - - $numTotal = 0; - $numSuccess = 0; - $user = new User; - $opts = ParserOptions::newFromUser( $user ); - $title = Title::makeTitle( NS_MAIN, 'Parser_test' ); - - while ( true ) { - // Generate test input - mt_srand( ++$this->fuzzSeed ); - $totalLength = mt_rand( 1, $this->maxFuzzTestLength ); - $input = ''; - while ( strlen( $input ) < $totalLength ) { - $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength; - $hairLength = min( intval( exp( $logHairLength ) ), $dictSize ); - $offset = mt_rand( 0, $dictSize - $hairLength ); - $input .= substr( $dict, $offset, $hairLength ); - } - - $this->setupGlobals(); - $parser = $this->getParser(); - // Run the test - try { - $parser->parse( $input, $title, $opts ); - $fail = false; - } catch ( Exception $exception ) { - $fail = true; - } - - if ( $fail ) { - echo "Test failed with seed {$this->fuzzSeed}\n"; - echo "Input:\n"; - var_dump( $input ); - echo "\n\n"; - echo "$exception\n"; - } else { - $numSuccess++; - } - $numTotal++; - $this->teardownGlobals(); - $parser->__destruct(); - - if ( $numTotal % 100 == 0 ) { - $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 ); - echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n"; - if ( $usage > 90 ) { - echo "Out of memory:\n"; - $memStats = $this->getMemoryBreakdown(); - foreach ( $memStats as $name => $usage ) { - echo "$name: $usage\n"; - } - $this->abort(); - } - } - } - } - - /** - * Get an input dictionary from a set of parser test files - */ - function getFuzzInput( $filenames ) { - $dict = ''; - foreach( $filenames as $filename ) { - $contents = file_get_contents( $filename ); - preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches ); - foreach ( $matches[1] as $match ) { - $dict .= $match . "\n"; - } - } - return $dict; - } - - /** - * Get a memory usage breakdown - */ - function getMemoryBreakdown() { - $memStats = array(); - foreach ( $GLOBALS as $name => $value ) { - $memStats['$'.$name] = strlen( serialize( $value ) ); - } - $classes = get_declared_classes(); - foreach ( $classes as $class ) { - $rc = new ReflectionClass( $class ); - $props = $rc->getStaticProperties(); - $memStats[$class] = strlen( serialize( $props ) ); - $methods = $rc->getMethods(); - foreach ( $methods as $method ) { - $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) ); - } - } - $functions = get_defined_functions(); - foreach ( $functions['user'] as $function ) { - $rf = new ReflectionFunction( $function ); - $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) ); - } - asort( $memStats ); - return $memStats; - } - - function abort() { - $this->abort(); - } - - /** - * Run a series of tests listed in the given text files. - * Each test consists of a brief description, wikitext input, - * and the expected HTML output. - * - * Prints status updates on stdout and counts up the total - * number and percentage of passed tests. - * - * @param array of strings $filenames - * @return bool True if passed all tests, false if any tests failed. - */ - public function runTestsFromFiles( $filenames ) { - $this->recorder->start(); - $this->setupDatabase(); - $ok = true; - foreach( $filenames as $filename ) { - $tests = new TestFileIterator( $filename, $this ); - $ok = $this->runTests( $tests ) && $ok; - } - $this->teardownDatabase(); - $this->recorder->report(); - $this->recorder->end(); - return $ok; - } - - function runTests($tests) { - $ok = true; - 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"; - } - } - - /** - * Get a Parser object - */ - function getParser() { - global $wgParserConf; - $class = $wgParserConf['class']; - $parser = new $class( $wgParserConf ); - foreach( $this->hooks as $tag => $callback ) { - $parser->setHook( $tag, $callback ); - } - foreach( $this->functionHooks as $tag => $bits ) { - list( $callback, $flags ) = $bits; - $parser->setFunctionHook( $tag, $callback, $flags ); - } - wfRunHooks( 'ParserTestParser', array( &$parser ) ); - return $parser; - } - - /** - * Run a given wikitext input through a freshly-constructed wiki parser, - * and compare the output against the expected results. - * Prints status and explanatory messages to stdout. - * - * @param string $input Wikitext to try rendering - * @param string $result Result to output - * @return bool - */ - 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 ); - - $m = array(); - if (isset( $opts['title'] ) ) { - $titleText = $opts['title']; - } - else { - $titleText = 'Parser test'; - } - - $noxml = isset( $opts['noxml'] ); - $local = isset( $opts['local'] ); - $parser = $this->getParser(); - $title = Title::newFromText( $titleText ); - - $matches = array(); - if( isset( $opts['pst'] ) ) { - $out = $parser->preSaveTransform( $input, $title, $user, $options ); - } elseif( isset( $opts['msg'] ) ) { - $out = $parser->transformMsg( $input, $options ); - } elseif( isset( $opts['section'] ) ) { - $section = $opts['section']; - $out = $parser->getSection( $input, $section ); - } 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 ( isset( $opts['showtitle'] ) ) { - if($output->getTitleText()) $title = $output->getTitleText(); - $out = "$title\n$out"; - } - if (isset( $opts['ill'] ) ) { - $out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) ); - } elseif( isset( $opts['cat'] ) ) { - global $wgOut; - $wgOut->addCategoryLinks($output->getCategories()); - $cats = $wgOut->getCategoryLinks(); - if ( isset( $cats['normal'] ) ) { - $out = $this->tidy( implode( ' ', $cats['normal'] ) ); - } else { - $out = ''; - } - } - - $result = $this->tidy($result); - } - - - $this->teardownGlobals(); - - if( $result === $out && ( $noxml === true || $this->wellFormed( $out ) ) ) { - return $this->showSuccess( $desc ); - } else { - return $this->showFailure( $desc, $result, $out ); - } - } - - - /** - * Use a regex to find out the value of an option - * @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( $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', $opts, 'en' ); - $variant = - self::getOptionValue( 'variant', $opts, false ); - $maxtoclevel = - self::getOptionValue( 'wgMaxTocLevel', $opts, 999 ); - $linkHolderBatchSize = - self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 ); - - $settings = array( - 'wgServer' => 'http://localhost', - 'wgScript' => '/index.php', - 'wgScriptPath' => '/', - 'wgArticlePath' => '/wiki/$1', - 'wgActionPaths' => array(), - 'wgLocalFileRepo' => array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'directory' => $this->uploadDir, - 'url' => 'http://example.com/images', - 'hashLevels' => 2, - 'transformVia404' => false, - ), - 'wgEnableUploads' => true, - 'wgStyleSheetPath' => '/skins', - 'wgSitename' => 'MediaWiki', - 'wgServerName' => 'Britney-Spears', - 'wgLanguageCode' => $lang, - 'wgContLanguageCode' => $lang, - 'wgDBprefix' => $wgDBtype != 'oracle' ? 'parsertest_' : 'pt_', - 'wgRawHtml' => isset( $opts['rawhtml'] ), - 'wgLang' => null, - 'wgContLang' => null, - 'wgNamespacesWithSubpages' => array( 0 => isset( $opts['subpage'] ) ), - 'wgMaxTocLevel' => $maxtoclevel, - 'wgCapitalLinks' => true, - 'wgNoFollowLinks' => true, - 'wgNoFollowDomainExceptions' => array(), - 'wgThumbnailScriptPath' => false, - 'wgUseImageResize' => false, - 'wgUseTeX' => isset( $opts['math'] ), - 'wgMathDirectory' => $this->uploadDir . '/math', - 'wgLocaltimezone' => 'UTC', - 'wgAllowExternalImages' => true, - 'wgUseTidy' => false, - 'wgDefaultLanguageVariant' => $variant, - 'wgVariantArticlePath' => false, - 'wgGroupPermissions' => array( '*' => array( - 'createaccount' => true, - 'read' => true, - 'edit' => true, - 'createpage' => true, - 'createtalk' => true, - ) ), - 'wgNamespaceProtection' => array( NS_MEDIAWIKI => 'editinterface' ), - 'wgDefaultExternalStore' => array(), - 'wgForeignFileRepos' => array(), - 'wgLinkHolderBatchSize' => $linkHolderBatchSize, - '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 ) { - 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; - - MagicWord::clearCache(); - - global $wgUser; - $wgUser = new User(); - } - - /** - * List of temporary tables to create, without prefix. - * Some of these probably aren't necessary. - */ - private function listTables() { - global $wgDBtype; - $tables = array('user', 'page', 'page_restrictions', - 'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks', - 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', - 'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage', - 'recentchanges', 'watchlist', 'math', 'interwiki', - 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo', - 'archive', 'user_groups', 'page_props', 'category' - ); - - 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. - wfRunHooks( 'ParserTestTables', array( &$tables ) ); - - return $tables; - } - - /** - * Set up a temporary set of wiki tables to work with for the tests. - * Currently this will only be done once per run, and any changes to - * the db will be visible to later tests in the run. - */ - function setupDatabase() { - global $wgDBprefix, $wgDBtype; - if ( $this->databaseSetupDone ) { - return; - } - if ( $wgDBprefix === 'parsertest_' || ($wgDBtype == 'oracle' && $wgDBprefix === 'pt_')) { - throw new MWException( 'setupDatabase should be called before setupGlobals' ); - } - $this->databaseSetupDone = true; - $this->oldTablePrefix = $wgDBprefix; - - # CREATE TEMPORARY TABLE breaks if there is more than one server - # FIXME: r40209 makes temporary tables break even with just one server - # FIXME: (bug 15892); disabling the feature entirely as a temporary fix - if ( true || wfGetLB()->getServerCount() != 1 ) { - $this->useTemporaryTables = false; - } - - $temporary = $this->useTemporaryTables || $wgDBtype == 'postgres'; - - $db = wfGetDB( DB_MASTER ); - $tables = $this->listTables(); - - 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( $wgDBtype != 'oracle' ? 'parsertest_' : 'pt_' ); - - # Hack: insert a few Wikipedia in-project interwiki prefixes, - # for testing inter-language links - $db->insert( 'interwiki', array( - array( 'iw_prefix' => 'wikipedia', - 'iw_url' => 'http://en.wikipedia.org/wiki/$1', - 'iw_local' => 0 ), - array( 'iw_prefix' => 'meatball', - 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1', - 'iw_local' => 0 ), - array( 'iw_prefix' => 'zh', - 'iw_url' => 'http://zh.wikipedia.org/wiki/$1', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'es', - 'iw_url' => 'http://es.wikipedia.org/wiki/$1', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'fr', - 'iw_url' => 'http://fr.wikipedia.org/wiki/$1', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'ru', - 'iw_url' => 'http://ru.wikipedia.org/wiki/$1', - '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', - 'img_size' => 12345, - 'img_description' => 'Some lame file', - 'img_user' => 1, - 'img_user_text' => 'WikiSysop', - 'img_timestamp' => $db->timestamp( '20010115123500' ), - 'img_width' => 1941, - 'img_height' => 220, - 'img_bits' => 24, - 'img_media_type' => MEDIATYPE_BITMAP, - 'img_major_mime' => "image", - 'img_minor_mime' => "jpeg", - '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' => 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, '' ); - } - - /** - * Change the table prefix on all open DB connections/ - */ - protected function changePrefix( $prefix ) { - global $wgDBprefix; - wfGetLBFactory()->forEachLB( array( $this, 'changeLBPrefix' ), array( $prefix ) ); - $wgDBprefix = $prefix; - } - - public function changeLBPrefix( $lb, $prefix ) { - $lb->forEachOpenConnection( array( $this, 'changeDBPrefix' ), array( $prefix ) ); - } - - public function changeDBPrefix( $db, $prefix ) { - $db->tablePrefix( $prefix ); - } - - private function teardownDatabase() { - global $wgDBtype; - if ( !$this->databaseSetupDone ) { - return; - } - $this->changePrefix( $this->oldTablePrefix ); - $this->databaseSetupDone = false; - if ( $this->useTemporaryTables ) { - # Don't need to do anything - return; - } - - /* - $tables = $this->listTables(); - $db = wfGetDB( DB_MASTER ); - foreach ( $tables as $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. - * @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; - } - - /** - * Restore default values and perform any necessary clean-up - * after each test runs. - */ - private function teardownGlobals() { - RepoGroup::destroySingleton(); - LinkCache::singleton()->clear(); - foreach( $this->savedGlobals as $var => $val ) { - $GLOBALS[$var] = $val; - } - if( isset( $this->uploadDir ) ) { - $this->teardownUploadDir( $this->uploadDir ); - unset( $this->uploadDir ); - } - } - - /** - * 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", - - "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png", - ) - ); - - 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/math/f/a/5", - "$dir/math/f/a", - "$dir/math/f", - "$dir/math", - "$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 ); - } - } - } - - /** - * "Running test $desc..." - */ - protected function showTesting( $desc ) { - print "Running test $desc... "; - } - - /** - * Print a happy success message. - * - * @param string $desc The test name - * @return bool - */ - protected function showSuccess( $desc ) { - if( $this->showProgress ) { - print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n"; - } - return true; - } - - /** - * Print a failure message and provide some explanatory output - * about what went wrong if so configured. - * - * @param string $desc The test name - * @param string $result Expected HTML output - * @param string $html Actual HTML output - * @return bool - */ - protected function showFailure( $desc, $result, $html ) { - if( $this->showFailure ) { - if( !$this->showProgress ) { - # In quiet mode we didn't show the 'Testing' message before the - # test, in case it succeeded. Show it now: - $this->showTesting( $desc ); - } - print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n"; - if ( $this->showOutput ) { - print "--- Expected ---\n$result\n--- Actual ---\n$html\n"; - } - if( $this->showDiffs ) { - print $this->quickDiff( $result, $html ); - if( !$this->wellFormed( $html ) ) { - print "XML error: $this->mXmlError\n"; - } - } - } - return false; - } - - /** - * Run given strings through a diff and return the (colorized) output. - * Requires writable /tmp directory and a 'diff' command in the PATH. - * - * @param string $input - * @param string $output - * @param string $inFileTail Tailing for the input file name - * @param string $outFileTail Tailing for the output file name - * @return string - */ - protected function quickDiff( $input, $output, $inFileTail='expected', $outFileTail='actual' ) { - $prefix = wfTempDir() . "/mwParser-" . mt_rand(); - - $infile = "$prefix-$inFileTail"; - $this->dumpToFile( $input, $infile ); - - $outfile = "$prefix-$outFileTail"; - $this->dumpToFile( $output, $outfile ); - - $diff = `diff -au $infile $outfile`; - unlink( $infile ); - unlink( $outfile ); - - return $this->colorDiff( $diff ); - } - - /** - * Write the given string to a file, adding a final newline. - * - * @param string $data - * @param string $filename - */ - private function dumpToFile( $data, $filename ) { - $file = fopen( $filename, "wt" ); - fwrite( $file, $data . "\n" ); - fclose( $file ); - } - - /** - * Colorize unified diff output if set for ANSI color output. - * Subtractions are colored blue, additions red. - * - * @param string $text - * @return string - */ - protected function colorDiff( $text ) { - return preg_replace( - array( '/^(-.*)$/m', '/^(\+.*)$/m' ), - array( $this->term->color( 34 ) . '$1' . $this->term->reset(), - $this->term->color( 31 ) . '$1' . $this->term->reset() ), - $text ); - } - - /** - * Show "Reading tests from ..." - * - * @param String $path - */ - public function showRunFile( $path ){ - print $this->term->color( 1 ) . - "Reading tests from \"$path\"..." . - $this->term->reset() . - "\n"; - } - - /** - * Insert a temporary test article - * @param string $name the title, including any prefix - * @param string $text the article text - * @param int $line the input line number, for reporting errors - */ - public function addArticle($name, $text, $line) { - $this->setupGlobals(); - $title = Title::newFromText( $name ); - if ( is_null($title) ) { - wfDie( "invalid title at line $line\n" ); - } - - $aid = $title->getArticleID( GAID_FOR_UPDATE ); - if ($aid != 0) { - wfDie( "duplicate article '$name' at line $line\n" ); - } - - $art = new Article($title); - $art->insertNewArticle($text, '', false, false ); - - $this->teardownGlobals(); - } - - /** - * Steal a callback function from the primary parser, save it for - * application to our scary parser. If the hook is not installed, - * die a painful dead to warn the others. - * @param string $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 { - wfDie( "This test suite requires the '$name' hook extension.\n" ); - } - } - - /** - * Steal a callback function from the primary parser, save it for - * application to our scary parser. If the hook is not installed, - * die a painful dead to warn the others. - * @param string $name - */ - 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 { - wfDie( "This test suite requires the '$name' function hook extension.\n" ); - } - } - - /* - * Run the "tidy" command on text if the $wgUseTidy - * global is true - * - * @param string $text the text to tidy - * @return string - * @static - */ - private function tidy( $text ) { - global $wgUseTidy; - if ($wgUseTidy) { - $text = Parser::tidy($text); - } - return $text; - } - - private function wellFormed( $text ) { - $html = - Sanitizer::hackDocType() . - '<html>' . - $text . - '</html>'; - - $parser = xml_parser_create( "UTF-8" ); - - # case folding violates XML standard, turn it off - xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); - - if( !xml_parse( $parser, $html, true ) ) { - $err = xml_error_string( xml_get_error_code( $parser ) ); - $position = xml_get_current_byte_index( $parser ); - $fragment = $this->extractFragment( $html, $position ); - $this->mXmlError = "$err at byte $position:\n$fragment"; - xml_parser_free( $parser ); - return false; - } - xml_parser_free( $parser ); - return true; - } - - private function extractFragment( $text, $position ) { - $start = max( 0, $position - 10 ); - $before = $position - $start; - $fragment = '...' . - $this->term->color( 34 ) . - substr( $text, $start, $before ) . - $this->term->color( 0 ) . - $this->term->color( 31 ) . - $this->term->color( 1 ) . - substr( $text, $position, 1 ) . - $this->term->color( 0 ) . - $this->term->color( 34 ) . - substr( $text, $position + 1, 9 ) . - $this->term->color( 0 ) . - '...'; - $display = str_replace( "\n", ' ', $fragment ); - $caret = ' ' . - str_repeat( ' ', $before ) . - $this->term->color( 31 ) . - '^' . - $this->term->color( 0 ); - return "$display\n$caret"; - } -} - -class AnsiTermColorer { - function __construct() { - } - - /** - * Return ANSI terminal escape code for changing text attribs/color - * - * @param string $color Semicolon-separated list of attribute/color codes - * @return string - */ - public function color( $color ) { - global $wgCommandLineDarkBg; - $light = $wgCommandLineDarkBg ? "1;" : "0;"; - return "\x1b[{$light}{$color}m"; - } - - /** - * Return ANSI terminal escape code for restoring default text attributes - * - * @return string - */ - public function reset() { - return $this->color( 0 ); - } -} - -/* A colour-less terminal */ -class DummyTermColorer { - public function color( $color ) { - return ''; - } - - public function reset() { - return ''; - } -} - -class TestRecorder { - var $parent; - var $term; - - function __construct( $parent ) { - $this->parent = $parent; - $this->term = $parent->term; - } - - function start() { - $this->total = 0; - $this->success = 0; - } - - function record( $test, $result ) { - $this->total++; - $this->success += ($result ? 1 : 0); - } - - function end() { - // dummy - } - - function report() { - if( $this->total > 0 ) { - $this->reportPercentage( $this->success, $this->total ); - } else { - wfDie( "No tests found.\n" ); - } - } - - function reportPercentage( $success, $total ) { - $ratio = wfPercent( 100 * $success / $total ); - print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)... "; - if( $success == $total ) { - print $this->term->color( 32 ) . "ALL TESTS PASSED!"; - } else { - $failed = $total - $success ; - print $this->term->color( 31 ) . "$failed tests failed!"; - } - print $this->term->reset() . "\n"; - return ($success == $total); - } -} - -class DbTestPreviewer extends TestRecorder { - protected $lb; ///< Database load balancer - protected $db; ///< Database connection to the main DB - protected $curRun; ///< run ID number for the current run - protected $prevRun; ///< run ID number for the previous run, if any - protected $results; ///< Result array - - /** - * This should be called before the table prefix is changed - */ - function __construct( $parent ) { - parent::__construct( $parent ); - $this->lb = wfGetLBFactory()->newMainLB(); - // This connection will have the wiki's table prefix, not parsertest_ - $this->db = $this->lb->getConnection( DB_MASTER ); - } - - /** - * Set up result recording; insert a record for the run with the date - * and all that fun stuff - */ - function start() { - global $wgDBtype; - parent::start(); - - if( ! $this->db->tableExists( 'testrun' ) - or ! $this->db->tableExists( 'testitem' ) ) - { - print "WARNING> `testrun` table not found in database.\n"; - $this->prevRun = false; - } else { - // We'll make comparisons against the previous run later... - $this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' ); - } - $this->results = array(); - } - - function record( $test, $result ) { - parent::record( $test, $result ); - $this->results[$test] = $result; - } - - function report() { - if( $this->prevRun ) { - // f = fail, p = pass, n = nonexistent - // codes show before then after - $table = array( - 'fp' => 'previously failing test(s) now PASSING! :)', - 'pn' => 'previously PASSING test(s) removed o_O', - 'np' => 'new PASSING test(s) :)', - - 'pf' => 'previously passing test(s) now FAILING! :(', - 'fn' => 'previously FAILING test(s) removed O_o', - 'nf' => 'new FAILING test(s) :(', - 'ff' => 'still FAILING test(s) :(', - ); - - $prevResults = array(); - - $res = $this->db->select( 'testitem', array( 'ti_name', 'ti_success' ), - array( 'ti_run' => $this->prevRun ), __METHOD__ ); - foreach ( $res as $row ) { - if ( !$this->parent->regex - || preg_match( "/{$this->parent->regex}/i", $row->ti_name ) ) - { - $prevResults[$row->ti_name] = $row->ti_success; - } - } - - $combined = array_keys( $this->results + $prevResults ); - - # Determine breakdown by change type - $breakdown = array(); - foreach ( $combined as $test ) { - if ( !isset( $prevResults[$test] ) ) { - $before = 'n'; - } elseif ( $prevResults[$test] == 1 ) { - $before = 'p'; - } else /* if ( $prevResults[$test] == 0 )*/ { - $before = 'f'; - } - if ( !isset( $this->results[$test] ) ) { - $after = 'n'; - } elseif ( $this->results[$test] == 1 ) { - $after = 'p'; - } else /*if ( $this->results[$test] == 0 ) */ { - $after = 'f'; - } - $code = $before . $after; - if ( isset( $table[$code] ) ) { - $breakdown[$code][$test] = $this->getTestStatusInfo( $test, $after ); - } - } - - # Write out results - foreach ( $table as $code => $label ) { - if( !empty( $breakdown[$code] ) ) { - $count = count($breakdown[$code]); - printf( "\n%4d %s\n", $count, $label ); - foreach ($breakdown[$code] as $differing_test_name => $statusInfo) { - print " * $differing_test_name [$statusInfo]\n"; - } - } - } - } else { - print "No previous test runs to compare against.\n"; - } - print "\n"; - parent::report(); - } - - /** - ** Returns a string giving information about when a test last had a status change. - ** Could help to track down when regressions were introduced, as distinct from tests - ** which have never passed (which are more change requests than regressions). - */ - private function getTestStatusInfo($testname, $after) { - - // If we're looking at a test that has just been removed, then say when it first appeared. - if ( $after == 'n' ) { - $changedRun = $this->db->selectField ( 'testitem', - 'MIN(ti_run)', - array( 'ti_name' => $testname ), - __METHOD__ ); - $appear = $this->db->selectRow ( 'testrun', - array( 'tr_date', 'tr_mw_version' ), - array( 'tr_id' => $changedRun ), - __METHOD__ ); - return "First recorded appearance: " - . date( "d-M-Y H:i:s", strtotime ( $appear->tr_date ) ) - . ", " . $appear->tr_mw_version; - } - - // Otherwise, this test has previous recorded results. - // See when this test last had a different result to what we're seeing now. - $conds = array( - 'ti_name' => $testname, - 'ti_success' => ($after == 'f' ? "1" : "0") ); - if ( $this->curRun ) { - $conds[] = "ti_run != " . $this->db->addQuotes ( $this->curRun ); - } - - $changedRun = $this->db->selectField ( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ ); - - // If no record of ever having had a different result. - if ( is_null ( $changedRun ) ) { - if ($after == "f") { - return "Has never passed"; - } else { - return "Has never failed"; - } - } - - // Otherwise, we're looking at a test whose status has changed. - // (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.) - // In this situation, give as much info as we can as to when it changed status. - $pre = $this->db->selectRow ( 'testrun', - array( 'tr_date', 'tr_mw_version' ), - array( 'tr_id' => $changedRun ), - __METHOD__ ); - $post = $this->db->selectRow ( 'testrun', - array( 'tr_date', 'tr_mw_version' ), - array( "tr_id > " . $this->db->addQuotes ( $changedRun) ), - __METHOD__, - array( "LIMIT" => 1, "ORDER BY" => 'tr_id' ) - ); - - if ( $post ) { - $postDate = date( "d-M-Y H:i:s", strtotime ( $post->tr_date ) ) . ", {$post->tr_mw_version}"; - } else { - $postDate = 'now'; - } - return ( $after == "f" ? "Introduced" : "Fixed" ) . " between " - . date( "d-M-Y H:i:s", strtotime ( $pre->tr_date ) ) . ", " . $pre->tr_mw_version - . " and $postDate"; - - } - - /** - * Commit transaction and clean up for result recording - */ - function end() { - $this->lb->commitMasterChanges(); - $this->lb->closeAll(); - parent::end(); - } - -} - -class DbTestRecorder extends DbTestPreviewer { - /** - * Set up result recording; insert a record for the run with the date - * and all that fun stuff - */ - function start() { - global $wgDBtype, $options; - $this->db->begin(); - - 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' => isset( $options['setversion'] ) ? - $options['setversion'] : SpecialVersion::getVersion(), - 'tr_php_version' => phpversion(), - 'tr_db_version' => $this->db->getServerVersion(), - 'tr_uname' => php_uname() - ), - __METHOD__ ); - if ($wgDBtype === 'postgres') - $this->curRun = $this->db->currentSequenceValue('testrun_id_seq'); - else - $this->curRun = $this->db->insertId(); - } - - /** - * Record an individual test item's success or failure to the db - * @param string $test - * @param bool $result - */ - function record( $test, $result ) { - parent::record( $test, $result ); - $this->db->insert( 'testitem', - array( - 'ti_run' => $this->curRun, - 'ti_name' => $test, - 'ti_success' => $result ? 1 : 0, - ), - __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/parserTestsParserHook.php b/maintenance/parserTestsParserHook.php deleted file mode 100644 index f55cd0e4..00000000 --- a/maintenance/parserTestsParserHook.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -if ( ! defined( 'MEDIAWIKI' ) ) - die( -1 ); -/** - * A basic extension that's used by the parser tests to test whether input and - * arguments are passed to extensions properly. - * - * @file - * @ingroup Maintenance - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -$wgHooks['ParserTestParser'][] = 'wfParserTestParserHookSetup'; - -function wfParserTestParserHookSetup( &$parser ) { - $parser->setHook( 'tag', 'wfParserTestParserHookHook' ); - - return true; -} - -function wfParserTestParserHookHook( $in, $argv ) { - ob_start(); - var_dump( - $in, - $argv - ); - $ret = ob_get_clean(); - - return "<pre>\n$ret</pre>"; -} - diff --git a/maintenance/parserTestsParserTime.php b/maintenance/parserTestsParserTime.php deleted file mode 100644 index c5903f25..00000000 --- a/maintenance/parserTestsParserTime.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php -if ( ! defined( 'MEDIAWIKI' ) ) - die( -1 ); -/** - * A basic extension that's used by the parser tests to test date magic words - * - * Handy so that we don't have to upgrade the parsertests every second to - * compensate with the passage of time and certainly less expensive than a - * time-freezing device, get yours now! - * - * @file - * @ingroup Maintenance - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -$wgHooks['ParserGetVariableValueTs'][] = 'wfParserTimeSetup'; - -function wfParserTimeSetup( &$parser, &$ts ) { - $ts = 123; //$ perl -le 'print scalar localtime 123' ==> Thu Jan 1 00:02:03 1970 - - return true; -} - diff --git a/maintenance/parserTestsStaticParserHook.php b/maintenance/parserTestsStaticParserHook.php deleted file mode 100644 index 98c4bba1..00000000 --- a/maintenance/parserTestsStaticParserHook.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php -if ( ! defined( 'MEDIAWIKI' ) ) - die( -1 ); -/** - * A basic extension that's used by the parser tests to test whether the parser - * calls extensions when they're called inside comments, it shouldn't do that - * - * @file - * @ingroup Maintenance - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -$wgHooks['ParserTestParser'][] = 'wfParserTestStaticParserHookSetup'; - -function wfParserTestStaticParserHookSetup( &$parser ) { - $parser->setHook( 'statictag', 'wfParserTestStaticParserHookHook' ); - - return true; -} - -function wfParserTestStaticParserHookHook( $in, $argv, $parser ) { - if ( ! count( $argv ) ) { - $parser->static_tag_buf = $in; - return ''; - } else if ( count( $argv ) === 1 && isset( $argv['action'] ) - && $argv['action'] === 'flush' && $in === null ) - { - // Clear the buffer, we probably don't need to - if ( isset( $parser->static_tag_buf ) ) { - $tmp = $parser->static_tag_buf; - } else { - $tmp = ''; - } - $parser->static_tag_buf = null; - return $tmp; - } else - // wtf? - return - "\nCall this extension as <statictag>string</statictag> or as" . - " <statictag action=flush/>, not in any other way.\n" . - "text: " . var_export( $in, true ) . "\n" . - "argv: " . var_export( $argv, true ) . "\n"; -} - diff --git a/maintenance/patchSql.php b/maintenance/patchSql.php index 69cb0f56..1f96d62c 100644 --- a/maintenance/patchSql.php +++ b/maintenance/patchSql.php @@ -21,7 +21,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class PatchSql extends Maintenance { public function __construct() { @@ -36,14 +36,14 @@ class PatchSql extends Maintenance { public function execute() { $dbw = wfGetDB( DB_MASTER ); - foreach( $this->mArgs as $arg ) { + foreach ( $this->mArgs as $arg ) { $files = array( $arg, - DatabaseBase::patchPath( $arg ), - DatabaseBase::patchPath( "patch-$arg.sql" ), + $dbw->patchPath( $arg ), + $dbw->patchPath( "patch-$arg.sql" ), ); - foreach( $files as $file ) { - if( file_exists( $file ) ) { + foreach ( $files as $file ) { + if ( file_exists( $file ) ) { $this->output( "$file ...\n" ); $dbw->sourceFile( $file ); continue 2; @@ -56,4 +56,4 @@ class PatchSql extends Maintenance { } $maintClass = "PatchSql"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/populateCategory.inc b/maintenance/populateCategory.inc deleted file mode 100644 index deca4530..00000000 --- a/maintenance/populateCategory.inc +++ /dev/null @@ -1,85 +0,0 @@ -<?php -/** - * @file - * @ingroup Maintenance - * @author Simetrical - */ - -define( 'REPORTING_INTERVAL', 1000 ); - -function populateCategory( $begin, $maxlag, $throttle, $force ) { - $dbw = wfGetDB( DB_MASTER ); - - if( !$force ) { - $row = $dbw->selectRow( - 'updatelog', - '1', - array( 'ul_key' => 'populate category' ), - __FUNCTION__ - ); - if( $row ) { - wfOut( "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 ) ) { - wfOut( "The category named $name is not valid?!\n" ); - } else { - $cat->refreshCounts(); - } - - ++$i; - if( !($i % REPORTING_INTERVAL) ) { - wfOut( "$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; - } -} diff --git a/maintenance/populateCategory.php b/maintenance/populateCategory.php index bf84bb0a..4f494e15 100644 --- a/maintenance/populateCategory.php +++ b/maintenance/populateCategory.php @@ -1,13 +1,13 @@ <?php /** - * @file + * @file * @ingroup Maintenance * @author Simetrical */ $optionsWithArgs = array( 'begin', 'max-slave-lag', 'throttle' ); -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class PopulateCategory extends Maintenance { @@ -31,12 +31,13 @@ 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. TEXT; +# ' $this->addOption( 'begin', 'Only do categories whose names are alphabetically after the provided name', false, true ); $this->addOption( 'max-slave-lag', 'If slave lag exceeds this many seconds, wait until it drops before continuing. Default: 10', false, true ); $this->addOption( 'throttle', 'Wait this many milliseconds after each category. Default: 0', false, true ); $this->addOption( '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 ); @@ -47,81 +48,80 @@ TEXT; private function doPopulateCategory( $begin, $maxlag, $throttle, $force ) { $dbw = wfGetDB( DB_MASTER ); - - if( !$force ) { + + if ( !$force ) { $row = $dbw->selectRow( 'updatelog', '1', array( 'ul_key' => 'populate category' ), - __FUNCTION__ + __METHOD__ ); - if( $row ) { - $this->output( "Category table already populated. Use php ". - "maintenance/populateCategory.php\n--force from the command line ". + 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 ); + if ( $begin !== '' ) { + $where = 'cl_to > ' . $dbw->addQuotes( $begin ); } else { $where = null; } $i = 0; - - while( true ) { + + while ( true ) { # Find which category to update $row = $dbw->selectRow( 'categorylinks', 'cl_to', $where, - __FUNCTION__, + __METHOD__, array( 'ORDER BY' => 'cl_to' ) ); - if( !$row ) { + if ( !$row ) { # Done, hopefully. break; } $name = $row->cl_to; - $where = 'cl_to > '.$dbw->addQuotes( $name ); - + $where = 'cl_to > ' . $dbw->addQuotes( $name ); + # Use the row to update the category count $cat = Category::newFromName( $name ); - if( !is_object( $cat ) ) { + if ( !is_object( $cat ) ) { $this->output( "The category named $name is not valid?!\n" ); } else { $cat->refreshCounts(); } - + ++$i; - if( !($i % self::REPORTING_INTERVAL) ) { + if ( !( $i % self::REPORTING_INTERVAL ) ) { $this->output( "$name\n" ); wfWaitForSlaves( $maxlag ); } - usleep( $throttle*1000 ); + usleep( $throttle * 1000 ); } - - if( $dbw->insert( + + if ( $dbw->insert( 'updatelog', array( 'ul_key' => 'populate category' ), - __FUNCTION__, + __METHOD__, 'IGNORE' ) ) { - wfOut( "Category population complete.\n" ); + $this->output( "Category population complete.\n" ); return true; } else { - wfOut( "Could not insert category population row.\n" ); + $this->output( "Could not insert category population row.\n" ); return false; } } } $maintClass = "PopulateCategory"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/populateLogSearch.inc b/maintenance/populateLogSearch.inc deleted file mode 100644 index b5e34fb7..00000000 --- a/maintenance/populateLogSearch.inc +++ /dev/null @@ -1,80 +0,0 @@ -<?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 index b045104e..ce2d95cc 100644 --- a/maintenance/populateLogSearch.php +++ b/maintenance/populateLogSearch.php @@ -21,14 +21,14 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +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'); - + 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"; @@ -40,75 +40,74 @@ class PopulateLogSearch extends Maintenance { $this->error( "log_search does not exist", true ); } $start = $db->selectField( 'logging', 'MIN(log_id)', false, __FUNCTION__ ); - if( !$start ) { + 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 ) { + + $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 ) { + foreach ( $res as $row ) { // RevisionDelete logs - revisions - if( LogEventsList::typeAction( $row, $delTypes, 'revision' ) ) { + 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]); + 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 ) { + 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 ) ); + $db->update( 'logging', + array( 'log_params' => implode( ',', $params ) ), + array( 'log_id' => $row->log_id ) ); } } - $items = explode(',',$params[1]); + $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]) ) + $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'; + $userField = $prefix . '_user'; + $userTextField = $prefix . '_user_text'; // Add item author relations... $userIds = $userIPs = array(); $sres = $db->select( $table, - array($userField,$userTextField), - array($field => $items) + array( $userField, $userTextField ), + array( $field => $items ) ); - foreach( $sres as $srow ) { - if( $srow->$userField > 0 ) - $userIds[] = intval($srow->$userField); - else if( $srow->$userTextField != '' ) + 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' ) ) { + } 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 + if ( count( $params ) < 1 ) continue; // bad row $items = explode( ',', $params[0] ); $log = new LogPage( $row->log_type ); // Add item relations... @@ -116,13 +115,13 @@ class PopulateLogSearch extends Maintenance { // Add item author relations... $userIds = $userIPs = array(); $sres = $db->select( 'logging', - array('log_user','log_user_text'), - array('log_id' => $items) + 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) ) + 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 ); @@ -133,7 +132,7 @@ class PopulateLogSearch extends Maintenance { $blockEnd += self::LOG_SEARCH_BATCH_SIZE; wfWaitForSlaves( 5 ); } - if( $db->insert( + if ( $db->insert( 'updatelog', array( 'ul_key' => 'populate log_search' ), __FUNCTION__, @@ -150,4 +149,4 @@ class PopulateLogSearch extends Maintenance { } $maintClass = "PopulateLogSearch"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/populateLogUsertext.php b/maintenance/populateLogUsertext.php index a491b2b0..bb3927ce 100644 --- a/maintenance/populateLogUsertext.php +++ b/maintenance/populateLogUsertext.php @@ -23,7 +23,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class PopulateLogUsertext extends Maintenance { public function __construct() { @@ -35,7 +35,7 @@ class PopulateLogUsertext extends Maintenance { public function execute() { $db = wfGetDB( DB_MASTER ); $start = $db->selectField( 'logging', 'MIN(log_id)', false, __METHOD__ ); - if( !$start ) { + if ( !$start ) { $this->output( "Nothing to do.\n" ); return true; } @@ -45,23 +45,23 @@ class PopulateLogUsertext extends Maintenance { $end += $this->mBatchSize - 1; $blockStart = $start; $blockEnd = $start + $this->mBatchSize - 1; - while( $blockEnd <= $end ) { + 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(); + $res = $db->select( array( 'logging', 'user' ), + array( 'log_id', 'user_name' ), $cond, __METHOD__ ); + $db->begin(); - foreach( $res as $row ) { - $db->update( 'logging', array('log_user_text' => $row->user_name), - array('log_id' => $row->log_id), __METHOD__ ); + 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( + if ( $db->insert( 'updatelog', array( 'ul_key' => 'populate log_usertext' ), __METHOD__, @@ -78,5 +78,5 @@ class PopulateLogUsertext extends Maintenance { } $maintClass = "PopulateLogUsertext"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/populateParentId.inc b/maintenance/populateParentId.inc deleted file mode 100644 index 7b1ae3e8..00000000 --- a/maintenance/populateParentId.inc +++ /dev/null @@ -1,83 +0,0 @@ -<?php - -define( 'BATCH_SIZE', 200 ); - -function populate_rev_parent_id( $db ) { - wfOut( "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 ) ){ - wfOut( "...revision table seems to be empty.\n" ); - $db->insert( 'updatelog', - array( 'ul_key' => 'populate rev_parent_id' ), - __FUNCTION__, - 'IGNORE' ); - return; - } - # Do remaining chunk - $end += BATCH_SIZE - 1; - $blockStart = intval( $start ); - $blockEnd = intval( $start ) + BATCH_SIZE - 1; - $count = 0; - $changed = 0; - while( $blockEnd <= $end ) { - wfOut( "...doing rev_id from $blockStart to $blockEnd\n" ); - $cond = "rev_id BETWEEN $blockStart AND $blockEnd"; - $res = $db->select( 'revision', - array('rev_id','rev_page','rev_timestamp','rev_parent_id'), - $cond, __FUNCTION__ ); - # Go through and update rev_parent_id from these rows. - # Assume that the previous revision of the title was - # the original previous revision of the title when the - # edit was made... - foreach( $res as $row ) { - # First, check rows with the same timestamp other than this one - # with a smaller rev ID. The highest ID "wins". This avoids loops - # as timestamp can only decrease and never loops with IDs (from parent to parent) - $previousID = $db->selectField( 'revision', 'rev_id', - array( 'rev_page' => $row->rev_page, 'rev_timestamp' => $row->rev_timestamp, - "rev_id < " . intval( $row->rev_id ) ), - __FUNCTION__, - array( 'ORDER BY' => 'rev_id DESC' ) ); - # If there are none, check the the highest ID with a lower timestamp - if( !$previousID ) { - # Get the highest older timestamp - $lastTimestamp = $db->selectField( 'revision', 'rev_timestamp', - array( 'rev_page' => $row->rev_page, "rev_timestamp < " . $db->addQuotes( $row->rev_timestamp ) ), - __FUNCTION__, - array( 'ORDER BY' => 'rev_timestamp DESC' ) ); - # If there is one, let the highest rev ID win - if( $lastTimestamp ) { - $previousID = $db->selectField( 'revision', 'rev_id', - array( 'rev_page' => $row->rev_page, 'rev_timestamp' => $lastTimestamp ), - __FUNCTION__, - array( 'ORDER BY' => 'rev_id DESC' ) ); - } - } - $previousID = intval($previousID); - if( $previousID != $row->rev_parent_id ) - $changed++; - # Update the row... - $db->update( 'revision', - array( 'rev_parent_id' => $previousID ), - array( 'rev_id' => $row->rev_id ), - __FUNCTION__ ); - $count++; - } - $blockStart += BATCH_SIZE - 1; - $blockEnd += BATCH_SIZE - 1; - wfWaitForSlaves( 5 ); - } - $logged = $db->insert( 'updatelog', - array( 'ul_key' => 'populate rev_parent_id' ), - __FUNCTION__, - 'IGNORE' ); - if( $logged ) { - wfOut( "rev_parent_id population complete ... {$count} rows [{$changed} changed]\n" ); - return true; - } else { - wfOut( "Could not insert rev_parent_id population row.\n" ); - return false; - } -} - diff --git a/maintenance/populateParentId.php b/maintenance/populateParentId.php index bf81cb68..387f5a56 100644 --- a/maintenance/populateParentId.php +++ b/maintenance/populateParentId.php @@ -22,7 +22,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class PopulateParentId extends Maintenance { public function __construct() { @@ -39,7 +39,7 @@ class PopulateParentId extends Maintenance { $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 ) ){ + 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' ), @@ -48,47 +48,46 @@ class PopulateParentId extends Maintenance { return; } # Do remaining chunk - $end += $this->mBatchSize - 1; $blockStart = intval( $start ); $blockEnd = intval( $start ) + $this->mBatchSize - 1; $count = 0; $changed = 0; - while( $blockEnd <= $end ) { + while ( $blockStart <= $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'), + $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 ) { + 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', + $previousID = $db->selectField( 'revision', 'rev_id', array( 'rev_page' => $row->rev_page, 'rev_timestamp' => $row->rev_timestamp, - "rev_id < " . intval( $row->rev_id ) ), + "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 ) { + if ( !$previousID ) { # Get the highest older timestamp - $lastTimestamp = $db->selectField( 'revision', 'rev_timestamp', - array( 'rev_page' => $row->rev_page, "rev_timestamp < " . $db->addQuotes( $row->rev_timestamp ) ), + $lastTimestamp = $db->selectField( 'revision', 'rev_timestamp', + array( 'rev_page' => $row->rev_page, "rev_timestamp < " . $db->addQuotes( $row->rev_timestamp ) ), __METHOD__, array( 'ORDER BY' => 'rev_timestamp DESC' ) ); # 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 ), + 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 ) + $previousID = intval( $previousID ); + if ( $previousID != $row->rev_parent_id ) $changed++; # Update the row... $db->update( 'revision', @@ -97,15 +96,15 @@ class PopulateParentId extends Maintenance { __METHOD__ ); $count++; } - $blockStart += $this->mBatchSize - 1; - $blockEnd += $this->mBatchSize - 1; + $blockStart += $this->mBatchSize; + $blockEnd += $this->mBatchSize; wfWaitForSlaves( 5 ); } $logged = $db->insert( 'updatelog', array( 'ul_key' => 'populate rev_parent_id' ), __METHOD__, 'IGNORE' ); - if( $logged ) { + if ( $logged ) { $this->output( "rev_parent_id population complete ... {$count} rows [{$changed} changed]\n" ); return true; } else { @@ -116,4 +115,4 @@ class PopulateParentId extends Maintenance { } $maintClass = "PopulateParentId"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/populateRevisionLength.php b/maintenance/populateRevisionLength.php new file mode 100644 index 00000000..0af51dc1 --- /dev/null +++ b/maintenance/populateRevisionLength.php @@ -0,0 +1,98 @@ +<?php +/* + * Populates the rev_len field for old revisions created before MW 1.10. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * 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 PopulateRevisionLength extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Populates rev_len"; + $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_len 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_len' ), + __METHOD__, + 'IGNORE' ); + return; + } + # Do remaining chunks + $blockStart = intval( $start ); + $blockEnd = intval( $start ) + $this->mBatchSize - 1; + $count = 0; + $missing = 0; + while ( $blockStart <= $end ) { + $this->output( "...doing rev_id from $blockStart to $blockEnd\n" ); + $res = $db->select( 'revision', + Revision::selectFields(), + array( "rev_id >= $blockStart", + "rev_id <= $blockEnd", + "rev_len IS NULL" ), + __METHOD__ ); + # Go through and update rev_len from these rows. + foreach ( $res as $row ) { + $rev = new Revision( $row ); + $text = $rev->getRawText(); + if ( !is_string( $text ) ) { + # This should not happen, but sometimes does (bug 20757) + $this->output( "Text of revision {$row->rev_id} unavailable!\n" ); + $missing++; + } + else { + # Update the row... + $db->update( 'revision', + array( 'rev_len' => strlen( $text ) ), + array( 'rev_id' => $row->rev_id ), + __METHOD__ ); + $count++; + } + } + $blockStart += $this->mBatchSize; + $blockEnd += $this->mBatchSize; + wfWaitForSlaves( 5 ); + } + $logged = $db->insert( 'updatelog', + array( 'ul_key' => 'populate rev_len' ), + __METHOD__, + 'IGNORE' ); + if ( $logged ) { + $this->output( "rev_len population complete ... {$count} rows changed ({$missing} missing)\n" ); + return true; + } else { + $this->output( "Could not insert rev_len population row.\n" ); + return false; + } + } +} + +$maintClass = "PopulateRevisionLength"; +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/populateSha1.php b/maintenance/populateSha1.php index 72ef9461..1714c0d6 100644 --- a/maintenance/populateSha1.php +++ b/maintenance/populateSha1.php @@ -20,7 +20,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__).'/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class PopulateSha1 extends Maintenance { public function __construct() { @@ -37,14 +37,14 @@ class PopulateSha1 extends Maintenance { $t = -microtime( true ); $dbw = wfGetDB( DB_MASTER ); - if( $file ) { - $res = $dbw->selectRow( - 'image', - array( 'img_name' ), + if ( $file ) { + $res = $dbw->selectRow( + 'image', + array( 'img_name' ), array( 'img_name' => $dbw->addQuotes( $file ) ), __METHOD__ ); - if( !$res ) { + if ( !$res ) { $this->error( "No such file: $file", true ); return; } @@ -52,19 +52,17 @@ class PopulateSha1 extends Maintenance { $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 ) . + $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 ) { @@ -98,4 +96,4 @@ class PopulateSha1 extends Maintenance { } $maintClass = "PopulateSha1"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/postgres/archives/patch-categorylinks-better-collation.sql b/maintenance/postgres/archives/patch-categorylinks-better-collation.sql new file mode 100644 index 00000000..b3fa6346 --- /dev/null +++ b/maintenance/postgres/archives/patch-categorylinks-better-collation.sql @@ -0,0 +1,8 @@ +CREATE TYPE link_type AS ENUM ('page', 'subcat', 'file'); +DROP INDEX cl_sortkey; +ALTER TABLE categorylinks + ADD COLUMN cl_sortkey_prefix TEXT NOT NULL DEFAULT '', + ADD COLUMN cl_collation SMALLINT NOT NULL DEFAULT 0, + ADD COLUMN cl_type link_type NOT NULL DEFAULT 'page'; +CREATE INDEX cl_collation ON categorylinks ( cl_collation ); +CREATE INDEX cl_sortkey ON categorylinks ( cl_to, cl_type, cl_sortkey, cl_from ); diff --git a/maintenance/postgres/archives/patch-change_tag.sql b/maintenance/postgres/archives/patch-change_tag.sql index 1f52c474..89d74b63 100644 --- a/maintenance/postgres/archives/patch-change_tag.sql +++ b/maintenance/postgres/archives/patch-change_tag.sql @@ -1,28 +1,11 @@ - CREATE TABLE change_tag ( - ct_rc_id INTEGER NULL, - ct_log_id INTEGER NULL, - ct_rev_id INTEGER NULL, - ct_tag TEXT NOT NULL, - ct_params TEXT NULL + ct_rc_id INTEGER NULL, + ct_log_id INTEGER NULL, + ct_rev_id INTEGER NULL, + ct_tag TEXT NOT NULL, + ct_params TEXT NULL ); 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); CREATE INDEX change_tag_tag_id ON change_tag(ct_tag,ct_rc_id,ct_rev_id,ct_log_id); - - -CREATE TABLE tag_summary ( - ts_rc_id INTEGER NULL, - ts_log_id INTEGER NULL, - ts_rev_id INTEGER NULL, - ts_tags TEXT 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 TEXT NOT NULL PRIMARY KEY -); diff --git a/maintenance/postgres/archives/patch-iwlinks.sql b/maintenance/postgres/archives/patch-iwlinks.sql new file mode 100644 index 00000000..db26eae4 --- /dev/null +++ b/maintenance/postgres/archives/patch-iwlinks.sql @@ -0,0 +1,8 @@ + +CREATE TABLE iwlinks ( + iwl_from INTEGER NOT NULL DEFAULT 0, + iwl_prefix TEXT NOT NULL DEFAULT '', + iwl_title TEXT NOT NULL DEFAULT '' +); +CREATE UNIQUE INDEX iwl_from ON iwlinks (iwl_from, iwl_prefix, iwl_title); +CREATE UNIQUE INDEX iwl_prefix_title_from ON iwlinks (iwl_prefix, iwl_title, iwl_from); diff --git a/maintenance/postgres/archives/patch-kill-iwl_pft.sql b/maintenance/postgres/archives/patch-kill-iwl_pft.sql new file mode 100644 index 00000000..4419d9e9 --- /dev/null +++ b/maintenance/postgres/archives/patch-kill-iwl_pft.sql @@ -0,0 +1,7 @@ +-- +-- Kill the old iwl_prefix_from_title index, which may be present on some +-- installs if they ran update.php between it being added and being renamed +-- + +DROP INDEX iwl_prefix_from_title; + diff --git a/maintenance/postgres/archives/patch-kill-iwl_prefix.sql b/maintenance/postgres/archives/patch-kill-iwl_prefix.sql new file mode 100644 index 00000000..8b6d1084 --- /dev/null +++ b/maintenance/postgres/archives/patch-kill-iwl_prefix.sql @@ -0,0 +1,7 @@ +-- +-- Kill the old iwl_prefix index, which may be present on some +-- installs if they ran update.php between it being added and being renamed +-- + +DROP INDEX iwl_prefix; + diff --git a/maintenance/postgres/archives/patch-mediawiki_version.sql b/maintenance/postgres/archives/patch-mediawiki_version.sql deleted file mode 100644 index 811b38a1..00000000 --- a/maintenance/postgres/archives/patch-mediawiki_version.sql +++ /dev/null @@ -1,18 +0,0 @@ -CREATE TABLE mediawiki_version ( - type TEXT NOT NULL, - mw_version TEXT NOT NULL, - notes TEXT NULL, - - pg_version TEXT NULL, - pg_dbname TEXT NULL, - pg_user TEXT NULL, - pg_port TEXT NULL, - mw_schema TEXT NULL, - ts2_schema TEXT NULL, - ctype TEXT NULL, - - sql_version TEXT NULL, - sql_date TEXT NULL, - cdate TIMESTAMPTZ NOT NULL DEFAULT now() -); - diff --git a/maintenance/postgres/archives/patch-module_deps.sql b/maintenance/postgres/archives/patch-module_deps.sql new file mode 100644 index 00000000..703dcdaf --- /dev/null +++ b/maintenance/postgres/archives/patch-module_deps.sql @@ -0,0 +1,7 @@ +CREATE TABLE module_deps ( + md_module TEXT NOT NULL, + md_skin TEXT NOT NULL, + md_deps TEXT NOT NULL +); + +CREATE UNIQUE INDEX md_module_skin_idx ON module_deps (md_module, md_skin); diff --git a/maintenance/postgres/archives/patch-msg_resource.sql b/maintenance/postgres/archives/patch-msg_resource.sql new file mode 100644 index 00000000..00d82073 --- /dev/null +++ b/maintenance/postgres/archives/patch-msg_resource.sql @@ -0,0 +1,8 @@ +CREATE TABLE msg_resource ( + mr_resource TEXT NOT NULL, + mr_lang TEXT NOT NULL, + mr_blob TEXT NOT NULL, + mr_timestamp TIMESTAMPTZ NOT NULL +); + +CREATE UNIQUE INDEX mr_resource_lang_idx ON msg_resource (mr_resource, mr_lang); diff --git a/maintenance/postgres/archives/patch-msg_resource_links.sql b/maintenance/postgres/archives/patch-msg_resource_links.sql new file mode 100644 index 00000000..e7b80219 --- /dev/null +++ b/maintenance/postgres/archives/patch-msg_resource_links.sql @@ -0,0 +1,6 @@ +CREATE TABLE msg_resource_links ( + mrl_resource TEXT NOT NULL, + mrl_message TEXT NOT NULL +); + +CREATE UNIQUE INDEX mrl_message_resource_idx ON msg_resource_links (mrl_message, mrl_resource); diff --git a/maintenance/postgres/archives/patch-mwuser.sql b/maintenance/postgres/archives/patch-mwuser.sql deleted file mode 100644 index 3984703a..00000000 --- a/maintenance/postgres/archives/patch-mwuser.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "user" RENAME TO mwuser; diff --git a/maintenance/postgres/archives/patch-page.sql b/maintenance/postgres/archives/patch-page.sql new file mode 100644 index 00000000..cceef898 --- /dev/null +++ b/maintenance/postgres/archives/patch-page.sql @@ -0,0 +1,24 @@ +CREATE SEQUENCE page_page_id_seq; +CREATE TABLE page ( + page_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('page_page_id_seq'), + page_namespace SMALLINT NOT NULL, + page_title TEXT NOT NULL, + page_restrictions TEXT, + 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 DEFAULT RANDOM(), + page_touched TIMESTAMPTZ, + page_latest INTEGER NOT NULL, + page_len INTEGER 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_mediawiki_title ON page (page_title) WHERE page_namespace = 8; +CREATE INDEX page_random_idx ON page (page_random); +CREATE INDEX page_len_idx ON page (page_len); + diff --git a/maintenance/postgres/archives/patch-pagecontent.sql b/maintenance/postgres/archives/patch-pagecontent.sql deleted file mode 100644 index c3651f92..00000000 --- a/maintenance/postgres/archives/patch-pagecontent.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "text" RENAME TO pagecontent; diff --git a/maintenance/postgres/archives/patch-rename-iwl_prefix.sql b/maintenance/postgres/archives/patch-rename-iwl_prefix.sql new file mode 100644 index 00000000..a4bdb6a9 --- /dev/null +++ b/maintenance/postgres/archives/patch-rename-iwl_prefix.sql @@ -0,0 +1,2 @@ +DROP INDEX iwl_prefix; +CREATE UNIQUE INDEX iwl_prefix_title_from ON iwlinks (iwl_prefix, iwl_from, iwl_title); diff --git a/maintenance/postgres/archives/patch-tag_summary.sql b/maintenance/postgres/archives/patch-tag_summary.sql new file mode 100644 index 00000000..49e05e77 --- /dev/null +++ b/maintenance/postgres/archives/patch-tag_summary.sql @@ -0,0 +1,9 @@ +CREATE TABLE tag_summary ( + ts_rc_id INTEGER NULL, + ts_log_id INTEGER NULL, + ts_rev_id INTEGER NULL, + ts_tags TEXT 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); diff --git a/maintenance/testRunner.postgres.sql b/maintenance/postgres/archives/patch-testrun.sql index c15300b5..c15300b5 100644 --- a/maintenance/testRunner.postgres.sql +++ b/maintenance/postgres/archives/patch-testrun.sql diff --git a/maintenance/postgres/archives/patch-update_sequences.sql b/maintenance/postgres/archives/patch-update_sequences.sql index a3d30681..94f7be4f 100644 --- a/maintenance/postgres/archives/patch-update_sequences.sql +++ b/maintenance/postgres/archives/patch-update_sequences.sql @@ -1,20 +1,20 @@ -ALTER SEQUENCE rev_rev_id_val RENAME TO revision_rev_id_seq; +ALTER TABLE revision RENAME rev_rev_id_val 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 RENAME text_old_id_val 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 RENAME category_id_seq 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 RENAME ipblocks_ipb_id_val 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 RENAME rc_rc_id_seq 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 RENAME log_log_id_seq 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 RENAME pr_id_val 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-valid_tag.sql b/maintenance/postgres/archives/patch-valid_tag.sql new file mode 100644 index 00000000..98575c6e --- /dev/null +++ b/maintenance/postgres/archives/patch-valid_tag.sql @@ -0,0 +1,3 @@ +CREATE TABLE valid_tag ( + vt_tag TEXT NOT NULL PRIMARY KEY +); diff --git a/maintenance/postgres/compare_schemas.pl b/maintenance/postgres/compare_schemas.pl index 9bddf504..7e3cdf71 100644 --- a/maintenance/postgres/compare_schemas.pl +++ b/maintenance/postgres/compare_schemas.pl @@ -43,7 +43,7 @@ tinytext mediumtext text char varchar varbinary binary timestamp datetime tinyblob mediumblob blob ); -$datatype .= q{|ENUM\([\"\w, ]+\)}; +$datatype .= q{|ENUM\([\"\w\', ]+\)}; $datatype = qr{($datatype)}; my $typeval = qr{(\(\d+\))?}; @@ -142,50 +142,6 @@ sub parse_sql { } ## end of parse_sql -## Read in the parser test information -my $parsefile = '../parserTests.inc'; -open my $pfh, '<', $parsefile or die qq{Could not open "$parsefile": $!\n}; -my $stat = 0; -my %ptable; -while (<$pfh>) { - if (!$stat) { - if (/function listTables/) { - $stat = 1; - } - next; - } - $ptable{$1}=2 while m{'(\w+)'}g; - last if /\);/; -} -close $pfh or die qq{Could not close "$parsefile": $!\n}; - -my $OK_NOT_IN_PTABLE = ' -change_tag -filearchive -logging -profiling -querycache_info -searchindex -tag_summary -trackbacks -transcache -user_newtalk -updatelog -valid_tag -'; - -## Make sure all tables in main tables.sql are accounted for in the parsertest. -for my $table (sort keys %{$old{'../tables.sql'}}) { - $ptable{$table}++; - next if $ptable{$table} > 2; - next if $OK_NOT_IN_PTABLE =~ /\b$table\b/; - print qq{Table "$table" is in the schema, but not used inside of parserTest.inc\n}; -} -## Any that are used in ptables but no longer exist in the schema? -for my $table (sort grep { $ptable{$_} == 2 } keys %ptable) { - print qq{Table "$table" ($ptable{$table}) used in parserTest.inc, but not found in schema\n}; -} - for my $oldfile (@old) { ## Begin non-standard indent @@ -316,18 +272,20 @@ rc_log_type varbinary(255) TEXT ## Simple text-only strings: ar_flags tinyblob TEXT +cl_collation varbinary(32) TEXT +cl_sortkey varbinary(230) TEXT ct_params blob TEXT -fa_minor_mime varbinary(32) TEXT +fa_minor_mime varbinary(100) TEXT fa_storage_group varbinary(16) TEXT # Just 'deleted' for now, should stay plain text fa_storage_key varbinary(64) TEXT # sha1 plus text extension 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 +img_minor_mime varbinary(100) TEXT lc_lang varbinary(32) TEXT lc_value varbinary(32) TEXT - img_sha1 varbinary(32) TEXT +iw_wikiid varchar(64) 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 @@ -335,7 +293,10 @@ 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 +md_deps mediumblob TEXT # JSON +mr_blob mediumblob TEXT # JSON +mr_lang varbinary(32) TEXT +oi_minor_mime varbinary(100) TEXT oi_sha1 varbinary(32) TEXT old_flags tinyblob TEXT old_text mediumblob TEXT @@ -354,6 +315,7 @@ rc_params blob TEXT rlc_to_blob blob TEXT ts_tags blob TEXT ug_group varbinary(16) TEXT +ul_value blob TEXT up_property varbinary(32) TEXT up_value blob TEXT user_email_token binary(32) TEXT @@ -362,10 +324,12 @@ user_newpassword tinyblob TEXT user_options blob TEXT user_password tinyblob TEXT user_token binary(32) TEXT +iwl_prefix varbinary(20) TEXT ## Text URLs: el_index blob TEXT el_to blob TEXT +iw_api blob TEXT iw_url blob TEXT tb_url blob TEXT tc_url varbinary(255) TEXT @@ -574,5 +538,4 @@ __DATA__ OLD: searchindex ## We use tsearch2 directly on the page table instead RENAME: user mwuser ## Reserved word causing lots of problems RENAME: text pagecontent ## Reserved word -NEW: mediawiki_version ## Just us, for now XFILE: ../archives/patch-profiling.sql diff --git a/maintenance/postgres/mediawiki_mysql2postgres.pl b/maintenance/postgres/mediawiki_mysql2postgres.pl index 220c779b..2b2bf50e 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 59489 2009-11-27 15:34:54Z greg $ +## svn: $Id: mediawiki_mysql2postgres.pl 65542 2010-04-26 13:46:04Z demon $ ## 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: 59489 $}.qq{) +-- Version: $VERSION (subversion }.q{$LastChangedRevision: 65542 $}.qq{) -- Author: Greg Sabino Mullane <greg\@turnstep.com> Comments welcome -- -- This file was created: $now @@ -421,12 +421,6 @@ SELECT setval('trackbacks_tb_id_seq', 1+coalesce(max(tb_id) ,0),false) FROM tr SELECT setval('user_user_id_seq', 1+coalesce(max(user_id),0),false) FROM mwuser; }; -## Finally, make a record in the mediawiki_version table about this import -print qq{ -INSERT INTO mediawiki_version (type,mw_version,notes) VALUES ('MySQL import','??', -'Imported from file created on $now. Old version: $current_version'); -}; - print "COMMIT;\n\\o\n\n-- End of dump\n\n"; select $oldselect; close $mdump or die qq{Could not close "$MYSQLDUMPFILE": $!\n}; @@ -438,7 +432,6 @@ __DATA__ ## or leave blank if it should be skipped pagecontent text mwuser user -mediawiki_version archive2 profiling objectcache diff --git a/maintenance/postgres/tables.sql b/maintenance/postgres/tables.sql index 38b607d9..8e869da7 100644 --- a/maintenance/postgres/tables.sql +++ b/maintenance/postgres/tables.sql @@ -34,13 +34,13 @@ INSERT INTO mwuser VALUES (DEFAULT,'Anonymous','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,now(),now()); CREATE TABLE user_groups ( - ug_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE CASCADE, + ug_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, ug_group TEXT NOT NULL ); CREATE UNIQUE INDEX user_groups_unique ON user_groups (ug_user, ug_group); CREATE TABLE user_newtalk ( - user_id INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, user_ip TEXT NULL, user_last_timestamp TIMESTAMPTZ ); @@ -68,6 +68,7 @@ CREATE INDEX page_talk_title ON page (page_title) WHERE page_namespace = 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_mediawiki_title ON page (page_title) WHERE page_namespace = 8; CREATE INDEX page_random_idx ON page (page_random); CREATE INDEX page_len_idx ON page (page_len); @@ -85,10 +86,10 @@ CREATE TRIGGER page_deleted AFTER DELETE ON page CREATE SEQUENCE revision_rev_id_seq; CREATE TABLE revision ( rev_id INTEGER NOT NULL UNIQUE DEFAULT nextval('revision_rev_id_seq'), - rev_page INTEGER NULL REFERENCES page (page_id) ON DELETE CASCADE, + rev_page INTEGER NULL REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, rev_text_id INTEGER NULL, -- FK rev_comment TEXT, - rev_user INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE RESTRICT, + rev_user INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED, rev_user_text TEXT NOT NULL, rev_timestamp TIMESTAMPTZ NOT NULL, rev_minor_edit SMALLINT NOT NULL DEFAULT 0, @@ -114,7 +115,7 @@ CREATE TABLE pagecontent ( -- replaces reserved word 'text' CREATE SEQUENCE page_restrictions_pr_id_seq; CREATE TABLE page_restrictions ( 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_page INTEGER NULL REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, pr_type TEXT NOT NULL, pr_level TEXT NOT NULL, pr_cascade SMALLINT NOT NULL, @@ -124,7 +125,7 @@ CREATE TABLE page_restrictions ( ALTER TABLE page_restrictions ADD CONSTRAINT page_restrictions_pk PRIMARY KEY (pr_page,pr_type); CREATE TABLE page_props ( - pp_page INTEGER NOT NULL REFERENCES page (page_id) ON DELETE CASCADE, + pp_page INTEGER NOT NULL REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, pp_propname TEXT NOT NULL, pp_value TEXT NOT NULL ); @@ -138,7 +139,7 @@ CREATE TABLE archive ( ar_page_id INTEGER NULL, ar_parent_id INTEGER NULL, ar_comment TEXT, - ar_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL, + ar_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, ar_user_text TEXT NOT NULL, ar_timestamp TIMESTAMPTZ NOT NULL, ar_minor_edit SMALLINT NOT NULL DEFAULT 0, @@ -153,7 +154,7 @@ 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_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, rd_namespace SMALLINT NOT NULL, rd_title TEXT NOT NULL, rd_interwiki TEXT NULL, @@ -163,14 +164,15 @@ 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 INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, pl_namespace SMALLINT NOT NULL, pl_title TEXT NOT NULL ); CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title); +CREATE INDEX pagelinks_title ON pagelinks (pl_title); CREATE TABLE templatelinks ( - tl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE, + tl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, tl_namespace SMALLINT NOT NULL, tl_title TEXT NOT NULL ); @@ -178,22 +180,25 @@ CREATE UNIQUE INDEX templatelinks_unique ON templatelinks (tl_namespace,tl_title CREATE INDEX templatelinks_from ON templatelinks (tl_from); CREATE TABLE imagelinks ( - il_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE, + il_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, il_to TEXT 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 TEXT NOT NULL, - cl_sortkey TEXT, - cl_timestamp TIMESTAMPTZ NOT NULL + cl_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + cl_to TEXT NOT NULL, + cl_sortkey TEXT NULL, + cl_timestamp TIMESTAMPTZ NOT NULL, + cl_sortkey_prefix TEXT NOT NULL DEFAULT '', + cl_collation TEXT NOT NULL DEFAULT 0, + cl_type TEXT NOT NULL DEFAULT 'page' ); CREATE UNIQUE INDEX cl_from ON categorylinks (cl_from, cl_to); 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_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, el_to TEXT NOT NULL, el_index TEXT NOT NULL ); @@ -208,7 +213,7 @@ CREATE TABLE external_user ( 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_from INTEGER NOT NULL REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, ll_lang TEXT, ll_title TEXT ); @@ -237,8 +242,8 @@ CREATE SEQUENCE ipblocks_ipb_id_seq; CREATE TABLE ipblocks ( 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, + ipb_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, + ipb_by INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, ipb_by_text TEXT NOT NULL DEFAULT '', ipb_reason TEXT NOT NULL, ipb_timestamp TIMESTAMPTZ NOT NULL, @@ -270,7 +275,7 @@ CREATE TABLE image ( img_major_mime TEXT DEFAULT 'unknown', img_minor_mime TEXT DEFAULT 'unknown', img_description TEXT NOT NULL, - img_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL, + img_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, img_user_text TEXT NOT NULL, img_timestamp TIMESTAMPTZ, img_sha1 TEXT NOT NULL DEFAULT '' @@ -287,7 +292,7 @@ CREATE TABLE oldimage ( oi_height INTEGER NOT NULL, oi_bits SMALLINT NULL, oi_description TEXT, - oi_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL, + oi_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, oi_user_text TEXT NOT NULL, oi_timestamp TIMESTAMPTZ NULL, oi_metadata BYTEA NOT NULL DEFAULT '', @@ -297,7 +302,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_cascaded FOREIGN KEY (oi_name) REFERENCES image(img_name) ON DELETE CASCADE ON UPDATE 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 DEFERRABLE INITIALLY DEFERRED; 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); @@ -310,7 +315,7 @@ CREATE TABLE filearchive ( fa_archive_name TEXT, fa_storage_group TEXT, fa_storage_key TEXT, - fa_deleted_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL, + fa_deleted_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, fa_deleted_timestamp TIMESTAMPTZ NOT NULL, fa_deleted_reason TEXT, fa_size INTEGER NOT NULL, @@ -322,7 +327,7 @@ CREATE TABLE filearchive ( fa_major_mime TEXT DEFAULT 'unknown', fa_minor_mime TEXT DEFAULT 'unknown', fa_description TEXT NOT NULL, - fa_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL, + fa_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, fa_user_text TEXT NOT NULL, fa_timestamp TIMESTAMPTZ, fa_deleted SMALLINT NOT NULL DEFAULT 0 @@ -338,7 +343,7 @@ CREATE TABLE recentchanges ( rc_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('recentchanges_rc_id_seq'), rc_timestamp TIMESTAMPTZ NOT NULL, rc_cur_time TIMESTAMPTZ NOT NULL, - rc_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL, + rc_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, rc_user_text TEXT NOT NULL, rc_namespace SMALLINT NOT NULL, rc_title TEXT NOT NULL, @@ -346,7 +351,7 @@ 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 NULL REFERENCES page(page_id) ON DELETE SET NULL, + rc_cur_id INTEGER NULL REFERENCES page(page_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, rc_this_oldid INTEGER NOT NULL, rc_last_oldid INTEGER NOT NULL, rc_type SMALLINT NOT NULL DEFAULT 0, @@ -371,7 +376,7 @@ 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 INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, wl_namespace SMALLINT NOT NULL DEFAULT 0, wl_title TEXT NOT NULL, wl_notificationtimestamp TIMESTAMPTZ @@ -392,7 +397,9 @@ CREATE TABLE interwiki ( iw_prefix TEXT NOT NULL UNIQUE, iw_url TEXT NOT NULL, iw_local SMALLINT NOT NULL, - iw_trans SMALLINT NOT NULL DEFAULT 0 + iw_trans SMALLINT NOT NULL DEFAULT 0, + iw_api TEXT NOT NULL DEFAULT '', + iw_wikiid TEXT NOT NULL DEFAULT '' ); @@ -441,7 +448,7 @@ CREATE TABLE logging ( log_type TEXT NOT NULL, log_action TEXT NOT NULL, log_timestamp TIMESTAMPTZ NOT NULL, - log_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL, + log_user INTEGER REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, log_namespace SMALLINT NOT NULL, log_title TEXT NOT NULL, log_comment TEXT, @@ -468,7 +475,7 @@ CREATE INDEX ls_log_id ON log_search (ls_log_id); CREATE SEQUENCE trackbacks_tb_id_seq; CREATE TABLE trackbacks ( tb_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('trackbacks_tb_id_seq'), - tb_page INTEGER REFERENCES page(page_id) ON DELETE CASCADE, + tb_page INTEGER REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, tb_title TEXT NOT NULL, tb_url TEXT NOT NULL, tb_ex TEXT, @@ -488,7 +495,7 @@ CREATE TABLE job ( CREATE INDEX job_cmd_namespace_title ON job (job_cmd, job_namespace, job_title); -- Tsearch2 2 stuff. Will fail if we don't have proper access to the tsearch2 tables --- Note: if version 8.3 or higher, we remove the 'default' arg +-- Version 8.3 or higher only. Previous versions would need another parmeter for to_tsvector. -- Make sure you also change patch-tsearch2funcs.sql if the funcs below change. ALTER TABLE page ADD titlevector tsvector; @@ -496,9 +503,9 @@ CREATE FUNCTION ts2_page_title() RETURNS TRIGGER LANGUAGE plpgsql AS $mw$ BEGIN IF TG_OP = 'INSERT' THEN - NEW.titlevector = to_tsvector('default',REPLACE(NEW.page_title,'/',' ')); + NEW.titlevector = to_tsvector(REPLACE(NEW.page_title,'/',' ')); ELSIF NEW.page_title != OLD.page_title THEN - NEW.titlevector := to_tsvector('default',REPLACE(NEW.page_title,'/',' ')); + NEW.titlevector := to_tsvector(REPLACE(NEW.page_title,'/',' ')); END IF; RETURN NEW; END; @@ -513,9 +520,9 @@ CREATE FUNCTION ts2_page_text() RETURNS TRIGGER LANGUAGE plpgsql AS $mw$ BEGIN IF TG_OP = 'INSERT' THEN - NEW.textvector = to_tsvector('default',NEW.old_text); + NEW.textvector = to_tsvector(NEW.old_text); ELSIF NEW.old_text != OLD.old_text THEN - NEW.textvector := to_tsvector('default',NEW.old_text); + NEW.textvector := to_tsvector(NEW.old_text); END IF; RETURN NEW; END; @@ -549,7 +556,7 @@ CREATE UNIQUE INDEX pf_name_server ON profiling (pf_name, pf_server); CREATE TABLE protected_titles ( pt_namespace SMALLINT NOT NULL, pt_title TEXT NOT NULL, - pt_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL, + pt_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED, pt_reason TEXT NULL, pt_timestamp TIMESTAMPTZ NOT NULL, pt_expiry TIMESTAMPTZ NULL, @@ -559,7 +566,8 @@ CREATE UNIQUE INDEX protected_titles_unique ON protected_titles(pt_namespace, pt CREATE TABLE updatelog ( - ul_key TEXT NOT NULL PRIMARY KEY + ul_key TEXT NOT NULL PRIMARY KEY, + ul_value TEXT ); @@ -602,37 +610,45 @@ CREATE TABLE valid_tag ( ); CREATE TABLE user_properties ( - up_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE CASCADE, + up_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, 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, - notes TEXT NULL, +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); - pg_version TEXT NULL, - pg_dbname TEXT NULL, - pg_user TEXT NULL, - pg_port TEXT NULL, - mw_schema TEXT NULL, - ts2_schema TEXT NULL, - ctype TEXT NULL, +CREATE TABLE iwlinks ( + iwl_from INTEGER NOT NULL DEFAULT 0, + iwl_prefix TEXT NOT NULL DEFAULT '', + iwl_title TEXT NOT NULL DEFAULT '' +); +CREATE UNIQUE INDEX iwl_from ON iwlinks (iwl_from, iwl_prefix, iwl_title); +CREATE UNIQUE INDEX iwl_prefix_title_from ON iwlinks (iwl_prefix, iwl_title, iwl_from); - sql_version TEXT NULL, - sql_date TEXT NULL, - cdate TIMESTAMPTZ NOT NULL DEFAULT now() +CREATE TABLE msg_resource ( + mr_resource TEXT NOT NULL, + mr_lang TEXT NOT NULL, + mr_blob TEXT NOT NULL, + mr_timestamp TIMESTAMPTZ NOT NULL ); +CREATE UNIQUE INDEX mr_resource_lang ON msg_resource (mr_resource, mr_lang); -INSERT INTO mediawiki_version (type,mw_version,sql_version,sql_date) - VALUES ('Creation','??','$LastChangedRevision: 59842 $','$LastChangedDate: 2009-12-09 06:32:17 +1100 (Wed, 09 Dec 2009) $'); +CREATE TABLE msg_resource_links ( + mrl_resource TEXT NOT NULL, + mrl_message TEXT NOT NULL +); +CREATE UNIQUE INDEX mrl_message_resource ON msg_resource_links (mrl_message, mrl_resource); -CREATE TABLE l10n_cache ( - lc_lang TEXT NOT NULL, - lc_key TEXT NOT NULL, - lc_value TEXT NOT NULL +CREATE TABLE module_deps ( + md_module TEXT NOT NULL, + md_skin TEXT NOT NULL, + md_deps TEXT NOT NULL ); -CREATE INDEX l10n_cache_lc_lang_key ON l10n_cache (lc_lang, lc_key); +CREATE UNIQUE INDEX md_module_skin ON module_deps (md_module, md_skin); diff --git a/maintenance/preprocessorFuzzTest.php b/maintenance/preprocessorFuzzTest.php index c271b117..31b372c2 100644 --- a/maintenance/preprocessorFuzzTest.php +++ b/maintenance/preprocessorFuzzTest.php @@ -4,28 +4,28 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/commandLine.inc' ); $wgHooks['BeforeParserFetchTemplateAndtitle'][] = 'PPFuzzTester::templateHook'; class PPFuzzTester { var $hairs = array( - '[[', ']]', '{{', '{{', '}}', '}}', '{{{', '}}}', + '[[', ']]', '{{', '{{', '}}', '}}', '{{{', '}}}', '<', '>', '<nowiki', '<gallery', '</nowiki>', '</gallery>', '<nOwIkI>', '</NoWiKi>', '<!--' , '-->', "\n==", "==\n", '|', '=', "\n", ' ', "\t", "\x7f", '~~', '~~~', '~~~~', 'subst:', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', // extensions - //'<ref>', '</ref>', '<references/>', + // '<ref>', '</ref>', '<references/>', ); var $minLength = 0; var $maxLength = 20; var $maxTemplates = 5; - //var $outputTypes = array( 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' ); + // var $outputTypes = array( 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' ); var $entryPoints = array( 'testSrvus', 'testPst', 'testPreprocess' ); var $verbose = false; static $currentTest = false; @@ -51,7 +51,7 @@ class PPFuzzTester { $exceptionReport = $e->getText(); $hash = md5( $testReport ); file_put_contents( "results/ppft-$hash.in", serialize( self::$currentTest ) ); - file_put_contents( "results/ppft-$hash.fail", + file_put_contents( "results/ppft-$hash.fail", "Input:\n$testReport\n\nException report:\n$exceptionReport\n" ); print "Test $hash failed\n"; $passed = 'failed'; @@ -99,7 +99,7 @@ class PPFuzzTester { $s .= $this->hairs[$hairIndex]; } // Send through the UTF-8 normaliser - // This resolves a few differences between the old preprocessor and the + // 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. global $wgContLang; @@ -131,16 +131,16 @@ class PPFuzzTest { $this->parent = $tester; $this->mainText = $tester->makeInputText(); $this->title = $tester->makeTitle(); - //$this->outputType = $tester->pickOutputType(); + // $this->outputType = $tester->pickOutputType(); $this->entryPoint = $tester->pickEntryPoint(); - $this->nickname = $tester->makeInputText( $wgMaxSigChars + 10); + $this->nickname = $tester->makeInputText( $wgMaxSigChars + 10 ); $this->fancySig = (bool)mt_rand( 0, 1 ); $this->templates = array(); } function templateHook( $title ) { $titleText = $title->getPrefixedDBkey(); - + if ( !isset( $this->templates[$titleText] ) ) { $finalTitle = $title; if ( count( $this->templates ) >= $this->parent->maxTemplates ) { @@ -182,9 +182,9 @@ class PPFuzzTest { function getReport() { $s = "Title: " . $this->title->getPrefixedDBkey() . "\n" . -// "Output type: {$this->outputType}\n" . - "Entry point: {$this->entryPoint}\n" . - "User: " . ( $this->fancySig ? 'fancy' : 'no-fancy' ) . ' ' . var_export( $this->nickname, true ) . "\n" . +// "Output type: {$this->outputType}\n" . + "Entry point: {$this->entryPoint}\n" . + "User: " . ( $this->fancySig ? 'fancy' : 'no-fancy' ) . ' ' . var_export( $this->nickname, true ) . "\n" . "Main text: " . var_export( $this->mainText, true ) . "\n"; foreach ( $this->templates as $titleText => $template ) { $finalTitle = $template['finalTitle']; diff --git a/maintenance/protect.php b/maintenance/protect.php index 126707a7..baef45fb 100644 --- a/maintenance/protect.php +++ b/maintenance/protect.php @@ -18,7 +18,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class Protect extends Maintenance { public function __construct() { @@ -28,34 +28,35 @@ class Protect extends Maintenance { $this->addOption( 'semiprotect', 'Adds semi-protection' ); $this->addOption( 'u', 'Username to protect with', false, true ); $this->addOption( 'r', 'Reason for un/protection', false, true ); + $this->addArg( 'title', 'Title to protect', true ); } public function execute() { - global $wgUser, $wgTitle, $wgArticle; + global $wgUser; $userName = $this->getOption( 'u', 'Maintenance script' ); $reason = $this->getOption( 'r', '' ); $protection = "sysop"; - if ( $this->hasOption('semiprotect') ) { + if ( $this->hasOption( 'semiprotect' ) ) { $protection = "autoconfirmed"; - } elseif ( $this->hasOption('unprotect') ) { + } elseif ( $this->hasOption( 'unprotect' ) ) { $protection = ""; } $wgUser = User::newFromName( $userName ); $restrictions = array( 'edit' => $protection, 'move' => $protection ); - $wgTitle = Title::newFromText( $this->getArg() ); - if ( !$wgTitle ) { + $t = Title::newFromText( $this->getArg() ); + if ( !$t ) { $this->error( "Invalid title", true ); } - $wgArticle = new Article( $wgTitle ); + $article = new Article( $t ); # un/protect the article $this->output( "Updating protection status... " ); - $success = $wgArticle->updateRestrictions($restrictions, $reason); + $success = $article->updateRestrictions( $restrictions, $reason ); if ( $success ) { $this->output( "done\n" ); } else { @@ -65,4 +66,4 @@ class Protect extends Maintenance { } $maintClass = "Protect"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/purgeList.php b/maintenance/purgeList.php index 7168a203..17be6d3d 100644 --- a/maintenance/purgeList.php +++ b/maintenance/purgeList.php @@ -20,29 +20,30 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +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"; + $this->addOption( 'purge', 'Whether to update page touched.' , false, false ); } public function execute() { $stdin = $this->getStdin(); $urls = array(); - while( !feof( $stdin ) ) { + while ( !feof( $stdin ) ) { $page = trim( fgets( $stdin ) ); if ( substr( $page, 0, 7 ) == 'http://' ) { $urls[] = $page; - } elseif( $page !== '' ) { + } elseif ( $page !== '' ) { $title = Title::newFromText( $page ); - if( $title ) { + if ( $title ) { $url = $title->getFullUrl(); $this->output( "$url\n" ); $urls[] = $url; - if( isset( $options['purge'] ) ) { + if ( $this->getOptions( 'purge' ) ) { $title->invalidateCache(); } } else { @@ -60,4 +61,4 @@ class PurgeList extends Maintenance { } $maintClass = "PurgeList"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/purgeOldText.inc b/maintenance/purgeOldText.inc index 0bd5f2eb..381d62a7 100644 --- a/maintenance/purgeOldText.inc +++ b/maintenance/purgeOldText.inc @@ -9,54 +9,54 @@ */ function PurgeRedundantText( $delete = false ) { - + # 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 echo( "Searching for active text records in revisions table..." ); $res = $dbw->query( "SELECT DISTINCT rev_text_id FROM $tbl_rev" ); - while( $row = $dbw->fetchObject( $res ) ) { + foreach ( $res as $row ) { $cur[] = $row->rev_text_id; } echo( "done.\n" ); - + # Get "active" text records from the archive table echo( "Searching for active text records in archive table..." ); $res = $dbw->query( "SELECT DISTINCT ar_text_id FROM $tbl_arc" ); - while( $row = $dbw->fetchObject( $res ) ) { + foreach ( $res as $row ) { $cur[] = $row->ar_text_id; } echo( "done.\n" ); - + # Get the IDs of all text records not in these sets echo( "Searching for inactive text records..." ); $set = implode( ', ', $cur ); $res = $dbw->query( "SELECT old_id FROM $tbl_txt WHERE old_id NOT IN ( $set )" ); $old = array(); - while( $row = $dbw->fetchObject( $res ) ) { + foreach ( $res as $row ) { $old[] = $row->old_id; } echo( "done.\n" ); - + # Inform the user of what we're going to do $count = count( $old ); echo( "$count inactive items found.\n" ); - + # Delete as appropriate - if( $delete && $count ) { + if ( $delete && $count ) { echo( "Deleting..." ); $set = implode( ', ', $old ); $dbw->query( "DELETE FROM $tbl_txt WHERE old_id IN ( $set )" ); echo( "done.\n" ); } - + # Done $dbw->commit(); - + } diff --git a/maintenance/purgeOldText.php b/maintenance/purgeOldText.php index 9621cb39..0cbc724e 100644 --- a/maintenance/purgeOldText.php +++ b/maintenance/purgeOldText.php @@ -21,7 +21,7 @@ * @author Rob Church <robchur@gmail.com> */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class PurgeOldText extends Maintenance { public function __construct() { @@ -29,11 +29,11 @@ class PurgeOldText extends Maintenance { $this->mDescription = "Purge old text records from the database"; $this->addOption( 'purge', 'Performs the deletion' ); } - + public function execute() { - $this->purgeRedundantText( $this->hasOption('purge') ); + $this->purgeRedundantText( $this->hasOption( 'purge' ) ); } } $maintClass = "PurgeOldText"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/reassignEdits.php b/maintenance/reassignEdits.php index e595e5b3..039422b3 100644 --- a/maintenance/reassignEdits.php +++ b/maintenance/reassignEdits.php @@ -22,7 +22,7 @@ * @licence GNU General Public Licence 2.0 or later */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class ReassignEdits extends Maintenance { public function __construct() { @@ -34,21 +34,22 @@ class ReassignEdits extends Maintenance { $this->addArg( 'from', 'Old user to take edits from' ); $this->addArg( 'to', 'New user to give edits to' ); } - + public function execute() { - if( $this->hasArg(0) && $this->hasArg(1) ) { + if ( $this->hasArg( 0 ) && $this->hasArg( 1 ) ) { # Set up the users involved - $from = $this->initialiseUser( $this->getArg(0) ); - $to = $this->initialiseUser( $this->getArg(1) ); - + $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') ) { + 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 ) + $report = $this->hasOption( 'report' ); + $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." ); @@ -83,7 +84,7 @@ class ReassignEdits extends Maintenance { $this->output( "found {$del}.\n" ); # Don't count recent changes if we're not supposed to - if( $rc ) { + 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 ); @@ -92,31 +93,34 @@ class ReassignEdits extends Maintenance { } else { $rec = 0; } - + $total = $cur + $del + $rec; $this->output( "\nTotal entries to change: {$total}\n" ); - - if( !$report ) { - if( $total ) { + + 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__ ); + $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__ ); + $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 ) { + 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__ ); + $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 (int)$total; } - + /** * Return the most efficient set of user conditions * i.e. a user => id mapping, or a user_text => text mapping @@ -129,7 +133,7 @@ class ReassignEdits extends Maintenance { 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 @@ -142,7 +146,7 @@ class ReassignEdits extends Maintenance { private function userSpecification( &$user, $idfield, $utfield ) { return array( $idfield => $user->getId(), $utfield => $user->getName() ); } - + /** * Initialise the user object * @@ -150,7 +154,7 @@ class ReassignEdits extends Maintenance { * @return User */ private function initialiseUser( $username ) { - if( User::isIP( $username ) ) { + if ( User::isIP( $username ) ) { $user = new User(); $user->setId( 0 ); $user->setName( $username ); @@ -165,5 +169,5 @@ class ReassignEdits extends Maintenance { } $maintClass = "ReassignEdits"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/rebuildFileCache.php b/maintenance/rebuildFileCache.php index 2a4e4884..a1aecd80 100644 --- a/maintenance/rebuildFileCache.php +++ b/maintenance/rebuildFileCache.php @@ -20,7 +20,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RebuildFileCache extends Maintenance { public function __construct() { @@ -32,24 +32,24 @@ class RebuildFileCache extends Maintenance { } public function execute() { - global $wgUseFileCache, $wgDisableCounters, $wgContentNamespaces; + global $wgUseFileCache, $wgDisableCounters, $wgContentNamespaces, $wgRequestTime; global $wgTitle, $wgArticle, $wgOut, $wgUser; - if( !$wgUseFileCache ) { + if ( !$wgUseFileCache ) { $this->error( "Nothing to do -- \$wgUseFileCache is disabled.", true ); } $wgDisableCounters = false; $start = $this->getArg( 0, "0" ); - if( !ctype_digit($start) ) { + if ( !ctype_digit( $start ) ) { $this->error( "Invalid value for start parameter.", true ); } - $start = intval($start); - $overwrite = $this->hasArg(1) && $this->getArg(1) === 'overwrite'; + $start = intval( $start ); + $overwrite = $this->hasArg( 1 ) && $this->getArg( 1 ) === 'overwrite'; $this->output( "Building content page file cache from page {$start}!\n" ); $dbr = wfGetDB( DB_SLAVE ); $start = $start > 0 ? $start : $dbr->selectField( 'page', 'MIN(page_id)', false, __FUNCTION__ ); $end = $dbr->selectField( 'page', 'MAX(page_id)', false, __FUNCTION__ ); - if( !$start ) { + if ( !$start ) { $this->error( "Nothing to do.", true ); } @@ -60,20 +60,21 @@ class RebuildFileCache extends Maintenance { $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 ) { + while ( $blockEnd <= $end ) { // Get the pages - $res = $dbr->select( 'page', array('page_namespace','page_title','page_id'), - array('page_namespace' => $wgContentNamespaces, + $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') + array( 'ORDER BY' => 'page_id ASC', 'USE INDEX' => 'PRIMARY' ) ); - foreach( $res as $row ) { + foreach ( $res as $row ) { $rebuilt = false; + $wgRequestTime = wfTime(); # bug 22852 $wgTitle = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); - if( null == $wgTitle ) { + if ( null == $wgTitle ) { $this->output( "Page {$row->page_id} has bad title\n" ); continue; // broken title? } @@ -81,24 +82,24 @@ class RebuildFileCache extends Maintenance { $wgUser->getSkin( $wgTitle ); // set skin title $wgArticle = new Article( $wgTitle ); // If the article is cacheable, then load it - if( $wgArticle->isFileCacheable() ) { + if ( $wgArticle->isFileCacheable() ) { $cache = new HTMLFileCache( $wgTitle ); - if( $cache->isFileCacheGood() ) { - if( $overwrite ) { + 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() + 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 ) + if ( $rebuilt ) $this->output( "Re-cached page {$row->page_id}\n" ); else $this->output( "Cached page {$row->page_id}\n" ); @@ -112,14 +113,14 @@ class RebuildFileCache extends Maintenance { wfWaitForSlaves( 5 ); } $this->output( "Done!\n" ); - + // Remove these to be safe - if( isset($wgTitle) ) - unset($wgTitle); - if( isset($wgArticle) ) - unset($wgArticle); + if ( isset( $wgTitle ) ) + unset( $wgTitle ); + if ( isset( $wgArticle ) ) + unset( $wgArticle ); } } $maintClass = "RebuildFileCache"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/rebuildImages.php b/maintenance/rebuildImages.php index 0d3bdb3f..1848b104 100644 --- a/maintenance/rebuildImages.php +++ b/maintenance/rebuildImages.php @@ -1,5 +1,5 @@ <?php -/* +/** * Script to update image metadata records * * Usage: php rebuildImages.php [--missing] [--dry-run] @@ -7,7 +7,7 @@ * --missing Crawl the uploads dir for images without records, and * add them only. * - * Copyright (C) 2005 Brion Vibber <brion@pobox.com> + * Copyright © 2005 Brion Vibber <brion@pobox.com> * http://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify @@ -27,23 +27,34 @@ * * @file * @author Brion Vibber <brion at pobox.com> - * @ingrouo maintenance + * @ingroup maintenance */ -$options = array( 'missing', 'dry-run' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); -require_once( dirname(__FILE__) . '/commandLine.inc' ); -require_once( 'FiveUpgrade.inc' ); +class ImageBuilder extends Maintenance { + function __construct() { + parent::__construct(); -class ImageBuilder extends FiveUpgrade { - function ImageBuilder( $dryrun = false ) { - parent::FiveUpgrade(); + $this->mDescription = 'Script to update image metadata records'; + $this->addOption( 'missing', 'Check for files without associated database record' ); + $this->addOption( 'dry-run', 'Only report, don\'t update the database' ); + } + + public function execute() { + $this->dbw = wfGetDB( DB_MASTER ); $this->maxLag = 10; # if slaves are lagged more than 10 secs, wait - $this->dryrun = $dryrun; - if ( $dryrun ) { + $this->dryrun = $this->hasOption( 'dry-run' ); + if ( $this->dryrun ) { $GLOBALS['wgReadOnly'] = 'Dry run mode, image upgrades are suppressed'; } + + if ( $this->hasOption( 'missing' ) ) { + $this->crawlMissing(); + } else { + $this->build(); + } } function getRepo() { @@ -69,7 +80,7 @@ class ImageBuilder extends FiveUpgrade { function progress( $updated ) { $this->updated += $updated; $this->processed++; - if( $this->processed % 100 != 0 ) { + if ( $this->processed % 100 != 0 ) { return; } $portion = $this->processed / $this->count; @@ -79,44 +90,40 @@ class ImageBuilder extends FiveUpgrade { $delta = $now - $this->startTime; $estimatedTotalTime = $delta / $portion; $eta = $this->startTime + $estimatedTotalTime; + $rate = $this->processed / $delta; - printf( "%s: %6.2f%% done on %s; ETA %s [%d/%d] %.2f/sec <%.2f%% updated>\n", + $this->output( sprintf( "%s: %6.2f%% done on %s; ETA %s [%d/%d] %.2f/sec <%.2f%% updated>\n", wfTimestamp( TS_DB, intval( $now ) ), $portion * 100.0, $this->table, wfTimestamp( TS_DB, intval( $eta ) ), - $completed, // $completed does not appear to be defined. + $this->processed, $this->count, - $rate, // $rate does not appear to be defined. - $updateRate * 100.0 ); + $rate, + $updateRate * 100.0 ) ); flush(); } function buildTable( $table, $key, $callback ) { - $fname = 'ImageBuilder::buildTable'; - - $count = $this->dbw->selectField( $table, 'count(*)', '', $fname ); + $count = $this->dbw->selectField( $table, 'count(*)', '', __METHOD__ ); $this->init( $count, $table ); - $this->log( "Processing $table..." ); + $this->output( "Processing $table...\n" ); - $tableName = $this->dbr->tableName( $table ); - $sql = "SELECT * FROM $tableName"; - $result = $this->dbr->query( $sql, $fname ); + $result = wfGetDB( DB_SLAVE )->select( $table, '*', array(), __METHOD__ ); - while( $row = $this->dbr->fetchObject( $result ) ) { + foreach ( $result as $row ) { $update = call_user_func( $callback, $row, null ); - if( $update ) { + if ( $update ) { $this->progress( 1 ); } else { $this->progress( 0 ); } } - $this->log( "Finished $table... $this->updated of $this->processed rows updated" ); - $this->dbr->freeResult( $result ); + $this->output( "Finished $table... $this->updated of $this->processed rows updated\n" ); } function buildImage() { - $callback = array( &$this, 'imageCallback' ); + $callback = array( $this, 'imageCallback' ); $this->buildTable( 'image', 'img_name', $callback ); } @@ -129,14 +136,14 @@ class ImageBuilder extends FiveUpgrade { function buildOldImage() { $this->buildTable( 'oldimage', 'oi_archive_name', - array( &$this, 'oldimageCallback' ) ); + array( $this, 'oldimageCallback' ) ); } function oldimageCallback( $row, $copy ) { // Create a File object from the row // This will also upgrade it if ( $row->oi_archive_name == '' ) { - $this->log( "Empty oi_archive_name for oi_name={$row->oi_name}" ); + $this->output( "Empty oi_archive_name for oi_name={$row->oi_name}\n" ); return false; } $file = $this->getRepo()->newFileFromRow( $row ); @@ -149,21 +156,20 @@ class ImageBuilder extends FiveUpgrade { } function checkMissingImage( $fullpath ) { - $fname = 'ImageBuilder::checkMissingImage'; $filename = wfBaseName( $fullpath ); - if( is_dir( $fullpath ) ) { + if ( is_dir( $fullpath ) ) { return; } - if( is_link( $fullpath ) ) { - $this->log( "skipping symlink at $fullpath" ); + if ( is_link( $fullpath ) ) { + $this->output( "skipping symlink at $fullpath\n" ); return; } $row = $this->dbw->selectRow( 'image', array( 'img_name' ), array( 'img_name' => $filename ), - $fname ); + __METHOD__ ); - if( $row ) { + if ( $row ) { // already known, move on return; } else { @@ -172,43 +178,35 @@ class ImageBuilder extends FiveUpgrade { } function addMissingImage( $filename, $fullpath ) { - $fname = 'ImageBuilder::addMissingImage'; - $timestamp = $this->dbw->timestamp( filemtime( $fullpath ) ); global $wgContLang; $altname = $wgContLang->checkTitleEncoding( $filename ); - if( $altname != $filename ) { - if( $this->dryrun ) { + if ( $altname != $filename ) { + if ( $this->dryrun ) { $filename = $altname; - $this->log( "Estimating transcoding... $altname" ); + $this->output( "Estimating transcoding... $altname\n" ); } else { $filename = $this->renameFile( $filename ); } } - if( $filename == '' ) { - $this->log( "Empty filename for $fullpath" ); + if ( $filename == '' ) { + $this->output( "Empty filename for $fullpath\n" ); return; } if ( !$this->dryrun ) { $file = wfLocalFile( $filename ); - if ( !$file->recordUpload( '', '(recovered file, missing upload log entry)', '', '', '', + if ( !$file->recordUpload( '', '(recovered file, missing upload log entry)', '', '', '', false, $timestamp ) ) { - $this->log( "Error uploading file $fullpath" ); + $this->output( "Error uploading file $fullpath\n" ); return; } } - $this->log( $fullpath ); + $this->output( $fullpath . "\n" ); } } -$builder = new ImageBuilder( isset( $options['dry-run'] ) ); -if( isset( $options['missing'] ) ) { - $builder->crawlMissing(); -} else { - $builder->build(); -} - - +$maintClass = 'ImageBuilder'; +require( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/rebuildInterwiki.inc b/maintenance/rebuildInterwiki.inc deleted file mode 100644 index 93261f86..00000000 --- a/maintenance/rebuildInterwiki.inc +++ /dev/null @@ -1,259 +0,0 @@ -<?php -/** - * Rebuild interwiki table using the file on meta and the language list - * Wikimedia specific! - * - * @file - * @todo document - * @ingroup Maintenance - * @ingroup Wikimedia - */ - -/** - * @todo document - * @ingroup Maintenance - */ -class Site { - var $suffix, $lateral, $url; - - function __construct( $s, $l, $u ) { - $this->suffix = $s; - $this->lateral = $l; - $this->url = $u; - } - - function getURL( $lang ) { - $xlang = str_replace( '_', '-', $lang ); - return "http://$xlang.{$this->url}/wiki/\$1"; - } -} - -function makeInterwikiSQL( $destDir ) { - global $langlist, $languageAliases, $prefixRewrites; - - # Multi-language sites - # db suffix => db suffix, iw prefix, hostname - $sites = array( - 'wiki' => new Site( 'wiki', 'w', 'wikipedia.org' ), - 'wiktionary' => new Site( 'wiktionary', 'wikt', 'wiktionary.org' ), - 'wikiquote' => new Site( 'wikiquote', 'q', 'wikiquote.org' ), - 'wikibooks' => new Site( 'wikibooks', 'b', 'wikibooks.org' ), - 'wikinews' => new Site( 'wikinews', 'n', 'wikinews.org' ), - 'wikisource' => new Site( 'wikisource', 's', 'wikisource.org' ), - 'wikimedia' => new Site( 'wikimedia', 'chapter', 'wikimedia.org' ), - 'wikiversity' => new Site( 'wikiversity', 'v', 'wikiversity.org' ), - ); - - # List of language prefixes likely to be found in multi-language sites - $langlist = array_map( "trim", file( "/home/wikipedia/common/langlist" ) ); - - # List of all database names - $dblist = array_map( "trim", file( "/home/wikipedia/common/all.dblist" ) ); - - # Special-case hostnames - $specials = array( - 'sourceswiki' => 'sources.wikipedia.org', - 'quotewiki' => 'wikiquote.org', - 'textbookwiki' => 'wikibooks.org', - 'sep11wiki' => 'sep11.wikipedia.org', - 'metawiki' => 'meta.wikimedia.org', - 'commonswiki' => 'commons.wikimedia.org', - 'specieswiki' => 'species.wikimedia.org', - ); - - # Extra interwiki links that can't be in the intermap for some reason - $extraLinks = array( - array( 'm', 'http://meta.wikimedia.org/wiki/$1', 1 ), - array( 'meta', 'http://meta.wikimedia.org/wiki/$1', 1 ), - array( 'sep11', 'http://sep11.wikipedia.org/wiki/$1', 1 ), - ); - - # Language aliases, usually configured as redirects to the real wiki in apache - # Interlanguage links are made directly to the real wiki - # Something horrible happens if you forget to list an alias here, I can't - # remember what - $languageAliases = array( - 'zh-cn' => 'zh', - 'zh-tw' => 'zh', - 'dk' => 'da', - 'nb' => 'no', - ); - - # Special case prefix rewrites, for the benefit of Swedish which uses s:t - # as an abbreviation for saint - $prefixRewrites = array( - 'svwiki' => array( 's' => 'src' ), - ); - - # Construct a list of reserved prefixes - $reserved = array(); - foreach ( $langlist as $lang ) { - $reserved[$lang] = 1; - } - foreach ( $languageAliases as $alias => $lang ) { - $reserved[$alias] = 1; - } - foreach( $sites as $site ) { - $reserved[$site->lateral] = 1; - } - - # Extract the intermap from meta - $intermap = Http::get( 'http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw', 30 ); - $lines = array_map( 'trim', explode( "\n", trim( $intermap ) ) ); - - if ( !$lines || count( $lines ) < 2 ) { - wfDie( "m:Interwiki_map not found" ); - } - - $iwArray = array(); - - foreach ( $lines as $line ) { - $matches = array(); - if ( preg_match( '/^\|\s*(.*?)\s*\|\|\s*(https?:\/\/.*?)\s*$/', $line, $matches ) ) { - $prefix = strtolower( $matches[1] ); - $url = $matches[2]; - if ( preg_match( '/(wikipedia|wiktionary|wikisource|wikiquote|wikibooks|wikimedia)\.org/', $url ) ) { - $local = 1; - } else { - $local = 0; - } - - if ( empty( $reserved[$prefix] ) ) { - $iwArray[$prefix] = array( "iw_prefix" => $prefix, "iw_url" => $url, "iw_local" => $local ); - } - } - } - - - - foreach ( $dblist as $db ) { - $sql = "-- Generated by rebuildInterwiki.php"; - if ( isset( $specials[$db] ) ) { - # Special wiki - # Has interwiki links and interlanguage links to wikipedia - - $host = $specials[$db]; - $sql .= "\n--$host\n\n"; - $sql .= "USE $db;\n" . - "TRUNCATE TABLE interwiki;\n" . - "INSERT INTO interwiki (iw_prefix, iw_url, iw_local) VALUES \n"; - $first = true; - - # Intermap links - foreach ( $iwArray as $iwEntry ) { - $sql .= makeLink( $iwEntry, $first, $db ); - } - - # Links to multilanguage sites - foreach ( $sites as $targetSite ) { - $sql .= makeLink( array( $targetSite->lateral, $targetSite->getURL( 'en' ), 1 ), $first, $db ); - } - - # Interlanguage links to wikipedia - $sql .= makeLanguageLinks( $sites['wiki'], $first, $db ); - - # Extra links - foreach ( $extraLinks as $link ) { - $sql .= makeLink( $link, $first, $db ); - } - - $sql .= ";\n"; - } else { - # Find out which site this DB belongs to - $site = false; - foreach( $sites as $candidateSite ) { - $suffix = $candidateSite->suffix; - if ( preg_match( "/(.*)$suffix$/", $db, $matches ) ) { - $site = $candidateSite; - break; - } - } - if ( !$site ) { - print "Invalid database $db\n"; - continue; - } - $lang = $matches[1]; - $host = "$lang." . $site->url; - $sql .= "\n--$host\n\n"; - - $sql .= "USE $db;\n" . - "TRUNCATE TABLE interwiki;\n" . - "INSERT INTO interwiki (iw_prefix,iw_url,iw_local) VALUES\n"; - $first = true; - - # Intermap links - foreach ( $iwArray as $iwEntry ) { - # Suppress links with the same name as the site - if ( ( $suffix == 'wiki' && $iwEntry['iw_prefix'] != 'wikipedia' ) || - ( $suffix != 'wiki' && $suffix != $iwEntry['iw_prefix'] ) ) - { - $sql .= makeLink( $iwEntry, $first, $db ); - } - } - - # Lateral links - foreach ( $sites as $targetSite ) { - # Suppress link to self - if ( $targetSite->suffix != $site->suffix ) { - $sql .= makeLink( array( $targetSite->lateral, $targetSite->getURL( $lang ), 1 ), $first, $db ); - } - } - - # Interlanguage links - $sql .= makeLanguageLinks( $site, $first, $db ); - - # w link within wikipedias - # Other sites already have it as a lateral link - if ( $site->suffix == "wiki" ) { - $sql .= makeLink( array("w", "http://en.wikipedia.org/wiki/$1", 1), $first, $db ); - } - - # Extra links - foreach ( $extraLinks as $link ){ - $sql .= makeLink( $link, $first, $db ); - } - $sql .= ";\n"; - } - file_put_contents( "$destDir/$db.sql", $sql ); - } -} - -# ------------------------------------------------------------------------------------------ - -# Returns part of an INSERT statement, corresponding to all interlanguage links to a particular site -function makeLanguageLinks( &$site, &$first, $source ) { - global $langlist, $languageAliases; - - $sql = ""; - - # Actual languages with their own databases - foreach ( $langlist as $targetLang ) { - $sql .= makeLink( array( $targetLang, $site->getURL( $targetLang ), 1 ), $first, $source ); - } - - # Language aliases - foreach ( $languageAliases as $alias => $lang ) { - $sql .= makeLink( array( $alias, $site->getURL( $lang ), 1 ), $first, $source ); - } - return $sql; -} - -# Make SQL for a single link from an array -function makeLink( $entry, &$first, $source ) { - global $prefixRewrites; - - if ( isset( $prefixRewrites[$source] ) && isset( $prefixRewrites[$source][$entry[0]] ) ) { - $entry[0] = $prefixRewrites[$source][$entry[0]]; - } - - $sql = ""; - # Add comma - if ( $first ) { - $first = false; - } else { - $sql .= ",\n"; - } - $dbr = wfGetDB( DB_SLAVE ); - $sql .= "(" . $dbr->makeList( $entry ) . ")"; - return $sql; -} diff --git a/maintenance/rebuildInterwiki.php b/maintenance/rebuildInterwiki.php index d3f3a4d2..3da920f8 100644 --- a/maintenance/rebuildInterwiki.php +++ b/maintenance/rebuildInterwiki.php @@ -9,21 +9,269 @@ * @ingroup Wikimedia */ -/** */ -$oldCwd = getcwd(); - -$optionsWithArgs = array( "d" ); -require_once( dirname(__FILE__) . '/commandLine.inc' ); -require( "rebuildInterwiki.inc" ); -chdir( $oldCwd ); - -# Output -if ( isset( $options['d'] ) ) { - $destDir = $options['d']; -} else { - $destDir = '/home/wikipedia/conf/interwiki/sql'; +/** + * @todo document + * @ingroup Maintenance + */ +class Site { + var $suffix, $lateral, $url; + + function __construct( $s, $l, $u ) { + $this->suffix = $s; + $this->lateral = $l; + $this->url = $u; + } + + function getURL( $lang ) { + $xlang = str_replace( '_', '-', $lang ); + return "http://$xlang.{$this->url}/wiki/\$1"; + } +} + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); + +class RebuildInterwiki extends Maintenance { + public function __construct() { + parent::__construct(); + $this->mDescription = "Rebuild the interwiki table using the file on meta and the language list."; + $this->addOption( 'langlist', 'File with one language code per line', false, true ); + $this->addOption( 'dblist', 'File with one db per line', false, true ); + $this->addOption( 'd', 'Output folder', false, true ); + } + + function execute() { + # List of language prefixes likely to be found in multi-language sites + $this->langlist = array_map( "trim", file( $this->getOption( 'langlist', "/home/wikipedia/common/langlist" ) ) ); + + # List of all database names + $this->dblist = array_map( "trim", file( $this->getOption( 'dblist', "/home/wikipedia/common/all.dblist" ) ) ); + + # Special-case databases + //$this->specials = array_flip( array_map( "trim", file( $this->getOption( 'specialdbs', "/home/wikipedia/common/special.dblist" ) ) ) ); + + $this->makeInterwikiSQL( $this->getOption( 'd', '/home/wikipedia/conf/interwiki/sql' ) ); + } + + function makeInterwikiSQL( $destDir ) { + $this->output( "Making new interwiki SQL files in $destDir\n" ); + + # Multi-language sites + # db suffix => db suffix, iw prefix, hostname + $sites = array( + 'wiki' => new Site( 'wiki', 'w', 'wikipedia.org' ), + 'wiktionary' => new Site( 'wiktionary', 'wikt', 'wiktionary.org' ), + 'wikiquote' => new Site( 'wikiquote', 'q', 'wikiquote.org' ), + 'wikibooks' => new Site( 'wikibooks', 'b', 'wikibooks.org' ), + 'wikinews' => new Site( 'wikinews', 'n', 'wikinews.org' ), + 'wikisource' => new Site( 'wikisource', 's', 'wikisource.org' ), + 'wikimedia' => new Site( 'wikimedia', 'chapter', 'wikimedia.org' ), + 'wikiversity' => new Site( 'wikiversity', 'v', 'wikiversity.org' ), + ); + + # Special-case hostnames + $this->specials = array( + 'sourceswiki' => 'sources.wikipedia.org', + 'quotewiki' => 'wikiquote.org', + 'textbookwiki' => 'wikibooks.org', + 'sep11wiki' => 'sep11.wikipedia.org', + 'metawiki' => 'meta.wikimedia.org', + 'commonswiki' => 'commons.wikimedia.org', + 'specieswiki' => 'species.wikimedia.org', + ); + + # Extra interwiki links that can't be in the intermap for some reason + $extraLinks = array( + array( 'm', 'http://meta.wikimedia.org/wiki/$1', 1 ), + array( 'meta', 'http://meta.wikimedia.org/wiki/$1', 1 ), + array( 'sep11', 'http://sep11.wikipedia.org/wiki/$1', 1 ), + ); + + # Language aliases, usually configured as redirects to the real wiki in apache + # Interlanguage links are made directly to the real wiki + # Something horrible happens if you forget to list an alias here, I can't + # remember what + $this->languageAliases = array( + 'zh-cn' => 'zh', + 'zh-tw' => 'zh', + 'dk' => 'da', + 'nb' => 'no', + ); + + # Special case prefix rewrites, for the benefit of Swedish which uses s:t + # as an abbreviation for saint + $this->prefixRewrites = array( + 'svwiki' => array( 's' => 'src' ), + ); + + # Construct a list of reserved prefixes + $reserved = array(); + foreach ( $this->langlist as $lang ) { + $reserved[$lang] = 1; + } + foreach ( $this->languageAliases as $alias => $lang ) { + $reserved[$alias] = 1; + } + foreach ( $sites as $site ) { + $reserved[$site->lateral] = 1; + } + + # Extract the intermap from meta + $intermap = Http::get( 'http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw', 30 ); + $lines = array_map( 'trim', explode( "\n", trim( $intermap ) ) ); + + if ( !$lines || count( $lines ) < 2 ) { + $this->error( "m:Interwiki_map not found", true ); + } + + $iwArray = array(); + + foreach ( $lines as $line ) { + $matches = array(); + if ( preg_match( '/^\|\s*(.*?)\s*\|\|\s*(https?:\/\/.*?)\s*$/', $line, $matches ) ) { + $prefix = strtolower( $matches[1] ); + $url = $matches[2]; + if ( preg_match( '/(wikipedia|wiktionary|wikisource|wikiquote|wikibooks|wikimedia)\.org/', $url ) ) { + $local = 1; + } else { + $local = 0; + } + + if ( empty( $reserved[$prefix] ) ) { + $iwArray[$prefix] = array( "iw_prefix" => $prefix, "iw_url" => $url, "iw_local" => $local ); + } + } + } + + foreach ( $this->dblist as $db ) { + $sql = "-- Generated by rebuildInterwiki.php"; + if ( isset( $this->specials[$db] ) ) { + # Special wiki + # Has interwiki links and interlanguage links to wikipedia + + $host = $this->specials[$db]; + $sql .= "\n--$host\n\n"; + $sql .= "USE $db;\n" . + "TRUNCATE TABLE interwiki;\n" . + "INSERT INTO interwiki (iw_prefix, iw_url, iw_local) VALUES \n"; + $first = true; + + # Intermap links + foreach ( $iwArray as $iwEntry ) { + $sql .= $this->makeLink( $iwEntry, $first, $db ); + } + + # Links to multilanguage sites + foreach ( $sites as $targetSite ) { + $sql .= $this->makeLink( array( $targetSite->lateral, $targetSite->getURL( 'en' ), 1 ), $first, $db ); + } + + # Interlanguage links to wikipedia + $sql .= $this->makeLanguageLinks( $sites['wiki'], $first, $db ); + + # Extra links + foreach ( $extraLinks as $link ) { + $sql .= $this->makeLink( $link, $first, $db ); + } + + $sql .= ";\n"; + } else { + # Find out which site this DB belongs to + $site = false; + foreach ( $sites as $candidateSite ) { + $suffix = $candidateSite->suffix; + if ( preg_match( "/(.*)$suffix$/", $db, $matches ) ) { + $site = $candidateSite; + break; + } + } + if ( !$site ) { + print "Invalid database $db\n"; + continue; + } + $lang = $matches[1]; + $host = "$lang." . $site->url; + $sql .= "\n--$host\n\n"; + + $sql .= "USE $db;\n" . + "TRUNCATE TABLE interwiki;\n" . + "INSERT INTO interwiki (iw_prefix,iw_url,iw_local) VALUES\n"; + $first = true; + + # Intermap links + foreach ( $iwArray as $iwEntry ) { + # Suppress links with the same name as the site + if ( ( $suffix == 'wiki' && $iwEntry['iw_prefix'] != 'wikipedia' ) || + ( $suffix != 'wiki' && $suffix != $iwEntry['iw_prefix'] ) ) + { + $sql .= $this->makeLink( $iwEntry, $first, $db ); + } + } + + # Lateral links + foreach ( $sites as $targetSite ) { + # Suppress link to self + if ( $targetSite->suffix != $site->suffix ) { + $sql .= $this->makeLink( array( $targetSite->lateral, $targetSite->getURL( $lang ), 1 ), $first, $db ); + } + } + + # Interlanguage links + $sql .= $this->makeLanguageLinks( $site, $first, $db ); + + # w link within wikipedias + # Other sites already have it as a lateral link + if ( $site->suffix == "wiki" ) { + $sql .= $this->makeLink( array( "w", "http://en.wikipedia.org/wiki/$1", 1 ), $first, $db ); + } + + # Extra links + foreach ( $extraLinks as $link ) { + $sql .= $this->makeLink( $link, $first, $db ); + } + $sql .= ";\n"; + } + file_put_contents( "$destDir/$db.sql", $sql ); + } + } + + # ------------------------------------------------------------------------------------------ + + # Returns part of an INSERT statement, corresponding to all interlanguage links to a particular site + function makeLanguageLinks( &$site, &$first, $source ) { + $sql = ""; + + # Actual languages with their own databases + foreach ( $this->langlist as $targetLang ) { + $sql .= $this->makeLink( array( $targetLang, $site->getURL( $targetLang ), 1 ), $first, $source ); + } + + # Language aliases + foreach ( $this->languageAliases as $alias => $lang ) { + $sql .= $this->makeLink( array( $alias, $site->getURL( $lang ), 1 ), $first, $source ); + } + return $sql; + } + + # Make SQL for a single link from an array + function makeLink( $entry, &$first, $source ) { + + if ( isset( $this->prefixRewrites[$source] ) && isset($entry[0]) && isset( $this->prefixRewrites[$source][$entry[0]] ) ) { + $entry[0] = $this->prefixRewrites[$source][$entry[0]]; + } + + $sql = ""; + # Add comma + if ( $first ) { + $first = false; + } else { + $sql .= ",\n"; + } + $dbr = wfGetDB( DB_SLAVE ); + $sql .= "(" . $dbr->makeList( $entry ) . ")"; + return $sql; + } } -echo "Making new interwiki SQL files in $destDir\n"; -makeInterwikiSQL( $destDir ); +$maintClass = "RebuildInterwiki"; +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/rebuildLocalisationCache.php b/maintenance/rebuildLocalisationCache.php index 1c517415..0ca99610 100644 --- a/maintenance/rebuildLocalisationCache.php +++ b/maintenance/rebuildLocalisationCache.php @@ -28,7 +28,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RebuildLocalisationCache extends Maintenance { public function __construct() { @@ -37,7 +37,7 @@ class RebuildLocalisationCache extends Maintenance { $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'; } @@ -45,17 +45,17 @@ class RebuildLocalisationCache extends Maintenance { public function execute() { global $wgLocalisationCacheConf; - $force = $this->hasOption('force'); + $force = $this->hasOption( 'force' ); $threads = $this->getOption( 'threads', 1 ); - if( $threads < 1 || $threads != intval( $threads ) ) { + if ( $threads < 1 || $threads != intval( $threads ) ) { $this->output( "Invalid thread count specified; running single-threaded.\n" ); $threads = 1; } - if( $threads > 1 && wfIsWindows() ) { + 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' ) ) { + if ( $threads > 1 && !function_exists( 'pcntl_fork' ) ) { $this->output( "PHP pcntl extension is not present; running single-threaded.\n" ); $threads = 1; } @@ -72,8 +72,8 @@ class RebuildLocalisationCache extends Maintenance { // Initialise and split into chunks $numRebuilt = 0; - $total = count($codes); - $chunks = array_chunk( $codes, ceil(count($codes)/$threads) ); + $total = count( $codes ); + $chunks = array_chunk( $codes, ceil( count( $codes ) / $threads ) ); $pids = array(); foreach ( $chunks as $codes ) { // Do not fork for only one thread @@ -82,11 +82,11 @@ class RebuildLocalisationCache extends Maintenance { if ( $pid === 0 ) { // Child, reseed because there is no bug in PHP: // http://bugs.php.net/bug.php?id=42465 - mt_srand(getmypid()); + mt_srand( getmypid() ); $numRebuilt = $this->doRebuild( $codes, $lc, $force ); // Abuse the exit value for the count of rebuild languages - exit($numRebuilt); - } elseif ($pid === -1) { + exit( $numRebuilt ); + } elseif ( $pid === -1 ) { // Fork failed or one thread, do it serialized $numRebuilt += $this->doRebuild( $codes, $lc, $force ); } else { @@ -97,9 +97,9 @@ class RebuildLocalisationCache extends Maintenance { // Wait for all children foreach ( $pids as $pid ) { $status = 0; - pcntl_waitpid($pid, $status); + pcntl_waitpid( $pid, $status ); // Fetch the count from the return value - $numRebuilt += pcntl_wexitstatus($status); + $numRebuilt += pcntl_wexitstatus( $status ); } $this->output( "$numRebuilt languages rebuilt out of $total\n" ); @@ -130,4 +130,4 @@ class RebuildLocalisationCache extends Maintenance { } $maintClass = "RebuildLocalisationCache"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/rebuildall.php b/maintenance/rebuildall.php index a2c1be93..82619048 100644 --- a/maintenance/rebuildall.php +++ b/maintenance/rebuildall.php @@ -21,7 +21,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RebuildAll extends Maintenance { public function __construct() { @@ -47,10 +47,10 @@ class RebuildAll extends Maintenance { $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" ); } } $maintClass = "RebuildAll"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/rebuildmessages.php b/maintenance/rebuildmessages.php index 546d5214..de37da7e 100644 --- a/maintenance/rebuildmessages.php +++ b/maintenance/rebuildmessages.php @@ -20,7 +20,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RebuildMessages extends Maintenance { public function __construct() { @@ -30,16 +30,16 @@ class RebuildMessages extends Maintenance { public function execute() { global $wgLocalDatabases, $wgDBname, $wgEnableSidebarCache, $messageMemc; - if( $wgLocalDatabases ) { + if ( $wgLocalDatabases ) { $databases = $wgLocalDatabases; } else { $databases = array( $wgDBname ); } - - foreach( $databases as $db ) { + + foreach ( $databases as $db ) { $this->output( "Deleting message cache for {$db}... " ); $messageMemc->delete( "{$db}:messages" ); - if( $wgEnableSidebarCache ) + if ( $wgEnableSidebarCache ) $messageMemc->delete( "{$db}:sidebar" ); $this->output( "Deleted\n" ); } @@ -47,4 +47,4 @@ class RebuildMessages extends Maintenance { } $maintClass = "RebuildMessages"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/rebuildrecentchanges.php b/maintenance/rebuildrecentchanges.php index 6c76dc59..8964d1a4 100644 --- a/maintenance/rebuildrecentchanges.php +++ b/maintenance/rebuildrecentchanges.php @@ -22,7 +22,7 @@ * @todo Document */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RebuildRecentchanges extends Maintenance { public function __construct() { @@ -31,8 +31,6 @@ class RebuildRecentchanges extends Maintenance { } public function execute() { - global $wgTitle; - $wgTitle = Title::newFromText( "Rebuild recent changes script" ); $this->rebuildRecentChangesTablePass1(); $this->rebuildRecentChangesTablePass2(); $this->rebuildRecentChangesTablePass3(); @@ -45,24 +43,23 @@ class RebuildRecentchanges extends Maintenance { * Rebuild pass 1 * DOCUMENT ME! */ - function rebuildRecentChangesTablePass1() - { + private 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 ) { + if ( intval( $days ) == $days ) { $this->output( " (" . $days . " days)\n" ); } else { - $this->output( " (approx. " . intval($days) . " days)\n" ); + $this->output( " (approx. " . intval( $days ) . " days)\n" ); } - + $cutoff = time() - $wgRCMaxAge; $dbw->insertSelect( 'recentchanges', array( 'page', 'revision' ), array( @@ -96,57 +93,65 @@ class RebuildRecentchanges extends Maintenance { */ private function rebuildRecentChangesTablePass2() { $dbw = wfGetDB( DB_MASTER ); - list ($recentchanges, $revision) = $dbw->tableNamesN( 'recentchanges', 'revision' ); - + 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 ) { + 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} ". + "WHERE rev_page={$lastCurId} " . "AND rev_timestamp<'{$emit}' ORDER BY rev_timestamp DESC"; - $sql2 = $dbw->limitResult($sql2, 1, false); + $sql2 = $dbw->limitResult( $sql2, 1, false ); $res2 = $dbw->query( $sql2 ); - if( $row = $dbw->fetchObject( $res2 ) ) { - $lastOldId = intval($row->rev_id); + $row = $dbw->fetchObject( $res2 ); + if ( $row ) { + $lastOldId = intval( $row->rev_id ); # Grab the last text size if available - $lastSize = !is_null($row->rev_len) ? intval($row->rev_len) : 'NULL'; + $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 ) { + 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 ); - + $size = $dbw->selectField( 'revision', 'rev_len', array( 'rev_id' => $obj->rc_this_oldid ) ); + $size = !is_null( $size ) ? intval( $size ) : 'NULL'; + + $dbw->update( 'recentchanges', + array( + 'rc_last_oldid' => $lastOldId, + 'rc_new' => $new, + 'rc_type' => $new, + 'rc_old_len' => $lastSize, + 'rc_new_len' => $size, + ), array( + 'rc_cur_id' => $lastCurId, + 'rc_this_oldid' => $obj->rc_this_oldid, + ), + __METHOD__ + ); + $lastOldId = intval( $obj->rc_this_oldid ); $lastSize = $size; } } - $dbw->freeResult( $res ); } /** @@ -155,22 +160,22 @@ class RebuildRecentchanges extends Maintenance { */ 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 ) { + foreach ( $basicRCLogs as $logtype ) { $safetype = $dbw->strencode( $logtype ); $selectLogs[] = "'$safetype'"; } - + $cutoff = time() - $wgRCMaxAge; - list($logging, $page) = $dbw->tableNamesN( 'logging', 'page' ); + 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', @@ -196,7 +201,7 @@ class RebuildRecentchanges extends Maintenance { ), array( 'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $cutoff ) ), 'log_user=user_id', - 'log_type IN(' . implode(',',$selectLogs) . ')' + 'log_type IN(' . implode( ',', $selectLogs ) . ')' ), __METHOD__, array(), // INSERT options array( 'ORDER BY' => 'log_timestamp DESC', 'LIMIT' => 5000 ) // SELECT options @@ -209,38 +214,38 @@ class RebuildRecentchanges extends Maintenance { */ private function rebuildRecentChangesTablePass4() { global $wgGroupPermissions, $wgUseRCPatrol; - + $dbw = wfGetDB( DB_MASTER ); - - list($recentchanges,$usergroups,$user) = $dbw->tableNamesN( 'recentchanges', 'user_groups', 'user' ); - + + list( $recentchanges, $usergroups, $user ) = $dbw->tableNamesN( 'recentchanges', 'user_groups', 'user' ); + $botgroups = $autopatrolgroups = array(); - foreach( $wgGroupPermissions as $group => $rights ) { - if( isset( $rights['bot'] ) && $rights['bot'] == true ) { + foreach ( $wgGroupPermissions as $group => $rights ) { + if ( isset( $rights['bot'] ) && $rights['bot'] ) { $botgroups[] = $dbw->addQuotes( $group ); } - if( $wgUseRCPatrol && isset( $rights['autopatrol'] ) && $rights['autopatrol'] == true ) { + if ( $wgUseRCPatrol && isset( $rights['autopatrol'] ) && $rights['autopatrol'] ) { $autopatrolgroups[] = $dbw->addQuotes( $group ); } } # Flag our recent bot edits - if( !empty($botgroups) ) { - $botwhere = implode(',',$botgroups); + 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 ) { + + foreach ( $res as $obj ) { $botusers[] = $dbw->addQuotes( $obj->user_name ); } # Fill in the rc_bot field - if( !empty($botusers) ) { - $botwhere = implode(',',$botusers); + if ( !empty( $botusers ) ) { + $botwhere = implode( ',', $botusers ); $sql2 = "UPDATE $recentchanges SET rc_bot=1 " . "WHERE rc_user_text IN($botwhere)"; $dbw->query( $sql2 ); @@ -248,31 +253,29 @@ class RebuildRecentchanges extends Maintenance { } global $wgMiserMode; # Flag our recent autopatrolled edits - if( !$wgMiserMode && !empty($autopatrolgroups) ) { - $patrolwhere = implode(',',$autopatrolgroups); + 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 ) { + + foreach ( $res as $obj ) { $patrolusers[] = $dbw->addQuotes( $obj->user_name ); } - + # Fill in the rc_patrolled field - if( !empty($patrolusers) ) { - $patrolwhere = implode(',',$patrolusers); + 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 ); } /** @@ -283,7 +286,7 @@ class RebuildRecentchanges extends Maintenance { $this->output( "Deleting feed timestamps.\n" ); - foreach( $wgFeedClasses as $feed => $className ) { + foreach ( $wgFeedClasses as $feed => $className ) { $messageMemc->delete( wfMemcKey( 'rcfeed', $feed, 'timestamp' ) ); # Good enough for now. } } @@ -291,4 +294,4 @@ class RebuildRecentchanges extends Maintenance { } $maintClass = "RebuildRecentchanges"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/rebuildtextindex.php b/maintenance/rebuildtextindex.php index 4521c6f5..46282c18 100644 --- a/maintenance/rebuildtextindex.php +++ b/maintenance/rebuildtextindex.php @@ -24,10 +24,10 @@ * @todo document */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RebuildTextIndex extends Maintenance { - const RTI_CHUNK_SIZE = 500; + const RTI_CHUNK_SIZE = 500; private $db; public function __construct() { @@ -46,11 +46,20 @@ class RebuildTextIndex extends Maintenance { if ( $wgDBtype == 'postgres' ) { $this->error( "This script is not needed when using Postgres.\n", true ); } - + $this->db = wfGetDB( DB_MASTER ); + if ( $this->db->getType() == 'sqlite' ) { + if ( !DatabaseSqlite::getFulltextSearchModule() ) { + $this->error( "Your version of SQLite module for PHP doesn't support full-text search (FTS3).\n", true ); + } + if ( !$this->db->checkForEnabledSearch() ) { + $this->error( "Your database schema is not configured for full-text search support. Run update.php.\n", true ); + } + } + $wgTitle = Title::newFromText( "Rebuild text index script" ); - - if ( $wgDBtype == 'mysql' ) { + + if ( $this->db->getType() == 'mysql' ) { $this->dropMysqlTextIndex(); $this->populateSearchIndex(); $this->createMysqlTextIndex(); @@ -58,7 +67,7 @@ class RebuildTextIndex extends Maintenance { $this->clearSearchIndex(); $this->populateSearchIndex(); } - + $this->output( "Done.\n" ); } @@ -67,27 +76,28 @@ class RebuildTextIndex extends Maintenance { */ protected function populateSearchIndex() { $res = $this->db->select( 'page', 'MAX(page_id) AS count' ); - $s = $this->db->fetchObject($res); + $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" ); + if ( $n ) { + $this->output( $n . "\n" ); + } $end = $n + self::RTI_CHUNK_SIZE - 1; - $res = $this->db->select( array( 'page', 'revision', 'text' ), + $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 ) { + + 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; } } @@ -100,7 +110,7 @@ class RebuildTextIndex extends Maintenance { 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__ ); + $this->db->query( $sql, __METHOD__ ); } } @@ -126,4 +136,4 @@ class RebuildTextIndex extends Maintenance { } $maintClass = "RebuildTextIndex"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/refreshImageCount.php b/maintenance/refreshImageCount.php index 44794cc7..f9bdeea7 100644 --- a/maintenance/refreshImageCount.php +++ b/maintenance/refreshImageCount.php @@ -21,14 +21,14 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +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 ); @@ -41,7 +41,7 @@ class RefreshImageCount extends Maintenance { $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 ), @@ -50,5 +50,5 @@ class RefreshImageCount extends Maintenance { } $maintClass = "RefreshImageCount"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/refreshLinks.php b/maintenance/refreshLinks.php index 863dd022..144e96c5 100644 --- a/maintenance/refreshLinks.php +++ b/maintenance/refreshLinks.php @@ -18,7 +18,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RefreshLinks extends Maintenance { public function __construct() { @@ -35,10 +35,10 @@ class RefreshLinks extends Maintenance { } public function execute() { - if( !$this->hasOption( 'dfn-only' ) ) { + $max = $this->getOption( 'm', 0 ); + 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 ); @@ -56,7 +56,7 @@ class RefreshLinks extends Maintenance { * @param $redirectsOnly bool Only fix redirects * @param $oldRedirectsOnly bool Only fix redirects without redirect entries */ - private function doRefreshLinks( $start, $newOnly = false, $maxLag = false, + private function doRefreshLinks( $start, $newOnly = false, $maxLag = false, $end = 0, $redirectsOnly = false, $oldRedirectsOnly = false ) { global $wgUser, $wgParser, $wgUseTidy; @@ -65,10 +65,10 @@ class RefreshLinks extends Maintenance { $start = intval( $start ); # Don't generate TeX PNGs (lack of a sensible current directory causes errors anyway) - $wgUser->setOption('math', MW_MATH_SOURCE); + $wgUser->setOption( 'math', MW_MATH_SOURCE ); # Don't generate extension images (e.g. Timeline) - if( method_exists( $wgParser, "clearTagHooks" ) ) { + if ( method_exists( $wgParser, "clearTagHooks" ) ) { $wgParser->clearTagHooks(); } @@ -77,28 +77,29 @@ class RefreshLinks extends Maintenance { $what = $redirectsOnly ? "redirects" : "links"; - if( $oldRedirectsOnly ) { + 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"), + "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 ) { + $i = 0; + foreach ( $res as $row ) { if ( !( ++$i % $reportingInterval ) ) { $this->output( "$i\n" ); wfWaitForSlaves( $maxLag ); } $this->fixRedirect( $row->page_id ); } - } elseif( $newOnly ) { + } elseif ( $newOnly ) { $this->output( "Refreshing $what from " ); $res = $dbr->select( 'page', array( 'page_id' ), @@ -109,17 +110,17 @@ class RefreshLinks extends Maintenance { ); $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) + if ( $redirectsOnly ) $this->fixRedirect( $row->page_id ); else - $this->fixLinksFromArticle( $row->page_id ); + self::fixLinksFromArticle( $row->page_id ); } } else { if ( !$end ) { @@ -129,27 +130,27 @@ class RefreshLinks extends Maintenance { } $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) ) { + + for ( $id = $start; $id <= $end; $id++ ) { + + if ( !( $id % $reportingInterval ) ) { $this->output( "$id\n" ); wfWaitForSlaves( $maxLag ); } $this->fixRedirect( $id ); } - if(!$redirectsOnly) { + 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) ) { + for ( $id = $start; $id <= $end; $id++ ) { + + if ( !( $id % $reportingInterval ) ) { $this->output( "$id\n" ); wfWaitForSlaves( $maxLag ); } - $this->fixLinksFromArticle( $id ); + self::fixLinksFromArticle( $id ); } } } @@ -159,12 +160,12 @@ class RefreshLinks extends Maintenance { * Update the redirect entry for a given page * @param $id int The page_id of the redirect */ - private function fixRedirect( $id ){ + 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 @@ -172,17 +173,17 @@ class RefreshLinks extends Maintenance { __METHOD__ ); return; } - $wgArticle = new Article($wgTitle); - + $wgArticle = new Article( $wgTitle ); + $rt = $wgArticle->followRedirect(); - - if($rt == false || !is_object($rt)) { + + if ( !$rt || !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); + $wgArticle->updateRedirectOn( $dbw, $rt ); } } @@ -190,14 +191,13 @@ class RefreshLinks extends Maintenance { * Run LinksUpdate for all links on a given page_id * @param $id int The page_id */ - private function fixLinksFromArticle( $id ) { + public static function fixLinksFromArticle( $id ) { global $wgTitle, $wgParser; $wgTitle = Title::newFromID( $id ); $dbw = wfGetDB( DB_MASTER ); - $linkCache =& LinkCache::singleton(); - $linkCache->clear(); + LinkCache::singleton()->clear(); if ( is_null( $wgTitle ) ) { return; @@ -248,21 +248,21 @@ class RefreshLinks extends Maintenance { // 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 ), + array( 'page_id' => null ), __METHOD__, 'DISTINCT', - array( 'page' => array( 'LEFT JOIN', "$field=page_id")) + array( 'page' => array( 'LEFT JOIN', "$field=page_id" ) ) ); $counter = 0; $list = array(); $this->output( "0.." ); - foreach( $results as $row ) { + foreach ( $results as $row ) { $counter++; $list[] = $row->$field; if ( ( $counter % $batchSize ) == 0 ) { - wfWaitForSlaves(5); + wfWaitForSlaves( 5 ); $dbw->delete( $table, array( $field => $list ), __METHOD__ ); $this->output( $counter . ".." ); @@ -270,7 +270,7 @@ class RefreshLinks extends Maintenance { } } $this->output( $counter ); - if (count($list) > 0) { + if ( count( $list ) > 0 ) { $dbw->delete( $table, array( $field => $list ), __METHOD__ ); } $this->output( "\n" ); @@ -280,4 +280,4 @@ class RefreshLinks extends Maintenance { } $maintClass = 'RefreshLinks'; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/removeUnusedAccounts.php b/maintenance/removeUnusedAccounts.php index 7b0535b7..44c27b35 100644 --- a/maintenance/removeUnusedAccounts.php +++ b/maintenance/removeUnusedAccounts.php @@ -22,7 +22,7 @@ * @author Rob Church <robchur@gmail.com> */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RemoveUnusedAccounts extends Maintenance { public function __construct() { @@ -35,26 +35,26 @@ class RemoveUnusedAccounts extends Maintenance { public function execute() { $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 { + if ( $this->hasOption( 'ignore-groups' ) ) { + $excludedGroups = explode( ',', $this->getOption( 'ignore-groups' ) ); + } else { $excludedGroups = array(); } $touched = $this->getOption( 'ignore-touched', "1" ); - if( !ctype_digit( $touched ) ) { + 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 ) { + 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 + 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 ) ) { @@ -65,9 +65,9 @@ class RemoveUnusedAccounts extends Maintenance { } $count = count( $del ); $this->output( "...found {$count}.\n" ); - + # If required, go back and delete each marked account - if( $count > 0 && $this->hasOption('delete') ) { + if ( $count > 0 && $this->hasOption( 'delete' ) ) { $this->output( "\nDeleting inactive accounts..." ); $dbw = wfGetDB( DB_MASTER ); $dbw->delete( 'user', array( 'user_id' => $del ), __METHOD__ ); @@ -75,12 +75,12 @@ class RemoveUnusedAccounts extends Maintenance { # 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 ) { + } 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) @@ -94,17 +94,17 @@ class RemoveUnusedAccounts extends Maintenance { $checks = array( 'revision' => 'rev', 'archive' => 'ar', 'logging' => 'log', 'image' => 'img', 'oldimage' => 'oi' ); $count = 0; - + $dbo->begin(); - foreach( $checks as $table => $fprefix ) { + foreach ( $checks as $table => $fprefix ) { $conds = array( $fprefix . '_user' => $id ); $count += (int)$dbo->selectField( $table, 'COUNT(*)', $conds, __METHOD__ ); } $dbo->commit(); - + return $count == 0; } } $maintClass = "RemoveUnusedAccounts"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/renameDbPrefix.php b/maintenance/renameDbPrefix.php index f73db508..289e747f 100644 --- a/maintenance/renameDbPrefix.php +++ b/maintenance/renameDbPrefix.php @@ -20,8 +20,8 @@ * * @ingroup Maintenance */ - -require_once( dirname(__FILE__) . '/Maintenance.php' ); + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RenameDbPrefix extends Maintenance { public function __construct() { @@ -35,43 +35,45 @@ class RenameDbPrefix extends Maintenance { } public function execute() { + global $wgDBname; + // Allow for no old prefix - if( $this->getOption( 'old', 0 ) === '0' ) { + if ( $this->getOption( 'old', 0 ) === '0' ) { $old = ''; } else { // Use nice safe, sane, prefixes - preg_match( '/^[a-zA-Z]+_$/', $this->getOption('old'), $m ); + 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' ) { + if ( $this->getOption( 'new', 0 ) === '0' ) { $new = ''; } else { // Use nice safe, sane, prefixes - preg_match( '/^[a-zA-Z]+_$/', $this->getOption('new'), $m ); + preg_match( '/^[a-zA-Z]+_$/', $this->getOption( 'new' ), $m ); $new = isset( $m[0] ) ? $m[0] : false; } - - if( $old === false || $new === false ) { + + if ( $old === false || $new === false ) { $this->error( "Invalid prefix!", true ); } - if( $old === $new ) { + 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 ) { + $res = $dbw->query( "SHOW TABLES " . $dbw->buildLike( $old, $dbw->anyString() ) ); + 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 ) { + foreach ( $fields as $table ) { // $old should be regexp safe ([a-zA-Z_]) - $newTable = preg_replace( '/^'.$old.'/', $new, $table ); + $newTable = preg_replace( '/^' . $old . '/', $new, $table ); $this->output( "Renaming table $table to $newTable\n" ); $dbw->query( "RENAME TABLE $table TO $newTable" ); } @@ -82,4 +84,4 @@ class RenameDbPrefix extends Maintenance { } $maintClass = "RenameDbPrefix"; -require_once( DO_MAINTENANCE );
\ No newline at end of file +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/renamewiki.php b/maintenance/renamewiki.php index 36437bea..4146525e 100644 --- a/maintenance/renamewiki.php +++ b/maintenance/renamewiki.php @@ -23,7 +23,7 @@ * @ingroup Wikimedia */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RenameWiki extends Maintenance { public function __construct() { @@ -32,7 +32,7 @@ class RenameWiki extends Maintenance { $this->addArg( 'olddb', 'Old DB name' ); $this->addArg( 'newdb', 'New DB name' ); } - + public function getDbType() { return Maintenance::DB_ADMIN; } @@ -45,7 +45,7 @@ class RenameWiki extends Maintenance { $to = $this->getArg( 1 ); $this->output( "Renaming blob tables in ES from $from to $to...\n" ); $this->output( "Sleeping 5 seconds...\n" ); - sleep(5); + sleep( 5 ); # Initialise external storage if ( is_array( $wgDefaultExternalStore ) ) { @@ -57,20 +57,20 @@ class RenameWiki extends Maintenance { } if ( count( $stores ) ) { - $this->output( "Initialising external storage $store...\n" ); + $this->output( "Initialising external storage...\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" ); @@ -86,4 +86,4 @@ class RenameWiki extends Maintenance { } $maintClass = "RenameWiki"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/renderDump.php b/maintenance/renderDump.php index d36953f8..78c5b6f3 100644 --- a/maintenance/renderDump.php +++ b/maintenance/renderDump.php @@ -27,8 +27,8 @@ * @file * @ingroup Maintenance */ - -require_once( dirname(__FILE__) . '/Maintenance.php' ); + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class DumpRenderer extends Maintenance { @@ -39,28 +39,45 @@ class DumpRenderer extends Maintenance { 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 ); + $this->addOption( 'prefix', 'Prefix for the rendered files (defaults to wiki)', false, true ); + $this->addOption( 'parser', 'Use an alternative parser class', false, true ); } public function execute() { $this->outputDirectory = $this->getOption( 'output-dir' ); + $this->prefix = $this->getOption( 'prefix', 'wiki' ); $this->startTime = wfTime(); + if ( $this->hasOption( 'parser' ) ) { + global $wgParserConf; + $wgParserConf['class'] = $this->getOption( 'parser' ); + $this->prefix .= "-{$wgParserConf['class']}"; + } + $source = new ImportStreamSource( $this->getStdin() ); $importer = new WikiImporter( $source ); $importer->setRevisionCallback( array( &$this, 'handleRevision' ) ); - return $importer->doImport(); + $importer->doImport(); + + $delta = wfTime() - $this->startTime; + $this->error( "Rendered {$this->count} pages in " . round($delta, 2) . " seconds " ); + if ($delta > 0) + $this->error( round($this->count / $delta, 2) . " pages/sec" ); + $this->error( "\n" ); } - + /** * Callback function for each revision, turn into HTML and save * @param $rev Revision */ - private function handleRevision( $rev ) { + public function handleRevision( $rev ) { + global $wgParserConf; + $title = $rev->getTitle(); - if (!$title) { + if ( !$title ) { $this->error( "Got bogus revision with null title!" ); return; } @@ -69,15 +86,15 @@ class DumpRenderer extends Maintenance { $this->count++; $sanitized = rawurlencode( $display ); - $filename = sprintf( "%s/wiki-%07d-%s.html", + $filename = sprintf( "%s/%s-%07d-%s.html", $this->outputDirectory, + $this->prefix, $this->count, $sanitized ); - $this->output( sprintf( $this->stderr, "%s\n", $filename, $display ) ); + $this->output( sprintf( "%s\n", $filename, $display ) ); - // fixme (what?) $user = new User(); - $parser = new Parser(); + $parser = new $wgParserConf['class'](); $options = ParserOptions::newFromUser( $user ); $output = $parser->parse( $rev->getText(), $title, $options ); @@ -89,7 +106,7 @@ class DumpRenderer extends Maintenance { "<head>\n" . "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n" . "<title>" . htmlspecialchars( $display ) . "</title>\n" . - "</head>\n" . + "</head>\n" . "<body>\n" . $output->getText() . "</body>\n" . @@ -98,4 +115,4 @@ class DumpRenderer extends Maintenance { } $maintClass = "DumpRenderer"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/rollbackEdits.php b/maintenance/rollbackEdits.php index 5d6a80a4..e1c126e1 100644 --- a/maintenance/rollbackEdits.php +++ b/maintenance/rollbackEdits.php @@ -21,7 +21,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RollbackEdits extends Maintenance { public function __construct() { @@ -36,7 +36,7 @@ class RollbackEdits extends Maintenance { public function execute() { $user = $this->getOption( 'user' ); $username = User::isIP( $user ) ? $user : User::getCanonicalName( $user ); - if( !$username ) { + if ( !$username ) { $this->error( 'Invalid username', true ); } @@ -44,10 +44,10 @@ class RollbackEdits extends Maintenance { $summary = $this->getOption( 'summary', $this->mSelf . ' mass rollback' ); $titles = array(); $results = array(); - if( $this->hasOption( 'titles' ) ) { - foreach( explode( '|', $this->getOption( 'titles' ) ) as $title ) { + if ( $this->hasOption( 'titles' ) ) { + foreach ( explode( '|', $this->getOption( 'titles' ) ) as $title ) { $t = Title::newFromText( $title ); - if( !$t ) { + if ( !$t ) { $this->error( 'Invalid title, ' . $title ); } else { $titles[] = $t; @@ -57,15 +57,15 @@ class RollbackEdits extends Maintenance { $titles = $this->getRollbackTitles( $user ); } - if( !$titles ) { + if ( !$titles ) { $this->output( 'No suitable titles to be rolled back' ); return; } - foreach( $titles as $t ) { + foreach ( $titles as $t ) { $a = new Article( $t ); $this->output( 'Processing ' . $t->getPrefixedText() . '...' ); - if( !$a->commitRollback( $user, $summary, $bot, $results ) ) { + if ( !$a->commitRollback( $user, $summary, $bot, $results ) ) { $this->output( "done\n" ); } else { $this->output( "failed\n" ); @@ -86,7 +86,7 @@ class RollbackEdits extends Maintenance { array( 'page_latest = rev_id', 'rev_user_text' => $user ), __METHOD__ ); - while( $row = $dbr->fetchObject( $results ) ) { + foreach ( $results as $row ) { $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title ); } return $titles; @@ -94,4 +94,4 @@ class RollbackEdits extends Maintenance { } $maintClass = 'RollbackEdits'; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/runBatchedQuery.php b/maintenance/runBatchedQuery.php index 03c56aa9..dd3680c9 100644 --- a/maintenance/runBatchedQuery.php +++ b/maintenance/runBatchedQuery.php @@ -22,7 +22,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class BatchedQueryRunner extends Maintenance { public function __construct() { @@ -35,7 +35,7 @@ class BatchedQueryRunner extends Maintenance { 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; @@ -43,7 +43,7 @@ class BatchedQueryRunner extends Maintenance { do { $this->output( "Batch $n: " ); $n++; - $dbw->query( $query ); + $dbw->query( $query, __METHOD__ ); $affected = $dbw->affectedRows(); $this->output( "$affected rows\n" ); wfWaitForSlaves( $wait ); @@ -57,4 +57,4 @@ class BatchedQueryRunner extends Maintenance { $maintClass = "BatchedQueryRunner"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/runJobs.php b/maintenance/runJobs.php index e03bf5d1..79ea7bfe 100644 --- a/maintenance/runJobs.php +++ b/maintenance/runJobs.php @@ -24,7 +24,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class RunJobs extends Maintenance { public function __construct() { @@ -34,7 +34,7 @@ class RunJobs extends Maintenance { $this->addOption( 'type', 'Type of job to run', false, true ); $this->addOption( 'procs', 'Number of processes to use', false, true ); } - + public function memoryLimit() { // Don't eat all memory on the machine if we get a bad job. return "150M"; @@ -43,7 +43,7 @@ class RunJobs extends Maintenance { public function execute() { global $wgTitle; if ( $this->hasOption( 'procs' ) ) { - $procs = intval( $this->getOption('procs') ); + $procs = intval( $this->getOption( 'procs' ) ); if ( $procs < 1 || $procs > 1000 ) { $this->error( "Invalid argument to --procs", true ); } @@ -58,22 +58,20 @@ class RunJobs extends Maintenance { $dbw = wfGetDB( DB_MASTER ); $n = 0; $conds = ''; - if ($type !== false) - $conds = "job_cmd = " . $dbw->addQuotes($type); + 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) + $offset = 0; + for ( ; ; ) { + $job = !$type ? Job::pop( $offset ) : Job::pop_type( $type ); + + if ( !$job ) break; - + wfWaitForSlaves( 5 ); $t = microtime( true ); - $offset=$job->id; + $offset = $job->id; $status = $job->run(); $t = microtime( true ) - $t; $timeMs = intval( $t * 1000 ); @@ -100,4 +98,4 @@ class RunJobs extends Maintenance { } $maintClass = "RunJobs"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/showJobs.php b/maintenance/showJobs.php index b385c50a..886d7d45 100644 --- a/maintenance/showJobs.php +++ b/maintenance/showJobs.php @@ -23,8 +23,8 @@ * @author Tim Starling * @author Ashar Voultoiz */ - -require_once( dirname(__FILE__) . '/Maintenance.php' ); + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class ShowJobs extends Maintenance { public function __construct() { @@ -42,7 +42,7 @@ class ShowJobs extends Maintenance { __METHOD__, array( 'GROUP BY' => 'job_cmd' ) ); - foreach( $res as $row ) { + foreach ( $res as $row ) { $this->output( $row->job_cmd . ': ' . $row->count . "\n" ); } } else { @@ -52,4 +52,4 @@ class ShowJobs extends Maintenance { } $maintClass = "ShowJobs"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/showStats.php b/maintenance/showStats.php index dfce3613..f695c440 100644 --- a/maintenance/showStats.php +++ b/maintenance/showStats.php @@ -20,7 +20,7 @@ * http://www.gnu.org/copyleft/gpl.html * * @ingroup Maintenance - * @author Ashar Voultoiz <hashar@altern.org> + * @author Ashar Voultoiz <hashar at free dot fr> * Based on initStats.php by: * @author Brion Vibber * @author Rob Church <robchur@gmail.com> @@ -28,10 +28,11 @@ * @license GNU General Public License 2.0 or later */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class ShowStats extends Maintenance { public function __construct() { + parent::__construct(); $this->mDescription = "Show the cached statistics"; } public function execute() { @@ -44,25 +45,25 @@ class ShowStats extends Maintenance { '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 ) { + foreach ( $fields as $field => $desc ) { $max_length_value = max( $max_length_value, strlen( $stats->$field ) ); - $max_length_desc = max( $max_length_desc , strlen( $desc )) ; + $max_length_desc = max( $max_length_desc , strlen( $desc ) ) ; } - + // Show them - foreach( $fields as $field => $desc ) { + foreach ( $fields as $field => $desc ) { $this->output( sprintf( "%-{$max_length_desc}s: %{$max_length_value}d\n", $desc, $stats->$field ) ); } } } $maintClass = "ShowStats"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/sql.php b/maintenance/sql.php index fd4be19a..e1f2bb5f 100644 --- a/maintenance/sql.php +++ b/maintenance/sql.php @@ -21,7 +21,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class MwSql extends Maintenance { public function __construct() { @@ -39,7 +39,7 @@ class MwSql extends Maintenance { $promptObject = new SqlPromptPrinter( "> " ); $promptCallback = $promptObject->cb(); } - + if ( !$file ) $this->error( "Unable to open input file", true ); @@ -69,7 +69,7 @@ class MwSql extends Maintenance { $this->output( "Query OK, $affected row(s) affected\n" ); } } - + public function getDbType() { return Maintenance::DB_ADMIN; } @@ -90,4 +90,4 @@ class SqlPromptPrinter { } $maintClass = "MwSql"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/sqlite.inc b/maintenance/sqlite.inc new file mode 100644 index 00000000..238fe82b --- /dev/null +++ b/maintenance/sqlite.inc @@ -0,0 +1,67 @@ +<?php + +/** + * This class contains code common to different SQLite-related maintenance scripts + */ +class Sqlite { + + /** + * Checks whether PHP has SQLite support + * @return bool + */ + public static function isPresent() { + wfSuppressWarnings(); + $compiled = wfDl( 'pdo_sqlite' ); + wfRestoreWarnings(); + return $compiled; + } + + /** + * Checks given files for correctness of SQL syntax. MySQL DDL will be converted to + * SQLite-compatible during processing. + * Will throw exceptions on SQL errors + * @return mixed true if no error or error string in case of errors + */ + public static function checkSqlSyntax( $files ) { + if ( !Sqlite::isPresent() ) { + throw new MWException( "Can't check SQL syntax: SQLite not found" ); + } + if ( !is_array( $files ) ) { + $files = array( $files ); + } + + $allowedTypes = array_flip( array( + 'integer', + 'real', + 'text', + 'blob', // NULL type is omitted intentionally + ) ); + + $db = new DatabaseSqliteStandalone( ':memory:' ); + try { + foreach ( $files as $file ) { + $err = $db->sourceFile( $file ); + if ( $err != true ) { + return $err; + } + } + + $tables = $db->query( "SELECT name FROM sqlite_master WHERE type='table'", __METHOD__ ); + foreach ( $tables as $table ) { + if ( strpos( $table->name, 'sqlite_' ) === 0 ) continue; + + $columns = $db->query( "PRAGMA table_info({$table->name})", __METHOD__ ); + foreach ( $columns as $col ) { + if ( !isset( $allowedTypes[strtolower( $col->type )] ) ) { + $db->close(); + return "Table {$table->name} has column {$col->name} with non-native type '{$col->type}'"; + } + } + } + } catch ( DBError $e ) { + return $e->getMessage(); + } + $db->close(); + return true; + } + };
\ No newline at end of file diff --git a/maintenance/sqlite.php b/maintenance/sqlite.php index 8886fe74..13d136d8 100644 --- a/maintenance/sqlite.php +++ b/maintenance/sqlite.php @@ -20,7 +20,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class SqliteMaintenance extends Maintenance { public function __construct() { @@ -29,6 +29,7 @@ class SqliteMaintenance extends Maintenance { $this->addOption( 'vacuum', 'Clean up database by removing deleted pages. Decreases database file size' ); $this->addOption( 'integrity', 'Check database for integrity' ); $this->addOption( 'backup-to', 'Backup database to the given file', false, true ); + $this->addOption( 'check-syntax', 'Check SQL file(s) for syntax errors', false, true ); } /** @@ -41,7 +42,12 @@ class SqliteMaintenance extends Maintenance { public function execute() { global $wgDBtype; - + + // Should work even if we use a non-SQLite database + if ( $this->hasOption( 'check-syntax' ) ) { + $this->checkSyntax(); + } + if ( $wgDBtype != 'sqlite' ) { $this->error( "This maintenance script requires a SQLite database.\n" ); return; @@ -66,14 +72,14 @@ class SqliteMaintenance extends Maintenance { $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 ) ); + $prevSize, $newSize, ( $prevSize - $newSize ) * 100.0 / $prevSize ) ); } else { $this->output( 'Error\n' ); } @@ -107,7 +113,21 @@ class SqliteMaintenance extends Maintenance { $this->output( " Releasing lock...\n" ); $this->db->query( 'COMMIT TRANSACTION', __METHOD__ ); } + + private function checkSyntax() { + if ( !Sqlite::IsPresent() ) { + $this->error( "Error: SQLite support not found\n" ); + } + $files = array( $this->getOption( 'check-syntax' ) ); + $files += $this->mArgs; + $result = Sqlite::checkSqlSyntax( $files ); + if ( $result === true ) { + $this->output( "SQL syntax check: no errors detected.\n" ); + } else { + $this->error( "Error: $result\n" ); + } + } } $maintClass = "SqliteMaintenance"; -require_once( DO_MAINTENANCE );
\ No newline at end of file +require_once( RUN_MAINTENANCE_IF_MAIN );
\ No newline at end of file diff --git a/maintenance/sqlite/README b/maintenance/sqlite/README deleted file mode 100644 index b8a45553..00000000 --- a/maintenance/sqlite/README +++ /dev/null @@ -1,12 +0,0 @@ -SQLite shares the MySQL schema file at maintenance/tables.sql, with a set of -compatibility regexes to convert MySQL syntax to SQLite syntax: - -* BINARY() and VARBINARY() fields are converted to BLOB -* the UNSIGNED modifier is removed -* "INT" fields are converted to "INTEGER" -* ENUM is converted to BLOB -* the BINARY collation modifier is removed -* AUTO_INCREMENT is converted to AUTOINCREMENT -* Any table options are removed -* Truncated indexes are upgraded to full-width indexes -* FULLTEXT indexes are converted to ordinary indexes diff --git a/maintenance/sqlite/archives/initial-indexes.sql b/maintenance/sqlite/archives/initial-indexes.sql index f0851163..2573ca13 100644 --- a/maintenance/sqlite/archives/initial-indexes.sql +++ b/maintenance/sqlite/archives/initial-indexes.sql @@ -413,4 +413,4 @@ CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value); CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title); CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo); -INSERT INTO /*_*/updatelog VALUES ('initial_indexes'); +INSERT INTO /*_*/updatelog (ul_key) VALUES ('initial_indexes'); diff --git a/maintenance/sqlite/archives/patch-categorylinks-better-collation.sql b/maintenance/sqlite/archives/patch-categorylinks-better-collation.sql new file mode 100644 index 00000000..f32af134 --- /dev/null +++ b/maintenance/sqlite/archives/patch-categorylinks-better-collation.sql @@ -0,0 +1,7 @@ +ALTER TABLE /*_*/categorylinks ADD COLUMN cl_sortkey_prefix TEXT NOT NULL default ''; +ALTER TABLE /*_*/categorylinks ADD COLUMN cl_collation BLOB NOT NULL default ''; +ALTER TABLE /*_*/categorylinks ADD COLUMN cl_type TEXT NOT NULL default 'page'; +CREATE INDEX cl_collation ON /*_*/categorylinks (cl_collation); +DROP INDEX cl_sortkey; +CREATE INDEX cl_sortkey ON /*_*/categorylinks (cl_to, cl_type, cl_sortkey, cl_from); +INSERT OR IGNORE INTO /*_*/updatelog (ul_key) VALUES ('cl_fields_update'); diff --git a/maintenance/sqlite/archives/patch-iw_api_and_wikiid.sql b/maintenance/sqlite/archives/patch-iw_api_and_wikiid.sql new file mode 100644 index 00000000..f9172b5e --- /dev/null +++ b/maintenance/sqlite/archives/patch-iw_api_and_wikiid.sql @@ -0,0 +1,19 @@ +-- +-- Add iw_api and iw_wikiid to interwiki table +-- + + +CREATE TABLE /*_*/interwiki_tmp ( + iw_prefix TEXT NOT NULL, + iw_url BLOB NOT NULL, + iw_api BLOB NOT NULL, + iw_wikiid TEXT NOT NULL, + iw_local INTEGER NOT NULL, + iw_trans INTEGER NOT NULL default 0 +) /*$wgDBTableOptions*/; + +INSERT INTO /*_*/interwiki_tmp SELECT iw_prefix, iw_url, '', '', iw_local, iw_trans FROM /*_*/interwiki; +DROP TABLE /*_*/interwiki; +ALTER TABLE /*_*/interwiki_tmp RENAME TO /*_*/interwiki; + +CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
\ No newline at end of file diff --git a/maintenance/sqlite/archives/patch-kill-iwl_pft.sql b/maintenance/sqlite/archives/patch-kill-iwl_pft.sql new file mode 100644 index 00000000..8fc4b5cd --- /dev/null +++ b/maintenance/sqlite/archives/patch-kill-iwl_pft.sql @@ -0,0 +1,7 @@ +-- +-- Kill the old iwl_prefix_from_title index, which may be present on some +-- installs if they ran update.php between it being added and being renamed +-- + +DROP INDEX IF EXISTS /*i*/iwl_prefix; + diff --git a/maintenance/sqlite/archives/patch-kill-iwl_prefix.sql b/maintenance/sqlite/archives/patch-kill-iwl_prefix.sql new file mode 100644 index 00000000..78ed385e --- /dev/null +++ b/maintenance/sqlite/archives/patch-kill-iwl_prefix.sql @@ -0,0 +1,7 @@ +-- +-- Kill the old iwl_prefix index, which may be present on some +-- installs if they ran update.php between it being added and being renamed +-- + +DROP INDEX IF EXISTS /*i*/iwl_prefix; + diff --git a/maintenance/sqlite/archives/patch-log_search-rename-index.sql b/maintenance/sqlite/archives/patch-log_search-rename-index.sql new file mode 100644 index 00000000..4b98a0f2 --- /dev/null +++ b/maintenance/sqlite/archives/patch-log_search-rename-index.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id); diff --git a/maintenance/sqlite/archives/patch-rename-iwl_prefix.sql b/maintenance/sqlite/archives/patch-rename-iwl_prefix.sql new file mode 100644 index 00000000..08c3ae5f --- /dev/null +++ b/maintenance/sqlite/archives/patch-rename-iwl_prefix.sql @@ -0,0 +1,5 @@ +-- +-- Recreates the iwl_prefix for the iwlinks table +-- +DROP INDEX IF EXISTS /*i*/iwl_prefix; +CREATE INDEX /*i*/iwl_prefix_from_title ON /*_*/iwlinks (iwl_prefix, iwl_from, iwl_title);
\ No newline at end of file diff --git a/maintenance/sqlite/archives/patch-tc-timestamp.sql b/maintenance/sqlite/archives/patch-tc-timestamp.sql index 551a5f1c..5c09bf35 100644 --- a/maintenance/sqlite/archives/patch-tc-timestamp.sql +++ b/maintenance/sqlite/archives/patch-tc-timestamp.sql @@ -1,3 +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'); +INSERT INTO /*_*/updatelog (ul_key) VALUES ('convert transcache field'); diff --git a/maintenance/sqlite/archives/searchindex-fts3.sql b/maintenance/sqlite/archives/searchindex-fts3.sql index c3a86894..28554c02 100644 --- a/maintenance/sqlite/archives/searchindex-fts3.sql +++ b/maintenance/sqlite/archives/searchindex-fts3.sql @@ -15,4 +15,4 @@ CREATE VIRTUAL TABLE /*_*/searchindex USING FTS3( si_text ); -INSERT INTO /*_*/updatelog VALUES ('fts3');
\ No newline at end of file +INSERT INTO /*_*/updatelog (ul_key) VALUES ('fts3');
\ No newline at end of file diff --git a/maintenance/stats.php b/maintenance/stats.php index e20c345a..2cbbcf91 100644 --- a/maintenance/stats.php +++ b/maintenance/stats.php @@ -20,70 +20,71 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class CacheStats extends Maintenance { public function __construct() { $this->mDescription = "Show statistics from the cache"; + parent::__construct(); + } + + public function getDbType() { + return Maintenance::DB_NONE; } public function execute() { global $wgMemc; - - // Can't do stats if - if( get_class( $wgMemc ) == 'FakeMemCachedClient' ) { + + // 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'))); + $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( "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'))); + $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( "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'))); + + $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( "\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'))); + + $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 ) ); + $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 ) ); } } $maintClass = "CacheStats"; -require_once( DO_MAINTENANCE ); - - - - +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/storage/checkStorage.php b/maintenance/storage/checkStorage.php index 245c2fec..c288d682 100644 --- a/maintenance/storage/checkStorage.php +++ b/maintenance/storage/checkStorage.php @@ -9,7 +9,7 @@ define( 'CONCAT_HEADER', 'O:27:"concatenatedgziphistoryblob"' ); if ( !defined( 'MEDIAWIKI' ) ) { - require_once( dirname(__FILE__) . '/../commandLine.inc' ); + require_once( dirname( __FILE__ ) . '/../commandLine.inc' ); $cs = new CheckStorage; $fix = isset( $options['fix'] ); @@ -22,7 +22,7 @@ if ( !defined( 'MEDIAWIKI' ) ) { } -//---------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------- /** * @ingroup Maintenance ExternalStorage @@ -37,7 +37,7 @@ class CheckStorage { 'unfixable' => 'Unexpected errors with no automated fixing method', 'fixed' => 'Errors already fixed', 'fixable' => 'Errors which would already be fixed if --fix was specified', - ); + ); function check( $fix = false, $xml = '' ) { $fname = 'checkStorage'; @@ -63,14 +63,14 @@ class CheckStorage { for ( $chunkStart = 1 ; $chunkStart < $maxRevId; $chunkStart += $chunkSize ) { $chunkEnd = $chunkStart + $chunkSize - 1; - //print "$chunkStart of $maxRevId\n"; + // print "$chunkStart of $maxRevId\n"; // Fetch revision rows $this->oldIdMap = array(); - $dbr->ping(); - $res = $dbr->select( 'revision', array( 'rev_id', 'rev_text_id' ), + $dbr->ping(); + $res = $dbr->select( 'revision', array( 'rev_id', 'rev_text_id' ), array( "rev_id BETWEEN $chunkStart AND $chunkEnd" ), $fname ); - while ( $row = $dbr->fetchObject( $res ) ) { + foreach ( $res as $row ) { $this->oldIdMap[$row->rev_id] = $row->rev_text_id; } $dbr->freeResult( $res ); @@ -83,9 +83,9 @@ class CheckStorage { $missingTextRows = array_flip( $this->oldIdMap ); $externalRevs = array(); $objectRevs = array(); - $res = $dbr->select( 'text', array( 'old_id', 'old_flags' ), + $res = $dbr->select( 'text', array( 'old_id', 'old_flags' ), 'old_id IN (' . implode( ',', $this->oldIdMap ) . ')', $fname ); - while ( $row = $dbr->fetchObject( $res ) ) { + foreach ( $res as $row ) { $flags = $row->old_flags; $id = $row->old_id; @@ -116,7 +116,7 @@ class CheckStorage { if ( $fix ) { $this->error( 'fixed', "Warning: old_flags set to 0", $id ); $dbw->ping(); - $dbw->update( 'text', array( 'old_flags' => '' ), + $dbw->update( 'text', array( 'old_flags' => '' ), array( 'old_id' => $id ), $fname ); echo "Fixed\n"; } else { @@ -137,15 +137,15 @@ class CheckStorage { $externalConcatBlobs = array(); $externalNormalBlobs = array(); if ( count( $externalRevs ) ) { - $res = $dbr->select( 'text', array( 'old_id', 'old_flags', 'old_text' ), + $res = $dbr->select( 'text', array( 'old_id', 'old_flags', 'old_text' ), array( 'old_id IN (' . implode( ',', $externalRevs ) . ')' ), $fname ); - while ( $row = $dbr->fetchObject( $res ) ) { + foreach ( $res as $row ) { $urlParts = explode( '://', $row->old_text, 2 ); if ( count( $urlParts ) !== 2 || $urlParts[1] == '' ) { $this->error( 'restore text', "Error: invalid URL \"{$row->old_text}\"", $row->old_id ); continue; } - list( $proto, $path ) = $urlParts; + list( $proto, ) = $urlParts; if ( $proto != 'DB' ) { $this->error( 'restore text', "Error: invalid external protocol \"$proto\"", $row->old_id ); continue; @@ -164,7 +164,7 @@ class CheckStorage { // Check external concat blobs for the right header $this->checkExternalConcatBlobs( $externalConcatBlobs ); - + // Check external normal blobs for existence if ( count( $externalNormalBlobs ) ) { if ( is_null( $this->dbStore ) ) { @@ -174,10 +174,10 @@ class CheckStorage { $blobIds = array_keys( $xBlobIds ); $extDb =& $this->dbStore->getSlave( $cluster ); $blobsTable = $this->dbStore->getTable( $extDb ); - $res = $extDb->select( $blobsTable, - array( 'blob_id' ), + $res = $extDb->select( $blobsTable, + array( 'blob_id' ), array( 'blob_id IN( ' . implode( ',', $blobIds ) . ')' ), $fname ); - while ( $row = $extDb->fetchObject( $res ) ) { + foreach ( $res as $row ) { unset( $xBlobIds[$row->blob_id] ); } $extDb->freeResult( $res ); @@ -194,9 +194,9 @@ class CheckStorage { $curIds = array(); if ( count( $objectRevs ) ) { $headerLength = 300; - $res = $dbr->select( 'text', array( 'old_id', 'old_flags', "LEFT(old_text, $headerLength) AS header" ), + $res = $dbr->select( 'text', array( 'old_id', 'old_flags', "LEFT(old_text, $headerLength) AS header" ), array( 'old_id IN (' . implode( ',', $objectRevs ) . ')' ), $fname ); - while ( $row = $dbr->fetchObject( $res ) ) { + foreach ( $res as $row ) { $oldId = $row->old_id; $matches = array(); if ( !preg_match( '/^O:(\d+):"(\w+)"/', $row->header, $matches ) ) { @@ -245,9 +245,9 @@ class CheckStorage { $externalConcatBlobs = array(); if ( count( $concatBlobs ) ) { $headerLength = 300; - $res = $dbr->select( 'text', array( 'old_id', 'old_flags', "LEFT(old_text, $headerLength) AS header" ), + $res = $dbr->select( 'text', array( 'old_id', 'old_flags', "LEFT(old_text, $headerLength) AS header" ), array( 'old_id IN (' . implode( ',', array_keys( $concatBlobs ) ) . ')' ), $fname ); - while ( $row = $dbr->fetchObject( $res ) ) { + foreach ( $res as $row ) { $flags = explode( ',', $row->old_flags ); if ( in_array( 'external', $flags ) ) { // Concat blob is in external storage? @@ -261,7 +261,7 @@ class CheckStorage { if ( !isset( $externalConcatBlobs[$cluster][$id] ) ) { $externalConcatBlobs[$cluster][$id] = array(); } - $externalConcatBlobs[$cluster][$id] = array_merge( + $externalConcatBlobs[$cluster][$id] = array_merge( $externalConcatBlobs[$cluster][$id], $concatBlobs[$row->old_id] ); } @@ -270,7 +270,7 @@ class CheckStorage { $concatBlobs[$row->old_id] ); } } elseif ( strcasecmp( substr( $row->header, 0, strlen( CONCAT_HEADER ) ), CONCAT_HEADER ) ) { - $this->error( 'restore text', "Error: Incorrect object header for concat bulk row {$row->old_id}", + $this->error( 'restore text', "Error: Incorrect object header for concat bulk row {$row->old_id}", $concatBlobs[$row->old_id] ); } # else good @@ -286,7 +286,7 @@ class CheckStorage { } print "\n\nErrors:\n"; - foreach( $this->errors as $name => $errors ) { + foreach ( $this->errors as $name => $errors ) { if ( count( $errors ) ) { $description = $this->errorDescriptions[$name]; echo "$description: " . implode( ',', array_keys( $errors ) ) . "\n"; @@ -323,7 +323,7 @@ class CheckStorage { foreach ( $ids as $id ) { $revIds = array_merge( $revIds, array_keys( $this->oldIdMap, $id ) ); } - print "$msg in text rows " . implode( ', ', $ids ) . + print "$msg in text rows " . implode( ', ', $ids ) . ", revisions " . implode( ', ', $revIds ) . "\n"; } else { $id = $ids; @@ -346,18 +346,18 @@ class CheckStorage { if ( is_null( $this->dbStore ) ) { $this->dbStore = new ExternalStoreDB; } - + foreach ( $externalConcatBlobs as $cluster => $oldIds ) { $blobIds = array_keys( $oldIds ); $extDb =& $this->dbStore->getSlave( $cluster ); $blobsTable = $this->dbStore->getTable( $extDb ); $headerLength = strlen( CONCAT_HEADER ); - $res = $extDb->select( $blobsTable, - array( 'blob_id', "LEFT(blob_text, $headerLength) AS header" ), + $res = $extDb->select( $blobsTable, + array( 'blob_id', "LEFT(blob_text, $headerLength) AS header" ), array( 'blob_id IN( ' . implode( ',', $blobIds ) . ')' ), $fname ); - while ( $row = $extDb->fetchObject( $res ) ) { + foreach ( $res as $row ) { if ( strcasecmp( $row->header, CONCAT_HEADER ) ) { - $this->error( 'restore text', "Error: invalid header on target $cluster/{$row->blob_id} of two-part ES URL", + $this->error( 'restore text', "Error: invalid header on target $cluster/{$row->blob_id} of two-part ES URL", $oldIds[$row->blob_id] ); } unset( $oldIds[$row->blob_id] ); @@ -383,7 +383,7 @@ class CheckStorage { $revFileName = "$wgTmpDirectory/broken-revlist-$wgDBname"; $filteredXmlFileName = "$wgTmpDirectory/filtered-$wgDBname.xml"; - + // Write revision list if ( !file_put_contents( $revFileName, implode( "\n", $revIds ) ) ) { echo "Error writing revision list, can't restore text\n"; @@ -393,8 +393,8 @@ class CheckStorage { // Run mwdumper echo "Filtering XML dump...\n"; $exitStatus = 0; - passthru( 'mwdumper ' . - wfEscapeShellArg( + passthru( 'mwdumper ' . + wfEscapeShellArg( "--output=file:$filteredXmlFileName", "--filter=revlist:$revFileName", $xml @@ -416,7 +416,7 @@ class CheckStorage { $dbw = wfGetDB( DB_MASTER ); $dbr->ping(); $dbw->ping(); - + $source = new ImportStreamSource( $file ); $importer = new WikiImporter( $source ); $importer->setRevisionCallback( array( &$this, 'importRevision' ) ); @@ -429,8 +429,8 @@ class CheckStorage { $id = $revision->getID(); $text = $revision->getText(); if ( $text === '' ) { - // This is what happens if the revision was broken at the time the - // dump was made. Unfortunately, it also happens if the revision was + // This is what happens if the revision was broken at the time the + // dump was made. Unfortunately, it also happens if the revision was // legitimately blank, so there's no way to tell the difference. To // be safe, we'll skip it and leave it broken $id = $id ? $id : ''; @@ -457,7 +457,7 @@ class CheckStorage { // Update the text row $dbw = wfGetDB( DB_MASTER ); - $dbw->update( 'text', + $dbw->update( 'text', array( 'old_flags' => $flags, 'old_text' => $text ), array( 'old_id' => $oldId ), $fname, array( 'LIMIT' => 1 ) diff --git a/maintenance/storage/compressOld.inc b/maintenance/storage/compressOld.inc index 981cfda5..93be5f75 100644 --- a/maintenance/storage/compressOld.inc +++ b/maintenance/storage/compressOld.inc @@ -18,12 +18,11 @@ function compressOldPages( $start = 0, $extdb = '' ) { break; } $last = $start; - while( $row = $dbw->fetchObject( $res ) ) { + foreach ( $res as $row ) { # print " {$row->old_id} - {$row->old_namespace}:{$row->old_title}\n"; compressPage( $row, $extdb ); $last = $row->old_id; } - $dbw->freeResult( $res ); $start = $last + 1; # Deletion may leave long empty stretches print "$start...\n"; } while( true ); @@ -67,7 +66,7 @@ define( 'LS_INDIVIDUAL', 0 ); define( 'LS_CHUNKED', 1 ); /** @todo document */ -function compressWithConcat( $startId, $maxChunkSize, $beginDate, +function compressWithConcat( $startId, $maxChunkSize, $beginDate, $endDate, $extdb="", $maxPageId = false ) { $fname = 'compressWithConcat'; @@ -94,12 +93,12 @@ function compressWithConcat( $startId, $maxChunkSize, $beginDate, $pageConds[] = 'page_namespace<>0'; } if ( $queryExtra ) { - $pageConds[] = $queryExtra; + $pageConds[] = $queryExtra; } */ # For each article, get a list of revisions which fit the criteria - + # No recompression, use a condition on old_flags # Don't compress object type entities, because that might produce data loss when # overwriting bulk storage concat rows. Don't compress external references, because @@ -142,10 +141,10 @@ function compressWithConcat( $startId, $maxChunkSize, $beginDate, wfWaitForSlaves( 5 ); # Wake up - $dbr->ping(); + $dbr->ping(); # Get the page row - $pageRes = $dbr->select( 'page', + $pageRes = $dbr->select( 'page', array('page_id', 'page_namespace', 'page_title','page_latest'), $pageConds + array('page_id' => $pageId), $fname ); if ( $dbr->numRows( $pageRes ) == 0 ) { @@ -159,10 +158,10 @@ function compressWithConcat( $startId, $maxChunkSize, $beginDate, # Load revisions $revRes = $dbw->select( $tables, $fields, - array_merge( array( - 'rev_page' => $pageRow->page_id, + array_merge( array( + 'rev_page' => $pageRow->page_id, # Don't operate on the current revision - # Use < instead of <> in case the current revision has changed + # Use < instead of <> in case the current revision has changed # since the page select, which wasn't locking 'rev_id < ' . $pageRow->page_latest ), $conds ), @@ -170,7 +169,7 @@ function compressWithConcat( $startId, $maxChunkSize, $beginDate, $revLoadOptions ); $revs = array(); - while ( $revRow = $dbw->fetchObject( $revRes ) ) { + foreach ( $revRes as $revRow ) { $revs[] = $revRow; } diff --git a/maintenance/storage/compressOld.php b/maintenance/storage/compressOld.php index 7ff102a5..bc05b340 100644 --- a/maintenance/storage/compressOld.php +++ b/maintenance/storage/compressOld.php @@ -25,10 +25,10 @@ */ $optionsWithArgs = array( 't', 'c', 's', 'f', 'h', 'extdb', 'endid', 'e' ); -require_once( dirname(__FILE__) . '/../commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/../commandLine.inc' ); require_once( "compressOld.inc" ); -if( !function_exists( "gzdeflate" ) ) { +if ( !function_exists( "gzdeflate" ) ) { print "You must enable zlib support in PHP to compress old revisions!\n"; print "Please see http://www.php.net/manual/en/ref.zlib.php\n\n"; wfDie(); @@ -39,9 +39,9 @@ $defaults = array( 'c' => 20, 's' => 0, 'b' => '', - 'e' => '', - 'extdb' => '', - 'endid' => false, + 'e' => '', + 'extdb' => '', + 'endid' => false, ); $options = $options + $defaults; @@ -51,15 +51,15 @@ if ( $options['t'] != 'concat' && $options['t'] != 'gzip' ) { } if ( $options['extdb'] != '' ) { - print "Compressing database $wgDBname to external cluster {$options['extdb']}\n" . str_repeat('-', 76) . "\n\n"; + print "Compressing database $wgDBname to external cluster {$options['extdb']}\n" . str_repeat( '-', 76 ) . "\n\n"; } else { - print "Compressing database $wgDBname\n" . str_repeat('-', 76) . "\n\n"; + print "Compressing database $wgDBname\n" . str_repeat( '-', 76 ) . "\n\n"; } $success = true; if ( $options['t'] == 'concat' ) { - $success = compressWithConcat( $options['s'], $options['c'], $options['b'], - $options['e'], $options['extdb'], $options['endid'] ); + $success = compressWithConcat( $options['s'], $options['c'], $options['b'], + $options['e'], $options['extdb'], $options['endid'] ); } else { compressOldPages( $options['s'], $options['extdb'] ); } @@ -68,6 +68,6 @@ if ( $success ) { print "Done.\n"; } -exit(0); +exit( 0 ); diff --git a/maintenance/storage/dumpRev.php b/maintenance/storage/dumpRev.php index 95404244..b200d8af 100644 --- a/maintenance/storage/dumpRev.php +++ b/maintenance/storage/dumpRev.php @@ -18,7 +18,7 @@ * @ingroup Maintenance ExternalStorage */ -require_once( dirname(__FILE__) . '/../Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/../Maintenance.php' ); class DumpRev extends Maintenance { public function __construct() { @@ -28,15 +28,15 @@ class DumpRev extends Maintenance { public function execute() { $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( - array( 'text', 'revision' ), - array( 'old_flags', 'old_text' ), + $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 ) ) { @@ -65,15 +65,15 @@ class DumpRev extends Maintenance { $obj = unserialize( $text ); $text = $obj->getText(); } - + if ( is_object( $text ) ) { $this->error( "Unexpectedly got object of type: " . get_class( $text ) ); } else { - $this->output( "Text length: " . strlen( $text ) ."\n" ); + $this->output( "Text length: " . strlen( $text ) . "\n" ); $this->output( substr( $text, 0, 100 ) . "\n" ); } } } $maintClass = "DumpRev"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/storage/fixBug20757.php b/maintenance/storage/fixBug20757.php index 922d4725..4aac1202 100644 --- a/maintenance/storage/fixBug20757.php +++ b/maintenance/storage/fixBug20757.php @@ -14,7 +14,7 @@ class FixBug20757 extends Maintenance { $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 ); @@ -31,19 +31,29 @@ class FixBug20757 extends Maintenance { $totalRevs = $dbr->selectField( 'text', 'MAX(old_id)', false, __METHOD__ ); + if ( $dbr->getType() == 'mysql' + && version_compare( $dbr->getServerVersion(), '4.1.0', '>=' ) ) + { + // In MySQL 4.1+, the binary field old_text has a non-working LOWER() function + $lowerLeft = 'LOWER(CONVERT(LEFT(old_text,22) USING latin1))'; + } else { + // No CONVERT() in MySQL 4.0 + $lowerLeft = 'LOWER(LEFT(old_text,22))'; + } + while ( true ) { print "ID: $startId / $totalRevs\r"; $res = $dbr->select( 'text', array( 'old_id', 'old_flags', 'old_text' ), - array( + 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"\'', + "$lowerLeft = 'o:15:\"historyblobstub\"'", ), __METHOD__, - array( + array( 'ORDER BY' => 'old_id', 'LIMIT' => $this->batchSize, ) @@ -68,7 +78,7 @@ class FixBug20757 extends Maintenance { } if ( !is_object( $obj ) ) { - print "{$row->old_id}: unrecoverable: unserialized to type " . + print "{$row->old_id}: unrecoverable: unserialized to type " . gettype( $obj ) . ", possible double-serialization\n"; ++$numBad; continue; @@ -120,22 +130,21 @@ class FixBug20757 extends Maintenance { } // 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', + $secondaryRow = $dbr->selectRow( + 'text', array( 'old_flags', 'old_text' ), - array( 'old_id' => $secondaryId ), + 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 + // 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 ) { @@ -196,7 +205,7 @@ class FixBug20757 extends Maintenance { __METHOD__ ); - // Add a blob_tracking row so that the new reference can be recompressed + // 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( @@ -255,7 +264,7 @@ class FixBug20757 extends Maintenance { $dbr = wfGetDB( DB_SLAVE ); $map = array(); - $res = $dbr->select( 'revision', + $res = $dbr->select( 'revision', array( 'rev_id', 'rev_text_id' ), array( 'rev_page' => $pageId ), __METHOD__ @@ -276,7 +285,7 @@ class FixBug20757 extends Maintenance { function isUnbrokenStub( $stub, $secondaryRow ) { $flags = explode( ',', $secondaryRow->old_flags ); $text = $secondaryRow->old_text; - if( in_array( 'external', $flags ) ) { + if ( in_array( 'external', $flags ) ) { $url = $text; @list( /* $proto */ , $path ) = explode( '://', $url, 2 ); if ( $path == "" ) { @@ -284,17 +293,17 @@ class FixBug20757 extends Maintenance { } $text = ExternalStore::fetchFromUrl( $url ); } - if( !in_array( 'object', $flags ) ) { + if ( !in_array( 'object', $flags ) ) { return false; } - if( in_array( 'gzip', $flags ) ) { + if ( in_array( 'gzip', $flags ) ) { $obj = unserialize( gzinflate( $text ) ); } else { $obj = unserialize( $text ); } - if( !is_object( $obj ) ) { + if ( !is_object( $obj ) ) { // Correct for old double-serialization bug. $obj = unserialize( $obj ); } @@ -310,5 +319,5 @@ class FixBug20757 extends Maintenance { } $maintClass = 'FixBug20757'; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/storage/moveToExternal.php b/maintenance/storage/moveToExternal.php index dc11856a..928cbf97 100644 --- a/maintenance/storage/moveToExternal.php +++ b/maintenance/storage/moveToExternal.php @@ -9,10 +9,8 @@ define( 'REPORTING_INTERVAL', 1 ); if ( !defined( 'MEDIAWIKI' ) ) { - $optionsWithArgs = array( 'e', 's' ); - - require_once( dirname(__FILE__) . '/../commandLine.inc' ); - require_once( 'ExternalStoreDB.php' ); + require_once( dirname( __FILE__ ) . '/../commandLine.inc' ); + require_once( dirname( __FILE__ ) . '/../../includes/ExternalStoreDB.php' ); require_once( 'resolveStubs.php' ); $fname = 'moveToExternal'; @@ -35,8 +33,6 @@ if ( !defined( 'MEDIAWIKI' ) ) { moveToExternal( $cluster, $maxID, $minID ); } - - function moveToExternal( $cluster, $maxID, $minID = 1 ) { $fname = 'moveToExternal'; $dbw = wfGetDB( DB_MASTER ); @@ -48,23 +44,22 @@ function moveToExternal( $cluster, $maxID, $minID = 1 ) { print "Moving text rows from $minID to $maxID to external storage\n"; $ext = new ExternalStoreDB; $numMoved = 0; - $numStubs = 0; - + for ( $block = 0; $block < $numBlocks; $block++ ) { $blockStart = $block * $blockSize + $minID; $blockEnd = $blockStart + $blockSize - 1; - - if ( !($block % REPORTING_INTERVAL) ) { + + if ( !( $block % REPORTING_INTERVAL ) ) { print "oldid=$blockStart, moved=$numMoved\n"; wfWaitForSlaves( 2 ); } - + $res = $dbr->select( 'text', array( 'old_id', 'old_flags', 'old_text' ), array( "old_id BETWEEN $blockStart AND $blockEnd", 'old_flags NOT ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ), ), $fname ); - while ( $row = $dbr->fetchObject( $res ) ) { + foreach ( $res as $row ) { # Resolve stubs $text = $row->old_text; $id = $row->old_id; @@ -73,13 +68,13 @@ function moveToExternal( $cluster, $maxID, $minID = 1 ) { } else { $flags = "{$row->old_flags},external"; } - + if ( strpos( $flags, 'object' ) !== false ) { $obj = unserialize( $text ); $className = strtolower( get_class( $obj ) ); if ( $className == 'historyblobstub' ) { - #resolveStub( $id, $row->old_text, $row->old_flags ); - #$numStubs++; + # resolveStub( $id, $row->old_text, $row->old_flags ); + # $numStubs++; continue; } elseif ( $className == 'historyblobcurstub' ) { $text = gzdeflate( $obj->getText() ); @@ -99,8 +94,8 @@ function moveToExternal( $cluster, $maxID, $minID = 1 ) { continue; } - #print "Storing " . strlen( $text ) . " bytes to $url\n"; - #print "old_id=$id\n"; + # print "Storing " . strlen( $text ) . " bytes to $url\n"; + # print "old_id=$id\n"; $url = $ext->store( $cluster, $text ); if ( !$url ) { @@ -112,7 +107,6 @@ function moveToExternal( $cluster, $maxID, $minID = 1 ) { array( 'old_id' => $id ), $fname ); $numMoved++; } - $dbr->freeResult( $res ); } } diff --git a/maintenance/storage/orphanStats.php b/maintenance/storage/orphanStats.php index 63f9025b..f30f07e4 100644 --- a/maintenance/storage/orphanStats.php +++ b/maintenance/storage/orphanStats.php @@ -20,7 +20,7 @@ * * @ingroup Maintenance ExternalStorage */ -require_once( dirname(__FILE__) . '/../Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/../Maintenance.php' ); class OrphanStats extends Maintenance { public function __construct() { @@ -34,13 +34,12 @@ class OrphanStats extends Maintenance { } public function execute() { - $extDBs = array(); $dbr = wfGetDB( DB_SLAVE ); - if( !$dbr->tableExists( 'blob_orphans' ) ) { + 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; $totalSize = 0; $hashes = array(); @@ -49,7 +48,7 @@ class OrphanStats extends Maintenance { foreach ( $res as $boRow ) { $extDB = $this->getDB( $boRow->bo_cluster ); $blobRow = $extDB->selectRow( 'blobs', '*', array( 'blob_id' => $boRow->bo_blob_id ), __METHOD__ ); - + $num++; $size = strlen( $blobRow->blob_text ); $totalSize += $size; @@ -61,11 +60,11 @@ class OrphanStats extends Maintenance { $this->output( "Number of orphans: $num\n" ); if ( $num > 0 ) { $this->output( "Average size: " . round( $totalSize / $num, 0 ) . " bytes\n" . - "Max size: $maxSize\n" . + "Max size: $maxSize\n" . "Number of unique texts: " . count( $hashes ) . "\n" ); } } } $maintClass = "OrphanStats"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/storage/recompressTracked.php b/maintenance/storage/recompressTracked.php index e43dbe5c..8974a74d 100644 --- a/maintenance/storage/recompressTracked.php +++ b/maintenance/storage/recompressTracked.php @@ -1,14 +1,14 @@ <?php $optionsWithArgs = RecompressTracked::getOptionsWithArgs(); -require( dirname( __FILE__ ) .'/../commandLine.inc' ); +require( dirname( __FILE__ ) . '/../commandLine.inc' ); if ( count( $args ) < 1 ) { echo "Usage: php recompressTracked.php [options] <cluster> [... <cluster>...] Moves blobs indexed by trackBlobs.php to a specified list of destination clusters, and recompresses them in the process. Restartable. -Options: - --procs <procs> Set the number of child processes (default 1) +Options: + --procs <procs> Set the number of child processes (default 1) --copy-only Copy only, do not update the text table. Restart without this option to complete. --debug-log <file> Log debugging data to the specified file --info-log <file> Log progress messages to the specified file @@ -99,7 +99,7 @@ class RecompressTracked { } function logToFile( $msg, $file ) { - $header = '[' . date('d\TH:i:s') . '] ' . wfHostname() . ' ' . posix_getpid(); + $header = '[' . date( 'd\TH:i:s' ) . '] ' . wfHostname() . ' ' . posix_getpid(); if ( $this->slaveId !== false ) { $header .= "({$this->slaveId})"; } @@ -109,8 +109,8 @@ class RecompressTracked { /** * Wait until the selected slave has caught up to the master. - * This allows us to use the slave for things that were committed in a - * previous part of this batch process. + * This allows us to use the slave for things that were committed in a + * previous part of this batch process. */ function syncDBs() { $dbw = wfGetDB( DB_MASTER ); @@ -179,14 +179,14 @@ class RecompressTracked { $cmd .= " --$cmdOption"; } } - $cmd .= ' --child' . + $cmd .= ' --child' . ' --wiki ' . wfEscapeShellArg( wfWikiID() ) . ' ' . call_user_func_array( 'wfEscapeShellArg', $this->destClusters ); $this->slavePipes = $this->slaveProcs = array(); for ( $i = 0; $i < $this->numProcs; $i++ ) { $pipes = false; - $spec = array( + $spec = array( array( 'pipe', 'r' ), array( 'file', 'php://stdout', 'w' ), array( 'file', 'php://stderr', 'w' ) @@ -228,7 +228,7 @@ class RecompressTracked { function dispatch( /*...*/ ) { $args = func_get_args(); $pipes = $this->slavePipes; - $numPipes = stream_select( $x=array(), $pipes, $y=array(), 3600 ); + $numPipes = stream_select( $x = array(), $pipes, $y = array(), 3600 ); if ( !$numPipes ) { $this->critical( "Error waiting to write to slaves. Aborting" ); exit( 1 ); @@ -264,8 +264,8 @@ class RecompressTracked { if ( $this->noCount ) { $numPages = '[unknown]'; } else { - $numPages = $dbr->selectField( 'blob_tracking', - 'COUNT(DISTINCT bt_page)', + $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__ @@ -277,15 +277,15 @@ class RecompressTracked { $this->info( "Moving pages..." ); } while ( true ) { - $res = $dbr->select( 'blob_tracking', + $res = $dbr->select( 'blob_tracking', array( 'bt_page' ), - array( + array( 'bt_moved' => 0, 'bt_page > ' . $dbr->addQuotes( $startId ) ), __METHOD__, - array( - 'DISTINCT', + array( + 'DISTINCT', 'ORDER BY' => 'bt_page', 'LIMIT' => $this->batchSize, ) @@ -330,8 +330,8 @@ class RecompressTracked { if ( $this->noCount ) { $numOrphans = '[unknown]'; } else { - $numOrphans = $dbr->selectField( 'blob_tracking', - 'COUNT(DISTINCT bt_text_id)', + $numOrphans = $dbr->selectField( 'blob_tracking', + 'COUNT(DISTINCT bt_text_id)', array( 'bt_moved' => 0, 'bt_page' => 0 ), __METHOD__ ); if ( !$numOrphans ) { @@ -440,8 +440,8 @@ class RecompressTracked { $trx = new CgzCopyTransaction( $this, $this->pageBlobClass ); while ( true ) { - $res = $dbr->select( - array( 'blob_tracking', 'text' ), + $res = $dbr->select( + array( 'blob_tracking', 'text' ), '*', array( 'bt_page' => $pageId, @@ -451,7 +451,7 @@ class RecompressTracked { 'bt_text_id=old_id', ), __METHOD__, - array( + array( 'ORDER BY' => 'bt_text_id', 'LIMIT' => $this->batchSize ) @@ -496,7 +496,7 @@ class RecompressTracked { * * This is done in a single transaction to provide restartable behaviour * without data loss. - * + * * The transaction is kept short to reduce locking. */ function moveTextRow( $textId, $url ) { @@ -536,16 +536,16 @@ class RecompressTracked { $dbr = wfGetDB( DB_SLAVE ); $startId = 0; - $conds = array_merge( $conds, array( + $conds = array_merge( $conds, array( 'bt_moved' => 0, 'bt_new_url IS NOT NULL' - )); + ) ); while ( true ) { $res = $dbr->select( 'blob_tracking', '*', array_merge( $conds, array( 'bt_text_id > ' . $dbr->addQuotes( $startId ) ) ), __METHOD__, - array( + array( 'ORDER BY' => 'bt_text_id', 'LIMIT' => $this->batchSize, ) @@ -592,17 +592,17 @@ class RecompressTracked { $this->finishIncompleteMoves( array( 'bt_text_id' => $textIds ) ); $this->syncDBs(); } - + $trx = new CgzCopyTransaction( $this, $this->orphanBlobClass ); $res = wfGetDB( DB_SLAVE )->select( - array( 'text', 'blob_tracking' ), - array( 'old_id', 'old_text', 'old_flags' ), - array( + array( 'text', 'blob_tracking' ), + array( 'old_id', 'old_text', 'old_flags' ), + array( 'old_id' => $textIds, 'bt_text_id=old_id', 'bt_moved' => 0, - ), + ), __METHOD__, array( 'DISTINCT' ) ); @@ -610,10 +610,10 @@ class RecompressTracked { foreach ( $res as $row ) { $text = Revision::getRevisionText( $row ); if ( $text === false ) { - $this->critical( "Error: cannot load revision text for old_id=$textId" ); + $this->critical( "Error: cannot load revision text for old_id={$row->old_id}" ); continue; } - + if ( !$trx->addItem( $text, $row->old_id ) ) { $this->debug( "[orphan]: committing blob with " . $trx->getSize() . " rows" ); $trx->commit(); @@ -625,7 +625,7 @@ class RecompressTracked { $trx->commit(); } - /** + /** * Wait for slaves (quietly) */ function waitForSlaves() { @@ -704,14 +704,14 @@ class CgzCopyTransaction { // Check to see if the target text_ids have been moved already. // - // We originally read from the slave, so this can happen when a single - // text_id is shared between multiple pages. It's rare, but possible + // We originally read from the slave, so this can happen when a single + // text_id is shared between multiple pages. It's rare, but possible // if a delete/move/undelete cycle splits up a null edit. // // We do a locking read to prevent closer-run race conditions. $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); - $res = $dbw->select( 'blob_tracking', + $res = $dbw->select( 'blob_tracking', array( 'bt_text_id', 'bt_moved' ), array( 'bt_text_id' => array_keys( $this->referrers ) ), __METHOD__, array( 'FOR UPDATE' ) ); diff --git a/maintenance/storage/resolveStubs.php b/maintenance/storage/resolveStubs.php index 346151e9..2269e37f 100644 --- a/maintenance/storage/resolveStubs.php +++ b/maintenance/storage/resolveStubs.php @@ -9,7 +9,7 @@ define( 'REPORTING_INTERVAL', 100 ); if ( !defined( 'MEDIAWIKI' ) ) { $optionsWithArgs = array( 'm' ); - require_once( dirname(__FILE__) . '/../commandLine.inc' ); + require_once( dirname( __FILE__ ) . '/../commandLine.inc' ); resolveStubs(); } @@ -28,22 +28,19 @@ function resolveStubs() { for ( $b = 0; $b < $numBlocks; $b++ ) { wfWaitForSlaves( 2 ); - + printf( "%5.2f%%\n", $b / $numBlocks * 100 ); - $start = intval($maxID / $numBlocks) * $b + 1; - $end = intval($maxID / $numBlocks) * ($b + 1); - + $start = intval( $maxID / $numBlocks ) * $b + 1; + $end = intval( $maxID / $numBlocks ) * ( $b + 1 ); + $res = $dbr->select( 'text', array( 'old_id', 'old_text', 'old_flags' ), "old_id>=$start AND old_id<=$end " . - "AND old_flags LIKE '%object%' AND old_flags NOT LIKE '%external%' ". - 'AND LOWER(CONVERT(LEFT(old_text,22) USING latin1)) = \'o:15:"historyblobstub"\'', + "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 ) ) { + foreach ( $res as $row ) { resolveStub( $row->old_id, $row->old_text, $row->old_flags ); } - $dbr->freeResult( $res ); - - } print "100%\n"; } @@ -84,7 +81,7 @@ function resolveStub( $id, $stubText, $flags ) { } # Update the row - #print "oldid=$id\n"; + # print "oldid=$id\n"; $dbw->update( 'text', array( /* SET */ 'old_flags' => $newFlags, diff --git a/maintenance/storage/storageTypeStats.php b/maintenance/storage/storageTypeStats.php index 85858620..be86c531 100644 --- a/maintenance/storage/storageTypeStats.php +++ b/maintenance/storage/storageTypeStats.php @@ -1,6 +1,6 @@ <?php -require_once( dirname(__FILE__).'/../Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/../Maintenance.php' ); class StorageTypeStats extends Maintenance { function execute() { @@ -12,7 +12,6 @@ class StorageTypeStats extends Maintenance { exit( 1 ); } - $rangeStart = 0; $binSize = intval( pow( 10, floor( log10( $endId ) ) - 3 ) ); if ( $binSize < 100 ) { $binSize = 100; @@ -86,7 +85,7 @@ SQL; echo str_repeat( '-', 120 ) . "\n"; foreach ( $stats as $flags => $flagStats ) { foreach ( $flagStats as $class => $entry ) { - printf( $format, $flags, $class, $entry['count'], + printf( $format, $flags, $class, $entry['count'], sprintf( "%-13d - %-13d", $entry['first'], $entry['last'] ) ); } } @@ -94,5 +93,5 @@ SQL; } $maintClass = 'StorageTypeStats'; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/storage/testCompression.php b/maintenance/storage/testCompression.php index 9c96c9f8..e2718325 100644 --- a/maintenance/storage/testCompression.php +++ b/maintenance/storage/testCompression.php @@ -1,7 +1,7 @@ <?php $optionsWithArgs = array( 'start', 'limit', 'type' ); -require( dirname(__FILE__).'/../commandLine.inc' ); +require( dirname( __FILE__ ) . '/../commandLine.inc' ); if ( !isset( $args[0] ) ) { echo "Usage: php testCompression.php [--type=<type>] [--start=<start-date>] [--limit=<num-revs>] <page-title>\n"; @@ -26,10 +26,10 @@ $type = isset( $options['type'] ) ? $options['type'] : 'ConcatenatedGzipHistoryB $dbr = wfGetDB( DB_SLAVE ); -$res = $dbr->select( +$res = $dbr->select( array( 'page', 'revision', 'text' ), '*', - array( + array( 'page_namespace' => $title->getNamespace(), 'page_title' => $title->getDBkey(), 'page_id=rev_page', @@ -56,9 +56,9 @@ foreach ( $res as $row ) { $serialized = serialize( $blob ); $t += microtime( true ); -#print_r( $blob->mDiffMap ); +# print_r( $blob->mDiffMap ); -printf( "%s\nCompression ratio for %d revisions: %5.2f, %s -> %d\n", +printf( "%s\nCompression ratio for %d revisions: %5.2f, %s -> %d\n", $type, count( $hashes ), $uncompressedSize / strlen( $serialized ), @@ -73,7 +73,7 @@ foreach ( $keys as $id => $key ) { $text = $blob->getItem( $key ); if ( md5( $text ) != $hashes[$id] ) { echo "Content hash mismatch for rev_id $id\n"; - #var_dump( $text ); + # var_dump( $text ); } } $t += microtime( true ); diff --git a/maintenance/storage/trackBlobs.php b/maintenance/storage/trackBlobs.php index 63327d53..15aeec3b 100644 --- a/maintenance/storage/trackBlobs.php +++ b/maintenance/storage/trackBlobs.php @@ -1,6 +1,6 @@ <?php -require( dirname( __FILE__ ) .'/../commandLine.inc' ); +require( dirname( __FILE__ ) . '/../commandLine.inc' ); if ( count( $args ) < 1 ) { @@ -35,6 +35,7 @@ class TrackBlobs { } function run() { + $this->checkIntegrity(); $this->initTrackingTable(); $this->trackRevisions(); $this->trackOrphanText(); @@ -43,6 +44,47 @@ class TrackBlobs { } } + function checkIntegrity() { + echo "Doing integrity check...\n"; + $dbr = wfGetDB( DB_SLAVE ); + + // Scan for HistoryBlobStub objects in the text table (bug 20757) + + $exists = $dbr->selectField( 'text', 1, + 'old_flags LIKE \'%object%\' AND old_flags NOT LIKE \'%external%\' ' . + 'AND LOWER(CONVERT(LEFT(old_text,22) USING latin1)) = \'o:15:"historyblobstub"\'', + __METHOD__ + ); + + if ( $exists ) { + echo "Integrity check failed: found HistoryBlobStub objects in your text table.\n" . + "This script could destroy these objects if it continued. Run resolveStubs.php\n" . + "to fix this.\n"; + exit( 1 ); + } + + // Scan the archive table for HistoryBlobStub objects or external flags (bug 22624) + $flags = $dbr->selectField( 'archive', 'ar_flags', + 'ar_flags LIKE \'%external%\' OR (' . + 'ar_flags LIKE \'%object%\' ' . + 'AND LOWER(CONVERT(LEFT(ar_text,22) USING latin1)) = \'o:15:"historyblobstub"\' )', + __METHOD__ + ); + + if ( strpos( $flags, 'external' ) !== false ) { + echo "Integrity check failed: found external storage pointers in your archive table.\n" . + "Run normaliseArchiveTable.php to fix this.\n"; + exit( 1 ); + } elseif ( $flags ) { + echo "Integrity check failed: found HistoryBlobStub objects in your archive table.\n" . + "These objects are probably already broken, continuing would make them\n" . + "unrecoverable. Run \"normaliseArchiveTable.php --fix-cgz-bug\" to fix this.\n"; + exit( 1 ); + } + + echo "Integrity check OK\n"; + } + function initTrackingTable() { $dbw = wfGetDB( DB_MASTER ); if ( $dbw->tableExists( 'blob_tracking' ) ) { @@ -170,9 +212,9 @@ class TrackBlobs { # Scan the text table for orphan text while ( true ) { - $res = $dbr->select( array( 'text', 'blob_tracking' ), + $res = $dbr->select( array( 'text', 'blob_tracking' ), array( 'old_id', 'old_flags', 'old_text' ), - array( + array( 'old_id>' . $dbr->addQuotes( $startId ), $textClause, 'old_flags ' . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ), @@ -181,7 +223,7 @@ class TrackBlobs { __METHOD__, array( 'ORDER BY' => 'old_id', - 'LIMIT' => $this->batchSize + 'LIMIT' => $this->batchSize ), array( 'blob_tracking' => array( 'LEFT JOIN', 'bt_text_id=old_id' ) ) ); @@ -275,8 +317,8 @@ class TrackBlobs { // Build a bitmap of actual blob rows while ( true ) { - $res = $extDB->select( $table, - array( 'blob_id' ), + $res = $extDB->select( $table, + array( 'blob_id' ), array( 'blob_id > ' . $extDB->addQuotes( $startId ) ), __METHOD__, array( 'LIMIT' => $this->batchSize, 'ORDER BY' => 'blob_id' ) @@ -301,7 +343,7 @@ class TrackBlobs { // Find actual blobs that weren't tracked by the previous passes // This is a set-theoretic difference A \ B, or in bitwise terms, A & ~B $orphans = gmp_and( $actualBlobs, gmp_com( $this->trackedBlobs[$cluster] ) ); - + // Traverse the orphan list $insertBatch = array(); $id = 0; diff --git a/maintenance/tables.sql b/maintenance/tables.sql index 0809f4b6..1dcf98b7 100644 --- a/maintenance/tables.sql +++ b/maintenance/tables.sql @@ -52,22 +52,21 @@ -- CREATE TABLE /*_*/user ( user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, - + -- Usernames must be unique, must not be in the form of -- an IP address. _Shouldn't_ allow slashes or case -- conflicts. Spaces are allowed, and are _not_ converted -- to underscores like titles. See the User::newFromName() for -- the specific tests that usernames have to pass. user_name varchar(255) binary NOT NULL default '', - + -- Optional 'real name' to be displayed in credit listings user_real_name varchar(255) binary NOT NULL default '', - - -- Password hashes, normally hashed like so: - -- MD5(CONCAT(user_id,'-',MD5(plaintext_password))), see - -- wfEncryptPassword() in GlobalFunctions.php + + -- Password hashes, see User::crypt() and User::comparePasswords() + -- in User.php for the algorithm user_password tinyblob NOT NULL, - + -- When using 'mail me a new password', a random -- password is generated and the hash stored here. -- The previous password is left in place until @@ -75,49 +74,52 @@ CREATE TABLE /*_*/user ( -- at which point the hash is moved to user_password -- and the old password is invalidated. user_newpassword tinyblob NOT NULL, - + -- Timestamp of the last time when a new password was - -- sent, for throttling purposes + -- sent, for throttling and expiring purposes + -- Emailed passwords will expire $wgNewPasswordExpiry + -- (a week) after being set. If user_newpass_time is NULL + -- (eg. created by mail) it doesn't expire. user_newpass_time binary(14), -- Note: email should be restricted, not public info. -- Same with passwords. user_email tinytext NOT NULL, - + -- 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 -- logs in, logs out, changes preferences, or performs -- some other action requiring HTML cache invalidation -- to ensure that the UI is updated. user_touched binary(14) NOT NULL default '', - + -- A pseudorandomly generated value that is stored in -- a cookie when the "remember password" feature is -- used (previously, a hash of the password was used, but -- this was vulnerable to cookie-stealing attacks) user_token binary(32) NOT NULL default '', - + -- Initially NULL; when a user's e-mail address has been -- validated by returning with a mailed token, this is -- set to the current timestamp. user_email_authenticated binary(14), - + -- Randomly generated token created when the e-mail address -- is set and a confirmation test mail sent. user_email_token binary(32), - + -- Expiration date for the user_email_token user_email_token_expires binary(14), - + -- Timestamp of account registration. -- Accounts predating this schema addition may contain NULL. user_registration binary(14), - + -- Count of edits and edit-like actions. -- -- *NOT* intended to be an accurate copy of COUNT(*) WHERE rev_user=user_id @@ -146,7 +148,7 @@ CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token); CREATE TABLE /*_*/user_groups ( -- Key to user_id ug_user int unsigned NOT NULL default 0, - + -- Group names are short symbolic string keys. -- The set of group names is open-ended, though in practice -- only some predefined ones are likely to be used. @@ -194,10 +196,10 @@ CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip); 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*/; @@ -213,33 +215,33 @@ CREATE TABLE /*_*/page ( -- Unique identifier number. The page_id will be preserved across -- edits and rename operations, but not deletions and recreations. page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, - + -- A page name is broken into a namespace and a title. -- The namespace keys are UI-language-independent constants, -- defined in includes/Defines.php page_namespace int NOT NULL, - + -- The rest of the title, as text. -- Spaces are transformed into underscores in title storage. page_title varchar(255) binary NOT NULL, - + -- Comma-separated set of permission keys indicating who -- can move or edit the page. page_restrictions tinyblob NOT NULL, - + -- Number of times this page has been viewed. page_counter bigint unsigned NOT NULL default 0, - + -- 1 indicates the article is a redirect. page_is_redirect tinyint unsigned NOT NULL default 0, - + -- 1 indicates this is a new entry, with only one edit. -- Not all pages with one edit are new pages. page_is_new tinyint unsigned NOT NULL default 0, - + -- Random value between 0 and 1, used for Special:Randompage page_random real unsigned NOT NULL, - + -- This timestamp is updated whenever the page changes in -- a way requiring it to be re-rendered, invalidating caches. -- Aside from editing this includes permission changes, @@ -251,7 +253,7 @@ CREATE TABLE /*_*/page ( -- This may be 0 during page creation, but that shouldn't -- happen outside of a transaction... hopefully. page_latest int unsigned NOT NULL, - + -- Uncompressed length in bytes of the page's current source text. page_len int unsigned NOT NULL ) /*$wgDBTableOptions*/; @@ -268,38 +270,38 @@ CREATE INDEX /*i*/page_len ON /*_*/page (page_len); -- CREATE TABLE /*_*/revision ( rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, - + -- Key to page_id. This should _never_ be invalid. rev_page int unsigned NOT NULL, - + -- Key to text.old_id, where the actual bulk text is stored. -- It's possible for multiple revisions to use the same text, -- for instance revisions where only metadata is altered -- or a rollback to a previous version. rev_text_id int unsigned NOT NULL, - + -- Text comment summarizing the change. -- This text is shown in the history and other changes lists, -- rendered in a subset of wiki markup by Linker::formatComment() rev_comment tinyblob NOT NULL, - + -- Key to user.user_id of the user who made this edit. -- Stores 0 for anonymous edits and for some mass imports. rev_user int unsigned NOT NULL default 0, - + -- Text username or IP address of the editor. rev_user_text varchar(255) binary NOT NULL default '', - + -- Timestamp rev_timestamp binary(14) NOT NULL default '', - + -- Records whether the user marked the 'minor edit' checkbox. -- Many automated edits are marked as minor. rev_minor_edit tinyint unsigned NOT NULL default 0, - + -- Not yet used; reserved for future changes to the deletion system. rev_deleted tinyint unsigned NOT NULL default 0, - + -- Length of this revision in bytes rev_len int unsigned, @@ -331,11 +333,11 @@ CREATE TABLE /*_*/text ( -- -- revision.rev_text_id is a key to this column old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, - + -- Depending on the contents of the old_flags field, the text -- may be convenient plain text, or it may be funkily encoded. old_text mediumblob NOT NULL, - + -- Comma-separated list of flags: -- gzip: text is compressed with PHP's gzdeflate() function. -- utf8: text was stored as UTF-8. @@ -359,7 +361,7 @@ CREATE TABLE /*_*/text ( CREATE TABLE /*_*/archive ( ar_namespace int NOT NULL default 0, ar_title varchar(255) binary NOT NULL default '', - + -- Newly deleted pages will not store text in this table, -- but will reference the separately existing text rows. -- This field is retained for backwards compatibility, @@ -367,26 +369,26 @@ CREATE TABLE /*_*/archive ( -- upgrading from 1.4 to 1.5. -- Text may be gzipped or otherwise funky. ar_text mediumblob NOT NULL, - + -- Basic revision stuff... ar_comment tinyblob NOT NULL, ar_user int unsigned NOT NULL default 0, ar_user_text varchar(255) binary NOT NULL, ar_timestamp binary(14) NOT NULL default '', ar_minor_edit tinyint NOT NULL default 0, - + -- See ar_text note. ar_flags tinyblob NOT NULL, - + -- When revisions are deleted, their unique rev_id is stored -- here so it can be retained after undeletion. This is necessary -- to retain permalinks to given revisions after accidental delete -- cycles or messy operations like history merges. - -- + -- -- Old entries from 1.4 will be NULL here, and a new rev_id will -- be created on undeletion for those revisions. ar_rev_id int unsigned, - + -- For newly deleted revisions, this is the text.old_id key to the -- actual stored text. To avoid breaking the block-compression scheme -- and otherwise making storage changes harder, the actual text is @@ -404,19 +406,20 @@ CREATE TABLE /*_*/archive ( -- Length of this revision in bytes ar_len int unsigned, - -- Reference to page_id. Useful for sysadmin fixing of large pages + -- Reference to page_id. Useful for sysadmin fixing of large pages -- merged together in the archives, or for cleanly restoring a page -- at its original ID number if possible. -- -- Will be NULL for pages deleted prior to 1.11. ar_page_id int unsigned, - + -- Original previous revision ar_parent_id int unsigned default NULL ) /*$wgDBTableOptions*/; CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp); CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp); +CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id); -- @@ -425,7 +428,7 @@ CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timesta CREATE TABLE /*_*/pagelinks ( -- Key to the page_id of the page containing the link. pl_from int unsigned NOT NULL default 0, - + -- Key to page_namespace/page_title of the target page. -- The target page may or may not exist, and due to renames -- and deletions may refer to different page records as time @@ -444,7 +447,7 @@ CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,p CREATE TABLE /*_*/templatelinks ( -- Key to the page_id of the page containing the link. tl_from int unsigned NOT NULL default 0, - + -- Key to page_namespace/page_title of the target page. -- The target page may or may not exist, and due to renames -- and deletions may refer to different page records as time @@ -465,7 +468,7 @@ CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_tit CREATE TABLE /*_*/imagelinks ( -- Key to page_id of the page containing the image / media link. il_from int unsigned NOT NULL default 0, - + -- Filename of target image. -- This is also the page_title of the file's description page; -- all such pages are in namespace 6 (NS_FILE). @@ -483,37 +486,56 @@ CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from); CREATE TABLE /*_*/categorylinks ( -- Key to page_id of the page defined as a category member. cl_from int unsigned NOT NULL default 0, - + -- Name of the category. -- This is also the page_title of the category's description page; -- all such pages are in namespace 14 (NS_CATEGORY). cl_to varchar(255) binary NOT NULL default '', - - -- The title of the linking page, or an optional override - -- to determine sort order. Sorting is by binary order, which - -- isn't always ideal, but collations seem to be an exciting - -- and dangerous new world in MySQL... The sortkey is updated - -- if no override exists and cl_from is renamed. - -- - -- Truncate so that the cl_sortkey key fits in 1000 bytes - -- (MyISAM 5 with server_character_set=utf8) - cl_sortkey varchar(70) binary NOT NULL default '', - + + -- A binary string obtained by applying a sortkey generation algorithm + -- (Collation::getSortKey()) to page_title, or cl_sortkey_prefix . "\n" + -- . page_title if cl_sortkey_prefix is nonempty. + cl_sortkey varbinary(230) NOT NULL default '', + + -- A prefix for the raw sortkey manually specified by the user, either via + -- [[Category:Foo|prefix]] or {{defaultsort:prefix}}. If nonempty, it's + -- concatenated with a line break followed by the page title before the sortkey + -- conversion algorithm is run. We store this so that we can update + -- collations without reparsing all pages. + cl_sortkey_prefix varchar(255) binary NOT NULL default '', + -- This isn't really used at present. Provided for an optional -- sorting method by approximate addition time. - cl_timestamp timestamp NOT NULL + cl_timestamp timestamp NOT NULL, + + -- Stores $wgCategoryCollation at the time cl_sortkey was generated. This + -- can be used to install new collation versions, tracking which rows are not + -- yet updated. '' means no collation, this is a legacy row that needs to be + -- updated by updateCollation.php. In the future, it might be possible to + -- specify different collations per category. + cl_collation varbinary(32) NOT NULL default '', + + -- Stores whether cl_from is a category, file, or other page, so we can + -- paginate the three categories separately. This never has to be updated + -- after the page is created, since none of these page types can be moved to + -- any other. + cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page' ) /*$wgDBTableOptions*/; CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to); --- We always sort within a given category... -CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_sortkey,cl_from); +-- We always sort within a given category, and within a given type. FIXME: +-- Formerly this index didn't cover cl_type (since that didn't exist), so old +-- callers won't be using an index: fix this? +CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from); -- Not really used? CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp); +-- For finding rows with outdated collation +CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation); --- +-- -- Track all existing categories. Something is a category if 1) it has an en- -- try somewhere in categorylinks, or 2) it once did. Categories might not -- have corresponding pages, so they need to be tracked separately. @@ -557,15 +579,15 @@ CREATE TABLE /*_*/externallinks ( el_to blob NOT NULL, -- In the case of HTTP URLs, this is the URL with any username or password - -- removed, and with the labels in the hostname reversed and converted to + -- removed, and with the labels in the hostname reversed and converted to -- lower case. An extra dot is added to allow for matching of either -- example.com or *.example.com in a single scan. - -- Example: + -- Example: -- http://user:password@sub.example.com/page.html -- becomes -- http://com.example.sub./page.html -- which allows for fast searching for all pages under example.com with the - -- clause: + -- clause: -- WHERE el_index LIKE 'http://com.example.%' el_index blob NOT NULL ) /*$wgDBTableOptions*/; @@ -589,13 +611,13 @@ CREATE TABLE /*_*/external_user ( CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id); --- +-- -- Track interlanguage links -- CREATE TABLE /*_*/langlinks ( -- page_id of the referring page ll_from int unsigned NOT NULL default 0, - + -- Language code of the target ll_lang varbinary(20) NOT NULL default '', @@ -608,32 +630,50 @@ CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title); -- +-- Track inline interwiki links +-- +CREATE TABLE /*_*/iwlinks ( + -- page_id of the referring page + iwl_from int unsigned NOT NULL default 0, + + -- Interwiki prefix code of the target + iwl_prefix varbinary(20) NOT NULL default '', + + -- Title of the target, including namespace + iwl_title varchar(255) binary NOT NULL default '' +) /*$wgDBTableOptions*/; + +CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title); +CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from); + + +-- -- Contains a single row with some aggregate info -- on the state of the site. -- CREATE TABLE /*_*/site_stats ( -- The single row should contain 1 here. ss_row_id int unsigned NOT NULL, - + -- Total number of page views, if hit counters are enabled. ss_total_views bigint unsigned default 0, - + -- Total number of edits performed. ss_total_edits bigint unsigned default 0, - + -- An approximate count of pages matching the following criteria: -- * in namespace 0 -- * not a redirect -- * contains the text '[[' -- See Article::isCountable() in includes/Article.php ss_good_articles bigint unsigned default 0, - + -- Total pages, theoretically equal to SELECT COUNT(*) FROM page; except faster ss_total_pages bigint default '-1', -- Number of users, theoretically equal to SELECT COUNT(*) FROM user; ss_users bigint default '-1', - + -- Number of users that still edit ss_active_users bigint default '-1', @@ -667,26 +707,26 @@ CREATE TABLE /*_*/hitcounter ( CREATE TABLE /*_*/ipblocks ( -- Primary key, introduced for privacy. ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT, - + -- Blocked IP address in dotted-quad form or user name. ipb_address tinyblob NOT NULL, - + -- Blocked user ID or 0 for IP blocks. ipb_user int unsigned NOT NULL default 0, - + -- User ID who made the block. ipb_by int unsigned NOT NULL default 0, - + -- User name of blocker ipb_by_text varchar(255) binary NOT NULL default '', - + -- Text comment made by blocker. ipb_reason tinyblob NOT NULL, - + -- Creation (or refresh) date in standard YMDHMS form. -- IP blocks expire automatically. ipb_timestamp binary(14) NOT NULL default '', - + -- Indicates that the IP address was banned because a banned -- user accessed a page through it. If this is 1, ipb_address -- will be hidden, and the block identified by block ID number. @@ -700,11 +740,11 @@ CREATE TABLE /*_*/ipblocks ( -- Block triggers autoblocks ipb_enable_autoblock bool NOT NULL default '1', - + -- Time at which the block will expire. -- May be "infinity" ipb_expiry varbinary(14) NOT NULL default '', - + -- Start and end of an address range, in hexadecimal -- Size chosen to allow IPv6 ipb_range_start tinyblob NOT NULL, @@ -715,12 +755,12 @@ CREATE TABLE /*_*/ipblocks ( -- Block prevents user from accessing Special:Emailuser ipb_block_email bool NOT NULL default 0, - + -- Block allows user to edit their own talk page ipb_allow_usertalk bool NOT NULL default 0 ) /*$wgDBTableOptions*/; - + -- Unique index to support "user already blocked" messages -- Any new options which prevent collisions should be included CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only); @@ -739,52 +779,52 @@ CREATE TABLE /*_*/image ( -- This is also the title of the associated description page, -- which will be in namespace 6 (NS_FILE). img_name varchar(255) binary NOT NULL default '' PRIMARY KEY, - + -- File size in bytes. img_size int unsigned NOT NULL default 0, - + -- For images, size in pixels. img_width int NOT NULL default 0, img_height int NOT NULL default 0, - + -- Extracted EXIF metadata stored as a serialized PHP array. img_metadata mediumblob NOT NULL, - + -- For images, bits per pixel if known. img_bits int NOT NULL default 0, - + -- Media type as defined by the MEDIATYPE_xxx constants img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, - + -- major part of a MIME media type as defined by IANA -- see http://www.iana.org/assignments/media-types/ img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown", - + -- minor part of a MIME media type as defined by IANA -- the minor parts are not required to adher to any standard -- but should be consistent throughout the database -- see http://www.iana.org/assignments/media-types/ img_minor_mime varbinary(100) NOT NULL default "unknown", - + -- Description field as entered by the uploader. -- This is displayed in image upload history and logs. img_description tinyblob NOT NULL, - + -- user_id and user_name of uploader. img_user int unsigned NOT NULL default 0, img_user_text varchar(255) binary NOT NULL, - + -- Time of the upload. img_timestamp varbinary(14) NOT NULL default '', - + -- SHA-1 content hash in base-36 img_sha1 varbinary(32) NOT NULL default '' ) /*$wgDBTableOptions*/; CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp); --- Used by Special:Imagelist for sort-by-size +-- Used by Special:ListFiles for sort-by-size CREATE INDEX /*i*/img_size ON /*_*/image (img_size); --- Used by Special:Newimages and Special:Imagelist +-- Used by Special:Newimages and Special:ListFiles CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp); -- Used in API and duplicate search CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1); @@ -798,11 +838,11 @@ CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1); CREATE TABLE /*_*/oldimage ( -- Base filename: key to image.img_name oi_name varchar(255) binary NOT NULL default '', - + -- Filename of the archived file. -- This is generally a timestamp and '!' prepended to the base name. oi_archive_name varchar(255) binary NOT NULL default '', - + -- Other fields as in image... oi_size int unsigned NOT NULL default 0, oi_width int NOT NULL default 0, @@ -834,30 +874,30 @@ CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1); CREATE TABLE /*_*/filearchive ( -- Unique row id fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT, - + -- Original base filename; key to image.img_name, page.page_title, etc fa_name varchar(255) binary NOT NULL default '', - + -- Filename of archived file, if an old revision fa_archive_name varchar(255) binary default '', - + -- Which storage bin (directory tree or object store) the file data -- is stored in. Should be 'deleted' for files that have been deleted; -- any other bin is not yet in use. fa_storage_group varbinary(16), - + -- SHA-1 of the file contents plus extension, used as a key for storage. -- eg 8f8a562add37052a1848ff7771a2c515db94baa9.jpg -- -- If NULL, the file was missing at deletion time or has been purged -- from the archival storage. fa_storage_key varbinary(64) default '', - + -- Deletion information, if this file is deleted. fa_deleted_user int, fa_deleted_timestamp binary(14) default '', fa_deleted_reason text, - + -- Duped fields from image fa_size int unsigned default 0, fa_width int default 0, @@ -895,52 +935,52 @@ CREATE TABLE /*_*/recentchanges ( rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT, rc_timestamp varbinary(14) NOT NULL default '', rc_cur_time varbinary(14) NOT NULL default '', - + -- As in revision rc_user int unsigned NOT NULL default 0, rc_user_text varchar(255) binary NOT NULL, - + -- When pages are renamed, their RC entries do _not_ change. rc_namespace int NOT NULL default 0, rc_title varchar(255) binary NOT NULL default '', - + -- as in revision... rc_comment varchar(255) binary NOT NULL default '', rc_minor tinyint unsigned NOT NULL default 0, - + -- Edits by user accounts with the 'bot' rights key are -- marked with a 1 here, and will be hidden from the -- default view. rc_bot tinyint unsigned NOT NULL default 0, - + rc_new tinyint unsigned NOT NULL default 0, - + -- Key to page_id (was cur_id prior to 1.5). -- This will keep links working after moves while -- retaining the at-the-time name in the changes list. rc_cur_id int unsigned NOT NULL default 0, - + -- rev_id of the given revision rc_this_oldid int unsigned NOT NULL default 0, - + -- rev_id of the prior revision, for generating diff links. rc_last_oldid int unsigned NOT NULL default 0, - + -- These may no longer be used, with the new move log. rc_type tinyint unsigned NOT NULL default 0, rc_moved_to_ns tinyint unsigned NOT NULL default 0, rc_moved_to_title varchar(255) binary NOT NULL default '', - + -- If the Recent Changes Patrol option is enabled, -- users may mark edits as having been reviewed to -- remove a warning flag on the RC list. -- A value of 1 indicates the page has been reviewed. rc_patrolled tinyint unsigned NOT NULL default 0, - + -- Recorded IP address the edit was made from, if the -- $wgPutIPinRC option is enabled. rc_ip varbinary(40) NOT NULL default '', - + -- Text length in characters before -- and after the edit rc_old_len int, @@ -971,17 +1011,17 @@ CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp CREATE TABLE /*_*/watchlist ( -- Key to user.user_id wl_user int unsigned NOT NULL, - + -- Key to page_namespace/page_title -- Note that users may watch pages which do not exist yet, -- or existed in the past but have been deleted. wl_namespace int NOT NULL default 0, wl_title varchar(255) binary NOT NULL default '', - + -- Timestamp when user was last sent a notification e-mail; -- cleared when the user visits the page. wl_notificationtimestamp varbinary(14) - + ) /*$wgDBTableOptions*/; CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title); @@ -995,17 +1035,17 @@ CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title); CREATE TABLE /*_*/math ( -- Binary MD5 hash of the latex fragment, used as an identifier key. math_inputhash varbinary(16) NOT NULL, - + -- Not sure what this is, exactly... math_outputhash varbinary(16) NOT NULL, - + -- texvc reports how well it thinks the HTML conversion worked; -- if it's a low level the PNG rendering may be preferred. math_html_conservativeness tinyint NOT NULL, - + -- HTML output from texvc, if any math_html text, - + -- MathML output from texvc, if any math_mathml text ) /*$wgDBTableOptions*/; @@ -1024,10 +1064,10 @@ CREATE UNIQUE INDEX /*i*/math_inputhash ON /*_*/math (math_inputhash); CREATE TABLE /*_*/searchindex ( -- Key to page_id si_page int unsigned NOT NULL, - + -- Munged version of title si_title varchar(255) NOT NULL default '', - + -- Munged version of body text si_text mediumtext NOT NULL ) ENGINE=MyISAM; @@ -1043,16 +1083,22 @@ CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text); CREATE TABLE /*_*/interwiki ( -- The interwiki prefix, (e.g. "Meatball", or the language prefix "de") iw_prefix varchar(32) NOT NULL, - + -- The URL of the wiki, with "$1" as a placeholder for an article name. -- Any spaces in the name will be transformed to underscores before -- insertion. iw_url blob NOT NULL, - + + -- The URL of the file api.php + iw_api blob NOT NULL, + + -- The name of the database (for a connection to be established with wfGetLB( 'wikiid' )) + iw_wikiid varchar(64) NOT NULL, + -- A boolean value indicating whether the wiki is in this project -- (used, for example, to detect redirect loops) iw_local bool NOT NULL, - + -- Boolean value indicating whether interwiki transclusions are allowed. iw_trans tinyint NOT NULL default 0 ) /*$wgDBTableOptions*/; @@ -1066,10 +1112,10 @@ CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix); CREATE TABLE /*_*/querycache ( -- A key name, generally the base name of of the special page. qc_type varbinary(32) NOT NULL, - + -- Some sort of stored value. Sizes, counts... qc_value int unsigned NOT NULL default 0, - + -- Target namespace+title qc_namespace int NOT NULL default 0, qc_title varchar(255) binary NOT NULL default '' @@ -1110,25 +1156,25 @@ CREATE TABLE /*_*/logging ( -- action field, but only the type controls categorization. log_type varbinary(32) NOT NULL default '', log_action varbinary(32) NOT NULL default '', - + -- Timestamp. Duh. log_timestamp binary(14) NOT NULL default '19700101000000', - + -- 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 '', - + -- LF separated list of miscellaneous parameters log_params blob NOT NULL, @@ -1170,7 +1216,7 @@ CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page); -- Jobs performed by parallel apache threads or a command-line daemon CREATE TABLE /*_*/job ( job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, - + -- Command name -- Limited to 60 to prevent key length overflow job_cmd varbinary(60) NOT NULL default '', @@ -1223,14 +1269,14 @@ CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from); CREATE TABLE /*_*/querycachetwo ( -- A key name, generally the base name of of the special page. qcc_type varbinary(32) NOT NULL, - + -- Some sort of stored value. Sizes, counts... qcc_value int unsigned NOT NULL default 0, - + -- Target namespace+title qcc_namespace int NOT NULL default 0, qcc_title varchar(255) binary NOT NULL default '', - + -- Target namespace+title2 qcc_namespacetwo int NOT NULL default 0, qcc_titletwo varchar(255) binary NOT NULL default '' @@ -1292,7 +1338,8 @@ CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propnam -- A table to log updates, one text key row per update. CREATE TABLE /*_*/updatelog ( - ul_key varchar(255) NOT NULL PRIMARY KEY + ul_key varchar(255) NOT NULL PRIMARY KEY, + ul_value blob ) /*$wgDBTableOptions*/; @@ -1320,7 +1367,7 @@ CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_i -- 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 + -- RCID for the change ts_rc_id int NULL, -- LOGID for the change ts_log_id int NULL, @@ -1350,4 +1397,38 @@ CREATE TABLE /*_*/l10n_cache ( ) /*$wgDBTableOptions*/; CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key); +-- Table for storing JSON message blobs for the resource loader +CREATE TABLE /*_*/msg_resource ( + -- Resource name + mr_resource varbinary(255) NOT NULL, + -- Language code + mr_lang varbinary(32) NOT NULL, + -- JSON blob + mr_blob mediumblob NOT NULL, + -- Timestamp of last update + mr_timestamp binary(14) NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang); + +-- Table for administering which message is contained in which resource +CREATE TABLE /*_*/msg_resource_links ( + mrl_resource varbinary(255) NOT NULL, + -- Message key + mrl_message varbinary(255) NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource); + +-- Table for tracking which local files a module depends on that aren't +-- registered directly. +-- Currently only used for tracking images that CSS depends on +CREATE TABLE /*_*/module_deps ( + -- Module name + md_module varbinary(255) NOT NULL, + -- Skin name + md_skin varbinary(32) NOT NULL, + -- JSON blob with file dependencies + md_deps mediumblob NOT NULL +) /*$wgDBTableOptions*/; +CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin); + -- vim: sw=2 sts=2 et diff --git a/maintenance/tests/ApiSetup.php b/maintenance/tests/ApiSetup.php deleted file mode 100644 index 549d8aef..00000000 --- a/maintenance/tests/ApiSetup.php +++ /dev/null @@ -1,39 +0,0 @@ -<?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 deleted file mode 100644 index d098b1a2..00000000 --- a/maintenance/tests/ApiTest.php +++ /dev/null @@ -1,164 +0,0 @@ -<?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 deleted file mode 100644 index 444229e7..00000000 --- a/maintenance/tests/CdbTest.php +++ /dev/null @@ -1,79 +0,0 @@ -<?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 deleted file mode 100644 index 011ef798..00000000 --- a/maintenance/tests/DatabaseSqliteTest.php +++ /dev/null @@ -1,57 +0,0 @@ -<?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 deleted file mode 100644 index aa50de2e..00000000 --- a/maintenance/tests/DatabaseTest.php +++ /dev/null @@ -1,92 +0,0 @@ -<?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 deleted file mode 100644 index ec694241..00000000 --- a/maintenance/tests/GlobalTest.php +++ /dev/null @@ -1,212 +0,0 @@ -<?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 deleted file mode 100644 index 83734910..00000000 --- a/maintenance/tests/HttpTest.php +++ /dev/null @@ -1,567 +0,0 @@ -<?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 deleted file mode 100644 index 9db77f72..00000000 --- a/maintenance/tests/IPTest.php +++ /dev/null @@ -1,52 +0,0 @@ -<?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 deleted file mode 100644 index 9794a2a2..00000000 --- a/maintenance/tests/ImageFunctionsTest.php +++ /dev/null @@ -1,48 +0,0 @@ -<?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 deleted file mode 100644 index 22b396e7..00000000 --- a/maintenance/tests/LanguageConverterTest.php +++ /dev/null @@ -1,148 +0,0 @@ -<?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 deleted file mode 100644 index c5357f89..00000000 --- a/maintenance/tests/LicensesTest.php +++ /dev/null @@ -1,17 +0,0 @@ -<?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 deleted file mode 100644 index e57798be..00000000 --- a/maintenance/tests/LocalFileTest.php +++ /dev/null @@ -1,97 +0,0 @@ -<?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 deleted file mode 100644 index b2c0fb71..00000000 --- a/maintenance/tests/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# 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 deleted file mode 100644 index a545b3bb..00000000 --- a/maintenance/tests/MediaWikiParserTest.php +++ /dev/null @@ -1,283 +0,0 @@ -<?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 deleted file mode 100644 index e7acc338..00000000 --- a/maintenance/tests/MediaWiki_Setup.php +++ /dev/null @@ -1,28 +0,0 @@ -<?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 deleted file mode 100644 index b52e790e..00000000 --- a/maintenance/tests/README +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index 78fcc7c3..00000000 --- a/maintenance/tests/RevisionTest.php +++ /dev/null @@ -1,114 +0,0 @@ -<?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/RunSeleniumTests.php b/maintenance/tests/RunSeleniumTests.php new file mode 100644 index 00000000..2574f4b2 --- /dev/null +++ b/maintenance/tests/RunSeleniumTests.php @@ -0,0 +1,220 @@ +#!/usr/bin/php +<?php +/** + * @file + * @ingroup Maintenance + * @copyright Copyright © Wikimedia Deuschland, 2009 + * @author Hallo Welt! Medienwerkstatt GmbH + * @author Markus Glaser, Dan Nessett, Priyanka Dhanda + * initial idea by Daniel Kinzler + * + * 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 + */ + +define( 'SELENIUMTEST', true ); + +require_once( dirname( dirname( __FILE__ ) )."/Maintenance.php" ); +require_once( 'PHPUnit/Framework.php' ); +require_once( 'PHPUnit/Extensions/SeleniumTestCase.php' ); +include_once( 'PHPUnit/Util/Log/JUnit.php' ); +require_once( dirname( __FILE__ ) . "/selenium/SeleniumServerManager.php" ); + +class SeleniumTester extends Maintenance { + protected $selenium; + protected $serverManager; + protected $seleniumServerExecPath; + + public function __construct() { + parent::__construct(); + $this->mDescription = "Selenium Test Runner. For documentation, visit http://www.mediawiki.org/wiki/SeleniumFramework"; + $this->addOption( 'port', 'Port used by selenium server. Default: 4444', false, true ); + $this->addOption( 'host', 'Host selenium server. Default: $wgServer . $wgScriptPath', false, true ); + $this->addOption( 'testBrowser', 'The browser used during testing. Default: firefox', false, true ); + $this->addOption( 'wikiUrl', 'The Mediawiki installation to point to. Default: http://localhost', false, true ); + $this->addOption( 'username', 'The login username for sunning tests. Default: empty', false, true ); + $this->addOption( 'userPassword', 'The login password for running tests. Default: empty', false, true ); + $this->addOption( 'seleniumConfig', 'Location of the selenium config file. Default: empty', false, true ); + $this->addOption( 'list-browsers', 'List the available browsers.' ); + $this->addOption( 'verbose', 'Be noisier.' ); + $this->addOption( 'startserver', 'Start Selenium Server (on localhost) before the run.' ); + $this->addOption( 'stopserver', 'Stop Selenium Server (on localhost) after the run.' ); + $this->addOption( 'jUnitLogFile', 'Log results in a specified JUnit log file. Default: empty', false, true ); + $this->addOption( 'runAgainstGrid', 'The test will be run against a Selenium Grid. Default: false.', false, true ); + $this->deleteOption( 'dbpass' ); + $this->deleteOption( 'dbuser' ); + $this->deleteOption( 'globals' ); + $this->deleteOption( 'wiki' ); + } + + public function listBrowsers() { + $desc = "Available browsers:\n"; + + foreach ($this->selenium->getAvailableBrowsers() as $k => $v) { + $desc .= " $k => $v\n"; + } + + echo $desc; + } + + protected function startServer() { + if ( $this->seleniumServerExecPath == '' ) { + die ( "The selenium server exec path is not set in " . + "selenium_settings.ini. Cannot start server \n" . + "as requested - terminating RunSeleniumTests\n" ); + } + $this->serverManager = new SeleniumServerManager( 'true', + $this->selenium->getPort(), + $this->seleniumServerExecPath ); + switch ( $this->serverManager->start() ) { + case 'started': + break; + case 'failed': + die ( "Unable to start the Selenium Server - " . + "terminating RunSeleniumTests\n" ); + case 'running': + echo ( "Warning: The Selenium Server is " . + "already running\n" ); + break; + } + + return; + } + + protected function stopServer() { + if ( !isset ( $this->serverManager ) ) { + echo ( "Warning: Request to stop Selenium Server, but it was " . + "not stared by RunSeleniumTests\n" . + "RunSeleniumTests cannot stop a Selenium Server it " . + "did not start\n" ); + } else { + switch ( $this->serverManager->stop() ) { + case 'stopped': + break; + case 'failed': + echo ( "unable to stop the Selenium Server\n" ); + } + } + return; + } + + protected function runTests( $seleniumTestSuites = array() ) { + $result = new PHPUnit_Framework_TestResult; + $result->addListener( new SeleniumTestListener( $this->selenium->getLogger() ) ); + if ( $this->selenium->getJUnitLogFile() ) { + $jUnitListener = new PHPUnit_Util_Log_JUnit( $this->selenium->getJUnitLogFile(), true ); + $result->addListener( $jUnitListener ); + } + + foreach ( $seleniumTestSuites as $testSuiteName => $testSuiteFile ) { + require( $testSuiteFile ); + $suite = new $testSuiteName(); + $suite->setName( $testSuiteName ); + $suite->addTests(); + + try { + $suite->run( $result ); + } catch ( Testing_Selenium_Exception $e ) { + $suite->tearDown(); + throw new MWException( $e->getMessage() ); + } + } + + if ( $this->selenium->getJUnitLogFile() ) { + $jUnitListener->flush(); + } + } + + public function execute() { + global $wgServer, $wgScriptPath, $wgHooks; + + $seleniumSettings = array(); + $seleniumBrowsers = array(); + $seleniumTestSuites = array(); + + $configFile = $this->getOption( 'seleniumConfig', '' ); + if ( strlen( $configFile ) > 0 ) { + $this->output("Using Selenium Configuration file: " . $configFile . "\n"); + SeleniumConfig::getSeleniumSettings( $seleniumSettings, + $seleniumBrowsers, + $seleniumTestSuites, + $configFile ); + } else if ( !isset( $wgHooks['SeleniumSettings'] ) ) { + $this->output("No command line configuration file or configuration hook found.\n"); + SeleniumConfig::getSeleniumSettings( $seleniumSettings, + $seleniumBrowsers, + $seleniumTestSuites + ); + } else { + $this->output("Using 'SeleniumSettings' hook for configuration.\n"); + wfRunHooks('SeleniumSettings', array( $seleniumSettings, + $seleniumBrowsers, + $seleniumTestSuites ) ); + } + + // State for starting/stopping the Selenium server has nothing to do with the Selenium + // class. Keep this state local to SeleniumTester class. Using getOption() is clumsy, but + // the Maintenance class does not have a setOption() + if ( isset( $seleniumSettings['startserver'] ) ) $this->getOption( 'startserver', true ); + if ( isset( $seleniumSettings['stopserver'] ) ) $this->getOption( 'stopserver', true ); + if ( !isset( $seleniumSettings['seleniumserverexecpath'] ) ) $seleniumSettings['seleniumserverexecpath'] = ''; + $this->seleniumServerExecPath = $seleniumSettings['seleniumserverexecpath']; + + //set reasonable defaults if we did not find the settings + if ( !isset( $seleniumBrowsers ) ) $seleniumBrowsers = array ('firefox' => '*firefox'); + if ( !isset( $seleniumSettings['host'] ) ) $seleniumSettings['host'] = $wgServer . $wgScriptPath; + if ( !isset( $seleniumSettings['port'] ) ) $seleniumSettings['port'] = '4444'; + if ( !isset( $seleniumSettings['wikiUrl'] ) ) $seleniumSettings['wikiUrl'] = 'http://localhost'; + if ( !isset( $seleniumSettings['username'] ) ) $seleniumSettings['username'] = ''; + if ( !isset( $seleniumSettings['userPassword'] ) ) $seleniumSettings['userPassword'] = ''; + if ( !isset( $seleniumSettings['testBrowser'] ) ) $seleniumSettings['testBrowser'] = 'firefox'; + if ( !isset( $seleniumSettings['jUnitLogFile'] ) ) $seleniumSettings['jUnitLogFile'] = false; + if ( !isset( $seleniumSettings['runAgainstGrid'] ) ) $seleniumSettings['runAgainstGrid'] = false; + + // Setup Selenium class + $this->selenium = new Selenium( ); + $this->selenium->setAvailableBrowsers( $seleniumBrowsers ); + $this->selenium->setRunAgainstGrid( $this->getOption( 'runAgainstGrid', $seleniumSettings['runAgainstGrid'] ) ); + $this->selenium->setUrl( $this->getOption( 'wikiUrl', $seleniumSettings['wikiUrl'] ) ); + $this->selenium->setBrowser( $this->getOption( 'testBrowser', $seleniumSettings['testBrowser'] ) ); + $this->selenium->setPort( $this->getOption( 'port', $seleniumSettings['port'] ) ); + $this->selenium->setHost( $this->getOption( 'host', $seleniumSettings['host'] ) ); + $this->selenium->setUser( $this->getOption( 'username', $seleniumSettings['username'] ) ); + $this->selenium->setPass( $this->getOption( 'userPassword', $seleniumSettings['userPassword'] ) ); + $this->selenium->setVerbose( $this->hasOption( 'verbose' ) ); + $this->selenium->setJUnitLogFile( $this->getOption( 'jUnitLogFile', $seleniumSettings['jUnitLogFile'] ) ); + + if( $this->hasOption( 'list-browsers' ) ) { + $this->listBrowsers(); + exit(0); + } + if ( $this->hasOption( 'startserver' ) ) { + $this->startServer(); + } + + $logger = new SeleniumTestConsoleLogger; + $this->selenium->setLogger( $logger ); + + $this->runTests( $seleniumTestSuites ); + + if ( $this->hasOption( 'stopserver' ) ) { + $this->stopServer(); + } + } +} + +$maintClass = "SeleniumTester"; + +require_once( DO_MAINTENANCE ); diff --git a/maintenance/tests/SanitizerTest.php b/maintenance/tests/SanitizerTest.php deleted file mode 100644 index 8a2287d5..00000000 --- a/maintenance/tests/SanitizerTest.php +++ /dev/null @@ -1,73 +0,0 @@ -<?php - - -class SanitizerTest extends PHPUnit_Framework_TestCase { - - function setUp() { - AutoLoader::loadClass( 'Sanitizer' ); - } - - function testDecodeNamedEntities() { - $this->assertEquals( - "\xc3\xa9cole", - Sanitizer::decodeCharReferences( 'école' ), - 'decode named entities' - ); - } - - function testDecodeNumericEntities() { - $this->assertEquals( - "\xc4\x88io bonas dans l'\xc3\xa9cole!", - Sanitizer::decodeCharReferences( "Ĉio bonas dans l'école!" ), - 'decode numeric entities' - ); - } - - function testDecodeMixedEntities() { - $this->assertEquals( - "\xc4\x88io bonas dans l'\xc3\xa9cole!", - Sanitizer::decodeCharReferences( "Ĉio bonas dans l'école!" ), - 'decode mixed numeric/named entities' - ); - } - - function testDecodeMixedComplexEntities() { - $this->assertEquals( - "\xc4\x88io bonas dans l'\xc3\xa9cole! (mais pas Ĉio dans l'école)", - Sanitizer::decodeCharReferences( - "Ĉio bonas dans l'école! (mais pas &#x108;io dans l'&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( "�" ), '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 deleted file mode 100644 index 0cae2d42..00000000 --- a/maintenance/tests/SearchEngineTest.php +++ /dev/null @@ -1,138 +0,0 @@ -<?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 deleted file mode 100644 index 526f6216..00000000 --- a/maintenance/tests/SearchMySQLTest.php +++ /dev/null @@ -1,26 +0,0 @@ -<?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 deleted file mode 100644 index d21319a4..00000000 --- a/maintenance/tests/SearchUpdateTest.php +++ /dev/null @@ -1,103 +0,0 @@ -<?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 deleted file mode 100644 index 791b6fe5..00000000 --- a/maintenance/tests/SiteConfigurationTest.php +++ /dev/null @@ -1,311 +0,0 @@ -<?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 deleted file mode 100644 index bbd697bf..00000000 --- a/maintenance/tests/TimeAdjustTest.php +++ /dev/null @@ -1,40 +0,0 @@ -<?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 deleted file mode 100644 index 5b42c1c5..00000000 --- a/maintenance/tests/TitleTest.php +++ /dev/null @@ -1,17 +0,0 @@ -<?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 deleted file mode 100644 index 330e60c6..00000000 --- a/maintenance/tests/XmlTest.php +++ /dev/null @@ -1,115 +0,0 @@ -<?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 <there> you & 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" <>="<>">', - 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"><txt></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 deleted file mode 100644 index 019bee07..00000000 --- a/maintenance/tests/bootstrap.php +++ /dev/null @@ -1,15 +0,0 @@ -<?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/parser/ExtraParserTests.txt b/maintenance/tests/parser/ExtraParserTests.txt Binary files differnew file mode 100644 index 00000000..66b8032d --- /dev/null +++ b/maintenance/tests/parser/ExtraParserTests.txt diff --git a/maintenance/tests/parser/parserTest.inc b/maintenance/tests/parser/parserTest.inc new file mode 100644 index 00000000..c553550c --- /dev/null +++ b/maintenance/tests/parser/parserTest.inc @@ -0,0 +1,1305 @@ +<?php +# 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 +# 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 more independent of the configuration (and if possible the database) + * @todo document + * @file + * @ingroup Maintenance + */ + +/** + * @ingroup Maintenance + */ +class ParserTest { + /** + * boolean $color whereas output should be colorized + */ + private $color; + + /** + * boolean $showOutput Show test output + */ + private $showOutput; + + /** + * boolean $useTemporaryTables Use temporary tables for the temporary database + */ + private $useTemporaryTables = true; + + /** + * boolean $databaseSetupDone True if the database has been set up + */ + private $databaseSetupDone = false; + + /** + * string $oldTablePrefix Original table prefix + */ + private $oldTablePrefix; + + private $maxFuzzTestLength = 300; + private $fuzzSeed = 0; + private $memoryLimit = 50; + private $uploadDir = null; + + public $regex = ""; + private $savedGlobals = array(); + /** + * Sets terminal colorization and diff/quick modes depending on OS and + * command-line options (--color and --quick). + */ + public function ParserTest( $options = array() ) { + # Only colorize output if stdout is a terminal. + $this->color = !wfIsWindows() && posix_isatty( 1 ); + + if ( isset( $options['color'] ) ) { + switch( $options['color'] ) { + case 'no': + $this->color = false; + break; + case 'yes': + default: + $this->color = true; + break; + } + } + + $this->term = $this->color + ? new AnsiTermColorer() + : new DummyTermColorer(); + + $this->showDiffs = !isset( $options['quick'] ); + $this->showProgress = !isset( $options['quiet'] ); + $this->showFailure = !( + isset( $options['quiet'] ) + && ( isset( $options['record'] ) + || isset( $options['compare'] ) ) ); // redundant output + + $this->showOutput = isset( $options['show-output'] ); + + + if ( isset( $options['regex'] ) ) { + if ( isset( $options['record'] ) ) { + echo "Warning: --record cannot be used with --regex, disabling --record\n"; + unset( $options['record'] ); + } + $this->regex = $options['regex']; + } else { + # Matches anything + $this->regex = ''; + } + + $this->setupRecorder( $options ); + $this->keepUploads = isset( $options['keep-uploads'] ); + + if ( isset( $options['seed'] ) ) { + $this->fuzzSeed = intval( $options['seed'] ) - 1; + } + + $this->runDisabled = isset( $options['run-disabled'] ); + + $this->hooks = array(); + $this->functionHooks = array(); + self::setUp(); + } + + static function setUp() { + global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgDeferredUpdateList, + $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache, + $wgMessageCache, $wgUseDatabaseMessages, $wgMsgCacheExpiry, $parserMemc, + $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo, + $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' => wfTempDir() . '/test-repo', + 'url' => 'http://example.com/images', + 'deletedDir' => wfTempDir() . '/test-repo/delete', + '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 User; + $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 ) ); + if ( $wgStyleDirectory === false ) { + $wgStyleDirectory = "$IP/skins"; + } + + } + + public function setupRecorder ( $options ) { + if ( isset( $options['record'] ) ) { + $this->recorder = new DbTestRecorder( $this ); + $this->recorder->version = isset( $options['setversion'] ) ? + $options['setversion'] : SpecialVersion::getVersion(); + } elseif ( isset( $options['compare'] ) ) { + $this->recorder = new DbTestPreviewer( $this ); + } elseif ( isset( $options['upload'] ) ) { + $this->recorder = new RemoteTestRecorder( $this ); + } else { + $this->recorder = new TestRecorder( $this ); + } + } + + /** + * Remove last character if it is a newline + * @group utility + */ + static public function chomp( $s ) { + if ( substr( $s, -1 ) === "\n" ) { + return substr( $s, 0, -1 ); + } + else { + return $s; + } + } + + /** + * Run a fuzz test series + * Draw input from a set of test files + */ + function fuzzTest( $filenames ) { + $GLOBALS['wgContLang'] = Language::factory( 'en' ); + $dict = $this->getFuzzInput( $filenames ); + $dictSize = strlen( $dict ); + $logMaxLength = log( $this->maxFuzzTestLength ); + $this->setupDatabase(); + ini_set( 'memory_limit', $this->memoryLimit * 1048576 ); + + $numTotal = 0; + $numSuccess = 0; + $user = new User; + $opts = ParserOptions::newFromUser( $user ); + $title = Title::makeTitle( NS_MAIN, 'Parser_test' ); + + while ( true ) { + // Generate test input + mt_srand( ++$this->fuzzSeed ); + $totalLength = mt_rand( 1, $this->maxFuzzTestLength ); + $input = ''; + + while ( strlen( $input ) < $totalLength ) { + $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength; + $hairLength = min( intval( exp( $logHairLength ) ), $dictSize ); + $offset = mt_rand( 0, $dictSize - $hairLength ); + $input .= substr( $dict, $offset, $hairLength ); + } + + $this->setupGlobals(); + $parser = $this->getParser(); + + // Run the test + try { + $parser->parse( $input, $title, $opts ); + $fail = false; + } catch ( Exception $exception ) { + $fail = true; + } + + if ( $fail ) { + echo "Test failed with seed {$this->fuzzSeed}\n"; + echo "Input:\n"; + var_dump( $input ); + echo "\n\n"; + echo "$exception\n"; + } else { + $numSuccess++; + } + + $numTotal++; + $this->teardownGlobals(); + $parser->__destruct(); + + if ( $numTotal % 100 == 0 ) { + $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 ); + echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n"; + if ( $usage > 90 ) { + echo "Out of memory:\n"; + $memStats = $this->getMemoryBreakdown(); + + foreach ( $memStats as $name => $usage ) { + echo "$name: $usage\n"; + } + $this->abort(); + } + } + } + } + + /** + * Get an input dictionary from a set of parser test files + */ + function getFuzzInput( $filenames ) { + $dict = ''; + + foreach ( $filenames as $filename ) { + $contents = file_get_contents( $filename ); + preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches ); + + foreach ( $matches[1] as $match ) { + $dict .= $match . "\n"; + } + } + + return $dict; + } + + /** + * Get a memory usage breakdown + */ + function getMemoryBreakdown() { + $memStats = array(); + + foreach ( $GLOBALS as $name => $value ) { + $memStats['$' . $name] = strlen( serialize( $value ) ); + } + + $classes = get_declared_classes(); + + foreach ( $classes as $class ) { + $rc = new ReflectionClass( $class ); + $props = $rc->getStaticProperties(); + $memStats[$class] = strlen( serialize( $props ) ); + $methods = $rc->getMethods(); + + foreach ( $methods as $method ) { + $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) ); + } + } + + $functions = get_defined_functions(); + + foreach ( $functions['user'] as $function ) { + $rf = new ReflectionFunction( $function ); + $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) ); + } + + asort( $memStats ); + + return $memStats; + } + + function abort() { + $this->abort(); + } + + /** + * Run a series of tests listed in the given text files. + * Each test consists of a brief description, wikitext input, + * and the expected HTML output. + * + * Prints status updates on stdout and counts up the total + * number and percentage of passed tests. + * + * @param $filenames Array of strings + * @return Boolean: true if passed all tests, false if any tests failed. + */ + public function runTestsFromFiles( $filenames ) { + $ok = false; + $GLOBALS['wgContLang'] = Language::factory( 'en' ); + $this->recorder->start(); + try { + $this->setupDatabase(); + $ok = true; + + foreach ( $filenames as $filename ) { + $tests = new TestFileIterator( $filename, $this ); + $ok = $this->runTests( $tests ) && $ok; + } + + $this->teardownDatabase(); + $this->recorder->report(); + } catch (DBError $e) { + echo $e->getMessage(); + } + $this->recorder->end(); + + return $ok; + } + + function runTests( $tests ) { + $ok = true; + + foreach ( $tests as $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; + } + + /** + * Get a Parser object + */ + function getParser( $preprocessor = null ) { + global $wgParserConf; + + $class = $wgParserConf['class']; + $parser = new $class( array( 'preprocessorClass' => $preprocessor ) + $wgParserConf ); + + foreach ( $this->hooks as $tag => $callback ) { + $parser->setHook( $tag, $callback ); + } + + foreach ( $this->functionHooks as $tag => $bits ) { + list( $callback, $flags ) = $bits; + $parser->setFunctionHook( $tag, $callback, $flags ); + } + + wfRunHooks( 'ParserTestParser', array( &$parser ) ); + + return $parser; + } + + /** + * Run a given wikitext input through a freshly-constructed wiki parser, + * and compare the output against the expected results. + * Prints status and explanatory messages to stdout. + * + * @param $desc String: test's description + * @param $input String: wikitext to try rendering + * @param $result String: result to output + * @param $opts Array: test's options + * @param $config String: overrides for global variables, one per line + * @return Boolean + */ + 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 ( isset( $opts['title'] ) ) { + $titleText = $opts['title']; + } + else { + $titleText = 'Parser test'; + } + + $local = isset( $opts['local'] ); + $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null; + $parser = $this->getParser( $preprocessor ); + $title = Title::newFromText( $titleText ); + + if ( isset( $opts['pst'] ) ) { + $out = $parser->preSaveTransform( $input, $title, $user, $options ); + } elseif ( isset( $opts['msg'] ) ) { + $out = $parser->transformMsg( $input, $options ); + } elseif ( isset( $opts['section'] ) ) { + $section = $opts['section']; + $out = $parser->getSection( $input, $section ); + } 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 ); + } elseif ( isset( $opts['preload'] ) ) { + $out = $parser->getpreloadText( $input, $title, $options ); + } else { + $output = $parser->parse( $input, $title, $options, true, true, 1337 ); + $out = $output->getText(); + + if ( isset( $opts['showtitle'] ) ) { + if ( $output->getTitleText() ) { + $title = $output->getTitleText(); + } + + $out = "$title\n$out"; + } + + if ( isset( $opts['ill'] ) ) { + $out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) ); + } elseif ( isset( $opts['cat'] ) ) { + global $wgOut; + + $wgOut->addCategoryLinks( $output->getCategories() ); + $cats = $wgOut->getCategoryLinks(); + + if ( isset( $cats['normal'] ) ) { + $out = $this->tidy( implode( ' ', $cats['normal'] ) ); + } else { + $out = ''; + } + } + + $result = $this->tidy( $result ); + } + + $this->teardownGlobals(); + return $this->showTestResult( $desc, $result, $out ); + } + + /** + * + */ + function showTestResult( $desc, $result, $out ) { + if ( $result === $out ) { + $this->showSuccess( $desc ); + return true; + } else { + $this->showFailure( $desc, $result, $out ); + return false; + } + } + + /** + * Use a regex to find out the value of an option + * @param $key String: name of option val to retrieve + * @param $opts Options array to look in + * @param $default Mixed: default value returned if not found + */ + 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(); + // 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 ) { + 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; + + # Find out values for some special options. + $lang = + self::getOptionValue( 'language', $opts, 'en' ); + $variant = + self::getOptionValue( 'variant', $opts, false ); + $maxtoclevel = + self::getOptionValue( 'wgMaxTocLevel', $opts, 999 ); + $linkHolderBatchSize = + self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 ); + + $settings = array( + 'wgServer' => 'http://Britney-Spears', + 'wgScript' => '/index.php', + 'wgScriptPath' => '/', + 'wgArticlePath' => '/wiki/$1', + 'wgActionPaths' => array(), + 'wgLocalFileRepo' => array( + 'class' => 'LocalRepo', + 'name' => 'local', + 'directory' => $this->uploadDir, + 'url' => 'http://example.com/images', + 'hashLevels' => 2, + 'transformVia404' => false, + ), + 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ), + 'wgStylePath' => '/skins', + 'wgStyleSheetPath' => '/skins', + 'wgSitename' => 'MediaWiki', + 'wgLanguageCode' => $lang, + 'wgDBprefix' => $wgDBtype != 'oracle' ? 'parsertest_' : 'pt_', + 'wgRawHtml' => isset( $opts['rawhtml'] ), + 'wgLang' => null, + 'wgContLang' => null, + 'wgNamespacesWithSubpages' => array( 0 => isset( $opts['subpage'] ) ), + 'wgMaxTocLevel' => $maxtoclevel, + 'wgCapitalLinks' => true, + 'wgNoFollowLinks' => true, + 'wgNoFollowDomainExceptions' => array(), + 'wgThumbnailScriptPath' => false, + 'wgUseImageResize' => false, + 'wgUseTeX' => isset( $opts['math'] ), + 'wgMathDirectory' => $this->uploadDir . '/math', + 'wgLocaltimezone' => 'UTC', + 'wgAllowExternalImages' => true, + 'wgUseTidy' => false, + 'wgDefaultLanguageVariant' => $variant, + 'wgVariantArticlePath' => false, + 'wgGroupPermissions' => array( '*' => array( + 'createaccount' => true, + 'read' => true, + 'edit' => true, + 'createpage' => true, + 'createtalk' => true, + ) ), + 'wgNamespaceProtection' => array( NS_MEDIAWIKI => 'editinterface' ), + 'wgDefaultExternalStore' => array(), + 'wgForeignFileRepos' => array(), + 'wgLinkHolderBatchSize' => $linkHolderBatchSize, + '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 ) { + 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; + + global $wgHooks; + + $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; + $wgHooks['ParserTestParser'][] = 'ParserTestStaticParserHook::setup'; + $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; + + MagicWord::clearCache(); + + global $wgUser; + $wgUser = new User(); + } + + /** + * List of temporary tables to create, without prefix. + * Some of these probably aren't necessary. + */ + private function listTables() { + global $wgDBtype; + + $tables = array( 'user', 'user_properties', 'page', 'page_restrictions', + 'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks', + 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks', + 'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage', + 'recentchanges', 'watchlist', 'math', 'interwiki', 'logging', + 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo', + 'archive', 'user_groups', 'page_props', 'category', 'msg_resource', 'msg_resource_links' + ); + + if ( in_array( $wgDBtype, array( 'mysql', 'sqlite', 'oracle' ) ) ) + 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. + wfRunHooks( 'ParserTestTables', array( &$tables ) ); + + return $tables; + } + + /** + * Set up a temporary set of wiki tables to work with for the tests. + * Currently this will only be done once per run, and any changes to + * the db will be visible to later tests in the run. + */ + public function setupDatabase() { + global $wgDBprefix, $wgDBtype; + + if ( $this->databaseSetupDone ) { + return; + } + + if ( $wgDBprefix === 'parsertest_' || ( $wgDBtype == 'oracle' && $wgDBprefix === 'pt_' ) ) { + throw new MWException( 'setupDatabase should be called before setupGlobals' ); + } + + $this->databaseSetupDone = true; + $this->oldTablePrefix = $wgDBprefix; + + # SqlBagOStuff broke when using temporary tables on r40209 (bug 15892). + # It seems to have been fixed since (r55079?). + # If it fails, $wgCaches[CACHE_DB] = new HashBagOStuff(); should work around it. + + # CREATE TEMPORARY TABLE breaks if there is more than one server + if ( wfGetLB()->getServerCount() != 1 ) { + $this->useTemporaryTables = false; + } + + $temporary = $this->useTemporaryTables || $wgDBtype == 'postgres'; + + $db = wfGetDB( DB_MASTER ); + $tables = $this->listTables(); + + 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 ( $wgDBtype == 'mysql' ) { + $db->query( "DROP TABLE IF EXISTS $newTableName" ); + } elseif ( in_array( $wgDBtype, array( 'postgres', 'oracle' ) ) ) { + /* DROPs wouldn't work due to Foreign Key Constraints (bug 14990, r58669) + * Use "DROP TABLE IF EXISTS $newTableName CASCADE" for postgres? That + * syntax would also work for mysql. + */ + } elseif ( $db->tableExists( $tbl ) ) { + $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( $wgDBtype != 'oracle' ? 'parsertest_' : 'pt_' ); + + if ( $wgDBtype == 'oracle' ) { + # Insert 0 user to prevent FK violations + + # Anonymous user + $db->insert( 'user', array( + 'user_id' => 0, + 'user_name' => 'Anonymous' ) ); + } + + # Hack: insert a few Wikipedia in-project interwiki prefixes, + # for testing inter-language links + $db->insert( 'interwiki', array( + array( 'iw_prefix' => 'wikipedia', + 'iw_url' => 'http://en.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 0 ), + array( 'iw_prefix' => 'meatball', + 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 0 ), + array( 'iw_prefix' => 'zh', + 'iw_url' => 'http://zh.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + array( 'iw_prefix' => 'es', + 'iw_url' => 'http://es.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + array( 'iw_prefix' => 'fr', + 'iw_url' => 'http://fr.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + array( 'iw_prefix' => 'ru', + 'iw_url' => 'http://ru.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + ) ); + + + # Update certain things in site_stats + $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 ); + + $this->uploadDir = $this->setupUploadDir(); + $user = User::createNew( 'WikiSysop' ); + $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) ); + $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', array( + 'size' => 12345, + 'width' => 1941, + 'height' => 220, + 'bits' => 24, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '', 16, 36, 31 ), + 'fileExists' => true + ), $db->timestamp( '20010115123500' ), $user ); + + # This image will be blacklisted in [[MediaWiki:Bad image list]] + $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) ); + $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', array( + 'size' => 12345, + 'width' => 320, + 'height' => 240, + 'bits' => 24, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '', 16, 36, 31 ), + 'fileExists' => true + ), $db->timestamp( '20010115123500' ), $user ); + } + + /** + * Change the table prefix on all open DB connections/ + */ + protected function changePrefix( $prefix ) { + global $wgDBprefix; + wfGetLBFactory()->forEachLB( array( $this, 'changeLBPrefix' ), array( $prefix ) ); + $wgDBprefix = $prefix; + } + + public function changeLBPrefix( $lb, $prefix ) { + $lb->forEachOpenConnection( array( $this, 'changeDBPrefix' ), array( $prefix ) ); + } + + public function changeDBPrefix( $db, $prefix ) { + $db->tablePrefix( $prefix ); + } + + public function teardownDatabase() { + global $wgDBtype; + + if ( !$this->databaseSetupDone ) { + $this->teardownGlobals(); + return; + } + $this->teardownUploadDir( $this->uploadDir ); + + $this->changePrefix( $this->oldTablePrefix ); + $this->databaseSetupDone = false; + + if ( $this->useTemporaryTables ) { + # Don't need to do anything + $this->teardownGlobals(); + return; + } + + $tables = $this->listTables(); + $db = wfGetDB( DB_MASTER ); + + foreach ( $tables as $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;' ); + + $this->teardownGlobals(); + } + + /** + * 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; + } + + /** + * Restore default values and perform any necessary clean-up + * after each test runs. + */ + private function teardownGlobals() { + RepoGroup::destroySingleton(); + LinkCache::singleton()->clear(); + + foreach ( $this->savedGlobals as $var => $val ) { + $GLOBALS[$var] = $val; + } + } + + /** + * 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", + + "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png", + ) + ); + + 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/math/f/a/5", + "$dir/math/f/a", + "$dir/math/f", + "$dir/math", + "$dir", + ) + ); + } + + /** + * Delete the specified files, if they exist. + * @param $files Array: 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 $dirs Array: full paths to directories to delete. + */ + private static function deleteDirs( $dirs ) { + foreach ( $dirs as $dir ) { + if ( is_dir( $dir ) ) { + rmdir( $dir ); + } + } + } + + /** + * "Running test $desc..." + */ + protected function showTesting( $desc ) { + print "Running test $desc... "; + } + + /** + * Print a happy success message. + * + * @param $desc String: the test name + * @return Boolean + */ + protected function showSuccess( $desc ) { + if ( $this->showProgress ) { + print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n"; + } + + return true; + } + + /** + * Print a failure message and provide some explanatory output + * about what went wrong if so configured. + * + * @param $desc String: the test name + * @param $result String: expected HTML output + * @param $html String: actual HTML output + * @return Boolean + */ + protected function showFailure( $desc, $result, $html ) { + if ( $this->showFailure ) { + if ( !$this->showProgress ) { + # In quiet mode we didn't show the 'Testing' message before the + # test, in case it succeeded. Show it now: + $this->showTesting( $desc ); + } + + print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n"; + + if ( $this->showOutput ) { + print "--- Expected ---\n$result\n--- Actual ---\n$html\n"; + } + + if ( $this->showDiffs ) { + print $this->quickDiff( $result, $html ); + if ( !$this->wellFormed( $html ) ) { + print "XML error: $this->mXmlError\n"; + } + } + } + + return false; + } + + /** + * Run given strings through a diff and return the (colorized) output. + * Requires writable /tmp directory and a 'diff' command in the PATH. + * + * @param $input String + * @param $output String + * @param $inFileTail String: tailing for the input file name + * @param $outFileTail String: tailing for the output file name + * @return String + */ + protected function quickDiff( $input, $output, $inFileTail = 'expected', $outFileTail = 'actual' ) { + $prefix = wfTempDir() . "/mwParser-" . mt_rand(); + + $infile = "$prefix-$inFileTail"; + $this->dumpToFile( $input, $infile ); + + $outfile = "$prefix-$outFileTail"; + $this->dumpToFile( $output, $outfile ); + + $diff = `diff -au $infile $outfile`; + unlink( $infile ); + unlink( $outfile ); + + return $this->colorDiff( $diff ); + } + + /** + * Write the given string to a file, adding a final newline. + * + * @param $data String + * @param $filename String + */ + private function dumpToFile( $data, $filename ) { + $file = fopen( $filename, "wt" ); + fwrite( $file, $data . "\n" ); + fclose( $file ); + } + + /** + * Colorize unified diff output if set for ANSI color output. + * Subtractions are colored blue, additions red. + * + * @param $text String + * @return String + */ + protected function colorDiff( $text ) { + return preg_replace( + array( '/^(-.*)$/m', '/^(\+.*)$/m' ), + array( $this->term->color( 34 ) . '$1' . $this->term->reset(), + $this->term->color( 31 ) . '$1' . $this->term->reset() ), + $text ); + } + + /** + * Show "Reading tests from ..." + * + * @param $path String + */ + public function showRunFile( $path ) { + print $this->term->color( 1 ) . + "Reading tests from \"$path\"..." . + $this->term->reset() . + "\n"; + } + + /** + * Insert a temporary test article + * @param $name String: the title, including any prefix + * @param $text String: the article text + * @param $line Integer: the input line number, for reporting errors + */ + static public function addArticle( $name, $text, $line = 'unknown' ) { + global $wgCapitalLinks; + + $text = self::chomp($text); + + $oldCapitalLinks = $wgCapitalLinks; + $wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637 + + $name = self::chomp( $name ); + $title = Title::newFromText( $name ); + + if ( is_null( $title ) ) { + wfDie( "invalid title ('$name' => '$title') at line $line\n" ); + } + + $aid = $title->getArticleID( Title::GAID_FOR_UPDATE ); + + if ( $aid != 0 ) { + debug_print_backtrace(); + wfDie( "duplicate article '$name' at line $line\n" ); + } + + $art = new Article( $title ); + $art->insertNewArticle( $text, '', false, false ); + + $wgCapitalLinks = $oldCapitalLinks; + } + + /** + * Steal a callback function from the primary parser, save it for + * application to our scary parser. If the hook is not installed, + * abort processing of this file. + * + * @param $name String + * @return Bool true if tag hook is present + */ + 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 { + echo " This test suite requires the '$name' hook extension, skipping.\n"; + return false; + } + + return true; + } + + /** + * Steal a callback function from the primary parser, save it for + * application to our scary parser. If the hook is not installed, + * abort processing of this file. + * + * @param $name String + * @return Bool true if function hook is present + */ + public function requireFunctionHook( $name ) { + global $wgParser; + + $wgParser->firstCallInit( ); // make sure hooks are loaded. + + if ( isset( $wgParser->mFunctionHooks[$name] ) ) { + $this->functionHooks[$name] = $wgParser->mFunctionHooks[$name]; + } else { + echo " This test suite requires the '$name' function hook extension, skipping.\n"; + return false; + } + + return true; + } + + /* + * Run the "tidy" command on text if the $wgUseTidy + * global is true + * + * @param $text String: the text to tidy + * @return String + * @static + */ + private function tidy( $text ) { + global $wgUseTidy; + + if ( $wgUseTidy ) { + $text = MWTidy::tidy( $text ); + } + + return $text; + } + + private function wellFormed( $text ) { + $html = + Sanitizer::hackDocType() . + '<html>' . + $text . + '</html>'; + + $parser = xml_parser_create( "UTF-8" ); + + # case folding violates XML standard, turn it off + xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); + + if ( !xml_parse( $parser, $html, true ) ) { + $err = xml_error_string( xml_get_error_code( $parser ) ); + $position = xml_get_current_byte_index( $parser ); + $fragment = $this->extractFragment( $html, $position ); + $this->mXmlError = "$err at byte $position:\n$fragment"; + xml_parser_free( $parser ); + + return false; + } + + xml_parser_free( $parser ); + + return true; + } + + private function extractFragment( $text, $position ) { + $start = max( 0, $position - 10 ); + $before = $position - $start; + $fragment = '...' . + $this->term->color( 34 ) . + substr( $text, $start, $before ) . + $this->term->color( 0 ) . + $this->term->color( 31 ) . + $this->term->color( 1 ) . + substr( $text, $position, 1 ) . + $this->term->color( 0 ) . + $this->term->color( 34 ) . + substr( $text, $position + 1, 9 ) . + $this->term->color( 0 ) . + '...'; + $display = str_replace( "\n", ' ', $fragment ); + $caret = ' ' . + str_repeat( ' ', $before ) . + $this->term->color( 31 ) . + '^' . + $this->term->color( 0 ); + + return "$display\n$caret"; + } + + static function getFakeTimestamp( &$parser, &$ts ) { + $ts = 123; + return true; + } +} diff --git a/maintenance/parserTests.txt b/maintenance/tests/parser/parserTests.txt index 4515943a..b3fa560c 100644 --- a/maintenance/parserTests.txt +++ b/maintenance/tests/parser/parserTests.txt @@ -53,6 +53,12 @@ Template:! | !! endarticle +!!article +MediaWiki:bad image list +!!text +* [[File:Bad.jpg]] except [[Nasty page]] +!!endarticle + ### ### Basic tests ### @@ -370,6 +376,21 @@ Regression with preformatted in <center> !! end +# Expected output in the following test is not really expected (there should be +# <pre> in the output) -- it's only testing for well-formedness. +!! test +Bug 6200: Preformatted in <blockquote> +!! input +<blockquote> + Blah +</blockquote> +!! result +<blockquote> + Blah +</blockquote> + +!! end + !! test <pre> with attributes (bug 3202) !! input @@ -406,6 +427,59 @@ Regression with preformatted in <center> !! end +!! test +<nowiki> inside <pre> (bug 13238) +!! input +<pre> +<nowiki> +</pre> +<pre> +<nowiki></nowiki> +</pre> +<pre><nowiki><nowiki></nowiki>Foo<nowiki></nowiki></nowiki></pre> +!! result +<pre> +<nowiki> +</pre> +<pre> + +</pre> +<pre><nowiki>Foo</nowiki></pre> + +!! end + +!! test +<nowiki> and <pre> preference (first one wins) +!! input +<pre> +<nowiki> +</pre> +</nowiki> +</pre> + +<nowiki> +<pre> +<nowiki> +</pre> +</nowiki> +</pre> + +!! result +<pre> +<nowiki> +</pre> +<p></nowiki> +</pre> +</p><p> +<pre> +<nowiki> +</pre> + +</pre> +</p> +!! end + + ### ### Definition lists ### @@ -414,7 +488,7 @@ Simple definition !! input ; name : Definition !! result -<dl><dt> name </dt><dd> Definition +<dl><dt> name </dt><dd> Definition </dd></dl> !! end @@ -444,7 +518,7 @@ Definition list with URL link !! input ; http://example.com/ : definition !! result -<dl><dt> <a href="http://example.com/" class="external free" rel="nofollow">http://example.com/</a> </dt><dd> definition +<dl><dt> <a href="http://example.com/" class="external free" rel="nofollow">http://example.com/</a> </dt><dd> definition </dd></dl> !! end @@ -495,7 +569,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" rel="nofollow">Wikipedia : 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 : The Next Generation</a></dt><dd> OK, I made that up </dd></dl> !! end @@ -516,7 +590,7 @@ Definition lists: self-closed tag !! input ;one<br/>two : two-line fun !! result -<dl><dt>one<br />two </dt><dd> two-line fun +<dl><dt>one<br />two </dt><dd> two-line fun </dd></dl> !! end @@ -865,18 +939,6 @@ BUG 289: literal double quote in bracketed URL !!end !! test -External links: invalid character -Fixme: the missing char seems to have gone missing -!! options -disabled -!! input -[http://www.example.com test] -!! result -<p>[<a href="http://www.example.com" class="external free" rel="nofollow">http://www.example.com</a> test] -</p> -!! end - -!! test External links: multiple legal whitespace is fine, Magnus. Don't break it please. (bug 5081) !! input [http://www.example.com test] @@ -1026,6 +1088,15 @@ External link containing double-single-quotes in text embedded in italics (bug 4 !! end !! test +External link containing double-single-quotes with no space separating the url from text in italics +!! input +[http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm''La muerte de Casagemas'' (1901) en el sitio de [[Museo Picasso (París)|Museo Picasso]].] +!! result +<p><a href="http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm" class="external text" rel="nofollow"><i>La muerte de Casagemas</i> (1901) en el sitio de <a href="/index.php?title=Museo_Picasso_(Par%C3%ADs)&action=edit&redlink=1" class="new" title="Museo Picasso (París) (page does not exist)">Museo Picasso</a>.</a> +</p> +!! end + +!! test URL-encoding in URL functions (single parameter) !! input {{localurl:Some page|amp=&}} @@ -1136,10 +1207,12 @@ Simple table !! result <table> <tr> -<td> 1 </td><td> 2 +<td> 1 </td> +<td> 2 </td></tr> <tr> -<td> 3 </td><td> 4 +<td> 3 </td> +<td> 4 </td></tr></table> !! end @@ -1172,27 +1245,45 @@ Multiplication table <caption>Multiplication table </caption> <tr> -<th> × </th><th> 1 </th><th> 2 </th><th> 3 +<th> × </th> +<th> 1 </th> +<th> 2 </th> +<th> 3 </th></tr> <tr> <th> 1 -</th><td> 1 </td><td> 2 </td><td> 3 +</th> +<td> 1 </td> +<td> 2 </td> +<td> 3 </td></tr> <tr> <th> 2 -</th><td> 2 </td><td> 4 </td><td> 6 +</th> +<td> 2 </td> +<td> 4 </td> +<td> 6 </td></tr> <tr> <th> 3 -</th><td> 3 </td><td> 6 </td><td> 9 +</th> +<td> 3 </td> +<td> 6 </td> +<td> 9 </td></tr> <tr> <th> 4 -</th><td> 4 </td><td> 8 </td><td> 12 +</th> +<td> 4 </td> +<td> 8 </td> +<td> 12 </td></tr> <tr> <th> 5 -</th><td> 5 </td><td> 10 </td><td> 15 +</th> +<td> 5 </td> +<td> 10 </td> +<td> 15 </td></tr></table> !! end @@ -1212,12 +1303,15 @@ Table rowspan <table align="right" border="1"> <tr> <td> Cell 1, row 1 -</td><td rowspan="2"> Cell 2, row 1 (and 2) -</td><td> Cell 3, row 1 +</td> +<td rowspan="2"> Cell 2, row 1 (and 2) +</td> +<td> Cell 3, row 1 </td></tr> <tr> <td> Cell 1, row 2 -</td><td> Cell 3, row 2 +</td> +<td> Cell 3, row 2 </td></tr></table> !! end @@ -1239,7 +1333,8 @@ Nested table <table border="1"> <tr> <td> α -</td><td> +</td> +<td> <table bgcolor="#ABCDEF" border="2"> <tr> <td>nested @@ -1247,7 +1342,8 @@ Nested table <tr> <td>table </td></tr></table> -</td><td>the original table again +</td> +<td>the original table again </td></tr></table> !! end @@ -1267,20 +1363,16 @@ Invalid attributes in table cell (bug 1830) !! end -# FIXME: It's not clear at all that this is the result we want, but the actual -# output right now is invalid XML, so clearly something is wrong. The result -# 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 !! result <table> <tr> -<td><a href="ftp://|x||" class="external autonumber" title="ftp://|x||" rel="nofollow">[1]</a></td><td>" onmouseover="alert(document.cookie)">test +<td>[<a href="ftp://%7Cx" class="external free" rel="nofollow">ftp://%7Cx</a></td> +<td>]" onmouseover="alert(document.cookie)">test </td> </tr> </table> @@ -1296,7 +1388,7 @@ Plain link, capitalized !! input [[Main Page]] !! result -<p><a href="/wiki/Main_Page" title="Main Page">Main Page</a> +<p><a href="/wiki/Main_Page">Main Page</a> </p> !! end @@ -1305,7 +1397,7 @@ Plain link, uncapitalized !! input [[main Page]] !! result -<p><a href="/wiki/Main_Page" title="Main Page">main Page</a> +<p><a href="/wiki/Main_Page">main Page</a> </p> !! end @@ -1359,7 +1451,7 @@ Link with prefix !! input xxx[[main Page]], xxx[[Main Page]], Xxx[[main Page]] XXX[[main Page]], XXX[[Main Page]] !! result -<p>xxx<a href="/wiki/Main_Page" title="Main Page">main Page</a>, xxx<a href="/wiki/Main_Page" title="Main Page">Main Page</a>, Xxx<a href="/wiki/Main_Page" title="Main Page">main Page</a> XXX<a href="/wiki/Main_Page" title="Main Page">main Page</a>, XXX<a href="/wiki/Main_Page" title="Main Page">Main Page</a> +<p>xxx<a href="/wiki/Main_Page">main Page</a>, xxx<a href="/wiki/Main_Page">Main Page</a>, Xxx<a href="/wiki/Main_Page">main Page</a> XXX<a href="/wiki/Main_Page">main Page</a>, XXX<a href="/wiki/Main_Page">Main Page</a> </p> !! end @@ -1368,7 +1460,7 @@ Link with suffix !! input [[Main Page]]xxx, [[Main Page]]XXX, [[Main Page]]!!! !! result -<p><a href="/wiki/Main_Page" title="Main Page">Main Pagexxx</a>, <a href="/wiki/Main_Page" title="Main Page">Main Page</a>XXX, <a href="/wiki/Main_Page" title="Main Page">Main Page</a>!!! +<p><a href="/wiki/Main_Page" title="Main Page">Main Pagexxx</a>, <a href="/wiki/Main_Page">Main Page</a>XXX, <a href="/wiki/Main_Page">Main Page</a>!!! </p> !! end @@ -1474,12 +1566,10 @@ 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 -<p><a href="/index.php?title=Lista_d%27%27e_paise_d%27%27o_munno&action=edit" class="new" title="Lista d''e paise d''o munno">Lista d''e paise d''o munno</a> +<p><a href="/index.php?title=Lista_d%27%27e_paise_d%27%27o_munno&action=edit&redlink=1" class="new" title="Lista d''e paise d''o munno (page does not exist)">Lista d''e paise d''o munno</a> </p> !! end @@ -1497,7 +1587,25 @@ Link containing double-single-quotes '' in text embedded in italics (bug 4598 sa !! input ''Some [[Link|pretty ''italics'' and stuff]]! !! result -<p><i>Some </i><a href="/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)"><i>pretty </i>italics<i> and stuff</i></a><i>!</i> +<p><i>Some <a href="/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">pretty <i>italics</i> and stuff</a>!</i> +</p> +!! end + +!! test +Link with double quotes in title part (literal) and alternate part (interpreted) +!! input +[[File:Denys Savchenko ''Pentecoste''.jpg]] + +[[''Pentecoste'']] + +[[''Pentecoste''|Pentecoste]] + +[[''Pentecoste''|''Pentecoste'']] +!! result +<p><a href="/index.php?title=Special:Upload&wpDestFile=Denys_Savchenko_%27%27Pentecoste%27%27.jpg" class="new" title="File:Denys Savchenko ''Pentecoste''.jpg">File:Denys Savchenko <i>Pentecoste</i>.jpg</a> +</p><p><a href="/index.php?title=%27%27Pentecoste%27%27&action=edit&redlink=1" class="new" title="''Pentecoste'' (page does not exist)">''Pentecoste''</a> +</p><p><a href="/index.php?title=%27%27Pentecoste%27%27&action=edit&redlink=1" class="new" title="''Pentecoste'' (page does not exist)">Pentecoste</a> +</p><p><a href="/index.php?title=%27%27Pentecoste%27%27&action=edit&redlink=1" class="new" title="''Pentecoste'' (page does not exist)"><i>Pentecoste</i></a> </p> !! end @@ -1526,7 +1634,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" rel="nofollow">example URL</a>] +<p>Piped link to URL: [<a href="http://www.example.com%7Can" class="external text" rel="nofollow">example URL</a>] </p> !! end @@ -1585,7 +1693,7 @@ title=[[0]] !!input [[00]] !!result -<p><a href="/wiki/00" title="00">00</a> +<p><a href="/wiki/00">00</a> </p> !!end @@ -1617,7 +1725,7 @@ Inline interwiki link !! input [[MeatBall:SoftSecurity]] !! result -<p><a href="http://www.usemod.com/cgi-bin/mb.pl?SoftSecurity" class="extiw" title="meatball:SoftSecurity">MeatBall:SoftSecurity</a> +<p><a href="http://www.usemod.com/cgi-bin/mb.pl?SoftSecurity" class="extiw">MeatBall:SoftSecurity</a> </p> !! end @@ -1626,7 +1734,7 @@ Inline interwiki link with empty title (bug 2372) !! input [[MeatBall:]] !! result -<p><a href="http://www.usemod.com/cgi-bin/mb.pl?" class="extiw" title="meatball:">MeatBall:</a> +<p><a href="http://www.usemod.com/cgi-bin/mb.pl?" class="extiw">MeatBall:</a> </p> !! end @@ -1982,15 +2090,6 @@ title=[[User:Ævar Arnfjörð Bjarmason]] !! end !! test -Magic Word: {{NUMBEROFARTICLES}} -!! input -{{NUMBEROFARTICLES}} -!! result -<p>2 -</p> -!! end - -!! test Magic Word: {{NUMBEROFFILES}} !! input {{NUMBEROFFILES}} @@ -2044,7 +2143,7 @@ Magic Word: {{SERVER}} !! input {{SERVER}} !! result -<p><a href="http://localhost" class="external free" rel="nofollow">http://localhost</a> +<p><a href="http://Britney-Spears" class="external free" rel="nofollow">http://Britney-Spears</a> </p> !! end @@ -2177,6 +2276,21 @@ language=de !! end +!! test +Urlencode +!! input +{{urlencode:hi world?!}} +{{urlencode:hi world?!|WIKI}} +{{urlencode:hi world?!|PATH}} +{{urlencode:hi world?!|QUERY}} +!! result +<p>hi+world%3F%21 +hi_world%3F! +hi%20world%3F%21 +hi+world%3F%21 +</p> +!! end + ### ### Magic links ### @@ -2494,7 +2608,7 @@ Template as link source !! input [[{{linktest2}}]] !! result -<p><a href="/wiki/Main_Page" title="Main Page">Main Page</a> +<p><a href="/wiki/Main_Page">Main Page</a> </p> !! end @@ -2516,7 +2630,7 @@ Template infinite loop !! input {{loop1}} !! result -<p><span class="error">Template loop detected: <a href="/wiki/Template:Loop1" title="Template:Loop1">Template:Loop1</a></span> +<p><span class="error">Template loop detected: <a href="/wiki/Template:Loop1">Template:Loop1</a></span> </p> !! end @@ -2548,10 +2662,12 @@ foo {{table}} </p> <table> <tr> -<td> 1 </td><td> 2 +<td> 1 </td> +<td> 2 </td></tr> <tr> -<td> 3 </td><td> 4 +<td> 3 </td> +<td> 4 </td></tr></table> !! end @@ -2566,10 +2682,12 @@ foo </p> <table> <tr> -<td> 1 </td><td> 2 +<td> 1 </td> +<td> 2 </td></tr> <tr> -<td> 3 </td><td> 4 +<td> 3 </td> +<td> 4 </td></tr></table> !! end @@ -2588,7 +2706,7 @@ BUG 41: Template parameters shown as broken links Template:MSGNW test !! text ''None'' of '''this''' should be -* interepreted +* interpreted but rather passed unmodified {{test}} !! endarticle @@ -2602,7 +2720,7 @@ disabled {{msgnw:MSGNW test}} !! result <p>''None'' of '''this''' should be -* interepreted +* interpreted but rather passed unmodified {{test}} </p> @@ -3167,6 +3285,16 @@ Special:RecentChanges !! end !! test +{{#special:}} page name with subpage, known +!! options +msg +!! input +{{#special:Recentchanges/param}} +!! result +Special:RecentChanges/param +!! end + +!! test {{#special:}} page name, unknown !! options msg @@ -3260,14 +3388,21 @@ Image with link parameter (URL target) and unnamed parameter </p> !! end +!! test +Thumbnail image with link parameter +!! input +[[Image:foobar.jpg|thumb|link=http://example.com/|Title]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="http://example.com/"><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>Title</div></div></div> +!! end !! test 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"><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> +<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">Main Page</a></div></div></div> !! end @@ -3276,7 +3411,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"><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> +<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">Main Page</a></div></div></div> !! end @@ -3486,6 +3621,32 @@ Bug 3090: External links other than http: in image captions !! end +!! article +File:Barfoo.jpg +!! text +#REDIRECT [[File:Barfoo.jpg]] +!! endarticle + +!! test +Redirected image +!! input +[[Image:Barfoo.jpg]] +!! result +<p><a href="/wiki/File:Barfoo.jpg">File:Barfoo.jpg</a> +</p> +!! end + +!! test +Missing image with uploads disabled +!! options +wgEnableUploads=0 +!! input +[[Image:Foobaz.jpg]] +!! result +<p><a href="/wiki/File:Foobaz.jpg">File:Foobaz.jpg</a> +</p> +!! end + ### ### Subpages @@ -3552,7 +3713,7 @@ Link to category !! input [[:Category:MediaWiki User's Guide]] !! result -<p><a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">Category:MediaWiki User's Guide</a> +<p><a href="/wiki/Category:MediaWiki_User%27s_Guide">Category:MediaWiki User's Guide</a> </p> !! end @@ -3648,7 +3809,7 @@ Some text </ul> </li> </ul> -</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script> +</td></tr></table> <h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&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&action=edit&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&action=edit&section=3" title="Edit section: Skipping a level">edit</a>]</span> <span class="mw-headline" id="Skipping_a_level"> Skipping a level </span></h5> @@ -3703,7 +3864,7 @@ Handling of sections up to level 6 and beyond </ul> </li> </ul> -</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script> +</td></tr></table> <h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&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&action=edit&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&action=edit&section=3" title="Edit section: Level 3 Heading">edit</a>]</span> <span class="mw-headline" id="Level_3_Heading"> Level 3 Heading</span></h3> @@ -3745,7 +3906,7 @@ TOC regression (bug 9764) </ul> </li> </ul> -</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script> +</td></tr></table> <h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&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&action=edit&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&action=edit&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> @@ -3781,7 +3942,7 @@ wgMaxTocLevel=3 </ul> </li> </ul> -</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script> +</td></tr></table> <h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&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&action=edit&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&action=edit&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> @@ -3792,6 +3953,36 @@ wgMaxTocLevel=3 !! end !! test +TOC with wgMaxTocLevel=3 and two level four headings (bug 6204) +!! options +wgMaxTocLevel=3 +!! input +==Section 1== +===Section 1.1=== +====Section 1.1.1==== +====Section 1.1.1.1==== +==Section 2== +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a> +<ul> +<li class="toclevel-2 tocsection-2"><a href="#Section_1.1"><span class="tocnumber">1.1</span> <span class="toctext">Section 1.1</span></a></li> +</ul> +</li> +<li class="toclevel-1 tocsection-5"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 1">edit</a>]</span> <span class="mw-headline" id="Section_1">Section 1</span></h2> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Section 1.1">edit</a>]</span> <span class="mw-headline" id="Section_1.1">Section 1.1</span></h3> +<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Section 1.1.1">edit</a>]</span> <span class="mw-headline" id="Section_1.1.1">Section 1.1.1</span></h4> +<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Section 1.1.1.1">edit</a>]</span> <span class="mw-headline" id="Section_1.1.1.1">Section 1.1.1.1</span></h4> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Section 2">edit</a>]</span> <span class="mw-headline" id="Section_2">Section 2</span></h2> + +!! end + + +!! test Resolving duplicate section names !! input == Foo bar == @@ -3873,7 +4064,7 @@ __TOC__ </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>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script> +</td></tr></table> <h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&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&action=edit&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&action=edit&section=3" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline" id="title_2"> title 2 </span></h2> @@ -3890,6 +4081,71 @@ http://example.com [[Image:foobar.jpg]] !!end !! test +Short headings with trailing space should match behaviour of Parser::doHeadings (bug 19910) +!! input +=== +The line above must have a trailing space! +=== <!-- +--> <!-- --> +But just in case it doesn't... +!! result +<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: =">edit</a>]</span> <span class="mw-headline" id=".3D">=</span></h1> +<p>The line above must have a trailing space! +</p> +<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: =">edit</a>]</span> <span class="mw-headline" id=".3D_2">=</span></h1> +<p>But just in case it doesn't... +</p> +!! end + +!! test +Header with special characters (bug 25462) +!! input +The tooltips shall not show entities to the user (ie. be double escaped) + +== text > text == +section 1 + +== text < text == +section 2 + +== text & text == +section 3 + +== text ' text == +section 4 + +== text " text == +section 5 +!! result +<p>The tooltips shall not show entities to the user (ie. be double escaped) +</p> +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#text_.3E_text"><span class="tocnumber">1</span> <span class="toctext">text > text</span></a></li> +<li class="toclevel-1 tocsection-2"><a href="#text_.3C_text"><span class="tocnumber">2</span> <span class="toctext">text < text</span></a></li> +<li class="toclevel-1 tocsection-3"><a href="#text_.26_text"><span class="tocnumber">3</span> <span class="toctext">text & text</span></a></li> +<li class="toclevel-1 tocsection-4"><a href="#text_.27_text"><span class="tocnumber">4</span> <span class="toctext">text ' text</span></a></li> +<li class="toclevel-1 tocsection-5"><a href="#text_.22_text"><span class="tocnumber">5</span> <span class="toctext">text " text</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: text > text">edit</a>]</span> <span class="mw-headline" id="text_.3E_text"> text > text </span></h2> +<p>section 1 +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: text < text">edit</a>]</span> <span class="mw-headline" id="text_.3C_text"> text < text </span></h2> +<p>section 2 +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: text & text">edit</a>]</span> <span class="mw-headline" id="text_.26_text"> text & text </span></h2> +<p>section 3 +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: text ' text">edit</a>]</span> <span class="mw-headline" id="text_.27_text"> text ' text </span></h2> +<p>section 4 +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: text " text">edit</a>]</span> <span class="mw-headline" id="text_.22_text"> text " text </span></h2> +<p>section 5 +</p> +!! end + +!! test BUG 1219 URL next to image (broken) !! input http://example.com[[Image:foobar.jpg]] @@ -4104,6 +4360,31 @@ Character reference normalization in link text (bug 1938) </p> !!end +!! article +אַ +!! text +Test for unicode normalization + +The page's name is U+05d0 U+05b7, with non-canonical form U+FB2E +!! endarticle + +!! test +(bug 19451) Links should refer to the normalized form. +!! input +[[אַ]] +[[אַ]] +[[אַ]] +[[אַ]] +[[אַ]] +!! result +<p><a href="/wiki/%D7%90%D6%B7" title="אַ">אַ</a> +<a href="/wiki/%D7%90%D6%B7" title="אַ">אַ</a> +<a href="/wiki/%D7%90%D6%B7" title="אַ">אַ</a> +<a href="/wiki/%D7%90%D6%B7" title="אַ">אַ</a> +<a href="/wiki/%D7%90%D6%B7">אַ</a> +</p> +!! end + !! test Empty attribute crash test (bug 2067) !! input @@ -4163,7 +4444,7 @@ Bug 2095: link with three closing brackets !! input [[Main Page]]] !! result -<p><a href="/wiki/Main_Page" title="Main Page">Main Page</a>] +<p><a href="/wiki/Main_Page">Main Page</a>] </p> !! end @@ -4771,7 +5052,7 @@ disabled !! input <br id=9 /> !! result -Something, but defenetly not <br id="9" />... +Something, but definitely not <br id="9" />... !! end !! test @@ -4825,7 +5106,7 @@ Punctuation: nbsp before exclamation !! input C'est grave ! !! result -<p>C'est grave ! +<p>C'est grave ! </p> !! end @@ -5074,7 +5355,7 @@ http://<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2>< <ul> <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>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script> +</td></tr></table> !! end @@ -5102,7 +5383,10 @@ noxml !! result <table> <tr> -<th>https://</th><th></th><th></th><th> +<th>https://</th> +<th></th> +<th></th> +<th> </td> </tr> </table> @@ -5119,7 +5403,8 @@ Fuzz testing: Parser21 <table> <tr> <th> <a href="irc://{{ftp://a" class="external free" rel="nofollow">irc://{{ftp://a</a>" onmouseover="alert('hello world');" -</th><td> +</th> +<td> </td> </tr> </table> @@ -5350,7 +5635,7 @@ Special page transclusion !! result <p><br /> </p> -<table border="0" id="mw-prefixindex-list-table"><tr><td><a href="/wiki/Xyzzyx" title="Xyzzyx">Xyzzyx</a></td></tr></table> +<table border="0" id="mw-prefixindex-list-table"><tr><td><a href="/wiki/Xyzzyx">Xyzzyx</a></td></tr></table> !! end @@ -5363,10 +5648,10 @@ Special page transclusion twice (bug 5021) !! result <p><br /> </p> -<table border="0" id="mw-prefixindex-list-table"><tr><td><a href="/wiki/Xyzzyx" title="Xyzzyx">Xyzzyx</a></td></tr></table> +<table border="0" id="mw-prefixindex-list-table"><tr><td><a href="/wiki/Xyzzyx">Xyzzyx</a></td></tr></table> <p><br /> </p> -<table border="0" id="mw-prefixindex-list-table"><tr><td><a href="/wiki/Xyzzyx" title="Xyzzyx">Xyzzyx</a></td></tr></table> +<table border="0" id="mw-prefixindex-list-table"><tr><td><a href="/wiki/Xyzzyx">Xyzzyx</a></td></tr></table> !! end @@ -5811,6 +6096,22 @@ section=1 ==marked== !!end +# Test behaviour of bug 19910 +!! test +Sectiion with all-equals +!! options +section=2 +!! input +=== +The line above must have a trailing space +=== <!-- +--> <!-- --> +But just in case it doesn't... +!! result +=== <!-- +--> <!-- --> +But just in case it doesn't... +!! end !! test Section replacement test (section 0) @@ -6245,7 +6546,7 @@ Special:Search page linking. !! input {{Special:search}} !! result -<p><a href="/wiki/Special:Search" title="Special:Search">Special:Search</a> +<p><a href="/wiki/Special:Search">Special:Search</a> </p> !! end @@ -6306,48 +6607,129 @@ image4 |300px| centre * image6 </gallery> !! result -<table class="gallery" cellspacing="0" cellpadding="0"> - <tr> - <td><div class="gallerybox" style="width: 155px;"> - <div style="height: 152px;">Image1.png</div> +<ul class="gallery"> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Image1.png</div> <div class="gallerytext"> </div> - </div></td> - <td><div class="gallerybox" style="width: 155px;"> - <div style="height: 152px;">Image2.gif</div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Image2.gif</div> <div class="gallerytext"> <p>|||| </p> </div> - </div></td> - <td><div class="gallerybox" style="width: 155px;"> - <div style="height: 152px;">Image3</div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Image3</div> <div class="gallerytext"> </div> - </div></td> - <td><div class="gallerybox" style="width: 155px;"> - <div style="height: 152px;">Image4</div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Image4</div> <div class="gallerytext"> <p>300px| centre </p> </div> - </div></td> - </tr> - <tr> - <td><div class="gallerybox" style="width: 155px;"> - <div style="height: 152px;">Image5.svg</div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Image5.svg</div> <div class="gallerytext"> <p><a href="http://///////" class="external free" rel="nofollow">http://///////</a> </p> </div> - </div></td> - <td><div class="gallerybox" style="width: 155px;"> - <div style="height: 152px;">* image6</div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">* image6</div> <div class="gallerytext"> </div> - </div></td> - </tr> -</table> + </div></li> +</ul> + +!! end + +!! test +Gallery (with options) +!! input +<gallery widths='60px' heights='40px' perrow='2' caption='Foo [[Main Page]]' > +File:Nonexistant.jpg|caption +File:Nonexistant.jpg +image:foobar.jpg|some '''caption''' [[Main Page]] +image:foobar.jpg +</gallery> +!! result +<ul class="gallery" style="max-width: 206px;_width: 206px;"> + <li class='gallerycaption'>Foo <a href="/wiki/Main_Page">Main Page</a></li> + <li class="gallerybox" style="width: 95px"><div style="width: 95px"> + <div style="height: 70px;">Nonexistant.jpg</div> + <div class="gallerytext"> +<p>caption +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 95px"><div style="width: 95px"> + <div style="height: 70px;">Nonexistant.jpg</div> + <div class="gallerytext"> + </div> + </div></li> + <li class="gallerybox" style="width: 95px"><div style="width: 95px"> + <div class="thumb" style="width: 90px; height: 70px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="60" height="7" /></a></div></div> + <div class="gallerytext"> +<p>some <b>caption</b> <a href="/wiki/Main_Page">Main Page</a> +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 95px"><div style="width: 95px"> + <div class="thumb" style="width: 90px; height: 70px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="60" height="7" /></a></div></div> + <div class="gallerytext"> + </div> + </div></li> +</ul> + +!! end + +!! test +gallery (with showfilename option) +!! input +<gallery showfilename> +File:Nonexistant.jpg|caption +File:Nonexistant.jpg +image:foobar.jpg|some '''caption''' [[Main Page]] +File:Foobar.jpg +</gallery> +!! result +<ul class="gallery"> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Nonexistant.jpg</div> + <div class="gallerytext"> +<p><a href="/wiki/File:Nonexistant.jpg" title="File:Nonexistant.jpg">Nonexistant.jpg</a><br /> +caption +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Nonexistant.jpg</div> + <div class="gallerytext"> +<p><a href="/wiki/File:Nonexistant.jpg" title="File:Nonexistant.jpg">Nonexistant.jpg</a><br /> +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div class="thumb" style="width: 150px; height: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="gallerytext"> +<p><a href="/wiki/File:Foobar.jpg" title="File:Foobar.jpg">Foobar.jpg</a><br /> +some <b>caption</b> <a href="/wiki/Main_Page">Main Page</a> +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div class="thumb" style="width: 150px; height: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="gallerytext"> +<p><a href="/wiki/File:Foobar.jpg" title="File:Foobar.jpg">Foobar.jpg</a><br /> +</p> + </div> + </div></li> +</ul> !! end @@ -6398,6 +6780,15 @@ ISBN ISBN 1234567890 !! end !! test +Bug 22905: <abbr> followed by ISBN followed by </a> +!! input +<abbr>(fr)</abbr> ISBN 2753300917 [http://www.example.com example.com] +!! result +<p><abbr>(fr)</abbr> <a href="/wiki/Special:BookSources/2753300917" class="internal mw-magiclink-isbn">ISBN 2753300917</a> <a href="http://www.example.com" class="external text" rel="nofollow">example.com</a> +</p> +!! end + +!! test Double RFC !! input RFC RFC 1234 @@ -6511,12 +6902,10 @@ disabled # 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|¶m2=|x external] URL]] !! result -<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|&param2=|x" class="external text" 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="" 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=%7Cleft%7C&param2=%7Cx" class="external text" rel="nofollow">external</a> URL</div></div></div> !!end @@ -6568,12 +6957,14 @@ subpage title=[[Subpage test/L1/L2/L3]] !! test Parents of subpages, two levels up !! options -disabled subpage title=[[Subpage test/L1/L2/L3]] !! input [[../../|L1]]2 + +[[../../|L1]]l !! result -<p><a href="/index.php?title=Subpage_test/L1&action=edit&redlink=1" class="new" title="Subpage test/L1 (page does not exist)">L1</a> +<p><a href="/index.php?title=Subpage_test/L1&action=edit&redlink=1" class="new" title="Subpage test/L1 (page does not exist)">L1</a>2 +</p><p><a href="/index.php?title=Subpage_test/L1&action=edit&redlink=1" class="new" title="Subpage test/L1 (page does not exist)">L1l</a> </p> !! end @@ -6606,8 +6997,8 @@ Definition list code coverage ; title : def ;title: def !! result -<dl><dt> title </dt><dd> def -</dd><dt> title </dt><dd> def +<dl><dt> title  </dt><dd> def +</dd><dt> title </dt><dd> def </dd><dt>title</dt><dd> def </dd></dl> @@ -6703,7 +7094,7 @@ Out-of-order TOC heading levels </ul> </li> </ul> -</td></tr></table><script>if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script> +</td></tr></table> <h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&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&action=edit&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&action=edit&section=3" title="Edit section: 3">edit</a>]</span> <span class="mw-headline" id="3">3</span></h3> @@ -6782,6 +7173,67 @@ anchorencode </p> !! end +!! test +anchorencode trims spaces +!! input +{{anchorencode: __pretty__please__}} +!! result +<p>pretty_please +</p> +!! end + +!! test +anchorencode deals with links +!! input +{{anchorencode: [[hello|world]] [[hi]]}} +!! result +<p>world_hi +</p> +!! end + +!! test +anchorencode deals with templates +!! input +{{anchorencode: {{Foo}} }} +!! result +<p>FOO +</p> +!! end + +!! test +anchorencode encodes like the TOC generator: (bug 18431) +!! input +=== _ +:.3A%3A&&]] === +{{anchorencode: _ +:.3A%3A&&]] }} +__NOEDITSECTION__ +!! result +<h3> <span class="mw-headline" id=".2B:.3A.253A.26.26.5D.5D"> _ +:.3A%3A&&]] </span></h3> +<p>.2B:.3A.253A.26.26.5D.5D +</p> +!! end + +# Expected output in the following test is not necessarily expected (there +# should probably be <p> tags inside the <blockquote> in the output) -- it's +# only testing for well-formedness. +!! test +Bug 6200: blockquotes and paragraph formatting +!! input +<blockquote> +foo +</blockquote> + +bar + + baz +!! result +<blockquote> +foo +</blockquote> +<p>bar +</p> +<pre>baz +</pre> +!! end !! test Bug 8293: Use of center tag ruins paragraph formatting @@ -6839,7 +7291,7 @@ language=sr !! input [[Main Page]] can be written as [[Маин Паге]] same as [[Маин Паге]]. !! result -<p><a href="/wiki/Main_Page" title="Main Page">Main Page</a> can be written as <a href="/wiki/Main_Page" title="Main Page">Маин Паге</a> same as <a href="/wiki/Main_Page" title="Main Page">Маин Паге</a>. +<p><a href="/wiki/Main_Page">Main Page</a> can be written as <a href="/wiki/Main_Page" title="Main Page">Маин Паге</a> same as <a href="/wiki/Main_Page" title="Main Page">Маин Паге</a>. </p> !!end @@ -6934,7 +7386,7 @@ language=sr variant=sr-ec !! input -{[[Main Page]]}- !! result -<p><a href="/index.php?title=Main_Page&variant=sr-ec" title="Main Page">Main Page</a> +<p><a href="/wiki/Main_Page">Main Page</a> </p> !! end @@ -7115,10 +7567,12 @@ y </p> <table> <tr> -<td> 1 </td><td> 2 +<td> 1 </td> +<td> 2 </td></tr> <tr> -<td> 3 </td><td> 4 +<td> 3 </td> +<td> 4 </td></tr></table> <p>y </p> @@ -7284,6 +7738,20 @@ Line two !! end +!! test +Nesting tags, paragraphs on lines which begin with <div> +!! options +disabled +!! input +<div></div><strong>A +B</strong> +!! result +<div></div> +<p><strong>A +B</strong> +</p> +!! end + # Bug 6200: <blockquote> should behave like <div> with respect to line breaks !! test Bug 6200: paragraphs inside blockquotes (no extra line breaks) @@ -7420,9 +7888,9 @@ wgLinkHolderBatchSize=0 [[meatball:2]] [[meatball:3]] !! result -<p><a href="http://www.usemod.com/cgi-bin/mb.pl?1" class="extiw" title="meatball:1">meatball:1</a> -<a href="http://www.usemod.com/cgi-bin/mb.pl?2" class="extiw" title="meatball:2">meatball:2</a> -<a href="http://www.usemod.com/cgi-bin/mb.pl?3" class="extiw" title="meatball:3">meatball:3</a> +<p><a href="http://www.usemod.com/cgi-bin/mb.pl?1" class="extiw">meatball:1</a> +<a href="http://www.usemod.com/cgi-bin/mb.pl?2" class="extiw">meatball:2</a> +<a href="http://www.usemod.com/cgi-bin/mb.pl?3" class="extiw">meatball:3</a> </p> !! end @@ -7539,7 +8007,7 @@ 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 +I like the <a href="/wiki/Main_Page">Main Page</a> a lot !!end !! test @@ -7640,11 +8108,16 @@ title=[[Main Page]] <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 +Space normalisation on autocomment (bug 22784) +!! options +comment +title=[[Main Page]] +!!input +/* __hello__world__ */ +!! result +<span class="autocomment"><a href="/wiki/Main_Page#hello_world" title="Main Page">→</a>__hello__world__</span> +!! end !! test Bad images - basic functionality @@ -7762,6 +8235,75 @@ Screen <p>this is not the the title </p> !! end + +!! test +preload: check <noinclude> and <includeonly> +!! options +preload +!! input +Hello <noinclude>cruel</noinclude><includeonly>kind</includeonly> world. +!! result +Hello kind world. +!! end + +!! test +preload: check <onlyinclude> +!! options +preload +!! input +Goodbye <onlyinclude>Hello world</onlyinclude> +!! result +Hello world +!! end + +!! test +preload: can pass tags through if we want to +!! options +preload +!! input +<includeonly><</includeonly>includeonly>Hello world<includeonly><</includeonly>/includeonly> +!! result +<includeonly>Hello world</includeonly> +!! end + +!! test +preload: check that it doesn't try to do tricks +!! options +preload +!! input +* <!-- Hello --> ''{{world}}'' {{<includeonly>subst:</includeonly>How are you}}{{ {{{|safesubst:}}} #if:1|2|3}} +!! result +* <!-- Hello --> ''{{world}}'' {{subst:How are you}}{{ {{{|safesubst:}}} #if:1|2|3}} +!! end + +!! test +Play a bit with r67090 and bug 3158 +!! options +disabled +!! input +<div style="width:50% !important"> </div> +<div style="width:50% !important"> </div> +<div style="width:50% !important"> </div> +<div style="border : solid;"> </div> +!! result +<div style="width:50% !important"> </div> +<div style="width:50% !important"> </div> +<div style="width:50% !important"> </div> +<div style="border : solid;"> </div> + +!! end + +!! test +HTML5 data attributes +!! input +<span data-foo="bar">Baz</span> +<p data-abc-def_hij="">Quuz</p> +!! result +<p><span data-foo="bar">Baz</span> +</p> +<p data-abc-def_hij="">Quuz</p> + +!! end TODO: diff --git a/maintenance/tests/parser/parserTestsParserHook.php b/maintenance/tests/parser/parserTestsParserHook.php new file mode 100644 index 00000000..6387208a --- /dev/null +++ b/maintenance/tests/parser/parserTestsParserHook.php @@ -0,0 +1,46 @@ +<?php +/** + * A basic extension that's used by the parser tests to test whether input and + * arguments are passed to extensions properly. + * + * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> + */ + +class ParserTestParserHook { + + static function setup( &$parser ) { + $parser->setHook( 'tag', array( __CLASS__, 'hook' ) ); + + return true; + } + + static function hook( $in, $argv ) { + ob_start(); + var_dump( + $in, + $argv + ); + $ret = ob_get_clean(); + + return "<pre>\n$ret</pre>"; + } +} diff --git a/maintenance/tests/parser/parserTestsStaticParserHook.php b/maintenance/tests/parser/parserTestsStaticParserHook.php new file mode 100644 index 00000000..72f82276 --- /dev/null +++ b/maintenance/tests/parser/parserTestsStaticParserHook.php @@ -0,0 +1,58 @@ +<?php +/** + * A basic extension that's used by the parser tests to test whether the parser + * calls extensions when they're called inside comments, it shouldn't do that + * + * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> + */ + +class ParserTestStaticParserHook { + static function setup( &$parser ) { + $parser->setHook( 'statictag', array( __CLASS__, 'hook' ) ); + + return true; + } + + static function hook( $in, $argv, $parser ) { + if ( ! count( $argv ) ) { + $parser->static_tag_buf = $in; + return ''; + } else if ( count( $argv ) === 1 && isset( $argv['action'] ) + && $argv['action'] === 'flush' && $in === null ) + { + // Clear the buffer, we probably don't need to + if ( isset( $parser->static_tag_buf ) ) { + $tmp = $parser->static_tag_buf; + } else { + $tmp = ''; + } + $parser->static_tag_buf = null; + return $tmp; + } else + // wtf? + return + "\nCall this extension as <statictag>string</statictag> or as" . + " <statictag action=flush/>, not in any other way.\n" . + "text: " . var_export( $in, true ) . "\n" . + "argv: " . var_export( $argv, true ) . "\n"; + } +} diff --git a/maintenance/parserTests.php b/maintenance/tests/parserTests.php index 78530716..7793e6b8 100644 --- a/maintenance/parserTests.php +++ b/maintenance/tests/parserTests.php @@ -1,32 +1,36 @@ <?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 - /** + * MediaWiki parser test suite + * + * Copyright © 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 + * * @file * @ingroup Maintenance */ -/** */ -require('parserTests.inc'); +$options = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' ); +$optionsWithArgs = array( 'regex', 'seed', 'setversion' ); + +require_once( dirname( __FILE__ ) . '/../commandLine.inc' ); -if( isset( $options['help'] ) ) { - echo <<<ENDS +if ( isset( $options['help'] ) ) { + echo <<<ENDS MediaWiki $wgVersion parser test suite Usage: php parserTests.php [options...] @@ -35,13 +39,13 @@ Options: --quiet Suppress notification of passed tests (shows only failed tests) --show-output Show expected and actual output --color[=yes|no] Override terminal detection and force color output on or off - use wgCommandLineDarkBg = true; if your term is dark + use wgCommandLineDarkBg = true; if your term is dark --regex Only run tests whose descriptions which match given regex --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) + 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 @@ -50,7 +54,7 @@ Options: --upload Upload test results to remote wiki (per \$wgParserTestRemote) ENDS; - exit( 0 ); + exit( 0 ); } # Cases of weird db corruption were encountered when running tests on earlyish @@ -67,9 +71,9 @@ if ( $wgDBtype == 'sqlite' ) { # refer to $wgTitle directly, but instead use the title # passed to it. $wgTitle = Title::newFromText( 'Parser test script do not use' ); -$tester = new ParserTest(); +$tester = new ParserTest($options); -if( isset( $options['file'] ) ) { +if ( isset( $options['file'] ) ) { $files = array( $options['file'] ); } else { // Default parser tests and any set from extensions or local config @@ -84,5 +88,5 @@ if ( isset( $options['fuzz'] ) ) { $tester->fuzzTest( $files ); } else { $ok = $tester->runTestsFromFiles( $files ); - exit ($ok ? 0 : 1); + exit ( $ok ? 0 : 1 ); } diff --git a/maintenance/tests/phpunit.xml b/maintenance/tests/phpunit.xml deleted file mode 100644 index ce7d44f5..00000000 --- a/maintenance/tests/phpunit.xml +++ /dev/null @@ -1,17 +0,0 @@ -<!-- 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/selenium/Selenium.php b/maintenance/tests/selenium/Selenium.php new file mode 100644 index 00000000..ecf7f9ec --- /dev/null +++ b/maintenance/tests/selenium/Selenium.php @@ -0,0 +1,190 @@ +<?php +/** + * Selenium connector + * This is implemented as a singleton. + */ + +require( 'Testing/Selenium.php' ); + +class Selenium { + protected static $_instance = null; + + public $isStarted = false; + public $tester; + + protected $port; + protected $host; + protected $browser; + protected $browsers; + protected $logger; + protected $user; + protected $pass; + protected $timeout = 30000; + protected $verbose; + protected $junitlogfile; //processed by phpUnderControl + protected $runagainstgrid = false; + + /** + * @todo this shouldn't have to be static + */ + static protected $url; + + /** + * Override parent + */ + public function __construct() { + /** + * @todo this is an ugly hack to make information available to + * other tests. It should be fixed. + */ + if ( null === self::$_instance ) { + self::$_instance = $this; + } else { + throw new MWException( "Already have one Selenium instance." ); + } + } + + public function start() { + $this->tester = new Testing_Selenium( $this->browser, self::$url, $this->host, + $this->port, $this->timeout ); + if ( method_exists( $this->tester, "setVerbose" ) ) $this->tester->setVerbose( $this->verbose ); + + $this->tester->start(); + $this->isStarted = true; + } + + public function stop() { + $this->tester->stop(); + $this->tester = null; + $this->isStarted = false; + } + + public function login() { + if ( strlen( $this->user ) == 0 ) { + return; + } + $this->open( self::$url . '/index.php?title=Special:Userlogin' ); + $this->type( 'wpName1', $this->user ); + $this->type( 'wpPassword1', $this->pass ); + $this->click( "//input[@id='wpLoginAttempt']" ); + $this->waitForPageToLoad( 10000 ); + + // after login we redirect to the main page. So check whether the "Prefernces" top menu item exists + $value = $this->isElementPresent( "//li[@id='pt-preferences']" ); + + if ( $value != true ) { + throw new Testing_Selenium_Exception( "Login Failed" ); + } + + } + + public static function getInstance() { + if ( null === self::$_instance ) { + throw new MWException( "No instance set yet" ); + } + + return self::$_instance; + } + + public function loadPage( $title, $action ) { + $this->open( self::$url . '/index.php?title=' . $title . '&action=' . $action ); + } + + public function setLogger( $logger ) { + $this->logger = $logger; + } + + public function getLogger( ) { + return $this->logger; + } + + public function log( $message ) { + $this->logger->write( $message ); + } + + public function setUrl( $url ) { + self::$url = $url; + } + + static public function getUrl() { + return self::$url; + } + + public function setPort( $port ) { + $this->port = $port; + } + + public function getPort() { + return $this->port; + } + + public function setUser( $user ) { + $this->user = $user; + } + + // Function to get username + public function getUser() { + return $this->user; + } + + + public function setPass( $pass ) { + $this->pass = $pass; + } + + //add function to get password + public function getPass( ) { + return $this->pass; + } + + + public function setHost( $host ) { + $this->host = $host; + } + + public function setVerbose( $verbose ) { + $this->verbose = $verbose; + } + + public function setAvailableBrowsers( $availableBrowsers ) { + $this->browsers = $availableBrowsers; + } + + public function setJUnitLogfile( $junitlogfile ) { + $this->junitlogfile = $junitlogfile; + } + + public function getJUnitLogfile( ) { + return $this->junitlogfile; + } + + public function setRunAgainstGrid( $runagainstgrid ) { + $this->runagainstgrid = $runagainstgrid; + } + + public function setBrowser( $b ) { + if ($this->runagainstgrid) { + $this->browser = $b; + return true; + } + if ( !isset( $this->browsers[$b] ) ) { + throw new MWException( "Invalid Browser: $b.\n" ); + } + + $this->browser = $this->browsers[$b]; + } + + public function getAvailableBrowsers() { + return $this->browsers; + } + + public function __call( $name, $args ) { + $t = call_user_func_array( array( $this->tester, $name ), $args ); + return $t; + } + + // Prevent external cloning + protected function __clone() { } + // Prevent external construction + // protected function __construct() {} +} diff --git a/maintenance/tests/selenium/SeleniumConfig.php b/maintenance/tests/selenium/SeleniumConfig.php new file mode 100644 index 00000000..ca69b1f0 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumConfig.php @@ -0,0 +1,88 @@ +<?php +if ( !defined( 'SELENIUMTEST' ) ) { + die( 1 ); +} + +class SeleniumConfig { + + /* + * Retreives the Selenium configuration values from an ini file. + * See sample config file in selenium_settings.ini.sample + * + */ + + public static function getSeleniumSettings ( &$seleniumSettings, + &$seleniumBrowsers, + &$seleniumTestSuites, + $seleniumConfigFile = null ) { + if ( strlen( $seleniumConfigFile ) == 0 ) { + global $wgSeleniumConfigFile; + if ( isset( $wgSeleniumConfigFile ) ) $seleniumConfigFile = $wgSeleniumConfigFile ; + } + + if ( strlen( $seleniumConfigFile ) == 0 || !file_exists( $seleniumConfigFile ) ) { + throw new MWException( "Unable to read local Selenium Settings from " . $seleniumConfigFile . "\n" ); + } + + if ( !defined( 'PHP_VERSION_ID' ) || + ( PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3 ) ) { + $configArray = self::parse_5_2_ini_file( $seleniumConfigFile ); + } else { + $configArray = parse_ini_file( $seleniumConfigFile, true ); + } + if ( $configArray === false ) { + throw new MWException( "Error parsing " . $seleniumConfigFile . "\n" ); + } + + if ( array_key_exists( 'SeleniumSettings', $configArray) ) { + wfSuppressWarnings(); + //we may need to change how this is set. But for now leave it in the ini file + $seleniumBrowsers = $configArray['SeleniumSettings']['browsers']; + + $seleniumSettings['host'] = $configArray['SeleniumSettings']['host']; + $seleniumSettings['port'] = $configArray['SeleniumSettings']['port']; + $seleniumSettings['wikiUrl'] = $configArray['SeleniumSettings']['wikiUrl']; + $seleniumSettings['username'] = $configArray['SeleniumSettings']['username']; + $seleniumSettings['userPassword'] = $configArray['SeleniumSettings']['userPassword']; + $seleniumSettings['testBrowser'] = $configArray['SeleniumSettings']['testBrowser']; + $seleniumSettings['startserver'] = $configArray['SeleniumSettings']['startserver']; + $seleniumSettings['stopserver'] = $configArray['SeleniumSettings']['stopserver']; + $seleniumSettings['seleniumserverexecpath'] = $configArray['SeleniumSettings']['seleniumserverexecpath']; + $seleniumSettings['jUnitLogFile'] = $configArray['SeleniumSettings']['jUnitLogFile']; + $seleniumSettings['runAgainstGrid'] = $configArray['SeleniumSettings']['runAgainstGrid']; + + wfRestoreWarnings(); + } + if ( array_key_exists( 'SeleniumTests', $configArray) ) { + wfSuppressWarnings(); + $seleniumTestSuites = $configArray['SeleniumTests']['testSuite']; + wfRestoreWarnings(); + } + return true; + } + + private static function parse_5_2_ini_file ( $ConfigFile ) { + + $configArray = parse_ini_file( $ConfigFile, true ); + if ( $configArray === false ) return false; + + // PHP 5.2 ini files have [browsers] and [testSuite] sections + // to get around lack of support for array keys. It then + // inserts the section arrays into the appropriate places in + // the SeleniumSettings and SeleniumTests arrays. + + if ( isset( $configArray['browsers'] ) ) { + $configArray['SeleniumSettings']['browsers'] = $configArray['browsers']; + unset ( $configArray['browsers'] ); + } + + if ( isset( $configArray['testSuite'] ) ) { + $configArray['SeleniumTests']['testSuite'] = $configArray['testSuite']; + unset ( $configArray['testSuite'] ); + } + + return $configArray; + + } + +} diff --git a/maintenance/tests/selenium/SeleniumLoader.php b/maintenance/tests/selenium/SeleniumLoader.php new file mode 100644 index 00000000..8d5e7713 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumLoader.php @@ -0,0 +1,9 @@ +<?php + +class SeleniumLoader { + static function load() { + require_once( 'Testing/Selenium.php' ); + require_once( 'PHPUnit/Framework.php' ); + require_once( 'PHPUnit/Extensions/SeleniumTestCase.php' ); + } +} diff --git a/maintenance/tests/selenium/SeleniumServerManager.php b/maintenance/tests/selenium/SeleniumServerManager.php new file mode 100644 index 00000000..ae5ea682 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumServerManager.php @@ -0,0 +1,239 @@ +<?php +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + */ + +class SeleniumServerManager { + private $SeleniumStartServer = false; + private $OS = ''; + private $SeleniumServerPid = 'NaN'; + private $SeleniumServerPort = 4444; + private $SeleniumServerStartTimeout = 10; // 10 secs. + private $SeleniumServerExecPath; + + public function __construct( $startServer, + $serverPort, + $serverExecPath ) { + $this->OS = (string) PHP_OS; + if ( isset( $startServer ) ) + $this->SeleniumStartServer = $startServer; + if ( isset( $serverPort ) ) + $this->SeleniumServerPort = $serverPort; + if ( isset( $serverExecPath ) ) + $this->SeleniumServerExecPath = $serverExecPath; + return; + } + + // Getters for certain private attributes. No setters, since they + // should not change after the manager object is created. + + public function getSeleniumStartServer() { + return $this->SeleniumStartServer; + } + + public function getSeleniumServerPort() { + return $this->SeleniumServerPort; + } + + public function getSeleniumServerPid() { + return $this->SeleniumServerPid; + } + + // Changing value of SeleniumStartServer allows starting server after + // creation of the class instance. Only allow setting SeleniumStartServer + // to true, since after server is started, it is shut down by stop(). + + public function setSeleniumStartServer( $startServer ) { + if ( $startServer == true ) $this->SeleniumStartServer = true; + } + + // return values are: 1) started - server started, 2) failed - + // server not started, 3) running - instructed to start server, but + // server already running + + public function start() { + + if ( !$this->SeleniumStartServer ) return 'failed'; + + // commented out cases are untested + + switch ( $this->OS ) { + case "Linux": +# case' CYGWIN_NT-5.1': + case 'Darwin': +# case 'FreeBSD': +# case 'HP-UX': +# case 'IRIX64': +# case 'NetBSD': +# case 'OpenBSD': +# case 'SunOS': +# case 'Unix': + // *nix based OS + return $this->startServerOnUnix(); + break; + case "Windows": + case "WIN32": + case "WINNT": + // Windows + return $this->startServerOnWindows(); + break; + default: + // An untested OS + return 'failed'; + break; + } + } + + public function stop() { + + // commented out cases are untested + + switch ( $this->OS ) { + case "Linux": +# case' CYGWIN_NT-5.1': + case 'Darwin': +# case 'FreeBSD': +# case 'HP-UX': +# case 'IRIX64': +# case 'NetBSD': +# case 'OpenBSD': +# case 'SunOS': +# case 'Unix': + // *nix based OS + return $this->stopServerOnUnix(); + break; + case "Windows": + case "WIN32": + case "WINNT": + // Windows + return $this->stopServerOnWindows(); + break; + default: + // An untested OS + return 'failed'; + break; + } + } + + private function startServerOnUnix() { + + $output = array(); + $user = $_ENV['USER']; + // @fixme this should be a little more generalized :) + if (PHP_OS == 'Darwin') { + // Mac OS X's ps barfs on the 'w' param, but doesn't need it. + $ps = "ps -U %s"; + } else { + // Good on Linux + $ps = "ps -U %s w"; + } + $psCommand = sprintf($ps, escapeshellarg($user)); + exec($psCommand . " | grep -i selenium-server", $output); + + // Start server. If there is already a server running, + // return running. + + if ( isset( $this->SeleniumServerExecPath ) ) { + $found = 0; + foreach ( $output as $string ) { + $found += preg_match( + '~^(.*)java(.+)-jar(.+)selenium-server~', + $string ); + } + if ( $found == 0 ) { + + // Didn't find the selenium server. Start it up. + // First set up comamand line suffix. + // NB: $! is pid of last job run in background + // The echo guarentees it is put into $op when + // the exec command is run. + + $commandSuffix = ' > /dev/null 2>&1'. ' & echo $!'; + $portText = ' -port ' . $this->SeleniumServerPort; + $command = "java -jar " . + escapeshellarg($this->SeleniumServerExecPath) . + $portText . $commandSuffix; + exec($command ,$op); + $pid = (int)$op[0]; + if ( $pid != "" ) + $this->SeleniumServerPid = $pid; + else { + $this->SeleniumServerPid = 'NaN'; + // Server start failed. + return 'failed'; + } + // Wait for the server to startup and listen + // on its port. Note: this solution kinda + // stinks, since it uses a wait loop - dnessett + + wfSuppressWarnings(); + for ( $cnt = 1; + $cnt <= $this->SeleniumServerStartTimeout; + $cnt++ ) { + $fp = fsockopen ( 'localhost', + $this->SeleniumServerPort, + $errno, $errstr, 0 ); + if ( !$fp ) { + sleep( 1 ); + continue; + // Server start succeeded. + } else { + fclose ( $fp ); + return 'started'; + } + } + wfRestoreWarnings(); + echo ( "Starting Selenium server timed out.\n" ); + return 'failed'; + } + // server already running. + else return 'running'; + + } + // No Server execution path defined. + return 'failed'; + } + + private function startServerOnWindows() { + // Unimplemented. + return 'failed'; + } + + private function stopServerOnUnix() { + + if ( !empty( $this->SeleniumServerPid ) && + $this->SeleniumServerPid != 'NaN' ) { + exec( "kill -9 " . $this->SeleniumServerPid ); + return 'stopped'; + } + else return 'failed'; + } + + private function stopServerOnWindows() { + // Unimplemented. + return 'failed'; + + } +} diff --git a/maintenance/tests/selenium/SeleniumTestCase.php b/maintenance/tests/selenium/SeleniumTestCase.php new file mode 100644 index 00000000..11e1b192 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumTestCase.php @@ -0,0 +1,103 @@ +<?php +class SeleniumTestCase extends PHPUnit_Framework_TestCase { // PHPUnit_Extensions_SeleniumTestCase + protected $selenium; + + public function setUp() { + set_time_limit( 60 ); + $this->selenium = Selenium::getInstance(); + } + + public function tearDown() { + + } + + public function __call( $method, $args ) { + return call_user_func_array( array( $this->selenium, $method ), $args ); + } + + public function assertSeleniumAttributeEquals( $attribute, $value ) { + $attr = $this->getAttribute( $attribute ); + $this->assertEquals( $attr, $value ); + } + + public function assertSeleniumHTMLContains( $element, $text ) { + $innerHTML = $this->getText( $element ); + // or assertContains + $this->assertRegExp( "/$text/", $innerHTML ); + } + +//Common Funtions Added for Selenium Tests + + public function getExistingPage(){ + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type("searchInput", "new" ); + $this->click("searchGoButton"); + $this->waitForPageToLoad("30000"); + } + + public function getNewPage($pageName){ + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type("searchInput", $pageName ); + $this->click("searchGoButton"); + $this->waitForPageToLoad("30000"); + $this->click("link=".$pageName); + $this->waitForPageToLoad("600000"); + + + } + // Loading the mediawiki editor + public function loadWikiEditor(){ + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + } + + // Clear the content of the mediawiki editor + public function clearWikiEditor(){ + $this->type("wpTextbox1", ""); + } + + // Click on the 'Show preview' button of the mediawiki editor + public function clickShowPreviewBtn(){ + $this->click("wpPreview"); + } + + // Click on the 'Save Page' button of the mediawiki editor + public function clickSavePageBtn(){ + $this->click("wpSave"); + } + + // Click on the 'Edit' link + public function clickEditLink(){ + $this->click("link=Edit"); + $this->waitForPageToLoad("30000"); + } + + public function deletePage($pageName){ + $isLinkPresent = False; + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click("link=Log out"); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + $this->type( "wpName1", "nadeesha" ); + $this->type( "wpPassword1", "12345" ); + $this->click( "wpLoginAttempt" ); + $this->waitForPageToLoad( "30000" ); + $this->type( "searchInput", $pageName ); + $this->click( "searchGoButton"); + $this->waitForPageToLoad( "30000" ); + + $this->click( "link=Delete" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "wpConfirmB" ); + $this->waitForPageToLoad( "30000" ); + + } + + + +} diff --git a/maintenance/tests/selenium/SeleniumTestConsoleLogger.php b/maintenance/tests/selenium/SeleniumTestConsoleLogger.php new file mode 100644 index 00000000..b6f5496c --- /dev/null +++ b/maintenance/tests/selenium/SeleniumTestConsoleLogger.php @@ -0,0 +1,25 @@ +<?php + +class SeleniumTestConsoleLogger { + public function __construct() { + // Prepare testsuite for immediate output + @ini_set( 'zlib.output_compression', 0 ); + @ini_set( 'implicit_flush', 1 ); + for ( $i = 0; $i < ob_get_level(); $i++ ) { + ob_end_flush(); + } + ob_implicit_flush( 1 ); + } + + public function write( $message, $mode = false ) { + $out = ''; + // if ( $mode == SeleniumTestSuite::RESULT_OK ) $out .= '<font color="green">'; + $out .= htmlentities( $message ); + // if ( $mode == SeleniumTestSuite::RESULT_OK ) $out .= '</font>'; + if ( $mode != SeleniumTestSuite::CONTINUE_LINE ) { + $out .= "\n"; + } + + echo $out; + } +} diff --git a/maintenance/tests/selenium/SeleniumTestHTMLLogger.php b/maintenance/tests/selenium/SeleniumTestHTMLLogger.php new file mode 100644 index 00000000..21332cf0 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumTestHTMLLogger.php @@ -0,0 +1,36 @@ +<?php + +class SeleniumTestHTMLLogger { + public function setHeaders() { + global $wgOut; + $wgOut->addHeadItem( 'selenium', '<style type="text/css"> + .selenium pre { + overflow-x: auto; /* Use horizontal scroller if needed; for Firefox 2, not needed in Firefox 3 */ + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + /* width: 99%; */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + } + .selenium-success { color: green } + </style>' ); + } + + public function write( $message, $mode = false ) { + global $wgOut; + $out = ''; + if ( $mode == SeleniumTestSuite::RESULT_OK ) { + $out .= '<span class="selenium-success">'; + } + $out .= htmlspecialchars( $message ); + if ( $mode == SeleniumTestSuite::RESULT_OK ) { + $out .= '</span>'; + } + if ( $mode != SeleniumTestSuite::CONTINUE_LINE ) { + $out .= '<br />'; + } + + $wgOut->addHTML( $out ); + } +} diff --git a/maintenance/tests/selenium/SeleniumTestListener.php b/maintenance/tests/selenium/SeleniumTestListener.php new file mode 100644 index 00000000..9436f672 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumTestListener.php @@ -0,0 +1,68 @@ +<?php + +class SeleniumTestListener implements PHPUnit_Framework_TestListener { + private $logger; + private $tests_ok = 0; + private $tests_failed = 0; + + public function __construct( $loggerInstance ) { + $this->logger = $loggerInstance; + } + + public function addError( PHPUnit_Framework_Test $test, Exception $e, $time ) { + $this->logger->write( 'Error: ' . $e->getMessage() ); + $this->tests_failed++; + } + + public function addFailure( PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time ) + { + $this->logger->write( 'Failed: ' . $e->getMessage() ); + $this->tests_failed++; + } + + public function addIncompleteTest( PHPUnit_Framework_Test $test, Exception $e, $time ) + { + $this->logger->write( 'Incomplete.' ); + $this->tests_failed++; + } + + public function addSkippedTest( PHPUnit_Framework_Test $test, Exception $e, $time ) + { + $this->logger->write( 'Skipped.' ); + $this->tests_failed++; + } + + public function startTest( PHPUnit_Framework_Test $test ) { + $this->logger->write( + 'Testing ' . $test->getName() . ' ... ', + SeleniumTestSuite::CONTINUE_LINE + ); + } + + public function endTest( PHPUnit_Framework_Test $test, $time ) { + if ( !$test->hasFailed() ) { + $this->logger->write( 'OK', SeleniumTestSuite::RESULT_OK ); + $this->tests_ok++; + } + } + + public function startTestSuite( PHPUnit_Framework_TestSuite $suite ) { + $this->logger->write( 'Testsuite ' . $suite->getName() . ' started.' ); + $this->tests_ok = 0; + $this->tests_failed = 0; + } + + public function endTestSuite( PHPUnit_Framework_TestSuite $suite ) { + $this->logger->write('Testsuite ' . $suite->getName() . ' ended.' ); + if ( $this->tests_ok > 0 || $this->tests_failed > 0 ) { + $this->logger->write( ' OK: ' . $this->tests_ok . ' Failed: ' . $this->tests_failed ); + } + $this->tests_ok = 0; + $this->tests_failed = 0; + } + + public function statusMessage( $message ) { + $this->logger->write( $message ); + } +} + diff --git a/maintenance/tests/selenium/SeleniumTestSuite.php b/maintenance/tests/selenium/SeleniumTestSuite.php new file mode 100644 index 00000000..ba178051 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumTestSuite.php @@ -0,0 +1,46 @@ +<?php + +abstract class SeleniumTestSuite extends PHPUnit_Framework_TestSuite { + private $selenium; + private $isSetUp = false; + private $loginBeforeTests = true; + + // Do not add line break after test output + const CONTINUE_LINE = 1; + const RESULT_OK = 2; + const RESULT_ERROR = 3; + + public abstract function addTests(); + + public function setUp() { + // Hack because because PHPUnit version 3.0.6 which is on prototype does not + // run setUp as part of TestSuite::run + if ( $this->isSetUp ) { + return; + } + $this->isSetUp = true; + $this->selenium = Selenium::getInstance(); + $this->selenium->start(); + $this->selenium->open( $this->selenium->getUrl() . '/index.php?setupTestSuite=' . $this->getName() ); + if ( $this->loginBeforeTests ) { + $this->login(); + } + } + + public function tearDown() { + $this->selenium->open( $this->selenium->getUrl() . '/index.php?clearTestSuite=' . $this->getName() ); + $this->selenium->stop(); + } + + public function login() { + $this->selenium->login(); + } + + public function loadPage( $title, $action ) { + $this->selenium->loadPage( $title, $action ); + } + + protected function setLoginBeforeTests( $loginBeforeTests = true ) { + $this->loginBeforeTests = $loginBeforeTests; + } +} diff --git a/maintenance/tests/selenium/data/Wikipedia-logo-v2-de.png b/maintenance/tests/selenium/data/Wikipedia-logo-v2-de.png Binary files differnew file mode 100644 index 00000000..70385243 --- /dev/null +++ b/maintenance/tests/selenium/data/Wikipedia-logo-v2-de.png diff --git a/maintenance/tests/selenium/selenium_settings.ini.php52.sample b/maintenance/tests/selenium/selenium_settings.ini.php52.sample new file mode 100644 index 00000000..ad21037e --- /dev/null +++ b/maintenance/tests/selenium/selenium_settings.ini.php52.sample @@ -0,0 +1,23 @@ +[browsers] + +firefox = "*firefox" +iexploreproxy = "*iexploreproxy" +chrome = "*chrome" + +[SeleniumSettings] + +host = "localhost" +port = "4444" +wikiUrl = "http://localhost/mediawiki/latest_trunk/trunk/phase3" +username = "Wikiadmin" +userPassword = "Wikiadminpw" +testBrowser = "firefox" +startserver = +stopserver = +jUnitLogFile = +runAgainstGrid = false + +[testSuite] + +SimpleSeleniumTestSuite = "maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php" +WikiEditorTestSuite = "extensions/WikiEditor/selenium/WikiEditorTestSuite.php" diff --git a/maintenance/tests/selenium/selenium_settings.ini.sample b/maintenance/tests/selenium/selenium_settings.ini.sample new file mode 100644 index 00000000..bacc0a90 --- /dev/null +++ b/maintenance/tests/selenium/selenium_settings.ini.sample @@ -0,0 +1,32 @@ +[SeleniumSettings] + +; Set up the available browsers that Selenium can control. +browsers[firefox] = "*firefox" +browsers[iexplorer] = "*iexploreproxy" +browsers[chrome] = "*chrome" + +; The simple configurations above usually work on Linux, but Windows and +; Mac OS X hosts may need to specify a full path: +;browsers[firefox] = "*firefox /Applications/Firefox.app/Contents/MacOS/firefox-bin" +;browsers[firefox] = "*firefox C:\Program Files\Mozilla Firefox\firefox.exe" + +host = "localhost" +port = "4444" +wikiUrl = "http://localhost/deployment" +username = "wikiuser" +userPassword = "wikipass" +testBrowser = "firefox" +startserver = +stopserver = +jUnitLogFile = +runAgainstGrid = false + +; To let the test runner start and stop the selenium server, it needs the full +; path to selenium-server.jar from the selenium-remote-control package. +seleniumserverexecpath = "/opt/local/selenium-remote-control-1.0.3/selenium-server-1.0.3/selenium-server.jar" + +[SeleniumTests] + +testSuite[SimpleSeleniumTestSuite] = "maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php" +testSuite[WikiEditorTestSuite] = "extensions/WikiEditor/selenium/WikiEditorTestSuite.php" + diff --git a/maintenance/tests/selenium/selenium_settings_grid.ini.sample b/maintenance/tests/selenium/selenium_settings_grid.ini.sample new file mode 100644 index 00000000..eca60b0a --- /dev/null +++ b/maintenance/tests/selenium/selenium_settings_grid.ini.sample @@ -0,0 +1,14 @@ +[SeleniumSettings] + +host = "grid.tesla.usability.wikimedia.org" +port = "4444" +wikiUrl = "http://208.80.152.253:5001" +username = "wikiuser" +userPassword = "wikipass" +testBrowser = "Safari on OS X Snow Leopard" +jUnitLogFile = +runAgainstGrid = true + +[testSuite] + +SimpleSeleniumTestSuite = "maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php"
\ No newline at end of file diff --git a/maintenance/tests/selenium/suites/AddContentToNewPageTestCase.php b/maintenance/tests/selenium/suites/AddContentToNewPageTestCase.php new file mode 100644 index 00000000..dd4bc005 --- /dev/null +++ b/maintenance/tests/selenium/suites/AddContentToNewPageTestCase.php @@ -0,0 +1,182 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + + +class AddContentToNewPageTestCase extends SeleniumTestCase { + + + // Add bold text and verify output + public function testAddBoldText() { + + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "//*[@id='mw-editbutton-bold']" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify bold text displayed on mediawiki preview + $this->assertTrue($this->isElementPresent( "//div[@id='wikiPreview']/p/b" )); + $this->assertTrue($this->isTextPresent( "Bold text" )); + } + + // Add italic text and verify output + public function testAddItalicText() { + + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "//*[@id='mw-editbutton-italic']" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify italic text displayed on mediawiki preview + $this->assertTrue($this->isElementPresent("//div[@id='wikiPreview']/p/i")); + $this->assertTrue($this->isTextPresent( "Italic text" )); + } + + // Add internal link for a new page and verify output in the preview + public function testAddInternalLinkNewPage() { + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "//*[@id='mw-editbutton-link']" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify internal link displayed on mediawiki preview + $source = $this->getText( "//*[@id='wikiPreview']/p/a" ); + $correct = strstr( $source, "Link title" ); + $this->assertEquals( $correct, true ); + + $this->click( "link=Link title" ); + $this->waitForPageToLoad( "600000" ); + + // Verify internal link open as a new page - editing mode + $source = $this->getText( "firstHeading" ); + $correct = strstr( $source, "Editing Link title" ); + $this->assertEquals( $correct, true ); + } + + // Add external link and verify output in the preview + public function testAddExternalLink() { + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "//*[@id='mw-editbutton-extlink']" ); + $this->type( "wpTextbox1", "[http://www.google.com Google]" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify external links displayed on mediawiki preview + $source = $this->getText( "//*[@id='wikiPreview']/p/a" ); + $correct = strstr( $source, "Google" ); + $this->assertEquals( $correct, true ); + + $this->click( "link=Google" ); + $this->waitForPageToLoad( "600000" ); + + // Verify external link opens + $source = $this->getTitle(); + $correct = strstr( $source, "Google" ); + $this->assertEquals( $correct, true); + } + + // Add level 2 headline and verify output in the preview + public function testAddLevel2HeadLine() { + $blnElementPresent = False; + $blnTextPresent = False; + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "mw-editbutton-headline" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + $this->assertTrue($this->isElementPresent( "//div[@id='wikiPreview']/h2" )); + + // Verify level 2 headline displayed on mediawiki preview + $source = $this->getText( "//*[@id='Headline_text']" ); + $correct = strstr( $source, "Headline text" ); + $this->assertEquals( $correct, true ); + } + + // Add text with ignore wiki format and verify output the preview + public function testAddNoWikiFormat() { + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "//*[@id='mw-editbutton-nowiki']" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify ignore wiki format text displayed on mediawiki preview + $source = $this->getText( "//div[@id='wikiPreview']/p" ); + $correct = strstr( $source, "Insert non-formatted text here" ); + $this->assertEquals( $correct, true ); + } + + // Add signature and verify output in the preview + public function testAddUserSignature() { + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "mw-editbutton-signature" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify signature displayed on mediawiki preview + $source = $this->getText( "//*[@id='wikiPreview']/p/a" ); + $username = $this->getText( "//*[@id='pt-userpage']/a" ); + $correct = strstr( $source, $username ); + $this->assertEquals( $correct, true ); + } + + // Add horizontal line and verify output in the preview + public function testHorizontalLine() { + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "mw-editbutton-hr" ); + + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify horizontal line displayed on mediawiki preview + $this->assertTrue( $this->isElementPresent( "//div[@id='wikiPreview']/hr" )); + $this->deletePage( "new" ); + } +} diff --git a/maintenance/tests/selenium/suites/AddNewPageTestCase.php b/maintenance/tests/selenium/suites/AddNewPageTestCase.php new file mode 100644 index 00000000..423f2a2c --- /dev/null +++ b/maintenance/tests/selenium/suites/AddNewPageTestCase.php @@ -0,0 +1,65 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + + +class AddNewPageTestCase extends SeleniumTestCase { + + // Verify adding a new page + public function testAddNewPage() { + $newPage = "new"; + $displayName = "New"; + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "600000" ); + + // Verify 'Search results' text available + $source = $this->gettext( "firstHeading" ); + $correct = strstr( $source, "Search results" ); + $this->assertEquals( $correct, true); + + // Verify 'Create the page "<page name>" on this wiki' text available + $source = $this->gettext( "//div[@id='bodyContent']/div[4]/p/b" ); + $correct = strstr ( $source, "Create the page \"New\" on this wiki!" ); + $this->assertEquals( $correct, true ); + + $this->click( "link=".$displayName ); + $this->waitForPageToLoad( "600000" ); + + $this->assertTrue($this->isElementPresent( "link=Create" )); + $this->type( "wpTextbox1", "add new test page" ); + $this->click( "wpSave" ); + + // Verify new page added + $source = $this->gettext( "firstHeading" ); + $correct = strstr ( $source, $displayName ); + $this->assertEquals( $correct, true ); + } +} diff --git a/maintenance/tests/selenium/suites/CreateAccountTestCase.php b/maintenance/tests/selenium/suites/CreateAccountTestCase.php new file mode 100644 index 00000000..1cfda2e0 --- /dev/null +++ b/maintenance/tests/selenium/suites/CreateAccountTestCase.php @@ -0,0 +1,114 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +Class CreateAccountTestCase extends SeleniumTestCase { + + // Change these values before run the test + private $userName = "yourname4000"; + private $password = "yourpass4000"; + + // Verify 'Log in/create account' link existance in Main page. + public function testMainPageLink() { + + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + $this->assertTrue($this->isElementPresent( "link=Log in / create account" )); + } + + // Verify 'Create an account' link existance in 'Log in / create account' Page. + public function testCreateAccountPageLink() { + + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + // click Log in / create account link to open Log in / create account' page + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + $this->assertTrue($this->isElementPresent( "link=Create an account" )); + } + + // Verify Create account + public function testCreateAccount() { + + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + + $this->click( "link=Create an account" ); + $this->waitForPageToLoad( "30000" ); + + // Verify for blank user name + $this->type( "wpName2", "" ); + $this->click( "wpCreateaccount" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n You have not specified a valid user name.", + $this->getText( "//div[@id='bodyContent']/div[4]" )); + + // Verify for invalid user name + $this->type( "wpName2", "@" ); + $this->click("wpCreateaccount" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n You have not specified a valid user name.", + $this->getText( "//div[@id='bodyContent']/div[4]" )); + + // start of test for blank password + $this->type( "wpName2", $this->userName); + $this->type( "wpPassword2", "" ); + $this->click( "wpCreateaccount" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n Passwords must be at least 1 character.", + $this->getText("//div[@id='bodyContent']/div[4]" )); + + $this->type( "wpName2", $this->userName ); + $this->type( "wpPassword2", $this->password ); + $this->click( "wpCreateaccount" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n The passwords you entered do not match.", + $this->getText( "//div[@id='bodyContent']/div[4]" )); + + $this->type( "wpName2", $this->userName ); + $this->type( "wpPassword2", $this->password ); + $this->type( "wpRetype", $this->password ); + $this->click( "wpCreateaccount" ); + $this->waitForPageToLoad( "30000 "); + + // Verify successful account creation for valid combination of 'Username', 'Password', 'Retype password' + $this->assertEquals( "Welcome, ".ucfirst( $this->userName )."!", + $this->getText( "Welcome,_".ucfirst( $this->userName )."!" )); + } +} + diff --git a/maintenance/tests/selenium/suites/DeletePageAdminTestCase.php b/maintenance/tests/selenium/suites/DeletePageAdminTestCase.php new file mode 100644 index 00000000..40628986 --- /dev/null +++ b/maintenance/tests/selenium/suites/DeletePageAdminTestCase.php @@ -0,0 +1,89 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + + +class DeletePageAdminTestCase extends SeleniumTestCase { + + // Verify adding a new page + public function testDeletePage() { + + + $newPage = "new"; + $displayName = "New"; + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=".$displayName ); + $this->waitForPageToLoad( "60000" ); + $this->type( "wpTextbox1", $newPage." text" ); + $this->click( "wpSave" ); + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "wpName1", $this->selenium->getUser() ); + $this->type( "wpPassword1", $this->selenium->getPass() ); + $this->click( "wpLoginAttempt" ); + $this->waitForPageToLoad( "30000" ); + $this->type( "searchInput", "new" ); + $this->click( "searchGoButton"); + $this->waitForPageToLoad( "30000" ); + + // Verify 'Delete' link displayed + $source = $this->gettext( "link=Delete" ); + $correct = strstr ( $source, "Delete" ); + $this->assertEquals($correct, true ); + + $this->click( "link=Delete" ); + $this->waitForPageToLoad( "30000" ); + + // Verify 'Delete' button available + $this->assertTrue($this->isElementPresent( "wpConfirmB" )); + + $this->click( "wpConfirmB" ); + $this->waitForPageToLoad( "30000" ); + + // Verify 'Action complete' text displayed + $source = $this->gettext( "firstHeading" ); + $correct = strstr ( $source, "Action complete" ); + $this->assertEquals( $correct, true ); + + // Verify '<Page Name> has been deleted. See deletion log for a record of recent deletions.' text displayed + $source = $this->gettext( "//div[@id='bodyContent']/p[1]" ); + $correct = strstr ( $source, "\"New\" has been deleted. See deletion log for a record of recent deletions." ); + $this->assertEquals( $correct, true ); + } +} diff --git a/maintenance/tests/selenium/suites/EmailPasswordTestCase.php b/maintenance/tests/selenium/suites/EmailPasswordTestCase.php new file mode 100644 index 00000000..8356f43a --- /dev/null +++ b/maintenance/tests/selenium/suites/EmailPasswordTestCase.php @@ -0,0 +1,81 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class EmailPasswordTestCase extends SeleniumTestCase { + + // change user name for each and every test (with in 24 hours) + private $userName = "test1"; + + public function testEmailPasswordButton() { + + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + // click Log in / create account link to open Log in / create account' page + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + $this->assertTrue($this->isElementPresent( "wpMailmypassword" )); + } + + // Verify Email password functionality + public function testEmailPasswordMessages() { + + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + // click Log in / create account link to open Log in / create account' page + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "wpName1", "" ); + $this->click( "wpMailmypassword" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n You have not specified a valid user name.", + $this->getText("//div[@id='bodyContent']/div[4]")); + + $this->type( "wpName1", $this->userName ); + $this->click( "wpMailmypassword" ); + $this->waitForPageToLoad( "30000" ); + + // Can not run on localhost + $this->assertEquals( "A new password has been sent to the e-mail address registered for ".ucfirst($this->userName).". Please log in again after you receive it.", + $this->getText("//div[@id='bodyContent']/div[4]" )); + + $this->type( "wpName1", $this->userName ); + $this->click( "wpMailmypassword" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n A password reminder has already been sent, within the last 24 hours. To prevent abuse, only one password reminder will be sent per 24 hours.", + $this->getText( "//div[@id='bodyContent']/div[4]" )); + } +} + diff --git a/maintenance/tests/selenium/suites/MediaWikExtraTestSuite.php b/maintenance/tests/selenium/suites/MediaWikExtraTestSuite.php new file mode 100644 index 00000000..205cb332 --- /dev/null +++ b/maintenance/tests/selenium/suites/MediaWikExtraTestSuite.php @@ -0,0 +1,20 @@ +<?php + +class MediaWikExtraTestSuite extends SeleniumTestSuite { + public function setUp() { + $this->setLoginBeforeTests( true ); + parent::setUp(); + } + public function addTests() { + $testFiles = array( + 'maintenance/tests/selenium/suites/MyContributionsTestCase.php', + 'maintenance/tests/selenium/suites/MyWatchListTestCase.php', + 'maintenance/tests/selenium/suites/UserPreferencesTestCase.php', + 'maintenance/tests/selenium/suites/MovePageTestCase.php', + 'maintenance/tests/selenium/suites/PageSearchTestCase.php', + 'maintenance/tests/selenium/suites/EmailPasswordTestCase.php', + 'maintenance/tests/selenium/suites/CreateAccountTestCase.php' + ); + parent::addTestFiles( $testFiles ); + } +} diff --git a/maintenance/tests/selenium/suites/MediaWikiEditorConfig.php b/maintenance/tests/selenium/suites/MediaWikiEditorConfig.php new file mode 100644 index 00000000..2803117d --- /dev/null +++ b/maintenance/tests/selenium/suites/MediaWikiEditorConfig.php @@ -0,0 +1,47 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class MediaWikiEditorConfig { + + public static function getSettings(&$includeFiles, &$globalConfigs) { + $includes = array( + //files that needed to be included would go here + 'maintenance/tests/selenium/suites/MediaWikiCommonFunction.php' + ); + $configs = array( + 'wgPageLoadTime' => "600000" + ); + $includeFiles = array_merge( $includeFiles, $includes ); + $globalConfigs = array_merge( $globalConfigs, $configs); + return true; + } +} + + + diff --git a/maintenance/tests/selenium/suites/MediaWikiEditorTestSuite.php b/maintenance/tests/selenium/suites/MediaWikiEditorTestSuite.php new file mode 100644 index 00000000..06046365 --- /dev/null +++ b/maintenance/tests/selenium/suites/MediaWikiEditorTestSuite.php @@ -0,0 +1,18 @@ +<?php + +class MediaWikiEditorTestSuite extends SeleniumTestSuite { + public function setUp() { + $this->setLoginBeforeTests( true ); + parent::setUp(); + } + public function addTests() { + $testFiles = array( + 'maintenance/tests/selenium/suites/AddNewPageTestCase.php', + 'maintenance/tests/selenium/suites/AddContentToNewPageTestCase.php', + 'maintenance/tests/selenium/suites/PreviewPageTestCase.php', + 'maintenance/tests/selenium/suites/SavePageTestCase.php', + ); + parent::addTestFiles( $testFiles ); + } +} + diff --git a/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestCase.php b/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestCase.php new file mode 100644 index 00000000..7b9525af --- /dev/null +++ b/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestCase.php @@ -0,0 +1,69 @@ +<?php +/* + * Stub of tests be need as part of the hack-a-ton + */ +class MediawikiCoreSmokeTestCase extends SeleniumTestCase { + public function testUserLogin() { + + } + + public function testChangeUserPreference() { + + } + + /* + * TODO: generalize this test to be reusable for different skins + */ + public function testCreateNewPageVector() { + + } + + /* + * TODO: generalize this test to be reusable for different skins + */ + public function testEditExistingPageVector() { + + } + + /* + * TODO: generalize this test to be reusable for different skins + */ + public function testCreateNewPageMonobook() { + + } + + /* + * TODO: generalize this test to be reusable for different skins + */ + public function testEditExistingPageMonobook() { + + } + + public function testImageUpload() { + $this->login(); + $this->open( $this->getUrl() . + '/index.php?title=Special:Upload' ); + $this->type( 'wpUploadFile', dirname( __FILE__ ) . + "\\..\\data\\Wikipedia-logo-v2-de.png" ); + $this->check( 'wpIgnoreWarning' ); + $this->click( 'wpUpload' ); + $this->waitForPageToLoad( 30000 ); + + $this->assertSeleniumHTMLContains( + '//h1[@class="firstHeading"]', "Wikipedia-logo-v2-de.png" ); + + /* + $this->open( $this->getUrl() . '/index.php?title=Image:' + . ucfirst( $this->filename ) . '&action=delete' ); + $this->type( 'wpReason', 'Remove test file' ); + $this->click( 'mw-filedelete-submit' ); + $this->waitForPageToLoad( 10000 ); + + // Todo: This message is localized + $this->assertSeleniumHTMLContains( '//div[@id="bodyContent"]/p', + ucfirst( $this->filename ) . '.*has been deleted.' ); + */ + } + + +} diff --git a/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php b/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php new file mode 100644 index 00000000..76287b23 --- /dev/null +++ b/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php @@ -0,0 +1,19 @@ +<?php +/* + * Stubs for now. We're going to start populating this test. + */ +class MediawikiCoreSmokeTestSuite extends SeleniumTestSuite +{ + public function setUp() { + $this->setLoginBeforeTests( false ); + parent::setUp(); + } + public function addTests() { + $testFiles = array( + 'maintenance/tests/selenium/suites/MediawikiCoreSmokeTestCase.php' + ); + parent::addTestFiles( $testFiles ); + } + + +} diff --git a/maintenance/tests/selenium/suites/MovePageTestCase.php b/maintenance/tests/selenium/suites/MovePageTestCase.php new file mode 100644 index 00000000..d4d3b1f2 --- /dev/null +++ b/maintenance/tests/selenium/suites/MovePageTestCase.php @@ -0,0 +1,117 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class MovePageTestCase extends SeleniumTestCase { + + // Verify move(rename) wiki page + public function testMovePage() { + + $newPage = "mypage99"; + $displayName = "Mypage99"; + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=".$displayName ); + $this->waitForPageToLoad( "60000" ); + $this->type( "wpTextbox1", $newPage." text" ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "60000" ); + + // Verify link 'Move' available + $this->assertTrue($this->isElementPresent( "link=Move" )); + + $this->click( "link=Move" ); + $this->waitForPageToLoad( "30000" ); + + // Verify correct page name displayed under 'Move Page' field + $this->assertEquals($displayName, + $this->getText("//table[@id='mw-movepage-table']/tbody/tr[1]/td[2]/strong/a")); + $movePageName = $this->getText( "//table[@id='mw-movepage-table']/tbody/tr[1]/td[2]/strong/a" ); + + // Verify 'To new title' field has current page name as the default name + $newTitle = $this->getValue( "wpNewTitle" ); + $correct = strstr( $movePageName , $newTitle ); + $this->assertEquals( $correct, true ); + + $this->type( "wpNewTitle", $displayName ); + $this->click( "wpMove" ); + $this->waitForPageToLoad( "30000" ); + + // Verify warning message for the same source and destination titles + $this->assertEquals( "Source and destination titles are the same; cannot move a page over itself.", + $this->getText("//div[@id='bodyContent']/p[4]/strong" )); + + // Verify warning message for the blank title + $this->type( "wpNewTitle", "" ); + $this->click( "wpMove" ); + $this->waitForPageToLoad( "30000" ); + + // Verify warning message for the blank title + $this->assertEquals( "The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title. It may contain one or more characters which cannot be used in titles.", + $this->getText( "//div[@id='bodyContent']/p[4]/strong" )); + + // Verify warning messages for the invalid titles + $this->type( "wpNewTitle", "# < > [ ] | { }" ); + $this->click( "wpMove" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title. It may contain one or more characters which cannot be used in titles.", + $this->getText( "//div[@id='bodyContent']/p[4]/strong" )); + + $this->type( "wpNewTitle", $displayName."move" ); + $this->click( "wpMove" ); + $this->waitForPageToLoad( "30000" ); + + // Verify move success message displayed correctly + $this->assertEquals( "\"".$displayName."\" has been moved to \"".$displayName."move"."\"", + $this->getText( "//div[@id='bodyContent']/p[1]/b" )); + + $this->type( "searchInput", $newPage."move" ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + + // Verify search using new page name + $this->assertEquals( $displayName."move", $this->getText( "firstHeading" )); + + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + + // Verify search using old page name + $redirectPageName = $this->getText( "//*[@id='contentSub']" ); + $this->assertEquals( "(Redirected from ".$displayName.")" , $redirectPageName ); + + // newpage delete + $this->deletePage( $newPage."move" ); + $this->deletePage( $newPage ); + } +} + diff --git a/maintenance/tests/selenium/suites/MyContributionsTestCase.php b/maintenance/tests/selenium/suites/MyContributionsTestCase.php new file mode 100644 index 00000000..95011c3b --- /dev/null +++ b/maintenance/tests/selenium/suites/MyContributionsTestCase.php @@ -0,0 +1,76 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class MyContributionsTestCase extends SeleniumTestCase { + + // Verify user contributions + public function testRecentChangesAvailability() { + + $newPage = "mypage999"; + $displayName = "Mypage999"; + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + + $this->type( "searchInput", $newPage); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "60000" ); + $this->click( "link=".$displayName ); + $this->waitForPageToLoad( "600000" ); + $this->type( "wpTextbox1", $newPage." text" ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "60000" ); + + // Verify My contributions Link available + $this->assertTrue($this->isElementPresent( "link=My contributions" )); + + $this->click( "link=My contributions" ); + $this->waitForPageToLoad( "30000" ); + + // Verify recent page adding available on My Contributions list + $this->assertEquals( $displayName, $this->getText( "link=".$displayName )); + + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + + $this->click( "link=Edit" ); + $this->waitForPageToLoad( "30000" ); + $this->type( "wpTextbox1", $newPage." text changed" ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=My contributions" ); + $this->waitForPageToLoad( "30000" ); + + // Verify recent page changes available on My Contributions + $this->assertTrue($this->isTextPresent($displayName." (top)")); + $this->deletePage($newPage); + } +} + diff --git a/maintenance/tests/selenium/suites/MyWatchListTestCase.php b/maintenance/tests/selenium/suites/MyWatchListTestCase.php new file mode 100644 index 00000000..150c1f51 --- /dev/null +++ b/maintenance/tests/selenium/suites/MyWatchListTestCase.php @@ -0,0 +1,73 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + + +class MyWatchListTestCase extends SeleniumTestCase { + + // Verify user watchlist + public function testMyWatchlist() { + + $newPage = "mypage"; + $displayName = "Mypage"; + $wikiText = "watch page"; + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=".$displayName ); + $this->waitForPageToLoad( "600000" ); + + $this->click( "wpWatchthis" ); + $this->type( "wpTextbox1",$wikiText ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "30000" ); + + // Verify link 'My Watchlist' available + $this->assertTrue( $this->isElementPresent( "link=My watchlist" ) ); + + $this->click( "link=My watchlist" ); + $this->waitForPageToLoad( "30000" ); + + // Verify newly added page to the watchlist is available + $watchList = $this->getText( "//*[@id='bodyContent']" ); + $this->assertContains( $displayName, $watchList ); + + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + $this->click("link=Edit"); + $this->waitForPageToLoad( "30000" ); + $this->click( "wpWatchthis" ); + $this->click( "wpSave" ); + $this->deletePage( $newPage ); + } +} + diff --git a/maintenance/tests/selenium/suites/PageDeleteTestSuite.php b/maintenance/tests/selenium/suites/PageDeleteTestSuite.php new file mode 100644 index 00000000..2e535c11 --- /dev/null +++ b/maintenance/tests/selenium/suites/PageDeleteTestSuite.php @@ -0,0 +1,16 @@ +<?php + +class PageDeleteTestSuite extends SeleniumTestSuite { + public function setUp() { + $this->setLoginBeforeTests( true ); + parent::setUp(); + } + public function addTests() { + $testFiles = array( + 'maintenance/tests/selenium/suites/DeletePageAdminTestCase.php' + ); + parent::addTestFiles( $testFiles ); + } + + +} diff --git a/maintenance/tests/selenium/suites/PageSearchTestCase.php b/maintenance/tests/selenium/suites/PageSearchTestCase.php new file mode 100644 index 00000000..e139f7a0 --- /dev/null +++ b/maintenance/tests/selenium/suites/PageSearchTestCase.php @@ -0,0 +1,105 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class PageSearchTestCase extends SeleniumTestCase { + + // Verify the functionality of the 'Go' button + public function testPageSearchBtnGo() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type( "searchInput", "calcey qa" ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "600000" ); + + // Verify no page matched with the entered search text + $source = $this->gettext( "//div[@id='bodyContent']/div[4]/p/b" ); + $correct = strstr ( $source, "Create the page \"Calcey qa\" on this wiki!" ); + $this->assertEquals( $correct, true ); + + $this->click( "link=Calcey qa" ); + $this->waitForPageToLoad( "600000" ); + + $this->type( "wpTextbox1", "Calcey QA team" ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "600000" ); + + } + + // Verify the functionality of the 'Search' button + public function testPageSearchBtnSearch() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type( "searchInput", "Calcey web" ); + $this->click( "mw-searchButton" ); + $this->waitForPageToLoad( "30000" ); + + // Verify no page is available as the search text + $source = $this->gettext( "//div[@id='bodyContent']/div[4]/p[2]/b" ); + $correct = strstr ( $source, "Create the page \"Calcey web\" on this wiki!" ); + $this->assertEquals( $correct, true ); + + $this->click( "link=Calcey web" ); + $this->waitForPageToLoad( "600000" ); + + $this->type( "wpTextbox1", "Calcey web team" ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "600000" ); + + // Verify saved page is opened when the exact page name is given + $this->type( "searchInput", "Calcey web" ); + $this->click( "mw-searchButton" ); + $this->waitForPageToLoad( "30000" ); + + // Verify exact page matched with the entered search text using 'Search' button + $source = $this->getText( "//*[@id='bodyContent']/div[4]/p/b" ); + $correct = strstr( $source, "There is a page named \"Calcey web\" on this wiki." ); + $this->assertEquals( $correct, true ); + + // Verify resutls available when partial page name is entered as the search text + $this->type( "searchInput", "Calcey" ); + $this->click( "mw-searchButton" ); + $this->waitForPageToLoad( "30000" ); + + // Verify text avaialble in the search result under the page titles + if($this->isElementPresent( "Page_title_matches" )) { + $textPageTitle = $this->getText( "//*[@id='bodyContent']/div[4]/ul[1]/li[1]/div[1]/a" ); + $this->assertContains( 'Calcey', $textPageTitle ); + } + + // Verify text avaialble in the search result under the page text + if($this->isElementPresent( "Page_text_matches" )) { + $textPageText = $this->getText( "//*[@id='bodyContent']/div[4]/ul[2]/li[2]/div[2]/span" ); + $this->assertContains( 'Calcey', $textPageText ); + } + $this->deletePage("Calcey QA"); + $this->deletePage("Calcey web"); + } +} diff --git a/maintenance/tests/selenium/suites/PreviewPageTestCase.php b/maintenance/tests/selenium/suites/PreviewPageTestCase.php new file mode 100644 index 00000000..b704bf39 --- /dev/null +++ b/maintenance/tests/selenium/suites/PreviewPageTestCase.php @@ -0,0 +1,53 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class PreviewPageTestCase extends SeleniumTestCase { + + // Verify adding a new page + public function testPreviewPage() { + $wikiText = "Adding this page to test the \n Preview button functionality"; + $newPage = "Test Preview Page"; + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->getNewPage( $newPage ); + $this->type( "wpTextbox1", $wikiText."" ); + $this->assertTrue($this->isElementPresent( "//*[@id='wpPreview']" )); + + $this->click( "wpPreview" ); + + // Verify saved page available + $source = $this->gettext( "firstHeading" ); + $correct = strstr( $source, "Test Preview Page" ); + $this->assertEquals( $correct, true); + + // Verify page content previewed succesfully + $contentOfPreviewPage = $this->getText( "//*[@id='content']" ); + $this->assertContains( $wikiText, $contentOfPreviewPage ); + } +} diff --git a/maintenance/tests/selenium/suites/SavePageTestCase.php b/maintenance/tests/selenium/suites/SavePageTestCase.php new file mode 100644 index 00000000..7f1a6924 --- /dev/null +++ b/maintenance/tests/selenium/suites/SavePageTestCase.php @@ -0,0 +1,58 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class SavePageTestCase extends SeleniumTestCase { + + // Verify adding a new page + public function testSavePage() { + $wikiText = "Adding this page to test the Save button functionality"; + $newPage = "Test Save Page"; + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->getNewPage($newPage); + $this->type("wpTextbox1", $wikiText); + + // verify 'Save' button available + $this->assertTrue($this->isElementPresent( "wpSave" )); + $this->click( "wpSave" ); + + // Verify saved page available + $source = $this->gettext( "firstHeading" ); + $correct = strstr( $source, "Test Save Page" ); + + // Verify Saved page name displayed correctly + $this->assertEquals( $correct, true ); + + // Verify page content saved succesfully + $contentOfSavedPage = $this->getText( "//*[@id='content']" ); + $this->assertContains( $wikiText, $contentOfSavedPage ); + $this->deletePage( $newPage ); + } +} diff --git a/maintenance/tests/selenium/suites/SimpleSeleniumConfig.php b/maintenance/tests/selenium/suites/SimpleSeleniumConfig.php new file mode 100644 index 00000000..cffa83c4 --- /dev/null +++ b/maintenance/tests/selenium/suites/SimpleSeleniumConfig.php @@ -0,0 +1,15 @@ +<?php +class SimpleSeleniumConfig { + + public static function getSettings(&$includeFiles, &$globalConfigs) { + $includes = array( + //files that needed to be included would go here + ); + $configs = array( + 'wgDefaultSkin' => 'chick' + ); + $includeFiles = array_merge( $includeFiles, $includes ); + $globalConfigs = array_merge( $globalConfigs, $configs); + return true; + } +}
\ No newline at end of file diff --git a/maintenance/tests/selenium/suites/SimpleSeleniumTestCase.php b/maintenance/tests/selenium/suites/SimpleSeleniumTestCase.php new file mode 100644 index 00000000..8f01b437 --- /dev/null +++ b/maintenance/tests/selenium/suites/SimpleSeleniumTestCase.php @@ -0,0 +1,30 @@ +<?php +/* + * This test case is part of the SimpleSeleniumTestSuite. + * Configuration for these tests are dosumented as part of SimpleSeleniumTestSuite.php + */ +class SimpleSeleniumTestCase extends SeleniumTestCase { + public function testBasic() { + $this->open( $this->getUrl() . + '/index.php?title=Selenium&action=edit' ); + $this->type( "wpTextbox1", "This is a basic test" ); + $this->click( "wpPreview" ); + $this->waitForPageToLoad( 10000 ); + + // check result + $source = $this->getText( "//div[@id='wikiPreview']/p" ); + $correct = strstr( $source, "This is a basic test" ); + $this->assertEquals( $correct, true ); + } + + /* + * All this test really does is verify that a global var was set. + * It depends on $wgDefaultSkin = 'chick'; being set + */ + public function testGlobalVariableForDefaultSkin() { + $this->open( $this->getUrl() . '/index.php?&action=purge' ); + $bodyClass = $this->getAttribute( "//body/@class" ); + $this-> assertContains('skin-chick', $bodyClass, 'Chick skin not set'); + } + +} diff --git a/maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php b/maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php new file mode 100644 index 00000000..a04f33ed --- /dev/null +++ b/maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php @@ -0,0 +1,26 @@ +<?php +/* + * Sample test suite. + * Two ways to configure MW for these tests + * 1) If you are running multiple test suites, add the following in LocalSettings.php + * require_once("maintenance/tests/selenium/SimpleSeleniumConfig.php"); + * $wgSeleniumTestConfigs['SimpleSeleniumTestSuite'] = 'SimpleSeleniumConfig::getSettings'; + * OR + * 2) Add the following to your Localsettings.php + * $wgDefaultSkin = 'chick'; + */ +class SimpleSeleniumTestSuite extends SeleniumTestSuite +{ + public function setUp() { + $this->setLoginBeforeTests( false ); + parent::setUp(); + } + public function addTests() { + $testFiles = array( + 'maintenance/tests/selenium/suites/SimpleSeleniumTestCase.php' + ); + parent::addTestFiles( $testFiles ); + } + + +} diff --git a/maintenance/tests/selenium/suites/UserPreferencesTestCase.php b/maintenance/tests/selenium/suites/UserPreferencesTestCase.php new file mode 100644 index 00000000..12824307 --- /dev/null +++ b/maintenance/tests/selenium/suites/UserPreferencesTestCase.php @@ -0,0 +1,179 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class UserPreferencesTestCase extends SeleniumTestCase { + + // Verify user information + public function testUserInfoDisplay() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + + // Verify correct username displayed in User Preferences + $this->assertEquals( $this->getText( "//li[@id='pt-userpage']/a" ), + $this->getText( "//table[@id='mw-htmlform-info']/tbody/tr[1]/td[2]" )); + + // Verify existing Signature Displayed correctly + $this->assertEquals( $this->selenium->getUser(), + $this->getTable( "mw-htmlform-signature.0.1" ) ); + } + + // Verify change password + public function testChangePassword() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + + $this->click( "link=Change password" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "wpPassword", "12345" ); + $this->type( "wpNewPassword", "54321" ); + $this->type( "wpRetype", "54321" ); + $this->click( "//input[@value='Change password']" ); + $this->waitForPageToLoad( "30000" ); + + $this->assertEquals( "Preferences", $this->getText( "firstHeading" )); + + $this->click( "link=Change password" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "wpPassword", "54321" ); + $this->type( "wpNewPassword", "12345" ); + $this->type( "wpRetype", "12345" ); + $this->click( "//input[@value='Change password']" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Preferences", $this->getText( "firstHeading" )); + + $this->click( "link=Change password" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "wpPassword", "54321" ); + $this->type( "wpNewPassword", "12345" ); + $this->type( "wpRetype", "12345" ); + $this->click( "//input[@value='Change password']" ); + $this->waitForPageToLoad( "30000" ); + } + + // Verify successful preferences save + public function testSuccessfullSave() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "mw-input-realname", "Test User" ); + $this->click( "prefcontrol" ); + $this->waitForPageToLoad( "30000" ); + + // Verify "Your preferences have been saved." message + $this->assertEquals( "Your preferences have been saved.", + $this->getText( "//div[@id='bodyContent']/div[4]/strong/p" )); + $this->type( "mw-input-realname", "" ); + $this->click( "prefcontrol" ); + $this->waitForPageToLoad( "30000" ); + + } + + // Verify change signature + public function testChangeSignature() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "mw-input-nickname", "TestSignature" ); + $this->click( "prefcontrol" ); + $this->waitForPageToLoad( "30000" ); + + // Verify change user signature + $this->assertEquals( "TestSignature", $this->getText( "link=TestSignature" )); + $this->type( "mw-input-nickname", "Test" ); + $this->click( "prefcontrol" ); + $this->waitForPageToLoad("30000"); + } + + // Verify change date format + public function testChangeDateFormatTimeZone() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=Date and time" ); + $this->waitForPageToLoad( "30000" ); + + $this->click( "mw-input-date-dmy" ); + $this->select( "mw-input-timecorrection", "label=Asia/Colombo" ); + $this->click( "prefcontrol" ); + $this->waitForPageToLoad( "30000" ); + + // Verify Date format and time zome saved + $this->assertEquals( "Your preferences have been saved.", + $this->getText( "//div[@id='bodyContent']/div[4]/strong/p" )); + } + + // Verify restoring all default settings + public function testSetAllDefault() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + + // Verify restoring all default settings + $this->assertEquals( "Restore all default settings", + $this->getText( "link=Restore all default settings" )); + + $this->click("//*[@id='preferences']/div/a"); + $this->waitForPageToLoad("30000"); + + // Verify 'This can not be undone' warning message displayed + $this->assertTrue($this->isElementPresent("//input[@value='Restore all default settings']")); + + // Verify 'Restore all default settings' button available + $this->assertEquals("You can use this page to reset your preferences to the site defaults. This cannot be undone.", + $this->getText("//div[@id='bodyContent']/p")); + + $this->click("//input[@value='Restore all default settings']"); + $this->waitForPageToLoad("30000"); + + // Verify preferences saved successfully + $this->assertEquals("Your preferences have been saved.", + $this->getText("//div[@id='bodyContent']/div[4]/strong/p")); + } +} + diff --git a/maintenance/tests/test-prefetch-current.xml b/maintenance/tests/test-prefetch-current.xml deleted file mode 100644 index a4c8bda3..00000000 --- a/maintenance/tests/test-prefetch-current.xml +++ /dev/null @@ -1,75 +0,0 @@ -<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 deleted file mode 100644 index 95eb82dd..00000000 --- a/maintenance/tests/test-prefetch-previous.xml +++ /dev/null @@ -1,57 +0,0 @@ -<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 deleted file mode 100644 index 59d43d2f..00000000 --- a/maintenance/tests/test-prefetch-stub.xml +++ /dev/null @@ -1,75 +0,0 @@ -<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/tests/testHelpers.inc b/maintenance/tests/testHelpers.inc new file mode 100644 index 00000000..94ca6abc --- /dev/null +++ b/maintenance/tests/testHelpers.inc @@ -0,0 +1,652 @@ +<?php + +/** + * @ingroup Maintenance + * + * Set of classes to help with test output and such. Right now pretty specific + * to the parser tests but could be more useful one day :) + * + * @todo Fixme: Make this more generic + */ + +class AnsiTermColorer { + function __construct() { + } + + /** + * Return ANSI terminal escape code for changing text attribs/color + * + * @param $color String: semicolon-separated list of attribute/color codes + * @return String + */ + public function color( $color ) { + global $wgCommandLineDarkBg; + + $light = $wgCommandLineDarkBg ? "1;" : "0;"; + + return "\x1b[{$light}{$color}m"; + } + + /** + * Return ANSI terminal escape code for restoring default text attributes + * + * @return String + */ + public function reset() { + return $this->color( 0 ); + } +} + +/* A colour-less terminal */ +class DummyTermColorer { + public function color( $color ) { + return ''; + } + + public function reset() { + return ''; + } +} + +class TestRecorder { + var $parent; + var $term; + + function __construct( $parent ) { + $this->parent = $parent; + $this->term = $parent->term; + } + + function start() { + $this->total = 0; + $this->success = 0; + } + + function record( $test, $result ) { + $this->total++; + $this->success += ( $result ? 1 : 0 ); + } + + function end() { + // dummy + } + + function report() { + if ( $this->total > 0 ) { + $this->reportPercentage( $this->success, $this->total ); + } else { + wfDie( "No tests found.\n" ); + } + } + + function reportPercentage( $success, $total ) { + $ratio = wfPercent( 100 * $success / $total ); + print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)... "; + + if ( $success == $total ) { + print $this->term->color( 32 ) . "ALL TESTS PASSED!"; + } else { + $failed = $total - $success ; + print $this->term->color( 31 ) . "$failed tests failed!"; + } + + print $this->term->reset() . "\n"; + + return ( $success == $total ); + } +} + +class DbTestPreviewer extends TestRecorder { + protected $lb; // /< Database load balancer + protected $db; // /< Database connection to the main DB + protected $curRun; // /< run ID number for the current run + protected $prevRun; // /< run ID number for the previous run, if any + protected $results; // /< Result array + + /** + * This should be called before the table prefix is changed + */ + function __construct( $parent ) { + parent::__construct( $parent ); + + $this->lb = wfGetLBFactory()->newMainLB(); + // This connection will have the wiki's table prefix, not parsertest_ + $this->db = $this->lb->getConnection( DB_MASTER ); + } + + /** + * Set up result recording; insert a record for the run with the date + * and all that fun stuff + */ + function start() { + parent::start(); + + if ( ! $this->db->tableExists( 'testrun' ) + or ! $this->db->tableExists( 'testitem' ) ) + { + print "WARNING> `testrun` table not found in database.\n"; + $this->prevRun = false; + } else { + // We'll make comparisons against the previous run later... + $this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' ); + } + + $this->results = array(); + } + + function record( $test, $result ) { + parent::record( $test, $result ); + $this->results[$test] = $result; + } + + function report() { + if ( $this->prevRun ) { + // f = fail, p = pass, n = nonexistent + // codes show before then after + $table = array( + 'fp' => 'previously failing test(s) now PASSING! :)', + 'pn' => 'previously PASSING test(s) removed o_O', + 'np' => 'new PASSING test(s) :)', + + 'pf' => 'previously passing test(s) now FAILING! :(', + 'fn' => 'previously FAILING test(s) removed O_o', + 'nf' => 'new FAILING test(s) :(', + 'ff' => 'still FAILING test(s) :(', + ); + + $prevResults = array(); + + $res = $this->db->select( 'testitem', array( 'ti_name', 'ti_success' ), + array( 'ti_run' => $this->prevRun ), __METHOD__ ); + + foreach ( $res as $row ) { + if ( !$this->parent->regex + || preg_match( "/{$this->parent->regex}/i", $row->ti_name ) ) + { + $prevResults[$row->ti_name] = $row->ti_success; + } + } + + $combined = array_keys( $this->results + $prevResults ); + + # Determine breakdown by change type + $breakdown = array(); + foreach ( $combined as $test ) { + if ( !isset( $prevResults[$test] ) ) { + $before = 'n'; + } elseif ( $prevResults[$test] == 1 ) { + $before = 'p'; + } else /* if ( $prevResults[$test] == 0 )*/ { + $before = 'f'; + } + + if ( !isset( $this->results[$test] ) ) { + $after = 'n'; + } elseif ( $this->results[$test] == 1 ) { + $after = 'p'; + } else /*if ( $this->results[$test] == 0 ) */ { + $after = 'f'; + } + + $code = $before . $after; + + if ( isset( $table[$code] ) ) { + $breakdown[$code][$test] = $this->getTestStatusInfo( $test, $after ); + } + } + + # Write out results + foreach ( $table as $code => $label ) { + if ( !empty( $breakdown[$code] ) ) { + $count = count( $breakdown[$code] ); + printf( "\n%4d %s\n", $count, $label ); + + foreach ( $breakdown[$code] as $differing_test_name => $statusInfo ) { + print " * $differing_test_name [$statusInfo]\n"; + } + } + } + } else { + print "No previous test runs to compare against.\n"; + } + + print "\n"; + parent::report(); + } + + /** + * Returns a string giving information about when a test last had a status change. + * Could help to track down when regressions were introduced, as distinct from tests + * which have never passed (which are more change requests than regressions). + */ + private function getTestStatusInfo( $testname, $after ) { + // If we're looking at a test that has just been removed, then say when it first appeared. + if ( $after == 'n' ) { + $changedRun = $this->db->selectField ( 'testitem', + 'MIN(ti_run)', + array( 'ti_name' => $testname ), + __METHOD__ ); + $appear = $this->db->selectRow ( 'testrun', + array( 'tr_date', 'tr_mw_version' ), + array( 'tr_id' => $changedRun ), + __METHOD__ ); + + return "First recorded appearance: " + . date( "d-M-Y H:i:s", strtotime ( $appear->tr_date ) ) + . ", " . $appear->tr_mw_version; + } + + // Otherwise, this test has previous recorded results. + // See when this test last had a different result to what we're seeing now. + $conds = array( + 'ti_name' => $testname, + 'ti_success' => ( $after == 'f' ? "1" : "0" ) ); + + if ( $this->curRun ) { + $conds[] = "ti_run != " . $this->db->addQuotes ( $this->curRun ); + } + + $changedRun = $this->db->selectField ( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ ); + + // If no record of ever having had a different result. + if ( is_null ( $changedRun ) ) { + if ( $after == "f" ) { + return "Has never passed"; + } else { + return "Has never failed"; + } + } + + // Otherwise, we're looking at a test whose status has changed. + // (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.) + // In this situation, give as much info as we can as to when it changed status. + $pre = $this->db->selectRow ( 'testrun', + array( 'tr_date', 'tr_mw_version' ), + array( 'tr_id' => $changedRun ), + __METHOD__ ); + $post = $this->db->selectRow ( 'testrun', + array( 'tr_date', 'tr_mw_version' ), + array( "tr_id > " . $this->db->addQuotes ( $changedRun ) ), + __METHOD__, + array( "LIMIT" => 1, "ORDER BY" => 'tr_id' ) + ); + + if ( $post ) { + $postDate = date( "d-M-Y H:i:s", strtotime ( $post->tr_date ) ) . ", {$post->tr_mw_version}"; + } else { + $postDate = 'now'; + } + + return ( $after == "f" ? "Introduced" : "Fixed" ) . " between " + . date( "d-M-Y H:i:s", strtotime ( $pre->tr_date ) ) . ", " . $pre->tr_mw_version + . " and $postDate"; + + } + + /** + * Commit transaction and clean up for result recording + */ + function end() { + $this->lb->commitMasterChanges(); + $this->lb->closeAll(); + parent::end(); + } + +} + +class DbTestRecorder extends DbTestPreviewer { + var $version; + + /** + * Set up result recording; insert a record for the run with the date + * and all that fun stuff + */ + function start() { + global $wgDBtype; + $this->db->begin(); + + if ( ! $this->db->tableExists( 'testrun' ) + or ! $this->db->tableExists( 'testitem' ) ) + { + print "WARNING> `testrun` table not found in database. Trying to create table.\n"; + $this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) ); + echo "OK, resuming.\n"; + } + + parent::start(); + + $this->db->insert( 'testrun', + array( + 'tr_date' => $this->db->timestamp(), + 'tr_mw_version' => $this->version, + 'tr_php_version' => phpversion(), + 'tr_db_version' => $this->db->getServerVersion(), + 'tr_uname' => php_uname() + ), + __METHOD__ ); + if ( $wgDBtype === 'postgres' ) { + $this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' ); + } else { + $this->curRun = $this->db->insertId(); + } + } + + /** + * Record an individual test item's success or failure to the db + * + * @param $test String + * @param $result Boolean + */ + function record( $test, $result ) { + parent::record( $test, $result ); + + $this->db->insert( 'testitem', + array( + 'ti_run' => $this->curRun, + 'ti_name' => $test, + 'ti_success' => $result ? 1 : 0, + ), + __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 $status string: "running" - tell it we've started + * "complete" - provide test results array + * "abort" - something went horribly awry + * @param $results array of test name => true/false + */ + function ping( $status, $results = false ) { + global $wgParserTestRemote, $IP; + + $remote = $wgParserTestRemote; + $revId = SpecialVersion::getSvnRevision( $IP ); + $jsonResults = FormatJson::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 = FormatJson::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 $parserTest; /* An instance of ParserTest (parserTests.php) or MediaWikiParserTest (phpunit) */ + private $index = 0; + private $test; + private $lineNum; + private $eof; + + function __construct( $file, $parserTest = null ) { + global $IP; + + $this->file = $file; + $this->fh = fopen( $this->file, "rt" ); + + if ( !$this->fh ) { + wfDie( "Couldn't open file '$file'\n" ); + } + + $this->parserTest = $parserTest; + + if ( $this->parserTest ) { + $this->parserTest->showRunFile( wfRelativePath( $this->file, $IP ) ); + } + + $this->lineNum = $this->index = 0; + } + + function rewind() { + if ( fseek( $this->fh, 0 ) ) { + wfDie( "Couldn't fseek to the start of '$this->file'\n" ); + } + + $this->index = -1; + $this->lineNum = 0; + $this->eof = false; + $this->next(); + + 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 $this->file\n" ); + } + + if ( !isset( $data['article'] ) ) { + wfDie( "'endarticle' without 'article' at line {$this->lineNum} of $this->file\n" ); + } + + if ( $this->parserTest ) { + $this->parserTest->addArticle( ParserTest::chomp( $data['article'] ), $data['text'], $this->lineNum ); + } else {wfDie("JAJA"); + ParserTest::addArticle( $data['article'], $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 $this->file\n" ); + } + + foreach ( explode( "\n", $data['hooks'] ) as $line ) { + $line = trim( $line ); + + if ( $line ) { + if ( $this->parserTest && !$this->parserTest->requireHook( $line ) ) { + return false; + } + } + } + + $data = array(); + $section = null; + + continue; + } + + if ( $section == 'endfunctionhooks' ) { + if ( !isset( $data['functionhooks'] ) ) { + wfDie( "'endfunctionhooks' without 'functionhooks' at line {$this->lineNum} of $this->file\n" ); + } + + foreach ( explode( "\n", $data['functionhooks'] ) as $line ) { + $line = trim( $line ); + + if ( $line ) { + if ( $this->parserTest && !$this->parserTest->requireFunctionHook( $line ) ) { + return false; + } + } + } + + $data = array(); + $section = null; + + continue; + } + + if ( $section == 'end' ) { + if ( !isset( $data['test'] ) ) { + wfDie( "'end' without 'test' at line {$this->lineNum} of $this->file\n" ); + } + + if ( !isset( $data['input'] ) ) { + wfDie( "'end' without 'input' at line {$this->lineNum} of $this->file\n" ); + } + + if ( !isset( $data['result'] ) ) { + wfDie( "'end' without 'result' at line {$this->lineNum} of $this->file\n" ); + } + + if ( !isset( $data['options'] ) ) { + $data['options'] = ''; + } + + if ( !isset( $data['config'] ) ) + $data['config'] = ''; + + if ( $this->parserTest + && ( ( preg_match( '/\\bdisabled\\b/i', $data['options'] ) && !$this->parserTest->runDisabled ) + || !preg_match( "/" . $this->parserTest->regex . "/i", $data['test'] ) ) ) { + # disabled test + $data = array(); + $section = null; + + continue; + } + + global $wgUseTeX; + + if ( $this->parserTest && + preg_match( '/\\bmath\\b/i', $data['options'] ) && !$wgUseTeX ) { + # don't run math tests if $wgUseTeX is set to false in LocalSettings + $data = array(); + $section = null; + + continue; + } + + if ( $this->parserTest ) { + $this->test = array( + 'test' => ParserTest::chomp( $data['test'] ), + 'input' => ParserTest::chomp( $data['input'] ), + 'result' => ParserTest::chomp( $data['result'] ), + 'options' => ParserTest::chomp( $data['options'] ), + 'config' => ParserTest::chomp( $data['config'] ) ); + } else { + $this->test['test'] = $data['test']; + } + + return true; + } + + if ( isset ( $data[$section] ) ) { + wfDie( "duplicate section '$section' at line {$this->lineNum} of $this->file\n" ); + } + + $data[$section] = ''; + + continue; + } + + if ( $section ) { + $data[$section] .= $line; + } + } + + return false; + } +} diff --git a/maintenance/undelete.php b/maintenance/undelete.php index 099ea88d..50bf791a 100644 --- a/maintenance/undelete.php +++ b/maintenance/undelete.php @@ -6,7 +6,7 @@ * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class Undelete extends Maintenance { public function __construct() { @@ -37,4 +37,4 @@ class Undelete extends Maintenance { } $maintClass = "Undelete"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/update.php b/maintenance/update.php index 5977a4c1..e3941a3c 100644 --- a/maintenance/update.php +++ b/maintenance/update.php @@ -3,58 +3,121 @@ * Run all updaters. * * This is used when the database schema is modified and we need to apply patches. + * It is kept compatible with php 4 parsing so that it can give out a meaningful error. * * @file * @todo document * @ingroup Maintenance */ -/** */ -define( 'MW_CMDLINE_CALLBACK', 'wfSetupUpdateScript' ); +if ( !function_exists( 'version_compare' ) || ( version_compare( phpversion(), '5.2.3' ) < 0 ) ) { + echo "You are using PHP version " . phpversion() . " but MediaWiki needs PHP 5.2.3 or higher. ABORTING.\n" . + "Check if you have a newer php executable with a different name, such as php5.\n"; + die( 1 ); +} + $wgUseMasterForMaintenance = true; -require_once( dirname(__FILE__) . '/commandLine.inc' ); -require( "updaters.inc" ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); -$wgTitle = Title::newFromText( "MediaWiki database updater" ); +class UpdateMediaWiki extends Maintenance { -echo( "MediaWiki {$wgVersion} Updater\n\n" ); + function __construct() { + parent::__construct(); + $this->mDescription = "MediaWiki database updater"; + $this->addOption( 'skip-compat-checks', 'Skips compatibility checks, mostly for developers' ); + $this->addOption( 'quick', 'Skip 5 second countdown before starting' ); + $this->addOption( 'doshared', 'Also update shared tables' ); + $this->addOption( 'nopurge', 'Do not purge the objectcache table after updates' ); + } -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); -} + function getDbType() { + /* If we used the class constant PHP4 would give a parser error here */ + return 2 /* Maintenance::DB_ADMIN */; + } -# Attempt to connect to the database as a privileged user -# This will vomit up an error if there are permissions problems -$wgDatabase = wfGetDB( DB_MASTER ); + function compatChecks() { + $test = new PhpXmlBugTester(); + if ( !$test->ok ) { + $this->error( + "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 (see http://bugs.php.net/bug.php?id=45996).\n", + true ); + } -print "Going to run database updates for ".wfWikiID()."\n"; -print "Depending on the size of your database this may take a while!\n"; + $test = new PhpRefCallBugTester; + $test->execute(); + if ( !$test->ok ) { + $ver = phpversion(); + $this->error( + "PHP $ver is not compatible with MediaWiki due to a bug involving\n" . + "reference parameters to __call. Upgrade to PHP 5.3.2 or higher, or \n" . + "downgrade to PHP 5.3.0 to fix this.\n" . + "ABORTING (see http://bugs.php.net/bug.php?id=50394 for details)\n", + true ); + } + } -if( !isset( $options['quick'] ) ) { - print "Abort with control-c in the next five seconds (skip this countdown with --quick) ... "; - wfCountDown( 5 ); -} + function execute() { + global $wgVersion, $wgTitle, $wgLang; + + $wgLang = Language::factory( 'en' ); + $wgTitle = Title::newFromText( "MediaWiki database updater" ); + + $this->output( "MediaWiki {$wgVersion} Updater\n\n" ); + + if ( !$this->hasOption( 'skip-compat-checks' ) ) { + $this->compatChecks(); + } else { + $this->output( "Skipping compatibility checks, proceed at your own risk (Ctrl+C to abort)\n" ); + wfCountdown( 5 ); + } -$shared = isset( $options['doshared'] ); -$purge = !isset( $options['nopurge'] ); + # Attempt to connect to the database as a privileged user + # This will vomit up an error if there are permissions problems + $db = wfGetDB( DB_MASTER ); -do_all_updates( $shared, $purge ); + $this->output( "Going to run database updates for " . wfWikiID() . "\n" ); + $this->output( "Depending on the size of your database this may take a while!\n" ); -print "Done.\n"; + if ( !$this->hasOption( 'quick' ) ) { + $this->output( "Abort with control-c in the next five seconds (skip this countdown with --quick) ... " ); + wfCountDown( 5 ); + } -function wfSetupUpdateScript() { - global $wgLocalisationCacheConf; + $shared = $this->hasOption( 'doshared' ); - # 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, - ); + $updates = array('core','extensions'); + if( !$this->hasOption('nopurge') ) { + $updates[] = 'purge'; + } + + $updater = DatabaseUpdater::newForDb( $db, $shared, $this ); + $updater->doUpdates( $updates ); + + foreach( $updater->getPostDatabaseUpdateMaintenance() as $maint ) { + $child = $this->runChild( $maint ); + $child->execute(); + } + + $this->output( "\nDone.\n" ); + } + + function afterFinalSetup() { + 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 $wgExtensionFunctions (bug 20471) + $wgLocalisationCacheConf = array( + 'class' => 'LocalisationCache', + 'storeClass' => 'LCStore_Null', + 'storeDirectory' => false, + 'manualRecache' => false, + ); + } } + +$maintClass = 'UpdateMediaWiki'; +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/updateArticleCount.php b/maintenance/updateArticleCount.php index e6818458..b0dceb58 100644 --- a/maintenance/updateArticleCount.php +++ b/maintenance/updateArticleCount.php @@ -18,11 +18,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance * @author Rob Church <robchur@gmail.com> */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class UpdateArticleCount extends Maintenance { @@ -40,10 +41,10 @@ class UpdateArticleCount extends Maintenance { $this->namespaces = $wgContentNamespaces; $this->output( "Counting articles..." ); $result = $this->count(); - - if( $result !== false ) { + + if ( $result !== false ) { $this->output( "found {$result}.\n" ); - if( $this->hasOption( 'update' ) ) { + 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__ ); @@ -63,7 +64,7 @@ class UpdateArticleCount extends Maintenance { * @return string */ private function makeNsSet() { - foreach( $this->namespaces as $namespace ) + foreach ( $this->namespaces as $namespace ) $namespaces[] = intval( $namespace ); return implode( ', ', $namespaces ); } @@ -92,10 +93,9 @@ class UpdateArticleCount extends Maintenance { $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->query( $this->makeSql( $dbr ), __METHOD__ ); $row = $dbr->fetchObject( $res ); - $dbr->freeResult( $res ); return $row ? $row->pagecount : false; } } $maintClass = "UpdateArticleCount"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/updateCollation.php b/maintenance/updateCollation.php new file mode 100644 index 00000000..e890bce7 --- /dev/null +++ b/maintenance/updateCollation.php @@ -0,0 +1,145 @@ +<?php +/** + * @file + * @ingroup Maintenance + * @author Aryeh Gregor (Simetrical) + */ + +#$optionsWithArgs = array( 'begin', 'max-slave-lag' ); + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); + +class UpdateCollation extends Maintenance { + const BATCH_SIZE = 50; + + public function __construct() { + parent::__construct(); + + global $wgCategoryCollation; + $this->mDescription = <<<TEXT +This script will find all rows in the categorylinks table whose collation is +out-of-date (cl_collation != '$wgCategoryCollation') and repopulate cl_sortkey +using the page title and cl_sortkey_prefix. If everything's collation is +up-to-date, it will do nothing. +TEXT; + + $this->addOption( 'force', 'Run on all rows, even if the collation is ' . + 'supposed to be up-to-date.' ); + } + + public function syncDBs() { + $lb = wfGetLB(); + // bug 27975 - Don't try to wait for slaves if there are none + // Prevents permission error when getting master position + if ( $lb->getServerCount() > 1 ) { + $dbw = $lb->getConnection( DB_MASTER ); + $pos = $dbw->getMasterPos(); + $lb->waitForAll( $pos ); + } + } + + public function execute() { + global $wgCategoryCollation, $wgMiserMode; + + $dbw = wfGetDB( DB_MASTER ); + $force = $this->getOption( 'force' ); + + $options = array( 'LIMIT' => self::BATCH_SIZE ); + + if ( $force ) { + $options['ORDER BY'] = 'cl_from, cl_to'; + $collationConds = array(); + } else { + $collationConds = array( 0 => + 'cl_collation != ' . $dbw->addQuotes( $wgCategoryCollation ) ); + + if ( !$wgMiserMode ) { + $count = $dbw->selectField( + 'categorylinks', + 'COUNT(*)', + $collationConds, + __METHOD__ + ); + + if ( $count == 0 ) { + $this->output( "Collations up-to-date.\n" ); + return; + } + $this->output( "Fixing collation for $count rows.\n" ); + } + } + + $count = 0; + $row = false; + $batchConds = array(); + do { + $this->output( 'Processing next ' . self::BATCH_SIZE . ' rows... '); + $res = $dbw->select( + array( 'categorylinks', 'page' ), + array( 'cl_from', 'cl_to', 'cl_sortkey_prefix', 'cl_collation', + 'cl_sortkey', 'page_namespace', 'page_title' + ), + array_merge( $collationConds, $batchConds, array( 'cl_from = page_id' ) ), + __METHOD__, + $options + ); + + $dbw->begin(); + foreach ( $res as $row ) { + $title = Title::newFromRow( $row ); + if ( !$row->cl_collation ) { + # This is an old-style row, so the sortkey needs to be + # converted. + if ( $row->cl_sortkey == $title->getText() + || $row->cl_sortkey == $title->getPrefixedText() ) { + $prefix = ''; + } else { + # Custom sortkey, use it as a prefix + $prefix = $row->cl_sortkey; + } + } else { + $prefix = $row->cl_sortkey_prefix; + } + # cl_type will be wrong for lots of pages if cl_collation is 0, + # so let's update it while we're here. + if ( $title->getNamespace() == NS_CATEGORY ) { + $type = 'subcat'; + } elseif ( $title->getNamespace() == NS_FILE ) { + $type = 'file'; + } else { + $type = 'page'; + } + $dbw->update( + 'categorylinks', + array( + 'cl_sortkey' => Collation::singleton()->getSortKey( + $title->getCategorySortkey( $prefix ) ), + 'cl_sortkey_prefix' => $prefix, + 'cl_collation' => $wgCategoryCollation, + 'cl_type' => $type, + 'cl_timestamp = cl_timestamp', + ), + array( 'cl_from' => $row->cl_from, 'cl_to' => $row->cl_to ), + __METHOD__ + ); + } + $dbw->commit(); + + if ( $force && $row ) { + $encFrom = $dbw->addQuotes( $row->cl_from ); + $encTo = $dbw->addQuotes( $row->cl_to ); + $batchConds = array( + "(cl_from = $encFrom AND cl_to > $encTo) " . + " OR cl_from > $encFrom" ); + } + + $count += $res->numRows(); + $this->output( "$count done.\n" ); + + $this->syncDBs(); + } while ( $res->numRows() == self::BATCH_SIZE ); + } +} + +$maintClass = "UpdateCollation"; +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/updateDoubleWidthSearch.php b/maintenance/updateDoubleWidthSearch.php new file mode 100644 index 00000000..bfbc441f --- /dev/null +++ b/maintenance/updateDoubleWidthSearch.php @@ -0,0 +1,71 @@ +<?php +/** + * Script to normalize double-byte latin UTF-8 characters + * + * Usage: php updateDoubleWidthSearch.php + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + */ + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); + +class UpdateDoubleWidthSearch extends Maintenance { + + public function __construct() { + parent::__construct(); + $this->mDescription = "Script to normalize double-byte latin UTF-8 characters"; + $this->addOption( 'q', 'quiet', false, true ); + $this->addOption( 'l', 'How long the searchindex and revision tables will be locked for', false, true ); + } + + public function getDbType() { + return Maintenance::DB_ADMIN; + } + + public function execute() { + $maxLockTime = $this->getOption( 'l', 20 ); + + $dbw = wfGetDB( DB_MASTER ); + if ( $dbw->getType() !== 'mysql' ) { + $this->output( "This change is only needed on MySQL, quitting.\n" ); + exit( 1 ); + } + + $res = $this->findRows( $dbw ); + $this->updateSearchIndex( $maxLockTime, array( $this, 'searchIndexUpdateCallback' ), $dbw, $res ); + + $this->output( "Done\n" ); + } + + public function searchIndexUpdateCallback( $dbw, $row ) { + return $this->updateSearchIndexForPage( $dbw, $row->si_page ); + } + + private function findRows( $dbw ) { + $searchindex = $dbw->tableName( 'searchindex' ); + $regexp = '[[:<:]]u8efbd([89][1-9a]|8[b-f]|90)[[:>:]]'; + $sql = "SELECT si_page FROM $searchindex + WHERE ( si_text RLIKE '$regexp' ) + OR ( si_title RLIKE '$regexp' )"; + return $dbw->query( $sql, __METHOD__ ); + } +} + +$maintClass = "UpdateDoubleWidthSearch"; +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/updateRestrictions.php b/maintenance/updateRestrictions.php index 80e98fd0..c815f4b9 100644 --- a/maintenance/updateRestrictions.php +++ b/maintenance/updateRestrictions.php @@ -20,10 +20,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class UpdateRestrictions extends Maintenance { public function __construct() { @@ -34,48 +35,48 @@ class UpdateRestrictions extends Maintenance { public function execute() { $db = wfGetDB( DB_MASTER ); - if( !$db->tableExists( 'page_restrictions' ) ) { + if ( !$db->tableExists( 'page_restrictions' ) ) { $this->error( "page_restrictions table does not exist", true ); } $start = $db->selectField( 'page', 'MIN(page_id)', false, __METHOD__ ); - if( !$start ) { + if ( !$start ) { $this->error( "Nothing to do.", true ); } $end = $db->selectField( 'page', 'MAX(page_id)', false, __METHOD__ ); - + # Do remaining chunk $end += $this->mBatchSize - 1; $blockStart = $start; $blockEnd = $start + $this->mBatchSize - 1; $encodedExpiry = 'infinity'; - while( $blockEnd <= $end ) { + while ( $blockEnd <= $end ) { $this->output( "...doing page_id from $blockStart to $blockEnd\n" ); $cond = "page_id BETWEEN $blockStart AND $blockEnd AND page_restrictions !=''"; - $res = $db->select( 'page', array('page_id','page_namespace','page_restrictions'), $cond, __METHOD__ ); + $res = $db->select( 'page', array( 'page_id', 'page_namespace', 'page_restrictions' ), $cond, __METHOD__ ); $batch = array(); - foreach( $res as $row ) { + foreach ( $res as $row ) { $oldRestrictions = array(); - foreach( explode( ':', trim( $row->page_restrictions ) ) as $restrict ) { + 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] ) { + 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] ) { + } else if ( $temp[1] ) { $oldRestrictions[$temp[0]] = trim( $temp[1] ); } } # Clear invalid columns - if( $row->page_namespace == NS_MEDIAWIKI ) { + 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( + foreach ( $oldRestrictions as $action => $restrictions ) { + $batch[] = array( 'pr_page' => $row->page_id, 'pr_type' => $action, 'pr_level' => $restrictions, @@ -87,9 +88,9 @@ class UpdateRestrictions extends Maintenance { # 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', + $ok = $db->deadlockLoop( array( $db, 'insert' ), 'page_restrictions', $batch, __FUNCTION__, array( 'IGNORE' ) ); - if( !$ok ) { + if ( !$ok ) { throw new MWException( "Deadlock loop failed wtf :(" ); } } @@ -101,10 +102,10 @@ class UpdateRestrictions extends Maintenance { // Kill any broken rows from previous imports $db->delete( 'page_restrictions', array( 'pr_level' => '' ) ); // Kill other invalid rows - $db->deleteJoin( 'page_restrictions', 'page', 'pr_page', 'page_id', array('page_namespace' => NS_MEDIAWIKI) ); + $db->deleteJoin( 'page_restrictions', 'page', 'pr_page', 'page_id', array( 'page_namespace' => NS_MEDIAWIKI ) ); $this->output( "...Done!\n" ); } } $maintClass = "UpdateRestrictions"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/updateSearchIndex.php b/maintenance/updateSearchIndex.php index 152ce1b6..97863101 100644 --- a/maintenance/updateSearchIndex.php +++ b/maintenance/updateSearchIndex.php @@ -24,10 +24,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ - -require_once( dirname(__FILE__) . '/Maintenance.php' ); + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class UpdateSearchIndex extends Maintenance { @@ -48,8 +49,8 @@ class UpdateSearchIndex extends Maintenance { $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' ) ) { + $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' ); @@ -61,13 +62,21 @@ class UpdateSearchIndex extends Maintenance { } } $lockTime = $this->getOption( 'l', 20 ); - + $this->doUpdateSearchIndex( $start, $end, $lockTime ); - $file = fopen( $posFile, 'w' ); - fwrite( $file, $end ); - fclose( $file ); + if ( is_writable( dirname( realpath( $posFile ) ) ) ) { + $file = fopen( $posFile, 'w' ); + if ( $file !== false ) { + fwrite( $file, $end ); + fclose( $file ); + } else { + $this->output( "*** Couldn't write to the $posFile!\n" ); + } + } else { + $this->output( "*** Couldn't write to the $posFile!\n" ); + } } - + private function doUpdateSearchIndex( $start, $end, $maxLockTime ) { global $wgDisableSearchUpdate; @@ -89,85 +98,25 @@ class UpdateSearchIndex extends Maintenance { "; $res = $dbw->query( $sql, __METHOD__ ); + $this->updateSearchIndex( $maxLockTime, array( $this, 'searchIndexUpdateCallback' ), $dbw, $res ); - # 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 ); + public function searchIndexUpdateCallback( $dbw, $row ) { + if ( $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 ); + $u->doUpdate(); + $this->output( "\n" ); + } elseif ( $row->rc_type !== RC_LOG ) { + $this->updateSearchIndexForPage( $dbw, $row->rc_cur_id ); + } } } $maintClass = "UpdateSearchIndex"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/updateSpecialPages.php b/maintenance/updateSpecialPages.php index aaad3714..3e5df982 100644 --- a/maintenance/updateSpecialPages.php +++ b/maintenance/updateSpecialPages.php @@ -18,10 +18,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance */ - -require_once( dirname(__FILE__) . '/Maintenance.php' ); + +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class UpdateSpecialPages extends Maintenance { public function __construct() { @@ -36,8 +37,8 @@ class UpdateSpecialPages extends Maintenance { $wgOut->disable(); $dbw = wfGetDB( DB_MASTER ); - foreach( $wgSpecialPageCacheUpdates as $special => $call ) { - if( !is_callable($call) ) { + foreach ( $wgSpecialPageCacheUpdates as $special => $call ) { + if ( !is_callable( $call ) ) { $this->error( "Uncallable function $call!" ); continue; } @@ -45,7 +46,7 @@ class UpdateSpecialPages extends Maintenance { call_user_func( $call, $dbw ); $t2 = explode( ' ', microtime() ); $this->output( sprintf( '%-30s ', $special ) ); - $elapsed = ($t2[0] - $t1[0]) + ($t2[1] - $t1[1]); + $elapsed = ( $t2[0] - $t1[0] ) + ( $t2[1] - $t1[1] ); $hours = intval( $elapsed / 3600 ); $minutes = intval( $elapsed % 3600 / 60 ); $seconds = $elapsed - $hours * 3600 - $minutes * 60; @@ -63,16 +64,16 @@ class UpdateSpecialPages extends Maintenance { // This is needed to initialise $wgQueryPages require_once( "$IP/includes/QueryPage.php" ); - foreach( $wgQueryPages as $page ) { + foreach ( $wgQueryPages as $page ) { @list( $class, $special, $limit ) = $page; # --list : just show the name of pages - if( $this->hasOption('list') ) { + if ( $this->hasOption( 'list' ) ) { $this->output( "$special\n" ); continue; } - if ( !$this->hasOption('override') && $wgDisableQueryPageUpdate && in_array( $special, $wgDisableQueryPageUpdate ) ) { + if ( !$this->hasOption( 'override' ) && $wgDisableQueryPageUpdate && in_array( $special, $wgDisableQueryPageUpdate ) ) { $this->output( sprintf( "%-30s disabled\n", $special ) ); continue; } @@ -88,7 +89,7 @@ class UpdateSpecialPages extends Maintenance { } $queryPage = new $class; - if( !$this->hasOption('only') || $this->getOption('only') == $queryPage->getName() ) { + if ( !$this->hasOption( 'only' ) || $this->getOption( 'only' ) == $queryPage->getName() ) { $this->output( sprintf( '%-30s ', $special ) ); if ( $queryPage->isExpensive() ) { $t1 = explode( ' ', microtime() ); @@ -100,7 +101,7 @@ class UpdateSpecialPages extends Maintenance { } else { $this->output( "got $num rows in " ); - $elapsed = ($t2[0] - $t1[0]) + ($t2[1] - $t1[1]); + $elapsed = ( $t2[0] - $t1[0] ) + ( $t2[1] - $t1[1] ); $hours = intval( $elapsed / 3600 ); $minutes = intval( $elapsed % 3600 / 60 ); $seconds = $elapsed - $hours * 3600 - $minutes * 60; @@ -113,7 +114,7 @@ class UpdateSpecialPages extends Maintenance { $this->output( sprintf( "%.2fs\n", $seconds ) ); } # Reopen any connections that have closed - if ( !wfGetLB()->pingAll()) { + if ( !wfGetLB()->pingAll() ) { $this->output( "\n" ); do { $this->error( "Connection failed, reconnecting in 10 seconds..." ); @@ -135,4 +136,4 @@ class UpdateSpecialPages extends Maintenance { } $maintClass = "UpdateSpecialPages"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/updaters.inc b/maintenance/updaters.inc deleted file mode 100644 index 594d4d78..00000000 --- a/maintenance/updaters.inc +++ /dev/null @@ -1,1979 +0,0 @@ -<?php -/** - * @file - * @ingroup Maintenance - */ - -if ( !defined( 'MEDIAWIKI' ) ) { - echo "This file is not a valid entry point\n"; - exit( 1 ); -} - -require_once 'convertLinks.inc'; -require_once 'userDupes.inc'; -# Extension updates -require_once( "$IP/includes/Hooks.php" ); - -/** - * List of update functions to call for each DB type, in sequence. First item - * is function name, rest are parameters to pass. - */ -$wgUpdates = array( - 'mysql' => array( - // 1.2 - // update_passwords obsolete - array( 'add_field', 'ipblocks', 'ipb_id', 'patch-ipblocks.sql' ), - array( 'add_field', 'ipblocks', 'ipb_expiry', 'patch-ipb_expiry.sql' ), - array( 'do_interwiki_update' ), - array( 'do_index_update' ), - // 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' ), - array( 'add_table', 'objectcache', 'patch-objectcache.sql' ), - array( 'add_table', 'categorylinks', 'patch-categorylinks.sql' ), - // do_linkscc_1_3_update obsolete - 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' ), - array( 'add_field', 'recentchanges', 'rc_patrolled', 'patch-rc-patrol.sql' ), - array( 'add_table', 'logging', 'patch-logging.sql' ), - // do_user_rights_update obsolete - array( 'add_field', 'user', 'user_token', 'patch-user_token.sql' ), - // old, old_articleid, patch-remove-old-title-namespace.sql obsolete - // user_groups, patch-userlevels.sql obsolete - // do_group_update() obsolete - 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' ), - array( 'check_bin', 'logging', 'log_title', 'patch-logging-title.sql', ), - array( 'add_field', 'archive', 'ar_rev_id', 'patch-archive-rev_id.sql' ), - array( 'add_field', 'page', 'page_len', 'patch-page_len.sql' ), - array( 'do_inverse_timestamp' ), - array( 'do_text_id' ), - array( 'add_field', 'revision', 'rev_deleted', 'patch-rev_deleted.sql' ), - array( 'add_field', 'image', 'img_width', 'patch-img_width.sql' ), - array( 'add_field', 'image', 'img_metadata', 'patch-img_metadata.sql' ), - array( 'add_field', 'user', 'user_email_token', 'patch-user_email_token.sql' ), - array( 'add_field', 'archive', 'ar_text_id', 'patch-archive-text_id.sql' ), - array( 'do_namespace_size' ), - array( 'add_field', 'image', 'img_media_type', 'patch-img_media_type.sql' ), - array( 'do_pagelinks_update' ), - array( 'do_drop_img_type' ), - array( 'do_user_unique_update' ), - array( 'do_user_groups_update' ), - array( 'add_field', 'site_stats', 'ss_total_pages', 'patch-ss_total_articles.sql' ), - array( 'add_table', 'user_newtalk', 'patch-usernewtalk2.sql' ), - 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 - array( 'do_logging_timestamp_index' ), - array( 'add_field', 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ), - array( 'do_page_random_update' ), - array( 'add_field', 'user', 'user_registration','patch-user_registration.sql' ), - array( 'do_templatelinks_update' ), - array( 'add_table', 'externallinks', 'patch-externallinks.sql' ), - array( 'add_table', 'job', 'patch-job.sql' ), - array( 'add_field', 'site_stats', 'ss_images', 'patch-ss_images.sql' ), - array( 'add_table', 'langlinks', 'patch-langlinks.sql' ), - array( 'add_table', 'querycache_info', 'patch-querycacheinfo.sql' ), - 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_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' ), - array( 'add_field', 'revision', 'rev_parent_id', 'patch-rev_parent_id.sql' ), - array( 'add_field', 'page_restrictions', 'pr_id', 'patch-page_restrictions_sortkey.sql' ), - array( 'add_field', 'revision', 'rev_len', 'patch-rev_len.sql' ), - array( 'add_field', 'recentchanges', 'rc_deleted', 'patch-rc_deleted.sql' ), - array( 'add_field', 'logging', 'log_deleted', 'patch-log_deleted.sql' ), - array( 'add_field', 'archive', 'ar_deleted', 'patch-ar_deleted.sql' ), - 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' ), - array( 'add_field', 'oldimage', 'oi_metadata', 'patch-oi_metadata.sql'), - array( 'do_archive_user_index' ), - array( 'do_image_user_index' ), - array( 'do_oldimage_user_index' ), - array( 'add_field', 'archive', 'ar_page_id', 'patch-archive-page_id.sql'), - array( 'add_field', 'image', 'img_sha1', 'patch-img_sha1.sql' ), - - // 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' ), - array( 'add_table', 'updatelog', 'patch-updatelog.sql' ), - array( 'add_table', 'category', 'patch-category.sql' ), - array( 'do_category_population' ), - array( 'add_field', 'archive', 'ar_parent_id', 'patch-ar_parent_id.sql'), - array( 'add_field', 'user_newtalk', 'user_last_timestamp', 'patch-user_last_timestamp.sql'), - array( 'do_populate_parent_id' ), - 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( 'do_active_users_init' ), - array( 'add_field', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ), - - // 1.15 - 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( 'do_active_users_init' ), - 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' ), - - // 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' ), - ), -); - - -# For extensions only, should be populated via hooks -# $wgDBtype should be checked to specifiy the proper file -$wgExtNewTables = array(); // table, dir -$wgExtNewFields = array(); // table, column, dir -$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 -# created! -function update_row_exists( $key ) { - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( - 'updatelog', - '1', - array( 'ul_key' => $key ), - __FUNCTION__ - ); - return (bool)$row; -} - -function rename_table( $from, $to, $patch ) { - global $wgDatabase; - if ( $wgDatabase->tableExists( $from ) ) { - if ( $wgDatabase->tableExists( $to ) ) { - wfOut( "...can't move table $from to $to, $to already exists.\n" ); - } else { - wfOut( "Moving table $from to $to..." ); - $wgDatabase->sourceFile( archive($patch) ); - wfOut( "ok\n" ); - } - } else { - // Source table does not exist - // Renames are done before creations, so this is typical for a new installation - // Ignore silently - } -} - -function add_table( $name, $patch, $fullpath=false ) { - global $wgDatabase; - if ( $wgDatabase->tableExists( $name ) ) { - wfOut( "...$name table already exists.\n" ); - } else { - wfOut( "Creating $name table..." ); - if( $fullpath ) { - $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 { - $wgDatabase->sourceFile( archive($patch) ); - } - wfOut( "ok\n" ); - } -} - - - -function add_field( $table, $field, $patch, $fullpath=false ) { - global $wgDatabase; - if ( !$wgDatabase->tableExists( $table ) ) { - wfOut( "...$table table does not exist, skipping new field patch\n" ); - } elseif ( $wgDatabase->fieldExists( $table, $field ) ) { - wfOut( "...have $field field in $table table.\n" ); - } else { - wfOut( "Adding $field field to table $table..." ); - if( $fullpath ) { - $wgDatabase->sourceFile( $patch ); - } else { - $wgDatabase->sourceFile( archive($patch) ); - } - wfOut( "ok\n" ); - } -} - -function add_index( $table, $index, $patch, $fullpath=false ) { - global $wgDatabase; - if( $wgDatabase->indexExists( $table, $index ) ) { - wfOut( "...$index key already set on $table table.\n" ); - } else { - wfOut( "Adding $index key to table $table... " ); - if( $fullpath ) { - $wgDatabase->sourceFile( $patch ); - } else { - $wgDatabase->sourceFile( archive($patch) ); - } - wfOut( "ok\n" ); - } -} - -function do_interwiki_update() { - # Check that interwiki table exists; if it doesn't source it - global $wgDatabase, $IP; - if( $wgDatabase->tableExists( "interwiki" ) ) { - wfOut( "...already have interwiki table\n" ); - return true; - } - wfOut( "Creating interwiki table: " ); - $wgDatabase->sourceFile( archive("patch-interwiki.sql") ); - wfOut( "ok\n" ); - wfOut( "Adding default interwiki definitions: " ); - $wgDatabase->sourceFile( "$IP/maintenance/interwiki.sql" ); - wfOut( "ok\n" ); -} - -function do_index_update() { - # Check that proper indexes are in place - global $wgDatabase; - $meta = $wgDatabase->fieldInfo( "recentchanges", "rc_timestamp" ); - if( !$meta->isMultipleKey() ) { - wfOut( "Updating indexes to 20031107: " ); - $wgDatabase->sourceFile( archive("patch-indexes.sql") ); - wfOut( "ok\n" ); - return true; - } - wfOut( "...indexes seem up to 20031107 standards\n" ); - return false; -} - -function do_image_index_update() { - global $wgDatabase; - - $meta = $wgDatabase->fieldInfo( "image", "img_major_mime" ); - if( !$meta->isMultipleKey() ) { - wfOut( "Updating indexes to 20050912: " ); - $wgDatabase->sourceFile( archive("patch-mimesearch-indexes.sql") ); - wfOut( "ok\n" ); - return true; - } - wfOut( "...indexes seem up to 20050912 standards\n" ); - return false; -} - -function do_image_name_unique_update() { - global $wgDatabase; - if( $wgDatabase->indexExists( 'image', 'PRIMARY' ) ) { - wfOut( "...image primary key already set.\n" ); - } else { - wfOut( "Making img_name the primary key... " ); - $wgDatabase->sourceFile( archive("patch-image_name_primary.sql") ); - wfOut( "ok\n" ); - } -} - -function do_logging_timestamp_index() { - global $wgDatabase; - if( $wgDatabase->indexExists( 'logging', 'times' ) ) { - wfOut( "...timestamp key on logging already exists.\n" ); - } else { - wfOut( "Adding timestamp key on logging table... " ); - $wgDatabase->sourceFile( archive("patch-logging-times-index.sql") ); - wfOut( "ok\n" ); - } -} - -function do_archive_user_index() { - global $wgDatabase; - if( $wgDatabase->indexExists( 'archive', 'usertext_timestamp' ) ) { - wfOut( "...usertext,timestamp key on archive already exists.\n" ); - } else { - wfOut( "Adding usertext,timestamp key on archive table... " ); - $wgDatabase->sourceFile( archive("patch-archive-user-index.sql") ); - wfOut( "ok\n" ); - } -} - -function do_image_user_index() { - global $wgDatabase; - if( $wgDatabase->indexExists( 'image', 'img_usertext_timestamp' ) ) { - wfOut( "...usertext,timestamp key on image already exists.\n" ); - } else { - wfOut( "Adding usertext,timestamp key on image table... " ); - $wgDatabase->sourceFile( archive("patch-image-user-index.sql") ); - wfOut( "ok\n" ); - } -} - -function do_oldimage_user_index() { - global $wgDatabase; - if( $wgDatabase->indexExists( 'oldimage', 'oi_usertext_timestamp' ) ) { - wfOut( "...usertext,timestamp key on oldimage already exists.\n" ); - } else { - wfOut( "Adding usertext,timestamp key on oldimage table... " ); - $wgDatabase->sourceFile( archive("patch-oldimage-user-index.sql") ); - wfOut( "ok\n" ); - } -} - -function do_watchlist_update() { - global $wgDatabase; - $fname = 'do_watchlist_update'; - if( $wgDatabase->fieldExists( 'watchlist', 'wl_notificationtimestamp' ) ) { - wfOut( "The watchlist table is already set up for email notification.\n" ); - } else { - wfOut( "Adding wl_notificationtimestamp field for email notification management." ); - /* ALTER TABLE watchlist ADD (wl_notificationtimestamp varchar(14) binary NOT NULL default '0'); */ - $wgDatabase->sourceFile( archive( 'patch-email-notification.sql' ) ); - wfOut( "ok\n" ); - } - # Check if we need to add talk page rows to the watchlist - $talk = $wgDatabase->selectField( 'watchlist', 'count(*)', 'wl_namespace & 1', $fname ); - $nontalk = $wgDatabase->selectField( 'watchlist', 'count(*)', 'NOT (wl_namespace & 1)', $fname ); - if ( $talk != $nontalk ) { - wfOut( "Adding missing watchlist talk page rows... " ); - flush(); - - $wgDatabase->insertSelect( 'watchlist', 'watchlist', - array( - 'wl_user' => 'wl_user', - 'wl_namespace' => 'wl_namespace | 1', - 'wl_title' => 'wl_title', - 'wl_notificationtimestamp' => 'wl_notificationtimestamp' - ), array( 'NOT (wl_namespace & 1)' ), $fname, 'IGNORE' ); - wfOut( "ok\n" ); - } else { - wfOut( "...watchlist talk page rows already present\n" ); - } -} - -function do_copy_newtalk_to_watchlist() { - global $wgDatabase; - 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' ) ); - $num_newtalks=$wgDatabase->numRows($res); - wfOut( "Now converting $num_newtalks user_newtalk entries to watchlist table entries ... \n" ); - - $user = new User(); - for ( $i = 1; $i <= $num_newtalks; $i++ ) { - $wluser = $wgDatabase->fetchObject( $res ); - if ($wluser->user_id == 0) { # anonymous users ... have IP numbers as "names" - 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' - ), 'updaters.inc::do_watchlist_update2' - ); - } - } else { # normal users ... have user_ids - $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' - ), 'updaters.inc::do_watchlist_update3' - ); - } - } - wfOut( "Done.\n" ); -} - - -function do_user_update() { - global $wgDatabase; - if( $wgDatabase->fieldExists( 'user', 'user_emailauthenticationtimestamp' ) ) { - wfOut( "User table contains old email authentication field. Dropping... " ); - $wgDatabase->sourceFile( archive( 'patch-email-authentication.sql' ) ); - wfOut( "ok\n" ); - } else { - wfOut( "...user table does not contain old email authentication field.\n" ); - } -} - -/** - * 1.4 betas were missing the 'binary' marker from logging.log_title, - * which causes a collation mismatch error on joins in MySQL 4.1. - */ -function check_bin( $table, $field, $patchFile ) { - global $wgDatabase, $wgDBtype; - if ($wgDBtype != 'mysql') - return; - $tableName = $wgDatabase->tableName( $table ); - $res = $wgDatabase->query( "SELECT $field FROM $tableName LIMIT 0" ); - $flags = explode( ' ', mysql_field_flags( $res->result, 0 ) ); - $wgDatabase->freeResult( $res ); - - if( in_array( 'binary', $flags ) ) { - wfOut( "$table table has correct $field encoding.\n" ); - } else { - wfOut( "Fixing $field encoding on $table table... " ); - $wgDatabase->sourceFile( archive( $patchFile ) ); - wfOut( "ok\n" ); - } -} - -function do_schema_restructuring() { - global $wgDatabase; - $fname="do_schema_restructuring"; - if ( $wgDatabase->tableExists( 'page' ) ) { - wfOut( "...page table already exists.\n" ); - } else { - wfOut( "...converting from cur/old to page/revision/text DB structure.\n" ); - wfOut( wfTimestamp( TS_DB ) ); - wfOut( "......checking for duplicate entries.\n" ); - - list ($cur, $old, $page, $revision, $text) = $wgDatabase->tableNamesN( 'cur', 'old', 'page', 'revision', 'text' ); - - $rows = $wgDatabase->query( "SELECT cur_title, cur_namespace, COUNT(cur_namespace) AS c - FROM $cur GROUP BY cur_title, cur_namespace HAVING c>1", $fname ); - - if ( $wgDatabase->numRows( $rows ) > 0 ) { - wfOut( wfTimestamp( TS_DB ) ); - wfOut( "......<b>Found duplicate entries</b>\n" ); - wfOut( sprintf( "<b> %-60s %3s %5s</b>\n", 'Title', 'NS', 'Count' ) ); - while ( $row = $wgDatabase->fetchObject( $rows ) ) { - if ( ! isset( $duplicate[$row->cur_namespace] ) ) { - $duplicate[$row->cur_namespace] = array(); - } - $duplicate[$row->cur_namespace][] = $row->cur_title; - wfOut( sprintf( " %-60s %3s %5s\n", $row->cur_title, $row->cur_namespace, $row->c ) ); - } - $sql = "SELECT cur_title, cur_namespace, cur_id, cur_timestamp FROM $cur WHERE "; - $firstCond = true; - foreach ( $duplicate as $ns => $titles ) { - if ( $firstCond ) { - $firstCond = false; - } else { - $sql .= ' OR '; - } - $sql .= "( cur_namespace = {$ns} AND cur_title in ("; - $first = true; - foreach ( $titles as $t ) { - if ( $first ) { - $sql .= $wgDatabase->addQuotes( $t ); - $first = false; - } else { - $sql .= ', ' . $wgDatabase->addQuotes( $t ); - } - } - $sql .= ") ) \n"; - } - # By sorting descending, the most recent entry will be the first in the list. - # All following entries will be deleted by the next while-loop. - $sql .= 'ORDER BY cur_namespace, cur_title, cur_timestamp DESC'; - - $rows = $wgDatabase->query( $sql, $fname ); - - $prev_title = $prev_namespace = false; - $deleteId = array(); - - while ( $row = $wgDatabase->fetchObject( $rows ) ) { - if ( $prev_title == $row->cur_title && $prev_namespace == $row->cur_namespace ) { - $deleteId[] = $row->cur_id; - } - $prev_title = $row->cur_title; - $prev_namespace = $row->cur_namespace; - } - $sql = "DELETE FROM $cur WHERE cur_id IN ( " . join( ',', $deleteId ) . ')'; - $rows = $wgDatabase->query( $sql, $fname ); - wfOut( wfTimestamp( TS_DB ) ); - wfOut( "......<b>Deleted</b> ".$wgDatabase->affectedRows()." records.\n" ); - } - - - 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) - ) 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_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) - ) ENGINE=InnoDB", $fname ); - - wfOut( wfTimestamp( TS_DB ) ); - wfOut( "......Locking tables.\n" ); - $wgDatabase->query( "LOCK TABLES $page WRITE, $revision WRITE, $old WRITE, $cur WRITE", $fname ); - - $maxold = intval( $wgDatabase->selectField( 'old', 'max(old_id)', '', $fname ) ); - wfOut( wfTimestamp( TS_DB ) ); - wfOut( "......maxold is {$maxold}\n" ); - - wfOut( wfTimestamp( TS_DB ) ); - global $wgLegacySchemaConversion; - if( $wgLegacySchemaConversion ) { - // Create HistoryBlobCurStub entries. - // Text will be pulled from the leftover 'cur' table at runtime. - wfOut( "......Moving metadata from cur; using blob references to text in cur table.\n" ); - $cur_text = "concat('O:18:\"historyblobcurstub\":1:{s:6:\"mCurId\";i:',cur_id,';}')"; - $cur_flags = "'object'"; - } else { - // Copy all cur text in immediately: this may take longer but avoids - // having to keep an extra table around. - wfOut( "......Moving text from cur.\n" ); - $cur_text = 'cur_text'; - $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 ); - - 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) - SELECT old_id, cur_id, old_comment, old_user, old_user_text, - 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 ); - - wfOut( wfTimestamp( TS_DB ) ); - wfOut( "......Unlocking tables.\n" ); - $wgDatabase->query( "UNLOCK TABLES", $fname ); - - wfOut( wfTimestamp( TS_DB ) ); - wfOut( "......Renaming old.\n" ); - $wgDatabase->query( "ALTER TABLE $old RENAME TO $text", $fname ); - - wfOut( wfTimestamp( TS_DB ) ); - wfOut( "...done.\n" ); - } -} - -function do_inverse_timestamp() { - global $wgDatabase; - if( $wgDatabase->fieldExists( 'revision', 'inverse_timestamp' ) ) { - wfOut( "Removing revision.inverse_timestamp and fixing indexes... " ); - $wgDatabase->sourceFile( archive( 'patch-inverse_timestamp.sql' ) ); - wfOut( "ok\n" ); - } else { - wfOut( "revision timestamp indexes already up to 2005-03-13\n" ); - } -} - -function do_text_id() { - global $wgDatabase; - if( $wgDatabase->fieldExists( 'revision', 'rev_text_id' ) ) { - wfOut( "...rev_text_id already in place.\n" ); - } else { - wfOut( "Adding rev_text_id field... " ); - $wgDatabase->sourceFile( archive( 'patch-rev_text_id.sql' ) ); - wfOut( "ok\n" ); - } -} - -function do_namespace_size() { - $tables = array( - 'page' => 'page', - 'archive' => 'ar', - 'recentchanges' => 'rc', - 'watchlist' => 'wl', - 'querycache' => 'qc', - 'logging' => 'log', - ); - foreach( $tables as $table => $prefix ) { - do_namespace_size_on( $table, $prefix ); - flush(); - } -} - -function do_namespace_size_on( $table, $prefix ) { - global $wgDatabase, $wgDBtype; - if ($wgDBtype != 'mysql') - return; - $field = $prefix . '_namespace'; - - $tablename = $wgDatabase->tableName( $table ); - $result = $wgDatabase->query( "SHOW COLUMNS FROM $tablename LIKE '$field'" ); - $info = $wgDatabase->fetchObject( $result ); - $wgDatabase->freeResult( $result ); - - if( substr( $info->Type, 0, 3 ) == 'int' ) { - wfOut( "...$field is already a full int ($info->Type).\n" ); - } else { - wfOut( "Promoting $field from $info->Type to int... " ); - - $sql = "ALTER TABLE $tablename MODIFY $field int NOT NULL"; - $wgDatabase->query( $sql ); - - wfOut( "ok\n" ); - } -} - -function do_pagelinks_update() { - global $wgDatabase; - if( $wgDatabase->tableExists( 'pagelinks' ) ) { - wfOut( "...already have pagelinks table.\n" ); - } else { - wfOut( "Converting links and brokenlinks tables to pagelinks... " ); - $wgDatabase->sourceFile( archive( 'patch-pagelinks.sql' ) ); - wfOut( "ok\n" ); - flush(); - - global $wgCanonicalNamespaceNames; - foreach( $wgCanonicalNamespaceNames as $ns => $name ) { - if( $ns != 0 ) { - do_pagelinks_namespace( $ns ); - } - } - } -} - -function do_pagelinks_namespace( $namespace ) { - global $wgDatabase, $wgContLang; - - $ns = intval( $namespace ); - wfOut( "Cleaning up broken links for namespace $ns... " ); - - $pagelinks = $wgDatabase->tableName( 'pagelinks' ); - $name = $wgContLang->getNsText( $ns ); - $prefix = $wgDatabase->strencode( $name ); - $likeprefix = str_replace( '_', '\\_', $prefix); - - $sql = "UPDATE $pagelinks - SET pl_namespace=$ns, - pl_title=TRIM(LEADING '$prefix:' FROM pl_title) - WHERE pl_namespace=0 - AND pl_title LIKE '$likeprefix:%'"; - - $wgDatabase->query( $sql, 'do_pagelinks_namespace' ); - wfOut( "ok\n" ); -} - -function do_drop_img_type() { - global $wgDatabase; - - if( $wgDatabase->fieldExists( 'image', 'img_type' ) ) { - wfOut( "Dropping unused img_type field in image table... " ); - $wgDatabase->sourceFile( archive( 'patch-drop_img_type.sql' ) ); - wfOut( "ok\n" ); - } else { - wfOut( "No img_type field in image table; Good.\n" ); - } -} - -function do_old_links_update() { - global $wgDatabase; - if( $wgDatabase->tableExists( 'pagelinks' ) ) { - wfOut( "Already have pagelinks; skipping old links table updates.\n" ); - } else { - convertLinks(); flush(); - } -} - -function fix_ancient_imagelinks() { - global $wgDatabase; - $info = $wgDatabase->fieldInfo( 'imagelinks', 'il_from' ); - 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" ); - $wgDatabase->sourceFile( archive( 'patch-fix-il_from.sql' ) ); - wfOut( "ok\n" ); - } else { - wfOut( "...il_from OK\n" ); - } -} - -function do_user_unique_update() { - global $wgDatabase; - $duper = new UserDupes( $wgDatabase ); - if( $duper->hasUniqueIndex() ) { - wfOut( "Already have unique user_name index.\n" ); - } else { - if( !$duper->clearDupes() ) { - wfOut( "WARNING: This next step will probably fail due to unfixed duplicates...\n" ); - } - wfOut( "Adding unique index on user_name... " ); - $wgDatabase->sourceFile( archive( 'patch-user_nameindex.sql' ) ); - wfOut( "ok\n" ); - } -} - -function do_user_groups_update() { - $fname = 'do_user_groups_update'; - global $wgDatabase; - - if( $wgDatabase->tableExists( 'user_groups' ) ) { - wfOut( "...user_groups table already exists.\n" ); - return do_user_groups_reformat(); - } - - wfOut( "Adding user_groups table... " ); - $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..." ); - $wgDatabase->sourceFile( archive( 'patch-user_rights.sql' ) ); - wfOut( "ok\n" ); - } else { - wfOut( "*** WARNING: couldn't locate user_rights table or field for upgrade.\n" ); - wfOut( "*** You may need to manually configure some sysops by manipulating\n" ); - wfOut( "*** the user_groups table.\n" ); - return; - } - } - - wfOut( "Converting user_rights table to user_groups... " ); - $result = $wgDatabase->select( 'user_rights', - array( 'ur_user', 'ur_rights' ), - array( "ur_rights != ''" ), - $fname ); - - while( $row = $wgDatabase->fetchObject( $result ) ) { - $groups = array_unique( - array_map( 'trim', - explode( ',', $row->ur_rights ) ) ); - - foreach( $groups as $group ) { - $wgDatabase->insert( 'user_groups', - array( - 'ug_user' => $row->ur_user, - 'ug_group' => $group ), - $fname ); - } - } - $wgDatabase->freeResult( $result ); - wfOut( "ok\n" ); -} - -function do_user_groups_reformat() { - # Check for bogus formats from previous 1.5 alpha code. - global $wgDatabase; - $info = $wgDatabase->fieldInfo( 'user_groups', 'ug_group' ); - - if( $info->type() == 'int' ) { - $oldug = $wgDatabase->tableName( 'user_groups' ); - $newug = $wgDatabase->tableName( 'user_groups_bogus' ); - wfOut( "user_groups is in bogus intermediate format. Renaming to $newug... " ); - $wgDatabase->query( "ALTER TABLE $oldug RENAME TO $newug" ); - wfOut( "ok\n" ); - - wfOut( "Re-adding fresh user_groups table... " ); - $wgDatabase->sourceFile( archive( 'patch-user_groups.sql' ) ); - wfOut( "ok\n" ); - - wfOut( "***\n" ); - wfOut( "*** WARNING: You will need to manually fix up user permissions in the user_groups\n" ); - wfOut( "*** table. Old 1.5 alpha versions did some pretty funky stuff...\n" ); - wfOut( "***\n" ); - } else { - wfOut( "...user_groups is in current format.\n" ); - } - -} - -function do_watchlist_null() { - # Make sure wl_notificationtimestamp can be NULL, - # and update old broken items. - global $wgDatabase; - $info = $wgDatabase->fieldInfo( 'watchlist', 'wl_notificationtimestamp' ); - - if( !$info->nullable() ) { - wfOut( "Making wl_notificationtimestamp nullable... " ); - $wgDatabase->sourceFile( archive( 'patch-watchlist-null.sql' ) ); - wfOut( "ok\n" ); - } else { - wfOut( "...wl_notificationtimestamp is already nullable.\n" ); - } - -} - -/** - * @bug 3946 - */ -function do_page_random_update() { - global $wgDatabase; - - wfOut( "Setting page_random to a random value on rows where it equals 0..." ); - - $page = $wgDatabase->tableName( 'page' ); - $wgDatabase->query( "UPDATE $page SET page_random = RAND() WHERE page_random = 0", 'do_page_random_update' ); - $rows = $wgDatabase->affectedRows(); - - wfOut( "changed $rows rows\n" ); -} - -function do_templatelinks_update() { - global $wgDatabase, $wgLoadBalancer; - $fname = 'do_templatelinks_update'; - - if ( $wgDatabase->tableExists( 'templatelinks' ) ) { - wfOut( "...templatelinks table already exists\n" ); - return; - } - wfOut( "Creating templatelinks table...\n" ); - $wgDatabase->sourceFile( archive('patch-templatelinks.sql') ); - wfOut( "Populating...\n" ); - if ( isset( $wgLoadBalancer ) && $wgLoadBalancer->getServerCount() > 1 ) { - // Slow, replication-friendly update - $res = $wgDatabase->select( 'pagelinks', array( 'pl_from', 'pl_namespace', 'pl_title' ), - array( 'pl_namespace' => NS_TEMPLATE ), $fname ); - $count = 0; - while ( $row = $wgDatabase->fetchObject( $res ) ) { - $count = ($count + 1) % 100; - if ( $count == 0 ) { - if ( function_exists( 'wfWaitForSlaves' ) ) { - wfWaitForSlaves( 10 ); - } else { - sleep( 1 ); - } - } - $wgDatabase->insert( 'templatelinks', - array( - 'tl_from' => $row->pl_from, - 'tl_namespace' => $row->pl_namespace, - 'tl_title' => $row->pl_title, - ), $fname - ); - - } - $wgDatabase->freeResult( $res ); - } else { - // Fast update - $wgDatabase->insertSelect( 'templatelinks', 'pagelinks', - array( - 'tl_from' => 'pl_from', - 'tl_namespace' => 'pl_namespace', - 'tl_title' => 'pl_title' - ), array( - 'pl_namespace' => 10 - ), $fname - ); - } - wfOut( "Done. Please run maintenance/refreshLinks.php for a more thorough templatelinks update.\n" ); -} - -// Add index on ( rc_namespace, rc_user_text ) [Jul. 2006] -// Add index on ( rc_user_text, rc_timestamp ) [Nov. 2006] -function do_rc_indices_update() { - global $wgDatabase; - wfOut( "Checking for additional recent changes indices...\n" ); - - $indexes = array( - '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..." ); - $wgDatabase->sourceFile( archive( $patch ) ); - wfOut( "done.\n" ); - } else { - wfOut( "...index `{$index}` seems ok.\n" ); - } - } -} - -function index_has_field($table, $index, $field) { - global $wgDatabase; - wfOut( "Checking if $table index $index includes field $field...\n" ); - $info = $wgDatabase->indexInfo( $table, $index, __METHOD__ ); - if( $info ) { - foreach($info as $row) { - if($row->Column_name == $field) { - wfOut( "...index $index on table $table seems to be ok\n" ); - return true; - } - } - } - wfOut( "...index $index on table $table has no field $field; adding\n" ); - return false; -} - -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')) - { - $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')) - { - $wgDatabase->sourceFile( archive( 'patch-categorylinksindex.sql' ) ); - wfOut( "...categorylinks indices updated\n" ); - } -} - -function do_filearchive_indices_update() { - global $wgDatabase; - wfOut( "Checking filearchive indices...\n" ); - $info = $wgDatabase->indexInfo( 'filearchive', 'fa_user_timestamp', __METHOD__ ); - if ( !$info ) - { - $wgDatabase->sourceFile( archive( 'patch-filearchive-user-index.sql' ) ); - wfOut( "...filearchive indices updated\n" ); - } -} - -function maybe_do_profiling_memory_update() { - global $wgDatabase; - if ( !$wgDatabase->tableExists( 'profiling' ) ) { - // Simply ignore - } elseif ( $wgDatabase->fieldExists( 'profiling', 'pf_memory' ) ) { - wfOut( "profiling table has pf_memory field.\n" ); - } else { - wfOut( "Adding pf_memory field to table profiling..." ); - $wgDatabase->sourceFile( archive( 'patch-profiling-memory.sql' ) ); - wfOut( "ok\n" ); - } -} - -function do_stats_init() { - // Sometimes site_stats table is not properly populated. - global $wgDatabase; - wfOut( "Checking site_stats row..." ); - $row = $wgDatabase->selectRow( 'site_stats', '*', array( 'ss_row_id' => 1 ), __METHOD__ ); - if( $row === false ) { - wfOut( "data is missing! rebuilding...\n" ); - } elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) { - wfOut( "missing ss_total_pages, rebuilding...\n" ); - } else { - wfOut( "ok.\n" ); - return; - } - 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', - 'COUNT( DISTINCT rc_user_text )', - array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers'" ), __METHOD__ - ); - $wgDatabase->update( 'site_stats', - array( 'ss_active_users' => intval($activeUsers) ), - array( 'ss_row_id' => 1 ), __METHOD__, array( 'LIMIT' => 1 ) - ); - } - wfOut( "...ss_active_users user count set...\n" ); -} - -function purge_cache() { - global $wgDatabase; - # We can't guarantee that the user will be able to use TRUNCATE, - # but we know that DELETE is available to us - wfOut( "Purging caches..." ); - $wgDatabase->delete( 'objectcache', '*', __METHOD__ ); - wfOut( "done.\n" ); -} - -function do_all_updates( $shared = false, $purge = true ) { - global $wgNewTables, $wgExtModifiedFields, $wgNewFields, $wgRenamedTables, $wgSharedDB, $wgSharedTables, $wgDatabase, $wgDBtype, $IP; - - wfRunHooks('LoadExtensionSchemaUpdates'); - - $doUser = $shared ? $wgSharedDB && in_array('user', $wgSharedTables) : !$wgSharedDB || !in_array('user', $wgSharedTables); - - if ($wgDBtype === 'postgres') { - do_postgres_updates(); - return; - } - - # Run core updates in sequence... - global $wgUpdates; - if ( isset( $wgUpdates[$wgDBtype] ) ) { - foreach( $wgUpdates[$wgDBtype] as $params ) { - $func = array_shift( $params ); - call_user_func_array( $func, $params ); - flush(); - } - } - - /// @fixme clean up this mess too! - global $wgExtNewTables, $wgExtNewFields, $wgExtNewIndexes; - # Add missing extension tables - foreach ( $wgExtNewTables as $tableRecord ) { - add_table( $tableRecord[0], $tableRecord[1], true ); - flush(); - } - # Add missing extension fields - foreach ( $wgExtNewFields as $fieldRecord ) { - if ( $fieldRecord[0] != 'user' || $doUser ) { - add_field( $fieldRecord[0], $fieldRecord[1], $fieldRecord[2], true ); - } - flush(); - } - # Add missing extension indexes - foreach ( $wgExtNewIndexes as $fieldRecord ) { - 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!)..." ); - 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(); - } -} - -function archive($name) { - global $wgDBtype, $IP; - if ( file_exists( "$IP/maintenance/$wgDBtype/archives/$name" ) ) { - return "$IP/maintenance/$wgDBtype/archives/$name"; - } else { - return "$IP/maintenance/archives/$name"; - } -} - -function do_restrictions_update() { - # Adding page_restrictions table, obsoleting page.page_restrictions. - # Migrating old restrictions to new table - # -- Andrew Garrett, January 2007. - - global $wgDatabase; - - $name = 'page_restrictions'; - $patch = 'patch-page_restrictions.sql'; - $patch2 = 'patch-page_restrictions_sortkey.sql'; - - if ( $wgDatabase->tableExists( $name ) ) { - wfOut( "...$name table already exists.\n" ); - } else { - wfOut( "Creating $name table..." ); - $wgDatabase->sourceFile( archive($patch) ); - $wgDatabase->sourceFile( archive($patch2) ); - wfOut( "ok\n" ); - - wfOut( "Migrating old restrictions to new table..." ); - - $res = $wgDatabase->select( 'page', array( 'page_id', 'page_restrictions' ), array("page_restrictions!=''", "page_restrictions!='edit=:move='"), __METHOD__ ); - - $count = 0; - - while ($row = $wgDatabase->fetchObject($res) ) { - $count = ($count + 1) % 100; - - if ($count == 0) { - if ( function_exists( 'wfWaitForSlaves' ) ) { - wfWaitForSlaves( 10 ); - } else { - sleep( 1 ); - } - } - - # Figure out what the restrictions are.. - $id = $row->page_id; - $flatrestrictions = explode( ':', $row->page_restrictions ); - - $restrictions = array (); - foreach( $flatrestrictions as $restriction ) { - $thisrestriction = explode( '=', $restriction, 2 ); - if( count( $thisrestriction ) == 1 ) { - // Compatibility with old protections from before - // separate move protection was added. - list( $level ) = $thisrestriction; - if( $level ) { - $restrictions['edit'] = $level; - $restrictions['move'] = $level; - } - } else { - list( $type, $level ) = $thisrestriction; - if( $level ) { - $restrictions[$type] = $level; - } - } - - $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, - 'pr_level' => $level, - 'pr_cascade' => 0, - 'pr_expiry' => 'infinity' ), - __METHOD__ ); - } - } - wfOut( "ok\n" ); - } -} - -function do_category_population() { - if( update_row_exists( 'populate category' ) ) { - wfOut( "...category table already populated.\n" ); - return; - } - require_once( 'populateCategory.inc' ); - wfOut( "Populating category table, printing progress markers. " ). -"For large databases, you\n". -"may want to hit Ctrl-C and do this manually with maintenance/\n". -"populateCategory.php.\n"; - populateCategory( '', 10, 0, true ); - wfOut( "Done populating category table.\n" ); -} - -function do_populate_parent_id() { - if( update_row_exists( 'populate rev_parent_id' ) ) { - wfOut( "...rev_parent_id column already populated.\n" ); - return; - } - require_once( 'populateParentId.inc' ); - - global $wgDatabase; - populate_rev_parent_id( $wgDatabase ); -} - -function sqlite_initial_indexes() { - global $wgDatabase; - // initial-indexes.sql fails if the indexes are already present, so we perform a quick check if our database is newer. - if ( update_row_exists( 'initial_indexes' ) || $wgDatabase->indexExists( 'user', 'user_name' ) ) { - wfOut( "...have initial indexes\n" ); - return; - } - wfOut( "Adding initial indexes..." ); - $wgDatabase->sourceFile( archive( 'initial-indexes.sql' ) ); - 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' ); - if( is_array($info) && !$info[0]->Non_unique ) { - wfOut( "...pl_namespace, tl_namespace, il_to indices are already UNIQUE.\n" ); - } else { - wfOut( "Making pl_namespace, tl_namespace and il_to indices UNIQUE... " ); - $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 stuff - * TODO: merge with above - ***********************************************************************/ - -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 - AND attrelid=pg_class.oid AND attnum > 0 - AND relname=%s AND nspname=%s -END; - $res = $wgDatabase->query(sprintf($q, - $wgDatabase->addQuotes($table), - $wgDatabase->addQuotes($wgDBmwschema))); - if (!$res) - return null; - - $cols = array(); - while ($r = $wgDatabase->fetchRow($res)) { - $cols[] = array( - "name" => $r[0], - "ord" => $r[1], - ); - } - return $cols; -} - -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) - $q = <<<END -SELECT indkey, indrelid FROM pg_namespace, pg_class, pg_index - WHERE nspname=%s - AND pg_class.relnamespace = pg_namespace.oid - AND relname=%s - AND indexrelid=pg_class.oid -END; - $res = $wgDatabase->query(sprintf($q, - $wgDatabase->addQuotes($wgDBmwschema), - $wgDatabase->addQuotes($idx))); - if (!$res) - return null; - if (!($r = $wgDatabase->fetchRow($res))) { - $wgDatabase->freeResult($res); - return null; - } - - $indkey = $r[0]; - $relid = intval($r[1]); - $indkeys = explode(" ", $indkey); - $wgDatabase->freeResult($res); - - $colnames = array(); - foreach ($indkeys as $rid) { - $query = <<<END -SELECT attname FROM pg_class, pg_attribute - WHERE attrelid=$relid - AND attnum=%d - AND attrelid=pg_class.oid -END; - $r2 = $wgDatabase->query(sprintf($query, $rid)); - if (!$r2) - return null; - if (!($row2 = $wgDatabase->fetchRow($r2))) { - $wgDatabase->freeResult($r2); - return null; - } - $colnames[] = $row2[0]; - $wgDatabase->freeResult($r2); - } - - return $colnames; -} - -function pg_index_exists($table, $index) { - global $wgDatabase, $wgDBmwschema; - $exists = $wgDatabase->selectField("pg_indexes", "indexname", - array( "indexname" => $index, - "tablename" => $table, - "schemaname" => $wgDBmwschema)); - return $exists === $index; -} - -function pg_fkey_deltype($fkey) { - global $wgDatabase, $wgDBmwschema; - $q = <<<END -SELECT confdeltype FROM pg_constraint, pg_namespace - WHERE connamespace=pg_namespace.oid - AND nspname=%s - AND conname=%s; -END; - $r = $wgDatabase->query(sprintf($q, - $wgDatabase->addQuotes($wgDBmwschema), - $wgDatabase->addQuotes($fkey))); - if (!($row = $wgDatabase->fetchRow($r))) - return null; - return $row[0]; -} - -function pg_rule_def($table, $rule) { - global $wgDatabase, $wgDBmwschema; - $q = <<<END -SELECT definition FROM pg_rules - WHERE schemaname = %s - AND tablename = %s - AND rulename = %s -END; - $r = $wgDatabase->query(sprintf($q, - $wgDatabase->addQuotes($wgDBmwschema), - $wgDatabase->addQuotes($table), - $wgDatabase->addQuotes($rule))); - $row = $wgDatabase->fetchRow($r); - if (!$row) - return null; - $d = $row[0]; - $wgDatabase->freeResult($r); - return $d; -} - -function do_postgres_updates() { - global $wgDatabase, $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgShowExceptionDetails, $wgDBuser; - - ## Gather version numbers in case we need them - $version = $wgDatabase->getServerVersion(); ## long string - $numver = $wgDatabase->numeric_version; ## X.Y e.g. 8.3 - - $wgShowExceptionDetails = 1; - - # Just in case their LocalSettings.php does not have this: - if ( !isset( $wgDBmwschema )) - $wgDBmwschema = 'mediawiki'; - - # Verify that this user is configured correctly - $safeuser = $wgDatabase->addQuotes($wgDBuser); - $SQL = "SELECT array_to_string(useconfig,'*') FROM pg_catalog.pg_user WHERE usename = $safeuser"; - $config = pg_fetch_result( $wgDatabase->doQuery( $SQL ), 0, 0 ); - $conf = array(); - foreach( explode( '*', $config ) as $c ) { - list( $x,$y ) = explode( '=', $c ); - $conf[$x] = $y; - } - if( !array_key_exists( 'search_path', $conf ) ) { - $search_path = ''; - } - else { - $search_path = $conf['search_path']; - } - - $safeuser = $wgDatabase->quote_ident($wgDBuser); - if( strpos( $search_path, $wgDBmwschema ) === false ) { - wfOut( "Adding in schema \"$wgDBmwschema\" to search_path for user \"$wgDBuser\"\n" ); - $search_path = "$wgDBmwschema, $search_path"; - } - if( strpos( $search_path, $wgDBts2schema ) === false ) { - wfOut( "Adding in schema \"$wgDBts2schema\" to search_path for user \"$wgDBuser\"\n" ); - $search_path = "$search_path, $wgDBts2schema"; - } - $search_path = str_replace( ', ,', ',', $search_path); - if( array_key_exists( 'search_path', $conf ) === false || $search_path != $conf['search_path'] ) { - $wgDatabase->doQuery( "ALTER USER $safeuser SET search_path = $search_path" ); - $wgDatabase->doQuery( "SET search_path = $search_path" ); - } - else { - $path = $conf['search_path']; - wfOut( "... search_path for user \"$wgDBuser\" looks correct ($path)\n" ); - } - $goodconf = array( - 'client_min_messages' => 'error', - 'DateStyle' => 'ISO, YMD', - 'TimeZone' => 'GMT' - ); - foreach( array_keys( $goodconf ) AS $key ) { - $value = $goodconf[$key]; - if( !array_key_exists( $key, $conf ) or $conf[$key] !== $value ) { - wfOut( "Setting $key to '$value' for user \"$wgDBuser\"\n" ); - $wgDatabase->doQuery( "ALTER USER $safeuser SET $key = '$value'" ); - $wgDatabase->doQuery( "SET $key = '$value'" ); - } - else { - wfOut( "... default value of \"$key\" is correctly set to \"$value\" for user \"$wgDBuser\"\n" ); - } - } - - $newsequences = array( - "logging_log_id_seq", - "page_restrictions_pr_id_seq", - ); - - $newtables = array( - array("category", "patch-category.sql"), - array("mediawiki_version", "patch-mediawiki_version.sql"), - array("mwuser", "patch-mwuser.sql"), - array("pagecontent", "patch-pagecontent.sql"), - array("querycachetwo", "patch-querycachetwo.sql"), - array("page_props", "patch-page_props.sql"), - array("page_restrictions", "patch-page_restrictions.sql"), - array("profiling", "patch-profiling.sql"), - array("protected_titles", "patch-protected_titles.sql"), - 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('user_properties', 'patch-user_properties.sql'), - array('log_search', 'patch-log_search.sql'), - array('l10n_cache', 'patch-l10n_cache.sql'), - ); - - $newcols = array( - array("archive", "ar_deleted", "SMALLINT NOT NULL DEFAULT 0"), - array("archive", "ar_len", "INTEGER"), - array("archive", "ar_page_id", "INTEGER"), - array("archive", "ar_parent_id", "INTEGER"), - array("image", "img_sha1", "TEXT NOT NULL DEFAULT ''"), - array("ipblocks", "ipb_allow_usertalk", "SMALLINT NOT NULL DEFAULT 0"), - array("ipblocks", "ipb_anon_only", "SMALLINT NOT NULL DEFAULT 0"), - array("ipblocks", "ipb_by_text", "TEXT NOT NULL DEFAULT ''"), - array("ipblocks", "ipb_block_email", "SMALLINT NOT NULL DEFAULT 0"), - array("ipblocks", "ipb_create_account", "SMALLINT NOT NULL DEFAULT 1"), - array("ipblocks", "ipb_deleted", "SMALLINT NOT NULL DEFAULT 0"), - 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('logging_log_id_seq')"), - array("logging", "log_params", "TEXT"), - array("mwuser", "user_editcount", "INTEGER"), - array("mwuser", "user_hidden", "SMALLINT NOT NULL DEFAULT 0"), - array("mwuser", "user_newpass_time", "TIMESTAMPTZ"), - array("oldimage", "oi_deleted", "SMALLINT NOT NULL DEFAULT 0"), - array("oldimage", "oi_major_mime", "TEXT NOT NULL DEFAULT 'unknown'"), - array("oldimage", "oi_media_type", "TEXT"), - 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('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_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"), - ); - - - # table, column, desired type, USING clause if needed (with new default if needed) - $typechanges = array( - array("archive", "ar_deleted", "smallint", ""), - array("archive", "ar_minor_edit", "smallint", "ar_minor_edit::smallint DEFAULT 0"), - array("filearchive", "fa_deleted", "smallint", ""), - array("filearchive", "fa_height", "integer", ""), - array("filearchive", "fa_metadata", "bytea", "decode(fa_metadata,'escape')"), - array("filearchive", "fa_size", "integer", ""), - array("filearchive", "fa_width", "integer", ""), - array("filearchive", "fa_storage_group","text", ""), - array("filearchive", "fa_storage_key", "text", ""), - array("image", "img_metadata", "bytea", "decode(img_metadata,'escape')"), - array("image", "img_size", "integer", ""), - array("image", "img_width", "integer", ""), - array("image", "img_height", "integer", ""), - array("interwiki", "iw_local", "smallint", "iw_local::smallint DEFAULT 0"), - array("interwiki", "iw_trans", "smallint", "iw_trans::smallint DEFAULT 0"), - array("ipblocks", "ipb_auto", "smallint", "ipb_auto::smallint DEFAULT 0"), - array("ipblocks", "ipb_anon_only", "smallint", "CASE WHEN ipb_anon_only=' ' THEN 0 ELSE ipb_anon_only::smallint END DEFAULT 0"), - array("ipblocks", "ipb_create_account", "smallint", "CASE WHEN ipb_create_account=' ' THEN 0 ELSE ipb_create_account::smallint END DEFAULT 1"), - array("ipblocks", "ipb_enable_autoblock", "smallint", "CASE WHEN ipb_enable_autoblock=' ' THEN 0 ELSE ipb_enable_autoblock::smallint END DEFAULT 1"), - array("ipblocks", "ipb_block_email", "smallint", "CASE WHEN ipb_block_email=' ' THEN 0 ELSE ipb_block_email::smallint END DEFAULT 0"), - array("ipblocks", "ipb_address", "text", "ipb_address::text"), - array("ipblocks", "ipb_deleted", "smallint", "ipb_deleted::smallint DEFAULT 0"), - array("math", "math_inputhash", "bytea", "decode(math_inputhash,'escape')"), - array("math", "math_outputhash", "bytea", "decode(math_outputhash,'escape')"), - array("mwuser", "user_token", "text", ""), - array("mwuser", "user_email_token","text", ""), - array("objectcache", "keyname", "text", ""), - array("oldimage", "oi_height", "integer", ""), - array("oldimage", "oi_metadata", "bytea", "decode(img_metadata,'escape')"), - array("oldimage", "oi_size", "integer", ""), - array("oldimage", "oi_width", "integer", ""), - array("page", "page_is_redirect","smallint", "page_is_redirect::smallint DEFAULT 0"), - array("page", "page_is_new", "smallint", "page_is_new::smallint DEFAULT 0"), - array("querycache", "qc_value", "integer", ""), - array("querycachetwo","qcc_value", "integer", ""), - array("recentchanges","rc_bot", "smallint", "rc_bot::smallint DEFAULT 0"), - array("recentchanges","rc_deleted", "smallint", ""), - array("recentchanges","rc_minor", "smallint", "rc_minor::smallint DEFAULT 0"), - array("recentchanges","rc_new", "smallint", "rc_new::smallint DEFAULT 0"), - array("recentchanges","rc_type", "smallint", "rc_type::smallint DEFAULT 0"), - array("recentchanges","rc_patrolled", "smallint", "rc_patrolled::smallint DEFAULT 0"), - array("revision", "rev_deleted", "smallint", "rev_deleted::smallint DEFAULT 0"), - array("revision", "rev_minor_edit", "smallint", "rev_minor_edit::smallint DEFAULT 0"), - array("templatelinks","tl_namespace", "smallint", "tl_namespace::smallint"), - array("user_newtalk", "user_ip", "text", "host(user_ip)"), - ); - - # table, column, nullability - $nullchanges = array( - array("oldimage", "oi_bits", "NULL"), - array("oldimage", "oi_timestamp", "NULL"), - array("oldimage", "oi_major_mime", "NULL"), - array("oldimage", "oi_minor_mime", "NULL"), - ); - - $newindexes = array( - array("archive", "archive_user_text", "(ar_user_text)"), - array("image", "img_sha1", "(img_sha1)"), - array("oldimage", "oi_sha1", "(oi_sha1)"), - array("revision", "rev_text_id_idx", "(rev_text_id)"), - 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('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"); - } - } - - foreach ($newtables as $nt) { - if ($wgDatabase->tableExists($nt[0])) { - wfOut( "... table \"$nt[0]\" already exists\n" ); - continue; - } - - wfOut( "Creating table \"$nt[0]\"\n" ); - $wgDatabase->sourceFile(archive($nt[1])); - } - - ## Needed before newcols - if ($wgDatabase->tableExists("archive2")) { - wfOut( "Converting \"archive2\" back to normal archive table\n" ); - if ($wgDatabase->ruleExists("archive", "archive_insert")) { - wfOut( "Dropping rule \"archive_insert\"\n" ); - $wgDatabase->query("DROP RULE archive_insert ON archive"); - } - if ($wgDatabase->ruleExists("archive", "archive_delete")) { - wfOut( "Dropping rule \"archive_delete\"\n" ); - $wgDatabase->query("DROP RULE archive_delete ON archive"); - } - $wgDatabase->sourceFile(archive("patch-remove-archive2.sql")); - } - else - wfOut( "... obsolete table \"archive2\" does not exist\n" ); - - foreach ($newcols as $nc) { - $fi = $wgDatabase->fieldInfo($nc[0], $nc[1]); - if (!is_null($fi)) { - wfOut( "... column \"$nc[0].$nc[1]\" already exists\n" ); - continue; - } - - wfOut( "Adding column \"$nc[0].$nc[1]\"\n" ); - $wgDatabase->query("ALTER TABLE $nc[0] ADD $nc[1] $nc[2]"); - } - - foreach ($typechanges as $tc) { - $fi = $wgDatabase->fieldInfo($tc[0], $tc[1]); - if (is_null($fi)) { - wfOut( "... error: expected column $tc[0].$tc[1] to exist\n" ); - exit(1); - } - - if ($fi->type() === $tc[2]) - wfOut( "... column \"$tc[0].$tc[1]\" is already of type \"$tc[2]\"\n" ); - else { - wfOut( "Changing column type of \"$tc[0].$tc[1]\" from \"{$fi->type()}\" to \"$tc[2]\"\n" ); - $sql = "ALTER TABLE $tc[0] ALTER $tc[1] TYPE $tc[2]"; - if (strlen($tc[3])) { - $default = array(); - if (preg_match( '/DEFAULT (.+)/', $tc[3], $default)) { - $sqldef = "ALTER TABLE $tc[0] ALTER $tc[1] SET DEFAULT $default[1]"; - $wgDatabase->query($sqldef); - $tc[3] = preg_replace( '/\s*DEFAULT .+/', '', $tc[3]); - } - $sql .= " USING $tc[3]"; - } - $sql .= ";\nCOMMIT;\n"; - $wgDatabase->query($sql); - } - } - - foreach ($nullchanges as $nc) { - $fi = $wgDatabase->fieldInfo($nc[0], $nc[1]); - if (is_null($fi)) { - wfOut( "... error: expected column $nc[0].$nc[1] to exist\n" ); - exit(1); - } - if ($fi->nullable()) { - ## It's NULL - does it need to be NOT NULL? - if ('NOT NULL' === $nc[2]) { - wfOut( "Changing \"$nc[0].$nc[1]\" to not allow NULLs\n" ); - $wgDatabase->query( "ALTER TABLE $nc[0] ALTER $nc[1] SET NOT NULL" ); - } - else { - wfOut( "... column \"$nc[0].$nc[1]\" is already set as NULL\n" ); - } - } - else { - ## It's NOT NULL - does it need to be NULL? - if ('NULL' === $nc[2]) { - wfOut( "Changing \"$nc[0].$nc[1]\" to allow NULLs\n" ); - $wgDatabase->query( "ALTER TABLE $nc[0] ALTER $nc[1] DROP NOT NULL" ); - } - else { - wfOut( "... column \"$nc[0].$nc[1]\" is already set as NOT NULL\n" ); - } - } - } - - if ($wgDatabase->fieldInfo('oldimage','oi_deleted')->type() !== 'smallint') { - wfOut( "Changing \"oldimage.oi_deleted\" to type \"smallint\"\n" ); - $wgDatabase->query( "ALTER TABLE oldimage ALTER oi_deleted DROP DEFAULT" ); - $wgDatabase->query( "ALTER TABLE oldimage ALTER oi_deleted TYPE SMALLINT USING (oi_deleted::smallint)" ); - $wgDatabase->query( "ALTER TABLE oldimage ALTER oi_deleted SET DEFAULT 0" ); - } - else - wfOut( "... column \"oldimage.oi_deleted\" is already of type \"smallint\"\n" ); - - - foreach ($newindexes as $ni) { - if (pg_index_exists($ni[0], $ni[1])) { - wfOut( "... index \"$ni[1]\" on table \"$ni[0]\" already exists\n" ); - continue; - } - wfOut( "Creating index \"$ni[1]\" on table \"$ni[0]\" $ni[2]\n" ); - $wgDatabase->query( "CREATE INDEX $ni[1] ON $ni[0] $ni[2]" ); - } - - foreach ($newrules as $nr) { - if ($wgDatabase->ruleExists($nr[0], $nr[1])) { - wfOut( "... rule \"$nr[1]\" on table \"$nr[0]\" already exists\n" ); - continue; - } - wfOut( "Adding rule \"$nr[1]\" to table \"$nr[0]\"\n" ); - $wgDatabase->sourceFile(archive($nr[2])); - } - - 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" ); - } - - if (!$wgDatabase->triggerExists("page", "page_deleted")) { - wfOut( "Adding function and trigger \"page_deleted\" to table \"page\"\n" ); - $wgDatabase->sourceFile(archive('patch-page_deleted.sql')); - } - else - wfOut( "... table \"page\" has \"page_deleted\" trigger\n" ); - - $fi = $wgDatabase->fieldInfo("recentchanges", "rc_cur_id"); - if (!$fi->nullable()) { - wfOut( "Removing NOT NULL constraint from \"recentchanges.rc_cur_id\"\n" ); - $wgDatabase->sourceFile(archive('patch-rc_cur_id-not-null.sql')); - } - else - wfOut( "... column \"recentchanges.rc_cur_id\" has a NOT NULL constraint\n" ); - - $pu = pg_describe_index("pagelink_unique"); - if (!is_null($pu) && ($pu[0] != "pl_from" || $pu[1] != "pl_namespace" || $pu[2] != "pl_title")) { - wfOut( "Dropping obsolete version of index \"pagelink_unique index\"\n" ); - $wgDatabase->query("DROP INDEX pagelink_unique"); - $pu = null; - } - else - wfOut( "... obsolete version of index \"pagelink_unique index\" does not exist\n" ); - - if (is_null($pu)) { - wfOut( "Creating index \"pagelink_unique index\"\n" ); - $wgDatabase->query("CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title)"); - } - else - wfOut( "... index \"pagelink_unique_index\" already exists\n" ); - - if (pg_fkey_deltype("revision_rev_user_fkey") == 'r') { - wfOut( "... constraint \"revision_rev_user_fkey\" is ON DELETE RESTRICT\n" ); - } - else { - wfOut( "Changing constraint \"revision_rev_user_fkey\" to ON DELETE RESTRICT\n" ); - $wgDatabase->sourceFile(archive('patch-revision_rev_user_fkey.sql')); - } - - # Fix ipb_address index - if (pg_index_exists('ipblocks', 'ipb_address' )) { - wfOut( "Removing deprecated index 'ipb_address'...\n" ); - $wgDatabase->query('DROP INDEX ipb_address'); - } - if (pg_index_exists('ipblocks', 'ipb_address_unique' )) { - wfOut( "... have ipb_address_unique\n" ); - } - else { - wfOut( "Adding ipb_address_unique index\n" ); - $wgDatabase->sourceFile(archive('patch-ipb_address_unique.sql')); - } - - global $wgExtNewTables, $wgExtPGNewFields, $wgExtPGAlteredFields, $wgExtNewIndexes; - # Add missing extension tables - foreach ( $wgExtNewTables as $nt ) { - if ($wgDatabase->tableExists($nt[0])) { - wfOut( "... table \"$nt[0]\" already exists\n" ); - continue; - } - wfOut( "Creating table \"$nt[0]\"\n" ); - $wgDatabase->sourceFile($nt[1]); - } - # Add missing extension fields - foreach ( $wgExtPGNewFields as $nc ) { - $fi = $wgDatabase->fieldInfo($nc[0], $nc[1]); - if (!is_null($fi)) { - wfOut( "... column \"$nc[0].$nc[1]\" already exists\n" ); - continue; - } - 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])) { - wfOut( "... index \"$ni[1]\" on table \"$ni[0]\" already exists\n" ); - continue; - } - wfOut( "Creating index \"$ni[1]\" on table \"$ni[0]\"\n" ); - if ( preg_match( '/^\(/', $ni[2] ) ) { - $wgDatabase->query( "CREATE INDEX $ni[1] ON $ni[0] $ni[2]" ); - } - else { - $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 - $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 ) - $wgDatabase->sourceFile(archive('patch-tsearch2funcs.sql')); - - ## Put a new row in the mediawiki_version table - $wgDatabase->insert( 'mediawiki_version', - array( - 'type' => 'Update', - 'ctype' => 'U', - 'mw_version' => $wgVersion, - 'pg_version' => $version, - 'sql_version' => '$LastChangedRevision: 84593 $', - 'sql_date' => '$LastChangedDate: 2011-03-23 23:14:46 +1100 (Wed, 23 Mar 2011) $', - ) ); - return; -} diff --git a/maintenance/upgrade1_5.php b/maintenance/upgrade1_5.php index 4834a4a1..c5e1a33d 100644 --- a/maintenance/upgrade1_5.php +++ b/maintenance/upgrade1_5.php @@ -13,22 +13,1298 @@ * @ingroup Maintenance */ -$options = array( 'step', 'noimages' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); -require_once( dirname(__FILE__) . '/commandLine.inc' ); -require_once( 'FiveUpgrade.inc' ); +define( 'MW_UPGRADE_COPY', false ); +define( 'MW_UPGRADE_ENCODE', true ); +define( 'MW_UPGRADE_NULL', null ); +define( 'MW_UPGRADE_CALLBACK', null ); // for self-documentation only -echo "ATTENTION: This script is for upgrades from 1.4 to 1.5 (NOT 1.15) in very special cases.\n"; -echo "Use update.php for usual updates.\n"; +/** + * @ingroup Maintenance + */ +class FiveUpgrade extends Maintenance { + function __construct() { + parent::__construct(); -// Seems to confuse some people -if ( !array_search( '--upgrade', $_SERVER['argv'] ) ) { - echo "Please run this script with --upgrade key to actually run the updater.\n"; - die; -} + $this->mDescription = 'Script for upgrades from 1.4 to 1.5 (NOT 1.15) in very special cases.'; + + $this->addOption( 'upgrade', 'Really run the script' ); + $this->addOption( 'noimage', '' ); + $this->addOption( 'step', 'Only do a specific step', false, true ); + } + + public function getDbType() { + return Maintenance::DB_ADMIN; + } + + public function execute() { + $this->output( "ATTENTION: This script is for upgrades from 1.4 to 1.5 (NOT 1.15) in very special cases.\n" ); + $this->output( "Use update.php for usual updates.\n" ); + + if ( !$this->hasOption( 'upgrade' ) ) { + $this->output( "Please run this script with --upgrade key to actually run the updater.\n" ); + return; + } + + $this->setMembers(); + + $tables = array( + 'page', + 'links', + 'user', + 'image', + 'oldimage', + 'watchlist', + 'logging', + 'archive', + 'imagelinks', + 'categorylinks', + 'ipblocks', + 'recentchanges', + 'querycache' + ); + + foreach ( $tables as $table ) { + if ( $this->doing( $table ) ) { + $method = 'upgrade' . ucfirst( $table ); + $this->$method(); + } + } + + if ( $this->doing( 'cleanup' ) ) { + $this->upgradeCleanup(); + } + } + + protected function setMembers() { + $this->conversionTables = $this->prepareWindows1252(); + + $this->loadBalancers = array(); + $this->dbw = wfGetDB( DB_MASTER ); + $this->dbr = $this->streamConnection(); + + $this->cleanupSwaps = array(); + $this->emailAuth = false; # don't preauthenticate emails + $this->maxLag = 10; # if slaves are lagged more than 10 secs, wait + $this->step = $this->getOption( 'step', null ); + } + + function doing( $step ) { + return is_null( $this->step ) || $step == $this->step; + } + + /** + * Open a connection to the master server with the admin rights. + * @return Database + * @access private + */ + function newConnection() { + $lb = wfGetLBFactory()->newMainLB(); + $db = $lb->getConnection( DB_MASTER ); + + $this->loadBalancers[] = $lb; + return $db; + } + + /** + * Commit transactions and close the connections when we're done... + */ + function close() { + foreach ( $this->loadBalancers as $lb ) { + $lb->commitMasterChanges(); + $lb->closeAll(); + } + } + + /** + * Open a second connection to the master server, with buffering off. + * This will let us stream large datasets in and write in chunks on the + * other end. + * @return Database + * @access private + */ + function streamConnection() { + global $wgDBtype; + + $timeout = 3600 * 24; + $db = $this->newConnection(); + $db->bufferResults( false ); + if ( $wgDBtype == 'mysql' ) { + $db->query( "SET net_read_timeout=$timeout" ); + $db->query( "SET net_write_timeout=$timeout" ); + } + return $db; + } + + /** + * Prepare a conversion array for converting Windows Code Page 1252 to + * UTF-8. This should provide proper conversion of text that was miscoded + * as Windows-1252 by naughty user-agents, and doesn't rely on an outside + * iconv library. + * + * @return array + * @access private + */ + function prepareWindows1252() { + # Mappings from: + # http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT + static $cp1252 = array( + 0x80 => 0x20AC, # EURO SIGN + 0x81 => 0xFFFD, # REPLACEMENT CHARACTER (no mapping) + 0x82 => 0x201A, # SINGLE LOW-9 QUOTATION MARK + 0x83 => 0x0192, # LATIN SMALL LETTER F WITH HOOK + 0x84 => 0x201E, # DOUBLE LOW-9 QUOTATION MARK + 0x85 => 0x2026, # HORIZONTAL ELLIPSIS + 0x86 => 0x2020, # DAGGER + 0x87 => 0x2021, # DOUBLE DAGGER + 0x88 => 0x02C6, # MODIFIER LETTER CIRCUMFLEX ACCENT + 0x89 => 0x2030, # PER MILLE SIGN + 0x8A => 0x0160, # LATIN CAPITAL LETTER S WITH CARON + 0x8B => 0x2039, # SINGLE LEFT-POINTING ANGLE QUOTATION MARK + 0x8C => 0x0152, # LATIN CAPITAL LIGATURE OE + 0x8D => 0xFFFD, # REPLACEMENT CHARACTER (no mapping) + 0x8E => 0x017D, # LATIN CAPITAL LETTER Z WITH CARON + 0x8F => 0xFFFD, # REPLACEMENT CHARACTER (no mapping) + 0x90 => 0xFFFD, # REPLACEMENT CHARACTER (no mapping) + 0x91 => 0x2018, # LEFT SINGLE QUOTATION MARK + 0x92 => 0x2019, # RIGHT SINGLE QUOTATION MARK + 0x93 => 0x201C, # LEFT DOUBLE QUOTATION MARK + 0x94 => 0x201D, # RIGHT DOUBLE QUOTATION MARK + 0x95 => 0x2022, # BULLET + 0x96 => 0x2013, # EN DASH + 0x97 => 0x2014, # EM DASH + 0x98 => 0x02DC, # SMALL TILDE + 0x99 => 0x2122, # TRADE MARK SIGN + 0x9A => 0x0161, # LATIN SMALL LETTER S WITH CARON + 0x9B => 0x203A, # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + 0x9C => 0x0153, # LATIN SMALL LIGATURE OE + 0x9D => 0xFFFD, # REPLACEMENT CHARACTER (no mapping) + 0x9E => 0x017E, # LATIN SMALL LETTER Z WITH CARON + 0x9F => 0x0178, # LATIN CAPITAL LETTER Y WITH DIAERESIS + ); + $pairs = array(); + for ( $i = 0; $i < 0x100; $i++ ) { + $unicode = isset( $cp1252[$i] ) ? $cp1252[$i] : $i; + $pairs[chr( $i )] = codepointToUtf8( $unicode ); + } + return $pairs; + } + + /** + * Convert from 8-bit Windows-1252 to UTF-8 if necessary. + * @param string $text + * @return string + * @access private + */ + function conv( $text ) { + global $wgUseLatin1; + return is_null( $text ) + ? null + : ( $wgUseLatin1 + ? strtr( $text, $this->conversionTables ) + : $text ); + } + + /** + * Dump timestamp and message to output + * @param $message String + * @access private + */ + function log( $message ) { + $this->output( wfWikiID() . ' ' . wfTimestamp( TS_DB ) . ': ' . $message . "\n" ); + } + + /** + * Initialize the chunked-insert system. + * Rows will be inserted in chunks of the given number, rather + * than in a giant INSERT...SELECT query, to keep the serialized + * MySQL database replication from getting hung up. This way other + * things can be going on during conversion without waiting for + * slaves to catch up as badly. + * + * @param int $chunksize Number of rows to insert at once + * @param int $final Total expected number of rows / id of last row, + * used for progress reports. + * @param string $table to insert on + * @param string $fname function name to report in SQL + * @access private + */ + function setChunkScale( $chunksize, $final, $table, $fname ) { + $this->chunkSize = $chunksize; + $this->chunkFinal = $final; + $this->chunkCount = 0; + $this->chunkStartTime = wfTime(); + $this->chunkOptions = array( 'IGNORE' ); + $this->chunkTable = $table; + $this->chunkFunction = $fname; + } + + /** + * Chunked inserts: perform an insert if we've reached the chunk limit. + * Prints a progress report with estimated completion time. + * @param array &$chunk -- This will be emptied if an insert is done. + * @param int $key A key identifier to use in progress estimation in + * place of the number of rows inserted. Use this if + * you provided a max key number instead of a count + * as the final chunk number in setChunkScale() + * @access private + */ + function addChunk( &$chunk, $key = null ) { + if ( count( $chunk ) >= $this->chunkSize ) { + $this->insertChunk( $chunk ); + + $this->chunkCount += count( $chunk ); + $now = wfTime(); + $delta = $now - $this->chunkStartTime; + $rate = $this->chunkCount / $delta; + + if ( is_null( $key ) ) { + $completed = $this->chunkCount; + } else { + $completed = $key; + } + $portion = $completed / $this->chunkFinal; + + $estimatedTotalTime = $delta / $portion; + $eta = $this->chunkStartTime + $estimatedTotalTime; + + printf( "%s: %6.2f%% done on %s; ETA %s [%d/%d] %.2f/sec\n", + wfTimestamp( TS_DB, intval( $now ) ), + $portion * 100.0, + $this->chunkTable, + wfTimestamp( TS_DB, intval( $eta ) ), + $completed, + $this->chunkFinal, + $rate ); + flush(); + + $chunk = array(); + } + } + + /** + * Chunked inserts: perform an insert unconditionally, at the end, and log. + * @param array &$chunk -- This will be emptied if an insert is done. + * @access private + */ + function lastChunk( &$chunk ) { + $n = count( $chunk ); + if ( $n > 0 ) { + $this->insertChunk( $chunk ); + } + $this->log( "100.00% done on $this->chunkTable (last chunk $n rows)." ); + } + + /** + * Chunked inserts: perform an insert. + * @param array &$chunk -- This will be emptied if an insert is done. + * @access private + */ + function insertChunk( &$chunk ) { + // Give slaves a chance to catch up + wfWaitForSlaves( $this->maxLag ); + $this->dbw->insert( $this->chunkTable, $chunk, $this->chunkFunction, $this->chunkOptions ); + } + + + /** + * Copy and transcode a table to table_temp. + * @param string $name Base name of the source table + * @param string $tabledef CREATE TABLE definition, w/ $1 for the name + * @param array $fields set of destination fields to these constants: + * MW_UPGRADE_COPY - straight copy + * MW_UPGRADE_ENCODE - for old Latin1 wikis, conv to UTF-8 + * MW_UPGRADE_NULL - just put NULL + * @param callable $callback An optional callback to modify the data + * or perform other processing. Func should be + * ( object $row, array $copy ) and return $copy + * @access private + */ + function copyTable( $name, $tabledef, $fields, $callback = null ) { + $name_temp = $name . '_temp'; + $this->log( "Migrating $name table to $name_temp..." ); + + $table_temp = $this->dbw->tableName( $name_temp ); + + // Create temporary table; we're going to copy everything in there, + // then at the end rename the final tables into place. + $def = str_replace( '$1', $table_temp, $tabledef ); + $this->dbw->query( $def, __METHOD__ ); + + $numRecords = $this->dbw->selectField( $name, 'COUNT(*)', '', __METHOD__ ); + $this->setChunkScale( 100, $numRecords, $name_temp, __METHOD__ ); + + // Pull all records from the second, streaming database connection. + $sourceFields = array_keys( array_filter( $fields, + create_function( '$x', 'return $x !== MW_UPGRADE_NULL;' ) ) ); + $result = $this->dbr->select( $name, + $sourceFields, + '', + __METHOD__ ); + + $add = array(); + foreach ( $result as $row ) { + $copy = array(); + foreach ( $fields as $field => $source ) { + if ( $source === MW_UPGRADE_COPY ) { + $copy[$field] = $row->$field; + } elseif ( $source === MW_UPGRADE_ENCODE ) { + $copy[$field] = $this->conv( $row->$field ); + } elseif ( $source === MW_UPGRADE_NULL ) { + $copy[$field] = null; + } else { + $this->log( "Unknown field copy type: $field => $source" ); + } + } + if ( is_callable( $callback ) ) { + $copy = call_user_func( $callback, $row, $copy ); + } + $add[] = $copy; + $this->addChunk( $add ); + } + $this->lastChunk( $add ); + + $this->log( "Done converting $name." ); + $this->cleanupSwaps[] = $name; + } + + function upgradePage() { + $chunksize = 100; + + if ( $this->dbw->tableExists( 'page' ) ) { + $this->error( 'Page table already exists.', true ); + } + + $this->log( "Checking cur table for unique title index and applying if necessary" ); + $this->checkDupes(); + + $this->log( "...converting from cur/old to page/revision/text DB structure." ); + + list ( $cur, $old, $page, $revision, $text ) = $this->dbw->tableNamesN( 'cur', 'old', 'page', 'revision', 'text' ); + + $this->log( "Creating page and revision tables..." ); + $this->dbw->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 default '', + 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) + ) TYPE=InnoDB", __METHOD__ ); + $this->dbw->query( "CREATE TABLE $revision ( + rev_id int(8) unsigned NOT NULL auto_increment, + rev_page int(8) unsigned NOT NULL, + rev_text_id int(8) unsigned NOT NULL, + rev_comment tinyblob NOT NULL default '', + 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', + + 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) + ) TYPE=InnoDB", __METHOD__ ); + + $maxold = intval( $this->dbw->selectField( 'old', 'max(old_id)', '', __METHOD__ ) ); + $this->log( "Last old record is {$maxold}" ); + + global $wgLegacySchemaConversion; + if ( $wgLegacySchemaConversion ) { + // Create HistoryBlobCurStub entries. + // Text will be pulled from the leftover 'cur' table at runtime. + echo "......Moving metadata from cur; using blob references to text in cur table.\n"; + $cur_text = "concat('O:18:\"historyblobcurstub\":1:{s:6:\"mCurId\";i:',cur_id,';}')"; + $cur_flags = "'object'"; + } else { + // Copy all cur text in immediately: this may take longer but avoids + // having to keep an extra table around. + echo "......Moving text from cur.\n"; + $cur_text = 'cur_text'; + $cur_flags = "''"; + } + + $maxcur = $this->dbw->selectField( 'cur', 'max(cur_id)', '', __METHOD__ ); + $this->log( "Last cur entry is $maxcur" ); + + /** + * Copy placeholder records for each page's current version into old + * Don't do any conversion here; text records are converted at runtime + * based on the flags (and may be originally binary!) while the meta + * fields will be converted in the old -> rev and cur -> page steps. + */ + $this->setChunkScale( $chunksize, $maxcur, 'old', __METHOD__ ); + $result = $this->dbr->query( + "SELECT cur_id, cur_namespace, cur_title, $cur_text AS text, cur_comment, + cur_user, cur_user_text, cur_timestamp, cur_minor_edit, $cur_flags AS flags + FROM $cur + ORDER BY cur_id", __METHOD__ ); + $add = array(); + foreach ( $result as $row ) { + $add[] = array( + 'old_namespace' => $row->cur_namespace, + 'old_title' => $row->cur_title, + 'old_text' => $row->text, + 'old_comment' => $row->cur_comment, + 'old_user' => $row->cur_user, + 'old_user_text' => $row->cur_user_text, + 'old_timestamp' => $row->cur_timestamp, + 'old_minor_edit' => $row->cur_minor_edit, + 'old_flags' => $row->flags ); + $this->addChunk( $add, $row->cur_id ); + } + $this->lastChunk( $add ); + + /** + * Copy revision metadata from old into revision. + * We'll also do UTF-8 conversion of usernames and comments. + */ + # $newmaxold = $this->dbw->selectField( 'old', 'max(old_id)', '', __METHOD__ ); + # $this->setChunkScale( $chunksize, $newmaxold, 'revision', __METHOD__ ); + # $countold = $this->dbw->selectField( 'old', 'count(old_id)', '', __METHOD__ ); + $countold = $this->dbw->selectField( 'old', 'max(old_id)', '', __METHOD__ ); + $this->setChunkScale( $chunksize, $countold, 'revision', __METHOD__ ); + + $this->log( "......Setting up revision table." ); + $result = $this->dbr->query( + "SELECT old_id, cur_id, old_comment, old_user, old_user_text, + old_timestamp, old_minor_edit + FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title", + __METHOD__ ); + + $add = array(); + foreach ( $result as $row ) { + $add[] = array( + 'rev_id' => $row->old_id, + 'rev_page' => $row->cur_id, + 'rev_text_id' => $row->old_id, + 'rev_comment' => $this->conv( $row->old_comment ), + 'rev_user' => $row->old_user, + 'rev_user_text' => $this->conv( $row->old_user_text ), + 'rev_timestamp' => $row->old_timestamp, + 'rev_minor_edit' => $row->old_minor_edit ); + $this->addChunk( $add ); + } + $this->lastChunk( $add ); + + + /** + * Copy page metadata from cur into page. + * We'll also do UTF-8 conversion of titles. + */ + $this->log( "......Setting up page table." ); + $this->setChunkScale( $chunksize, $maxcur, 'page', __METHOD__ ); + $result = $this->dbr->query( " + 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) AS len + FROM $cur,$revision + WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold} + ORDER BY cur_id", __METHOD__ ); + $add = array(); + foreach ( $result as $row ) { + $add[] = array( + 'page_id' => $row->cur_id, + 'page_namespace' => $row->cur_namespace, + 'page_title' => $this->conv( $row->cur_title ), + 'page_restrictions' => $row->cur_restrictions, + 'page_counter' => $row->cur_counter, + 'page_is_redirect' => $row->cur_is_redirect, + 'page_is_new' => $row->cur_is_new, + 'page_random' => $row->cur_random, + 'page_touched' => $this->dbw->timestamp(), + 'page_latest' => $row->rev_id, + 'page_len' => $row->len ); + # $this->addChunk( $add, $row->cur_id ); + $this->addChunk( $add ); + } + $this->lastChunk( $add ); + + $this->log( "...done with cur/old -> page/revision." ); + } + + function upgradeLinks() { + $chunksize = 200; + list ( $links, $brokenlinks, $pagelinks, $cur ) = $this->dbw->tableNamesN( 'links', 'brokenlinks', 'pagelinks', 'cur' ); + + $this->log( 'Checking for interwiki table change in case of bogus items...' ); + if ( $this->dbw->fieldExists( 'interwiki', 'iw_trans' ) ) { + $this->log( 'interwiki has iw_trans.' ); + } else { + global $IP; + $this->log( 'adding iw_trans...' ); + $this->dbw->sourceFile( $IP . '/maintenance/archives/patch-interwiki-trans.sql' ); + $this->log( 'added iw_trans.' ); + } + + $this->log( 'Creating pagelinks table...' ); + $this->dbw->query( " +CREATE TABLE $pagelinks ( + -- Key to the page_id of the page containing the link. + pl_from int(8) unsigned NOT NULL default '0', + + -- Key to page_namespace/page_title of the target page. + -- The target page may or may not exist, and due to renames + -- and deletions may refer to different page records as time + -- goes by. + pl_namespace int NOT NULL default '0', + pl_title varchar(255) binary NOT NULL default '', + + UNIQUE KEY pl_from(pl_from,pl_namespace,pl_title), + KEY (pl_namespace,pl_title) + +) TYPE=InnoDB" ); + + $this->log( 'Importing live links -> pagelinks' ); + $nlinks = $this->dbw->selectField( 'links', 'count(*)', '', __METHOD__ ); + if ( $nlinks ) { + $this->setChunkScale( $chunksize, $nlinks, 'pagelinks', __METHOD__ ); + $result = $this->dbr->query( " + SELECT l_from,cur_namespace,cur_title + FROM $links, $cur + WHERE l_to=cur_id", __METHOD__ ); + $add = array(); + foreach ( $result as $row ) { + $add[] = array( + 'pl_from' => $row->l_from, + 'pl_namespace' => $row->cur_namespace, + 'pl_title' => $this->conv( $row->cur_title ) ); + $this->addChunk( $add ); + } + $this->lastChunk( $add ); + } else { + $this->log( 'no links!' ); + } + + $this->log( 'Importing brokenlinks -> pagelinks' ); + $nbrokenlinks = $this->dbw->selectField( 'brokenlinks', 'count(*)', '', __METHOD__ ); + if ( $nbrokenlinks ) { + $this->setChunkScale( $chunksize, $nbrokenlinks, 'pagelinks', __METHOD__ ); + $result = $this->dbr->query( + "SELECT bl_from, bl_to FROM $brokenlinks", + __METHOD__ ); + $add = array(); + foreach ( $result as $row ) { + $pagename = $this->conv( $row->bl_to ); + $title = Title::newFromText( $pagename ); + if ( is_null( $title ) ) { + $this->log( "** invalid brokenlink: $row->bl_from -> '$pagename' (converted from '$row->bl_to')" ); + } else { + $add[] = array( + 'pl_from' => $row->bl_from, + 'pl_namespace' => $title->getNamespace(), + 'pl_title' => $title->getDBkey() ); + $this->addChunk( $add ); + } + } + $this->lastChunk( $add ); + } else { + $this->log( 'no brokenlinks!' ); + } + + $this->log( 'Done with links.' ); + } + + function userDupeCallback( $str ) { + echo $str; + } + + function upgradeUser() { + // Apply unique index, if necessary: + $duper = new UserDupes( $this->dbw, array( $this, 'userDupeCallback' ) ); + if ( $duper->hasUniqueIndex() ) { + $this->log( "Already have unique user_name index." ); + } else { + $this->log( "Clearing user duplicates..." ); + if ( !$duper->clearDupes() ) { + $this->log( "WARNING: Duplicate user accounts, may explode!" ); + } + } + + $tabledef = <<<END +CREATE TABLE $1 ( + user_id int(5) unsigned NOT NULL auto_increment, + user_name varchar(255) binary NOT NULL default '', + user_real_name varchar(255) binary NOT NULL default '', + user_password tinyblob NOT NULL default '', + user_newpassword tinyblob NOT NULL default '', + user_email tinytext NOT NULL default '', + user_options blob NOT NULL default '', + user_touched char(14) binary NOT NULL default '', + user_token char(32) binary NOT NULL default '', + user_email_authenticated CHAR(14) BINARY, + user_email_token CHAR(32) BINARY, + user_email_token_expires CHAR(14) BINARY, + + PRIMARY KEY user_id (user_id), + UNIQUE INDEX user_name (user_name), + INDEX (user_email_token) + +) TYPE=InnoDB +END; + $fields = array( + 'user_id' => MW_UPGRADE_COPY, + 'user_name' => MW_UPGRADE_ENCODE, + 'user_real_name' => MW_UPGRADE_ENCODE, + 'user_password' => MW_UPGRADE_COPY, + 'user_newpassword' => MW_UPGRADE_COPY, + 'user_email' => MW_UPGRADE_ENCODE, + 'user_options' => MW_UPGRADE_ENCODE, + 'user_touched' => MW_UPGRADE_CALLBACK, + 'user_token' => MW_UPGRADE_COPY, + 'user_email_authenticated' => MW_UPGRADE_CALLBACK, + 'user_email_token' => MW_UPGRADE_NULL, + 'user_email_token_expires' => MW_UPGRADE_NULL ); + $this->copyTable( 'user', $tabledef, $fields, + array( &$this, 'userCallback' ) ); + } + + function userCallback( $row, $copy ) { + $now = $this->dbw->timestamp(); + $copy['user_touched'] = $now; + $copy['user_email_authenticated'] = $this->emailAuth ? $now : null; + return $copy; + } + + function upgradeImage() { + $tabledef = <<<END +CREATE TABLE $1 ( + img_name varchar(255) binary NOT NULL default '', + img_size int(8) unsigned NOT NULL default '0', + img_width int(5) NOT NULL default '0', + img_height int(5) NOT NULL default '0', + img_metadata mediumblob NOT NULL, + img_bits int(3) NOT NULL default '0', + img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, + img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown", + img_minor_mime varchar(32) NOT NULL default "unknown", + img_description tinyblob NOT NULL default '', + img_user int(5) unsigned NOT NULL default '0', + img_user_text varchar(255) binary NOT NULL default '', + img_timestamp char(14) binary NOT NULL default '', + + PRIMARY KEY img_name (img_name), + INDEX img_size (img_size), + INDEX img_timestamp (img_timestamp) +) TYPE=InnoDB +END; + $fields = array( + 'img_name' => MW_UPGRADE_ENCODE, + 'img_size' => MW_UPGRADE_COPY, + 'img_width' => MW_UPGRADE_CALLBACK, + 'img_height' => MW_UPGRADE_CALLBACK, + 'img_metadata' => MW_UPGRADE_CALLBACK, + 'img_bits' => MW_UPGRADE_CALLBACK, + 'img_media_type' => MW_UPGRADE_CALLBACK, + 'img_major_mime' => MW_UPGRADE_CALLBACK, + 'img_minor_mime' => MW_UPGRADE_CALLBACK, + 'img_description' => MW_UPGRADE_ENCODE, + 'img_user' => MW_UPGRADE_COPY, + 'img_user_text' => MW_UPGRADE_ENCODE, + 'img_timestamp' => MW_UPGRADE_COPY ); + $this->copyTable( 'image', $tabledef, $fields, + array( &$this, 'imageCallback' ) ); + } + + function imageCallback( $row, $copy ) { + if ( !$this->hasOption( 'noimage' ) ) { + // Fill in the new image info fields + $info = $this->imageInfo( $row->img_name ); + + $copy['img_width' ] = $info['width']; + $copy['img_height' ] = $info['height']; + $copy['img_metadata' ] = ""; // loaded on-demand + $copy['img_bits' ] = $info['bits']; + $copy['img_media_type'] = $info['media']; + $copy['img_major_mime'] = $info['major']; + $copy['img_minor_mime'] = $info['minor']; + } + + // If doing UTF8 conversion the file must be renamed + $this->renameFile( $row->img_name, 'wfImageDir' ); + + return $copy; + } + + function imageInfo( $filename ) { + $info = array( + 'width' => 0, + 'height' => 0, + 'bits' => 0, + 'media' => '', + 'major' => '', + 'minor' => '' ); + + $magic = MimeMagic::singleton(); + $mime = $magic->guessMimeType( $filename, true ); + list( $info['major'], $info['minor'] ) = explode( '/', $mime ); -$upgrade = new FiveUpgrade(); -$step = isset( $options['step'] ) ? $options['step'] : null; -$upgrade->upgrade( $step ); + $info['media'] = $magic->getMediaType( $filename, $mime ); + $image = UnregisteredLocalFile::newFromPath( $filename, $mime ); + + $info['width'] = $image->getWidth(); + $info['height'] = $image->getHeight(); + + $gis = $image->getImageSize( $filename ); + if ( isset( $gis['bits'] ) ) { + $info['bits'] = $gis['bits']; + } + + return $info; + } + + + /** + * Truncate a table. + * @param string $table The table name to be truncated + */ + function clearTable( $table ) { + print "Clearing $table...\n"; + $tableName = $this->db->tableName( $table ); + $this->db->query( "TRUNCATE $tableName" ); + } + + /** + * Rename a given image or archived image file to the converted filename, + * leaving a symlink for URL compatibility. + * + * @param string $oldname pre-conversion filename + * @param string $basename pre-conversion base filename for dir hashing, if an archive + * @access private + */ + function renameFile( $oldname, $subdirCallback = 'wfImageDir', $basename = null ) { + $newname = $this->conv( $oldname ); + if ( $newname == $oldname ) { + // No need to rename; another field triggered this row. + return false; + } + + if ( is_null( $basename ) ) $basename = $oldname; + $ubasename = $this->conv( $basename ); + $oldpath = call_user_func( $subdirCallback, $basename ) . '/' . $oldname; + $newpath = call_user_func( $subdirCallback, $ubasename ) . '/' . $newname; + + $this->log( "$oldpath -> $newpath" ); + if ( rename( $oldpath, $newpath ) ) { + $relpath = wfRelativePath( $newpath, dirname( $oldpath ) ); + if ( !symlink( $relpath, $oldpath ) ) { + $this->log( "... symlink failed!" ); + } + return $newname; + } else { + $this->log( "... rename failed!" ); + return false; + } + } + + function upgradeOldImage() { + $tabledef = <<<END +CREATE TABLE $1 ( + -- Base filename: key to image.img_name + oi_name varchar(255) binary NOT NULL default '', + + -- Filename of the archived file. + -- This is generally a timestamp and '!' prepended to the base name. + oi_archive_name varchar(255) binary NOT NULL default '', + + -- Other fields as in image... + oi_size int(8) unsigned NOT NULL default 0, + oi_width int(5) NOT NULL default 0, + oi_height int(5) NOT NULL default 0, + oi_bits int(3) NOT NULL default 0, + oi_description tinyblob NOT NULL default '', + oi_user int(5) unsigned NOT NULL default '0', + oi_user_text varchar(255) binary NOT NULL default '', + oi_timestamp char(14) binary NOT NULL default '', + + INDEX oi_name (oi_name(10)) + +) TYPE=InnoDB; +END; + $fields = array( + 'oi_name' => MW_UPGRADE_ENCODE, + 'oi_archive_name' => MW_UPGRADE_ENCODE, + 'oi_size' => MW_UPGRADE_COPY, + 'oi_width' => MW_UPGRADE_CALLBACK, + 'oi_height' => MW_UPGRADE_CALLBACK, + 'oi_bits' => MW_UPGRADE_CALLBACK, + 'oi_description' => MW_UPGRADE_ENCODE, + 'oi_user' => MW_UPGRADE_COPY, + 'oi_user_text' => MW_UPGRADE_ENCODE, + 'oi_timestamp' => MW_UPGRADE_COPY ); + $this->copyTable( 'oldimage', $tabledef, $fields, + array( &$this, 'oldimageCallback' ) ); + } + + function oldimageCallback( $row, $copy ) { + global $options; + if ( !isset( $options['noimage'] ) ) { + // Fill in the new image info fields + $info = $this->imageInfo( $row->oi_archive_name, 'wfImageArchiveDir', $row->oi_name ); + $copy['oi_width' ] = $info['width' ]; + $copy['oi_height'] = $info['height']; + $copy['oi_bits' ] = $info['bits' ]; + } + + // If doing UTF8 conversion the file must be renamed + $this->renameFile( $row->oi_archive_name, 'wfImageArchiveDir', $row->oi_name ); + + return $copy; + } + + + function upgradeWatchlist() { + $chunksize = 100; + + list ( $watchlist, $watchlist_temp ) = $this->dbw->tableNamesN( 'watchlist', 'watchlist_temp' ); + + $this->log( 'Migrating watchlist table to watchlist_temp...' ); + $this->dbw->query( +"CREATE TABLE $watchlist_temp ( + -- Key to user_id + wl_user int(5) unsigned NOT NULL, + + -- Key to page_namespace/page_title + -- Note that users may watch patches which do not exist yet, + -- or existed in the past but have been deleted. + wl_namespace int NOT NULL default '0', + wl_title varchar(255) binary NOT NULL default '', + + -- Timestamp when user was last sent a notification e-mail; + -- cleared when the user visits the page. + -- FIXME: add proper null support etc + wl_notificationtimestamp varchar(14) binary NOT NULL default '0', + + UNIQUE KEY (wl_user, wl_namespace, wl_title), + KEY namespace_title (wl_namespace,wl_title) + +) TYPE=InnoDB;", __METHOD__ ); + + // Fix encoding for Latin-1 upgrades, add some fields, + // and double article to article+talk pairs + $numwatched = $this->dbw->selectField( 'watchlist', 'count(*)', '', __METHOD__ ); + + $this->setChunkScale( $chunksize, $numwatched * 2, 'watchlist_temp', __METHOD__ ); + $result = $this->dbr->select( 'watchlist', + array( + 'wl_user', + 'wl_namespace', + 'wl_title' ), + '', + __METHOD__ ); + + $add = array(); + foreach ( $result as $row ) { + $add[] = array( + 'wl_user' => $row->wl_user, + 'wl_namespace' => MWNamespace::getSubject( $row->wl_namespace ), + 'wl_title' => $this->conv( $row->wl_title ), + 'wl_notificationtimestamp' => '0' ); + $this->addChunk( $add ); + + $add[] = array( + 'wl_user' => $row->wl_user, + 'wl_namespace' => MWNamespace::getTalk( $row->wl_namespace ), + 'wl_title' => $this->conv( $row->wl_title ), + 'wl_notificationtimestamp' => '0' ); + $this->addChunk( $add ); + } + $this->lastChunk( $add ); + + $this->log( 'Done converting watchlist.' ); + $this->cleanupSwaps[] = 'watchlist'; + } + + function upgradeLogging() { + $tabledef = <<<ENDS +CREATE TABLE $1 ( + -- Symbolic keys for the general log type and the action type + -- within the log. The output format will be controlled by the + -- action field, but only the type controls categorization. + log_type char(10) NOT NULL default '', + log_action char(10) NOT NULL default '', + + -- Timestamp. Duh. + log_timestamp char(14) NOT NULL default '19700101000000', + + -- The user who performed this action; key to user_id + log_user int unsigned NOT NULL default 0, + + -- 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 '', + + -- Freeform text. Interpreted as edit history comments. + log_comment varchar(255) NOT NULL default '', + + -- LF separated list of miscellaneous parameters + log_params blob NOT NULL default '', + + KEY type_time (log_type, log_timestamp), + KEY user_time (log_user, log_timestamp), + KEY page_time (log_namespace, log_title, log_timestamp) + +) TYPE=InnoDB +ENDS; + $fields = array( + 'log_type' => MW_UPGRADE_COPY, + 'log_action' => MW_UPGRADE_COPY, + 'log_timestamp' => MW_UPGRADE_COPY, + 'log_user' => MW_UPGRADE_COPY, + 'log_namespace' => MW_UPGRADE_COPY, + 'log_title' => MW_UPGRADE_ENCODE, + 'log_comment' => MW_UPGRADE_ENCODE, + 'log_params' => MW_UPGRADE_ENCODE ); + $this->copyTable( 'logging', $tabledef, $fields ); + } + + function upgradeArchive() { + $tabledef = <<<ENDS +CREATE TABLE $1 ( + ar_namespace int NOT NULL default '0', + ar_title varchar(255) binary NOT NULL default '', + ar_text mediumblob NOT NULL default '', + + ar_comment tinyblob NOT NULL default '', + ar_user int(5) unsigned NOT NULL default '0', + ar_user_text varchar(255) binary NOT NULL, + ar_timestamp char(14) binary NOT NULL default '', + ar_minor_edit tinyint(1) NOT NULL default '0', + + ar_flags tinyblob NOT NULL default '', + + ar_rev_id int(8) unsigned, + ar_text_id int(8) unsigned, + + KEY name_title_timestamp (ar_namespace,ar_title,ar_timestamp) + +) TYPE=InnoDB +ENDS; + $fields = array( + 'ar_namespace' => MW_UPGRADE_COPY, + 'ar_title' => MW_UPGRADE_ENCODE, + 'ar_text' => MW_UPGRADE_COPY, + 'ar_comment' => MW_UPGRADE_ENCODE, + 'ar_user' => MW_UPGRADE_COPY, + 'ar_user_text' => MW_UPGRADE_ENCODE, + 'ar_timestamp' => MW_UPGRADE_COPY, + 'ar_minor_edit' => MW_UPGRADE_COPY, + 'ar_flags' => MW_UPGRADE_COPY, + 'ar_rev_id' => MW_UPGRADE_NULL, + 'ar_text_id' => MW_UPGRADE_NULL ); + $this->copyTable( 'archive', $tabledef, $fields ); + } + + function upgradeImagelinks() { + global $wgUseLatin1; + if ( $wgUseLatin1 ) { + $tabledef = <<<ENDS +CREATE TABLE $1 ( + -- Key to page_id of the page containing the image / media link. + il_from int(8) unsigned NOT NULL default '0', + + -- Filename of target image. + -- This is also the page_title of the file's description page; + -- all such pages are in namespace 6 (NS_FILE). + il_to varchar(255) binary NOT NULL default '', + + UNIQUE KEY il_from(il_from,il_to), + KEY (il_to) + +) TYPE=InnoDB +ENDS; + $fields = array( + 'il_from' => MW_UPGRADE_COPY, + 'il_to' => MW_UPGRADE_ENCODE ); + $this->copyTable( 'imagelinks', $tabledef, $fields ); + } + } + + function upgradeCategorylinks() { + global $wgUseLatin1; + if ( $wgUseLatin1 ) { + $tabledef = <<<ENDS +CREATE TABLE $1 ( + cl_from int(8) unsigned NOT NULL default '0', + cl_to varchar(255) binary NOT NULL default '', + cl_sortkey varchar(86) binary NOT NULL default '', + cl_timestamp timestamp NOT NULL, + + UNIQUE KEY cl_from(cl_from,cl_to), + KEY cl_sortkey(cl_to,cl_sortkey), + KEY cl_timestamp(cl_to,cl_timestamp) +) TYPE=InnoDB +ENDS; + $fields = array( + 'cl_from' => MW_UPGRADE_COPY, + 'cl_to' => MW_UPGRADE_ENCODE, + 'cl_sortkey' => MW_UPGRADE_ENCODE, + 'cl_timestamp' => MW_UPGRADE_COPY ); + $this->copyTable( 'categorylinks', $tabledef, $fields ); + } + } + + function upgradeIpblocks() { + global $wgUseLatin1; + if ( $wgUseLatin1 ) { + $tabledef = <<<ENDS +CREATE TABLE $1 ( + ipb_id int(8) NOT NULL auto_increment, + ipb_address varchar(40) binary NOT NULL default '', + ipb_user int(8) unsigned NOT NULL default '0', + ipb_by int(8) unsigned NOT NULL default '0', + ipb_reason tinyblob NOT NULL default '', + ipb_timestamp char(14) binary NOT NULL default '', + ipb_auto tinyint(1) NOT NULL default '0', + ipb_expiry char(14) binary NOT NULL default '', + + PRIMARY KEY ipb_id (ipb_id), + INDEX ipb_address (ipb_address), + INDEX ipb_user (ipb_user) + +) TYPE=InnoDB +ENDS; + $fields = array( + 'ipb_id' => MW_UPGRADE_COPY, + 'ipb_address' => MW_UPGRADE_COPY, + 'ipb_user' => MW_UPGRADE_COPY, + 'ipb_by' => MW_UPGRADE_COPY, + 'ipb_reason' => MW_UPGRADE_ENCODE, + 'ipb_timestamp' => MW_UPGRADE_COPY, + 'ipb_auto' => MW_UPGRADE_COPY, + 'ipb_expiry' => MW_UPGRADE_COPY ); + $this->copyTable( 'ipblocks', $tabledef, $fields ); + } + } + + function upgradeRecentchanges() { + // There's a format change in the namespace field + $tabledef = <<<ENDS +CREATE TABLE $1 ( + rc_id int(8) NOT NULL auto_increment, + 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 int 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_bot 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', + + rc_type tinyint(3) unsigned NOT NULL default '0', + rc_moved_to_ns tinyint(3) unsigned NOT NULL default '0', + rc_moved_to_title varchar(255) binary NOT NULL default '', + + rc_patrolled tinyint(3) unsigned NOT NULL default '0', + + rc_ip char(15) NOT NULL default '', + + PRIMARY KEY rc_id (rc_id), + INDEX rc_timestamp (rc_timestamp), + INDEX rc_namespace_title (rc_namespace, rc_title), + INDEX rc_cur_id (rc_cur_id), + INDEX new_name_timestamp(rc_new,rc_namespace,rc_timestamp), + INDEX rc_ip (rc_ip) + +) TYPE=InnoDB +ENDS; + $fields = array( + 'rc_id' => MW_UPGRADE_COPY, + 'rc_timestamp' => MW_UPGRADE_COPY, + 'rc_cur_time' => MW_UPGRADE_COPY, + 'rc_user' => MW_UPGRADE_COPY, + 'rc_user_text' => MW_UPGRADE_ENCODE, + 'rc_namespace' => MW_UPGRADE_COPY, + 'rc_title' => MW_UPGRADE_ENCODE, + 'rc_comment' => MW_UPGRADE_ENCODE, + 'rc_minor' => MW_UPGRADE_COPY, + 'rc_bot' => MW_UPGRADE_COPY, + 'rc_new' => MW_UPGRADE_COPY, + 'rc_cur_id' => MW_UPGRADE_COPY, + 'rc_this_oldid' => MW_UPGRADE_COPY, + 'rc_last_oldid' => MW_UPGRADE_COPY, + 'rc_type' => MW_UPGRADE_COPY, + 'rc_moved_to_ns' => MW_UPGRADE_COPY, + 'rc_moved_to_title' => MW_UPGRADE_ENCODE, + 'rc_patrolled' => MW_UPGRADE_COPY, + 'rc_ip' => MW_UPGRADE_COPY ); + $this->copyTable( 'recentchanges', $tabledef, $fields ); + } + + function upgradeQuerycache() { + // There's a format change in the namespace field + $tabledef = <<<ENDS +CREATE TABLE $1 ( + -- A key name, generally the base name of of the special page. + qc_type char(32) NOT NULL, + + -- Some sort of stored value. Sizes, counts... + qc_value int(5) unsigned NOT NULL default '0', + + -- Target namespace+title + qc_namespace int NOT NULL default '0', + qc_title char(255) binary NOT NULL default '', + + KEY (qc_type,qc_value) + +) TYPE=InnoDB +ENDS; + $fields = array( + 'qc_type' => MW_UPGRADE_COPY, + 'qc_value' => MW_UPGRADE_COPY, + 'qc_namespace' => MW_UPGRADE_COPY, + 'qc_title' => MW_UPGRADE_ENCODE ); + $this->copyTable( 'querycache', $tabledef, $fields ); + } + + /** + * Check for duplicate rows in "cur" table and move duplicates entries in + * "old" table. + * + * This was in cleanupDupes.inc before. + */ + function checkDupes() { + $dbw = wfGetDB( DB_MASTER ); + if ( $dbw->indexExists( 'cur', 'name_title' ) && + $dbw->indexUnique( 'cur', 'name_title' ) ) { + echo wfWikiID() . ": cur table has the current unique index; no duplicate entries.\n"; + return; + } elseif ( $dbw->indexExists( 'cur', 'name_title_dup_prevention' ) ) { + echo wfWikiID() . ": cur table has a temporary name_title_dup_prevention unique index; no duplicate entries.\n"; + return; + } + + echo wfWikiID() . ": cur table has the old non-unique index and may have duplicate entries.\n"; + + $dbw = wfGetDB( DB_MASTER ); + $cur = $dbw->tableName( 'cur' ); + $old = $dbw->tableName( 'old' ); + $dbw->query( "LOCK TABLES $cur WRITE, $old WRITE" ); + echo "Checking for duplicate cur table entries... (this may take a while on a large wiki)\n"; + $res = $dbw->query( <<<END +SELECT cur_namespace,cur_title,count(*) as c,min(cur_id) as id + FROM $cur + GROUP BY cur_namespace,cur_title +HAVING c > 1 +END + ); + $n = $dbw->numRows( $res ); + echo "Found $n titles with duplicate entries.\n"; + if ( $n > 0 ) { + echo "Correcting...\n"; + foreach ( $res as $row ) { + $ns = intval( $row->cur_namespace ); + $title = $dbw->addQuotes( $row->cur_title ); + + # Get the first responding ID; that'll be the one we keep. + $id = $dbw->selectField( 'cur', 'cur_id', array( + 'cur_namespace' => $row->cur_namespace, + 'cur_title' => $row->cur_title ) ); + + echo "$ns:$row->cur_title (canonical ID $id)\n"; + if ( $id != $row->id ) { + echo " ** minimum ID $row->id; "; + $timeMin = $dbw->selectField( 'cur', 'cur_timestamp', array( + 'cur_id' => $row->id ) ); + $timeFirst = $dbw->selectField( 'cur', 'cur_timestamp', array( + 'cur_id' => $id ) ); + if ( $timeMin == $timeFirst ) { + echo "timestamps match at $timeFirst; ok\n"; + } else { + echo "timestamps don't match! min: $timeMin, first: $timeFirst; "; + if ( $timeMin > $timeFirst ) { + $id = $row->id; + echo "keeping minimum: $id\n"; + } else { + echo "keeping first: $id\n"; + } + } + } + + $dbw->query( <<<END +INSERT + INTO $old + (old_namespace, old_title, old_text, + old_comment, old_user, old_user_text, + old_timestamp, old_minor_edit, old_flags, + inverse_timestamp) +SELECT cur_namespace, cur_title, cur_text, + cur_comment, cur_user, cur_user_text, + cur_timestamp, cur_minor_edit, '', + inverse_timestamp + FROM $cur + WHERE cur_namespace=$ns + AND cur_title=$title + AND cur_id != $id +END + ); + $dbw->query( <<<END +DELETE + FROM $cur + WHERE cur_namespace=$ns + AND cur_title=$title + AND cur_id != $id +END + ); + } + } + $dbw->query( 'UNLOCK TABLES' ); + echo "Done.\n"; + } + + /** + * Rename all our temporary tables into final place. + * We've left things in place so a read-only wiki can continue running + * on the old code during all this. + */ + function upgradeCleanup() { + $this->renameTable( 'old', 'text' ); + + foreach ( $this->cleanupSwaps as $table ) { + $this->swap( $table ); + } + } + + function renameTable( $from, $to ) { + $this->log( "Renaming $from to $to..." ); + + $fromtable = $this->dbw->tableName( $from ); + $totable = $this->dbw->tableName( $to ); + $this->dbw->query( "ALTER TABLE $fromtable RENAME TO $totable" ); + } + + function swap( $base ) { + $this->renameTable( $base, "{$base}_old" ); + $this->renameTable( "{$base}_temp", $base ); + } + +} +$maintClass = 'FiveUpgrade'; +require( RUN_MAINTENANCE_IF_MAIN ); diff --git a/maintenance/userDupes.inc b/maintenance/userDupes.inc index 2f224c3d..7c2bca1c 100644 --- a/maintenance/userDupes.inc +++ b/maintenance/userDupes.inc @@ -31,9 +31,19 @@ class UserDupes { var $reassigned; var $trimmed; var $failed; + private $outputCallback; - function UserDupes( &$database ) { - $this->db =& $database; + function __construct( &$database, $outputCallback ) { + $this->db = $database; + $this->outputCallback = $outputCallback; + } + + /** + * Output some text via the output callback provided + * @param $str String Text to print + */ + private function out( $str ) { + call_user_func( $this->outputCallback, $str ); } /** @@ -42,10 +52,9 @@ class UserDupes { * @return bool */ function hasUniqueIndex() { - $fname = 'UserDupes::hasUniqueIndex'; - $info = $this->db->indexInfo( 'user', 'user_name', $fname ); - if( !$info ) { - wfOut( "WARNING: doesn't seem to have user_name index at all!\n" ); + $info = $this->db->indexInfo( 'user', 'user_name', __METHOD__ ); + if ( !$info ) { + $this->out( "WARNING: doesn't seem to have user_name index at all!\n" ); return false; } @@ -85,55 +94,55 @@ class UserDupes { * @return bool */ function checkDupes( $doDelete = false ) { - if( $this->hasUniqueIndex() ) { - echo wfWikiID()." already has a unique index on its user table.\n"; + if ( $this->hasUniqueIndex() ) { + echo wfWikiID() . " already has a unique index on its user table.\n"; return true; } $this->lock(); - wfOut( "Checking for duplicate accounts...\n" ); + $this->out( "Checking for duplicate accounts...\n" ); $dupes = $this->getDupes(); $count = count( $dupes ); - wfOut( "Found $count accounts with duplicate records on ".wfWikiID().".\n" ); + $this->out( "Found $count accounts with duplicate records on " . wfWikiID() . ".\n" ); $this->trimmed = 0; $this->reassigned = 0; $this->failed = 0; - foreach( $dupes as $name ) { + foreach ( $dupes as $name ) { $this->examine( $name, $doDelete ); } $this->unlock(); - wfOut( "\n" ); + $this->out( "\n" ); - if( $this->reassigned > 0 ) { - if( $doDelete ) { - wfOut( "$this->reassigned duplicate accounts had edits reassigned to a canonical record id.\n" ); + if ( $this->reassigned > 0 ) { + if ( $doDelete ) { + $this->out( "$this->reassigned duplicate accounts had edits reassigned to a canonical record id.\n" ); } else { - wfOut( "$this->reassigned duplicate accounts need to have edits reassigned.\n" ); + $this->out( "$this->reassigned duplicate accounts need to have edits reassigned.\n" ); } } - if( $this->trimmed > 0 ) { - if( $doDelete ) { - wfOut( "$this->trimmed duplicate user records were deleted from ".wfWikiID().".\n" ); + if ( $this->trimmed > 0 ) { + if ( $doDelete ) { + $this->out( "$this->trimmed duplicate user records were deleted from " . wfWikiID() . ".\n" ); } else { - wfOut( "$this->trimmed duplicate user accounts were found on ".wfWikiID()." which can be removed safely.\n" ); + $this->out( "$this->trimmed duplicate user accounts were found on " . wfWikiID() . " which can be removed safely.\n" ); } } - if( $this->failed > 0 ) { - wfOut( "Something terribly awry; $this->failed duplicate accounts were not removed.\n" ); + if ( $this->failed > 0 ) { + $this->out( "Something terribly awry; $this->failed duplicate accounts were not removed.\n" ); return false; } - if( $this->trimmed == 0 || $doDelete ) { - wfOut( "It is now safe to apply the unique index on user_name.\n" ); + if ( $this->trimmed == 0 || $doDelete ) { + $this->out( "It is now safe to apply the unique index on user_name.\n" ); return true; } else { - wfOut( "Run this script again with the --fix option to automatically delete them.\n" ); + $this->out( "Run this script again with the --fix option to automatically delete them.\n" ); return false; } } @@ -143,8 +152,7 @@ class UserDupes { * @access private */ function lock() { - $fname = 'UserDupes::lock'; - if( $this->newSchema() ) { + if ( $this->newSchema() ) { $set = array( 'user', 'revision' ); } else { $set = array( 'user', 'cur', 'old' ); @@ -152,7 +160,7 @@ class UserDupes { $names = array_map( array( $this, 'lockTable' ), $set ); $tables = implode( ',', $names ); - $this->db->query( "LOCK TABLES $tables", $fname ); + $this->db->query( "LOCK TABLES $tables", __METHOD__ ); } function lockTable( $table ) { @@ -171,8 +179,7 @@ class UserDupes { * @access private */ function unlock() { - $fname = 'UserDupes::unlock'; - $this->db->query( "UNLOCK TABLES", $fname ); + $this->db->query( "UNLOCK TABLES", __METHOD__ ); } /** @@ -181,20 +188,17 @@ class UserDupes { * @access private */ function getDupes() { - $fname = 'UserDupes::listDupes'; $user = $this->db->tableName( 'user' ); $result = $this->db->query( "SELECT user_name,COUNT(*) AS n - FROM $user + FROM $user GROUP BY user_name - HAVING n > 1", $fname ); + HAVING n > 1", __METHOD__ ); $list = array(); - while( $row = $this->db->fetchObject( $result ) ) { + foreach ( $result as $row ) { $list[] = $row->user_name; } - $this->db->freeResult( $result ); - return $list; } @@ -207,46 +211,44 @@ class UserDupes { * @access private */ function examine( $name, $doDelete ) { - $fname = 'UserDupes::listDupes'; $result = $this->db->select( 'user', array( 'user_id' ), array( 'user_name' => $name ), - $fname ); + __METHOD__ ); $firstRow = $this->db->fetchObject( $result ); $firstId = $firstRow->user_id; - wfOut( "Record that will be used for '$name' is user_id=$firstId\n" ); + $this->out( "Record that will be used for '$name' is user_id=$firstId\n" ); - while( $row = $this->db->fetchObject( $result ) ) { + foreach ( $result as $row ) { $dupeId = $row->user_id; - wfOut( "... dupe id $dupeId: " ); + $this->out( "... dupe id $dupeId: " ); $edits = $this->editCount( $dupeId ); - if( $edits > 0 ) { + if ( $edits > 0 ) { $this->reassigned++; - wfOut( "has $edits edits! " ); - if( $doDelete ) { + $this->out( "has $edits edits! " ); + if ( $doDelete ) { $this->reassignEdits( $dupeId, $firstId ); $newEdits = $this->editCount( $dupeId ); - if( $newEdits == 0 ) { - wfOut( "confirmed cleaned. " ); + if ( $newEdits == 0 ) { + $this->out( "confirmed cleaned. " ); } else { $this->failed++; - wfOut( "WARNING! $newEdits remaining edits for $dupeId; NOT deleting user.\n" ); + $this->out( "WARNING! $newEdits remaining edits for $dupeId; NOT deleting user.\n" ); continue; } } else { - wfOut( "(will need to reassign edits on fix)" ); + $this->out( "(will need to reassign edits on fix)" ); } } else { - wfOut( "ok, no edits. " ); + $this->out( "ok, no edits. " ); } $this->trimmed++; - if( $doDelete ) { + if ( $doDelete ) { $this->trimAccount( $dupeId ); } - wfOut( "\n" ); + $this->out( "\n" ); } - $this->db->freeResult( $result ); } /** @@ -258,7 +260,7 @@ class UserDupes { * @access private */ function editCount( $userid ) { - if( $this->newSchema() ) { + if ( $this->newSchema() ) { return $this->editCountOn( 'revision', 'rev_user', $userid ); } else { return $this->editCountOn( 'cur', 'cur_user', $userid ) + @@ -275,12 +277,11 @@ class UserDupes { * @access private */ function editCountOn( $table, $field, $userid ) { - $fname = 'UserDupes::editCountOn'; return intval( $this->db->selectField( $table, 'COUNT(*)', array( $field => $userid ), - $fname ) ); + __METHOD__ ) ); } /** @@ -292,7 +293,7 @@ class UserDupes { $set = $this->newSchema() ? array( 'revision' => 'rev_user' ) : array( 'cur' => 'cur_user', 'old' => 'old_user' ); - foreach( $set as $table => $field ) { + foreach ( $set as $table => $field ) { $this->reassignEditsOn( $table, $field, $from, $to ); } } @@ -305,13 +306,12 @@ class UserDupes { * @access private */ function reassignEditsOn( $table, $field, $from, $to ) { - $fname = 'UserDupes::reassignEditsOn'; - wfOut( "reassigning on $table... " ); + $this->out( "reassigning on $table... " ); $this->db->update( $table, array( $field => $to ), array( $field => $from ), - $fname ); - wfOut( "ok. " ); + __METHOD__ ); + $this->out( "ok. " ); } /** @@ -320,10 +320,9 @@ class UserDupes { * @access private */ function trimAccount( $userid ) { - $fname = 'UserDupes::trimAccount'; - wfOut( "deleting..." ); - $this->db->delete( 'user', array( 'user_id' => $userid ), $fname ); - wfOut( " ok" ); + $this->out( "deleting..." ); + $this->db->delete( 'user', array( 'user_id' => $userid ), __METHOD__ ); + $this->out( " ok" ); } } diff --git a/maintenance/userOptions.inc b/maintenance/userOptions.inc index d660019d..deb12bc8 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( dirname(__FILE__) . '/commandLine.inc' ); +require_once( dirname( __FILE__ ) . '/commandLine.inc' ); /** * @ingroup Maintenance @@ -25,7 +25,7 @@ class userOptions { /** Constructor. Will show usage and exit if script options are not correct */ function __construct( $opts, $args ) { - if( !$this->checkOpts( $opts, $args ) ) { + if ( !$this->checkOpts( $opts, $args ) ) { userOptions::showUsageAndExit(); } else { $this->mReady = $this->initializeOpts( $opts, $args ); @@ -37,11 +37,11 @@ class userOptions { private function checkOpts( $opts, $args ) { // The three possible ways to run the script: $list = isset( $opts['list'] ); - $usage = isset( $opts['usage'] ) && (count($args) <= 1); - $change = isset( $opts['old']) && isset($opts['new']) && (count($args) <= 1) ; + $usage = isset( $opts['usage'] ) && ( count( $args ) <= 1 ); + $change = isset( $opts['old'] ) && isset( $opts['new'] ) && ( count( $args ) <= 1 ) ; // We want only one of them - $isValid = (($list + $usage + $change) == 1); + $isValid = ( ( $list + $usage + $change ) == 1 ); return $isValid; } @@ -54,18 +54,18 @@ class userOptions { $this->mDry = isset( $opts['dry'] ); // Set object properties, specially 'mMode' used by run() - if( isset($opts['list']) ) { + if ( isset( $opts['list'] ) ) { $this->mMode = 'LISTER' ; - } elseif( isset($opts['usage']) ) { + } elseif ( isset( $opts['usage'] ) ) { $this->mMode = 'USAGER' ; - $this->mAnOption = isset($args[0]) ? $args[0] : false ; - } elseif( isset($opts['old']) && isset($opts['new']) ) { + $this->mAnOption = isset( $args[0] ) ? $args[0] : false ; + } elseif ( isset( $opts['old'] ) && isset( $opts['new'] ) ) { $this->mMode = 'CHANGER' ; $this->mOldValue = $opts['old'] ; $this->mNewValue = $opts['new'] ; $this->mAnOption = $args[0]; } else { - die("There is a bug in the software, this should never happen\n"); + die( "There is a bug in the software, this should never happen\n" ); } return true; @@ -73,27 +73,27 @@ class userOptions { // Dumb stuff to run a mode. public function run() { - if(!$this->mReady ) { + if ( !$this->mReady ) { return false; } - $this->{$this->mMode}( ); + $this-> { $this->mMode } ( ); } # # Modes. - # + # /** List default options and their value */ private function LISTER( ) { $def = User::getDefaultOptions(); - ksort($def); + ksort( $def ); $maxOpt = 0; - foreach( $def as $opt => $value ) { - $maxOpt = max( $maxOpt, strlen($opt) ); + foreach ( $def as $opt => $value ) { + $maxOpt = max( $maxOpt, strlen( $opt ) ); } - foreach( $def as $opt => $value ) { + foreach ( $def as $opt => $value ) { printf( "%-{$maxOpt}s: %s\n", $opt, $value ); } } @@ -111,37 +111,37 @@ class userOptions { __METHOD__ ); - while( $id = $dbr->fetchObject( $result ) ) { + foreach ( $result as $id ) { $user = User::newFromId( $id->user_id ); // Get the options and update stats - if( $this->mAnOption ) { + if ( $this->mAnOption ) { - if(!array_key_exists( $this->mAnOption, $defaultOptions ) ) { + if ( !array_key_exists( $this->mAnOption, $defaultOptions ) ) { print "Invalid user option. Use --list to see valid choices\n"; exit; } $userValue = $user->getOption( $this->mAnOption ); - if( $userValue <> $defaultOptions[$this->mAnOption] ) { + if ( $userValue <> $defaultOptions[$this->mAnOption] ) { @$ret[$this->mAnOption][$userValue]++; } } else { - foreach( $defaultOptions as $name => $defaultValue ) { + foreach ( $defaultOptions as $name => $defaultValue ) { $userValue = $user->getOption( $name ); - if( $userValue <> $defaultValue ) { + if ( $userValue <> $defaultValue ) { @$ret[$name][$userValue]++; } } } } - foreach( $ret as $optionName => $usageStats ) { + foreach ( $ret as $optionName => $usageStats ) { print "Usage for <$optionName> (default: '{$defaultOptions[$optionName]}'):\n"; - foreach( $usageStats as $value => $count ) { + foreach ( $usageStats as $value => $count ) { print " $count user(s): '$value'\n"; } print "\n"; @@ -161,29 +161,29 @@ class userOptions { __METHOD__ ); - while( $id = $dbr->fetchObject( $result ) ) { + foreach ( $result as $id ) { $user = User::newFromId( $id->user_id ); $curValue = $user->getOption( $this->mAnOption ); $username = $user->getName(); - if( $curValue == $this->mOldValue ) { + if ( $curValue == $this->mOldValue ) { - if(!$this->mQuiet) { + if ( !$this->mQuiet ) { print "Setting {$this->mAnOption} for $username from '{$this->mOldValue}' to '{$this->mNewValue}'): "; } - // Change value + // Change value $user->setOption( $this->mAnOption, $this->mNewValue ); // Will not save the settings if run with --dry - if(!$this->mDry) { + if ( !$this->mDry ) { $user->saveSettings(); } - if( !$this->mQuiet) { print " OK\n"; } + if ( !$this->mQuiet ) { print " OK\n"; } - } elseif( !$this->mQuiet ) { + } elseif ( !$this->mQuiet ) { print "Not changing '$username' using <{$this->mAnOption}> = '$curValue'\n"; } } @@ -194,7 +194,7 @@ class userOptions { public static function getDefaultOptionsNames() { $def = User::getDefaultOptions(); $ret = array(); - foreach( $def as $optname => $defaultValue) { + foreach ( $def as $optname => $defaultValue ) { array_push( $ret, $optname ); } return $ret; @@ -212,31 +212,31 @@ This script pass through all users and change one of their options. The new option is NOT validated. Usage: - php userOptions.php --list - php userOptions.php [user option] --usage - php userOptions.php [options] <user option> --old <old value> --new <new value> + php userOptions.php --list + php userOptions.php [user option] --usage + php userOptions.php [options] <user option> --old <old value> --new <new value> Switchs: - --list : list available user options and their default value + --list : list available user options and their default value - --usage : report all options statistics or just one if you specify it. + --usage : report all options statistics or just one if you specify it. - --old <old value> : the value to look for - --new <new value> : new value to update users with + --old <old value> : the value to look for + --new <new value> : new value to update users with Options: - --nowarn: hides the 5 seconds warning - --quiet : do not print what is happening - --dry : do not save user settings back to database + --nowarn: hides the 5 seconds warning + --quiet : do not print what is happening + --dry : do not save user settings back to database USAGE; - exit(0); + exit( 0 ); } /** The warning message and countdown */ public function warn() { - if( $this->mQuick ) { + if ( $this->mQuick ) { return true; } diff --git a/maintenance/userOptions.php b/maintenance/userOptions.php index d4098ab2..597c6d45 100644 --- a/maintenance/userOptions.php +++ b/maintenance/userOptions.php @@ -8,7 +8,7 @@ * * @file * @ingroup Maintenance - * @author Ashar Voultoiz <hashar@altern.org> + * @author Ashar Voultoiz <hashar at free dot fr> */ // This is a command line script, load tools and parse args diff --git a/maintenance/waitForSlave.php b/maintenance/waitForSlave.php index f2a532c9..0bf01d6d 100644 --- a/maintenance/waitForSlave.php +++ b/maintenance/waitForSlave.php @@ -15,11 +15,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @file * @ingroup Maintenance * @see wfWaitForSlaves() */ -require_once( dirname(__FILE__) . '/Maintenance.php' ); +require_once( dirname( __FILE__ ) . '/Maintenance.php' ); class WaitForSlave extends Maintenance { public function __construct() { @@ -31,4 +32,4 @@ class WaitForSlave extends Maintenance { } $maintClass = "WaitForSlave"; -require_once( DO_MAINTENANCE ); +require_once( RUN_MAINTENANCE_IF_MAIN ); |