rootDirectory}/page/$letter";
if ( is_dir( $dir ) )
- importPageDirectory( $dir );
+ $this->importPageDirectory( $dir );
}
echo <<
XML;
-}
+ }
-function importPageDirectory( $dir, $prefix = "" )
-{
- echo "\n\n";
- $mydir = opendir( $dir );
- while ( $entry = readdir( $mydir ) ) {
- $m = array();
- if ( preg_match( '/^(.+)\.db$/', $entry, $m ) ) {
- echo importPage( $prefix . $m[1] );
- } else {
- if ( is_dir( "$dir/$entry" ) ) {
- if ( $entry != '.' && $entry != '..' ) {
- importPageDirectory( "$dir/$entry", "$entry/" );
- }
+ private function importPageDirectory( $dir, $prefix = "" ) {
+ echo "\n\n";
+ $mydir = opendir( $dir );
+ while ( $entry = readdir( $mydir ) ) {
+ $m = array();
+ if ( preg_match( '/^(.+)\.db$/', $entry, $m ) ) {
+ echo $this->importPage( $prefix . $m[1] );
} else {
- echo "\n";
+ if ( is_dir( "$dir/$entry" ) ) {
+ if ( $entry != '.' && $entry != '..' ) {
+ $this->importPageDirectory( "$dir/$entry", "$entry/" );
+ }
+ } else {
+ echo "\n";
+ }
}
}
}
-}
-
-# ------------------------------------------------------------------------------
-
-/* fetch_ functions
- Grab a given item from the database
- */
-
-function useModFilename( $title ) {
- $c = substr( $title, 0, 1 );
- if ( preg_match( '/[A-Z]/i', $c ) ) {
- return strtoupper( $c ) . "/$title";
- }
- return "other/$title";
-}
-
-function fetchPage( $title )
-{
- global $FS1, $FS2, $FS3, $wgRootDirectory;
-
- $fname = $wgRootDirectory . "/page/" . useModFilename( $title ) . ".db";
- if ( !file_exists( $fname ) ) {
- echo "Couldn't open file '$fname' for page '$title'.\n";
- die( -1 );
+ private function useModFilename( $title ) {
+ $c = substr( $title, 0, 1 );
+ if ( preg_match( '/[A-Z]/i', $c ) ) {
+ return strtoupper( $c ) . "/$title";
+ }
+ return "other/$title";
}
- $page = splitHash( $FS1, file_get_contents( $fname ) );
- $section = splitHash( $FS2, $page["text_default"] );
- $text = splitHash( $FS3, $section["data"] );
-
- return array2object( array( "text" => $text["text"] , "summary" => $text["summary"] ,
- "minor" => $text["minor"] , "ts" => $section["ts"] ,
- "username" => $section["username"] , "host" => $section["host"] ) );
-}
-
-function fetchKeptPages( $title )
-{
- global $FS1, $FS2, $FS3, $wgRootDirectory;
-
- $fname = $wgRootDirectory . "/keep/" . useModFilename( $title ) . ".kp";
- if ( !file_exists( $fname ) ) return array();
+ private function fetchPage( $title ) {
+ $fname = $this->rootDirectory . "/page/" . $this->useModFilename( $title ) . ".db";
+ if ( !file_exists( $fname ) ) {
+ echo "Couldn't open file '$fname' for page '$title'.\n";
+ die( -1 );
+ }
- $keptlist = explode( $FS1, file_get_contents( $fname ) );
- array_shift( $keptlist ); # Drop the junk at beginning of file
+ $page = $this->splitHash( $this->FS1, file_get_contents( $fname ) );
+ $section = $this->splitHash( $this->FS2, $page["text_default"] );
+ $text = $this->splitHash( $this->FS3, $section["data"] );
- $revisions = array();
- foreach ( $keptlist as $rev ) {
- $section = splitHash( $FS2, $rev );
- $text = splitHash( $FS3, $section["data"] );
- 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"] ) ) );
- } else {
- echo "\n";
- }
+ return $this->array2object( array( "text" => $text["text"] , "summary" => $text["summary"] ,
+ "minor" => $text["minor"] , "ts" => $section["ts"] ,
+ "username" => $section["username"] , "host" => $section["host"] ) );
}
- return $revisions;
-}
-function splitHash ( $sep , $str ) {
- $temp = explode ( $sep , $str ) ;
- $ret = array () ;
- for ( $i = 0; $i + 1 < count ( $temp ) ; $i++ ) {
- $ret[$temp[$i]] = $temp[++$i] ;
+ private function fetchKeptPages( $title ) {
+ $fname = $this->rootDirectory . "/keep/" . $this->useModFilename( $title ) . ".kp";
+ if ( !file_exists( $fname ) ) return array();
+
+ $keptlist = explode( $this->FS1, file_get_contents( $fname ) );
+ array_shift( $keptlist ); # Drop the junk at beginning of file
+
+ $revisions = array();
+ foreach ( $keptlist as $rev ) {
+ $section = $this->splitHash( $this->FS2, $rev );
+ $text = $this->splitHash( $this->FS3, $section["data"] );
+ if ( $text["text"] && $text["minor"] != "" && ( $section["ts"] * 1 > 0 ) ) {
+ array_push( $revisions, $this->array2object( array ( "text" => $text["text"] , "summary" => $text["summary"] ,
+ "minor" => $text["minor"] , "ts" => $section["ts"] ,
+ "username" => $section["username"] , "host" => $section["host"] ) ) );
+ } else {
+ echo "\n";
+ }
}
- return $ret ;
+ return $revisions;
}
+ private function splitHash( $sep , $str ) {
+ $temp = explode ( $sep , $str ) ;
+ $ret = array () ;
+ for ( $i = 0; $i + 1 < count ( $temp ) ; $i++ ) {
+ $ret[$temp[$i]] = $temp[++$i] ;
+ }
+ return $ret ;
+ }
-/* import_ functions
- Take a fetched item and produce SQL
- */
-
-function checkUserCache( $name, $host )
-{
- global $usercache;
-
- if ( $name ) {
- if ( in_array( $name, $usercache ) ) {
- $userid = $usercache[$name];
+ private function checkUserCache( $name, $host ) {
+ if ( $name ) {
+ if ( in_array( $name, $this->usercache ) ) {
+ $userid = $this->usercache[$name];
+ } else {
+ # If we haven't imported user accounts
+ $userid = 0;
+ }
+ $username = str_replace( '_', ' ', $name );
} else {
- # If we haven't imported user accounts
$userid = 0;
+ $username = $host;
}
- $username = str_replace( '_', ' ', $name );
- } else {
- $userid = 0;
- $username = $host;
+ return array( $userid, $username );
}
- return array( $userid, $username );
-}
-
-function importPage( $title )
-{
- echo "\n\n";
- $page = fetchPage( $title );
-
- $newtitle = xmlsafe( str_replace( '_', ' ', recodeText( $title ) ) );
- $munged = mungeFormat( $page->text );
- if ( $munged != $page->text ) {
- /**
- * Save a *new* revision with the conversion, and put the
- * previous last version into the history.
- */
- $next = array2object( array(
- 'text' => $munged,
- 'minor' => 1,
- 'username' => 'Conversion script',
- 'host' => '127.0.0.1',
- 'ts' => time(),
- 'summary' => 'link fix',
- ) );
- $revisions = array( $page, $next );
- } else {
- /**
- * Current revision:
- */
- $revisions = array( $page );
- }
- $xml = <<
- $newtitle
+ private function importPage( $title ) {
+ echo "\n\n";
+ $page = $this->fetchPage( $title );
+
+ $newtitle = $this->xmlsafe( str_replace( '_', ' ', $this->recodeText( $title ) ) );
+
+ $munged = $this->mungeFormat( $page->text );
+ if ( $munged != $page->text ) {
+ /**
+ * Save a *new* revision with the conversion, and put the
+ * previous last version into the history.
+ */
+ $next = $this->array2object( array(
+ 'text' => $munged,
+ 'minor' => 1,
+ 'username' => 'Conversion script',
+ 'host' => '127.0.0.1',
+ 'ts' => time(),
+ 'summary' => 'link fix',
+ ) );
+ $revisions = array( $page, $next );
+ } else {
+ /**
+ * Current revision:
+ */
+ $revisions = array( $page );
+ }
+ $xml = <<
+ $newtitle
XML;
- # History
- $revisions = array_merge( $revisions, fetchKeptPages( $title ) );
- if ( count( $revisions ) == 0 ) {
- return NULL; // Was "$sql", which does not appear to be defined.
- }
+ # History
+ $revisions = array_merge( $revisions, $this->fetchKeptPages( $title ) );
+ if ( count( $revisions ) == 0 ) {
+ return NULL; // Was "$sql", which does not appear to be defined.
+ }
- foreach ( $revisions as $rev ) {
- $text = xmlsafe( recodeText( $rev->text ) );
- $minor = ( $rev->minor ? '' : '' );
- list( /* $userid */ , $username ) = checkUserCache( $rev->username, $rev->host );
- $username = xmlsafe( recodeText( $username ) );
- $timestamp = xmlsafe( timestamp2ISO8601( $rev->ts ) );
- $comment = xmlsafe( recodeText( $rev->summary ) );
-
- $xml .= <<
- $timestamp
- $username
- $minor
- $comment
- $text
-
+ foreach ( $revisions as $rev ) {
+ $text = $this->xmlsafe( $this->recodeText( $rev->text ) );
+ $minor = ( $rev->minor ? '' : '' );
+ list( /* $userid */ , $username ) = $this->checkUserCache( $rev->username, $rev->host );
+ $username = $this->xmlsafe( $this->recodeText( $username ) );
+ $timestamp = $this->xmlsafe( $this->timestamp2ISO8601( $rev->ts ) );
+ $comment = $this->xmlsafe( $this->recodeText( $rev->summary ) );
+
+ $xml .= <<
+ $timestamp
+ $username
+ $minor
+ $comment
+ $text
+
XML;
+ }
+ $xml .= "\n\n";
+ return $xml;
}
- $xml .= "\n\n";
- return $xml;
-}
-# Whee!
-function recodeText( $string ) {
- global $wgImportEncoding;
- # For currently latin-1 wikis
- $string = str_replace( "\r\n", "\n", $string );
- $string = @iconv( $wgImportEncoding, "UTF-8", $string );
- $string = wfMungeToUtf8( $string ); # Any old Ӓ stuff
- 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 );
- # Doesn't yet handle outside the BMP
- return "$codepoint;";
-}
+ private function recodeText( $string ) {
+ # For currently latin-1 wikis
+ $string = str_replace( "\r\n", "\n", $string );
+ $string = @iconv( $this->encoding, "UTF-8", $string );
+ $string = $this->mungeToUtf8( $string ); # Any old Ӓ stuff
+ return $string;
+ }
-function wfMungeToUtf8( $string ) {
- $string = preg_replace ( '/([0-9]+);/e', 'wfUtf8Sequence($1)', $string );
- $string = preg_replace ( '/([0-9a-f]+);/ie', 'wfUtf8Sequence(0x$1)', $string );
- # Should also do named entities here
- return $string;
-}
+ /**
+ * @todo FIXME: Don't use /e
+ */
+ private function mungeToUtf8( $string ) {
+ $string = preg_replace ( '/([0-9]+);/e', 'wfUtf8Sequence($1)', $string );
+ $string = preg_replace ( '/([0-9a-f]+);/ie', 'wfUtf8Sequence(0x$1)', $string );
+ # Should also do named entities here
+ return $string;
+ }
-function timestamp2ISO8601( $ts ) {
- # 2003-08-05T18:30:02Z
- return gmdate( 'Y-m-d', $ts ) . 'T' . gmdate( 'H:i:s', $ts ) . 'Z';
-}
+ private function timestamp2ISO8601( $ts ) {
+ # 2003-08-05T18:30:02Z
+ return gmdate( 'Y-m-d', $ts ) . 'T' . gmdate( 'H:i:s', $ts ) . 'Z';
+ }
-function xmlsafe( $string ) {
/**
* The page may contain old data which has not been properly normalized.
* Invalid UTF-8 sequences or forbidden control characters will make our
* XML output invalid, so be sure to strip them out.
+ * @param String $string Text to clean up
+ * @return String
*/
- $string = UtfNormal::cleanUp( $string );
-
- $string = htmlspecialchars( $string );
- return $string;
-}
-
-function xmlCommentSafe( $text ) {
- return str_replace( '--', '\\-\\-', xmlsafe( recodeText( $text ) ) );
-}
+ private function xmlsafe( $string ) {
+ $string = UtfNormal::cleanUp( $string );
+ $string = htmlspecialchars( $string );
+ return $string;
+ }
+ private function xmlCommentSafe( $text ) {
+ return str_replace( '--', '\\-\\-', $this->xmlsafe( $this->recodeText( $text ) ) );
+ }
-function array2object( $arr ) {
- $o = (object)0;
- foreach ( $arr as $x => $y ) {
- $o->$x = $y;
+ private function array2object( $arr ) {
+ $o = (object)0;
+ foreach ( $arr as $x => $y ) {
+ $o->$x = $y;
+ }
+ return $o;
}
- return $o;
-}
+ /**
+ * Make CamelCase and /Talk links work
+ */
+ private function mungeFormat( $text ) {
+ $this->nowiki = array();
+ $staged = preg_replace_callback(
+ '/(.*?<\\/nowiki>|(?:http|https|ftp):\\S+|\[\[[^]\\n]+]])/s',
+ array( $this, 'nowikiPlaceholder' ), $text );
+
+ # This is probably not 100% correct, I'm just
+ # glancing at the UseModWiki code.
+ $upper = "[A-Z]";
+ $lower = "[a-z_0-9]";
+ $any = "[A-Za-z_0-9]";
+ $camel = "(?:$upper+$lower+$upper+$any*)";
+ $subpage = "(?:\\/$any+)";
+ $substart = "(?:\\/$upper$any*)";
+
+ $munged = preg_replace( "/(?!\\[\\[)($camel$subpage*|$substart$subpage*)\\b(?!\\]\\]|>)/",
+ '[[$1]]', $staged );
+
+ $final = preg_replace( '/' . preg_quote( $this->placeholder() ) . '/s',
+ array( $this, 'nowikiShift' ), $munged );
+ return $final;
+ }
-/**
- * Make CamelCase and /Talk links work
- */
-function mungeFormat( $text ) {
- global $nowiki;
- $nowiki = array();
- $staged = preg_replace_callback(
- '/(.*?<\\/nowiki>|(?:http|https|ftp):\\S+|\[\[[^]\\n]+]])/s',
- 'nowikiPlaceholder', $text );
-
- # This is probably not 100% correct, I'm just
- # glancing at the UseModWiki code.
- $upper = "[A-Z]";
- $lower = "[a-z_0-9]";
- $any = "[A-Za-z_0-9]";
- $camel = "(?:$upper+$lower+$upper+$any*)";
- $subpage = "(?:\\/$any+)";
- $substart = "(?:\\/$upper$any*)";
-
- $munged = preg_replace( "/(?!\\[\\[)($camel$subpage*|$substart$subpage*)\\b(?!\\]\\]|>)/",
- '[[$1]]', $staged );
-
- $final = preg_replace( '/' . preg_quote( placeholder() ) . '/es',
- 'array_shift( $nowiki )', $munged );
- return $final;
-}
+ private function placeholder( $x = null ) {
+ return '\xffplaceholder\xff';
+ }
+ public function nowikiPlaceholder( $matches ) {
+ $this->nowiki[] = $matches[1];
+ return $this->placeholder();
+ }
-function placeholder( $x = null ) {
- return '\xffplaceholder\xff';
+ public function nowikiShift() {
+ return array_shift( $this->nowiki );
+ }
}
-function nowikiPlaceholder( $matches ) {
- global $nowiki;
- $nowiki[] = $matches[1];
- return placeholder();
+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;";
}
-
+$maintClass = 'ImportUseModWiki';
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/maintenance/importUseModWikipedia.php b/maintenance/importUseModWikipedia.php
new file mode 100644
index 00000000..c4b8112f
--- /dev/null
+++ b/maintenance/importUseModWikipedia.php
@@ -0,0 +1,892 @@
+ 983862286,
+ 'TexaS' => 983918410,
+ 'HistoryOfUnitedStatesTalk' => 984795423,
+ 'MetallicA' => 985128533,
+ 'PythagoreanTheorem' => 985225545,
+ 'TheCanonofScripture' => 985368223,
+ 'TaoTehChing' => 985368222,
+ //'TheMostRemarkableFormulaInTheWorld' => 985368221,
+ 'TheRecorder' => 985368220,
+ 'GladstoneOregon' => 985368219,
+ 'PacificBeach' => '?',
+ 'AaRiver' => '?',
+ );
+
+ var $replacements = array();
+
+ var $renameTextLinksOps = array(
+ 983846265 => array(
+ 'TestIgnore' => 'IgnoreTest',
+ ),
+ 983848080 => array(
+ 'UnitedLocomotiveWorks' => 'Atlas Shrugged/United Locomotive Works'
+ ),
+ 983856376 => array(
+ 'WikiPedia' => 'Wikipedia',
+ ),
+ 983896152 => array(
+ 'John_F_Kennedy' => 'John_F._Kennedy',
+ ),
+ 983905871 => array(
+ 'LarrySanger' => 'Larry_Sanger'
+ ),
+ 984697068 => array(
+ 'UnitedStates' => 'United States',
+ ),
+ 984792748 => array(
+ 'LibertarianisM' => 'Libertarianism'
+ ),
+ 985327832 => array(
+ 'AnarchisM' => 'Anarchism',
+ ),
+ 985290063 => array(
+ 'HistoryOfUnitedStatesDiscussion' => 'History_Of_United_States_Discussion'
+ ),
+ 985290091 => array(
+ 'BritishEmpire' => 'British Empire'
+ ),
+ /*
+ 985468958 => array(
+ 'ScienceFiction' => 'Science fiction',
+ ),*/
+ );
+
+ /**
+ * Hack for observed substitution issues
+ */
+ var $skipSelfSubstitution = array(
+ 'Pythagorean_Theorem',
+ 'The_Most_Remarkable_Formula_In_The_World',
+ 'Wine',
+ );
+
+ var $unixLineEndingsOps = array(
+ 987743732 => 'Wikipedia_FAQ'
+ );
+
+ var $replacementsDone = array();
+
+ var $moveLog = array();
+ var $moveDests = array();
+ var $revId;
+
+ var $rc = array();
+ var $textCache = array();
+ var $blacklist = array();
+
+ var $FS, $FS1, $FS2, $FS3;
+ var $FreeLinkPattern, $UrlPattern, $LinkPattern, $InterLinkPattern;
+
+ var $cp1252Table = array(
+0x80 => 0x20ac,
+0x81 => 0x0081,
+0x82 => 0x201a,
+0x83 => 0x0192,
+0x84 => 0x201e,
+0x85 => 0x2026,
+0x86 => 0x2020,
+0x87 => 0x2021,
+0x88 => 0x02c6,
+0x89 => 0x2030,
+0x8a => 0x0160,
+0x8b => 0x2039,
+0x8c => 0x0152,
+0x8d => 0x008d,
+0x8e => 0x017d,
+0x8f => 0x008f,
+0x90 => 0x0090,
+0x91 => 0x2018,
+0x92 => 0x2019,
+0x93 => 0x201c,
+0x94 => 0x201d,
+0x95 => 0x2022,
+0x96 => 0x2013,
+0x97 => 0x2014,
+0x98 => 0x02dc,
+0x99 => 0x2122,
+0x9a => 0x0161,
+0x9b => 0x203a,
+0x9c => 0x0153,
+0x9d => 0x009d,
+0x9e => 0x017e,
+0x9f => 0x0178);
+
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( 'datadir', 'the value of $DataDir from wiki.cgi', true, true );
+ $this->addOption( 'outfile', 'the name of the output XML file', true, true );
+ $this->initLinkPatterns();
+
+ $this->encodeMap = $this->decodeMap = array();
+
+ for ($source = 0; $source <= 0xff; $source++) {
+ if ( isset( $this->cp1252Table[$source] ) ) {
+ $dest = $this->cp1252Table[$source];
+ } else {
+ $dest = $source;
+ }
+ $sourceChar = chr( $source );
+ $destChar = codepointToUtf8( $dest );
+ $this->encodeMap[$sourceChar] = $destChar;
+ $this->decodeMap[$destChar] = $sourceChar;
+ }
+ }
+
+ function initLinkPatterns() {
+ # Field separators are used in the URL-style patterns below.
+ $this->FS = "\xb3"; # The FS character is a superscript "3"
+ $this->FS1 = $this->FS . "1"; # The FS values are used to separate fields
+ $this->FS2 = $this->FS . "2"; # in stored hashtables and other data structures.
+ $this->FS3 = $this->FS . "3"; # The FS character is not allowed in user data.
+
+ $UpperLetter = "[A-Z";
+ $LowerLetter = "[a-z";
+ $AnyLetter = "[A-Za-z";
+ $AnyLetter .= "_0-9";
+ $UpperLetter .= "]"; $LowerLetter .= "]"; $AnyLetter .= "]";
+
+ # Main link pattern: lowercase between uppercase, then anything
+ $LpA = $UpperLetter . "+" . $LowerLetter . "+" . $UpperLetter
+ . $AnyLetter . "*";
+ # Optional subpage link pattern: uppercase, lowercase, then anything
+ $LpB = $UpperLetter . "+" . $LowerLetter . "+" . $AnyLetter . "*";
+
+ # Loose pattern: If subpage is used, subpage may be simple name
+ $this->LinkPattern = "((?:(?:$LpA)?\\/$LpB)|$LpA)";
+ $QDelim = '(?:"")?'; # Optional quote delimiter (not in output)
+ $this->LinkPattern .= $QDelim;
+
+ # Inter-site convention: sites must start with uppercase letter
+ # (Uppercase letter avoids confusion with URLs)
+ $InterSitePattern = $UpperLetter . $AnyLetter . "+";
+ $this->InterLinkPattern = "((?:$InterSitePattern:[^\\]\\s\"<>{$this->FS}]+)$QDelim)";
+
+ $AnyLetter = "[-,. _0-9A-Za-z]";
+ $this->FreeLinkPattern = "($AnyLetter+)";
+ $this->FreeLinkPattern = "((?:(?:$AnyLetter+)?\\/)?$AnyLetter+)";
+ $this->FreeLinkPattern .= $QDelim;
+
+ # Url-style links are delimited by one of:
+ # 1. Whitespace (kept in output)
+ # 2. Left or right angle-bracket (< or >) (kept in output)
+ # 3. Right square-bracket (]) (kept in output)
+ # 4. A single double-quote (") (kept in output)
+ # 5. A $FS (field separator) character (kept in output)
+ # 6. A double double-quote ("") (removed from output)
+
+ $UrlProtocols = "http|https|ftp|afs|news|nntp|mid|cid|mailto|wais|"
+ . "prospero|telnet|gopher";
+ $UrlProtocols .= '|file';
+ $this->UrlPattern = "((?:(?:$UrlProtocols):[^\\]\\s\"<>{$this->FS}]+)$QDelim)";
+ $ImageExtensions = "(gif|jpg|png|bmp|jpeg)";
+ $RFCPattern = "RFC\\s?(\\d+)";
+ $ISBNPattern = "ISBN:?([0-9- xX]{10,})";
+ }
+
+ function execute() {
+ $this->articleFileName = '/tmp/importUseMod.' . mt_rand( 0, 0x7ffffff ) . '.tmp';
+ $this->patchFileName = '/tmp/importUseMod.' . mt_rand( 0, 0x7ffffff ) . '.tmp';
+ $this->dataDir = $this->getOption( 'datadir' );
+ $this->outFile = fopen( $this->getOption( 'outfile' ), 'w' );
+ if ( !$this->outFile ) {
+ echo "Unable to open output file\n";
+ return 1;
+ }
+ $this->writeXmlHeader();
+ $this->readRclog();
+ $this->writeMoveLog();
+ $this->writeRevisions();
+ $this->reconcileCurrentRevs();
+ $this->writeXmlFooter();
+ unlink( $this->articleFileName );
+ unlink( $this->patchFileName );
+ return 0;
+ }
+
+ function writeXmlHeader() {
+ fwrite( $this->outFile, <<
+
+ Wikipedia
+ http://www.wikipedia.com/
+ MediaWiki 1.18alpha importUseModWikipedia.php
+ case-sensitive
+
+
+
+
+
+EOT
+ );
+ }
+
+ function writeXmlFooter() {
+ fwrite( $this->outFile, "\n" );
+ }
+
+ function readRclog() {
+ $rcFile = fopen( "{$this->dataDir}/rclog", 'r' );
+ while ( $line = fgets( $rcFile ) ) {
+ $bits = explode( $this->FS3, $line );
+ if ( count( $bits ) !== 7 ) {
+ echo "Error reading rclog\n";
+ return;
+ }
+ $params = array(
+ 'timestamp' => $bits[0],
+ 'rctitle' => $bits[1],
+ 'summary' => $bits[2],
+ 'minor' => $bits[3],
+ 'host' => $bits[4],
+ 'kind' => $bits[5],
+ 'extra' => array()
+ );
+ $extraList = explode( $this->FS2, $bits[6] );
+
+ for ( $i = 0; $i < count( $extraList ); $i += 2 ) {
+ $params['extra'][$extraList[$i]] = $extraList[$i + 1];
+ }
+ $this->rc[$params['timestamp']][] = $params;
+ }
+ }
+
+ function writeMoveLog() {
+ $this->moveLog = array();
+ $deepRenames = $this->deepRenames;
+ echo "Calculating move log...\n";
+ $this->processDiffFile( array( $this, 'moveLogCallback' ) );
+
+ // We have the timestamp intervals, now make a guess at the actual timestamp
+ foreach ( $this->moveLog as $newTitle => $params ) {
+ // Is there a time specified?
+ $drTime = false;
+ if ( isset( $deepRenames[$params['old']] ) ) {
+ $drTime = $deepRenames[$params['old']];
+ if ( $drTime !== '?' ) {
+ if ( ( !isset( $params['endTime'] ) || $drTime < $params['endTime'] )
+ && $drTime > $params['startTime'] )
+ {
+ $this->moveLog[$newTitle]['timestamp'] = $drTime;
+ $this->moveLog[$newTitle]['deep'] = true;
+
+ echo "{$params['old']} -> $newTitle at $drTime\n";
+ unset( $deepRenames[$params['old']] );
+ continue;
+ } else {
+ echo "WARNING: deep rename time invalid: {$params['old']}\n";
+ unset( $deepRenames[$params['old']] );
+ }
+ }
+ }
+
+ // Guess that it is one second after the last edit to the page before it was moved
+ $this->moveLog[$newTitle]['timestamp'] = $params['startTime'] + 1;
+ if ( $drTime === '?' ) {
+ $this->moveLog[$newTitle]['deep'] = true;
+ unset( $deepRenames[$params['old']] );
+ }
+ if ( isset( $params['endTime'] ) ) {
+ $this->printLatin1( "{$params['old']} -> $newTitle between " .
+ "{$params['startTime']} and {$params['endTime']}\n" );
+ } else {
+ $this->printLatin1( "{$params['old']} -> $newTitle after " .
+ "{$params['startTime']}\n" );
+ }
+ }
+
+ // Write the move log to the XML file
+ $id = 1;
+ foreach ( $this->moveLog as $newTitle => $params ) {
+ $out = "\n" .
+ $this->element( 'id', $id++ ) .
+ $this->element( 'timestamp', wfTimestamp( TS_ISO_8601, $params['timestamp'] ) ) .
+ "\n" .
+ $this->element( 'username', 'UseModWiki admin' ) .
+ "" .
+ $this->element( 'type', 'move' ) .
+ $this->element( 'action', 'move' ) .
+ $this->element( 'logtitle', $params['old'] ) .
+ "" .
+ htmlspecialchars( $this->encode( "{$newTitle}\n1" ) ) .
+ "\n" .
+ "\n";
+ fwrite( $this->outFile, $out );
+ }
+
+ // Check for remaining deep rename entries
+ if ( $deepRenames ) {
+ echo "WARNING: the following entries in \$this->deepRenames are " .
+ "invalid, since no such move exists:\n" .
+ implode( "\n", array_keys( $deepRenames ) ) .
+ "\n\n";
+ }
+
+ }
+
+ function element( $name, $value ) {
+ return "<$name>" . htmlspecialchars( $this->encode( $value ) ) . "$name>\n";
+ }
+
+ function moveLogCallback( $entry ) {
+ $rctitle = $entry['rctitle'];
+ $title = $entry['title'];
+ $this->moveDests[$rctitle] = $title;
+
+ if ( $rctitle === $title ) {
+ if ( isset( $this->moveLog[$rctitle] )
+ && !isset( $this->moveLog[$rctitle]['endTime'] ) )
+ {
+ // This is the latest time that the page could have been moved
+ $this->moveLog[$rctitle]['endTime'] = $entry['timestamp'];
+ }
+ } else {
+ if ( !isset( $this->moveLog[$rctitle] ) ) {
+ // Initialise the move log entry
+ $this->moveLog[$rctitle] = array(
+ 'old' => $title
+ );
+ }
+ // Update the earliest time the page could have been moved
+ $this->moveLog[$rctitle]['startTime'] = $entry['timestamp'];
+ }
+ }
+
+ function writeRevisions() {
+ $this->numGoodRevs = 0;
+ $this->revId = 1;
+ $this->processDiffFile( array( $this, 'revisionCallback' ) );
+ echo "\n\nImported {$this->numGoodRevs} out of {$this->numRevs}\n";
+ }
+
+ function revisionCallback( $params ) {
+ $title = $params['rctitle'];
+ $editTime = $params['timestamp'];
+
+ if ( isset( $this->blacklist[$title] ) ) {
+ return;
+ }
+ $this->doPendingOps( $editTime );
+
+ $origText = $this->getText( $title );
+ $text = $this->patch( $origText, $params['diff'] );
+ if ( $text === false ) {
+ echo "$editTime $title attempting resolution...\n";
+ $linkSubstitutes = $this->resolveFailedDiff( $origText, $params['diff'] );
+ if ( !$linkSubstitutes ) {
+ $this->printLatin1( "$editTime $title DIFF FAILED\n" );
+ $this->blacklist[$title] = true;
+ return;
+ }
+ $this->printLatin1( "$editTime $title requires substitutions:\n" );
+ $time = $editTime - 1;
+ foreach ( $linkSubstitutes as $old => $new ) {
+ $this->printLatin1( "SUBSTITUTE $old -> $new\n" );
+ $this->renameTextLinks( $old, $new, $time-- );
+ }
+ $origText = $this->getText( $title );
+ $text = $this->patch( $origText, $params['diff'] );
+ if ( $text === false ) {
+ $this->printLatin1( "$editTime $title STILL FAILS!\n" );
+ $this->blacklist[$title] = true;
+ return;
+ }
+
+ echo "\n";
+ }
+
+ $params['text'] = $text;
+ $this->saveRevision( $params );
+ $this->numGoodRevs++;
+ #$this->printLatin1( "$editTime $title\n" );
+ }
+
+ function doPendingOps( $editTime ) {
+ foreach ( $this->moveLog as $newTitle => $entry ) {
+ if ( $entry['timestamp'] <= $editTime ) {
+ unset( $this->moveLog[$newTitle] );
+ if ( isset( $entry['deep'] ) ) {
+ $this->renameTextLinks( $entry['old'], $newTitle, $entry['timestamp'] );
+ }
+ }
+ }
+
+ foreach ( $this->renameTextLinksOps as $renameTime => $replacements ) {
+ if ( $editTime >= $renameTime ) {
+ foreach ( $replacements as $old => $new ) {
+ $this->printLatin1( "SUBSTITUTE $old -> $new\n" );
+ $this->renameTextLinks( $old, $new, $renameTime );
+ }
+ unset( $this->renameTextLinksOps[$renameTime] );
+ }
+ }
+
+ foreach ( $this->unixLineEndingsOps as $fixTime => $title ) {
+ if ( $editTime >= $fixTime ) {
+ $this->printLatin1( "$fixTime $title FIXING LINE ENDINGS\n" );
+ $text = $this->getText( $title );
+ $text = str_replace( "\r", '', $text );
+ $this->saveRevision( array(
+ 'rctitle' => $title,
+ 'timestamp' => $fixTime,
+ 'extra' => array( 'name' => 'UseModWiki admin' ),
+ 'text' => $text,
+ 'summary' => 'Fixing line endings',
+ ) );
+ unset( $this->unixLineEndingsOps[$fixTime] );
+ }
+ }
+ }
+
+ function patch( $source, $diff ) {
+ file_put_contents( $this->articleFileName, $source );
+ file_put_contents( $this->patchFileName, $diff );
+ $error = wfShellExec(
+ wfEscapeShellArg(
+ 'patch',
+ '-n',
+ '-r', '-',
+ '--no-backup-if-mismatch',
+ '--binary',
+ $this->articleFileName,
+ $this->patchFileName
+ ) . ' 2>&1',
+ $status
+ );
+ $text = file_get_contents( $this->articleFileName );
+ if ( $status || $text === false ) {
+ return false;
+ } else {
+ return $text;
+ }
+ }
+
+ function resolveFailedDiff( $origText, $diff ) {
+ $context = array();
+ $diffLines = explode( "\n", $diff );
+ for ( $i = 0; $i < count( $diffLines ); $i++ ) {
+ $diffLine = $diffLines[$i];
+ if ( !preg_match( '/^(\d+)(?:,\d+)?[acd]\d+(?:,\d+)?$/', $diffLine, $m ) ) {
+ continue;
+ }
+
+ $sourceIndex = intval( $m[1] );
+ $i++;
+ while ( $i < count( $diffLines ) && substr( $diffLines[$i], 0, 1 ) === '<' ) {
+ $context[$sourceIndex - 1] = substr( $diffLines[$i], 2 );
+ $sourceIndex++;
+ $i++;
+ }
+ $i--;
+ }
+
+ $changedLinks = array();
+ $origLines = explode( "\n", $origText );
+ foreach ( $context as $i => $contextLine ) {
+ $origLine = isset( $origLines[$i] ) ? $origLines[$i] : '';
+ if ( $contextLine === $origLine ) {
+ continue;
+ }
+ $newChanges = $this->resolveTextChange( $origLine, $contextLine );
+ if ( is_array( $newChanges ) ) {
+ $changedLinks += $newChanges;
+ } else {
+ echo "Resolution failure on line " . ( $i + 1 ) . "\n";
+ $this->printLatin1( $newChanges );
+ }
+ }
+
+ return $changedLinks;
+ }
+
+ function resolveTextChange( $source, $dest ) {
+ $changedLinks = array();
+ $sourceLinks = $this->getLinkList( $source );
+ $destLinks = $this->getLinkList( $dest );
+ $newLinks = array_diff( $destLinks, $sourceLinks );
+ $removedLinks = array_diff( $sourceLinks, $destLinks );
+
+ // Match up the removed links with the new links
+ foreach ( $newLinks as $newLink ) {
+ $minDistance = 100000000;
+ $bestRemovedLink = false;
+ foreach ( $removedLinks as $removedLink ) {
+ $editDistance = levenshtein( $newLink, $removedLink );
+ if ( $editDistance < $minDistance ) {
+ $minDistance = $editDistance;
+ $bestRemovedLink = $removedLink;
+ }
+ }
+ if ( $bestRemovedLink !== false ) {
+ $changedLinks[$bestRemovedLink] = $newLink;
+ $newLinks = array_diff( $newLinks, array( $newLink ) );
+ $removedLinks = array_diff( $removedLinks, array( $bestRemovedLink ) );
+ }
+ }
+
+ $proposal = $source;
+ foreach ( $changedLinks as $removedLink => $newLink ) {
+ $proposal = $this->substituteTextLinks( $removedLink, $newLink, $proposal );
+ }
+ if ( $proposal !== $dest ) {
+ // Resolution failed
+ $msg = "Source line: $source\n" .
+ "Source links: " . implode( ', ', $sourceLinks ) . "\n" .
+ "Context line: $dest\n" .
+ "Context links: " . implode( ', ', $destLinks ) . "\n" .
+ "Proposal: $proposal\n";
+ return $msg;
+ }
+ return $changedLinks;
+ }
+
+ function processDiffFile( $callback ) {
+ $diffFile = fopen( "{$this->dataDir}/diff_log", 'r' );
+
+ $delimiter = "------\n";
+ file_put_contents( $this->articleFileName, "Describe the new page here.\n" );
+
+ $line = fgets( $diffFile );
+ $lineNum = 1;
+ if ( $line !== $delimiter ) {
+ echo "Invalid diff file\n";
+ return false;
+ }
+ $lastReportLine = 0;
+ $this->numRevs = 0;
+
+ while ( true ) {
+ $line = fgets( $diffFile );
+ $lineNum++;
+ if ( $line === false ) {
+ break;
+ }
+ if ( $lineNum > $lastReportLine + 1000 ) {
+ $lastReportLine = $lineNum;
+ fwrite( STDERR, "$lineNum \r" );
+ fflush( STDERR );
+ }
+ $line = trim( $line );
+ if ( !preg_match( '/^([^|]+)\|(\d+)$/', $line, $matches ) ) {
+ echo "Invalid header on line $lineNum\n";
+ return true;
+ }
+ list( , $title, $editTime ) = $matches;
+
+ $diff = '';
+ $diffStartLine = $lineNum;
+ while ( true ) {
+ $line = fgets( $diffFile );
+ $lineNum++;
+ if ( $line === $delimiter ) {
+ break;
+ }
+ if ( $line === false ) {
+ break 2;
+ }
+ $diff .= $line;
+ }
+
+ $this->numRevs++;
+
+ if ( !isset( $this->rc[$editTime] ) ) {
+ $this->printLatin1( "$editTime $title DELETED, skipping\n" );
+ continue;
+ }
+
+ if ( count( $this->rc[$editTime] ) == 1 ) {
+ $params = $this->rc[$editTime][0];
+ } else {
+ $params = false;
+ $candidates = '';
+ foreach ( $this->rc[$editTime] as $rc ) {
+ if ( $rc['rctitle'] === $title ) {
+ $params = $rc;
+ break;
+ }
+ if ( $candidates === '' ) {
+ $candidates = $rc['rctitle'];
+ } else {
+ $candidates .= ', ' . $rc['rctitle'];
+ }
+ }
+ if ( !$params ) {
+ $this->printLatin1( "$editTime $title ERROR cannot resolve rclog\n" );
+ $this->printLatin1( "$editTime $title CANDIDATES: $candidates\n" );
+ continue;
+ }
+ }
+ $params['diff'] = $diff;
+ $params['title'] = $title;
+ $params['diffStartLine'] = $diffStartLine;
+ call_user_func( $callback, $params );
+ }
+ echo "\n";
+
+ if ( !feof( $diffFile ) ) {
+ echo "Stopped at line $lineNum\n";
+ }
+ return true;
+ }
+
+ function reconcileCurrentRevs() {
+ foreach ( $this->textCache as $title => $text ) {
+ $fileName = "{$this->dataDir}/page/";
+ if ( preg_match( '/^[A-Z]/', $title, $m ) ) {
+ $fileName .= $m[0];
+ } else {
+ $fileName .= 'other';
+ }
+ $fileName .= "/$title.db";
+
+ if ( !file_exists( $fileName ) ) {
+ $this->printLatin1( "ERROR: Cannot find page file for {$title}\n" );
+ continue;
+ }
+
+ $fileContents = file_get_contents( $fileName );
+ $page = $this->unserializeUseMod( $fileContents, $this->FS1 );
+ $section = $this->unserializeUseMod( $page['text_default'], $this->FS2 );
+ $data = $this->unserializeUseMod( $section['data'], $this->FS3 );
+ $pageText = $data['text'];
+ if ( $text !== $pageText ) {
+ $substs = $this->resolveTextChange( $text, $pageText );
+ if ( is_array( $substs ) ) {
+ foreach ( $substs as $source => $dest ) {
+ if ( isset( $this->moveLog[$dest] ) ) {
+ $this->printLatin1( "ERROR: need deep rename: $source\n" );
+ } else {
+ $this->printLatin1( "ERROR: need substitute: $source -> $dest\n" );
+ }
+ }
+ } else {
+ $this->printLatin1( "ERROR: unresolved diff in $title:\n" );
+ wfSuppressWarnings();
+ $diff = xdiff_string_diff( $text, $pageText ) . '';
+ wfRestoreWarnings();
+ $this->printLatin1( "$diff\n" );
+ }
+ }
+ }
+ }
+
+ function makeTitle( $titleText ) {
+ return Title::newFromText( $this->encode( $titleText ) );
+ }
+
+ function getText( $titleText ) {
+ if ( !isset( $this->textCache[$titleText] ) ) {
+ return "Describe the new page here.\n";
+ } else {
+ return $this->textCache[$titleText];
+ }
+ }
+
+ function saveRevision( $params ) {
+ $this->textCache[$params['rctitle']] = $params['text'];
+
+ $out = "\n" .
+ $this->element( 'title', $params['rctitle'] ) .
+ "\n" .
+ $this->element( 'id', $this->revId ++ ) .
+ $this->element( 'timestamp', wfTimestamp( TS_ISO_8601, $params['timestamp'] ) ) .
+ "\n";
+ if ( isset( $params['extra']['name'] ) ) {
+ $out .= $this->element( 'username', $params['extra']['name'] );
+ }
+ if ( isset( $params['extra']['id'] ) ) {
+ $out .= $this->element( 'id', $params['extra']['id'] );
+ }
+ if ( isset( $params['host'] ) ) {
+ $out .= $this->element( 'ip', $params['host'] );
+ }
+ $out .=
+ "\n" .
+ $this->element( 'comment', $params['summary'] ) .
+ "" .
+ htmlspecialchars( $this->encode( $params['text'] ) ) .
+ "\n" .
+ "\n" .
+ "\n";
+ fwrite( $this->outFile, $out );
+ }
+
+ function renameTextLinks( $old, $new, $timestamp ) {
+ $newWithUnderscores = $new;
+ $old = str_replace( '_', ' ', $old );
+ $new = str_replace( '_', ' ', $new );
+
+ foreach ( $this->textCache as $title => $oldText ) {
+ if ( $newWithUnderscores === $title
+ && in_array( $title, $this->skipSelfSubstitution ) )
+ {
+ // Hack to make Pythagorean_Theorem etc. work
+ continue;
+ }
+
+ $newText = $this->substituteTextLinks( $old, $new, $oldText );
+ if ( $oldText !== $newText ) {
+ $this->saveRevision( array(
+ 'rctitle' => $title,
+ 'timestamp' => $timestamp,
+ 'text' => $newText,
+ 'extra' => array( 'name' => 'Page move link fixup script' ),
+ 'summary' => '',
+ 'minor' => true
+ ) );
+ }
+ }
+ }
+
+ function substituteTextLinks( $old, $new, $text ) {
+ $this->saveUrl = array();
+ $this->old = $old;
+ $this->new = $new;
+
+ $text = str_replace( $this->FS, '', $text ); # Remove separators (paranoia)
+ $text = preg_replace_callback( '/((.*?)<\/pre>)/is',
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( '/((.*?)<\/code>)/is',
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( '/((.*?)<\/nowiki>)/s',
+ array( $this, 'storeRaw' ), $text );
+
+ $text = preg_replace_callback( "/\[\[{$this->FreeLinkPattern}\|([^\]]+)\]\]/",
+ array( $this, 'subFreeLink' ), $text );
+ $text = preg_replace_callback( "/\[\[{$this->FreeLinkPattern}\]\]/",
+ array( $this, 'subFreeLink' ), $text );
+ $text = preg_replace_callback( "/(\[{$this->UrlPattern}\s+([^\]]+?)\])/",
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( "/(\[{$this->InterLinkPattern}\s+([^\]]+?)\])/",
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( "/(\[?{$this->UrlPattern}\]?)/",
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( "/(\[?{$this->InterLinkPattern}\]?)/",
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( "/{$this->LinkPattern}/",
+ array( $this, 'subWikiLink' ), $text );
+
+ $text = preg_replace_callback( "/{$this->FS}(\d+){$this->FS}/",
+ array( $this, 'restoreRaw' ), $text ); # Restore saved text
+ return $text;
+ }
+
+ function getLinkList( $text ) {
+ $this->saveUrl = array();
+ $this->linkList = array();
+
+ $text = str_replace( $this->FS, '', $text ); # Remove separators (paranoia)
+ $text = preg_replace_callback( '/((.*?)<\/pre>)/is',
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( '/((.*?)<\/code>)/is',
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( '/((.*?)<\/nowiki>)/s',
+ array( $this, 'storeRaw' ), $text );
+
+ $text = preg_replace_callback( "/\[\[{$this->FreeLinkPattern}\|([^\]]+)\]\]/",
+ array( $this, 'storeLink' ), $text );
+ $text = preg_replace_callback( "/\[\[{$this->FreeLinkPattern}\]\]/",
+ array( $this, 'storeLink' ), $text );
+ $text = preg_replace_callback( "/(\[{$this->UrlPattern}\s+([^\]]+?)\])/",
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( "/(\[{$this->InterLinkPattern}\s+([^\]]+?)\])/",
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( "/(\[?{$this->UrlPattern}\]?)/",
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( "/(\[?{$this->InterLinkPattern}\]?)/",
+ array( $this, 'storeRaw' ), $text );
+ $text = preg_replace_callback( "/{$this->LinkPattern}/",
+ array( $this, 'storeLink' ), $text );
+
+ return $this->linkList;
+ }
+
+ function storeRaw( $m ) {
+ $this->saveUrl[] = $m[1];
+ return $this->FS . (count( $this->saveUrl ) - 1) . $this->FS;
+ }
+
+ function subFreeLink( $m ) {
+ $link = $m[1];
+ if ( isset( $m[2] ) ) {
+ $name = $m[2];
+ } else {
+ $name = '';
+ }
+ $oldlink = $link;
+ $link = preg_replace( '/^\s+/', '', $link );
+ $link = preg_replace( '/\s+$/', '', $link );
+ if ( $link == $this->old ) {
+ $link = $this->new;
+ } else {
+ $link = $oldlink; # Preserve spaces if no match
+ }
+ $link = "[[$link";
+ if ( $name !== "" ) {
+ $link .= "|$name";
+ }
+ $link .= "]]";
+ return $this->storeRaw( array( 1 => $link ) );
+ }
+
+ function subWikiLink( $m ) {
+ $link = $m[1];
+ if ( $link == $this->old ) {
+ $link = $this->new;
+ if ( !preg_match( "/^{$this->LinkPattern}$/", $this->new ) ) {
+ $link = "[[$link]]";
+ }
+ }
+ return $this->storeRaw( array( 1 => $link ) );
+ }
+
+ function restoreRaw( $m ) {
+ return $this->saveUrl[$m[1]];
+ }
+
+ function storeLink( $m ) {
+ $this->linkList[] = $m[1];
+ return $this->storeRaw( $m );
+ }
+
+ function encode( $s ) {
+ return strtr( $s, $this->encodeMap );
+ }
+
+ function decode( $s ) {
+ return strtr( $s, $this->decodeMap );
+ }
+
+ function printLatin1( $s ) {
+ echo $this->encode( $s );
+ }
+
+ function unserializeUseMod( $s, $sep ) {
+ $parts = explode( $sep, $s );
+ $result = array();
+ for ( $i = 0; $i < count( $parts ); $i += 2 ) {
+ $result[$parts[$i]] = $parts[$i+1];
+ }
+ return $result;
+ }
+}
+
+$maintClass = 'ImportUseModWikipedia';
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/maintenance/initEditCount.php b/maintenance/initEditCount.php
index e421e29b..0f136450 100644
--- a/maintenance/initEditCount.php
+++ b/maintenance/initEditCount.php
@@ -93,7 +93,7 @@ in the load balancer, usually indicating a replication environment.' );
$delta,
$rate ) );
- wfWaitForSlaves( 10 );
+ wfWaitForSlaves();
}
} else {
// Subselect should work on modern MySQLs etc
diff --git a/maintenance/initStats.php b/maintenance/initStats.php
index 1ec5c925..eab9c8df 100644
--- a/maintenance/initStats.php
+++ b/maintenance/initStats.php
@@ -63,7 +63,7 @@ class InitStats extends Maintenance {
if ( $this->hasOption( 'active' ) ) {
$this->output( "Counting active users..." );
- $active = SiteStatsUpdate::cacheUpdate();
+ $active = SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) );
$this->output( "{$active}\n" );
}
diff --git a/maintenance/install-utils.inc b/maintenance/install-utils.inc
deleted file mode 100644
index 93ca0b58..00000000
--- a/maintenance/install-utils.inc
+++ /dev/null
@@ -1,31 +0,0 @@
-sourceFile( $fname );
- if ( $error !== true ) {
- print $error;
- exit( 1 );
- }
-}
-
-/**
- * @deprecated Use DatabaseBase::patchPath(). Will probably be removed in 1.18
- */
-function archive( $name ) {
- wfDeprecated( __METHOD__ );
- $dbr = wfGetDB( DB_SLAVE );
- return $dbr->patchPath( $name );
-}
diff --git a/maintenance/install.php b/maintenance/install.php
index 34e3ea85..dba43400 100644
--- a/maintenance/install.php
+++ b/maintenance/install.php
@@ -40,7 +40,7 @@ class CommandLineInstaller extends Maintenance {
$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( '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 );
@@ -55,6 +55,7 @@ class CommandLineInstaller extends Maintenance {
$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( 'dbpassfile', 'An alternative way to provide dbpass option, as the contents of this file', false, true );
$this->addOption( 'confpath', "Path to write LocalSettings.php to, default $IP", false, true );
/* $this->addOption( 'dbschema', 'The schema for the MediaWiki DB in pg (mediawiki)', false, true ); */
/* $this->addOption( 'namespace', 'The project namespace (same as the name)', false, true ); */
@@ -67,6 +68,17 @@ class CommandLineInstaller extends Maintenance {
$adminName = isset( $this->mArgs[1] ) ? $this->mArgs[1] : null;
$wgTitle = Title::newFromText( 'Installer script' );
+ $dbpassfile = $this->getOption( 'dbpassfile', false );
+ if ( $dbpassfile !== false ) {
+ wfSuppressWarnings();
+ $dbpass = file_get_contents( $dbpassfile );
+ wfRestoreWarnings();
+ if ( $dbpass === false ) {
+ $this->error( "Couldn't open $dbpassfile", true );
+ }
+ $this->mOptions['dbpass'] = trim( $dbpass, "\r\n" );
+ }
+
$installer =
new CliInstaller( $siteName, $adminName, $this->mOptions );
diff --git a/maintenance/jsparse.php b/maintenance/jsparse.php
new file mode 100644
index 00000000..ae6f1f1d
--- /dev/null
+++ b/maintenance/jsparse.php
@@ -0,0 +1,72 @@
+mDescription = "Runs parsing/syntax checks on JavaScript files";
+ $this->addArg( 'file(s)', 'JavaScript file to test', false );
+ }
+
+ public function execute() {
+ $iterations = $this->getOption( 'i', 100 );
+ if ( $this->hasArg() ) {
+ $files = $this->mArgs;
+ } else {
+ $this->maybeHelp( true ); // @fixme this is a lame API :)
+ exit( 1 ); // it should exit from the above first...
+ }
+
+ $parser = new JSParser();
+ foreach ( $files as $filename ) {
+ wfSuppressWarnings();
+ $js = file_get_contents( $filename );
+ wfRestoreWarnings();
+ if ($js === false) {
+ $this->output( "$filename ERROR: could not read file\n" );
+ $this->errs++;
+ continue;
+ }
+
+ try {
+ $parser->parse( $js, $filename, 1 );
+ } catch (Exception $e) {
+ $this->errs++;
+ $this->output( "$filename ERROR: " . $e->getMessage() . "\n" );
+ continue;
+ }
+
+ $this->output( "$filename OK\n" );
+ }
+
+ if ($this->errs > 0) {
+ exit(1);
+ }
+ }
+}
+
+$maintClass = "JSParseHelper";
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/maintenance/lag.php b/maintenance/lag.php
index fdc74293..dc8bff5f 100644
--- a/maintenance/lag.php
+++ b/maintenance/lag.php
@@ -1,8 +1,23 @@
@@ -31,10 +46,20 @@ class statsOutput {
/** Outputs WikiText */
class wikiStatsOutput extends statsOutput {
function heading() {
+ global $wgDummyLanguageCodes, $wgContLang;
$version = SpecialVersion::getVersion( 'nodb' );
echo "'''Statistics are based on:''' " . $version . "
\n\n";
echo "'''Note:''' These statistics can be generated by running php maintenance/language/transstat.php
.\n\n";
echo "For additional information on specific languages (the message names, the actual problems, etc.), run php maintenance/language/checkLanguage.php --lang=foo
.\n\n";
+ echo 'English (en) is excluded because it is the default localization';
+ if( is_array( $wgDummyLanguageCodes ) ) {
+ $dummyCodes = array();
+ foreach( $wgDummyLanguageCodes as $dummyCode ) {
+ $dummyCodes[] = $wgContLang->getLanguageName( $dummyCode ) . ' (' . $dummyCode . ')';
+ }
+ echo ', as well as the following languages that are not intended for system message translations, usually because they redirect to other language codes: ' . implode( ', ', $dummyCodes );
+ }
+ echo ".\n\n"; # dot to end sentence
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() {
diff --git a/maintenance/language/checkDupeMessages.php b/maintenance/language/checkDupeMessages.php
index ea9d5fb7..ea5b1870 100644
--- a/maintenance/language/checkDupeMessages.php
+++ b/maintenance/language/checkDupeMessages.php
@@ -1,6 +1,22 @@
'',\n" );
- } else if ( !strcmp( $runMode, 'wiki' ) ) {
+ } elseif ( !strcmp( $runMode, 'wiki' ) ) {
$uKey = ucfirst( $key );
print( "* MediaWiki:$uKey/$langCode\n" );
} else {
diff --git a/maintenance/language/checkExtensions.php b/maintenance/language/checkExtensions.php
index c05cf193..a58a8f5c 100644
--- a/maintenance/language/checkExtensions.php
+++ b/maintenance/language/checkExtensions.php
@@ -2,6 +2,21 @@
/**
* Check the extensions language files.
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup MaintenanceLanguage
*/
diff --git a/maintenance/language/checkLanguage.inc b/maintenance/language/checkLanguage.inc
index d8480c0f..1e4b94a2 100644
--- a/maintenance/language/checkLanguage.inc
+++ b/maintenance/language/checkLanguage.inc
@@ -1,4 +1,25 @@
groups as $weight => $group ) {
if ( preg_match( '/(\.[0-9A-F]*)\./', $weight, $m ) ) {
if ( isset( $this->groups[$m[1]] ) ) {
@@ -377,5 +380,5 @@ class UcdXmlReader {
}
$maintClass = 'GenerateCollationData';
-require_once( DO_MAINTENANCE );
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/maintenance/language/generateNormalizerData.php b/maintenance/language/generateNormalizerData.php
index cb9910f3..a958abf1 100644
--- a/maintenance/language/generateNormalizerData.php
+++ b/maintenance/language/generateNormalizerData.php
@@ -1,4 +1,25 @@
mRawMessages[$code] ) &&
@@ -190,7 +205,7 @@ class languages {
*
* @param $code The language code.
*
- * @return The messages in this language.
+ * @return string The messages in this language.
*/
public function getMessages( $code ) {
$this->loadMessages( $code );
diff --git a/maintenance/language/messageTypes.inc b/maintenance/language/messageTypes.inc
index 9baf7e76..62374500 100644
--- a/maintenance/language/messageTypes.inc
+++ b/maintenance/language/messageTypes.inc
@@ -2,6 +2,21 @@
/**
* Several types of messages.
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup MaintenanceLanguage
*/
@@ -86,9 +101,11 @@ $wgIgnoredMessages = array(
'exif-make-value',
'exif-model-value',
'exif-software-value',
+ 'exif-software-version-value',
'history_copyright',
'licenses',
'loginstart',
+ 'loginend-https',
'loginend',
'loginlanguagelinks',
'pear-mail-error',
@@ -96,6 +113,7 @@ $wgIgnoredMessages = array(
'markaspatrolledlink',
'newarticletextanon',
'newsectionheaderdefaultlevel',
+ 'mainpage-nstab',
'newtalkseparator',
'noarticletextanon',
'number_of_watching_users_RCview',
@@ -111,6 +129,7 @@ $wgIgnoredMessages = array(
'signature-anon',
'signupstart',
'signupend',
+ 'signupend-https',
'sitenotice',
'sitesubtitle',
'sitetitle',
@@ -125,7 +144,7 @@ $wgIgnoredMessages = array(
'allpages-summary',
'booksources-summary',
'categories-summary',
- 'ipblocklist-summary',
+ 'blocklist-summary',
'protectedtitles-summary',
'listusers-summary',
'longpages-summary',
@@ -183,7 +202,6 @@ $wgOptionalMessages = array(
'userrights-irreversible-marker',
'tog-nolangconversion',
'tog-noconvertlink',
- 'yourvariant',
'variantname-zh-hans',
'variantname-zh-hant',
'variantname-zh-cn',
@@ -212,6 +230,9 @@ $wgOptionalMessages = array(
'variantname-tg-cyrl',
'variantname-tg-latn',
'variantname-tg',
+ 'variantname-ike-cans',
+ 'variantname-ike-latn',
+ 'variantname-iu',
'rc-change-size',
'resetpass_text',
'image_sample',
@@ -237,6 +258,11 @@ $wgOptionalMessages = array(
'vector.css',
'print.css',
'handheld.css',
+ 'noscript.css',
+ 'group-autoconfirmed.css',
+ 'group-bot.css',
+ 'group-sysop.css',
+ 'group-bureaucrat.css',
'common.js',
'standard.js',
'nostalgia.js',
@@ -247,26 +273,39 @@ $wgOptionalMessages = array(
'simple.js',
'modern.js',
'vector.js',
+ 'group-autoconfirmed.js',
+ 'group-bot.js',
+ 'group-sysop.js',
+ 'group-bureaucrat.js',
'widthheight',
'exif-fnumber-format',
'exif-focallength-format',
+ 'exif-compression-5',
'exif-compression-6',
+ 'exif-compression-7',
+ 'exif-compression-8',
+ 'exif-compression-32773',
+ 'exif-compression-32946',
+ 'exif-compression-34712',
'exif-photometricinterpretation-2',
'exif-photometricinterpretation-6',
'exif-xyresolution-i',
'exif-xyresolution-c',
'exif-colorspace-1',
- 'exif-colorspace-ffff.h',
'exif-componentsconfiguration-1',
'exif-componentsconfiguration-2',
'exif-componentsconfiguration-3',
'exif-componentsconfiguration-4',
'exif-componentsconfiguration-5',
'exif-componentsconfiguration-6',
+ 'exif-contact-value',
+ 'exif-coordinate-format',
'exif-lightsource-20',
'exif-lightsource-21',
'exif-lightsource-22',
'exif-lightsource-23',
+ 'exif-maxaperturevalue-value',
+ 'exif-subjectnewscode-value',
'booksources-isbn',
'sp-contributions-explain',
'sorbs',
@@ -274,14 +313,15 @@ $wgOptionalMessages = array(
'seconds-abbrev',
'minutes-abbrev',
'hours-abbrev',
+ 'days-abbrev',
'filerevert-backlink',
'filedelete-backlink',
'delete-backlink',
- 'move-page-backlink',
'protect-backlink',
'pagetitle',
'filename-prefix-blacklist',
'edittools',
+ 'edittools-upload',
'size-bytes',
'size-kilobytes',
'size-megabytes',
@@ -338,8 +378,8 @@ $wgOptionalMessages = array(
'hebrew-calendar-m10-gen',
'hebrew-calendar-m11-gen',
'hebrew-calendar-m12-gen',
+ 'version-api',
'version-svn-revision',
- 'catseparator',
'semicolon-separator',
'comma-separator',
'colon-separator',
@@ -364,6 +404,9 @@ $wgOptionalMessages = array(
'shared-repo-name-wikimediacommons',
'usermessage-template',
'filepage.css',
+ 'metadata-langitem',
+ 'metadata-langitem-default',
+ 'nocookiesforlogin',
);
/** EXIF messages, which may be set as optional in several checks, but are generally mandatory */
@@ -380,13 +423,11 @@ $wgEXIFMessages = array(
'exif-ycbcrpositioning',
'exif-xresolution',
'exif-yresolution',
- 'exif-resolutionunit',
'exif-stripoffsets',
'exif-rowsperstrip',
'exif-stripbytecounts',
'exif-jpeginterchangeformat',
'exif-jpeginterchangeformatlength',
- 'exif-transferfunction',
'exif-whitepoint',
'exif-primarychromaticities',
'exif-ycbcrcoefficients',
@@ -405,7 +446,6 @@ $wgEXIFMessages = array(
'exif-compressedbitsperpixel',
'exif-pixelydimension',
'exif-pixelxdimension',
- 'exif-makernote',
'exif-usercomment',
'exif-relatedsoundfile',
'exif-datetimeoriginal',
@@ -416,10 +456,10 @@ $wgEXIFMessages = array(
'exif-exposuretime',
'exif-exposuretime-format',
'exif-fnumber',
+ 'exif-fnumber-format',
'exif-exposureprogram',
'exif-spectralsensitivity',
'exif-isospeedratings',
- 'exif-oecf',
'exif-shutterspeedvalue',
'exif-aperturevalue',
'exif-brightnessvalue',
@@ -430,9 +470,9 @@ $wgEXIFMessages = array(
'exif-lightsource',
'exif-flash',
'exif-focallength',
+ 'exif-focallength-format',
'exif-subjectarea',
'exif-flashenergy',
- 'exif-spatialfrequencyresponse',
'exif-focalplanexresolution',
'exif-focalplaneyresolution',
'exif-focalplaneresolutionunit',
@@ -441,7 +481,6 @@ $wgEXIFMessages = array(
'exif-sensingmethod',
'exif-filesource',
'exif-scenetype',
- 'exif-cfapattern',
'exif-customrendered',
'exif-exposuremode',
'exif-whitebalance',
@@ -486,7 +525,92 @@ $wgEXIFMessages = array(
'exif-gpsareainformation',
'exif-gpsdatestamp',
'exif-gpsdifferential',
+ 'exif-coordinate-format',
+ 'exif-jpegfilecomment',
+ 'exif-keywords',
+ 'exif-worldregioncreated',
+ 'exif-countrycreated',
+ 'exif-countrycodecreated',
+ 'exif-provinceorstatecreated',
+ 'exif-citycreated',
+ 'exif-sublocationcreated',
+ 'exif-worldregiondest',
+ 'exif-countrydest',
+ 'exif-countrycodedest',
+ 'exif-provinceorstatedest',
+ 'exif-citydest',
+ 'exif-sublocationdest',
+ 'exif-objectname',
+ 'exif-specialinstructions',
+ 'exif-headline',
+ 'exif-credit',
+ 'exif-source',
+ 'exif-editstatus',
+ 'exif-urgency',
+ 'exif-fixtureidentifier',
+ 'exif-locationdest',
+ 'exif-locationdestcode',
+ 'exif-objectcycle',
+ 'exif-contact',
+ 'exif-writer',
+ 'exif-languagecode',
+ 'exif-iimversion',
+ 'exif-iimcategory',
+ 'exif-iimsupplementalcategory',
+ 'exif-datetimeexpires',
+ 'exif-datetimereleased',
+ 'exif-originaltransmissionref',
+ 'exif-identifier',
+ 'exif-lens',
+ 'exif-serialnumber',
+ 'exif-cameraownername',
+ 'exif-label',
+ 'exif-datetimemetadata',
+ 'exif-nickname',
+ 'exif-rating',
+ 'exif-rightscertificate',
+ 'exif-copyrighted',
+ 'exif-copyrightowner',
+ 'exif-usageterms',
+ 'exif-webstatement',
+ 'exif-originaldocumentid',
+ 'exif-licenseurl',
+ 'exif-morepermissionsurl',
+ 'exif-attributionurl',
+ 'exif-preferredattributionname',
+ 'exif-pngfilecomment',
+ 'exif-disclaimer',
+ 'exif-contentwarning',
+ 'exif-giffilecomment',
+ 'exif-intellectualgenre',
+ 'exif-subjectnewscode',
+ 'exif-scenecode',
+ 'exif-event',
+ 'exif-organisationinimage',
+ 'exif-personinimage',
+ 'exif-originalimageheight',
+ 'exif-originalimagewidth',
+ 'exif-make-value',
+ 'exif-model-value',
+ 'exif-software-value',
+ 'exif-software-version-value',
+ 'exif-contact-value',
+ 'exif-subjectnewscode-value',
'exif-compression-1',
+ 'exif-compression-2',
+ 'exif-compression-3',
+ 'exif-compression-4',
+ 'exif-compression-5',
+ 'exif-compression-6',
+ 'exif-compression-7',
+ 'exif-compression-8',
+ 'exif-compression-32773',
+ 'exif-compression-32946',
+ 'exif-compression-34712',
+ 'exif-copyrighted-true',
+ 'exif-copyrighted-false',
+ 'exif-photometricinterpretation-2',
+ 'exif-photometricinterpretation-6',
'exif-unknowndate',
'exif-orientation-1',
'exif-orientation-2',
@@ -498,7 +622,17 @@ $wgEXIFMessages = array(
'exif-orientation-8',
'exif-planarconfiguration-1',
'exif-planarconfiguration-2',
+ 'exif-xyresolution-i',
+ 'exif-xyresolution-c',
+ 'exif-colorspace-1',
+ 'exif-colorspace-65535',
'exif-componentsconfiguration-0',
+ 'exif-componentsconfiguration-1',
+ 'exif-componentsconfiguration-2',
+ 'exif-componentsconfiguration-3',
+ 'exif-componentsconfiguration-4',
+ 'exif-componentsconfiguration-5',
+ 'exif-componentsconfiguration-6',
'exif-exposureprogram-0',
'exif-exposureprogram-1',
'exif-exposureprogram-2',
@@ -532,18 +666,22 @@ $wgEXIFMessages = array(
'exif-lightsource-17',
'exif-lightsource-18',
'exif-lightsource-19',
+ 'exif-lightsource-20',
+ 'exif-lightsource-21',
+ 'exif-lightsource-22',
+ 'exif-lightsource-23',
'exif-lightsource-24',
'exif-lightsource-255',
- 'exif-flash-fired-0' ,
- 'exif-flash-fired-1' ,
- 'exif-flash-return-0' ,
- 'exif-flash-return-2' ,
- 'exif-flash-return-3' ,
- 'exif-flash-mode-1' ,
- 'exif-flash-mode-2' ,
- 'exif-flash-mode-3' ,
- 'exif-flash-function-1' ,
- 'exif-flash-redeye-1' ,
+ 'exif-flash-fired-0',
+ 'exif-flash-fired-1',
+ 'exif-flash-return-0',
+ 'exif-flash-return-2',
+ 'exif-flash-return-3',
+ 'exif-flash-mode-1',
+ 'exif-flash-mode-2',
+ 'exif-flash-mode-3',
+ 'exif-flash-function-1',
+ 'exif-flash-redeye-1',
'exif-focalplaneresolutionunit-2',
'exif-sensingmethod-1',
'exif-sensingmethod-2',
@@ -552,6 +690,7 @@ $wgEXIFMessages = array(
'exif-sensingmethod-5',
'exif-sensingmethod-7',
'exif-sensingmethod-8',
+ 'exif-filesource-3',
'exif-scenetype-1',
'exif-customrendered-0',
'exif-customrendered-1',
@@ -586,8 +725,8 @@ $wgEXIFMessages = array(
'exif-gpslatitude-s',
'exif-gpslongitude-e',
'exif-gpslongitude-w',
- 'exif-gpsaltitude-0',
- 'exif-gpsaltitude-1',
+ 'exif-gpsaltitude-above-sealevel',
+ 'exif-gpsaltitude-below-sealevel',
'exif-gpsstatus-a',
'exif-gpsstatus-v',
'exif-gpsmeasuremode-2',
@@ -598,7 +737,48 @@ $wgEXIFMessages = array(
'exif-gpsdestdistance-k',
'exif-gpsdestdistance-m',
'exif-gpsdestdistance-n',
+ 'exif-gpsdop-excellent',
+ 'exif-gpsdop-good',
+ 'exif-gpsdop-moderate',
+ 'exif-gpsdop-fair',
+ 'exif-gpsdop-poor',
+ 'exif-objectcycle-a',
+ 'exif-objectcycle-p',
+ 'exif-objectcycle-b',
'exif-gpsdirection-t',
'exif-gpsdirection-m',
- 'exif-objectname',
+ 'exif-ycbcrpositioning-1',
+ 'exif-ycbcrpositioning-2',
+ 'exif-dc-contributor',
+ 'exif-dc-coverage',
+ 'exif-dc-date',
+ 'exif-dc-publisher',
+ 'exif-dc-relation',
+ 'exif-dc-rights',
+ 'exif-dc-source',
+ 'exif-dc-type',
+ 'exif-rating-rejected',
+ 'exif-isospeedratings-overflow',
+ 'exif-maxaperturevalue-value',
+ 'exif-iimcategory-ace',
+ 'exif-iimcategory-clj',
+ 'exif-iimcategory-dis',
+ 'exif-iimcategory-fin',
+ 'exif-iimcategory-edu',
+ 'exif-iimcategory-evn',
+ 'exif-iimcategory-hth',
+ 'exif-iimcategory-hum',
+ 'exif-iimcategory-lab',
+ 'exif-iimcategory-lif',
+ 'exif-iimcategory-pol',
+ 'exif-iimcategory-rel',
+ 'exif-iimcategory-sci',
+ 'exif-iimcategory-soi',
+ 'exif-iimcategory-spo',
+ 'exif-iimcategory-war',
+ 'exif-iimcategory-wea',
+ 'exif-urgency-normal',
+ 'exif-urgency-low',
+ 'exif-urgency-high',
+ 'exif-urgency-other',
);
diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc
index a0b19ac6..d707cf5d 100644
--- a/maintenance/language/messages.inc
+++ b/maintenance/language/messages.inc
@@ -2,6 +2,21 @@
/**
* Define the messages structure in the messages file, for an automated rewriting.
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup MaintenanceLanguage
*/
@@ -142,11 +157,10 @@ $wgMessageStructure = array(
'listingcontinuesabbrev',
'index-category',
'noindex-category',
+ 'broken-file-category',
),
'mainpage' => array(
'linkprefix',
- 'mainpagetext',
- 'mainpagedocfooter',
),
'miscellaneous1' => array(
'about',
@@ -202,10 +216,10 @@ $wgMessageStructure = array(
'history',
'history_short',
'updatedmarker',
- 'info_short',
'printableversion',
'permalink',
'print',
+ 'view',
'edit',
'create',
'editthispage',
@@ -213,6 +227,7 @@ $wgMessageStructure = array(
'delete',
'deletethispage',
'undelete_short',
+ 'viewdeleted_short',
'protect',
'protect_change',
'protectthispage',
@@ -302,6 +317,8 @@ $wgMessageStructure = array(
'toc',
'showtoc',
'hidetoc',
+ 'collapsible-collapse',
+ 'collapsible-expand',
'thisisdeleted',
'viewdeleted',
'restorelink',
@@ -318,6 +335,9 @@ $wgMessageStructure = array(
'anonnotice',
'newsectionheaderdefaultlevel',
'red-link-title',
+ 'sort-descending',
+ 'sort-ascending',
+
),
'nstab' => array(
'nstab-main',
@@ -330,6 +350,7 @@ $wgMessageStructure = array(
'nstab-template',
'nstab-help',
'nstab-category',
+ 'mainpage-nstab',
),
'main' => array(
'nosuchaction',
@@ -381,7 +402,8 @@ $wgMessageStructure = array(
'sqlhidden',
'cascadeprotected',
'namespaceprotected',
- 'customcssjsprotected',
+ 'customcssprotected',
+ 'customjsprotected',
'ns-specialprotected',
'titleprotected',
),
@@ -413,6 +435,7 @@ $wgMessageStructure = array(
'createaccount',
'gotaccount',
'gotaccountlink',
+ 'userlogin-resetlink',
'createaccountmail',
'createaccountreason',
'badretype',
@@ -421,6 +444,8 @@ $wgMessageStructure = array(
'createaccounterror',
'nocookiesnew',
'nocookieslogin',
+ 'nocookiesfornew',
+ 'nocookiesforlogin',
'noname',
'loginsuccesstitle',
'loginsuccess',
@@ -444,8 +469,10 @@ $wgMessageStructure = array(
'throttled-mailpassword',
'loginstart',
'loginend',
+ 'loginend-https',
'signupstart',
'signupend',
+ 'signupend-https',
'mailerror',
'acct_creation_throttle_hit',
'emailauthenticated',
@@ -459,6 +486,7 @@ $wgMessageStructure = array(
'createaccount-text',
'usernamehasherror',
'login-throttled',
+ 'login-abort-generic',
'loginlanguagelabel',
'loginlanguagelinks',
'suspicious-userlogout',
@@ -485,6 +513,21 @@ $wgMessageStructure = array(
'resetpass-wrong-oldpass',
'resetpass-temp-password',
),
+ 'passwordreset' => array(
+ 'passwordreset',
+ 'passwordreset-text',
+ 'passwordreset-legend',
+ 'passwordreset-disabled',
+ 'passwordreset-pretext',
+ 'passwordreset-username',
+ 'passwordreset-domain',
+ 'passwordreset-email',
+ 'passwordreset-emailtitle',
+ 'passwordreset-emailtext-ip',
+ 'passwordreset-emailtext-user',
+ 'passwordreset-emailelement',
+ 'passwordreset-emailsent',
+ ),
'toolbar' => array(
'bold_sample',
'bold_tip',
@@ -496,8 +539,6 @@ $wgMessageStructure = array(
'extlink_tip',
'headline_sample',
'headline_tip',
- 'math_sample',
- 'math_tip',
'nowiki_sample',
'nowiki_tip',
'image_sample',
@@ -566,6 +607,7 @@ $wgMessageStructure = array(
'session_fail_preview',
'session_fail_preview_html',
'token_suffix_mismatch',
+ 'edit_form_incomplete',
'editing',
'editingsection',
'editingcomment',
@@ -593,6 +635,7 @@ $wgMessageStructure = array(
'template-semiprotected',
'hiddencategories',
'edittools',
+ 'edittools-upload',
'nocreatetitle',
'nocreatetext',
'nocreate-loggedin',
@@ -869,6 +912,7 @@ $wgMessageStructure = array(
'qbsettings-fixedright',
'qbsettings-floatingleft',
'qbsettings-floatingright',
+ 'qbsettings-directionality',
),
'preferences' => array(
'preferences',
@@ -880,9 +924,10 @@ $wgMessageStructure = array(
'changepassword',
'prefs-skin',
'skin-preview',
- 'prefs-math',
'datedefault',
+ 'prefs-beta',
'prefs-datetime',
+ 'prefs-labs',
'prefs-personal',
'prefs-rc',
'prefs-watchlist',
@@ -904,8 +949,6 @@ $wgMessageStructure = array(
'columns',
'searchresultshead',
'resultsperpage',
- 'contextlines',
- 'contextchars',
'stub-threshold',
'stub-threshold-disabled',
'recentchangesdays',
@@ -965,8 +1008,12 @@ $wgMessageStructure = array(
'prefs-help-gender',
'email',
'prefs-help-realname',
+
+ # 3 messages depending upon wgEmailConfirmToEdit and $wgEnableUserEmail
'prefs-help-email',
+ 'prefs-help-email-others',
'prefs-help-email-required',
+
'prefs-info',
'prefs-i18n',
'prefs-signature',
@@ -1091,7 +1138,6 @@ $wgMessageStructure = array(
'right-userrights',
'right-userrights-interwiki',
'right-siteadmin',
- 'right-reset-passwords',
'right-override-export-depth',
'right-sendemail',
),
@@ -1099,6 +1145,7 @@ $wgMessageStructure = array(
'rightslog',
'rightslogtext',
'rightslogentry',
+ 'rightslogentry-autopromote',
'rightsnone',
),
'action' => array(
@@ -1239,6 +1286,7 @@ $wgMessageStructure = array(
'large-file',
'largefileserver',
'emptyfile',
+ 'windows-nonascii-filename',
'fileexists',
'filepageexists',
'fileexists-extension',
@@ -1260,6 +1308,7 @@ $wgMessageStructure = array(
'php-uploaddisabledtext',
'uploadscripted',
'uploadvirus',
+ 'uploadjava',
'upload-source',
'sourcefilename',
'sourceurl',
@@ -1269,7 +1318,6 @@ $wgMessageStructure = array(
'upload-options',
'watchthisupload',
'filewasdeleted',
- 'upload-wasdeleted',
'filename-bad-prefix',
'filename-prefix-blacklist',
'upload-success-subj',
@@ -1291,6 +1339,23 @@ $wgMessageStructure = array(
'upload-http-error',
),
+ 'zip' => array(
+ 'zip-file-open-error',
+ 'zip-wrong-format',
+ 'zip-bad',
+ 'zip-unsupported'
+ ),
+
+ 'uploadstash' => array(
+ 'uploadstash',
+ 'uploadstash-summary',
+ 'uploadstash-clear',
+ 'uploadstash-nofiles',
+ 'uploadstash-badtoken',
+ 'uploadstash-errclear',
+ 'uploadstash-refresh',
+ ),
+
'img-auth' => array(
'img-auth-accessdenied',
'img-auth-desc',
@@ -1367,7 +1432,7 @@ $wgMessageStructure = array(
'linkstoimage-more',
'nolinkstoimage',
'morelinkstoimage',
- 'redirectstofile',
+ 'linkstoimage-redirect',
'duplicatesoffile',
'sharedupload',
'sharedupload-desc-there',
@@ -1473,6 +1538,7 @@ $wgMessageStructure = array(
'doubleredirects-summary',
'doubleredirectstext',
'double-redirect-fixed-move',
+ 'double-redirect-fixed-maintenance',
'double-redirect-fixer',
),
'brokenredirects' => array(
@@ -1580,6 +1646,7 @@ $wgMessageStructure = array(
'pager-newer-n',
'pager-older-n',
'suppress',
+ 'querypage-disabled',
),
'booksources' => array(
'booksources',
@@ -1703,6 +1770,10 @@ $wgMessageStructure = array(
'noemailtext',
'nowikiemailtitle',
'nowikiemailtext',
+ 'emailnotarget',
+ 'emailtarget',
+ 'emailusername',
+ 'emailusernamesubmit',
'email-legend',
'emailfrom',
'emailto',
@@ -1728,9 +1799,9 @@ $wgMessageStructure = array(
'watchlistanontext',
'watchnologin',
'watchnologintext',
- 'addedwatch',
+ 'addwatch',
'addedwatchtext',
- 'removedwatch',
+ 'removewatch',
'removedwatchtext',
'watch',
'watchthispage',
@@ -1753,6 +1824,7 @@ $wgMessageStructure = array(
'watching' => array(
'watching',
'unwatching',
+ 'watcherrortext',
),
'enotif' => array(
'enotif_mailer',
@@ -1911,6 +1983,9 @@ $wgMessageStructure = array(
'nsform' => array(
'namespace',
'invert',
+ 'tooltip-invert',
+ 'namespace_association',
+ 'tooltip-namespace_association',
'blanknamespace',
),
'contributions' => array(
@@ -1965,17 +2040,19 @@ $wgMessageStructure = array(
'whatlinkshere-filters',
),
'block' => array(
+ 'autoblockid',
+ 'block',
+ 'unblock',
'blockip',
'blockip-title',
'blockip-legend',
'blockiptext',
- 'ipaddress',
'ipadressorusername',
'ipbexpiry',
'ipbreason',
'ipbreasonotherlist',
'ipbreason-dropdown',
- 'ipbanononly',
+ 'ipb-hardblock',
'ipbcreateaccount',
'ipbemailban',
'ipbenableautoblock',
@@ -1986,11 +2063,14 @@ $wgMessageStructure = array(
'ipbotherreason',
'ipbhidename',
'ipbwatchuser',
- 'ipballowusertalk',
+ 'ipb-disableusertalk',
'ipb-change-block',
+ 'ipb-confirm',
'badipaddress',
'blockipsuccesssub',
'blockipsuccesstext',
+ 'ipb-blockingself',
+ 'ipb-confirmhideuser',
'ipb-edit-dropdown',
'ipb-unblock-addr',
'ipb-unblock',
@@ -2000,18 +2080,25 @@ $wgMessageStructure = array(
'unblockiptext',
'ipusubmit',
'unblocked',
+ 'unblocked-range',
'unblocked-id',
+ 'blocklist',
'ipblocklist',
'ipblocklist-legend',
- 'ipblocklist-username',
- 'ipblocklist-sh-userblocks',
- 'ipblocklist-sh-tempblocks',
- 'ipblocklist-sh-addressblocks',
- 'ipblocklist-summary',
+ 'blocklist-userblocks',
+ 'blocklist-tempblocks',
+ 'blocklist-addressblocks',
+ 'blocklist-timestamp',
+ 'blocklist-target',
+ 'blocklist-expiry',
+ 'blocklist-by',
+ 'blocklist-params',
+ 'blocklist-reason',
+ 'blocklist-summary',
'ipblocklist-submit',
'ipblocklist-localblock',
'ipblocklist-otherblocks',
- 'blocklistline',
+
'infiniteblock',
'expiringblock',
'anononlyblock',
@@ -2047,6 +2134,7 @@ $wgMessageStructure = array(
'ipb_already_blocked',
'ipb-needreblock',
'ipb-otherblocks-header',
+ 'unblock-hideuser',
'ipb_cant_unblock',
'ipb_blocked_as_range',
'ip_range_invalid',
@@ -2080,10 +2168,10 @@ $wgMessageStructure = array(
'unlockdbsuccesstext',
'lockfilenotwritable',
'databasenotlocked',
+ 'lockedbyandtime',
),
'movepage' => array(
'move-page',
- 'move-page-backlink',
'move-page-legend',
'movepagetext',
'movepagetext-noredirectfixer',
@@ -2382,6 +2470,11 @@ $wgMessageStructure = array(
'vector.css',
'print.css',
'handheld.css',
+ 'noscript.css',
+ 'group-autoconfirmed.css',
+ 'group-bot.css',
+ 'group-sysop.css',
+ 'group-bureaucrat.css',
),
'scripts' => array(
'common.js',
@@ -2394,10 +2487,12 @@ $wgMessageStructure = array(
'simple.js',
'modern.js',
'vector.js',
+ 'group-autoconfirmed.js',
+ 'group-bot.js',
+ 'group-sysop.js',
+ 'group-bureaucrat.js',
),
'metadata_cc' => array(
- 'nodublincore',
- 'nocreativecommons',
'notacceptable',
),
'attribution' => array(
@@ -2421,12 +2516,17 @@ $wgMessageStructure = array(
'spam_blanking',
),
'info' => array(
- 'infosubtitle',
- 'numedits',
- 'numtalkedits',
- 'numwatchers',
- 'numauthors',
- 'numtalkauthors',
+ 'pageinfo-title',
+ 'pageinfo-header-edits',
+ 'pageinfo-header-watchlist',
+ 'pageinfo-header-views',
+ 'pageinfo-subjectpage',
+ 'pageinfo-talkpage',
+ 'pageinfo-watchers',
+ 'pageinfo-edits',
+ 'pageinfo-authors',
+ 'pageinfo-views',
+ 'pageinfo-viewsperedit',
),
'skin' => array(
'skinname-standard',
@@ -2439,25 +2539,6 @@ $wgMessageStructure = array(
'skinname-modern',
'skinname-vector',
),
- 'math' => array(
- 'mw_math_png',
- 'mw_math_simple',
- 'mw_math_html',
- 'mw_math_source',
- 'mw_math_modern',
- 'mw_math_mathml',
- ),
- 'matherrors' => array(
- 'math_failure',
- 'math_unknown_error',
- 'math_unknown_function',
- 'math_lexing_error',
- 'math_syntax_error',
- 'math_image_error',
- 'math_bad_tmpdir',
- 'math_bad_output',
- 'math_notexvc',
- ),
'patrolling' => array(
'markaspatrolleddiff',
'markaspatrolledlink',
@@ -2499,10 +2580,13 @@ $wgMessageStructure = array(
'widthheightpage',
'file-info',
'file-info-size',
+ 'file-info-size-pages',
'file-nohires',
'svg-long-desc',
'show-big-image',
- 'show-big-image-thumb',
+ 'show-big-image-preview',
+ 'show-big-image-other',
+ 'show-big-image-size',
'file-info-gif-looped',
'file-info-gif-frames',
'file-info-png-looped',
@@ -2526,6 +2610,7 @@ $wgMessageStructure = array(
'seconds-abbrev',
'minutes-abbrev',
'hours-abbrev',
+ 'days-abbrev',
),
'badimagelist' => array(
'bad_image_list',
@@ -2570,12 +2655,19 @@ $wgMessageStructure = array(
'variantname-tg-latn',
'variantname-tg',
),
+ 'variantname-iu' => array(
+ 'variantname-ike-cans',
+ 'variantname-ike-latn',
+ 'variantname-iu',
+ ),
'metadata' => array(
'metadata',
'metadata-help',
'metadata-expand',
'metadata-collapse',
'metadata-fields',
+ 'metadata-langitem',
+ 'metadata-langitem-default',
),
'exif' => array(
'exif-imagewidth',
@@ -2596,7 +2688,6 @@ $wgMessageStructure = array(
'exif-stripbytecounts',
'exif-jpeginterchangeformat',
'exif-jpeginterchangeformatlength',
- 'exif-transferfunction',
'exif-whitepoint',
'exif-primarychromaticities',
'exif-ycbcrcoefficients',
@@ -2615,7 +2706,6 @@ $wgMessageStructure = array(
'exif-compressedbitsperpixel',
'exif-pixelydimension',
'exif-pixelxdimension',
- 'exif-makernote',
'exif-usercomment',
'exif-relatedsoundfile',
'exif-datetimeoriginal',
@@ -2630,7 +2720,6 @@ $wgMessageStructure = array(
'exif-exposureprogram',
'exif-spectralsensitivity',
'exif-isospeedratings',
- 'exif-oecf',
'exif-shutterspeedvalue',
'exif-aperturevalue',
'exif-brightnessvalue',
@@ -2644,7 +2733,6 @@ $wgMessageStructure = array(
'exif-focallength-format',
'exif-subjectarea',
'exif-flashenergy',
- 'exif-spatialfrequencyresponse',
'exif-focalplanexresolution',
'exif-focalplaneyresolution',
'exif-focalplaneresolutionunit',
@@ -2653,7 +2741,6 @@ $wgMessageStructure = array(
'exif-sensingmethod',
'exif-filesource',
'exif-scenetype',
- 'exif-cfapattern',
'exif-customrendered',
'exif-exposuremode',
'exif-whitebalance',
@@ -2698,16 +2785,96 @@ $wgMessageStructure = array(
'exif-gpsareainformation',
'exif-gpsdatestamp',
'exif-gpsdifferential',
+ 'exif-coordinate-format',
+ 'exif-jpegfilecomment',
+ 'exif-keywords',
+ 'exif-worldregioncreated',
+ 'exif-countrycreated',
+ 'exif-countrycodecreated',
+ 'exif-provinceorstatecreated',
+ 'exif-citycreated',
+ 'exif-sublocationcreated',
+ 'exif-worldregiondest',
+ 'exif-countrydest',
+ 'exif-countrycodedest',
+ 'exif-provinceorstatedest',
+ 'exif-citydest',
+ 'exif-sublocationdest',
'exif-objectname',
+ 'exif-specialinstructions',
+ 'exif-headline',
+ 'exif-credit',
+ 'exif-source',
+ 'exif-editstatus',
+ 'exif-urgency',
+ 'exif-fixtureidentifier',
+ 'exif-locationdest',
+ 'exif-locationdestcode',
+ 'exif-objectcycle',
+ 'exif-contact',
+ 'exif-writer',
+ 'exif-languagecode',
+ 'exif-iimversion',
+ 'exif-iimcategory',
+ 'exif-iimsupplementalcategory',
+ 'exif-datetimeexpires',
+ 'exif-datetimereleased',
+ 'exif-originaltransmissionref',
+ 'exif-identifier',
+ 'exif-lens',
+ 'exif-serialnumber',
+ 'exif-cameraownername',
+ 'exif-label',
+ 'exif-datetimemetadata',
+ 'exif-nickname',
+ 'exif-rating',
+ 'exif-rightscertificate',
+ 'exif-copyrighted',
+ 'exif-copyrightowner',
+ 'exif-usageterms',
+ 'exif-webstatement',
+ 'exif-originaldocumentid',
+ 'exif-licenseurl',
+ 'exif-morepermissionsurl',
+ 'exif-attributionurl',
+ 'exif-preferredattributionname',
+ 'exif-pngfilecomment',
+ 'exif-disclaimer',
+ 'exif-contentwarning',
+ 'exif-giffilecomment',
+ 'exif-intellectualgenre',
+ 'exif-subjectnewscode',
+ 'exif-scenecode',
+ 'exif-event',
+ 'exif-organisationinimage',
+ 'exif-personinimage',
+ 'exif-originalimageheight',
+ 'exif-originalimagewidth',
),
'exif-values' => array(
'exif-make-value',
'exif-model-value',
'exif-software-value',
+ 'exif-software-version-value',
+ 'exif-contact-value',
+ 'exif-subjectnewscode-value',
),
'exif-compression' => array(
'exif-compression-1',
+ 'exif-compression-2',
+ 'exif-compression-3',
+ 'exif-compression-4',
+ 'exif-compression-5',
'exif-compression-6',
+ 'exif-compression-7',
+ 'exif-compression-8',
+ 'exif-compression-32773',
+ 'exif-compression-32946',
+ 'exif-compression-34712',
+ ),
+ 'exif-copyrighted' => array(
+ 'exif-copyrighted-true',
+ 'exif-copyrighted-false',
),
'exif-photometricinterpretation' => array(
'exif-photometricinterpretation-2',
@@ -2736,7 +2903,7 @@ $wgMessageStructure = array(
),
'exif-colorspace' => array(
'exif-colorspace-1',
- 'exif-colorspace-ffff.h',
+ 'exif-colorspace-65535',
),
'exif-componentsconfiguration' => array(
'exif-componentsconfiguration-0',
@@ -2879,6 +3046,10 @@ $wgMessageStructure = array(
'exif-gpslongitude-e',
'exif-gpslongitude-w',
),
+ 'exif-altituderef' => array(
+ 'exif-gpsaltitude-above-sealevel',
+ 'exif-gpsaltitude-below-sealevel',
+ ),
'exif-gpsstatus' => array(
'exif-gpsstatus-a',
'exif-gpsstatus-v',
@@ -2892,17 +3063,80 @@ $wgMessageStructure = array(
'exif-gpsspeed-m',
'exif-gpsspeed-n',
),
+ 'exif-gpsdestdistanceref' => array(
+ 'exif-gpsdestdistance-k',
+ 'exif-gpsdestdistance-m',
+ 'exif-gpsdestdistance-n',
+ ),
+ 'exif-gdop' => array(
+ 'exif-gpsdop-excellent',
+ 'exif-gpsdop-good',
+ 'exif-gpsdop-moderate',
+ 'exif-gpsdop-fair',
+ 'exif-gpsdop-poor',
+ ),
+ 'exif-objectcycle' => array(
+ 'exif-objectcycle-a',
+ 'exif-objectcycle-p',
+ 'exif-objectcycle-b',
+ ),
'exif-gpsdirection' => array(
'exif-gpsdirection-t',
'exif-gpsdirection-m',
),
+ 'exif-ycbcrpositioning' => array(
+ 'exif-ycbcrpositioning-1',
+ 'exif-ycbcrpositioning-2',
+ ),
+ 'exif-dc' => array(
+ 'exif-dc-contributor',
+ 'exif-dc-coverage',
+ 'exif-dc-date',
+ 'exif-dc-publisher',
+ 'exif-dc-relation',
+ 'exif-dc-rights',
+ 'exif-dc-source',
+ 'exif-dc-type',
+ ),
+ 'exif-rating' => array(
+ 'exif-rating-rejected',
+ ),
+ 'exif-isospeedratings' => array(
+ 'exif-isospeedratings-overflow',
+ ),
+ 'exif-maxaperturevalue' => array(
+ 'exif-maxaperturevalue-value',
+ ),
+ 'exif-iimcategory' => array(
+ 'exif-iimcategory-ace',
+ 'exif-iimcategory-clj',
+ 'exif-iimcategory-dis',
+ 'exif-iimcategory-fin',
+ 'exif-iimcategory-edu',
+ 'exif-iimcategory-evn',
+ 'exif-iimcategory-hth',
+ 'exif-iimcategory-hum',
+ 'exif-iimcategory-lab',
+ 'exif-iimcategory-lif',
+ 'exif-iimcategory-pol',
+ 'exif-iimcategory-rel',
+ 'exif-iimcategory-sci',
+ 'exif-iimcategory-soi',
+ 'exif-iimcategory-spo',
+ 'exif-iimcategory-war',
+ 'exif-iimcategory-wea',
+ ),
+ 'exif-urgency' => array(
+ 'exif-urgency-normal',
+ 'exif-urgency-low',
+ 'exif-urgency-high',
+ 'exif-urgency-other',
+ ),
'edit-externally' => array(
'edit-externally',
'edit-externally-help',
),
'all' => array(
- 'recentchangesall',
- 'imagelistall',
'watchlistall2',
'namespacesall',
'monthsall',
@@ -2945,6 +3179,7 @@ $wgMessageStructure = array(
'deleteconflict' => array(
'deletedwhileediting',
'confirmrecreate',
+ 'confirmrecreate-noreason',
'recreate',
),
'unit-pixel' => array(
@@ -2955,8 +3190,13 @@ $wgMessageStructure = array(
'confirm-purge-top',
'confirm-purge-bottom',
),
+ 'watch-unwatch' => array(
+ 'confirm-watch-button',
+ 'confirm-watch-top',
+ 'confirm-unwatch-button',
+ 'confirm-unwatch-top',
+ ),
'separators' => array(
- 'catseparator',
'semicolon-separator',
'comma-separator',
'colon-separator',
@@ -3010,6 +3250,9 @@ $wgMessageStructure = array(
'lag-warn-normal',
'lag-warn-high',
),
+ 'watch' => array(
+ 'confirm-watch-button',
+ ),
'watchlisteditor' => array(
'watchlistedit-numitems',
'watchlistedit-noitems',
@@ -3105,14 +3348,15 @@ $wgMessageStructure = array(
'version-specialpages',
'version-parserhooks',
'version-variables',
+ 'version-antispam',
'version-skins',
+ 'version-api',
'version-other',
'version-mediahandlers',
'version-hooks',
'version-extension-functions',
'version-parser-extensiontags',
'version-parser-function-hooks',
- 'version-skin-extension-functions',
'version-hook-name',
'version-hook-subscribedby',
'version-version',
@@ -3140,6 +3384,7 @@ $wgMessageStructure = array(
'fileduplicatesearch-info',
'fileduplicatesearch-result-1',
'fileduplicatesearch-result-n',
+ 'fileduplicatesearch-noresults',
),
'special-specialpages' => array(
'specialpages',
@@ -3212,6 +3457,9 @@ $wgMessageStructure = array(
'sqlite-has-fts',
'sqlite-no-fts',
),
+ 'unwatch' => array(
+ 'confirm-unwatch-button',
+ ),
);
/** Comments for each block */
@@ -3243,7 +3491,8 @@ XHTML id names.",
'login' => 'Login and logout pages',
'mail' => 'E-mail sending',
'passwordstrength' => 'JavaScript password checks',
- 'resetpass' => 'Password reset dialog',
+ 'resetpass' => 'Change password dialog',
+ 'passwordreset' => 'Special:PasswordReset',
'toolbar' => 'Edit page toolbar',
'edit' => 'Edit pages',
'parserwarnings' => 'Parser/template warnings',
@@ -3271,7 +3520,9 @@ XHTML id names.",
'recentchanges' => 'Recent changes',
'recentchangeslinked' => 'Recent changes linked',
'upload' => 'Upload',
+ 'zip' => 'ZipDirectoryReader',
'upload-errors' => '',
+ 'uploadstash' => 'Special:UploadStash',
'img-auth' => 'img_auth script messages',
'http-errors' => 'HTTP errors',
'upload-curl-errors' => 'Some likely curl errors. More could be added from ',
@@ -3337,8 +3588,6 @@ XHTML id names.",
'spamprotection' => 'Spam protection',
'info' => 'Info page',
'skin' => 'Skin names',
- 'math' => 'Math options',
- 'matherrors' => 'Math errors',
'patrolling' => 'Patrolling',
'patrol-log' => 'Patrol log',
'imagedeletion' => 'Image deletion',
@@ -3355,11 +3604,13 @@ Variants for Chinese language",
'variantname-kk' => 'Variants for Kazakh language',
'variantname-ku' => 'Variants for Kurdish language',
'variantname-tg' => 'Variants for Tajiki language',
+ 'variantname-iu' => 'Variants for Inuktitut language',
'media-info' => 'Media information',
'metadata' => 'Metadata',
'exif' => 'EXIF tags',
'exif-values' => 'Make & model, can be wikified in order to link to the camera and model name',
'exif-compression' => 'EXIF attributes',
+ 'exif-copyrighted' => '',
'exif-unknowndate' => '',
'exif-photometricinterpretation' => '',
'exif-orientation' => '',
@@ -3387,10 +3638,21 @@ Variants for Chinese language",
'exif-subjectdistancerange' => '',
'exif-gpslatitude' => 'Pseudotags used for GPSLatitudeRef and GPSDestLatitudeRef',
'exif-gpslongitude' => 'Pseudotags used for GPSLongitudeRef and GPSDestLongitudeRef',
+ 'exif-altituderef' => 'Pseudotags used for GPSAltitudeRef',
'exif-gpsstatus' => '',
'exif-gpsmeasuremode' => '',
'exif-gpsspeed' => 'Pseudotags used for GPSSpeedRef',
+ 'exif-gpsdestdistanceref' => 'Pseudotags used for GPSDestDistanceRef',
+ 'exif-gdop' => '',
+ 'exif-objectcycle' => '',
'exif-gpsdirection' => 'Pseudotags used for GPSTrackRef, GPSImgDirectionRef and GPSDestBearingRef',
+ 'exif-ycbcrpositioning' => '',
+ 'exif-dc' => '',
+ 'exif-rating' => '',
+ 'exif-isospeedratings' => '',
+ 'exif-maxaperturevalue' => '',
+ 'exif-iimcategory' => '',
+ 'exif-urgency' => '',
'edit-externally' => 'External editor support',
'all' => "'all' in various places, this might be different for inflected languages",
'confirmemail' => 'E-mail address confirmation',
@@ -3399,6 +3661,7 @@ Variants for Chinese language",
'deleteconflict' => 'Delete conflict',
'unit-pixel' => '',
'purge' => 'action=purge',
+ 'watch-unwatch' => 'action=watch/unwatch',
'separators' => 'Separators for various lists, etc.',
'imgmulti' => 'Multipage image navigation',
'tablepager' => 'Table pager',
@@ -3425,4 +3688,5 @@ Variants for Chinese language",
'db-error-messages' => 'Database error messages',
'html-forms' => 'HTML forms',
'sqlite' => 'SQLite database support',
+ 'ajax-category' => 'Add categories per AJAX',
);
diff --git a/maintenance/language/rebuildLanguage.php b/maintenance/language/rebuildLanguage.php
index fd8d62ee..9b3a4b9d 100644
--- a/maintenance/language/rebuildLanguage.php
+++ b/maintenance/language/rebuildLanguage.php
@@ -2,6 +2,21 @@
/**
* Rewrite the messages array in the files languages/messages/MessagesXx.php.
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup MaintenanceLanguage
* @defgroup MaintenanceLanguage MaintenanceLanguage
@@ -14,11 +29,12 @@ require_once( 'writeMessagesArray.inc' );
/**
* Rewrite a messages array.
*
+ * @param $languages
* @param $code The language code.
- * @param $write Write to the messages file?
- * @param $listUnknown List the unknown messages?
- * @param $removeUnknown Remove the unknown messages?
- * @param $removeDupes Remove the duplicated messages?
+ * @param bool $write Write to the messages file?
+ * @param bool $listUnknown List the unknown messages?
+ * @param bool $removeUnknown Remove the unknown messages?
+ * @param bool $removeDupes Remove the duplicated messages?
* @param $dupeMsgSource The source file intended to remove from the array.
*/
function rebuildLanguage( $languages, $code, $write, $listUnknown, $removeUnknown, $removeDupes, $dupeMsgSource ) {
@@ -35,7 +51,7 @@ function rebuildLanguage( $languages, $code, $write, $listUnknown, $removeUnknow
*
* @param $oldMsgArray The input message array.
* @param $dupeMsgSource The source file path for duplicates.
- * @return $newMsgArray The output message array, with duplicates removed.
+ * @return Array $newMsgArray The output message array, with duplicates removed.
*/
function removeDupes( $oldMsgArray, $dupeMsgSource ) {
if ( file_exists( $dupeMsgSource ) ) {
diff --git a/maintenance/language/transstat.php b/maintenance/language/transstat.php
index c2144eb6..a3213266 100644
--- a/maintenance/language/transstat.php
+++ b/maintenance/language/transstat.php
@@ -2,6 +2,21 @@
/**
* Statistics about the localisation.
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup MaintenanceLanguage
*
@@ -80,8 +95,9 @@ $wgGeneralMessages = $wgLanguages->getGeneralMessages();
$wgRequiredMessagesNumber = count( $wgGeneralMessages['required'] );
foreach ( $wgLanguages->getLanguages() as $code ) {
- # Don't check English or RTL English
- if ( $code == 'en' || $code == 'enRTL' ) {
+ # Don't check English, RTL English or dummy language codes
+ if ( $code == 'en' || $code == 'enRTL' || (is_array( $wgDummyLanguageCodes ) &&
+ in_array( $code, $wgDummyLanguageCodes ) ) ) {
continue;
}
diff --git a/maintenance/language/validate.php b/maintenance/language/validate.php
index d897e467..57517644 100644
--- a/maintenance/language/validate.php
+++ b/maintenance/language/validate.php
@@ -1,5 +1,22 @@
true/*, 'debug' => true*/ ) );
+$mcc = new MWMemcached( array( 'persistent' => true/*, 'debug' => true*/ ) );
$mcc->set_servers( $wgMemCachedServers );
# $mcc->set_debug( true );
diff --git a/maintenance/mergeMessageFileList.php b/maintenance/mergeMessageFileList.php
index 00c8cae5..8cfefcbd 100644
--- a/maintenance/mergeMessageFileList.php
+++ b/maintenance/mergeMessageFileList.php
@@ -1,4 +1,26 @@
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 ' .
diff --git a/maintenance/migrateUserGroup.php b/maintenance/migrateUserGroup.php
index c4163208..771ed947 100644
--- a/maintenance/migrateUserGroup.php
+++ b/maintenance/migrateUserGroup.php
@@ -60,7 +60,7 @@ class MigrateUserGroup extends Maintenance {
$dbw->commit();
$blockStart += $this->mBatchSize;
$blockEnd += $this->mBatchSize;
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
}
$this->output( "Done! $count user(s) in group '$oldGroup' are now in '$newGroup' instead.\n" );
}
diff --git a/maintenance/minify.php b/maintenance/minify.php
index 2884fd7b..e1fd862d 100644
--- a/maintenance/minify.php
+++ b/maintenance/minify.php
@@ -1,6 +1,24 @@
$svnstat,
'{{INPUT}}' => $input,
'{{EXCLUDE}}' => $exclude,
+ '{{EXCLUDE_PATTERNS}}' => $exclude_patterns,
+ '{{HAVE_DOT}}' => `which dot` ? 'YES' : 'NO',
);
$tmpCfg = str_replace( array_keys( $replacements ), array_values( $replacements ), $template );
$tmpFileName = tempnam( wfTempDir(), 'mwdocgen-' );
@@ -212,18 +232,23 @@ case 5:
$input = $mwPath . $file;
case 6:
$input = $mwPath;
- $exclude = 'extensions';
+ $exclude_patterns = 'extensions';
}
$versionNumber = getSvnRevision( $input );
if ( $versionNumber === false ) { # Not using subversion ?
$svnstat = ''; # Not really useful if subversion not available
- $version = 'trunk'; # FIXME
+ # @todo FIXME
+ $version = 'trunk';
} else {
$version = "trunk (r$versionNumber)";
}
-$generatedConf = generateConfigFile( $doxygenTemplate, $doxyOutput, $mwPath, $version, $svnstat, $input, $exclude );
+// Generate path exclusions
+$excludedPaths = $mwPath . join( " $mwPath", $mwExcludePaths );
+print "EXCLUDE: $excludedPaths\n\n";
+
+$generatedConf = generateConfigFile( $doxygenTemplate, $doxyOutput, $mwPath, $version, $svnstat, $input, $excludedPaths, $exclude_patterns );
$command = $doxygenBin . ' ' . $generatedConf;
echo <<mDescription = "";
$this->addOption( 'fix', 'Attempt to automatically fix errors' );
- $this->addOption( 'suffix', "Dupes will be renamed with correct namespace with\n" .
- "\t\t Appended after the article name", false, true );
- $this->addOption( 'prefix', "Do an explicit check for the given title prefix\n" .
- "\t\tappended after the article name", false, true );
+ $this->addOption( 'suffix', "Dupes will be renamed with correct namespace with " .
+ " appended after the article name", false, true );
+ $this->addOption( 'prefix', "Do an explicit check for the given title prefix " .
+ "appended after the article name", false, true );
}
public function execute() {
@@ -66,8 +66,7 @@ class NamespaceConflictChecker extends Maintenance {
* @param $suffix String: suffix to append to renamed articles
*/
private function checkAll( $fix, $suffix = '' ) {
- global $wgContLang, $wgNamespaceAliases, $wgCanonicalNamespaceNames;
- global $wgCapitalLinks;
+ global $wgContLang, $wgNamespaceAliases, $wgCapitalLinks;
$spaces = array();
@@ -79,7 +78,7 @@ class NamespaceConflictChecker extends Maintenance {
}
// Now pull in all canonical and alias namespaces...
- foreach ( $wgCanonicalNamespaceNames as $ns => $name ) {
+ foreach ( MWNamespace::getCanonicalNamespaces() as $ns => $name ) {
// This includes $wgExtraNamespaces
if ( $name !== '' ) {
$spaces[$name] = $ns;
diff --git a/maintenance/nextJobDB.php b/maintenance/nextJobDB.php
index 8d8229e2..2d3608d5 100644
--- a/maintenance/nextJobDB.php
+++ b/maintenance/nextJobDB.php
@@ -33,20 +33,72 @@ class nextJobDB extends Maintenance {
public function execute() {
global $wgMemc;
$type = $this->getOption( 'type', false );
- $mckey = $type === false
- ? "jobqueue:dbs"
- : "jobqueue:dbs:$type";
- $pendingDBs = $wgMemc->get( $mckey );
- # If we didn't get it from the cache
+ $memcKey = 'jobqueue:dbs:v2';
+ $pendingDBs = $wgMemc->get( $memcKey );
+
+ // If the cache entry wasn't present, or in 1% of cases otherwise,
+ // regenerate the cache.
+ if ( !$pendingDBs || mt_rand( 0, 100 ) == 0 ) {
+ $pendingDBs = $this->getPendingDbs();
+ $wgMemc->set( $memcKey, $pendingDBs, 300 );
+ }
+
if ( !$pendingDBs ) {
- $pendingDBs = $this->getPendingDbs( $type );
- $wgMemc->set( $mckey, $pendingDBs, 300 );
+ return;
}
- # If we've got a pending job in a db, display it.
- if ( $pendingDBs ) {
- $this->output( $pendingDBs[mt_rand( 0, count( $pendingDBs ) - 1 )] );
+
+ do {
+ $again = false;
+
+ if ( $type === false ) {
+ $candidates = call_user_func_array( 'array_merge', $pendingDBs );
+ } elseif ( isset( $pendingDBs[$type] ) ) {
+ $candidates = $pendingDBs[$type];
+ } else {
+ $candidates = array();
+ }
+ if ( !$candidates ) {
+ return;
+ }
+
+ $candidates = array_values( $candidates );
+ $db = $candidates[ mt_rand( 0, count( $candidates ) - 1 ) ];
+ if ( !$this->checkJob( $type, $db ) ) {
+ // This job is not available in the current database. Remove it from
+ // the cache.
+ if ( $type === false ) {
+ foreach ( $pendingDBs as $type2 => $dbs ) {
+ $pendingDBs[$type2] = array_diff( $pendingDBs[$type2], array( $db ) );
+ }
+ } else {
+ $pendingDBs[$type] = array_diff( $pendingDBs[$type], array( $db ) );
+ }
+
+ $wgMemc->set( $memcKey, $pendingDBs, 300 );
+ $again = true;
+ }
+ } while ( $again );
+
+ $this->output( $db . "\n" );
+ }
+
+ /**
+ * Check if the specified database has a job of the specified type in it.
+ * The type may be false to indicate "all".
+ */
+ function checkJob( $type, $dbName ) {
+ $lb = wfGetLB( $dbName );
+ $db = $lb->getConnection( DB_MASTER, array(), $dbName );
+ if ( $type === false ) {
+ $conds = array();
+ } else {
+ $conds = array( 'job_cmd' => $type );
}
+
+ $exists = (bool) $db->selectField( 'job', '1', $conds, __METHOD__ );
+ $lb->reuseConnection( $db );
+ return $exists;
}
/**
@@ -54,7 +106,7 @@ class nextJobDB extends Maintenance {
* @param $type String Job type
* @return array
*/
- private function getPendingDbs( $type ) {
+ private function getPendingDbs() {
global $wgLocalDatabases;
$pendingDBs = array();
# Cross-reference DBs by master DB server
@@ -66,10 +118,10 @@ class nextJobDB extends Maintenance {
foreach ( $dbsByMaster as $dbs ) {
$dbConn = wfGetDB( DB_MASTER, array(), $dbs[0] );
- $stype = $dbConn->addQuotes( $type );
# Padding row for MySQL bug
- $sql = "(SELECT '-------------------------------------------' as db)";
+ $pad = str_repeat( '-', 40 );
+ $sql = "(SELECT '$pad' as db, '$pad' as job_cmd)";
foreach ( $dbs as $wikiId ) {
if ( $sql != '' ) {
$sql .= ' UNION ';
@@ -79,10 +131,7 @@ class nextJobDB extends Maintenance {
$dbConn->tablePrefix( $tablePrefix );
$jobTable = $dbConn->tableName( 'job' );
- if ( $type === false )
- $sql .= "(SELECT '$wikiId' as db FROM $dbName.$jobTable LIMIT 1)";
- else
- $sql .= "(SELECT '$wikiId' as db FROM $dbName.$jobTable WHERE job_cmd=$stype LIMIT 1)";
+ $sql .= "(SELECT DISTINCT '$wikiId' as db, job_cmd FROM $dbName.$jobTable GROUP BY job_cmd)";
}
$res = $dbConn->query( $sql, __METHOD__ );
$first = true;
@@ -92,7 +141,7 @@ class nextJobDB extends Maintenance {
$first = false;
continue;
}
- $pendingDBs[] = $row->db;
+ $pendingDBs[$row->job_cmd][] = $row->db;
}
}
return $pendingDBs;
diff --git a/maintenance/nukePage.php b/maintenance/nukePage.php
index 4a073a5e..c818e73a 100644
--- a/maintenance/nukePage.php
+++ b/maintenance/nukePage.php
@@ -56,6 +56,7 @@ 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" );
+ $revs = array();
foreach ( $res as $row ) {
$revs[] = $row->rev_id;
}
diff --git a/maintenance/oracle/alterSharedConstraints.php b/maintenance/oracle/alterSharedConstraints.php
new file mode 100644
index 00000000..aa207821
--- /dev/null
+++ b/maintenance/oracle/alterSharedConstraints.php
@@ -0,0 +1,90 @@
+mDescription = "Alter foreign key to reference master tables in shared database setup.";
+ }
+
+ public function getDbType() {
+ return Maintenance::DB_ADMIN;
+ }
+
+ public function execute() {
+ global $wgSharedDB, $wgSharedTables, $wgSharedPrefix, $wgDBprefix;
+
+ if ( $wgSharedDB == null ) {
+ $this->output( "Database sharing is not enabled\n" );
+ return;
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+ foreach ( $wgSharedTables as $table ) {
+ $stable = $dbw->tableNameInternal($table);
+ if ( $wgSharedPrefix != null ) {
+ $ltable = preg_replace( "/^$wgSharedPrefix(.*)/i", "$wgDBprefix\\1", $stable );
+ } else {
+ $ltable = "{$wgDBprefix}{$stable}" ;
+ }
+
+ $result = $dbw->query( "SELECT uc.constraint_name, uc.table_name, ucc.column_name, uccpk.table_name pk_table_name, uccpk.column_name pk_column_name, uc.delete_rule, uc.deferrable, uc.deferred
+ FROM user_constraints uc, user_cons_columns ucc, user_cons_columns uccpk
+ WHERE uc.constraint_type = 'R'
+ AND ucc.constraint_name = uc.constraint_name
+ AND uccpk.constraint_name = uc.r_constraint_name
+ AND uccpk.table_name = '$ltable'" );
+ while (($row = $result->fetchRow()) !== false) {
+
+ $this->output( "Altering {$row['constraint_name']} ...");
+
+ try {
+ $dbw->query( "ALTER TABLE {$row['table_name']} DROP CONSTRAINT {$wgDBprefix}{$row['constraint_name']}" );
+ } catch (DBQueryError $exdb) {
+ if ($exdb->errno != 2443) {
+ throw $exdb;
+ }
+ }
+
+ $deleteRule = $row['delete_rule'] == 'NO ACTION' ? '' : "ON DELETE {$row['delete_rule']}";
+ $dbw->query( "ALTER TABLE {$row['table_name']} ADD CONSTRAINT {$wgDBprefix}{$row['constraint_name']}
+ FOREIGN KEY ({$row['column_name']})
+ REFERENCES {$wgSharedDB}.$stable({$row['pk_column_name']})
+ {$deleteRule} {$row['deferrable']} INITIALLY {$row['deferred']}" );
+
+ $this->output( "DONE\n" );
+ }
+ }
+ }
+
+}
+
+$maintClass = "AlterSharedConstraints";
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/maintenance/oracle/archives/patch-config.sql b/maintenance/oracle/archives/patch-config.sql
new file mode 100644
index 00000000..66714a73
--- /dev/null
+++ b/maintenance/oracle/archives/patch-config.sql
@@ -0,0 +1,8 @@
+define mw_prefix='{$wgDBprefix}';
+
+CREATE TABLE &mw_prefix.config (
+ cf_name VARCHAR2(255) NOT NULL,
+ cf_value blob NOT NULL
+);
+ALTER TABLE &mw_prefix.config ADD CONSTRAINT &mw_prefix.config_pk PRIMARY KEY (cf_name);
+
diff --git a/maintenance/oracle/archives/patch-up_property.sql b/maintenance/oracle/archives/patch-up_property.sql
new file mode 100644
index 00000000..c8e2dd95
--- /dev/null
+++ b/maintenance/oracle/archives/patch-up_property.sql
@@ -0,0 +1,3 @@
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.user_properties MODIFY up_property varchar2(255);
diff --git a/maintenance/oracle/archives/patch-uploadstash.sql b/maintenance/oracle/archives/patch-uploadstash.sql
new file mode 100644
index 00000000..3e37ceff
--- /dev/null
+++ b/maintenance/oracle/archives/patch-uploadstash.sql
@@ -0,0 +1,25 @@
+define mw_prefix='{$wgDBprefix}';
+
+CREATE SEQUENCE uploadstash_us_id_seq;
+CREATE TABLE &mw_prefix.uploadstash (
+ us_id NUMBER NOT NULL,
+ us_user NUMBER DEFAULT 0 NOT NULL,
+ us_key VARCHAR2(255) NOT NULL,
+ us_orig_path VARCHAR2(255) NOT NULL,
+ us_path VARCHAR2(255) NOT NULL,
+ us_source_type VARCHAR2(50),
+ us_timestamp TIMESTAMP(6) WITH TIME ZONE,
+ us_status VARCHAR2(50) NOT NULL,
+ us_size NUMBER NOT NULL,
+ us_sha1 VARCHAR2(32) NOT NULL,
+ us_mime VARCHAR2(255),
+ us_media_type VARCHAR2(32) DEFAULT NULL,
+ us_image_width NUMBER,
+ us_image_height NUMBER,
+ us_image_bits NUMBER
+);
+ALTER TABLE &mw_prefix.uploadstash ADD CONSTRAINT &mw_prefix.uploadstash_pk PRIMARY KEY (us_id);
+ALTER TABLE &mw_prefix.uploadstash ADD CONSTRAINT &mw_prefix.uploadstash_fk1 FOREIGN KEY (us_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
+CREATE INDEX &mw_prefix.uploadstash_i01 ON &mw_prefix.uploadstash (us_user);
+CREATE INDEX &mw_prefix.uploadstash_i02 ON &mw_prefix.uploadstash (us_timestamp);
+CREATE UNIQUE INDEX &mw_prefix.uploadstash_u01 ON &mw_prefix.uploadstash (us_key);
diff --git a/maintenance/oracle/archives/patch-user_email_index.sql b/maintenance/oracle/archives/patch-user_email_index.sql
new file mode 100644
index 00000000..e34d8656
--- /dev/null
+++ b/maintenance/oracle/archives/patch-user_email_index.sql
@@ -0,0 +1,4 @@
+define mw_prefix='{$wgDBprefix}';
+
+CREATE INDEX &mw_prefix.mwuser_i02 ON &mw_prefix.mwuser (user_email);
+
diff --git a/maintenance/oracle/archives/patch-user_former_groups.sql b/maintenance/oracle/archives/patch-user_former_groups.sql
new file mode 100644
index 00000000..59147eb2
--- /dev/null
+++ b/maintenance/oracle/archives/patch-user_former_groups.sql
@@ -0,0 +1,9 @@
+define mw_prefix='{$wgDBprefix}';
+
+CREATE TABLE &mw_prefix.user_former_groups (
+ ufg_user NUMBER DEFAULT 0 NOT NULL,
+ ufg_group VARCHAR2(16) NOT NULL
+);
+ALTER TABLE &mw_prefix.user_former_groups ADD CONSTRAINT &mw_prefix.user_former_groups_fk1 FOREIGN KEY (ufg_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+CREATE UNIQUE INDEX &mw_prefix.user_former_groups_u01 ON &mw_prefix.user_former_groups (ufg_user,ufg_group);
+
diff --git a/maintenance/oracle/archives/patch_rebuild_dupfunc.sql b/maintenance/oracle/archives/patch_rebuild_dupfunc.sql
new file mode 100644
index 00000000..0a232dbc
--- /dev/null
+++ b/maintenance/oracle/archives/patch_rebuild_dupfunc.sql
@@ -0,0 +1,146 @@
+/*$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);
+ l_temporary BOOLEAN := p_temporary;
+BEGIN
+ BEGIN
+ EXECUTE IMMEDIATE 'DROP TABLE ' || p_newprefix || p_tabname ||
+ ' CASCADE CONSTRAINTS';
+ EXCEPTION
+ WHEN e_table_not_exist THEN
+ NULL;
+ END;
+ IF (p_tabname = 'SEARCHINDEX') THEN
+ l_temporary := FALSE;
+ END IF;
+ IF (l_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);
+ IF nvl(length(l_temp_ei_sql), 0) > 0 THEN
+ EXECUTE IMMEDIATE l_temp_ei_sql;
+ END IF;
+ END LOOP;
+ IF (NOT l_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
+ IF nvl(length(l_temp_ei_sql), 0) > 0 THEN
+ EXECUTE IMMEDIATE l_temp_ei_sql;
+ END IF;
+ 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);
+ IF nvl(length(l_temp_ei_sql), 0) > 0 THEN
+ EXECUTE IMMEDIATE l_temp_ei_sql;
+ END IF;
+ END LOOP;
+ FOR rc IN (SELECT REPLACE(REPLACE(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('INDEX',
+ index_name),
+ 32767,
+ 1),
+ USER || '"."' || p_oldprefix,
+ USER || '"."' || p_newprefix),
+ '"' || index_name || '"',
+ '"' || p_newprefix || index_name || '"') DDLVC2,
+ index_name,
+ index_type
+ FROM user_indexes ui
+ WHERE table_name = p_oldprefix || p_tabname
+ AND index_type = '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 := rc.ddlvc2;
+ IF nvl(length(l_temp_ei_sql), 0) > 0 THEN
+ EXECUTE IMMEDIATE l_temp_ei_sql;
+ END IF;
+ 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);
+ IF nvl(length(l_temp_ei_sql), 0) > 0 THEN
+ EXECUTE IMMEDIATE l_temp_ei_sql;
+ END IF;
+ END LOOP;
+END;
+/*$mw$*/
+
diff --git a/maintenance/oracle/tables.sql b/maintenance/oracle/tables.sql
index f6d29f54..2fd62ef7 100644
--- a/maintenance/oracle/tables.sql
+++ b/maintenance/oracle/tables.sql
@@ -23,6 +23,7 @@ CREATE TABLE &mw_prefix.mwuser ( -- replace reserved word 'user'
ALTER TABLE &mw_prefix.mwuser ADD CONSTRAINT &mw_prefix.mwuser_pk PRIMARY KEY (user_id);
CREATE UNIQUE INDEX &mw_prefix.mwuser_u01 ON &mw_prefix.mwuser (user_name);
CREATE INDEX &mw_prefix.mwuser_i01 ON &mw_prefix.mwuser (user_email_token);
+CREATE INDEX &mw_prefix.mwuser_i02 ON &mw_prefix.mwuser (user_email, user_name);
-- Create a dummy user to satisfy fk contraints especially with revisions
INSERT INTO &mw_prefix.mwuser
@@ -47,7 +48,7 @@ CREATE INDEX &mw_prefix.user_newtalk_i02 ON &mw_prefix.user_newtalk (user_ip);
CREATE TABLE &mw_prefix.user_properties (
up_user NUMBER NOT NULL,
- up_property VARCHAR2(32) NOT NULL,
+ up_property VARCHAR2(255) NOT NULL,
up_value CLOB
);
CREATE UNIQUE INDEX &mw_prefix.user_properties_u01 on &mw_prefix.user_properties (up_user,up_property);
@@ -405,15 +406,6 @@ CREATE UNIQUE INDEX &mw_prefix.watchlist_u01 ON &mw_prefix.watchlist (wl_user, w
CREATE INDEX &mw_prefix.watchlist_i01 ON &mw_prefix.watchlist (wl_namespace, wl_title);
-CREATE TABLE &mw_prefix.math (
- math_inputhash VARCHAR2(32) NOT NULL,
- math_outputhash VARCHAR2(32) NOT NULL,
- math_html_conservativeness NUMBER NOT NULL,
- math_html CLOB,
- math_mathml CLOB
-);
-CREATE UNIQUE INDEX &mw_prefix.math_u01 ON &mw_prefix.math (math_inputhash);
-
CREATE TABLE &mw_prefix.searchindex (
si_page NUMBER NOT NULL,
si_title VARCHAR2(255),
@@ -645,6 +637,14 @@ CREATE TABLE &mw_prefix.module_deps (
);
CREATE UNIQUE INDEX &mw_prefix.module_deps_u01 ON &mw_prefix.module_deps (md_module, md_skin);
+CREATE TABLE &mw_prefix.config (
+ cf_name VARCHAR2(255) NOT NULL,
+ cf_value blob NOT NULL
+);
+ALTER TABLE &mw_prefix.config ADD CONSTRAINT &mw_prefix.config_pk PRIMARY KEY (cf_name);
+-- leaving index out for now ...
+
+
-- do not prefix this table as it breaks parserTests
CREATE TABLE wiki_field_info_full (
table_name VARCHAR2(35) NOT NULL,
@@ -718,6 +718,7 @@ CREATE OR REPLACE PROCEDURE duplicate_table(p_tabname IN VARCHAR2,
e_table_not_exist EXCEPTION;
PRAGMA EXCEPTION_INIT(e_table_not_exist, -00942);
l_temp_ei_sql VARCHAR2(2000);
+ l_temporary BOOLEAN := p_temporary;
BEGIN
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE ' || p_newprefix || p_tabname ||
@@ -726,7 +727,10 @@ BEGIN
WHEN e_table_not_exist THEN
NULL;
END;
- IF (p_temporary) THEN
+ IF (p_tabname = 'SEARCHINDEX') THEN
+ l_temporary := FALSE;
+ END IF;
+ IF (l_temporary) THEN
EXECUTE IMMEDIATE 'CREATE GLOBAL TEMPORARY TABLE ' || p_newprefix ||
p_tabname || ' AS SELECT * FROM ' || p_oldprefix ||
p_tabname || ' WHERE ROWNUM = 0';
@@ -756,22 +760,30 @@ BEGIN
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;
+ l_temp_ei_sql := SUBSTR(l_temp_ei_sql,
+ 1,
+ INSTR(l_temp_ei_sql,
+ ')',
+ INSTR(l_temp_ei_sql, 'PRIMARY KEY') + 1) + 1);
+ IF nvl(length(l_temp_ei_sql), 0) > 0 THEN
+ EXECUTE IMMEDIATE l_temp_ei_sql;
+ END IF;
END LOOP;
+ IF (NOT l_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
+ IF nvl(length(l_temp_ei_sql), 0) > 0 THEN
+ EXECUTE IMMEDIATE l_temp_ei_sql;
+ END IF;
+ END LOOP;
END IF;
FOR rc IN (SELECT REPLACE(REPLACE(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('INDEX',
index_name),
@@ -792,8 +804,38 @@ BEGIN
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;
+ 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);
+ IF nvl(length(l_temp_ei_sql), 0) > 0 THEN
+ EXECUTE IMMEDIATE l_temp_ei_sql;
+ END IF;
+ END LOOP;
+ FOR rc IN (SELECT REPLACE(REPLACE(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('INDEX',
+ index_name),
+ 32767,
+ 1),
+ USER || '"."' || p_oldprefix,
+ USER || '"."' || p_newprefix),
+ '"' || index_name || '"',
+ '"' || p_newprefix || index_name || '"') DDLVC2,
+ index_name,
+ index_type
+ FROM user_indexes ui
+ WHERE table_name = p_oldprefix || p_tabname
+ AND index_type = '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 := rc.ddlvc2;
+ IF nvl(length(l_temp_ei_sql), 0) > 0 THEN
+ EXECUTE IMMEDIATE l_temp_ei_sql;
+ END IF;
END LOOP;
FOR rc IN (SELECT REPLACE(REPLACE(UPPER(DBMS_LOB.SUBSTR(DBMS_METADATA.get_ddl('TRIGGER',
trigger_name),
@@ -807,9 +849,12 @@ BEGIN
FROM user_triggers
WHERE table_name = p_oldprefix || p_tabname) LOOP
l_temp_ei_sql := SUBSTR(rc.ddlvc2, 1, INSTR(rc.ddlvc2, 'ALTER ') - 1);
- EXECUTE IMMEDIATE l_temp_ei_sql;
+ IF nvl(length(l_temp_ei_sql), 0) > 0 THEN
+ EXECUTE IMMEDIATE l_temp_ei_sql;
+ END IF;
END LOOP;
END;
+
/*$mw$*/
/*$mw$*/
diff --git a/maintenance/orphans.php b/maintenance/orphans.php
index dbbddb9c..1986ff35 100644
--- a/maintenance/orphans.php
+++ b/maintenance/orphans.php
@@ -50,7 +50,7 @@ class Orphans extends Maintenance {
/**
* Lock the appropriate tables for the script
- * @param $db Database object
+ * @param $db DatabaseBase object
* @param $extraTable String The name of any extra tables to lock (eg: text)
*/
private function lockTables( &$db, $extraTable = null ) {
diff --git a/maintenance/ourusers.php b/maintenance/ourusers.php
index 499da5cf..c8578e24 100644
--- a/maintenance/ourusers.php
+++ b/maintenance/ourusers.php
@@ -6,6 +6,21 @@
* list of hosts. It takes care of setting the wikiuser for every
* database as well as setting up wikiadmin.
*
+ * 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 document
* @file
* @ingroup Maintenance
diff --git a/maintenance/populateCategory.php b/maintenance/populateCategory.php
index 4f494e15..4e9c44c4 100644
--- a/maintenance/populateCategory.php
+++ b/maintenance/populateCategory.php
@@ -1,5 +1,22 @@
' . $dbw->addQuotes( $begin );
@@ -102,7 +118,7 @@ TEXT;
++$i;
if ( !( $i % self::REPORTING_INTERVAL ) ) {
$this->output( "$name\n" );
- wfWaitForSlaves( $maxlag );
+ wfWaitForSlaves();
}
usleep( $throttle * 1000 );
}
diff --git a/maintenance/populateLogSearch.php b/maintenance/populateLogSearch.php
index ce2d95cc..f13873cb 100644
--- a/maintenance/populateLogSearch.php
+++ b/maintenance/populateLogSearch.php
@@ -35,7 +35,7 @@ class PopulateLogSearch extends Maintenance {
}
public function execute() {
- $db = wfGetDB( DB_MASTER );
+ $db = $this->getDB( DB_MASTER );
if ( !$db->tableExists( 'log_search' ) ) {
$this->error( "log_search does not exist", true );
}
@@ -97,14 +97,14 @@ class PopulateLogSearch extends Maintenance {
foreach ( $sres as $srow ) {
if ( $srow->$userField > 0 )
$userIds[] = intval( $srow->$userField );
- else if ( $srow->$userTextField != '' )
+ elseif ( $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' ) ) {
+ } elseif ( LogEventsList::typeAction( $row, $delTypes, 'event' ) ) {
$params = LogPage::extractParams( $row->log_params );
// Param format: - [ ]
if ( count( $params ) < 1 ) continue; // bad row
@@ -121,7 +121,7 @@ class PopulateLogSearch extends Maintenance {
foreach ( $sres as $srow ) {
if ( $srow->log_user > 0 )
$userIds[] = intval( $srow->log_user );
- else if ( IP::isIPAddress( $srow->log_user_text ) )
+ elseif ( IP::isIPAddress( $srow->log_user_text ) )
$userIPs[] = $srow->log_user_text;
}
$log->addRelations( 'target_author_id', $userIds, $row->log_id );
@@ -130,7 +130,7 @@ class PopulateLogSearch extends Maintenance {
}
$blockStart += self::LOG_SEARCH_BATCH_SIZE;
$blockEnd += self::LOG_SEARCH_BATCH_SIZE;
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
}
if ( $db->insert(
'updatelog',
diff --git a/maintenance/populateLogUsertext.php b/maintenance/populateLogUsertext.php
index bb3927ce..e9e6926e 100644
--- a/maintenance/populateLogUsertext.php
+++ b/maintenance/populateLogUsertext.php
@@ -33,7 +33,7 @@ class PopulateLogUsertext extends Maintenance {
}
public function execute() {
- $db = wfGetDB( DB_MASTER );
+ $db = $this->getDB( DB_MASTER );
$start = $db->selectField( 'logging', 'MIN(log_id)', false, __METHOD__ );
if ( !$start ) {
$this->output( "Nothing to do.\n" );
@@ -59,7 +59,7 @@ class PopulateLogUsertext extends Maintenance {
$db->commit();
$blockStart += $this->mBatchSize;
$blockEnd += $this->mBatchSize;
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
}
if ( $db->insert(
'updatelog',
diff --git a/maintenance/populateParentId.php b/maintenance/populateParentId.php
index 387f5a56..7cbc267b 100644
--- a/maintenance/populateParentId.php
+++ b/maintenance/populateParentId.php
@@ -98,7 +98,7 @@ class PopulateParentId extends Maintenance {
}
$blockStart += $this->mBatchSize;
$blockEnd += $this->mBatchSize;
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
}
$logged = $db->insert( 'updatelog',
array( 'ul_key' => 'populate rev_parent_id' ),
diff --git a/maintenance/populateRevisionLength.php b/maintenance/populateRevisionLength.php
index 0af51dc1..d020b4cb 100644
--- a/maintenance/populateRevisionLength.php
+++ b/maintenance/populateRevisionLength.php
@@ -30,7 +30,7 @@ class PopulateRevisionLength extends Maintenance {
}
public function execute() {
- $db = wfGetDB( DB_MASTER );
+ $db = $this->getDB( DB_MASTER );
if ( !$db->tableExists( 'revision' ) ) {
$this->error( "revision table does not exist", true );
}
@@ -78,7 +78,7 @@ class PopulateRevisionLength extends Maintenance {
}
$blockStart += $this->mBatchSize;
$blockEnd += $this->mBatchSize;
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
}
$logged = $db->insert( 'updatelog',
array( 'ul_key' => 'populate rev_len' ),
diff --git a/maintenance/populateSha1.php b/maintenance/populateSha1.php
index 1714c0d6..1ab9109d 100644
--- a/maintenance/populateSha1.php
+++ b/maintenance/populateSha1.php
@@ -54,7 +54,7 @@ class PopulateSha1 extends Maintenance {
$imageTable = $dbw->tableName( 'image' );
if ( $method == 'pipe' ) {
- // @fixme kill this and replace with a second unbuffered DB connection.
+ // @todo FIXME: Kill this and replace with a second unbuffered DB connection.
global $wgDBuser, $wgDBserver, $wgDBpassword, $wgDBname;
$cmd = 'mysql -u' . wfEscapeShellArg( $wgDBuser ) .
' -h' . wfEscapeShellArg( $wgDBserver ) .
@@ -68,7 +68,7 @@ class PopulateSha1 extends Maintenance {
foreach ( $res as $row ) {
if ( $i % 100 == 0 ) {
$this->output( sprintf( "Done %d of %d, %5.3f%% \r", $i, $numRows, $i / $numRows * 100 ) );
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
}
$file = wfLocalFile( $row->img_name );
if ( !$file ) {
diff --git a/maintenance/postgres/archives/patch-user_former_groups.sql b/maintenance/postgres/archives/patch-user_former_groups.sql
new file mode 100644
index 00000000..1ba011e3
--- /dev/null
+++ b/maintenance/postgres/archives/patch-user_former_groups.sql
@@ -0,0 +1,5 @@
+CREATE TABLE user_former_groups (
+ ufg_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ ufg_group TEXT NOT NULL
+);
+CREATE UNIQUE INDEX ufg_user_group ON user_former_groups (ufg_user, ufg_group);
diff --git a/maintenance/postgres/compare_schemas.pl b/maintenance/postgres/compare_schemas.pl
index 7e3cdf71..18210fcf 100644
--- a/maintenance/postgres/compare_schemas.pl
+++ b/maintenance/postgres/compare_schemas.pl
@@ -94,7 +94,7 @@ sub parse_sql {
next if /^\s*\-\-/ or /^\s+$/;
s/\s*\-\- [\w ]+$//;
chomp;
-
+
if (/CREATE\s*TABLE/i) {
if (m{^CREATE TABLE /\*_\*/(\w+) \($}) {
$table = $1;
@@ -179,6 +179,10 @@ while (<$newfh>) {
next if /^CREATE TRIGGER/ or /^ FOR EACH ROW/;
next if /^INSERT INTO/ or /^ VALUES \(/;
next if /^ALTER TABLE/;
+ next if /^DROP SEQUENCE/;
+ next if /^DROP FUNCTION/;
+
+
chomp;
if (/^\$mw\$;?$/) {
diff --git a/maintenance/postgres/mediawiki_mysql2postgres.pl b/maintenance/postgres/mediawiki_mysql2postgres.pl
index 2b2bf50e..16012762 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 65542 2010-04-26 13:46:04Z demon $
+## svn: $Id: mediawiki_mysql2postgres.pl 86721 2011-04-22 18:47:17Z mah $
## 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: 65542 $}.qq{)
+-- Version: $VERSION (subversion }.q{$LastChangedRevision: 86721 $}.qq{)
-- Author: Greg Sabino Mullane Comments welcome
--
-- This file was created: $now
@@ -279,8 +279,8 @@ for my $t (@torder, 'objectcache', 'querycache') {
}
print "\n\n";
-print qq{-- Temporarily rename pagecontent to "text"\n};
-print qq{ALTER TABLE pagecontent RENAME TO "text";\n\n};
+print qq{-- Temporarily rename pagecontent to "${table_prefix}text"\n};
+print qq{ALTER TABLE pagecontent RENAME TO "${table_prefix}text";\n\n};
print qq{-- Allow rc_ip to contain empty string, will convert at end\n};
print qq{ALTER TABLE recentchanges ALTER rc_ip TYPE text USING host(rc_ip);\n\n};
@@ -304,9 +304,9 @@ INSERT INTO page VALUES (0,-1,'Dummy Page','',0,0,0,default,now(),0,10);
if (length $table_prefix) {
print qq{\n\n-- Temporarily renaming tables to accomodate the table_prefix "$table_prefix"\n\n};
for my $t (@torder) {
- next if $t eq '---';
+ next if $t eq '---' or $t eq 'text' or $t eq 'user';
my $tname = $special{$t}||$t;
- printf qq{ALTER TABLE %-18s RENAME TO "${table_prefix}$tname"\n}, qq{"$tname"};
+ printf qq{ALTER TABLE %-18s RENAME TO "${table_prefix}$tname";\n}, qq{"$tname"};
}
}
@@ -391,9 +391,9 @@ if (length $table_prefix) {
$maxsize = length "$_$table_prefix" if length "$_$table_prefix" > $maxsize;
}
for my $t (@torder) {
- next if $t eq '---' or $t eq 'text';
+ next if $t eq '---' or $t eq 'text' or $t eq 'user';
my $tname = $special{$t}||$t;
- printf qq{ALTER TABLE %*s RENAME TO "$tname"\n}, $maxsize+1, qq{"${table_prefix}$tname"};
+ printf qq{ALTER TABLE %*s RENAME TO "$tname";\n}, $maxsize+1, qq{"${table_prefix}$tname"};
}
}
@@ -409,13 +409,13 @@ for my $t (sort keys %tz) {
## Reset sequences
print q{
SELECT setval('filearchive_fa_id_seq', 1+coalesce(max(fa_id) ,0),false) FROM filearchive;
-SELECT setval('ipblocks_ipb_id_val', 1+coalesce(max(ipb_id) ,0),false) FROM ipblocks;
+SELECT setval('ipblocks_ipb_id_seq', 1+coalesce(max(ipb_id) ,0),false) FROM ipblocks;
SELECT setval('job_job_id_seq', 1+coalesce(max(job_id) ,0),false) FROM job;
-SELECT setval('log_log_id_seq', 1+coalesce(max(log_id) ,0),false) FROM logging;
+SELECT setval('logging_log_id_seq', 1+coalesce(max(log_id) ,0),false) FROM logging;
SELECT setval('page_page_id_seq', 1+coalesce(max(page_id),0),false) FROM page;
-SELECT setval('pr_id_val', 1+coalesce(max(pr_id) ,0),false) FROM page_restrictions;
-SELECT setval('rc_rc_id_seq', 1+coalesce(max(rc_id) ,0),false) FROM recentchanges;
-SELECT setval('rev_rev_id_val', 1+coalesce(max(rev_id) ,0),false) FROM revision;
+SELECT setval('page_restrictions_pr_id_seq', 1+coalesce(max(pr_id) ,0),false) FROM page_restrictions;
+SELECT setval('recentchanges_rc_id_seq', 1+coalesce(max(rc_id) ,0),false) FROM recentchanges;
+SELECT setval('revision_rev_id_seq', 1+coalesce(max(rev_id) ,0),false) FROM revision;
SELECT setval('text_old_id_seq', 1+coalesce(max(old_id) ,0),false) FROM pagecontent;
SELECT setval('trackbacks_tb_id_seq', 1+coalesce(max(tb_id) ,0),false) FROM trackbacks;
SELECT setval('user_user_id_seq', 1+coalesce(max(user_id),0),false) FROM mwuser;
diff --git a/maintenance/postgres/tables.sql b/maintenance/postgres/tables.sql
index 8e869da7..ac0258ff 100644
--- a/maintenance/postgres/tables.sql
+++ b/maintenance/postgres/tables.sql
@@ -9,6 +9,21 @@
BEGIN;
SET client_min_messages = 'ERROR';
+DROP SEQUENCE IF EXISTS user_user_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS page_page_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS revision_rev_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS page_restrictions_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS ipblocks_ipb_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS recentchanges_rc_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS logging_log_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS trackbacks_tb_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS job_job_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS category_cat_id_seq CASCADE;
+DROP FUNCTION IF EXISTS page_deleted() CASCADE;
+DROP FUNCTION IF EXISTS ts2_page_title() CASCADE;
+DROP FUNCTION IF EXISTS ts2_page_text() CASCADE;
+DROP FUNCTION IF EXISTS add_interwiki(TEXT,INT,SMALLINT) CASCADE;
+
CREATE SEQUENCE user_user_id_seq MINVALUE 0 START WITH 0;
CREATE TABLE mwuser ( -- replace reserved word 'user'
user_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('user_user_id_seq'),
@@ -39,6 +54,12 @@ CREATE TABLE user_groups (
);
CREATE UNIQUE INDEX user_groups_unique ON user_groups (ug_user, ug_group);
+CREATE TABLE user_former_groups (
+ ufg_user INTEGER NULL REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ ufg_group TEXT NOT NULL
+);
+CREATE UNIQUE INDEX ufg_user_group ON user_former_groups (ufg_user, ufg_group);
+
CREATE TABLE user_newtalk (
user_id INTEGER NOT NULL REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
user_ip TEXT NULL,
@@ -63,12 +84,12 @@ CREATE TABLE page (
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_main_title ON page (page_title text_pattern_ops) WHERE page_namespace = 0;
+CREATE INDEX page_talk_title ON page (page_title text_pattern_ops) WHERE page_namespace = 1;
+CREATE INDEX page_user_title ON page (page_title text_pattern_ops) WHERE page_namespace = 2;
+CREATE INDEX page_utalk_title ON page (page_title text_pattern_ops) WHERE page_namespace = 3;
+CREATE INDEX page_project_title ON page (page_title text_pattern_ops) WHERE page_namespace = 4;
+CREATE INDEX page_mediawiki_title ON page (page_title text_pattern_ops) WHERE page_namespace = 8;
CREATE INDEX page_random_idx ON page (page_random);
CREATE INDEX page_len_idx ON page (page_len);
@@ -384,14 +405,6 @@ CREATE TABLE watchlist (
CREATE UNIQUE INDEX wl_user_namespace_title ON watchlist (wl_namespace, wl_title, wl_user);
CREATE INDEX wl_user ON watchlist (wl_user);
-CREATE TABLE math (
- math_inputhash BYTEA NOT NULL UNIQUE,
- math_outputhash BYTEA NOT NULL,
- math_html_conservativeness SMALLINT NOT NULL,
- math_html TEXT,
- math_mathml TEXT
-);
-
CREATE TABLE interwiki (
iw_prefix TEXT NOT NULL UNIQUE,
diff --git a/maintenance/preprocessDump.php b/maintenance/preprocessDump.php
new file mode 100644
index 00000000..ad9b4f14
--- /dev/null
+++ b/maintenance/preprocessDump.php
@@ -0,0 +1,86 @@
+getStripList();
+ }
+
+ public function __construct() {
+ parent::__construct();
+ $this->addOption( 'cache', 'Use and populate the preprocessor cache.', false, false );
+ $this->addOption( 'preprocessor', 'Preprocessor to use.', false, false );
+ }
+
+ public function getDbType() {
+ return Maintenance::DB_NONE;
+ }
+
+ public function checkOptions() {
+ global $wgParser, $wgParserConf, $wgPreprocessorCacheThreshold;
+
+ if ( !$this->hasOption( 'cache' ) ) {
+ $wgPreprocessorCacheThreshold = false;
+ }
+
+ if ( $this->hasOption( 'preprocessor' ) ) {
+ $name = $this->getOption( 'preprocessor' );
+ } elseif ( isset( $wgParserConf['preprocessorClass'] ) ) {
+ $name = $wgParserConf['preprocessorClass'];
+ } else {
+ $name = 'Preprocessor_DOM';
+ }
+
+ $wgParser->firstCallInit();
+ $this->mPreprocessor = new $name( $this );
+ }
+
+ /**
+ * Callback function for each revision, preprocessToObj()
+ * @param $rev Revision
+ */
+ public function processRevision( $rev ) {
+ try {
+ $this->mPreprocessor->preprocessToObj( $rev->getText(), 0 );
+ }
+ catch(Exception $e) {
+ $this->error("Caught exception " . $e->getMessage() . " in " . $rev->getTitle()->getPrefixedText() );
+ }
+ }
+}
+
+$maintClass = "PreprocessDump";
+require_once( RUN_MAINTENANCE_IF_MAIN );
+
diff --git a/maintenance/preprocessorFuzzTest.php b/maintenance/preprocessorFuzzTest.php
index 31b372c2..9dee67e2 100644
--- a/maintenance/preprocessorFuzzTest.php
+++ b/maintenance/preprocessorFuzzTest.php
@@ -1,5 +1,22 @@
templates = array();
}
+ /**
+ * @param $title Title
+ */
function templateHook( $title ) {
$titleText = $title->getPrefixedDBkey();
@@ -173,10 +192,10 @@ class PPFuzzTest {
$wgUser->mFrom = 'name';
$wgUser->ppfz_test = $this;
- $options = new ParserOptions;
+ $options = ParserOptions::newFromUser( $wgUser );
$options->setTemplateCallback( array( $this, 'templateHook' ) );
$options->setTimestamp( wfTimestampNow() );
- $this->output = call_user_func( array( $wgParser, $this->entryPoint ), $this->mainText, $this->title->getPrefixedText(), $options );
+ $this->output = call_user_func( array( $wgParser, $this->entryPoint ), $this->mainText, $this->title, $options );
return $this->output;
}
@@ -200,7 +219,7 @@ class PPFuzzTest {
}
class PPFuzzUser extends User {
- var $ppfz_test;
+ var $ppfz_test, $mDataLoaded;
function load() {
if ( $this->mDataLoaded ) {
@@ -210,13 +229,13 @@ class PPFuzzUser extends User {
$this->loadDefaults( $this->mName );
}
- function getOption( $option, $defaultOverride = '' ) {
- if ( $option === 'fancysig' ) {
+ function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
+ if ( $oname === 'fancysig' ) {
return $this->ppfz_test->fancySig;
- } elseif ( $option === 'nickname' ) {
+ } elseif ( $oname === 'nickname' ) {
return $this->ppfz_test->nickname;
} else {
- return parent::getOption( $option, $defaultOverride );
+ return parent::getOption( $oname, $defaultOverride, $ignoreHidden );
}
}
}
diff --git a/maintenance/protect.php b/maintenance/protect.php
index baef45fb..aab84d68 100644
--- a/maintenance/protect.php
+++ b/maintenance/protect.php
@@ -1,5 +1,7 @@
> /home/tstarling/open/proxy.log`;
diff --git a/maintenance/purgeList.php b/maintenance/purgeList.php
index 2c0308c0..85a7aae2 100644
--- a/maintenance/purgeList.php
+++ b/maintenance/purgeList.php
@@ -26,10 +26,22 @@ 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 );
+ $this->addOption( 'purge', 'Whether to update page_touched.' , false, false );
+ $this->addOption( 'namespace', 'Namespace number', false, true );
+ $this->setBatchSize( 100 );
}
public function execute() {
+ if( $this->hasOption( 'namespace' ) ) {
+ $this->purgeNamespace();
+ } else {
+ $this->purgeList();
+ }
+ $this->output( "Done!\n" );
+ }
+
+ /** Purge URL coming from stdin */
+ private function purgeList() {
$stdin = $this->getStdin();
$urls = array();
@@ -40,7 +52,7 @@ class PurgeList extends Maintenance {
} elseif ( $page !== '' ) {
$title = Title::newFromText( $page );
if ( $title ) {
- $url = $title->getFullUrl();
+ $url = $title->getInternalUrl();
$this->output( "$url\n" );
$urls[] = $url;
if ( $this->getOption( 'purge' ) ) {
@@ -51,13 +63,69 @@ class PurgeList extends Maintenance {
}
}
}
+ $this->sendPurgeRequest( $urls );
+ }
+
+ /** Purge a namespace given by --namespace */
+ private function purgeNamespace() {
+ $dbr = wfGetDB( DB_SLAVE );
+ $ns = $dbr->addQuotes( $this->getOption( 'namespace') );
+
+ $result = $dbr->select(
+ array( 'page' ),
+ array( 'page_namespace', 'page_title' ),
+ array( "page_namespace = $ns" ),
+ __METHOD__,
+ array( 'ORDER BY' => 'page_id' )
+ );
+
+ $start = 0;
+ $end = $dbr->numRows( $result );
+ $this->output( "Will purge $end pages from namespace $ns\n" );
+
+ # Do remaining chunk
+ $end += $this->mBatchSize - 1;
+ $blockStart = $start;
+ $blockEnd = $start + $this->mBatchSize - 1;
+
+ while( $blockEnd <= $end ) {
+ # Select pages we will purge:
+ $result = $dbr->select(
+ array( 'page' ),
+ array( 'page_namespace', 'page_title' ),
+ array( "page_namespace = $ns" ),
+ __METHOD__,
+ array( # conditions
+ 'ORDER BY' => 'page_id',
+ 'LIMIT' => $this->mBatchSize,
+ 'OFFSET' => $blockStart,
+ )
+ );
+ # Initialize/reset URLs to be purged
+ $urls = array();
+ foreach( $result as $row ) {
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $url = $title->getInternalUrl();
+ $urls[] = $url;
+ }
+
+ $this->sendPurgeRequest( $urls );
+
+ $blockStart += $this->mBatchSize;
+ $blockEnd += $this->mBatchSize;
+ }
+ }
- $this->output( "Purging " . count( $urls ) . " urls...\n" );
+ /**
+ * Helper to purge an array of $urls
+ * @param $urls array List of URLS to purge from squids
+ */
+ private function sendPurgeRequest( $urls ) {
+ $this->output( "Purging " . count( $urls ). " urls\n" );
$u = new SquidUpdate( $urls );
$u->doUpdate();
-
- $this->output( "Done!\n" );
}
+
}
$maintClass = "PurgeList";
diff --git a/maintenance/purgeOldText.inc b/maintenance/purgeOldText.inc
index 381d62a7..45a7ae28 100644
--- a/maintenance/purgeOldText.inc
+++ b/maintenance/purgeOldText.inc
@@ -3,6 +3,21 @@
/**
* Support functions for cleaning up redundant text records
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup Maintenance
* @author Rob Church
@@ -29,6 +44,7 @@ function PurgeRedundantText( $delete = false ) {
# Get "active" text records from the archive table
echo( "Searching for active text records in archive table..." );
$res = $dbw->query( "SELECT DISTINCT ar_text_id FROM $tbl_arc" );
+ $cur = array();
foreach ( $res as $row ) {
$cur[] = $row->ar_text_id;
}
diff --git a/maintenance/purgeParserCache.php b/maintenance/purgeParserCache.php
new file mode 100644
index 00000000..4b550b6e
--- /dev/null
+++ b/maintenance/purgeParserCache.php
@@ -0,0 +1,43 @@
+addDescription( "Remove old objects from the parser cache. " .
+ "This only works when the parser cache is in an SQL database." );
+ $this->addOption( 'expiredate', 'Delete objects expiring before this date.', false, true );
+ $this->addOption( 'age',
+ 'Delete objects created more than this many seconds ago, assuming $wgParserCacheExpireTime '.
+ 'has been consistent.',
+ false, true );
+ }
+
+ function execute() {
+ $inputDate = $this->getOption( 'expiredate' );
+ $inputAge = $this->getOption( 'age' );
+ if ( $inputDate !== null ) {
+ $date = wfTimestamp( TS_MW, strtotime( $inputDate ) );
+ } elseif ( $inputAge !== null ) {
+ global $wgParserCacheExpireTime;
+ $date = wfTimestamp( TS_MW, time() + $wgParserCacheExpireTime - intval( $inputAge ) );
+ } else {
+ echo "Must specify either --expiredate or --age\n";
+ exit( 1 );
+ }
+
+ $english = Language::factory( 'en' );
+ echo "Deleting objects expiring before " . $english->timeanddate( $date ) . "\n";
+
+ $pc = wfGetParserCacheStorage();
+ $success = $pc->deleteObjectsExpiringBefore( $date );
+ if ( !$success ) {
+ echo "Cannot purge this kind of parser cache.\n";
+ exit( 1 );
+ }
+ echo "Done\n";
+ }
+}
+$maintClass = 'PurgeParserCache';
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/maintenance/purgeStaleMemcachedText.php b/maintenance/purgeStaleMemcachedText.php
new file mode 100644
index 00000000..c4f5006f
--- /dev/null
+++ b/maintenance/purgeStaleMemcachedText.php
@@ -0,0 +1,34 @@
+selectField( 'text', 'max(old_id)' );
+ $latestReplicatedTextId = $db->selectField( array( 'recentchanges', 'revision' ), 'rev_text_id',
+ array( 'rev_id = rc_this_oldid', "rc_timestamp < '20101225183000'"), 'purgeStaleMemcachedText',
+ array( 'ORDER BY' => 'rc_timestamp DESC' ) );
+ $latestReplicatedTextId -= 100; # A bit of paranoia
+
+ echo "Going to purge text entries from $latestReplicatedTextId to $maxTextId in $wgDBname\n";
+
+ for ( $i = $latestReplicatedTextId; $i < $maxTextId; $i++ ) {
+ $key = wfMemcKey( 'revisiontext', 'textid', $i );
+
+ while (1) {
+ if (! $wgMemc->delete( $key ) ) {
+ echo "Memcache delete for $key returned false\n";
+ }
+ if ( $wgMemc->get( $key ) ) {
+ echo "There's still content in $key!\n";
+ } else {
+ break;
+ }
+ }
+
+ }
+}
+
+purgeStaleMemcachedText();
+
diff --git a/maintenance/reassignEdits.php b/maintenance/reassignEdits.php
index 039422b3..bb34e51c 100644
--- a/maintenance/reassignEdits.php
+++ b/maintenance/reassignEdits.php
@@ -126,8 +126,8 @@ class ReassignEdits extends Maintenance {
* i.e. a user => id mapping, or a user_text => text mapping
*
* @param $user User for the condition
- * @param $idfield Field name containing the identifier
- * @param $utfield Field name containing the user text
+ * @param $idfield string Field name containing the identifier
+ * @param $utfield string Field name containing the user text
* @return array
*/
private function userConditions( &$user, $idfield, $utfield ) {
diff --git a/maintenance/rebuildFileCache.php b/maintenance/rebuildFileCache.php
index a1aecd80..84ada11c 100644
--- a/maintenance/rebuildFileCache.php
+++ b/maintenance/rebuildFileCache.php
@@ -33,7 +33,7 @@ class RebuildFileCache extends Maintenance {
public function execute() {
global $wgUseFileCache, $wgDisableCounters, $wgContentNamespaces, $wgRequestTime;
- global $wgTitle, $wgArticle, $wgOut, $wgUser;
+ global $wgTitle, $wgOut;
if ( !$wgUseFileCache ) {
$this->error( "Nothing to do -- \$wgUseFileCache is disabled.", true );
}
@@ -54,7 +54,6 @@ class RebuildFileCache extends Maintenance {
}
$_SERVER['HTTP_ACCEPT_ENCODING'] = 'bgzip'; // hack, no real client
- OutputPage::setEncodings(); # Not really used yet
# Do remaining chunk
$end += $this->mBatchSize - 1;
@@ -73,16 +72,17 @@ class RebuildFileCache extends Maintenance {
foreach ( $res as $row ) {
$rebuilt = false;
$wgRequestTime = wfTime(); # bug 22852
+ $context = new RequestContext;
$wgTitle = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
+ $context->setTitle( $wgTitle );
if ( null == $wgTitle ) {
$this->output( "Page {$row->page_id} has bad title\n" );
continue; // broken title?
}
- $wgOut->setTitle( $wgTitle ); // set display title
- $wgUser->getSkin( $wgTitle ); // set skin title
- $wgArticle = new Article( $wgTitle );
+ $wgOut = $context->getOutput(); // set display title
+ $article = new Article( $wgTitle );
// If the article is cacheable, then load it
- if ( $wgArticle->isFileCacheable() ) {
+ if ( $article->isFileCacheable() ) {
$cache = new HTMLFileCache( $wgTitle );
if ( $cache->isFileCacheGood() ) {
if ( $overwrite ) {
@@ -93,12 +93,13 @@ class RebuildFileCache extends Maintenance {
}
}
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 = false; // hack, we don't want $article fiddling with filecache
+ $article->view();
+ wfSuppressWarnings(); // header notices
+ $wgOut->output();
+ wfRestoreWarnings();
$wgUseFileCache = true;
ob_end_clean(); // clear buffer
- $wgOut = new OutputPage(); // empty out any output page garbage
if ( $rebuilt )
$this->output( "Re-cached page {$row->page_id}\n" );
else
@@ -110,15 +111,13 @@ class RebuildFileCache extends Maintenance {
}
$blockStart += $this->mBatchSize;
$blockEnd += $this->mBatchSize;
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
}
$this->output( "Done!\n" );
// Remove these to be safe
if ( isset( $wgTitle ) )
unset( $wgTitle );
- if ( isset( $wgArticle ) )
- unset( $wgArticle );
}
}
diff --git a/maintenance/rebuildImages.php b/maintenance/rebuildImages.php
index c61d9480..17111831 100644
--- a/maintenance/rebuildImages.php
+++ b/maintenance/rebuildImages.php
@@ -33,9 +33,19 @@
require_once( dirname( __FILE__ ) . '/Maintenance.php' );
class ImageBuilder extends Maintenance {
+
+ /**
+ * @var DatabaseBase
+ */
+ protected $dbw;
+
function __construct() {
parent::__construct();
+ global $wgUpdateCompatibleMetadata;
+ //make sure to update old, but compatible img_metadata fields.
+ $wgUpdateCompatibleMetadata = true;
+
$this->mDescription = 'Script to update image metadata records';
$this->addOption( 'missing', 'Check for files without associated database record' );
@@ -57,6 +67,9 @@ class ImageBuilder extends Maintenance {
}
}
+ /**
+ * @return FileRepo
+ */
function getRepo() {
if ( !isset( $this->repo ) ) {
$this->repo = RepoGroup::singleton()->getLocalRepo();
diff --git a/maintenance/rebuildInterwiki.php b/maintenance/rebuildInterwiki.php
index 3da920f8..25aea2de 100644
--- a/maintenance/rebuildInterwiki.php
+++ b/maintenance/rebuildInterwiki.php
@@ -3,30 +3,28 @@
* Rebuild interwiki table using the file on meta and the language list
* Wikimedia specific!
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @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";
- }
-}
+require_once( dirname( __FILE__ ) . '/Site.php' );
require_once( dirname( __FILE__ ) . '/Maintenance.php' );
diff --git a/maintenance/rebuildLocalisationCache.php b/maintenance/rebuildLocalisationCache.php
index 0ca99610..831d808a 100644
--- a/maintenance/rebuildLocalisationCache.php
+++ b/maintenance/rebuildLocalisationCache.php
@@ -112,9 +112,9 @@ class RebuildLocalisationCache extends Maintenance {
* Helper function to rebuild list of languages codes. Prints the code
* for each language which is rebuilt.
* @param $codes list List of language codes to rebuild.
- * @param $lc object Instance of LocalisationCache_BulkLoad (?)
- * @param $force bool Rebuild up-to-date languages
- * @return int Number of rebuilt languages
+ * @param $lc LocalisationCache Instance of LocalisationCache_BulkLoad (?)
+ * @param $force bool Rebuild up-to-date languages
+ * @return int Number of rebuilt languages
*/
private function doRebuild( $codes, $lc, $force ) {
$numRebuilt = 0;
@@ -127,6 +127,15 @@ class RebuildLocalisationCache extends Maintenance {
}
return $numRebuilt;
}
+
+ /**
+ * Sets whether a run of this maintenance script has the force parameter set
+ *
+ * @param bool $forced
+ */
+ public function setForce( $forced = true ) {
+ $this->mOptions['force'] = $forced;
+ }
}
$maintClass = "RebuildLocalisationCache";
diff --git a/maintenance/rebuildall.php b/maintenance/rebuildall.php
index 82619048..dbbed86d 100644
--- a/maintenance/rebuildall.php
+++ b/maintenance/rebuildall.php
@@ -30,9 +30,8 @@ class RebuildAll extends Maintenance {
}
public function execute() {
- global $wgDBtype;
// Rebuild the text index
- if ( $wgDBtype != 'postgres' ) {
+ if ( wfGetDB( DB_SLAVE )->getType() != 'postgres' ) {
$this->output( "** Rebuilding fulltext search index (if you abort this will break searching; run this script again to fix):\n" );
$rebuildText = $this->runChild( 'RebuildTextIndex', 'rebuildtextindex.php' );
$rebuildText->execute();
diff --git a/maintenance/rebuildtextindex.php b/maintenance/rebuildtextindex.php
index 46282c18..04606c75 100644
--- a/maintenance/rebuildtextindex.php
+++ b/maintenance/rebuildtextindex.php
@@ -28,6 +28,10 @@ require_once( dirname( __FILE__ ) . '/Maintenance.php' );
class RebuildTextIndex extends Maintenance {
const RTI_CHUNK_SIZE = 500;
+
+ /**
+ * @var DatabaseBase
+ */
private $db;
public function __construct() {
@@ -40,10 +44,11 @@ class RebuildTextIndex extends Maintenance {
}
public function execute() {
- global $wgTitle, $wgDBtype;
+ global $wgTitle;
// Shouldn't be needed for Postgres
- if ( $wgDBtype == 'postgres' ) {
+ $this->db = wfGetDB( DB_MASTER );
+ if ( $this->db->getType() == 'postgres' ) {
$this->error( "This script is not needed when using Postgres.\n", true );
}
diff --git a/maintenance/refreshImageMetadata.php b/maintenance/refreshImageMetadata.php
new file mode 100644
index 00000000..ec612183
--- /dev/null
+++ b/maintenance/refreshImageMetadata.php
@@ -0,0 +1,199 @@
+mDescription = 'Script to update image metadata records';
+ $this->setBatchSize( 200 );
+
+ $this->addOption( 'force', 'Reload metadata from file even if the metadata looks ok', false, false, 'f' );
+ $this->addOption( 'broken-only', 'Only fix really broken records, leave old but still compatible records alone.' );
+ $this->addOption( 'verbose', 'Output extra information about each upgraded/non-upgraded file.', false, false, 'v' );
+ $this->addOption( 'start', 'Name of file to start with', false, true );
+ $this->addOption( 'end', 'Name of file to end with', false, true );
+
+ $this->addOption( 'mime', '(Inefficient!) Only refresh files with this mime type. Can accept wild-card image/*' , false, true );
+ $this->addOption( 'metadata-contains', '(Inefficient!) Only refresh files where the img_metadata field contains this string. Can be used if its known a specific property was being extracted incorrectly.', false, true );
+
+ }
+
+ public function execute() {
+ $force = $this->hasOption( 'force' );
+ $brokenOnly = $this->hasOption( 'broken-only' );
+ $verbose = $this->hasOption( 'verbose' );
+ $start = $this->getOption( 'start', false );
+ $this->setupParameters( $force, $brokenOnly );
+
+ $upgraded = 0;
+ $leftAlone = 0;
+ $error = 0;
+
+ $dbw = wfGetDB( DB_MASTER );
+ if ( $this->mBatchSize <= 0 ) {
+ $this->error( "Batch size is too low...", 12 );
+ }
+
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ $conds = $this->getConditions( $dbw );
+
+ // For the WHERE img_name > 'foo' condition that comes after doing a batch
+ $conds2 = array();
+ if ( $start !== false ) {
+ $conds2[] = 'img_name >= ' . $dbw->addQuotes( $start );
+ }
+
+ $options = array(
+ 'LIMIT' => $this->mBatchSize,
+ 'ORDER BY' => 'img_name ASC',
+ );
+
+ do {
+ $res = $dbw->select(
+ 'image',
+ '*',
+ array_merge( $conds, $conds2 ),
+ __METHOD__,
+ $options
+ );
+
+ if ( $res->numRows() > 0 ) {
+ $row1 = $res->current();
+ $this->output( "Processing next {$this->mBatchSize} rows starting with {$row1->img_name}.\n");
+ $res->rewind();
+ } else {
+ $this->error( "No images to process.", 4 );
+ }
+
+ foreach ( $res as $row ) {
+ $file = $repo->newFileFromRow( $row );
+ if ( $file->getUpgraded() ) {
+ // File was upgraded.
+ $upgraded++;
+ $newLength = strlen( $file->getMetadata() );
+ $oldLength = strlen( $row->img_metadata );
+ if ( $newLength < $oldLength - 5 ) {
+ // If after updating, the metadata is smaller then
+ // what it was before, that's probably not a good thing
+ // because we extract more data with time, not less.
+ // Thus this probably indicates an error of some sort,
+ // or at the very least is suspicious. Have the - 5 just
+ // to weed out any inconsequential changes.
+ $error++;
+ $this->output( "Warning: File:{$row->img_name} used to have " .
+ "$oldLength bytes of metadata but now has $newLength bytes.\n" );
+ } elseif ( $verbose ) {
+ $this->output("Refreshed File:{$row->img_name}.\n" );
+ }
+ } else {
+ $leftAlone++;
+ if ( $force ) {
+ $file->upgradeRow();
+ $newLength = strlen( $file->getMetadata() );
+ $oldLength = strlen( $row->img_metadata );
+ if ( $newLength < $oldLength - 5 ) {
+ $error++;
+ $this->output( "Warning: File:{$row->img_name} used to have " .
+ "$oldLength bytes of metadata but now has $newLength bytes. (forced)\n" );
+
+ }
+ if ( $verbose ) {
+ $this->output("Forcibly refreshed File:{$row->img_name}.\n" );
+ }
+ }
+ else {
+ if ( $verbose ) {
+ $this->output( "Skipping File:{$row->img_name}.\n" );
+ }
+ }
+ }
+
+ }
+ $conds2 = array( 'img_name > ' . $dbw->addQuotes( $row->img_name ) );
+ wfWaitForSlaves();
+ } while( $res->numRows() === $this->mBatchSize );
+
+ $total = $upgraded + $leftAlone;
+ if ( $force ) {
+ $this->output( "\nFinished refreshing file metadata for $total files. $upgraded needed to be refreshed, $leftAlone did not need to be but were refreshed anyways, and $error refreshes were suspicious.\n" );
+ } else {
+ $this->output( "\nFinished refreshing file metadata for $total files. $upgraded were refreshed, $leftAlone were already up to date, and $error refreshes were suspicious.\n" );
+ }
+ }
+
+ function getConditions( $dbw ) {
+ $conds = array();
+
+ $end = $this->getOption( 'end', false );
+ $mime = $this->getOption( 'mime', false );
+ $like = $this->getOption( 'metadata-contains', false );
+
+ if ( $end !== false ) {
+ $conds[] = 'img_name <= ' . $dbw->addQuotes( $end ) ;
+ }
+ if ( $mime !== false ) {
+ list( $major, $minor ) = File::splitMime( $mime );
+ $conds['img_major_mime'] = $major;
+ if ( $minor !== '*' ) {
+ $conds['img_minor_mime'] = $minor;
+ }
+ }
+ if ( $like ) {
+ $conds[] = 'img_metadata ' . $dbw->buildLike( $dbw->anyString(), $like, $dbw->anyString() );
+ }
+ return $conds;
+ }
+
+ function setupParameters( $force, $brokenOnly ) {
+ global $wgUpdateCompatibleMetadata, $wgReadOnly;
+
+ if ( $brokenOnly ) {
+ $wgUpdateCompatibleMetadata = false;
+ } else {
+ $wgUpdateCompatibleMetadata = true;
+ }
+
+ if ( $brokenOnly && $force ) {
+ $this->error( 'Cannot use --broken-only and --force together. ', 2 );
+ }
+ }
+}
+
+
+$maintClass = 'RefreshImageMetadata';
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/maintenance/refreshLinks.php b/maintenance/refreshLinks.php
index 144e96c5..0515ef1b 100644
--- a/maintenance/refreshLinks.php
+++ b/maintenance/refreshLinks.php
@@ -1,5 +1,7 @@
setOption( 'math', MW_MATH_SOURCE );
+ // Give extensions a chance to optimize settings
+ wfRunHooks( 'MaintenanceRefreshLinksInit', array( $this ) );
# Don't generate extension images (e.g. Timeline)
- if ( method_exists( $wgParser, "clearTagHooks" ) ) {
- $wgParser->clearTagHooks();
- }
+ $wgParser->clearTagHooks();
# Don't use HTML tidy
$wgUseTidy = false;
@@ -79,23 +79,35 @@ class RefreshLinks extends Maintenance {
if ( $oldRedirectsOnly ) {
# This entire code path is cut-and-pasted from below. Hurrah.
- $res = $dbr->query(
- "SELECT page_id " .
- "FROM page " .
- "LEFT JOIN redirect ON page_id=rd_from " .
- "WHERE page_is_redirect=1 AND rd_from IS NULL AND " .
- ( $end == 0 ? "page_id >= $start"
- : "page_id BETWEEN $start AND $end" ),
- __METHOD__
+
+ $conds = array(
+ "page_is_redirect=1",
+ "rd_from IS NULL"
+ );
+
+ if ( $end == 0 ) {
+ $conds[] = "page_id >= $start";
+ } else {
+ $conds[] = "page_id BETWEEN $start AND $end";
+ }
+
+ $res = $dbr->select(
+ array( 'page', 'redirect' ),
+ 'page_id',
+ $conds,
+ __METHOD__,
+ array(),
+ array( 'redirect' => array( "LEFT JOIN", "page_id=rd_from" ) )
);
$num = $dbr->numRows( $res );
$this->output( "Refreshing $num old redirects from $start...\n" );
$i = 0;
+
foreach ( $res as $row ) {
if ( !( ++$i % $reportingInterval ) ) {
$this->output( "$i\n" );
- wfWaitForSlaves( $maxLag );
+ wfWaitForSlaves();
}
$this->fixRedirect( $row->page_id );
}
@@ -115,12 +127,13 @@ class RefreshLinks extends Maintenance {
foreach ( $res as $row ) {
if ( !( ++$i % $reportingInterval ) ) {
$this->output( "$i\n" );
- wfWaitForSlaves( $maxLag );
+ wfWaitForSlaves();
}
- if ( $redirectsOnly )
+ if ( $redirectsOnly ) {
$this->fixRedirect( $row->page_id );
- else
+ } else {
self::fixLinksFromArticle( $row->page_id );
+ }
}
} else {
if ( !$end ) {
@@ -135,7 +148,7 @@ class RefreshLinks extends Maintenance {
if ( !( $id % $reportingInterval ) ) {
$this->output( "$id\n" );
- wfWaitForSlaves( $maxLag );
+ wfWaitForSlaves();
}
$this->fixRedirect( $id );
}
@@ -148,7 +161,7 @@ class RefreshLinks extends Maintenance {
if ( !( $id % $reportingInterval ) ) {
$this->output( "$id\n" );
- wfWaitForSlaves( $maxLag );
+ wfWaitForSlaves();
}
self::fixLinksFromArticle( $id );
}
@@ -161,29 +174,27 @@ class RefreshLinks extends Maintenance {
* @param $id int The page_id of the redirect
*/
private function fixRedirect( $id ) {
- global $wgTitle, $wgArticle;
-
- $wgTitle = Title::newFromID( $id );
+ $title = Title::newFromID( $id );
$dbw = wfGetDB( DB_MASTER );
- if ( is_null( $wgTitle ) ) {
+ if ( is_null( $title ) ) {
// This page doesn't exist (any more)
// Delete any redirect table entry for it
$dbw->delete( 'redirect', array( 'rd_from' => $id ),
__METHOD__ );
return;
}
- $wgArticle = new Article( $wgTitle );
+ $article = new Article( $title );
- $rt = $wgArticle->followRedirect();
+ $rt = $article->followRedirect();
if ( !$rt || !is_object( $rt ) ) {
- // $wgTitle is not a redirect
+ // $title is not a redirect
// Delete any redirect table entry for it
$dbw->delete( 'redirect', array( 'rd_from' => $id ),
__METHOD__ );
} else {
- $wgArticle->updateRedirectOn( $dbw, $rt );
+ $article->updateRedirectOn( $dbw, $rt );
}
}
@@ -192,31 +203,31 @@ class RefreshLinks extends Maintenance {
* @param $id int The page_id
*/
public static function fixLinksFromArticle( $id ) {
- global $wgTitle, $wgParser;
+ global $wgParser;
- $wgTitle = Title::newFromID( $id );
+ $title = Title::newFromID( $id );
$dbw = wfGetDB( DB_MASTER );
LinkCache::singleton()->clear();
- if ( is_null( $wgTitle ) ) {
+ if ( is_null( $title ) ) {
return;
}
$dbw->begin();
- $revision = Revision::newFromTitle( $wgTitle );
+ $revision = Revision::newFromTitle( $title );
if ( !$revision ) {
return;
}
$options = new ParserOptions;
- $parserOutput = $wgParser->parse( $revision->getText(), $wgTitle, $options, true, true, $revision->getId() );
- $update = new LinksUpdate( $wgTitle, $parserOutput, false );
+ $parserOutput = $wgParser->parse( $revision->getText(), $title, $options, true, true, $revision->getId() );
+ $update = new LinksUpdate( $title, $parserOutput, false );
$update->doUpdate();
$dbw->commit();
}
- /*
+ /**
* Removes non-existing links from pages from pagelinks, imagelinks,
* categorylinks, templatelinks and externallinks tables.
*
@@ -226,7 +237,7 @@ class RefreshLinks extends Maintenance {
* @author Merlijn van Deen
*/
private function deleteLinksFromNonexistent( $maxLag = 0, $batchSize = 100 ) {
- wfWaitForSlaves( $maxLag );
+ wfWaitForSlaves();
$dbw = wfGetDB( DB_MASTER );
@@ -240,6 +251,9 @@ class RefreshLinks extends Maintenance {
'categorylinks' => 'cl_from',
'templatelinks' => 'tl_from',
'externallinks' => 'el_from',
+ 'iwlinks' => 'iwl_from',
+ 'langlinks' => 'll_from',
+ 'redirect' => 'rd_from',
);
foreach ( $linksTables as $table => $field ) {
@@ -262,7 +276,7 @@ class RefreshLinks extends Maintenance {
$counter++;
$list[] = $row->$field;
if ( ( $counter % $batchSize ) == 0 ) {
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
$dbw->delete( $table, array( $field => $list ), __METHOD__ );
$this->output( $counter . ".." );
diff --git a/maintenance/resetUserTokens.php b/maintenance/resetUserTokens.php
new file mode 100644
index 00000000..a1c4eaeb
--- /dev/null
+++ b/maintenance/resetUserTokens.php
@@ -0,0 +1,73 @@
+
+ */
+
+require_once( dirname( __FILE__ ) . '/Maintenance.php' );
+
+class ResetUserTokens extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = "Reset the user_token of all users on the wiki. Note that this may log some of them out.";
+ $this->addOption( 'nowarn', "Hides the 5 seconds warning", false, false );
+ }
+
+ public function execute() {
+
+ if ( !$this->getOption( 'nowarn' ) ) {
+ $this->output( "The script is about to reset the user_token for ALL USERS in the database.\n" );
+ $this->output( "This may log some of them out and is not necessary unless you believe your\n" );
+ $this->output( "user table has been compromised.\n" );
+ $this->output( "\n" );
+ $this->output( "Abort with control-c in the next five seconds (skip this countdown with --nowarn) ... " );
+ wfCountDown( 5 );
+ }
+
+ // We list user by user_id from one of the slave database
+ $dbr = wfGetDB( DB_SLAVE );
+ $result = $dbr->select( 'user',
+ array( 'user_id' ),
+ array(),
+ __METHOD__
+ );
+
+ foreach ( $result as $id ) {
+ $user = User::newFromId( $id->user_id );
+
+ $username = $user->getName();
+
+ $this->output( "Resetting user_token for $username: " );
+
+ // Change value
+ $user->setToken();
+ $user->saveSettings();
+
+ $this->output( " OK\n" );
+
+ }
+
+ }
+}
+
+$maintClass = "ResetUserTokens";
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/maintenance/runBatchedQuery.php b/maintenance/runBatchedQuery.php
index dd3680c9..9a6b6383 100644
--- a/maintenance/runBatchedQuery.php
+++ b/maintenance/runBatchedQuery.php
@@ -29,7 +29,6 @@ class BatchedQueryRunner extends Maintenance {
parent::__construct();
$this->mDescription = "Run a query repeatedly until it affects 0 rows, and wait for slaves in between.\n" .
"NOTE: You need to set a LIMIT clause yourself.";
- $this->addOption( 'wait', "Wait for replication lag to go down to this value. Default: 5", false, true );
}
public function execute() {
@@ -39,14 +38,14 @@ class BatchedQueryRunner extends Maintenance {
$query = $this->getArg();
$wait = $this->getOption( 'wait', 5 );
$n = 1;
- $dbw = wfGetDb( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
do {
$this->output( "Batch $n: " );
$n++;
$dbw->query( $query, __METHOD__ );
$affected = $dbw->affectedRows();
$this->output( "$affected rows\n" );
- wfWaitForSlaves( $wait );
+ wfWaitForSlaves();
} while ( $affected > 0 );
}
diff --git a/maintenance/runJobs.php b/maintenance/runJobs.php
index 79ea7bfe..0edf7ac9 100644
--- a/maintenance/runJobs.php
+++ b/maintenance/runJobs.php
@@ -31,6 +31,7 @@ class RunJobs extends Maintenance {
parent::__construct();
$this->mDescription = "Run pending jobs";
$this->addOption( 'maxjobs', 'Maximum number of jobs to run', false, true );
+ $this->addOption( 'maxtime', 'Maximum amount of wall-clock time', false, true );
$this->addOption( 'type', 'Type of job to run', false, true );
$this->addOption( 'procs', 'Number of processes to use', false, true );
}
@@ -48,11 +49,13 @@ class RunJobs extends Maintenance {
$this->error( "Invalid argument to --procs", true );
}
$fc = new ForkController( $procs );
- if ( $fc->start( $procs ) != 'child' ) {
+ if ( $fc->start() != 'child' ) {
exit( 0 );
}
}
- $maxJobs = $this->getOption( 'maxjobs', 10000 );
+ $maxJobs = $this->getOption( 'maxjobs', false );
+ $maxTime = $this->getOption( 'maxtime', false );
+ $startTime = time();
$type = $this->getOption( 'type', false );
$wgTitle = Title::newFromText( 'RunJobs.php' );
$dbw = wfGetDB( DB_MASTER );
@@ -69,7 +72,7 @@ class RunJobs extends Maintenance {
if ( !$job )
break;
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
$t = microtime( true );
$offset = $job->id;
$status = $job->run();
@@ -80,9 +83,13 @@ class RunJobs extends Maintenance {
} else {
$this->runJobsLog( $job->toString() . " t=$timeMs good" );
}
+
if ( $maxJobs && ++$n > $maxJobs ) {
break 2;
}
+ if ( $maxTime && time() - $startTime > $maxTime ) {
+ break 2;
+ }
}
}
}
diff --git a/maintenance/sqlite.inc b/maintenance/sqlite.inc
index 238fe82b..1f821917 100644
--- a/maintenance/sqlite.inc
+++ b/maintenance/sqlite.inc
@@ -1,4 +1,25 @@
hasOption( 'check-syntax' ) ) {
$this->checkSyntax();
+ return;
}
- if ( $wgDBtype != 'sqlite' ) {
+ $this->db = wfGetDB( DB_MASTER );
+
+ if ( $this->db->getType() != 'sqlite' ) {
$this->error( "This maintenance script requires a SQLite database.\n" );
return;
}
- $this->db = wfGetDB( DB_MASTER );
-
if ( $this->hasOption( 'vacuum' ) ) {
$this->vacuum();
}
diff --git a/maintenance/sqlite/archives/patch-archive_kill_ar_page_revid.sql b/maintenance/sqlite/archives/patch-archive_kill_ar_page_revid.sql
new file mode 100644
index 00000000..0f3e9c7f
--- /dev/null
+++ b/maintenance/sqlite/archives/patch-archive_kill_ar_page_revid.sql
@@ -0,0 +1,3 @@
+-- Used for killing the wrong index added during SVN for 1.17
+-- Won't affect most people, but it doesn't need to exist
+DROP INDEX IF EXISTS ar_page_revid;
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-rename-iwl_prefix.sql b/maintenance/sqlite/archives/patch-rename-iwl_prefix.sql
index 08c3ae5f..851a6b37 100644
--- a/maintenance/sqlite/archives/patch-rename-iwl_prefix.sql
+++ b/maintenance/sqlite/archives/patch-rename-iwl_prefix.sql
@@ -2,4 +2,4 @@
-- 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
+CREATE INDEX IF NOT EXISTS /*i*/iwl_prefix_from_title ON /*_*/iwlinks (iwl_prefix, iwl_from, iwl_title);
diff --git a/maintenance/stats.php b/maintenance/stats.php
index 2cbbcf91..1fb46fa9 100644
--- a/maintenance/stats.php
+++ b/maintenance/stats.php
@@ -37,8 +37,8 @@ class CacheStats extends Maintenance {
global $wgMemc;
// Can't do stats if
- if ( get_class( $wgMemc ) == 'FakeMemCachedClient' ) {
- $this->error( "You are running FakeMemCachedClient, I can not provide any statistics.", true );
+ if ( get_class( $wgMemc ) == 'EmptyBagOStuff' ) {
+ $this->error( "You are running EmptyBagOStuff, I can not provide any statistics.", true );
}
$session = intval( $wgMemc->get( wfMemcKey( 'stats', 'request_with_session' ) ) );
$noSession = intval( $wgMemc->get( wfMemcKey( 'stats', 'request_without_session' ) ) );
@@ -59,30 +59,42 @@ class CacheStats extends Maintenance {
$absent = intval( $wgMemc->get( wfMemcKey( 'stats', 'pcache_miss_absent' ) ) );
$stub = intval( $wgMemc->get( wfMemcKey( 'stats', 'pcache_miss_stub' ) ) );
$total = $hits + $invalid + $expired + $absent + $stub;
- $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits / $total * 100 ) );
- $this->output( sprintf( "invalid: %-10d %6.2f%%\n", $invalid, $invalid / $total * 100 ) );
- $this->output( sprintf( "expired: %-10d %6.2f%%\n", $expired, $expired / $total * 100 ) );
- $this->output( sprintf( "absent: %-10d %6.2f%%\n", $absent, $absent / $total * 100 ) );
- $this->output( sprintf( "stub threshold: %-10d %6.2f%%\n", $stub, $stub / $total * 100 ) );
- $this->output( sprintf( "total: %-10d %6.2f%%\n", $total, 100 ) );
+ if ( $total ) {
+ $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 ) );
+ } else {
+ $this->output( "no statistics available\n" );
+ }
+ $this->output( "\nImage cache\n" );
$hits = intval( $wgMemc->get( wfMemcKey( 'stats', 'image_cache_hit' ) ) );
$misses = intval( $wgMemc->get( wfMemcKey( 'stats', 'image_cache_miss' ) ) );
$updates = intval( $wgMemc->get( wfMemcKey( 'stats', 'image_cache_update' ) ) );
$total = $hits + $misses;
- $this->output( "\nImage cache\n" );
- $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits / $total * 100 ) );
- $this->output( sprintf( "misses: %-10d %6.2f%%\n", $misses, $misses / $total * 100 ) );
- $this->output( sprintf( "updates: %-10d\n", $updates ) );
+ if ( $total ) {
+ $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits / $total * 100 ) );
+ $this->output( sprintf( "misses: %-10d %6.2f%%\n", $misses, $misses / $total * 100 ) );
+ $this->output( sprintf( "updates: %-10d\n", $updates ) );
+ } else {
+ $this->output( "no statistics available\n" );
+ }
+ $this->output( "\nDiff cache\n" );
$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 ) );
+ if ( $total ) {
+ $this->output( sprintf( "hits: %-10d %6.2f%%\n", $hits, $hits / $total * 100 ) );
+ $this->output( sprintf( "misses: %-10d %6.2f%%\n", $misses, $misses / $total * 100 ) );
+ $this->output( sprintf( "uncacheable: %-10d %6.2f%%\n", $uncacheable, $uncacheable / $total * 100 ) );
+ } else {
+ $this->output( "no statistics available\n" );
+ }
}
}
diff --git a/maintenance/storage/checkStorage.php b/maintenance/storage/checkStorage.php
index c288d682..c372b9c4 100644
--- a/maintenance/storage/checkStorage.php
+++ b/maintenance/storage/checkStorage.php
@@ -2,6 +2,21 @@
/**
* Fsck for MediaWiki
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup Maintenance ExternalStorage
*/
@@ -115,6 +130,7 @@ class CheckStorage {
// It's safe to just erase the old_flags field
if ( $fix ) {
$this->error( 'fixed', "Warning: old_flags set to 0", $id );
+ $dbw = wfGetDB( DB_MASTER );
$dbw->ping();
$dbw->update( 'text', array( 'old_flags' => '' ),
array( 'old_id' => $id ), $fname );
diff --git a/maintenance/storage/compressOld.inc b/maintenance/storage/compressOld.inc
deleted file mode 100644
index 93be5f75..00000000
--- a/maintenance/storage/compressOld.inc
+++ /dev/null
@@ -1,300 +0,0 @@
-select( 'text', array( 'old_id','old_flags','old_text' ),
- "old_id>=$start", $fname, array( 'ORDER BY' => 'old_id', 'LIMIT' => $chunksize, 'FOR UPDATE' ) );
- if( $dbw->numRows( $res ) == 0 ) {
- break;
- }
- $last = $start;
- foreach ( $res as $row ) {
- # print " {$row->old_id} - {$row->old_namespace}:{$row->old_title}\n";
- compressPage( $row, $extdb );
- $last = $row->old_id;
- }
- $start = $last + 1; # Deletion may leave long empty stretches
- print "$start...\n";
- } while( true );
-}
-
-/** @todo document */
-function compressPage( $row, $extdb ) {
- $fname = 'compressPage';
- if ( false !== strpos( $row->old_flags, 'gzip' ) || false !== strpos( $row->old_flags, 'object' ) ) {
- #print "Already compressed row {$row->old_id}\n";
- return false;
- }
- $dbw = wfGetDB( DB_MASTER );
- $flags = $row->old_flags ? "{$row->old_flags},gzip" : "gzip";
- $compress = gzdeflate( $row->old_text );
-
- # Store in external storage if required
- if ( $extdb !== '' ) {
- $storeObj = new ExternalStoreDB;
- $compress = $storeObj->store( $extdb, $compress );
- if ( $compress === false ) {
- print "Unable to store object\n";
- return false;
- }
- }
-
- # Update text row
- $dbw->update( 'text',
- array( /* SET */
- 'old_flags' => $flags,
- 'old_text' => $compress
- ), array( /* WHERE */
- 'old_id' => $row->old_id
- ), $fname,
- array( 'LIMIT' => 1 )
- );
- return true;
-}
-
-define( 'LS_INDIVIDUAL', 0 );
-define( 'LS_CHUNKED', 1 );
-
-/** @todo document */
-function compressWithConcat( $startId, $maxChunkSize, $beginDate,
- $endDate, $extdb="", $maxPageId = false )
-{
- $fname = 'compressWithConcat';
- $loadStyle = LS_CHUNKED;
-
- $dbr = wfGetDB( DB_SLAVE );
- $dbw = wfGetDB( DB_MASTER );
-
- # Set up external storage
- if ( $extdb != '' ) {
- $storeObj = new ExternalStoreDB;
- }
-
- # Get all articles by page_id
- if ( !$maxPageId ) {
- $maxPageId = $dbr->selectField( 'page', 'max(page_id)', '', $fname );
- }
- print "Starting from $startId of $maxPageId\n";
- $pageConds = array();
-
- /*
- if ( $exclude_ns0 ) {
- print "Excluding main namespace\n";
- $pageConds[] = 'page_namespace<>0';
- }
- if ( $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
- # the script doesn't yet delete rows from external storage.
- $conds = array(
- 'old_flags NOT ' . $dbr->buildLike( $dbr->anyString(), 'object', $dbr->anyString() ) . ' AND old_flags NOT '
- . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ) );
-
- if ( $beginDate ) {
- if ( !preg_match( '/^\d{14}$/', $beginDate ) ) {
- print "Invalid begin date \"$beginDate\"\n";
- return false;
- }
- $conds[] = "rev_timestamp>'" . $beginDate . "'";
- }
- if ( $endDate ) {
- if ( !preg_match( '/^\d{14}$/', $endDate ) ) {
- print "Invalid end date \"$endDate\"\n";
- return false;
- }
- $conds[] = "rev_timestamp<'" . $endDate . "'";
- }
- if ( $loadStyle == LS_CHUNKED ) {
- $tables = array( 'revision', 'text' );
- $fields = array( 'rev_id', 'rev_text_id', 'old_flags', 'old_text' );
- $conds[] = 'rev_text_id=old_id';
- $revLoadOptions = 'FOR UPDATE';
- } else {
- $tables = array( 'revision' );
- $fields = array( 'rev_id', 'rev_text_id' );
- $revLoadOptions = array();
- }
-
- # Don't work with current revisions
- # Don't lock the page table for update either -- TS 2006-04-04
- #$tables[] = 'page';
- #$conds[] = 'page_id=rev_page AND rev_id != page_latest';
-
- for ( $pageId = $startId; $pageId <= $maxPageId; $pageId++ ) {
- wfWaitForSlaves( 5 );
-
- # Wake up
- $dbr->ping();
-
- # Get the page row
- $pageRes = $dbr->select( 'page',
- array('page_id', 'page_namespace', 'page_title','page_latest'),
- $pageConds + array('page_id' => $pageId), $fname );
- if ( $dbr->numRows( $pageRes ) == 0 ) {
- continue;
- }
- $pageRow = $dbr->fetchObject( $pageRes );
-
- # Display progress
- $titleObj = Title::makeTitle( $pageRow->page_namespace, $pageRow->page_title );
- print "$pageId\t" . $titleObj->getPrefixedDBkey() . " ";
-
- # Load revisions
- $revRes = $dbw->select( $tables, $fields,
- 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
- # since the page select, which wasn't locking
- 'rev_id < ' . $pageRow->page_latest
- ), $conds ),
- $fname,
- $revLoadOptions
- );
- $revs = array();
- foreach ( $revRes as $revRow ) {
- $revs[] = $revRow;
- }
-
- if ( count( $revs ) < 2) {
- # No revisions matching, no further processing
- print "\n";
- continue;
- }
-
- # For each chunk
- $i = 0;
- while ( $i < count( $revs ) ) {
- if ( $i < count( $revs ) - $maxChunkSize ) {
- $thisChunkSize = $maxChunkSize;
- } else {
- $thisChunkSize = count( $revs ) - $i;
- }
-
- $chunk = new ConcatenatedGzipHistoryBlob();
- $stubs = array();
- $dbw->begin();
- $usedChunk = false;
- $primaryOldid = $revs[$i]->rev_text_id;
-
- # Get the text of each revision and add it to the object
- for ( $j = 0; $j < $thisChunkSize && $chunk->isHappy(); $j++ ) {
- $oldid = $revs[$i + $j]->rev_text_id;
-
- # Get text
- if ( $loadStyle == LS_INDIVIDUAL ) {
- $textRow = $dbw->selectRow( 'text',
- array( 'old_flags', 'old_text' ),
- array( 'old_id' => $oldid ),
- $fname,
- 'FOR UPDATE'
- );
- $text = Revision::getRevisionText( $textRow );
- } else {
- $text = Revision::getRevisionText( $revs[$i + $j] );
- }
-
- if ( $text === false ) {
- print "\nError, unable to get text in old_id $oldid\n";
- #$dbw->delete( 'old', array( 'old_id' => $oldid ) );
- }
-
- if ( $extdb == "" && $j == 0 ) {
- $chunk->setText( $text );
- print '.';
- } else {
- # Don't make a stub if it's going to be longer than the article
- # Stubs are typically about 100 bytes
- if ( strlen( $text ) < 120 ) {
- $stub = false;
- print 'x';
- } else {
- $stub = new HistoryBlobStub( $chunk->addItem( $text ) );
- $stub->setLocation( $primaryOldid );
- $stub->setReferrer( $oldid );
- print '.';
- $usedChunk = true;
- }
- $stubs[$j] = $stub;
- }
- }
- $thisChunkSize = $j;
-
- # If we couldn't actually use any stubs because the pages were too small, do nothing
- if ( $usedChunk ) {
- if ( $extdb != "" ) {
- # Move blob objects to External Storage
- $stored = $storeObj->store( $extdb, serialize( $chunk ));
- if ($stored === false) {
- print "Unable to store object\n";
- return false;
- }
- # Store External Storage URLs instead of Stub placeholders
- foreach ($stubs as $stub) {
- if ($stub===false)
- continue;
- # $stored should provide base path to a BLOB
- $url = $stored."/".$stub->getHash();
- $dbw->update( 'text',
- array( /* SET */
- 'old_text' => $url,
- 'old_flags' => 'external,utf-8',
- ), array ( /* WHERE */
- 'old_id' => $stub->getReferrer(),
- )
- );
- }
- } else {
- # Store the main object locally
- $dbw->update( 'text',
- array( /* SET */
- 'old_text' => serialize( $chunk ),
- 'old_flags' => 'object,utf-8',
- ), array( /* WHERE */
- 'old_id' => $primaryOldid
- )
- );
-
- # Store the stub objects
- for ( $j = 1; $j < $thisChunkSize; $j++ ) {
- # Skip if not compressing and don't overwrite the first revision
- if ( $stubs[$j] !== false && $revs[$i + $j]->rev_text_id != $primaryOldid ) {
- $dbw->update( 'text',
- array( /* SET */
- 'old_text' => serialize($stubs[$j]),
- 'old_flags' => 'object,utf-8',
- ), array( /* WHERE */
- 'old_id' => $revs[$i + $j]->rev_text_id
- )
- );
- }
- }
- }
- }
- # Done, next
- print "/";
- $dbw->commit();
- $i += $thisChunkSize;
- wfWaitForSlaves( 5 );
- }
- print "\n";
- }
- return true;
-}
diff --git a/maintenance/storage/compressOld.php b/maintenance/storage/compressOld.php
index bc05b340..da82d93b 100644
--- a/maintenance/storage/compressOld.php
+++ b/maintenance/storage/compressOld.php
@@ -17,57 +17,379 @@
* -c maximum number of revisions in a concat chunk
* -b earliest date to check for uncompressed revisions
* -e latest revision date to compress
- * -s the old_id to start from
+ * -s the old_id to start from
+ * -n the old_id to stop at
* --extdb store specified revisions in an external cluster (untested)
*
+ * 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 ExternalStorage
*/
-$optionsWithArgs = array( 't', 'c', 's', 'f', 'h', 'extdb', 'endid', 'e' );
-require_once( dirname( __FILE__ ) . '/../commandLine.inc' );
-require_once( "compressOld.inc" );
+require_once( dirname( __FILE__ ) . '/../Maintenance.php' );
-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();
-}
+class CompressOld extends Maintenance {
+ /**
+ * @todo document
+ */
+ const LS_INDIVIDUAL = 0;
+ const LS_CHUNKED = 1;
-$defaults = array(
- 't' => 'concat',
- 'c' => 20,
- 's' => 0,
- 'b' => '',
- 'e' => '',
- 'extdb' => '',
- 'endid' => false,
-);
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = 'Compress the text of a wiki';
+ $this->addOption( 'type', 'Set compression type to either: gzip|concat', false, true, 't' );
+ $this->addOption( 'chunksize', 'Maximum number of revisions in a concat chunk', false, true, 'c' );
+ $this->addOption( 'begin-date', 'Earliest date to check for uncompressed revisions', false, true, 'b' );
+ $this->addOption( 'end-date', 'Latest revision date to compress', false, true, 'e' );
+ $this->addOption( 'startid', 'The old_id to start from', false, true, 's' );
+ $this->addOption( 'extdb', 'Store specified revisions in an external cluster (untested)', false, true );
+ $this->addOption( 'endid', 'Stop at this old_id', false, true, 'n' );
+ }
-$options = $options + $defaults;
+ public function execute() {
+ global $wgDBname;
+ if ( !function_exists( "gzdeflate" ) ) {
+ $this->error( "You must enable zlib support in PHP to compress old revisions!\n" .
+ "Please see http://www.php.net/manual/en/ref.zlib.php\n", true );
+ }
-if ( $options['t'] != 'concat' && $options['t'] != 'gzip' ) {
- print "Type \"{$options['t']}\" not supported\n";
-}
+ $type = $this->getOption( 'type', 'concat' );
+ $chunkSize = $this->getOption( 'chunksize', 20 );
+ $startId = $this->getOption( 'start-id', 0 );
+ $beginDate = $this->getOption( 'begin-date', '' );
+ $endDate = $this->getOption( 'end-date', '' );
+ $extDB = $this->getOption( 'extdb', '' );
+ $endId = $this->getOption( 'endid', false );
-if ( $options['extdb'] != '' ) {
- 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";
-}
+ if ( $type != 'concat' && $type != 'gzip' ) {
+ $this->error( "Type \"{$type}\" not supported" );
+ }
-$success = true;
-if ( $options['t'] == 'concat' ) {
- $success = compressWithConcat( $options['s'], $options['c'], $options['b'],
- $options['e'], $options['extdb'], $options['endid'] );
-} else {
- compressOldPages( $options['s'], $options['extdb'] );
-}
+ if ( $extDB != '' ) {
+ $this->output( "Compressing database {$wgDBname} to external cluster {$extDB}\n"
+ . str_repeat( '-', 76 ) . "\n\n" );
+ } else {
+ $this->output( "Compressing database {$wgDBname}\n"
+ . str_repeat( '-', 76 ) . "\n\n" );
+ }
-if ( $success ) {
- print "Done.\n";
-}
+ $success = true;
+ if ( $type == 'concat' ) {
+ $success = $this->compressWithConcat( $startId, $chunkSize, $beginDate,
+ $endDate, $extDB, $endId );
+ } else {
+ $this->compressOldPages( $startId, $extDB );
+ }
+
+ if ( $success ) {
+ $this->output( "Done.\n" );
+ }
+ }
+
+ /** @todo document */
+ private function compressOldPages( $start = 0, $extdb = '' ) {
+ $chunksize = 50;
+ $this->output( "Starting from old_id $start...\n" );
+ $dbw = wfGetDB( DB_MASTER );
+ do {
+ $res = $dbw->select( 'text', array( 'old_id','old_flags','old_text' ),
+ "old_id>=$start", __METHOD__, array( 'ORDER BY' => 'old_id', 'LIMIT' => $chunksize, 'FOR UPDATE' ) );
+ if( $dbw->numRows( $res ) == 0 ) {
+ break;
+ }
+ $last = $start;
+ foreach ( $res as $row ) {
+ # print " {$row->old_id} - {$row->old_namespace}:{$row->old_title}\n";
+ $this->compressPage( $row, $extdb );
+ $last = $row->old_id;
+ }
+ $start = $last + 1; # Deletion may leave long empty stretches
+ $this->output( "$start...\n" );
+ } while( true );
+ }
+
+ /** @todo document */
+ private function compressPage( $row, $extdb ) {
+ if ( false !== strpos( $row->old_flags, 'gzip' ) || false !== strpos( $row->old_flags, 'object' ) ) {
+ #print "Already compressed row {$row->old_id}\n";
+ return false;
+ }
+ $dbw = wfGetDB( DB_MASTER );
+ $flags = $row->old_flags ? "{$row->old_flags},gzip" : "gzip";
+ $compress = gzdeflate( $row->old_text );
+
+ # Store in external storage if required
+ if ( $extdb !== '' ) {
+ $storeObj = new ExternalStoreDB;
+ $compress = $storeObj->store( $extdb, $compress );
+ if ( $compress === false ) {
+ $this->error( "Unable to store object" );
+ return false;
+ }
+ }
+
+ # Update text row
+ $dbw->update( 'text',
+ array( /* SET */
+ 'old_flags' => $flags,
+ 'old_text' => $compress
+ ), array( /* WHERE */
+ 'old_id' => $row->old_id
+ ), __METHOD__,
+ array( 'LIMIT' => 1 )
+ );
+ return true;
+ }
+
+ /** @todo document */
+ private function compressWithConcat( $startId, $maxChunkSize, $beginDate,
+ $endDate, $extdb = "", $maxPageId = false )
+ {
+ $loadStyle = self::LS_CHUNKED;
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $dbw = wfGetDB( DB_MASTER );
+
+ # Set up external storage
+ if ( $extdb != '' ) {
+ $storeObj = new ExternalStoreDB;
+ }
+
+ # Get all articles by page_id
+ if ( !$maxPageId ) {
+ $maxPageId = $dbr->selectField( 'page', 'max(page_id)', '', __METHOD__ );
+ }
+ $this->output( "Starting from $startId of $maxPageId\n" );
+ $pageConds = array();
+
+ /*
+ if ( $exclude_ns0 ) {
+ print "Excluding main namespace\n";
+ $pageConds[] = 'page_namespace<>0';
+ }
+ if ( $queryExtra ) {
+ $pageConds[] = $queryExtra;
+ }
+ */
-exit( 0 );
+ # 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
+ # the script doesn't yet delete rows from external storage.
+ $conds = array(
+ 'old_flags NOT ' . $dbr->buildLike( $dbr->anyString(), 'object', $dbr->anyString() ) . ' AND old_flags NOT '
+ . $dbr->buildLike( $dbr->anyString(), 'external', $dbr->anyString() ) );
+
+ if ( $beginDate ) {
+ if ( !preg_match( '/^\d{14}$/', $beginDate ) ) {
+ $this->error( "Invalid begin date \"$beginDate\"\n" );
+ return false;
+ }
+ $conds[] = "rev_timestamp>'" . $beginDate . "'";
+ }
+ if ( $endDate ) {
+ if ( !preg_match( '/^\d{14}$/', $endDate ) ) {
+ $this->error( "Invalid end date \"$endDate\"\n" );
+ return false;
+ }
+ $conds[] = "rev_timestamp<'" . $endDate . "'";
+ }
+ if ( $loadStyle == self::LS_CHUNKED ) {
+ $tables = array( 'revision', 'text' );
+ $fields = array( 'rev_id', 'rev_text_id', 'old_flags', 'old_text' );
+ $conds[] = 'rev_text_id=old_id';
+ $revLoadOptions = 'FOR UPDATE';
+ } else {
+ $tables = array( 'revision' );
+ $fields = array( 'rev_id', 'rev_text_id' );
+ $revLoadOptions = array();
+ }
+
+ # Don't work with current revisions
+ # Don't lock the page table for update either -- TS 2006-04-04
+ #$tables[] = 'page';
+ #$conds[] = 'page_id=rev_page AND rev_id != page_latest';
+
+ for ( $pageId = $startId; $pageId <= $maxPageId; $pageId++ ) {
+ wfWaitForSlaves();
+
+ # Wake up
+ $dbr->ping();
+
+ # Get the page row
+ $pageRes = $dbr->select( 'page',
+ array('page_id', 'page_namespace', 'page_title','page_latest'),
+ $pageConds + array('page_id' => $pageId), __METHOD__ );
+ if ( $dbr->numRows( $pageRes ) == 0 ) {
+ continue;
+ }
+ $pageRow = $dbr->fetchObject( $pageRes );
+
+ # Display progress
+ $titleObj = Title::makeTitle( $pageRow->page_namespace, $pageRow->page_title );
+ $this->output( "$pageId\t" . $titleObj->getPrefixedDBkey() . " " );
+
+ # Load revisions
+ $revRes = $dbw->select( $tables, $fields,
+ 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
+ # since the page select, which wasn't locking
+ 'rev_id < ' . $pageRow->page_latest
+ ), $conds ),
+ __METHOD__,
+ $revLoadOptions
+ );
+ $revs = array();
+ foreach ( $revRes as $revRow ) {
+ $revs[] = $revRow;
+ }
+
+ if ( count( $revs ) < 2) {
+ # No revisions matching, no further processing
+ $this->output( "\n" );
+ continue;
+ }
+
+ # For each chunk
+ $i = 0;
+ while ( $i < count( $revs ) ) {
+ if ( $i < count( $revs ) - $maxChunkSize ) {
+ $thisChunkSize = $maxChunkSize;
+ } else {
+ $thisChunkSize = count( $revs ) - $i;
+ }
+
+ $chunk = new ConcatenatedGzipHistoryBlob();
+ $stubs = array();
+ $dbw->begin();
+ $usedChunk = false;
+ $primaryOldid = $revs[$i]->rev_text_id;
+
+ # Get the text of each revision and add it to the object
+ for ( $j = 0; $j < $thisChunkSize && $chunk->isHappy(); $j++ ) {
+ $oldid = $revs[$i + $j]->rev_text_id;
+
+ # Get text
+ if ( $loadStyle == self::LS_INDIVIDUAL ) {
+ $textRow = $dbw->selectRow( 'text',
+ array( 'old_flags', 'old_text' ),
+ array( 'old_id' => $oldid ),
+ __METHOD__,
+ 'FOR UPDATE'
+ );
+ $text = Revision::getRevisionText( $textRow );
+ } else {
+ $text = Revision::getRevisionText( $revs[$i + $j] );
+ }
+
+ if ( $text === false ) {
+ $this->error( "\nError, unable to get text in old_id $oldid" );
+ #$dbw->delete( 'old', array( 'old_id' => $oldid ) );
+ }
+
+ if ( $extdb == "" && $j == 0 ) {
+ $chunk->setText( $text );
+ $this->output( '.' );
+ } else {
+ # Don't make a stub if it's going to be longer than the article
+ # Stubs are typically about 100 bytes
+ if ( strlen( $text ) < 120 ) {
+ $stub = false;
+ $this->output( 'x' );
+ } else {
+ $stub = new HistoryBlobStub( $chunk->addItem( $text ) );
+ $stub->setLocation( $primaryOldid );
+ $stub->setReferrer( $oldid );
+ $this->output( '.' );
+ $usedChunk = true;
+ }
+ $stubs[$j] = $stub;
+ }
+ }
+ $thisChunkSize = $j;
+
+ # If we couldn't actually use any stubs because the pages were too small, do nothing
+ if ( $usedChunk ) {
+ if ( $extdb != "" ) {
+ # Move blob objects to External Storage
+ $stored = $storeObj->store( $extdb, serialize( $chunk ));
+ if ($stored === false) {
+ $this->error( "Unable to store object" );
+ return false;
+ }
+ # Store External Storage URLs instead of Stub placeholders
+ foreach ($stubs as $stub) {
+ if ($stub===false)
+ continue;
+ # $stored should provide base path to a BLOB
+ $url = $stored."/".$stub->getHash();
+ $dbw->update( 'text',
+ array( /* SET */
+ 'old_text' => $url,
+ 'old_flags' => 'external,utf-8',
+ ), array ( /* WHERE */
+ 'old_id' => $stub->getReferrer(),
+ )
+ );
+ }
+ } else {
+ # Store the main object locally
+ $dbw->update( 'text',
+ array( /* SET */
+ 'old_text' => serialize( $chunk ),
+ 'old_flags' => 'object,utf-8',
+ ), array( /* WHERE */
+ 'old_id' => $primaryOldid
+ )
+ );
+
+ # Store the stub objects
+ for ( $j = 1; $j < $thisChunkSize; $j++ ) {
+ # Skip if not compressing and don't overwrite the first revision
+ if ( $stubs[$j] !== false && $revs[$i + $j]->rev_text_id != $primaryOldid ) {
+ $dbw->update( 'text',
+ array( /* SET */
+ 'old_text' => serialize($stubs[$j]),
+ 'old_flags' => 'object,utf-8',
+ ), array( /* WHERE */
+ 'old_id' => $revs[$i + $j]->rev_text_id
+ )
+ );
+ }
+ }
+ }
+ }
+ # Done, next
+ $this->output( "/" );
+ $dbw->commit();
+ $i += $thisChunkSize;
+ wfWaitForSlaves();
+ }
+ $this->output( "\n" );
+ }
+ return true;
+ }
+
+}
+$maintClass = 'CompressOld';
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/maintenance/storage/fixBug20757.php b/maintenance/storage/fixBug20757.php
index 4aac1202..b6def2da 100644
--- a/maintenance/storage/fixBug20757.php
+++ b/maintenance/storage/fixBug20757.php
@@ -1,4 +1,25 @@
50 == 0 ) {
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
$iteration = 0;
}
}
diff --git a/maintenance/storage/moveToExternal.php b/maintenance/storage/moveToExternal.php
index 928cbf97..64f3adaa 100644
--- a/maintenance/storage/moveToExternal.php
+++ b/maintenance/storage/moveToExternal.php
@@ -2,6 +2,21 @@
/**
* Move revision's text to external storage
*
+ * 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 ExternalStorage
*/
@@ -51,7 +66,7 @@ function moveToExternal( $cluster, $maxID, $minID = 1 ) {
if ( !( $block % REPORTING_INTERVAL ) ) {
print "oldid=$blockStart, moved=$numMoved\n";
- wfWaitForSlaves( 2 );
+ wfWaitForSlaves();
}
$res = $dbr->select( 'text', array( 'old_id', 'old_flags', 'old_text' ),
diff --git a/maintenance/storage/recompressTracked.php b/maintenance/storage/recompressTracked.php
index 8974a74d..09ab3e57 100644
--- a/maintenance/storage/recompressTracked.php
+++ b/maintenance/storage/recompressTracked.php
@@ -1,4 +1,26 @@
= $this->reportingInterval ) {
$batchesDone = 0;
echo "$startId / $endId\n";
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
}
}
echo "Found $rowsInserted revisions\n";
@@ -268,7 +290,7 @@ class TrackBlobs {
if ( $batchesDone >= $this->reportingInterval ) {
$batchesDone = 0;
echo "$startId / $endId\n";
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
}
}
echo "Found $rowsInserted orphan text rows\n";
diff --git a/maintenance/tables.sql b/maintenance/tables.sql
index 1dcf98b7..f879f02f 100644
--- a/maintenance/tables.sql
+++ b/maintenance/tables.sql
@@ -135,6 +135,7 @@ CREATE TABLE /*_*/user (
CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50));
--
@@ -163,6 +164,15 @@ CREATE TABLE /*_*/user_groups (
CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+-- Stores the groups the user has once belonged to.
+-- The user may still belong these groups. Check user_groups.
+CREATE TABLE /*_*/user_former_groups (
+ -- Key to user_id
+ ufg_user int unsigned NOT NULL default 0,
+ ufg_group varbinary(16) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
--
-- Stores notifications of user talk page changes, for the display
@@ -176,7 +186,7 @@ CREATE TABLE /*_*/user_newtalk (
user_ip varbinary(40) NOT NULL default '',
-- The highest timestamp of revisions of the talk page viewed
-- by this user
- user_last_timestamp binary(14) NOT NULL default ''
+ user_last_timestamp varbinary(14) NULL default NULL
) /*$wgDBTableOptions*/;
-- Indexes renamed for SQLite in 1.14
@@ -198,7 +208,7 @@ CREATE TABLE /*_*/user_properties (
up_user int NOT NULL,
-- Name of the option being saved. This is indexed for bulk lookup.
- up_property varbinary(32) NOT NULL,
+ up_property varbinary(255) NOT NULL,
-- Property value as a string.
up_value blob
@@ -502,6 +512,8 @@ CREATE TABLE /*_*/categorylinks (
-- concatenated with a line break followed by the page title before the sortkey
-- conversion algorithm is run. We store this so that we can update
-- collations without reparsing all pages.
+ -- Note: If you change the length of this field, you also need to change
+ -- code in LinksUpdate.php. See bug 25254.
cl_sortkey_prefix varchar(255) binary NOT NULL default '',
-- This isn't really used at present. Provided for an optional
@@ -926,6 +938,57 @@ CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp
CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+--
+-- Store information about newly uploaded files before they're
+-- moved into the actual filestore
+--
+CREATE TABLE /*_*/uploadstash (
+ us_id int unsigned NOT NULL PRIMARY KEY auto_increment,
+
+ -- the user who uploaded the file.
+ us_user int unsigned NOT NULL,
+
+ -- file key. this is how applications actually search for the file.
+ -- this might go away, or become the primary key.
+ us_key varchar(255) NOT NULL,
+
+ -- the original path
+ us_orig_path varchar(255) NOT NULL,
+
+ -- the temporary path at which the file is actually stored
+ us_path varchar(255) NOT NULL,
+
+ -- which type of upload the file came from (sometimes)
+ us_source_type varchar(50),
+
+ -- the date/time on which the file was added
+ us_timestamp varbinary(14) not null,
+
+ us_status varchar(50) not null,
+
+ -- file properties from File::getPropsFromPath. these may prove unnecessary.
+ --
+ us_size int unsigned NOT NULL,
+ -- this hash comes from File::sha1Base36(), and is 31 characters
+ us_sha1 varchar(31) NOT NULL,
+ us_mime varchar(255),
+ -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table
+ us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ -- image-specific properties
+ us_image_width int unsigned,
+ us_image_height int unsigned,
+ us_image_bits smallint unsigned
+
+) /*$wgDBTableOptions*/;
+
+-- sometimes there's a delete for all of a user's stuff.
+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
+-- pick out files by key, enforce key uniqueness
+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
+-- the abandoned upload cleanup script needs this
+CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
+
+
--
-- Primarily a summary table for Special:Recentchanges,
-- this table contains some additional info on edits from
@@ -1028,31 +1091,6 @@ CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_ti
CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
---
--- Used by the math module to keep track
--- of previously-rendered items.
---
-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*/;
-
-CREATE UNIQUE INDEX /*i*/math_inputhash ON /*_*/math (math_inputhash);
-
-
--
-- When using the default MySQL search backend, page titles
-- and text are munged to strip markup, do Unicode case folding,
diff --git a/maintenance/tests/.svnignore b/maintenance/tests/.svnignore
deleted file mode 100644
index 20cb61e9..00000000
--- a/maintenance/tests/.svnignore
+++ /dev/null
@@ -1,6 +0,0 @@
-LocalTestSettings.php
-*~
-bin
-.classpath
-.project
-project.index
diff --git a/maintenance/tests/RunSeleniumTests.php b/maintenance/tests/RunSeleniumTests.php
deleted file mode 100644
index 2574f4b2..00000000
--- a/maintenance/tests/RunSeleniumTests.php
+++ /dev/null
@@ -1,220 +0,0 @@
-#!/usr/bin/php
-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/parser/ExtraParserTests.txt b/maintenance/tests/parser/ExtraParserTests.txt
deleted file mode 100644
index 66b8032d..00000000
Binary files a/maintenance/tests/parser/ExtraParserTests.txt and /dev/null differ
diff --git a/maintenance/tests/parser/parserTest.inc b/maintenance/tests/parser/parserTest.inc
deleted file mode 100644
index c553550c..00000000
--- a/maintenance/tests/parser/parserTest.inc
+++ /dev/null
@@ -1,1305 +0,0 @@
-
-# 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() .
- '' .
- $text .
- '';
-
- $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/tests/parser/parserTests.txt b/maintenance/tests/parser/parserTests.txt
deleted file mode 100644
index b3fa560c..00000000
--- a/maintenance/tests/parser/parserTests.txt
+++ /dev/null
@@ -1,8315 +0,0 @@
-# MediaWiki Parser test cases
-# Some taken from http://meta.wikimedia.org/wiki/Parser_testing
-# All (C) their respective authors and released under the GPL
-#
-# The syntax should be fairly self-explanatory.
-#
-# Currently supported test options:
-# One of the following three:
-#
-# (default) generate HTML output
-# pst apply pre-save transform
-# msg apply message transform
-#
-# Plus any combination of these:
-#
-# cat add category links
-# ill add inter-language links
-# subpage enable subpages (disabled by default)
-# noxml don't check for XML well formdness
-# title=[[XXX]] run test using article title XXX
-# language=XXX set content language to XXX for this test
-# variant=XXX set the variant of language for this test (eg zh-tw)
-# disabled do not run test
-# showtitle make the first line the title
-# comment run through Linker::formatComment() instead of main parser
-# local format section links in edit comment text as local links
-#
-# For testing purposes, temporary articles can created:
-# !!article / NAMESPACE:TITLE / !!text / ARTICLE TEXT / !!endarticle
-# where '/' denotes a newline.
-
-# This is the standard article assumed to exist.
-!! article
-Main Page
-!! text
-blah blah
-!! endarticle
-
-!!article
-Template:Foo
-!!text
-FOO
-!!endarticle
-
-!! article
-Template:Blank
-!! text
-!! endarticle
-
-!! article
-Template:!
-!! text
-|
-!! endarticle
-
-!!article
-MediaWiki:bad image list
-!!text
-* [[File:Bad.jpg]] except [[Nasty page]]
-!!endarticle
-
-###
-### Basic tests
-###
-!! test
-Blank input
-!! input
-!! result
-!! end
-
-
-!! test
-Simple paragraph
-!! input
-This is a simple paragraph.
-!! result
-
This is a simple paragraph.
-
-!! end
-
-!! test
-Simple list
-!! input
-* Item 1
-* Item 2
-!! result
-
-
-!! end
-
-!! test
-Italics and bold
-!! input
-* plain
-* plain''italic''plain
-* plain''italic''plain''italic''plain
-* plain'''bold'''plain
-* plain'''bold'''plain'''bold'''plain
-* plain''italic''plain'''bold'''plain
-* plain'''bold'''plain''italic''plain
-* plain''italic'''bold-italic'''italic''plain
-* plain'''bold''bold-italic''bold'''plain
-* plain'''''bold-italic'''italic''plain
-* plain'''''bold-italic''bold'''plain
-* plain''italic'''bold-italic'''''plain
-* plain'''bold''bold-italic'''''plain
-* plain l'''italic''plain
-* plain l''''bold''' plain
-!! result
-- plain
-
- plainitalicplain
-
- plainitalicplainitalicplain
-
- plainboldplain
-
- plainboldplainboldplain
-
- plainitalicplainboldplain
-
- plainboldplainitalicplain
-
- plainitalicbold-italicitalicplain
-
- plainboldbold-italicboldplain
-
- plainbold-italicitalicplain
-
- plainbold-italicboldplain
-
- plainitalicbold-italicplain
-
- plainboldbold-italicplain
-
- plain l'italicplain
-
- plain l'bold plain
-
-
-!! end
-
-###
-### test cases
-###
-
-!! test
- unordered list
-!! input
-* This is not an unordered list item.
-!! result
-* This is not an unordered list item.
-
-!! end
-
-!! test
- spacing
-!! input
-Lorem ipsum dolor
-
-sed abit.
- sed nullum.
-
-:and a colon
-
-!! result
-Lorem ipsum dolor
-
-sed abit.
- sed nullum.
-
-:and a colon
-
-
-!! end
-
-!! test
-nowiki 3
-!! input
-:There is not nowiki.
-:There is nowiki.
-
-#There is not nowiki.
-#There is nowiki.
-
-*There is not nowiki.
-*There is nowiki.
-!! result
-- There is not nowiki.
-
- There is nowiki.
-
-- There is not nowiki.
-
- There is nowiki.
-
-- There is not nowiki.
-
- There is nowiki.
-
-
-!! end
-
-
-###
-### Comments
-###
-!! test
-Comment test 1
-!! input
- asdf
-
-!! result
-asdf
-
-
-!! end
-
-!! test
-Comment test 2
-!! input
-asdf
-
-jkl
-!! result
-asdf
-jkl
-
-!! end
-
-!! test
-Comment test 3
-!! input
-asdf
-
-
-jkl
-!! result
-asdf
-jkl
-
-!! end
-
-!! test
-Comment test 4
-!! input
-asdfjkl
-!! result
-asdfjkl
-
-!! end
-
-!! test
-Comment spacing
-!! input
-a
- b
-c
-!! result
-a
-
- b
-
-c
-
-!! end
-
-!! test
-Comment whitespace
-!! input
-
-!! result
-
-!! end
-
-!! test
-Comment semantics and delimiters
-!! input
-
-!! result
-
-!! end
-
-!! test
-Comment semantics and delimiters, redux
-!! input
-
-!! result
-
-!! end
-
-!! test
-Comment semantics and delimiters: directors cut
-!! input
--->
-!! result
--->
-
-!! end
-
-!! test
-Comment semantics: nesting
-!! input
--->
-!! result
--->
-
-!! end
-
-!! test
-Comment semantics: unclosed comment at end
-!! input
-oo}}
-!! result
-FOO
-
-!! end
-
-!! test
-Comment on its own line post-expand
-!! input
-a
-{{blank}}
-b
-!! result
-a
-
b
-
-!! end
-
-###
-### Preformatted text
-###
-!! test
-Preformatted text
-!! input
- This is some
- Preformatted text
- With ''italic''
- And '''bold'''
- And a [[Main Page|link]]
-!! result
-This is some
-Preformatted text
-With italic
-And bold
-And a link
-
-!! end
-
-!! test
- with inside (compatibility with 1.6 and earlier)
-!! input
-
-
-
-
-
-!! result
-
-<b>
-<cite>
-<em>
-
-
-!! end
-
-!! test
-Regression with preformatted in
-!! input
-
- Blah
-
-!! result
-
-Blah
-
-
-
-!! end
-
-# Expected output in the following test is not really expected (there should be
-# in the output) -- it's only testing for well-formedness.
-!! test
-Bug 6200: Preformatted in
-!! input
-
- Blah
-
-!! result
-
- Blah
-
-
-!! end
-
-!! test
- with attributes (bug 3202)
-!! input
-Bluescreen of WikiDeath
-!! result
-Bluescreen of WikiDeath
-
-!! end
-
-!! test
- with width attribute (bug 3202)
-!! input
-Narrow screen goodies
-!! result
-Narrow screen goodies
-
-!! end
-
-!! test
- with forbidden attribute (bug 3202)
-!! input
-Narrow screen goodies
-!! result
-Narrow screen goodies
-
-!! end
-
-!! test
- with forbidden attribute values (bug 3202)
-!! input
-Narrow screen goodies
-!! result
-Narrow screen goodies
-
-!! end
-
-!! test
- inside (bug 13238)
-!! input
-
-
-
-
-
-
-Foo
-!! result
-
-<nowiki>
-
-
-
-
-<nowiki>Foo</nowiki>
-
-!! end
-
-!! test
- and preference (first one wins)
-!! input
-
-
-
-
-
-
-
-
-
-
-
-
-
-!! result
-
-<nowiki>
-
-</nowiki>
-</pre>
-
-<pre>
-<nowiki>
-</pre>
-
-</pre>
-
-!! end
-
-
-###
-### Definition lists
-###
-!! test
-Simple definition
-!! input
-; name : Definition
-!! result
-
- name
- Definition
-
-
-!! end
-
-!! test
-Definition list for indentation only
-!! input
-: Indented text
-!! result
-- Indented text
-
-
-!! end
-
-!! test
-Definition list with no space
-!! input
-;name:Definition
-!! result
-- name
- Definition
-
-
-!!end
-
-!! test
-Definition list with URL link
-!! input
-; http://example.com/ : definition
-!! result
-- http://example.com/
- definition
-
-
-!! end
-
-!! test
-Definition list with bracketed URL link
-!! input
-;[http://www.example.com/ Example]:Something about it
-!! result
-- Example
- Something about it
-
-
-!! end
-
-!! test
-Definition list with wikilink containing colon
-!! input
-; [[Help:FAQ]]: The least-read page on Wikipedia
-!! result
-- Help:FAQ
- The least-read page on Wikipedia
-
-
-!! end
-
-# At Brion's and JeLuF's insistence... :)
-!! test
-Definition list with news link containing colon
-!! input
-; news:alt.wikipedia.rox: This isn't even a real newsgroup!
-!! result
-- news:alt.wikipedia.rox
- This isn't even a real newsgroup!
-
-
-!! end
-
-!! test
-Malformed definition list with colon
-!! input
-; news:alt.wikipedia.rox -- don't crash or enter an infinite loop
-!! result
-- news:alt.wikipedia.rox -- don't crash or enter an infinite loop
-
-
-!! end
-
-!! test
-Definition lists: colon in external link text
-!! input
-; [http://www.wikipedia2.org/ Wikipedia : The Next Generation]: OK, I made that up
-!! result
-- Wikipedia : The Next Generation
- OK, I made that up
-
-
-!! end
-
-!! test
-Definition lists: colon in HTML attribute
-!! input
-;bold
-!! result
-- bold
-
-
-!! end
-
-
-!! test
-Definition lists: self-closed tag
-!! input
-;one
two : two-line fun
-!! result
-- one
two - two-line fun
-
-
-!! end
-
-
-###
-### External links
-###
-!! test
-External links: non-bracketed
-!! input
-Non-bracketed: http://example.com
-!! result
-Non-bracketed: http://example.com
-
-!! end
-
-!! test
-External links: numbered
-!! input
-Numbered: [http://example.com]
-Numbered: [http://example.net]
-Numbered: [http://example.com]
-!! result
-Numbered: [1]
-Numbered: [2]
-Numbered: [3]
-
-!!end
-
-!! test
-External links: specified text
-!! input
-Specified text: [http://example.com link]
-!! result
-Specified text: link
-
-!!end
-
-!! test
-External links: trail
-!! input
-Linktrails should not work for external links: [http://example.com link]s
-!! result
-Linktrails should not work for external links: links
-
-!! end
-
-!! test
-External links: dollar sign in URL
-!! input
-http://example.com/1$2345
-!! result
-http://example.com/1$2345
-
-!! end
-
-!! test
-External links: dollar sign in URL (named)
-!! input
-[http://example.com/1$2345]
-!! result
-[1]
-
-!!end
-
-!! test
-External links: open square bracket forbidden in URL (bug 4377)
-!! input
-http://example.com/1[2345
-!! result
-http://example.com/1[2345
-
-!! end
-
-!! test
-External links: open square bracket forbidden in URL (named) (bug 4377)
-!! input
-[http://example.com/1[2345]
-!! result
-[2345
-
-!!end
-
-!! test
-External links: nowiki in URL link text (bug 6230)
-!!input
-[http://example.com/ ''example site'']
-!! result
-''example site''
-
-!! end
-
-!! test
-External links: newline forbidden in text (bug 6230 regression check)
-!! input
-[http://example.com/ first
-second]
-!! result
-[http://example.com/ first
-second]
-
-!!end
-
-!! test
-External image
-!! input
-External image: http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png
-!! result
-External image:
-
-!! end
-
-!! test
-External image from https
-!! input
-External image from https: https://meta.wikimedia.org/upload/f/f1/Ncwikicol.png
-!! result
-External image from https:
-
-!! end
-
-!! test
-Link to non-http image, no img tag
-!! input
-Link to non-http image, no img tag: ftp://example.com/test.jpg
-!! result
-Link to non-http image, no img tag: ftp://example.com/test.jpg
-
-!! end
-
-!! test
-External links: terminating separator
-!! input
-Terminating separator: http://example.com/thing,
-!! result
-Terminating separator: http://example.com/thing,
-
-!! end
-
-!! test
-External links: intervening separator
-!! input
-Intervening separator: http://example.com/1,2,3
-!! result
-Intervening separator: http://example.com/1,2,3
-
-!! end
-
-!! test
-External links: old bug with URL in query
-!! input
-Old bug with URL in query: [http://example.com/thing?url=http://example.com link]
-!! result
-Old bug with URL in query: link
-
-!! end
-
-!! test
-External links: old URL-in-URL bug, mixed protocols
-!! input
-And again with mixed protocols: [ftp://example.com?url=http://example.com link]
-!! result
-And again with mixed protocols: link
-
-!!end
-
-!! test
-External links: URL in text
-!! input
-URL in text: [http://example.com http://example.com]
-!! result
-URL in text: http://example.com
-
-!! end
-
-!! test
-External links: Clickable images
-!! input
-ja-style clickable images: [http://example.com http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png]
-!! result
-ja-style clickable images:
-
-!!end
-
-!! test
-External links: raw ampersand
-!! input
-Old & use: http://x&y
-!! result
-Old & use: http://x&y
-
-!! end
-
-!! test
-External links: encoded ampersand
-!! input
-Old & use: http://x&y
-!! result
-Old & use: http://x&y
-
-!! end
-
-!! test
-External links: encoded equals (bug 6102)
-!! input
-http://example.com/?foo=bar
-!! result
-http://example.com/?foo=bar
-
-!! end
-
-!! test
-External links: [raw ampersand]
-!! input
-Old & use: [http://x&y]
-!! result
-Old & use: [1]
-
-!! end
-
-!! test
-External links: [encoded ampersand]
-!! input
-Old & use: [http://x&y]
-!! result
-Old & use: [1]
-
-!! end
-
-!! test
-External links: [encoded equals] (bug 6102)
-!! input
-[http://example.com/?foo=bar]
-!! result
-[1]
-
-!! end
-
-!! test
-External links: [IDN ignored character reference in hostname; strip it right off]
-!! input
-[http://example.com/]
-!! result
-[1]
-
-!! end
-
-!! test
-External links: IDN ignored character reference in hostname; strip it right off
-!! input
-http://example.com/
-!! result
-http://example.com/
-
-!! end
-
-!! test
-External links: www.jpeg.org (bug 554)
-!! input
-http://www.jpeg.org
-!!result
-http://www.jpeg.org
-
-!! end
-
-!! test
-External links: URL within URL (original bug 2)
-!! input
-[http://www.unausa.org/newindex.asp?place=http://www.unausa.org/programs/mun.asp]
-!! result
-[1]
-
-!! end
-
-!! test
-BUG 361: URL inside bracketed URL
-!! input
-[http://www.example.com/foo http://www.example.com/bar]
-!! result
-http://www.example.com/bar
-
-!! end
-
-!! test
-BUG 361: URL within URL, not bracketed
-!! input
-http://www.example.com/foo?=http://www.example.com/bar
-!! result
-http://www.example.com/foo?=http://www.example.com/bar
-
-!! end
-
-!! test
-BUG 289: ">"-token in URL-tail
-!! input
-http://www.example.com/
-!! result
-http://www.example.com/<hello>
-
-!!end
-
-!! test
-BUG 289: literal ">"-token in URL-tail
-!! input
-http://www.example.com/html
-!! result
-http://www.example.com/html
-
-!!end
-
-!! test
-BUG 289: ">"-token in bracketed URL
-!! input
-[http://www.example.com/ stuff]
-!! result
-<hello> stuff
-
-!!end
-
-!! test
-BUG 289: literal ">"-token in bracketed URL
-!! input
-[http://www.example.com/html stuff]
-!! result
-html stuff
-
-!!end
-
-!! test
-BUG 289: literal double quote at end of URL
-!! input
-http://www.example.com/"hello"
-!! result
-http://www.example.com/"hello"
-
-!!end
-
-!! test
-BUG 289: literal double quote in bracketed URL
-!! input
-[http://www.example.com/"hello" stuff]
-!! result
-"hello" stuff
-
-!!end
-
-!! test
-External links: multiple legal whitespace is fine, Magnus. Don't break it please. (bug 5081)
-!! input
-[http://www.example.com test]
-!! result
-test
-
-!! end
-
-!! test
-External links: wiki links within external link (Bug 3695)
-!! input
-[http://example.com [[wikilink]] embedded in ext link]
-!! result
-wikilink embedded in ext link
-
-!! end
-
-!! test
-BUG 787: Links with one slash after the url protocol are invalid
-!! input
-http:/example.com
-
-[http:/example.com title]
-!! result
-http:/example.com
-
[http:/example.com title]
-
-!! end
-
-!! test
-Bug 2702: Mismatched , and tags are invalid
-!! input
-''[http://example.com text'']
-[http://example.com '''text]'''
-''Something [http://example.com in italic'']
-''Something [http://example.com mixed''''', even bold]'''
-'''''Now [http://example.com both''''']
-!! result
-text
-text
-Something in italic
-Something mixed, even bold
-Now both
-
-!! end
-
-
-!! test
-Bug 4781: %26 in URL
-!! input
-http://www.example.com/?title=AT%26T
-!! result
-http://www.example.com/?title=AT%26T
-
-!! end
-
-!! test
-Bug 4781, 5267: %26 in URL
-!! input
-http://www.example.com/?title=100%25_Bran
-!! result
-http://www.example.com/?title=100%25_Bran
-
-!! end
-
-!! test
-Bug 4781, 5267: %28, %29 in URL
-!! input
-http://www.example.com/?title=Ben-Hur_%281959_film%29
-!! result
-http://www.example.com/?title=Ben-Hur_%281959_film%29
-
-!! end
-
-
-!! test
-Bug 4781: %26 in autonumber URL
-!! input
-[http://www.example.com/?title=AT%26T]
-!! result
-[1]
-
-!! end
-
-!! test
-Bug 4781, 5267: %26 in autonumber URL
-!! input
-[http://www.example.com/?title=100%25_Bran]
-!! result
-[1]
-
-!! end
-
-!! test
-Bug 4781, 5267: %28, %29 in autonumber URL
-!! input
-[http://www.example.com/?title=Ben-Hur_%281959_film%29]
-!! result
-[1]
-
-!! end
-
-
-!! test
-Bug 4781: %26 in bracketed URL
-!! input
-[http://www.example.com/?title=AT%26T link]
-!! result
-link
-
-!! end
-
-!! test
-Bug 4781, 5267: %26 in bracketed URL
-!! input
-[http://www.example.com/?title=100%25_Bran link]
-!! result
-link
-
-!! end
-
-!! test
-Bug 4781, 5267: %28, %29 in bracketed URL
-!! input
-[http://www.example.com/?title=Ben-Hur_%281959_film%29 link]
-!! result
-link
-
-!! end
-
-!! test
-External link containing double-single-quotes in text '' (bug 4598 sanity check)
-!! input
-Some [http://example.com/ pretty ''italics'' and stuff]!
-!! result
-Some pretty italics and stuff!
-
-!! end
-
-!! test
-External link containing double-single-quotes in text embedded in italics (bug 4598 sanity check)
-!! input
-''Some [http://example.com/ pretty ''italics'' and stuff]!''
-!! result
-Some pretty italics and stuff!
-
-!! 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
-La muerte de Casagemas (1901) en el sitio de Museo Picasso.
-