diff -pruN 1:1.12.0-2/AdminSettings.sample 1:1.13.0-1/AdminSettings.sample
--- 1:1.12.0-2/AdminSettings.sample	2008-03-20 22:09:08.000000000 +0000
+++ 1:1.13.0-1/AdminSettings.sample	2008-03-18 16:56:52.000000000 +0000
@@ -7,8 +7,6 @@
  * privileges to do maintenance work.
  *
  * Developers: Do not check AdminSettings.php into Subversion
- *
- * @package MediaWiki
  */
 
 /*
@@ -27,5 +25,3 @@ $wgDBadminpassword  = 'adminpass';
  * Whether to enable the profileinfo.php script.
  */
 $wgEnableProfileInfo = false;
-
-?>
diff -pruN 1:1.12.0-2/api.php 1:1.13.0-1/api.php
--- 1:1.12.0-2/api.php	2008-03-20 22:09:08.000000000 +0000
+++ 1:1.13.0-1/api.php	2008-05-17 05:26:26.000000000 +0100
@@ -76,7 +76,13 @@ $processor = new ApiMain($wgRequest, $wg
 // Process data & print results
 $processor->execute();
 
+// Execute any deferred updates
+wfDoUpdates();
+
 // Log what the user did, for book-keeping purposes.
 wfProfileOut('api.php');
 wfLogProfilingData();
 
+// Shut down the database
+wfGetLBFactory()->shutdown();
+
diff -pruN 1:1.12.0-2/bin/svnstat 1:1.13.0-1/bin/svnstat
--- 1:1.12.0-2/bin/svnstat	2008-03-20 22:08:49.000000000 +0000
+++ 1:1.13.0-1/bin/svnstat	2008-06-25 14:42:01.000000000 +0100
@@ -1,2 +1,2 @@
 #!/bin/sh
-svn stat -v $1 | sed -n 's/^[ A-Z?\*|!]\{1,15\}/r/;s/ \{1,15\}/\/r/;s/ .*//p'
+svn stat -v $1 | sed -n 's/^[ A-Z?\*|!]\{1,15\}[0-9]\{1,10\} \{1,15\}/r/;s/ .*//p'
diff -pruN 1:1.12.0-2/config/index.php 1:1.13.0-1/config/index.php
--- 1:1.12.0-2/config/index.php	2008-03-20 22:08:49.000000000 +0000
+++ 1:1.13.0-1/config/index.php	2008-07-20 15:29:04.000000000 +0100
@@ -67,11 +67,23 @@ $ourdb['postgres']['compile']    = 'pgsq
 $ourdb['postgres']['bgcolor']    = '#aaccff';
 $ourdb['postgres']['rootuser']   = 'postgres';
 
+$ourdb['sqlite']['fullname']      = 'SQLite';
+$ourdb['sqlite']['havedriver']    = 0;
+$ourdb['sqlite']['compile']       = 'pdo_sqlite';
+$ourdb['sqlite']['bgcolor']       = '#b1ebb1';
+$ourdb['sqlite']['rootuser']      = '';
+
+$ourdb['mssql']['fullname']      = 'MSSQL';
+$ourdb['mssql']['havedriver']    = 0;
+$ourdb['mssql']['compile']       = 'mssql not ready'; # Change to 'mssql' after includes/DatabaseMssql.php added;
+$ourdb['mssql']['bgcolor']       = '#ffc0cb';
+$ourdb['mssql']['rootuser']      = 'administrator';
+
 ?>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-<html>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
 <head>
-	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
+	<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
 	<title>MediaWiki <?php echo( $wgVersion ); ?> Installation</title>
 	<style type="text/css">
 
@@ -164,15 +176,15 @@ $ourdb['postgres']['rootuser']   = 'post
 	<!--
 	function hideall() {
 		<?php foreach (array_keys($ourdb) as $db) {
-		echo "\n		document.getElementById('$db').style.display='none';";
+		echo "\n		var i = document.getElementById('$db'); if (i) i.style.display='none';";
 		}
 		?>
 
 	}
 	function toggleDBarea(id,defaultroot) {
 		hideall();
-		var dbarea = document.getElementById(id).style;
-		dbarea.display = (dbarea.display == 'none') ? 'block' : 'none';
+		var dbarea = document.getElementById(id);
+		if (dbarea) dbarea.style.display = (dbarea.style.display == 'none') ? 'block' : 'none';
 		var db = document.getElementById('RootUser');
 		if (defaultroot) {
 <?php foreach (array_keys($ourdb) as $db) {
@@ -193,19 +205,18 @@ $ourdb['postgres']['rootuser']   = 'post
 <h1>MediaWiki <?php print $wgVersion ?> Installation</h1>
 
 <?php
+$mainListOpened = false; # Is the main list (environement checking) opend ? Used by dieout
 
 /* Check for existing configurations and bug out! */
 
 if( file_exists( "../LocalSettings.php" ) ) {
 	$script = defined('MW_INSTALL_PHP5_EXT') ? 'index.php5' : 'index.php';
- dieout( "<p><strong>Setup has completed, <a href='../$script'>your wiki</a> is configured.</strong></p>
-
-	<p>Please delete the /config directory for extra security.</p></div></div></div></div>" );
+	dieout( "<p><strong>Setup has completed, <a href='../$script'>your wiki</a> is configured.</strong></p>
+	<p>Please delete the /config directory for extra security.</p>" );
 }
 
 if( file_exists( "./LocalSettings.php" ) ) {
 	writeSuccessMessage();
-
 	dieout( '' );
 }
 
@@ -281,6 +292,8 @@ class ConfigData {
 <p><em>Please include all of the lines below when reporting installation problems.</em></p>
 <ul class="env-check">
 <?php
+$mainListOpened = true;
+
 $endl = "
 ";
 define( 'MW_NO_OUTPUT_BUFFER', 1 );
@@ -310,7 +323,8 @@ if (!$phpdatabases) {
 		print "<li>For <b>$full</b>, compile PHP using <b>--with-$comp</b>, "
 			."or install the $comp.so module</li>\n";
 	}
-	dieout( "</ul></ul>" );
+	echo '</ul>';
+	dieout( '' );
 }
 
 print "<li>Found database drivers for:";
@@ -343,7 +357,7 @@ if( wfIniGetBool( "magic_quotes_runtime"
 	$fatal = true;
 	?><li class='error'><strong>Fatal: <a href='http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime'>magic_quotes_runtime</a> is active!</strong>
 	This option corrupts data input unpredictably; you cannot install or use
-	MediaWiki unless this option is disabled.
+	MediaWiki unless this option is disabled.</li>
 	<?php
 }
 
@@ -351,7 +365,7 @@ if( wfIniGetBool( "magic_quotes_sybase" 
 	$fatal = true;
 	?><li class='error'><strong>Fatal: <a href='http://www.php.net/manual/en/ref.sybase.php#ini.magic-quotes-sybase'>magic_quotes_sybase</a> is active!</strong>
 	This option corrupts data input unpredictably; you cannot install or use
-	MediaWiki unless this option is disabled.
+	MediaWiki unless this option is disabled.</li>
 	<?php
 }
 
@@ -359,7 +373,7 @@ if( wfIniGetBool( "mbstring.func_overloa
 	$fatal = true;
 	?><li class='error'><strong>Fatal: <a href='http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload'>mbstring.func_overload</a> is active!</strong>
 	This option causes errors and may corrupt data unpredictably;
-	you cannot install or use MediaWiki unless this option is disabled.
+	you cannot install or use MediaWiki unless this option is disabled.</li>
 	<?php
 }
 
@@ -367,13 +381,13 @@ if( wfIniGetBool( "zend.ze1_compatibilit
 	$fatal = true;
 	?><li class="error"><strong>Fatal: <a href="http://www.php.net/manual/en/ini.core.php">zend.ze1_compatibility_mode</a> is active!</strong>
 	This option causes horrible bugs with MediaWiki; you cannot install or use
-	MediaWiki unless this option is disabled.
+	MediaWiki unless this option is disabled.</li>
 	<?php
 }
 
 
 if( $fatal ) {
-	dieout( "</ul><p>Cannot install MediaWiki.</p>" );
+	dieout( "Cannot install MediaWiki." );
 }
 
 if( wfIniGetBool( "safe_mode" ) ) {
@@ -465,11 +479,11 @@ if ( $conf->turck ) {
 
 $conf->xcache = function_exists( 'xcache_get' );
 if( $conf->xcache )
-	print "<li><a href=\"http://trac.lighttpd.net/xcache/\">XCache</a> installed</li>";
+	print "<li><a href=\"http://trac.lighttpd.net/xcache/\">XCache</a> installed</li>\n";
 
 $conf->apc = function_exists('apc_fetch');
 if ($conf->apc ) {
-	print "<li><a href=\"http://www.php.net/apc\">APC</a> installed</li>";
+	print "<li><a href=\"http://www.php.net/apc\">APC</a> installed</li>\n";
 }
 
 $conf->eaccel = function_exists( 'eaccelerator_get' );
@@ -571,9 +585,7 @@ print "<li style='font-weight:bold;color
 		: $_SERVER["SERVER_ADMIN"];
 	$conf->EmergencyContact = importPost( "EmergencyContact", $defaultEmail );
 	$conf->DBtype = importPost( "DBtype", $DefaultDBtype );
-?>
 
-<?php
 	$conf->DBserver = importPost( "DBserver", "localhost" );
 	$conf->DBname = importPost( "DBname", "wikidb" );
 	$conf->DBuser = importPost( "DBuser", "wikiuser" );
@@ -590,13 +602,21 @@ print "<li style='font-weight:bold;color
 	## MySQL specific:
 	$conf->DBprefix     = importPost( "DBprefix" );
 	$conf->setSchema( 
-		importPost( "DBschema", "mysql4" ), 
+		importPost( "DBschema", "mysql5-binary" ), 
 		importPost( "DBengine", "InnoDB" ) );
 
 	## Postgres specific:
 	$conf->DBport      = importPost( "DBport",      "5432" );
 	$conf->DBmwschema  = importPost( "DBmwschema",  "mediawiki" );
 	$conf->DBts2schema = importPost( "DBts2schema", "public" );
+	
+	## SQLite specific
+	$conf->SQLiteDataDir = importPost( "SQLiteDataDir", "" );
+	
+	## MSSQL specific
+	// We need a second field so it doesn't overwrite the MySQL one
+	$conf->DBprefix2 = importPost( "DBprefix2" );
+
 
 /* Check for validity */
 $errs = array();
@@ -716,13 +736,12 @@ if( $conf->posted && ( 0 == count( $errs
 		chdir( ".." );
 		$ok = eval( $local );
 		if( $ok === false ) {
-			dieout( "Errors in generated configuration; " .
+			dieout( "<p>Errors in generated configuration; " .
 				"most likely due to a bug in the installer... " .
-				"Config file was: " .
+				"Config file was: </p>" .
 				"<pre>" .
 				htmlspecialchars( $local ) .
-				"</pre>" .
-				"</ul>" );
+				"</pre>" );
 		}
 		$conf->DBtypename = '';
 		foreach (array_keys($ourdb) as $db) {
@@ -752,6 +771,11 @@ if( $conf->posted && ( 0 == count( $errs
 		$wgDBmwschema  = $conf->DBmwschema;
 		$wgDBts2schema = $conf->DBts2schema;
 
+		if( $conf->DBprefix2 != '' ) {
+			// For MSSQL
+			$wgDBprefix = $conf->DBprefix2;
+		}
+
 		$wgCommandLineMode = true;
 		if (! defined ( 'STDERR' ) )
 			define( 'STDERR', fopen("php://stderr", "wb"));
@@ -761,7 +785,7 @@ if( $conf->posted && ( 0 == count( $errs
 
 		$wgTitle = Title::newFromText( "Installation script" );
 		error_reporting( E_ALL );
-		print "<li>Loading class: $dbclass";
+		print "<li>Loading class: $dbclass</li>\n";
 		$dbc = new $dbclass;
 
 		if( $conf->DBtype == 'mysql' ) {
@@ -828,11 +852,11 @@ if( $conf->posted && ( 0 == count( $errs
 
 			if( !$ok ) { continue; }
 
-		} else /* not mysql */ {
+		} else { # not mysql
 			error_reporting( E_ALL );
 			$wgSuperUser = '';
 			## Possible connect as a superuser
-			if( $useRoot ) {
+			if( $useRoot && $conf->DBtype != 'sqlite' ) {
 				$wgDBsuperuser = $conf->RootUser;
 				echo( "<li>Attempting to connect to database \"postgres\" as superuser \"$wgDBsuperuser\"..." );
 				$wgDatabase = $dbc->newFromParams($wgDBserver, $wgDBsuperuser, $conf->RootPW, "postgres", 1);
@@ -852,8 +876,8 @@ if( $conf->posted && ( 0 == count( $errs
 			} else {
 				$myver = $wgDatabase->getServerVersion();
 			}
-			$wgDatabase->initial_setup('', $wgDBname);
-		}
+			if (is_callable(array($wgDatabase, 'initial_setup'))) $wgDatabase->initial_setup('', $wgDBname);
+		} 
 
 		if ( !$wgDatabase->isOpen() ) {
 			$errs["DBserver"] = "Couldn't connect to database";
@@ -863,7 +887,8 @@ if( $conf->posted && ( 0 == count( $errs
 		print "<li>Connected to $myver";
 		if ($conf->DBtype == 'mysql') {
 			if( version_compare( $myver, "4.0.14" ) < 0 ) {
-				dieout( " -- mysql 4.0.14 or later required. Aborting." );
+				print "</li>\n";
+				dieout( "-- mysql 4.0.14 or later required. Aborting." );
 			}
 			$mysqlNewAuth = version_compare( $myver, "4.1.0", "ge" );
 			if( $mysqlNewAuth && $mysqlOldClient ) {
@@ -911,7 +936,7 @@ if( $conf->posted && ( 0 == count( $errs
 		}
 		else if ($conf->DBtype == 'postgres') {
 			if( version_compare( $myver, "PostgreSQL 8.0" ) < 0 ) {
-				dieout( " <b>Postgres 8.0 or later is required</b>. Aborting.</li></ul>" );
+				dieout( "<b>Postgres 8.0 or later is required</b>. Aborting." );
 			}
 		}
 
@@ -1006,7 +1031,7 @@ if( $conf->posted && ( 0 == count( $errs
 			if ($conf->DBtype == 'mysql') {
 				dbsource( "../maintenance/tables.sql", $wgDatabase );
 				dbsource( "../maintenance/interwiki.sql", $wgDatabase );
-			} else if ($conf->DBtype == 'postgres') {
+			} elseif (is_callable(array($wgDatabase, 'setup_database'))) {
 				$wgDatabase->setup_database();
 			}
 			else {
@@ -1088,27 +1113,26 @@ if( $conf->posted && ( 0 == count( $errs
 		$f = fopen( "LocalSettings.php", 'xt' );
 
 		if( $f == false ) {
+			print( "</li>\n" );
 			dieout( "<p>Couldn't write out LocalSettings.php. Check that the directory permissions are correct and that there isn't already a file of that name here...</p>\n" .
 			"<p>Here's the file that would have been written, try to paste it into place manually:</p>\n" .
 			"<pre>\n" . htmlspecialchars( $localSettings ) . "</pre>\n" );
 		}
 		if(fwrite( $f, $localSettings ) ) {
 			fclose( $f );
-			print "</li></ul><hr/>\n";
+			print "<hr/>\n";
 			writeSuccessMessage();
+			print "</li>\n";
 		} else {
 			fclose( $f );
-			die("<p class='error'>An error occured while writing the config/LocalSettings.php file. Check user rights and disk space then try again.</p>\n");
-			print "</li></ul>\n";
+			dieout( "<p class='error'>An error occured while writing the config/LocalSettings.php file. Check user rights and disk space then try again.</p></li>\n" );
 		}
 
 	} while( false );
 }
-?>
-</ul>
-
 
-<?php
+print "</ul>\n";
+$mainListOpened = false;
 
 if( count( $errs ) ) {
 	/* Display options form */
@@ -1120,39 +1144,30 @@ if( count( $errs ) ) {
 
 <form action="<?php echo defined('MW_INSTALL_PHP5_EXT') ? 'index.php5' : 'index.php'; ?>" name="config" method="post">
 
-
 <h2>Site config</h2>
 
 <div class="config-section">
 	<div class="config-input">
-		<?php
-		aField( $conf, "Sitename", "Wiki name:" );
-		?>
+		<?php aField( $conf, "Sitename", "Wiki name:" ); ?>
 	</div>
 	<p class="config-desc">
 		Preferably a short word without punctuation, i.e. "Wikipedia".<br />
 		Will appear as the namespace name for "meta" pages, and throughout the interface.
 	</p>
-
-	<div class="config-input">
-		<?php
-		aField( $conf, "EmergencyContact", "Contact e-mail:" );
-		?>
-	</div>
+	<div class="config-input"><?php aField( $conf, "EmergencyContact", "Contact e-mail:" ); ?></div>
 	<p class="config-desc">
 		Displayed to users in some error messages, used as the return address for password reminders, and used as the default sender address of e-mail notifications.
 	</p>
 
 	<div class="config-input">
 		<label class='column' for="LanguageCode">Language:</label>
-		<select id="LanguageCode" name="LanguageCode">
-
-		<?php
+		<select id="LanguageCode" name="LanguageCode"><?php
 			$list = getLanguageList();
 			foreach( $list as $code => $name ) {
 				$sel = ($code == $conf->LanguageCode) ? 'selected="selected"' : '';
-				echo "\t\t<option value=\"$code\" $sel>$name</option>\n";
+				echo "\n\t\t<option value=\"$code\" $sel>$name</option>";
 			}
+			echo "\n";
 		?>
 		</select>
 	</div>
@@ -1174,15 +1189,14 @@ if( count( $errs ) ) {
 			$icon = urlencode( "$wgServer$wgUploadPath/wiki.png" );
 			$ccApp = htmlspecialchars( "http://creativecommons.org/license/?partner=$partner&exit_url=$exit&partner_icon_url=$icon" );
 			print "<a href=\"$ccApp\" target='_blank'>choose</a>";
-			?>
-		<?php if( $conf->License == "cc" ) { ?>
+			if( $conf->License == "cc" ) { ?>
 			<ul>
-				<li><?php aField( $conf, "RightsIcon", "<img src=\"" . htmlspecialchars( $conf->RightsIcon ) . "\" alt='(Creative Commons icon)' />", "hidden" ); ?></li>
-				<li><?php aField( $conf, "RightsText", htmlspecialchars( $conf->RightsText ), "hidden" ); ?></li>
-				<li><?php aField( $conf, "RightsCode", "code: " . htmlspecialchars( $conf->RightsCode ), "hidden" ); ?></li>
-				<li><?php aField( $conf, "RightsUrl", "<a href=\"" . htmlspecialchars( $conf->RightsUrl ) . "\">" . htmlspecialchars( $conf->RightsUrl ) . "</a>", "hidden" ); ?></li>
+			<li><?php aField( $conf, "RightsIcon", "<img src=\"" . htmlspecialchars( $conf->RightsIcon ) . "\" alt='(Creative Commons icon)' />", "hidden" ); ?></li>
+			<li><?php aField( $conf, "RightsText", htmlspecialchars( $conf->RightsText ), "hidden" ); ?></li>
+			<li><?php aField( $conf, "RightsCode", "code: " . htmlspecialchars( $conf->RightsCode ), "hidden" ); ?></li>
+			<li><?php aField( $conf, "RightsUrl", "<a href=\"" . htmlspecialchars( $conf->RightsUrl ) . "\">" . htmlspecialchars( $conf->RightsUrl ) . "</a>", "hidden" ); ?></li>
 			</ul>
-		<?php } ?>
+			<?php } ?>
 			</li>
 		</ul>
 	</div>
@@ -1216,22 +1230,22 @@ if( count( $errs ) ) {
 			if ( $conf->turck ) {
 				echo "<li>";
 				aField( $conf, "Shm", "Turck MMCache", "radio", "turck" );
-				echo "</li>";
+				echo "</li>\n";
 			}
 			if( $conf->xcache ) {
-				echo( '<li>' );
+				echo "<li>";
 				aField( $conf, 'Shm', 'XCache', 'radio', 'xcache' );
-				echo( '</li>' );
+				echo "</li>\n";
 			}
 			if ( $conf->apc ) {
 				echo "<li>";
 				aField( $conf, "Shm", "APC", "radio", "apc" );
-				echo "</li>";
+				echo "</li>\n";
 			}
 			if ( $conf->eaccel ) {
 				echo "<li>";
 				aField( $conf, "Shm", "eAccelerator", "radio", "eaccel" );
-				echo "</li>";
+				echo "</li>\n";
 			}
 			if ( $conf->dba ) {
 				echo "<li>";
@@ -1306,7 +1320,7 @@ if( count( $errs ) ) {
 	</div>
 	<div class="config-desc">
 		<p>If this option is enabled, users have to confirm their e-mail address using a magic link sent to them whenever they set or change it, and only authenticated e-mail addresses can receive mails from other users and/or
-		change notification mails. Setting this option is <B>recommended</B> for public wikis because of potential abuse of the e-mail features above.</p>
+		change notification mails. Setting this option is <b>recommended</b> for public wikis because of potential abuse of the e-mail features above.</p>
 	</div>
 
 </div>
@@ -1315,30 +1329,24 @@ if( count( $errs ) ) {
 
 <div class="config-section">
 <div class="config-input">
-		<label class='column'>Database type:</label>
-<?php if (isset($errs['DBpicktype'])) print "<span class='error'>$errs[DBpicktype]</span>\n"; ?>
-		<ul class='plain'><?php database_picker($conf) ?></ul>
+	<label class='column'>Database type:</label>
+<?php if (isset($errs['DBpicktype'])) print "\t<span class='error'>$errs[DBpicktype]</span>\n"; ?>
+	<ul class='plain'><?php 
+		database_picker($conf); 
+	?></ul>
 	</div>
 
-	<div class="config-input" style="clear:left"><?php
-		aField( $conf, "DBserver", "Database host:" );
-	?></div>
+	<div class="config-input" style="clear:left">
+	<?php aField( $conf, "DBserver", "Database host:" ); ?>
+	</div>
 	<p class="config-desc">
 		If your database server isn't on your web server, enter the name or IP address here.
 	</p>
 
-	<div class="config-input"><?php
-		aField( $conf, "DBname", "Database name:" );
-	?></div>
-	<div class="config-input"><?php
-		aField( $conf, "DBuser", "DB username:" );
-	?></div>
-	<div class="config-input"><?php
-		aField( $conf, "DBpassword", "DB password:", "password" );
-	?></div>
-	<div class="config-input"><?php
-		aField( $conf, "DBpassword2", "DB password confirm:", "password" );
-	?></div>
+	<div class="config-input"><?php aField( $conf, "DBname", "Database name:" ); ?></div>
+	<div class="config-input"><?php aField( $conf, "DBuser", "DB username:" ); ?></div>
+	<div class="config-input"><?php aField( $conf, "DBpassword", "DB password:", "password" ); ?></div>
+	<div class="config-input"><?php aField( $conf, "DBpassword2", "DB password confirm:", "password" ); ?></div>
 	<p class="config-desc">
 		If you only have a single user account and database available,
 		enter those here. If you have database root access (see below)
@@ -1349,19 +1357,11 @@ if( count( $errs ) ) {
 
 	<div class="config-input">
 		<label class="column">Superuser account:</label>
-		<input type="checkbox" name="useroot" id="useroot" <?php if( $useRoot ) { ?>checked="checked" <?php } ?>/>
+		<input type="checkbox" name="useroot" id="useroot" <?php if( $useRoot ) { ?>checked="checked" <?php } ?> />
 		&nbsp;<label for="useroot">Use superuser account</label>
 	</div>
-	<div class="config-input">
-		<?php
-		aField( $conf, "RootUser", "Superuser name:", "superuser" );
-		?>
-	</div>
-	<div class="config-input">
-		<?php
-		aField( $conf, "RootPW", "Superuser password:", "password" );
-		?>
-	</div>
+	<div class="config-input"><?php aField( $conf, "RootUser", "Superuser name:", "text" ); ?></div>
+	<div class="config-input"><?php aField( $conf, "RootPW", "Superuser password:", "password" ); ?></div>
 
 	<p class="config-desc">
 		If the database user specified above does not exist, or does not have access to create
@@ -1370,9 +1370,7 @@ if( count( $errs ) ) {
 	</p>
 
 	<?php database_switcher('mysql'); ?>
-	<div class="config-input"><?php
-		aField( $conf, "DBprefix", "Database table prefix:" );
-	?></div>
+	<div class="config-input"><?php aField( $conf, "DBprefix", "Database table prefix:" ); ?></div>
 	<div class="config-desc">
 		<p>If you need to share one database between multiple wikis, or
 		between MediaWiki and another web application, you may choose to
@@ -1396,33 +1394,57 @@ if( count( $errs ) ) {
 	<div class="config-input"><label class="column">Database character set</label>
 		<div>Select one:</div>
 		<ul class="plain">
-		<li><?php aField( $conf, "DBschema", "Backwards-compatible UTF-8", "radio", "mysql4" ); ?></li>
-		<li><?php aField( $conf, "DBschema", "Experimental MySQL 4.1/5.0 UTF-8", "radio", "mysql5" ); ?></li>
-		<li><?php aField( $conf, "DBschema", "Experimental MySQL 4.1/5.0 binary", "radio", "mysql5-binary" ); ?></li>
+		<li><?php aField( $conf, "DBschema", "MySQL 4.1/5.0 binary", "radio", "mysql5-binary" ); ?></li>
+		<li><?php aField( $conf, "DBschema", "MySQL 4.1/5.0 UTF-8", "radio", "mysql5" ); ?></li>
+		<li><?php aField( $conf, "DBschema", "MySQL 4.0 backwards-compatible UTF-8", "radio", "mysql4" ); ?></li>
 		</ul>
 	</div>
 	<p class="config-desc">
-		<b>EXPERIMENTAL:</b> You can enable explicit Unicode charset support
-		for MySQL 4.1 and 5.0 servers. This is not well tested and may
-		cause things to break. <b>If upgrading an older installation, leave
-		in backwards-compatible mode.</b>
+		This option is ignored on upgrade, the same character set will be kept. 
+		<br/><br/>
+		<b>WARNING:</b> If you use <b>backwards-compatible UTF-8</b> on MySQL 4.1+, and subsequently back up the database with <tt>mysqldump</tt>, it may destroy all non-ASCII characters, irreversibly corrupting your backups!.
+		<br/><br/>
+		In <b>binary mode</b>, MediaWiki stores UTF-8 text to the database in binary fields. This is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters. In <b>UTF-8 mode</b>, MySQL will know what character set your data is in, and can present and convert it appropriately, but it won't let you store characters above the <a target="_blank" href="http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes">Basic Multilingual Plane</a>.
 	</p>
-	</div>
+	</fieldset>
 
 	<?php database_switcher('postgres'); ?>
+	<div class="config-input"><?php aField( $conf, "DBport", "Database port:" ); ?></div>
+	<div class="config-input"><?php aField( $conf, "DBmwschema", "Schema for mediawiki:" ); ?></div>
+	<div class="config-input"><?php aField( $conf, "DBts2schema", "Schema for tsearch2:" ); ?></div>
+	<div class="config-desc">
+		<p>The username specified above (at "DB username") will have its search path set to the above schemas, 
+		so it is recommended that you create a new user. The above schemas are generally correct: 
+        only change them if you are sure you need to.</p>
+	</div>
+	</fieldset>
+
+	<?php database_switcher('sqlite'); ?>
+	<div class="config-desc">
+		<b>NOTE:</b> SQLite only uses the <i>Database name</i> setting above, the user, password and root settings are ignored.
+	</div>
 	<div class="config-input"><?php
-		aField( $conf, "DBport", "Database port:" );
-	?></div>
-	<div class="config-input"><?php
-		aField( $conf, "DBmwschema", "Schema for mediawiki:" );
+		aField( $conf, "SQLiteDataDir", "SQLite data directory:" );
 	?></div>
+	<div class="config-desc">
+		<p>SQLite stores table data into files in the filesystem.
+		If you do not provide an explicit path, a "data" directory in
+		the parent of your document root will be used.</p>
+		
+		<p>This directory must exist and be writable by the web server.</p>
+	</div>
+	</fieldset>
+
+	<?php database_switcher('mssql'); ?>
 	<div class="config-input"><?php
-		aField( $conf, "DBts2schema", "Schema for tsearch2:" );
+		aField( $conf, "DBprefix2", "Database table prefix:" );
 	?></div>
 	<div class="config-desc">
-		<p>The username specified above (at "DB username") will have its search path set to the above schemas, 
-		so it is recommended that you create a new user. The above schemas are generally correct: 
-        only change them if you are sure you need to.</p>
+		<p>If you need to share one database between multiple wikis, or
+		between MediaWiki and another web application, you may choose to
+		add a prefix to all the table names to avoid conflicts.</p>
+
+		<p>Avoid exotic characters; something like <tt>mw_</tt> is good.</p>
 	</div>
 	</fieldset>
 
@@ -1430,9 +1452,8 @@ if( count( $errs ) ) {
 		<label class='column'>&nbsp;</label>
 		<input type="submit" value="Install MediaWiki!" class="btn-install" />
 	</div>
-
 </div>
-
+</form>
 <script type="text/javascript">
 window.onload = toggleDBarea('<?php echo $conf->DBtype; ?>',
 <?php
@@ -1440,9 +1461,6 @@ window.onload = toggleDBarea('<?php echo
 	echo strlen(importPost('RootUser', '')) ? 0 : 1;
 ?>);
 </script>
-
-</form>
-
 <?php
 }
 
@@ -1568,6 +1586,36 @@ function writeLocalSettings( $conf ) {
 		$slconf['RightsIcon'] = $conf->RightsIcon;
 	}
 
+	if( $conf->DBtype == 'mysql' ) {
+		$dbsettings =
+"# MySQL specific settings
+\$wgDBprefix         = \"{$slconf['DBprefix']}\";
+
+# MySQL table options to use during installation or update
+\$wgDBTableOptions   = \"{$slconf['DBTableOptions']}\";
+
+# Experimental charset support for MySQL 4.1/5.0.
+\$wgDBmysql5 = {$conf->DBmysql5};";
+	} elseif( $conf->DBtype == 'postgres' ) {
+		$dbsettings =
+"# Postgres specific settings
+\$wgDBport           = \"{$slconf['DBport']}\";
+\$wgDBmwschema       = \"{$slconf['DBmwschema']}\";
+\$wgDBts2schema      = \"{$slconf['DBts2schema']}\";";
+	} elseif( $conf->DBtype == 'sqlite' ) {
+		$dbsettings =
+"# SQLite-specific settings
+\$wgSQLiteDataDir    = \"{$slconf['SQLiteDataDir']}\";";
+	} elseif( $conf->DBtype == 'mssql' ) {
+		$dbsettings =
+"# MSSQL specific settings
+\$wgDBprefix         = \"{$slconf['DBprefix2']}\";";
+	} else {
+		// ummm... :D
+		$dbsettings = '';
+	}
+
+
 	$localsettings = "
 # This file was automatically generated by the MediaWiki installer.
 # If you make manual changes, please keep track in case you need to
@@ -1576,6 +1624,9 @@ function writeLocalSettings( $conf ) {
 # See includes/DefaultSettings.php for all configurable settings
 # and their default values, but don't forget to make changes in _this_
 # file, not there.
+#
+# Further documentation for configuration settings may be found at:
+# http://www.mediawiki.org/wiki/Manual:Configuration_settings
 
 # If you customize your file layout, set \$IP to the directory that contains
 # the other MediaWiki files. It will be used as a base to locate files.
@@ -1605,47 +1656,31 @@ if ( \$wgCommandLineMode ) {
 
 ## The URL base path to the directory containing the wiki;
 ## defaults for all runtime URL paths are based off of this.
+## For more information on customizing the URLs please see:
+## http://www.mediawiki.org/wiki/Manual:Short_URL
 \$wgScriptPath       = \"{$slconf['ScriptPath']}\";
 \$wgScriptExtension  = \"{$slconf['ScriptExtension']}\";
 
-## For more information on customizing the URLs please see:
-## http://www.mediawiki.org/wiki/Manual:Short_URL
+## UPO means: this is also a user preference option
 
 \$wgEnableEmail      = $enableemail;
-\$wgEnableUserEmail  = $enableuseremail;
+\$wgEnableUserEmail  = $enableuseremail; # UPO
 
 \$wgEmergencyContact = \"{$slconf['EmergencyContact']}\";
 \$wgPasswordSender = \"{$slconf['PasswordSender']}\";
 
-## For a detailed description of the following switches see
-## http://www.mediawiki.org/wiki/Extension:Email_notification 
-## and http://www.mediawiki.org/wiki/Extension:Email_notification
-## There are many more options for fine tuning available see
-## /includes/DefaultSettings.php
-## UPO means: this is also a user preference option
 \$wgEnotifUserTalk = $enotifusertalk; # UPO
 \$wgEnotifWatchlist = $enotifwatchlist; # UPO
 \$wgEmailAuthentication = $eauthent;
 
+## Database settings
 \$wgDBtype           = \"{$slconf['DBtype']}\";
 \$wgDBserver         = \"{$slconf['DBserver']}\";
 \$wgDBname           = \"{$slconf['DBname']}\";
 \$wgDBuser           = \"{$slconf['DBuser']}\";
 \$wgDBpassword       = \"{$slconf['DBpassword']}\";
 
-# MySQL specific settings
-\$wgDBprefix         = \"{$slconf['DBprefix']}\";
-
-# MySQL table options to use during installation or update
-\$wgDBTableOptions   = \"{$slconf['DBTableOptions']}\";
-
-# Experimental charset support for MySQL 4.1/5.0.
-\$wgDBmysql5 = {$conf->DBmysql5};
-
-# Postgres specific settings
-\$wgDBport           = \"{$slconf['DBport']}\";
-\$wgDBmwschema       = \"{$slconf['DBmwschema']}\";
-\$wgDBts2schema      = \"{$slconf['DBts2schema']}\";
+{$dbsettings}
 
 ## Shared memory settings
 \$wgMainCacheType = $cacheType;
@@ -1700,7 +1735,14 @@ if ( \$wgCommandLineMode ) {
 }
 
 function dieout( $text ) {
-	die( $text . "\n\n</body>\n</html>" );
+	global $mainListOpened;
+	if( $mainListOpened ) echo( "</ul>" );
+	if( $text != '' && substr( $text, 0, 2 ) != '<p'  && substr( $text, 0, 2 ) != '<h' ){
+		echo "<p>$text</p>\n";
+	} else {
+		echo $text;
+	}
+	die( "\n\n</div>\n</div>\n</div>\n</div>\n</body>\n</html>" );
 }
 
 function importVar( &$var, $name, $default = "" ) {
@@ -1743,10 +1785,8 @@ function aField( &$conf, $field, $text, 
 	if ($type == 'radio')
 		$id .= $radioCount++;
 
-	if( $nolabel ) {
-		echo "\t\t<label>";
-	} else {
-		echo "\t\t<label class='column' for=\"$id\">$text</label>\n";
+	if( !$nolabel ) {
+		echo "<label class='column' for=\"$id\">$text</label>";
 	}
 
 	if( $type == "radio" && $value == $conf->$field ) {
@@ -1754,7 +1794,7 @@ function aField( &$conf, $field, $text, 
 	} else {
 		$checked = "";
 	}
-	echo "\t\t<input $xtype name=\"$field\" id=\"$id\" class=\"iput-$type\" $checked ";
+	echo "<input $xtype name=\"$field\" id=\"$id\" class=\"iput-$type\" $checked ";
 	if ($onclick) {
 		echo " onclick='toggleDBarea(\"$value\",1)' " ;
 	}
@@ -1766,9 +1806,9 @@ function aField( &$conf, $field, $text, 
 	}
 
 
-	echo "\" />\n";
+	echo "\" />";
 	if( $nolabel ) {
-		echo " $text</label>\n";
+		echo "<label for=\"$id\">$text</label>";
 	}
 
 	global $errs;
@@ -1875,12 +1915,12 @@ function database_picker($conf) {
 	print "\n";
 	foreach(array_keys($ourdb) as $db) {
 		if ($ourdb[$db]['havedriver']) {
-			print "<li>";
+			print "\t<li>";
 			aField( $conf, "DBtype", $ourdb[$db]['fullname'], 'radio', $db, 'onclick');
 			print "</li>\n";
 		}
 	}
-	print "\n";
+	print "\n\t";
 }
 
 function database_switcher($db) {
@@ -1897,7 +1937,7 @@ function printListItem( $item ) {
 ?>
 
 	<div class="license">
-	<hr>
+	<hr/>
 	<p>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
@@ -1930,10 +1970,13 @@ function printListItem( $item ) {
 			<li><a href="../README">Readme</a></li>
 			<li><a href="../RELEASE-NOTES">Release notes</a></li>
 			<li><a href="../docs/">Documentation</a></li>
-			<li><a href="http://meta.wikipedia.org/wiki/MediaWiki_User's_Guide">User's Guide</a></li>
-			<li><a href="http://meta.wikimedia.org/wiki/MediaWiki_FAQ">FAQ</a></li>
+			<li><a href="http://www.mediawiki.org/wiki/Help:Contents">User's Guide</a></li>
+			<li><a href="http://www.mediawiki.org/wiki/Manual:Contents">Administrator's Guide</a></li>
+			<li><a href="http://www.mediawiki.org/wiki/Manual:FAQ">FAQ</a></li>
 		</ul>
-		<p style="font-size:90%;margin-top:1em">MediaWiki is Copyright &copy; 2001-2008 by Magnus Manske, Brion Vibber, Lee Daniel Crocker, Tim Starling, Erik M&ouml;ller, Gabriel Wicke and others.</p>
+		<p style="font-size:90%;margin-top:1em">MediaWiki is Copyright Â© 2001-2008 by Magnus Manske, Brion Vibber,
+		 Lee Daniel Crocker, Tim Starling, Erik MÃ¶ller, Gabriel Wicke, Ã†var ArnfjÃ¶rÃ° Bjarmason, Niklas LaxstrÃ¶m,
+		 Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor, Aaron Schulz and others.</p>
 	</div></div>
 </div>
 
diff -pruN 1:1.12.0-2/debian/changelog 1:1.13.0-1/debian/changelog
--- 1:1.12.0-2/debian/changelog	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/changelog	2008-08-17 23:16:41.000000000 +0100
@@ -1,3 +1,14 @@
+mediawiki (1:1.13.0-1) unstable; urgency=low
+
+  * New upstream release
+  * Fixed watch file. Closes: #490009
+  * Refreshed patches
+  * Bumped standard-version to 3.8.0
+  * Fixed latex-related dependencies in mediawiki-math
+  * Removed obsolete linda override, thanks lintian !
+
+ -- Romain Beauxis <toots@rastageeks.org>  Sun, 17 Aug 2008 11:01:43 +0200
+
 mediawiki (1:1.12.0-2) unstable; urgency=low
 
   * Fixed postgresql dependency
diff -pruN 1:1.12.0-2/debian/control 1:1.13.0-1/debian/control
--- 1:1.12.0-2/debian/control	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/control	2008-08-17 23:16:41.000000000 +0100
@@ -5,7 +5,7 @@ Maintainer: Mediawiki Maintenance Team <
 Uploaders: Romain Beauxis <toots@rastageeks.org>
 Build-Depends: debhelper (>= 4.2.0), quilt, patchutils (>= 0.2.25), cdbs (>= 0.4.27), ocaml-nox | ocaml, xsltproc, docbook-xml, docbook-xsl, po-debconf
 Homepage: http://www.mediawiki.org/
-Standards-Version: 3.7.3
+Standards-Version: 3.8.0
 
 Package: mediawiki
 Architecture: all
@@ -25,9 +25,9 @@ Description: website engine for collabor
 
 Package: mediawiki-math
 Architecture: any
-Depends: ${interpreter:Depends}, tetex-bin | texlive-latex-base, gs-gpl | gs-esp, imagemagick, ${shlibs:Depends}
+Depends: ${interpreter:Depends}, texlive-latex-base, ghostscript, imagemagick, ${shlibs:Depends}
 Replaces: mediawiki1.5-math, mediawiki1.9-math, mediawiki1.10-math
-Recommends: mediawiki, latex-cjk-all, tetex-extra
+Recommends: mediawiki, latex-cjk-all, texlive-latex-extra 
 Description: math rendering plugin for MediaWiki
  MediaWiki is a wiki engine (a program for creating a collaboratively
  edited website). It is designed to handle heavy websites containing
diff -pruN 1:1.12.0-2/debian/control.in 1:1.13.0-1/debian/control.in
--- 1:1.12.0-2/debian/control.in	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/control.in	2008-08-17 23:16:41.000000000 +0100
@@ -5,7 +5,7 @@ Maintainer: Mediawiki Maintenance Team <
 Uploaders: Romain Beauxis <toots@rastageeks.org>
 Build-Depends: @cdbs@, ocaml-nox | ocaml, xsltproc, docbook-xml, docbook-xsl, po-debconf
 Homepage: http://www.mediawiki.org/
-Standards-Version: 3.7.3
+Standards-Version: 3.8.0
 
 Package: mediawiki
 Architecture: all
@@ -25,9 +25,9 @@ Description: website engine for collabor
 
 Package: mediawiki-math
 Architecture: any
-Depends: ${interpreter:Depends}, tetex-bin | texlive-latex-base, gs-gpl | gs-esp, imagemagick, ${shlibs:Depends}
+Depends: ${interpreter:Depends}, texlive-latex-base, ghostscript, imagemagick, ${shlibs:Depends}
 Replaces: mediawiki1.5-math, mediawiki1.9-math, mediawiki1.10-math
-Recommends: mediawiki, latex-cjk-all, tetex-extra
+Recommends: mediawiki, latex-cjk-all, texlive-latex-extra 
 Description: math rendering plugin for MediaWiki
  MediaWiki is a wiki engine (a program for creating a collaboratively
  edited website). It is designed to handle heavy websites containing
diff -pruN 1:1.12.0-2/debian/mediawiki.dirs 1:1.13.0-1/debian/mediawiki.dirs
--- 1:1.12.0-2/debian/mediawiki.dirs	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/mediawiki.dirs	2008-08-17 23:16:41.000000000 +0100
@@ -1,3 +1,2 @@
 var/lib/mediawiki/images
 etc/mediawiki
-usr/share/linda/overrides/
diff -pruN 1:1.12.0-2/debian/mediawiki.linda-override 1:1.13.0-1/debian/mediawiki.linda-override
--- 1:1.12.0-2/debian/mediawiki.linda-override	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/mediawiki.linda-override	1970-01-01 01:00:00.000000000 +0100
@@ -1,2 +0,0 @@
-Tag: extra-license-file
-Data: usr/share/mediawiki/includes/Licenses.php
diff -pruN 1:1.12.0-2/debian/patches/debian_specific_config.patch 1:1.13.0-1/debian/patches/debian_specific_config.patch
--- 1:1.12.0-2/debian/patches/debian_specific_config.patch	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/patches/debian_specific_config.patch	2008-08-17 23:16:41.000000000 +0100
@@ -1,8 +1,8 @@
-Index: mediawiki-1.12.0/config/index.php
+Index: mediawiki-1.13.0/config/index.php
 ===================================================================
---- mediawiki-1.12.0.orig/config/index.php	2008-03-20 23:08:49.000000000 +0100
-+++ mediawiki-1.12.0/config/index.php	2008-03-24 02:31:45.000000000 +0100
-@@ -212,7 +212,7 @@
+--- mediawiki-1.13.0.orig/config/index.php	2008-07-20 16:29:04.000000000 +0200
++++ mediawiki-1.13.0/config/index.php	2008-08-17 11:12:07.000000000 +0200
+@@ -223,7 +223,7 @@
  if( !is_writable( "." ) ) {
  	dieout( "<h2>Can't write config file, aborting</h2>
  
@@ -11,7 +11,7 @@ Index: mediawiki-1.12.0/config/index.php
  	writable by the web server. Once configuration is done you'll move the created
  	<tt>LocalSettings.php</tt> to the parent directory, and for added safety you can
  	then remove the <tt>config</tt> subdirectory entirely.</p>
-@@ -1454,16 +1454,7 @@
+@@ -1472,16 +1472,7 @@
  <div class="success-box">
  <p>Installation successful!</p>
  <p>To complete the installation, please do the following:
@@ -29,7 +29,7 @@ Index: mediawiki-1.12.0/config/index.php
  </div>
  EOT;
  	} else {
-@@ -1471,7 +1462,7 @@
+@@ -1489,7 +1480,7 @@
  <div class="success-box">
  <p>
  <span class="success-message">Installation successful!</span>
@@ -38,7 +38,7 @@ Index: mediawiki-1.12.0/config/index.php
  <a href="../$script"> this link</a> to your wiki.</p>
  <p>You should change file permissions for <tt>LocalSettings.php</tt> as required to
  prevent other users on the server reading passwords and altering configuration data.</p>
-@@ -1579,6 +1570,12 @@
+@@ -1630,6 +1621,12 @@
  
  # If you customize your file layout, set \$IP to the directory that contains
  # the other MediaWiki files. It will be used as a base to locate files.
@@ -51,7 +51,7 @@ Index: mediawiki-1.12.0/config/index.php
  if( defined( 'MW_INSTALL_PATH' ) ) {
  	\$IP = MW_INSTALL_PATH;
  } else {
-@@ -1689,6 +1686,11 @@
+@@ -1724,6 +1721,11 @@
  
  \$wgDiff3 = \"{$slconf['diff3']}\";
  
diff -pruN 1:1.12.0-2/debian/patches/fix_postgre.patch 1:1.13.0-1/debian/patches/fix_postgre.patch
--- 1:1.12.0-2/debian/patches/fix_postgre.patch	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/patches/fix_postgre.patch	2008-08-17 23:16:41.000000000 +0100
@@ -1,8 +1,8 @@
-Index: mediawiki-1.12.0/includes/DatabasePostgres.php
+Index: mediawiki-1.13.0/includes/db/DatabasePostgres.php
 ===================================================================
---- mediawiki-1.12.0.orig/includes/DatabasePostgres.php	2008-03-20 23:08:48.000000000 +0100
-+++ mediawiki-1.12.0/includes/DatabasePostgres.php	2008-03-24 02:30:29.000000000 +0100
-@@ -93,6 +93,44 @@
+--- mediawiki-1.13.0.orig/includes/db/DatabasePostgres.php	2008-07-24 14:42:19.000000000 +0200
++++ mediawiki-1.13.0/includes/db/DatabasePostgres.php	2008-08-17 11:17:12.000000000 +0200
+@@ -89,6 +89,44 @@
  
  	}
  
diff -pruN 1:1.12.0-2/debian/patches/mimetypes.patch 1:1.13.0-1/debian/patches/mimetypes.patch
--- 1:1.12.0-2/debian/patches/mimetypes.patch	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/patches/mimetypes.patch	2008-08-17 23:16:41.000000000 +0100
@@ -1,15 +1,15 @@
-Index: mediawiki-1.12.0/includes/DefaultSettings.php
+Index: mediawiki-1.13.0/includes/DefaultSettings.php
 ===================================================================
---- mediawiki-1.12.0.orig/includes/DefaultSettings.php	2008-03-24 02:30:29.000000000 +0100
-+++ mediawiki-1.12.0/includes/DefaultSettings.php	2008-03-24 02:30:29.000000000 +0100
-@@ -349,8 +349,8 @@
- /** Sets the mime type definition file to use by MimeMagic.php.
- * @global string $wgMimeTypeFile
- */
+--- mediawiki-1.13.0.orig/includes/DefaultSettings.php	2008-08-17 11:10:34.000000000 +0200
++++ mediawiki-1.13.0/includes/DefaultSettings.php	2008-08-17 11:11:45.000000000 +0200
+@@ -343,8 +343,8 @@
+ $wgVerifyMimeType= true;
+ 
+ /** Sets the mime type definition file to use by MimeMagic.php. */
 -$wgMimeTypeFile= "includes/mime.types";
 -#$wgMimeTypeFile= "/etc/mime.types";
 +#$wgMimeTypeFile= "includes/mime.types";
 +$wgMimeTypeFile= "/etc/mime.types";
  #$wgMimeTypeFile= NULL; #use built-in defaults only.
  
- /** Sets the mime type info file to use by MimeMagic.php.
+ /** Sets the mime type info file to use by MimeMagic.php. */
diff -pruN 1:1.12.0-2/debian/patches/texvc_location.patch 1:1.13.0-1/debian/patches/texvc_location.patch
--- 1:1.12.0-2/debian/patches/texvc_location.patch	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/patches/texvc_location.patch	2008-08-17 23:16:41.000000000 +0100
@@ -1,8 +1,8 @@
-Index: mediawiki-1.12.0/includes/DefaultSettings.php
+Index: mediawiki-1.13.0/includes/DefaultSettings.php
 ===================================================================
---- mediawiki-1.12.0.orig/includes/DefaultSettings.php	2008-03-20 23:08:48.000000000 +0100
-+++ mediawiki-1.12.0/includes/DefaultSettings.php	2008-03-24 02:32:07.000000000 +0100
-@@ -1530,7 +1530,7 @@
+--- mediawiki-1.13.0.orig/includes/DefaultSettings.php	2008-08-17 11:10:06.000000000 +0200
++++ mediawiki-1.13.0/includes/DefaultSettings.php	2008-08-17 11:10:24.000000000 +0200
+@@ -1606,7 +1606,7 @@
   */
  $wgUseTeX = false;
  /** Location of the texvc binary */
diff -pruN 1:1.12.0-2/debian/rules 1:1.13.0-1/debian/rules
--- 1:1.12.0-2/debian/rules	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/rules	2008-08-17 23:16:41.000000000 +0100
@@ -28,7 +28,6 @@ binary-install/mediawiki::
 	chmod a+x debian/mediawiki/usr/share/mediawiki/maintenance/fetchInterwiki.pl
 	chmod a+x debian/mediawiki/usr/share/mediawiki/maintenance/postgres/compare_schemas.pl
 	chmod a+x debian/mediawiki/usr/share/mediawiki/maintenance/postgres/mediawiki_mysql2postgres.pl
-	chmod -x debian/mediawiki/usr/share/mediawiki/includes/SpecialProtectedtitles.php
 	find debian/mediawiki/usr/share/mediawiki -maxdepth 1 -mindepth 1 | grep -v "\(LocalSettings.php\|AdminSettings.php\|debian-scripts\|images\|extensions\|config\)" | \
 	while read i; do \
 		dh_link "`echo "$$i" | sed -e s#debian/mediawiki/##`" \
@@ -37,9 +36,8 @@ binary-install/mediawiki::
 	# Remove Makefiles
 	find debian/mediawiki/ -iname makefile -exec rm {} \;
 	# License added to copyright file:
-	rm -rf debian/mediawiki/usr/share/mediawiki/skins/common/images/icons/COPYING
-	# Add linda override about a class called Licence.php
-	cp $(CURDIR)/debian/mediawiki.linda-override $(CURDIR)/debian/mediawiki/usr/share/linda/overrides/mediawiki
+	rm -f debian/mediawiki/usr/share/mediawiki/skins/common/images/icons/COPYING
+	rm -f debian/mediawiki/usr/share/mediawiki/skins/common/images/cyrl/LICENSE
 
 binary-install/mediawiki-math::
 	if [ -x /usr/bin/ocamlopt ]; then\
diff -pruN 1:1.12.0-2/debian/watch 1:1.13.0-1/debian/watch
--- 1:1.12.0-2/debian/watch	2008-08-17 23:22:17.000000000 +0100
+++ 1:1.13.0-1/debian/watch	2008-08-17 23:16:41.000000000 +0100
@@ -4,8 +4,8 @@
 # See uscan(1) for format
 
 # Compulsory line, this is a version 3 file
-version=2
+version=3
 
 # Uncomment to examine a Webserver directory
-http://prdownloads.sourceforge.net/wikipedia/mediawiki-(1\.7\.\d*)\.tar\.gz
+http://www.mediawiki.org/wiki/Download http://download.wikimedia.org/mediawiki/.*/mediawiki-(.*).tar.gz
 
diff -pruN 1:1.12.0-2/docs/database.txt 1:1.13.0-1/docs/database.txt
--- 1:1.12.0-2/docs/database.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/database.txt	2008-07-09 06:50:05.000000000 +0100
@@ -158,12 +158,9 @@ enclose small groups of queries in their
 following syntax:
 
 $dbw = wfGetDB( DB_MASTER );
-$dbw->immediateBegin();
+$dbw->begin();
 /* Do queries */
-$dbw->immediateCommit();
-
-There are functions called begin() and commit() but they don't do what
-you would expect. Don't use them.
+$dbw->commit();
 
 Use of locking reads (e.g. the FOR UPDATE clause) is not advised. They
 are poorly implemented in InnoDB and will cause regular deadlock errors.
diff -pruN 1:1.12.0-2/docs/deferred.txt 1:1.13.0-1/docs/deferred.txt
--- 1:1.12.0-2/docs/deferred.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/deferred.txt	2008-03-16 18:14:31.000000000 +0000
@@ -1,27 +1,36 @@
-
 deferred.txt
 
-A few of the database updates required by various functions here
-can be deferred until after the result page is displayed to the
-user.  For example, updating the view counts, updating the
-linked-to tables after a save, etc.  PHP does not yet have any
-way to tell the server to actually return and disconnect while
-still running these updates (as a Java servelet could), but it
-might have such a feature in the future.
-
-We handle these by creating a deferred-update object (in a real
-O-O language these would be classes that implement an interface)
-and putting those objects on a global list, then executing the
-whole list after the page is displayed. We don't do anything
-smart like collating updates to the same table or such because
-the list is almost always going to have just one item on it, if
-that, so it's not worth the trouble.
-
-
-Since 1.6 there is a 'job queue' in the jobs table, which is used
-to update link tables of transcluding pages after edits; this
-may be extended in the future to more general background tasks.
-
-Job queue items are fetched out of the queue and run either
-at a random rate during regular page views (by default) or by
-a batch process which can be run via maintenance/runJobs.php.
+A few of the database updates required by various functions here can be
+deferred until after the result page is displayed to the user.  For example,
+updating the view counts, updating the linked-to tables after a save, etc.  PHP
+does not yet have any way to tell the server to actually return and disconnect
+while still running these updates (as a Java servelet could), but it might have
+such a feature in the future.
+
+We handle these by creating a deferred-update object and putting those objects
+on a global list, then executing the whole list after the page is displayed. We
+don't do anything smart like collating updates to the same table or such
+because the list is almost always going to have just one item on it, if that,
+so it's not worth the trouble.
+
+Since 1.6 there is a 'job queue' in the jobs table, which is used to update
+link tables of transcluding pages after edits; this may be extended in the
+future to more general background tasks.
+
+Job queue items are fetched out of the queue and run either at a random rate
+during regular page views (by default) or by a batch process which can be run
+via maintenance/runJobs.php.
+
+Currently there are a few different types of jobs:
+
+  refreshLinks
+    Used to refresh the database tables that store the links between pages.
+    When a page is changed, all pages using that page are also cleared by
+    inserting a new job for all those pages. Each job refreshes only one page.
+
+  htmlCacheUpdate
+    Clear caches when a template is changed to ensure that changes can be seen.
+    Each job clears $wgUpdateRowsPerJob pages (500 by default).
+
+  enotifNotify
+    Used when $wgEnotifUseJobQ is true to send mail using the job queue.
diff -pruN 1:1.12.0-2/docs/design.txt 1:1.13.0-1/docs/design.txt
--- 1:1.12.0-2/docs/design.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/design.txt	2008-07-17 19:12:20.000000000 +0100
@@ -1,128 +1,110 @@
+design.txt
+
 This is a brief overview of the new design.
 
 More thorough and up-to-date information is available on the documentation
 wiki at http://www.mediawiki.org/
 
-Primary source files/objects:
-
-  index.php
-    Main script. It creates the necessary global objects and parses
-    the URL to determine what to do, which it then generally passes
-    off to somebody else (depending on the action to be taken).
-
-    All of the functions to which it might delegate generally do
-    their job by sending content to the $wgOut object. After returning,
-    the script flushes that out by calling $wgOut->output(). If there
-    are any changes that need to be made to the database that can be
-    deferred until after page display, those happen at the end.
-
-    Note that the order in the includes is touchy; Language uses
-    some global functions, etc. Likewise with the creation of the
-    global variables. Don't move them around without some forethought.
+Primary classes:
 
   User
-    Encapsulates the state of the user viewing/using the site.
-    Can be queried for things like the user's settings, name, etc.
-    Handles the details of getting and saving to the "user" table
-    of the database, and dealing with sessions and cookies.
-    More details in USER.TXT.
+    Encapsulates the state of the user viewing/using the site. Can be queried
+    for things like the user's settings, name, etc. Handles the details of
+    getting and saving to the "user" table of the database, and dealing with
+    sessions and cookies.
 
   OutputPage
-    Encapsulates the entire HTML page that will be sent in
-    response to any server request. It is used by calling its
-    functions to add text, headers, etc., in any order, and then
-    calling output() to send it all. It could be easily changed
-    to send incrementally if that becomes useful, but I prefer
-    the flexibility. This should also do the output encoding.
-    The system allocates a global one in $wgOut.
+    Encapsulates the entire HTML page that will be sent in response to any
+    server request. It is used by calling its functions to add text, headers,
+    etc., in any order, and then calling output() to send it all. It could be
+    easily changed to send incrementally if that becomes useful, but I prefer
+    the flexibility. This should also do the output encoding. The system
+    allocates a global one in $wgOut.
 
   Title
-    Represents the title of an article, and does all the work
-    of translating among various forms such as plain text, URL,
-    database key, etc. For convenience, and for historical
-    reasons, it also represents a few features of articles that
-    don't involve their text, such as access rights.
+    Represents the title of an article, and does all the work of translating
+    among various forms such as plain text, URL, database key, etc. For
+    convenience, and for historical reasons, it also represents a few features
+    of articles that don't involve their text, such as access rights.
+    See also title.txt.
 
   Article
-    Encapsulates access to the "page" table of the database. The
-    object represents a an article, and maintains state such as
-    text (in Wikitext format), flags, etc.
+    Encapsulates access to the "page" table of the database. The object
+    represents a an article, and maintains state such as text (in Wikitext
+    format), flags, etc.
 
   Revision
     Encapsulates individual page revision data and access to the
-    revision/text/blobs storage system. Higher-level code should
-    never touch text storage directly; this class mediates it.
+    revision/text/blobs storage system. Higher-level code should never touch
+    text storage directly; this class mediates it.
 
   Skin
-    Encapsulates a "look and feel" for the wiki. All of the
-    functions that render HTML, and make choices about how to
-    render it, are here, and are called from various other
-    places when needed (most notably, OutputPage::addWikiText()).
-    The StandardSkin object is a complete implementation, and is
-    meant to be subclassed with other skins that may override
-    some of its functions. The User object contains a reference
-    to a skin (according to that user's preference), and so
-    rather than having a global skin object we just rely on the
-    global User and get the skin with $wgUser->getSkin().
+    Encapsulates a "look and feel" for the wiki. All of the functions that
+    render HTML, and make choices about how to render it, are here, and are
+    called from various other places when needed (most notably,
+    OutputPage::addWikiText()). The StandardSkin object is a complete
+    implementation, and is meant to be subclassed with other skins that may
+    override some of its functions. The User object contains a reference to a
+    skin (according to that user's preference), and so rather than having a
+    global skin object we just rely on the global User and get the skin with
+    $wgUser->getSkin().
+    See also skin.txt.
 
   Language
-    Represents the language used for incidental text, and also
-    has some character encoding functions and other locale stuff.
-	The current user interface language is instantiated as $wgLang,
-	and the local content language as $wgContLang; be sure to use
-	the *correct* language object depending upon the circumstances.
+    Represents the language used for incidental text, and also has some
+    character encoding functions and other locale stuff. The current user
+    interface language is instantiated as $wgLang, and the local content
+    language as $wgContLang; be sure to use the *correct* language object
+    depending upon the circumstances.
+	See also language.txt.
+
+  Parser
+    Class used to transform wikitext to html.
 
   LinkCache
-    Keeps information on existence of articles. See LINKCACHE.TXT.
+    Keeps information on existence of articles. See linkcache.txt.
 
 Naming/coding conventions:
 
-  These are meant to be descriptive, not dictatorial; I won't
-  presume to tell you how to program, I'm just describing the
-  methods I chose to use for myself. If you do choose to
-  follow these guidelines, it will probably be easier for you
-  to collaborate with others on the project, but if you want
-  to contribute without bothering, by all means do so (and don't
-  be surprised if I reformat your code).
-
-  - I have the code indented with tabs to save file size and
-    so that users can set their tab stops to any depth they like.
-    I use 4-space tab stops, which work well. I also use K&R brace
-    matching style. I know that's a religious issue for some,
-    so if you want to use a style that puts opening braces on the
-    next line, that's OK too, but please don't use a style where
-    closing braces don't align with either the opening brace on
-    its own line or the statement that opened the block--that's
-    confusing as hell.
-
-  - Certain functions and class members are marked with
-	/* private */, rather than being marked as such. This is a
-	hold-over from PHP 4, which didn't support proper visibilities.
-	You should not access things marked in this manner outside the
-	class/inheritance line as this code is subjected to be updated
-	in a manner that enforces this at some time in the near future,
-	and things will break. New code should use the standard method of
-	setting visibilities as normal.
-
-  - Member variables are generally "mXxx" to distinguish them.
-    This should make it easier to spot errors of forgetting the
-    required "$this->", which PHP will happily accept by creating
-    a new local variable rather than complaining.
-
-  - Globals are particularly evil in PHP; it sets a lot of them
-    automatically from cookies, query strings, and such, leading to
-    namespace conflicts; when a variable name is used in a function,
-    it is silently declared as a new local masking the global, so
-    you'll get weird error because you forgot the global declaration;
-    lack of static class member variables means you have to use
-    globals for them, etc. Evil, evil.
-
-    I think I've managed to pare down the number of globals we use
-    to a scant few dozen or so, and I've prefixed them all with "wg"
-    so you can spot errors better (odds are, if you see a "wg"
-    variable being used in a function that doesn't declare it global,
-    that's probably an error).
-
-    Other conventions: Top-level functions are wfFuncname(), names
-    of session variables are wsName, cookies wcName, and form field
-    values wpName ("p" for "POST").
\ No newline at end of file
+  These are meant to be descriptive, not dictatorial; I won't presume to tell
+  you how to program, I'm just describing the methods I chose to use for myself.
+  If you do choose to follow these guidelines, it will probably be easier for
+  you to collaborate with others on the project, but if you want to contribute
+  without bothering, by all means do so (and don't be surprised if I reformat
+  your code).
+
+  - I have the code indented with tabs to save file size and so that users can
+    set their tab stops to any depth they like. I use 4-space tab stops, which
+    work well. I also use K&R brace matching style. I know that's a religious
+    issue for some, so if you want to use a style that puts opening braces on
+    the next line, that's OK too, but please don't use a style where closing
+    braces don't align with either the opening brace on its own line or the
+    statement that opened the block--that's confusing as hell.
+
+  - Certain functions and class members are marked with /* private */, rather
+    than being marked as such. This is a hold-over from PHP 4, which didn't
+    support proper visibilities. You should not access things marked in this
+    manner outside the class/inheritance line as this code is subjected to be
+    updated in a manner that enforces this at some time in the near future, and
+    things will break. New code should use the standard method of setting
+    visibilities as normal.
+
+  - Member variables are generally "mXxx" to distinguish them. This should make
+    it easier to spot errors of forgetting the required "$this->", which PHP
+    will happily accept by creating a new local variable rather than complaining.
+
+  - Globals are particularly evil in PHP; it sets a lot of them automatically
+    from cookies, query strings, and such, leading to namespace conflicts; when
+    a variable name is used in a function, it is silently declared as a new
+    local masking the global, so you'll get weird error because you forgot the
+    global declaration; lack of static class member variables means you have to
+    use globals for them, etc. Evil, evil.
+
+    I think I've managed to pare down the number of globals we use to a scant
+    few dozen or so, and I've prefixed them all with "wg" so you can spot errors
+    better (odds are, if you see a "wg" variable being used in a function that
+    doesn't declare it global, that's probably an error).
+
+    Other conventions: Top-level functions are wfFuncname(), names of session
+    variables are wsName, cookies wcName, and form field values wpName ("p" for
+    "POST").
diff -pruN 1:1.12.0-2/docs/globals.txt 1:1.13.0-1/docs/globals.txt
--- 1:1.12.0-2/docs/globals.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/globals.txt	2008-07-17 19:12:20.000000000 +0100
@@ -1,12 +1,10 @@
 globals.txt
 
-Globals are evil. The original MediaWiki code relied on
-globals for processing context far too often. MediaWiki
-development since then has been a story of slowly moving
-context out of global variables and into objects. Storing
-processing context in object member variables allows those
-objects to be reused in a much more flexible way. Consider
-the elegance of:
+Globals are evil. The original MediaWiki code relied on globals for processing
+context far too often. MediaWiki development since then has been a story of
+slowly moving context out of global variables and into objects. Storing
+processing context in object member variables allows those objects to be reused
+in a much more flexible way. Consider the elegance of:
 
     # Generate the article HTML as if viewed by a web request
     $article = new Article( Title::newFromText( $t ) );
@@ -27,51 +25,43 @@ versus
     $wgTitle = $oldTitle
     $wgArticle = $oldArticle
 
-Some of the current MediaWiki developers have an idle
-fantasy that some day, globals will be eliminated from
-MediaWiki entirely, replaced by an application object which 
-would be passed to constructors. Whether that would be an 
-efficient, convenient solution remains to be seen, but 
-certainly PHP 5 makes such object-oriented programming 
-models easier than they were in previous versions.
-
-For the time being though, MediaWiki programmers will have
-to work in an environment with some global context. At the 
-time of writing, 418 globals were initialised on startup by 
-MediaWiki. 304 of these were configuration settings, which 
-are documented in DefaultSettings.php. There is no 
-comprehensive documentation for the remaining 114 globals, 
-however some of the most important ones are listed below. 
-They are typically initialised either in index.php or in 
+Some of the current MediaWiki developers have an idle fantasy that some day,
+globals will be eliminated from MediaWiki entirely, replaced by an application
+object which would be passed to constructors. Whether that would be an
+efficient, convenient solution remains to be seen, but certainly PHP 5 makes
+such object-oriented programming  models easier than they were in previous
+versions.
+
+For the time being though, MediaWiki programmers will have to work in an
+environment with some global context. At the time of writing, 418 globals were
+initialised on startup by  MediaWiki. 304 of these were configuration settings,
+which are documented in DefaultSettings.php. There is no  comprehensive
+documentation for the remaining 114 globals, however some of the most important
+ones are listed below. They are typically initialised either in index.php or in
 Setup.php.
 
+For a description of the classes, see design.txt.
+
+$wgTitle
+	Title object created from the request URL.
+
+$wgArticle
+	Article object corresponding to $wgTitle.
 
 $wgOut
 	OutputPage object for HTTP response.
 
 $wgUser
-	User object for the user associated with the current
-	request.
-
-$wgTitle
-	Title object created from the request URL.
+	User object for the user associated with the current request.
 
 $wgLang
-	Language object selected by user preferences
+	Language object selected by user preferences.
 
 $wgContLang
-	Language object associated with the wiki being
-	viewed.
-
-$wgArticle
-	Article object corresponding to $wgTitle.
+	Language object associated with the wiki being viewed.
 
 $wgParser
-	Parser object. Parser extensions register their
-	hooks here.
-
-$wgLoadBalancer
-	A LoadBalancer object, manages database connections.
+	Parser object. Parser extensions register their hooks here.
 
 $wgRequest
 	WebRequest object, to get request data
@@ -81,4 +71,3 @@ $wgMemc, $messageMemc, $parserMemc
 
 $wgMessageCache
 	Message cache, to manage interface messages
-
diff -pruN 1:1.12.0-2/docs/hooks.txt 1:1.13.0-1/docs/hooks.txt
--- 1:1.12.0-2/docs/hooks.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/hooks.txt	2008-07-29 15:56:27.000000000 +0100
@@ -1,39 +1,38 @@
 hooks.txt
 
-This document describes how event hooks work in MediaWiki; how to add
-hooks for an event; and how to run hooks for an event.
+This document describes how event hooks work in MediaWiki; how to add hooks for
+an event; and how to run hooks for an event.
 
 ==Glossary==
 
 event
-     Something that happens with the wiki. For example: a user logs
-     in. A wiki page is saved. A wiki page is deleted. Often there are
-     two events associated with a single action: one before the code
-     is run to make the event happen, and one after. Each event has a
-     name, preferably in CamelCase. For example, 'UserLogin',
-     'ArticleSave', 'ArticleSaveComplete', 'ArticleDelete'.
+     Something that happens with the wiki. For example: a user logs in. A wiki
+     page is saved. A wiki page is deleted. Often there are two events
+     associated with a single action: one before the code is run to make the
+     event happen, and one after. Each event has a name, preferably in
+     CamelCase. For example, 'UserLogin', 'ArticleSave', 'ArticleSaveComplete',
+     'ArticleDelete'.
 
 hook
-     A clump of code and data that should be run when an event
-     happens. This can be either a function and a chunk of data, or an
-     object and a method.
+     A clump of code and data that should be run when an event happens. This can
+     be either a function and a chunk of data, or an object and a method.
      
 hook function
      The function part of a hook.
      
 ==Rationale==
 
-Hooks allow us to decouple optionally-run code from code that is run
-for everyone. It allows MediaWiki hackers, third-party developers and
-local administrators to define code that will be run at certain points
-in the mainline code, and to modify the data run by that mainline
-code. Hooks can keep mainline code simple, and make it easier to
-write extensions. Hooks are a principled alternative to local patches.
-
-Consider, for example, two options in MediaWiki. One reverses the
-order of a title before displaying the article; the other converts the
-title to all uppercase letters. Currently, in MediaWiki code, we
-would handle this as follows (note: not real code, here):
+Hooks allow us to decouple optionally-run code from code that is run for
+everyone. It allows MediaWiki hackers, third-party developers and local
+administrators to define code that will be run at certain points in the mainline
+code, and to modify the data run by that mainline code. Hooks can keep mainline
+code simple, and make it easier to write extensions. Hooks are a principled
+alternative to local patches.
+
+Consider, for example, two options in MediaWiki. One reverses the order of a
+title before displaying the article; the other converts the title to all
+uppercase letters. Currently, in MediaWiki code, we would handle this as follows
+(note: not real code, here):
 
 	function showAnArticle($article) {
 		global $wgReverseTitle, $wgCapitalizeTitle;
@@ -49,31 +48,30 @@ would handle this as follows (note: not 
 		# code to actually show the article goes here
 	}
 
-An extension writer, or a local admin, will often add custom code to
-the function -- with or without a global variable. For example,
-someone wanting email notification when an article is shown may add:
+An extension writer, or a local admin, will often add custom code to the
+function -- with or without a global variable. For example, someone wanting
+email notification when an article is shown may add:
 
     function showAnArticle($article) {
-        global $wgReverseTitle, $wgCapitalizeTitle;
+        global $wgReverseTitle, $wgCapitalizeTitle, $wgNotifyArticle;
 	
-	if ($wgReverseTitle) {
-	    wfReverseTitle($article);
-        }
+		if ($wgReverseTitle) {
+			wfReverseTitle($article);
+		}
 	
-	if ($wgCapitalizeTitle) {
-	   wfCapitalizeTitle($article);
-        }
+		if ($wgCapitalizeTitle) {
+			wfCapitalizeTitle($article);
+		}
 
-	# code to actually show the article goes here
+		# code to actually show the article goes here
 	
-	if ($wgNotifyArticle) {
-	   wfNotifyArticleShow($article));
-        }
+		if ($wgNotifyArticle) {
+			wfNotifyArticleShow($article));
+		}
     }
 
-Using a hook-running strategy, we can avoid having all this
-option-specific stuff in our mainline code. Using hooks, the function
-becomes:
+Using a hook-running strategy, we can avoid having all this option-specific
+stuff in our mainline code. Using hooks, the function becomes:
 
 	function showAnArticle($article) {
 
@@ -85,16 +83,15 @@ becomes:
 		}
 	}
 
-We've cleaned up the code here by removing clumps of weird,
-infrequently used code and moving them off somewhere else. It's much
-easier for someone working with this code to see what's _really_ going
-on, and make changes or fix bugs.
-
-In addition, we can take all the code that deals with the little-used
-title-reversing options (say) and put it in one place. Instead of
-having little title-reversing if-blocks spread all over the codebase
-in showAnArticle, deleteAnArticle, exportArticle, etc., we can
-concentrate it all in an extension file:
+We've cleaned up the code here by removing clumps of weird, infrequently used
+code and moving them off somewhere else. It's much easier for someone working
+with this code to see what's _really_ going on, and make changes or fix bugs.
+
+In addition, we can take all the code that deals with the little-used 
+title-reversing options (say) and put it in one place. Instead of having little
+title-reversing if-blocks spread all over the codebase in showAnArticle,
+deleteAnArticle, exportArticle, etc., we can concentrate it all in an extension
+file:
 
 	function reverseArticleTitle($article) {
 		# ...
@@ -104,8 +101,8 @@ concentrate it all in an extension file:
 		# ...
 	}
 
-The setup function for the extension just has to add its hook
-functions to the appropriate events:
+The setup function for the extension just has to add its hook functions to the
+appropriate events:
 
 	setupTitleReversingExtension() {
 		global $wgHooks;
@@ -115,23 +112,21 @@ functions to the appropriate events:
 		$wgHooks['ArticleExport'][] = 'reverseForExport';
 	}
 
-Having all this code related to the title-reversion option in one
-place means that it's easier to read and understand; you don't have to
-do a grep-find to see where the $wgReverseTitle variable is used, say.
-
-If the code is well enough isolated, it can even be excluded when not
-used -- making for some slight savings in memory and load-up
-performance at runtime. Admins who want to have all the reversed
-titles can add:
+Having all this code related to the title-reversion option in one place means
+that it's easier to read and understand; you don't have to do a grep-find to see
+where the $wgReverseTitle variable is used, say.
+
+If the code is well enough isolated, it can even be excluded when not used -- 
+making for some slight savings in memory and load-up performance at runtime. 
+Admins who want to have all the reversed titles can add:
 
 	require_once('extensions/ReverseTitle.php');
 
-...to their LocalSettings.php file; those of us who don't want or need
-it can just leave it out.
+...to their LocalSettings.php file; those of us who don't want or need it can
+just leave it out.
 
-The extensions don't even have to be shipped with MediaWiki; they
-could be provided by a third-party developer or written by the admin
-him/herself.
+The extensions don't even have to be shipped with MediaWiki; they could be
+provided by a third-party developer or written by the admin him/herself.
 
 ==Writing hooks==
 
@@ -140,8 +135,8 @@ A hook is a chunk of code run at some pa
   * a function with some optional accompanying data, or
   * an object with a method and some optional accompanying data.
 
-Hooks are registered by adding them to the global $wgHooks array for a
-given event. All the following are valid ways to define hooks:
+Hooks are registered by adding them to the global $wgHooks array for a given
+event. All the following are valid ways to define hooks:
 
 	$wgHooks['EventName'][] = 'someFunction'; # function, no data
 	$wgHooks['EventName'][] = array('someFunction', $someData);
@@ -152,10 +147,9 @@ given event. All the following are valid
 	$wgHooks['EventName'][] = array($object, 'someMethod', $someData);
 	$wgHooks['EventName'][] = array($object); # weird but OK
 
-When an event occurs, the function (or object method) will be called
-with the optional data provided as well as event-specific parameters.
-The above examples would result in the following code being executed
-when 'EventName' happened:
+When an event occurs, the function (or object method) will be called with the
+optional data provided as well as event-specific parameters. The above examples
+would result in the following code being executed when 'EventName' happened:
 
 	# function, no data
 	someFunction($param1, $param2)
@@ -169,31 +163,30 @@ when 'EventName' happened:
 	# object with method and data
 	$object->someMethod($someData, $param1, $param2)
       
-Note that when an object is the hook, and there's no specified method,
-the default method called is 'onEventName'. For different events this
-would be different: 'onArticleSave', 'onUserLogin', etc.
+Note that when an object is the hook, and there's no specified method, the
+default method called is 'onEventName'. For different events this would be
+different: 'onArticleSave', 'onUserLogin', etc.
 
-The extra data is useful if we want to use the same function or object
-for different purposes. For example:
+The extra data is useful if we want to use the same function or object for
+different purposes. For example:
 
 	$wgHooks['ArticleSaveComplete'][] = array('ircNotify', 'TimStarling');
 	$wgHooks['ArticleSaveComplete'][] = array('ircNotify', 'brion');
 
-This code would result in ircNotify being run twice when an article is
-saved: once for 'TimStarling', and once for 'brion'.
+This code would result in ircNotify being run twice when an article is saved:
+once for 'TimStarling', and once for 'brion'.
 
 Hooks can return three possible values:
 
   * true: the hook has operated successfully
-  * "some string": an error occurred; processing should
-    	  stop and the error should be shown to the user
-  * false: the hook has successfully done the work
-    	   necessary and the calling function should skip
+  * "some string": an error occurred; processing should stop and the error
+                   should be shown to the user
+  * false: the hook has successfully done the work necessary and the calling
+           function should skip
 	
-The last result would be for cases where the hook function replaces
-the main functionality. For example, if you wanted to authenticate
-users to a custom system (LDAP, another PHP program, whatever), you
-could do:
+The last result would be for cases where the hook function replaces the main
+functionality. For example, if you wanted to authenticate users to a custom
+system (LDAP, another PHP program, whatever), you could do:
 
 	$wgHooks['UserLogin'][] = array('ldapLogin', $ldapServer);
   
@@ -202,13 +195,13 @@ could do:
 		return false;
 	}
 
-Returning false makes less sense for events where the action is
-complete, and will normally be ignored.
+Returning false makes less sense for events where the action is complete, and
+will normally be ignored.
 
 ==Using hooks==
 
-A calling function or method uses the wfRunHooks() function to run
-the hooks related to a particular event, like so:
+A calling function or method uses the wfRunHooks() function to run the hooks
+related to a particular event, like so:
 
 	class Article { 
 		# ...
@@ -221,22 +214,25 @@ the hooks related to a particular event,
 		}
 	}
 						    
-wfRunHooks() returns true if the calling function should continue
-processing (the hooks ran OK, or there are no hooks to run), or false
-if it shouldn't (an error occurred, or one of the hooks handled the
-action already). Checking the return value matters more for "before"
-hooks than for "complete" hooks.
+wfRunHooks() returns true if the calling function should continue processing
+(the hooks ran OK, or there are no hooks to run), or false if it shouldn't (an
+error occurred, or one of the hooks handled the action already). Checking the
+return value matters more for "before" hooks than for "complete" hooks.
 
 Note that hook parameters are passed in an array; this is a necessary
-inconvenience to make it possible to pass reference values (that can
-be changed) into the hook code. Also note that earlier versions of
-wfRunHooks took a variable number of arguments; the array() calling
-protocol came about after MediaWiki 1.4rc1.
+inconvenience to make it possible to pass reference values (that can be changed)
+into the hook code. Also note that earlier versions of wfRunHooks took a
+variable number of arguments; the array() calling protocol came about after
+MediaWiki 1.4rc1.
 
 ==Events and parameters==
 
-This is a list of known events and parameters; please add to it if
-you're going to add events to the MediaWiki code.
+This is a list of known events and parameters; please add to it if you're going
+to add events to the MediaWiki code.
+
+'AbortAutoblock': Return false to cancel an autoblock.
+$autoblockip: The IP going to be autoblocked.
+$block: The block from which the autoblock is coming.
 
 'AbortLogin': Return false to cancel account login.
 $user: the User object being authenticated against
@@ -250,6 +246,7 @@ $old: old title
 $nt: new title
 $user: user who is doing the move
 $err: error message
+$reason: the reason for the move (added in 1.13)
 
 'AbortNewAccount': Return false to cancel account creation.
 $user: the User object about to be created (read-only, incomplete)
@@ -268,6 +265,36 @@ before showing the edit form ( EditPage:
 on &action=edit.
 $EditPage : the EditPage object
 
+'APIEditBeforeSave': before saving a page with api.php?action=edit,
+after processing request parameters. Return false to let the request
+fail, returning an error message or an <edit result="Failure"> tag
+if $resultArr was filled.
+$EditPage : the EditPage object
+$text : the new text of the article (has yet to be saved)
+$resultArr : data in this array will be added to the API result
+
+'APIQueryInfoTokens': use this hook to add custom tokens to prop=info.
+Every token has an action, which will be used in the intoken parameter
+and in the output (actiontoken="..."), and a callback function which
+should return the token, or false if the user isn't allowed to obtain
+it. The prototype of the callback function is func($pageid, $title)
+where $pageid is the page ID of the page the token is requested for
+and $title is the associated Title object. In the hook, just add
+your callback to the $tokenFunctions array and return true (returning
+false makes no sense)
+$tokenFunctions: array(action => callback)
+
+'APIQueryRevisionsTokens': use this hook to add custom tokens to prop=revisions.
+Every token has an action, which will be used in the rvtoken parameter
+and in the output (actiontoken="..."), and a callback function which
+should return the token, or false if the user isn't allowed to obtain
+it. The prototype of the callback function is func($pageid, $title, $rev)
+where $pageid is the page ID of the page associated to the revision the
+token is requested for, $title the associated Title object and $rev the
+associated Revision object. In the hook, just add your callback to the
+$tokenFunctions array and return true (returning false makes no sense)
+$tokenFunctions: array(action => callback)
+
 'ArticleAfterFetchContent': after fetching content of an article from the database
 $article: the article (object) being loaded from the database
 $content: the content (string) of the article
@@ -276,11 +303,14 @@ $content: the content (string) of the ar
 $article: the article (object) being deleted
 $user: the user (object) deleting the article
 $reason: the reason (string) the article is being deleted
+$error: if the deletion was prohibited, the (raw HTML) error message to display
+  (added in 1.13)
 
 'ArticleDeleteComplete': after an article is deleted
 $article: the article that was deleted
 $user: the user that deleted the article
 $reason: the reason the article was deleted
+$id: id of the article that was deleted
 
 'ArticleEditUpdateNewTalk': before updating user_newtalk when a user talk page was changed
 $article: article (object) of the user talk page
@@ -292,7 +322,7 @@ $article: article (object) being modifie
 $title: title (object) used to create the article object
 $article: article (object) that will be returned
 
-'ArticleInsertComplete': After an article is created
+'ArticleInsertComplete': After a new article is created
 $article: Article created
 $user: User creating the article
 $text: New content
@@ -332,7 +362,11 @@ $moveonly: boolean whether it was for mo
 'ArticlePurge': before executing "&action=purge" 
 $article: article (object) to purge
 
-'ArticleRevisionUndeleted' after an article revision is restored
+'ArticleRevisionVisiblitySet': called when changing visibility of one or more
+revision of an article
+&$title: title object of the article
+
+'ArticleRevisionUndeleted': after an article revision is restored
 $title: the article title
 $revision: the revision
 $oldPageID: the page ID of the revision when archived (may be null)
@@ -391,13 +425,14 @@ $create: Whether or not the restoration 
 'ArticleViewRedirect': before setting "Redirected from ..." subtitle when follwed an redirect
 $article: target article (object)
 
+'AuthPluginAutoCreate': Called when creating a local account for an user logged
+in from an external authentication method
+$user: User object created locally
+
 'AuthPluginSetup': update or replace authentication plugin object ($wgAuth)
 Gives a chance for an extension to set it programattically to a variable class.
 &$auth: the $wgAuth object, probably a stub
 
-'AutoAuthenticate': called to authenticate users on external/environmental means
-$user: writes user object to this parameter
-
 'AutopromoteCondition': check autopromote condition for user.
 $type: condition type
 $args: arguments
@@ -417,7 +452,8 @@ rendered inline in wiki pages or galleri
 &$time: image timestamp
 
 'BeforePageDisplay': Prior to outputting a page
-$out: OutputPage object
+&$out: OutputPage object
+&$skin: Skin object
 
 'BeforeParserFetchTemplateAndtitle': before a template is fetched by Parser
 &$parser: Parser object
@@ -454,6 +490,18 @@ $user: the user who did the block (not t
 $isbn: ISBN to show information for
 $output: OutputPage object in use
 
+'BrokenLink': Before the HTML is created for a broken (i.e. red) link
+&$linker: Linker instance
+$nt: the page title
+$query: the URL query string passed in
+&$u: the URL of this link
+&$style: the inline CSS style
+&$prefix: a prefix prepended to the linked text
+&$text: the text placed by the user in the wiki-link
+&$inside: any additional alphanumeric characters placed after the wiki-link,
+that are made part of the link text
+&$trail: text placed immediately after the HTML link
+
 'CategoryPageView': before viewing a categorypage in CategoryPage::view
 $catpage: CategoryPage instance
 
@@ -465,6 +513,15 @@ $catpage: CategoryPage instance
 $unpatrolled:  Whether or not we are showing unpatrolled changes.
 $watched:      Whether or not the change is watched by the user.
 
+'ContribsPager::getQueryInfo': Before the contributions query is about to run
+&$pager: Pager object for contributions
+&queryInfo: The query for the contribs Pager
+
+'ContributionsLineEnding': Called before a contributions HTML line is finished
+$page: SpecialPage object for contributions
+$ret: the HTML line
+$row: the DB row for this line
+
 'ContributionsToolLinks': Change tool links above Special:Contributions
 $id: User identifier
 $title: User page title
@@ -492,11 +549,13 @@ $editor: Edit form (see includes/EditPag
 $text: Contents of the edit box
 $section: Section being edited
 &$error: Error message to return
+$summary: Edit summary for page
 
 'EditFilterMerged': Post-section-merge edit filter
 $editor: EditPage instance (object)
 $text: content of the edit box
 $error: error message to return
+$summary: Edit summary for page
 
 'EditFormPreloadText': Allows population of the edit form when creating new pages
 &$text: Text to preload with
@@ -519,6 +578,14 @@ Alternatively, modifying $error and retu
 to be echoed at the top of the edit form as wikitext. Return true without altering
 $error to allow the edit to proceed.
 
+'EditPageBeforeConflictDiff': allows modifying the EditPage object and output
+when there's an edit conflict.  Return false to halt normal diff output; in
+this case you're responsible for computing and outputting the entire "conflict"
+part, i.e., the "difference between revisions" and "your text" headers and
+sections.
+&$editor: EditPage instance
+&$out: OutputPage instance
+
 'EditPageBeforeEditButtons':  allows modifying the edit buttons below the textarea in the edit form
 &$editpage: The current EditPage object
 &$buttons: Array of edit buttons "Save", "Preview", "Live", and "Diff"
@@ -562,12 +629,35 @@ $text: text of the mail
 &$list: List object (defaults to NULL, change it to an object instance and return
 false override the list derivative used)
 
+'FileDeleteComplete': When a file is deleted
+$file: reference to the deleted file
+$oldimage: in case of the deletion of an old image, the name of the old file
+$article: in case all revisions of the file are deleted a reference to the article 
+	  associated with the file.
+$user: user who performed the deletion
+$reason: reason
+
 'FileUpload': When a file upload occurs
 $file : Image object representing the file that was uploaded
 
+'FileUndeleteComplete': When a file is undeleted
+$title: title object to the file
+$fileVersions: array of undeleted versions. Empty if all versions were restored
+$user: user who performed the undeletion
+$reason: reason
+
+'GetAutoPromoteGroups': When determining which autopromote groups a user is entitled to be in.
+&$user: user to promote.
+&$promote: groups that will be added.
+
 'GetBlockedStatus': after loading blocking status of an user from the database
 $user: user (object) being checked
 
+'GetCacheVaryCookies': get cookies that should vary cache options
+$out: OutputPage object
+&$cookies: array of cookies name, add a value to it if you want to add a cookie
+	that have to vary cache options
+
 'GetFullURL': modify fully-qualified URLs used in redirects/export/offsite data
 $title: Title object of page
 $url: string value as output (out parameter, can modify)
@@ -602,10 +692,41 @@ $result: User permissions error to add. 
 'getUserPermissionsErrorsExpensive': Absolutely the same, but is called only
 	if expensive checks are enabled.
 
-'ImageOpenShowImageInlineBefore': Call potential extension just before showing the image on an image page
+'ImageBeforeProduceHTML': Called before producing the HTML created by a wiki
+	image insertion.  You can skip the default logic entirely by returning
+	false, or just modify a	few things using call-by-reference.
+&$this: Skin object
+&$title: Title object of the image
+&$file: File object, or false if it doesn't exist
+&$frameParams: Various parameters with special meanings; see documentation in
+	includes/Linker.php for Linker::makeImageLink2
+&$handlerParams: Various parameters with special meanings; see documentation in
+	includes/Linker.php for Linker::makeImageLink2
+&$time: Timestamp of file in 'YYYYMMDDHHIISS' string form, or false for current
+&$res: Final HTML output, used if you return false
+
+
+'ImageOpenShowImageInlineBefore': Call potential extension just before showing
+	the image on an image page
 $imagePage: ImagePage object ($this)
 $output: $wgOut
 
+'ImagePageFileHistoryLine': called when a file history line is contructed
+$file: the file
+$line: the HTML of the history line
+$css: the line CSS class
+
+'ImagePageFindFile': called when fetching the file associated with an image page
+$page: ImagePage object
+&$file: File object
+&$displayFile: displayed File object
+
+'InitializeArticleMaybeRedirect': MediaWiki check to see if title is a redirect
+$title: Title object ($wgTitle)
+$request: WebRequest
+$ignoreRedirect: boolean to skip redirect check
+$target: Title/string of redirect target
+
 'InitPreferencesForm': called at the end of PreferencesForm's constructor
 $form: the PreferencesForm
 $request: the web request to initialized from
@@ -641,6 +762,16 @@ $lang: laguage code (string)
 $specialPageAliases: associative array of magic words synonyms
 $lang: laguage code (string)
 
+'LinkerMakeExternalImage': At the end of Linker::makeExternalImage() just before the return
+&$url: the image url
+&$alt: the image's alt text
+&$img: the new image HTML (if returning false)
+
+'LinkerMakeExternalLink': At the end of Linker::makeExternalLink() just before the return
+&$url: the link url
+&$text: the link text
+&$link: the new link HTML (if returning false)
+
 'LinksUpdate': At the beginning of LinksUpdate::doUpdate() just before the actual update
 &$linksUpdate: the LinkUpdate object
 
@@ -654,6 +785,14 @@ $lang: laguage code (string)
 
 'LoadExtensionSchemaUpdates': called by maintenance/updaters.inc when upgrading database schema
 
+'LocalFile::getHistory': called before file history query performed
+$file: the file
+$tables: tables
+$fields: select fields
+$conds: conditions
+$opts: query options
+$join_conds: JOIN conditions
+
 'LoginAuthenticateAudit': a login attempt for a valid user account either succeeded or failed.
                           No return data is accepted; this hook is for auditing only.
 $user: the User object being authenticated against
@@ -715,14 +854,35 @@ $article: $wgArticle
 $title:   $wgTitle
 $user:    $wgUser
 $request: $wgRequest
+$this:    The $mediawiki object
 
 'MessagesPreLoad': When loading a message from the database
 $title: title of the message (string)
 $message: value (string), change it to the message you want to define
 
 'MonoBookTemplateToolboxEnd': Called by Monobook skin after toolbox links have been rendered (useful for adding more)
+Note: this is only run for the Monobook skin.  To add items to the toolbox
+for all 'SkinTemplate'-type skins, use the SkinTemplateToolboxEnd hook
+instead.
 $tools: array of tools
 
+'NewRevisionFromEditComplete': called when a revision was inserted due to an edit
+$article: the article edited
+$rev: the new revision
+$baseID: the revision ID this was based off, if any
+
+'NormalizeMessageKey': Called before the software gets the text of a message
+  (stuff in the MediaWiki: namespace), useful for changing WHAT message gets displayed
+&$key: the message being looked up. Change this to something else to change what message gets displayed (string)
+&$useDB: whether or not to look up the message in the database (bool)
+&$langCode: the language code to get the message for (string) - or -
+  whether to use the content language (true) or site language (false) (bool)
+&$transform: whether or not to expand variables and templates in the message (bool)
+
+'OpenSearchUrls': Called when constructing the OpenSearch description XML.
+Hooks can alter or append to the array of URLs for search & suggestion formats.
+&$urls: array of associative arrays with Url element attributes
+
 'OutputPageBeforeHTML': a page has been processed by the parser and
 the resulting HTML is about to be displayed.  
 $parserOutput: the parserOutput (object) that corresponds to the page 
@@ -732,6 +892,12 @@ $text: the text that will be displayed, 
 $out: OutputPage instance (object)
 $parserOutput: parserOutput instance being added in $out
 
+'OutputPageMakeCategoryLinks': links are about to be generated for the page's categories.
+  Implementations should return false if they generate the category links, so the default link generation is skipped.
+$out: OutputPage instance (object)
+$categories: associative array, keys are category names, values are category types ("normal" or "hidden")
+$links: array, intended to hold the result. Must be an associative array with category types as keys and arrays of HTML links as values.
+
 'PageHistoryBeforeList': When a history page list is about to be constructed.
 $article: the article that the history is loading for
 
@@ -739,6 +905,10 @@ $article: the article that the history i
 $row: the revision row for this line
 $s: the string representing this parsed line
 
+'PageHistoryPager::getQueryInfo': when a history pager query parameter set is constructed
+$pager: the pager
+$queryInfo: the query parameters
+
 'PageRenderingHash': alter the parser cache option hash key
   A parser extension which depends on user options should install
   this hook and append its values to the key.
@@ -767,8 +937,8 @@ $text: actual text
 'ParserClearState': called at the end of Parser::clearState()
 $parser: Parser object being cleared
 
-'ParserFirstCallInit': called when the ther parser initialises for the first time
-$parser: Parser object being cleared
+'ParserFirstCallInit': called when the parser initialises for the first time
+&$parser: Parser object being cleared
 
 'ParserGetVariableValueSwitch': called when the parser need the value of a custom magic word
 $parser: Parser object
@@ -788,6 +958,11 @@ $varCache: varaiable cache (array)
 $parser: Parser object
 $limitReport: text that will be included (without comment tags)
 
+'ParserMakeImageParams': Called before the parser make an image link, use this to modify the parameters of the image.
+$title: title object representing the file
+$file: file object that will be used to create the image
+&$params: 2-D array of parameters
+
 'ParserTestParser': called when creating a new instance of Parser in maintenance/parserTests.inc
 $parser: Parser object created
 
@@ -815,7 +990,7 @@ $form : PreferencesForm object
 
 'PrefixSearchBackend': Override the title prefix search used for OpenSearch and
 AJAX search suggestions. Put results into &$results outparam and return false.
-$ns : int namespace key to search in
+$ns : array of int namespace keys to search in
 $search : search term (not guaranteed to be conveniently normalized)
 $limit : maximum number of results to return
 &$results : out param: array of page names (strings)
@@ -845,11 +1020,21 @@ $out: output page to render to, probably
 $form: the PreferencesForm
 $user: the User object to load preferences from
 
+'RevisionInsertComplete': called after a revision is inserted into the DB
+&$revision: the Revision
+$data: the data stored in old_text.  The meaning depends on $flags: if external
+  is set, it's the URL of the revision text in external storage; otherwise,
+  it's the revision text itself.  In either case, if gzip is set, the revision
+  text is gzipped.
+$flags: a comma-delimited list of strings representing the options used.  May
+  include: utf8 (this will always be set for new revisions); gzip; external.
+
 'SavePreferences': called at the end of PreferencesForm::savePreferences; 
                    returning false prevents the preferences from being saved.
 $form: the PreferencesForm
 $user: the User object to save preferences to
-$message: change this to set an error message (ignored if the hook does notreturn fals)
+$message: change this to set an error message (ignored if the hook does not return false)
+$old: old preferences of the user
 
 'SearchUpdate': Prior to search update completion
 $id : Page id
@@ -926,9 +1111,32 @@ $checkEdit: Whether or not the action=ed
 $sktemplate: SkinTemplate object
 $content_actions: array of tabs
 
+'SkinTemplateToolboxEnd': Called by SkinTemplate skins after toolbox links have been rendered (useful for adding more)
+$tools: array of tools
+
 'SpecialContributionsBeforeMainOutput': Before the form on Special:Contributions
 $id: User identifier
 
+'SpecialListusersDefaultQuery': called right before the end of UsersPager::getDefaultQuery()
+$pager: The UsersPager instance
+$query: The query array to be returned
+
+'SpecialListusersFormatRow': called right before the end of UsersPager::formatRow()
+$item: HTML to be returned. Will be wrapped in <li></li> after the hook finishes
+$row: Database row object
+
+'SpecialListusersHeader': called before closing the <fieldset> in UsersPager::getPageHeader()
+$pager: The UsersPager instance
+$out: The header HTML
+
+'SpecialListusersHeaderForm': called before adding the submit button in UsersPager::getPageHeader()
+$pager: The UsersPager instance
+$out: The header HTML
+
+'SpecialListusersQueryInfo': called right before the end of UsersPager::getQueryInfo()
+$pager: The UsersPager instance
+$query: The query array to be returned
+
 'SpecialMovepageAfterMove': called after moving a page
 $movePage: MovePageForm object
 $oldTitle: old title (object)
@@ -955,9 +1163,27 @@ $funct: function called to execute the s
 'SpecialPage_initList': called when setting up SpecialPage::$mList, use this hook to remove a core special page
 $list: list (array) of core special pages
 
+'SpecialRecentChangesPanel': called when building form options in SpecialRecentChanges
+&$extraOpts: array of added items, to which can be added
+$opts: FormOptions for this request
+
+'SpecialRecentChangesQuery': called when building sql query for SpecialRecentChanges
+&$conds: array of where conditionals for query
+&$tables: array of tables to be queried
+&$join_conds: join conditions for the tables
+$opts: FormOptions for this request
+
 'SpecialSearchNogomatch': called when user clicked the "Go" button but the target doesn't exist
 $title: title object generated from the text entred by the user
 
+'SpecialSearchResults': called before search result display when there are matches
+$term: string of search term
+&$titleMatches: empty or SearchResultSet object
+&$textMatches: empty or SearchResultSet object
+
+'SpecialSearchNoResults': called before search result display when there are no matches
+$term: string of search term
+
 'SpecialVersionExtensionTypes': called when generating the extensions credits, use this to change the tables headers
 $extTypes: associative array of extensions types
 
@@ -1006,7 +1232,11 @@ string $tempName: filesystem path to the
 string &$error: output: HTML error to show if upload canceled by returning false
 
 'UploadComplete': Upon completion of a file upload
-$image: Image object representing the file that was uploaded
+$uploadForm: Upload form object. File can be accessed by $uploadForm->mLocalFile.
+
+'UserArrayFromResult': called when creating an UserArray object from a database result
+&$userArray: set this to an object to override the default object returned
+$res: database result used to create the object
 
 'userCan': To interrupt/advise the "user can do X to Y article" check.
 	If you want to display an error message, try getUserPermissionsErrors.
@@ -1031,8 +1261,35 @@ $template: SimpleTemplate instance for t
 $user: User to get groups for
 &$groups: Current effective groups
 
+'UserGetAllRights': after calculating a list of all available rights
+&$rights: Array of rights, which may be added to.
+
+'UserGetEmail': called when getting an user email address
+$user: User object
+&$email: email, change this to override local email
+
+'UserGetEmailAuthenticationTimestamp': called when getting the timestamp of email authentification
+$user: User object
+&$timestamp: timestamp, change this to override local email authentification timestamp
+
+'UserGetImplicitGroups': Called in User::getImplicitGroups()
+&$groups: List of implicit (automatically-assigned) groups
+
+'UserGetRights': Called in User::getRights()
+$user: User to get rights for
+&$rights: Current rights
+
+'UserLoadDefaults': called when loading a default user
+$user: user object
+$name: user name
+
+'UserLoadFromSession': called to authenticate users on external/environmental means
+$user: user object being loaded
+&$result: set this to a boolean value to abort the normal authentification process
+
 'UserLoginComplete': after a user has logged in
 $user: the user object that was created on login
+$inject_html: Any HTML to inject after the "logged in" message.
 		    
 'UserLoginForm': change to manipulate the login form
 $template: SimpleTemplate instance for the form
@@ -1042,23 +1299,34 @@ $user: the user object that is about to 
        
 'UserLogoutComplete': after a user has logged out
 $user: the user object _after_ logout (won't have name, ID, etc.)
+$inject_html: Any HTML to inject after the "logged out" message.
+$oldName: name of the user before logout (string)
 
 'UserRights': After a user's group memberships are changed
 $user  : User object that was changed
 $add   : Array of strings corresponding to groups added
 $remove: Array of strings corresponding to groups removed
-		      
-'UserGetImplicitGroups': Called in User::getImplicitGroups()
-&$groups: List of implicit (automatically-assigned) groups
-
-'UserGetRights': Called in User::getRights()
-$user: User to get rights for
-&$rights: Current rights
 
 'UserRetrieveNewTalks': called when retrieving "You have new messages!" message(s)
 $user: user retrieving new talks messages
 $talks: array of new talks page(s)
 
+'UserSaveSettings': called when saving user settings
+$user: User object
+
+'UserSetCookies': called when setting user cookies
+$user: User object
+&$session: session array, will be added to $_SESSION
+&$cookies: cookies array mapping cookie name to its value
+
+'UserSetEmail': called when changing user email address
+$user: User object
+&$email: new email, change this to override new email address
+
+'UserSetEmailAuthenticationTimestamp': called when setting the timestamp of email authentification
+$user: User object
+&$timestamp: new timestamp, change this to override local email authentification timestamp
+
 'UserToggles': called when initialising User::$mToggles, use this to add new toggles
 $toggles: array of toggles to add
 
diff -pruN 1:1.12.0-2/docs/html/README 1:1.13.0-1/docs/html/README
--- 1:1.12.0-2/docs/html/README	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/html/README	2008-05-23 21:06:25.000000000 +0100
@@ -1,4 +1,4 @@
-This directory is for the auto-generated phpdoc documentation.
+This directory is for the auto-generated doxygen documentation.
 Run 'php mwdocgen.php' in the maintenance subdirectory to build the docs.
 
-Get phpDocumentor from http://phpdoc.org/
+Get Doxygen from http://www.doxygen.org/
diff -pruN 1:1.12.0-2/docs/language.txt 1:1.13.0-1/docs/language.txt
--- 1:1.12.0-2/docs/language.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/language.txt	2008-07-17 19:12:20.000000000 +0100
@@ -1,24 +1,21 @@
 language.txt
 
-The Language object handles all readable text produced by the
-software. The most used function is getMessage(), usually
-called with the wrapper function wfMsg() which calls that method
-on the global language object. It just returns a piece of text
-given a text key. It is recommended that you use each key only
-once--bits of text in different contexts that happen to be
-identical in English may not be in other languages, so it's
-better to add new keys than to reuse them a lot. Likewise,
-if there is text that gets combined with things like names and
-titles, it is better to put markers like "$1" inside a piece
-of text and use str_replace() than to compose such messages in
-code, because their order may change in other languages too.
+The Language object handles all readable text produced by the software. The most
+used function is getMessage(), usually called with the wrapper function wfMsg()
+which calls that method on the global language object. It just returns a piece
+of text given a text key. It is recommended that you use each key only
+once--bits of text in different contexts that happen to be identical in English
+may not be in other languages, so it's better to add new keys than to reuse them
+a lot. Likewise, if there is text that gets combined with things like names and
+titles, it is better to put markers like "$1" inside a piece of text and use
+str_replace() than to compose such messages in code, because their order may
+change in other languages too.
 
-While the system is running, there will be one global language
-object, which will be a subtype of Language. The methods in
-these objects will return the native text requested if available,
-otherwise they fall back to sending English text (which is why
-the LanguageEn object has no code at all--it just inherits the
-English defaults of the Language base class).
+While the system is running, there will be one global language object, which
+will be a subtype of Language. The methods in these objects will return the
+native text requested if available, otherwise they fall back to sending English
+text (which is why the LanguageEn object has no code at all--it just inherits
+the English defaults of the Language base class).
 
-The names of the namespaces are also contained in the language
-object, though the numbers are fixed.
+The names of the namespaces are also contained in the language object, though
+the numbers are fixed.
diff -pruN 1:1.12.0-2/docs/linkcache.txt 1:1.13.0-1/docs/linkcache.txt
--- 1:1.12.0-2/docs/linkcache.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/linkcache.txt	2008-04-13 15:31:15.000000000 +0100
@@ -1,18 +1,24 @@
 linkcache.txt
 
-The LinkCache class maintains a list of article titles and
-the information about whether or not the article exists in
-the database. This is used to mark up links when displaying
-a page. If the same link appears more than once on any page,
-then it only has to be looked up once. In most cases, link
-lookups are done in batches with the LinkBatch class, or the
-equivalent in Parser::replaceLinkHolders(), so the link
-cache is mostly useful for short snippets of parsed text
-(such as the site notice), and for links in the navigation
-areas of the skin. 
-
-The link cache was formerly used to track links used in a
-document for the purposes of updating the link tables. This
-application is now deprecated.
+The LinkCache class maintains a list of article titles and the information about
+whether or not the article exists in the database. This is used to mark up links
+when displaying a page. If the same link appears more than once on any page,
+then it only has to be looked up once. In most cases, link lookups are done in
+batches with the LinkBatch class, or the equivalent in Parser::replaceLinkHolders(),
+so the link cache is mostly useful for short snippets of parsed text (such as
+the site notice), and for links in the navigation areas of the skin.
 
+The link cache was formerly used to track links used in a document for the
+purposes of updating the link tables. This application is now deprecated.
 
+To create a batch, you can use the following code:
+
+$pages = array( 'Main Page', 'Project:Help', /* ... */ );
+$titles = array();
+
+foreach( $pages as $page ){
+	$titles[] = Title::newFromText( $page );
+}
+
+$batch = new LinkBatch( $titles );
+$batch->execute();
\ No newline at end of file
diff -pruN 1:1.12.0-2/docs/magicword.txt 1:1.13.0-1/docs/magicword.txt
--- 1:1.12.0-2/docs/magicword.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/magicword.txt	2008-03-12 10:15:02.000000000 +0000
@@ -1,44 +1,88 @@
 magicword.txt
 
-Magic Words are some phrases used in the wikitext. They are defined in several arrays:
-* $magicWords (includes/MagicWord.php) includes their internal names ('MAG_XXX').
-* $wgVariableIDs (includes/MagicWord.php) includes their IDs (MAG_XXX, which are constants),
-  after their internal names are used for "define()".
-* Localized arrays (languages/LanguageXX.php) include their different names to be used by the users.
-
-The localized arrays keys are the internal IDs, and the values are an array, whose include their
-case-sensitivity and their alias forms. The first form defined is used by the program, for example,
-when moving a page and its old name should include #REDIRECT.
-
-Adding magic words should be done using several hooks:
-* "MagicWordMagicWords" should be used to add the internal name ('MAG_XXX') to $magicWords.
-* "MagicWordwgVariableIDs" should be used to add the ID (MAG_XXX constant) to $wgVariableIDs.
-* "LanguageGetMagic" should be used to add the different names of the magic word. Use both
-  the localized name and the English name. Get the language code by the parameter $langCode;
+Magic Words are some phrases used in the wikitext. They are used for two things:
+* Variables (like {{PAGENAME}}, {{SERVER}}, ...): part of wikitext, that looks
+  like templates but that don't accept any parameter.
+* Parser functions (like {{fullurl:...}}, {{#special:...}}): behaves like 
+  functions and accepts parameters.
+
+The localized arrays keys are the internal name, and the values are an array, 
+whose include their case-sensitivity and their alias forms. The first form 
+defined is used by the program, for example, when moving a page and its old name
+should include #REDIRECT.
+
+They can be added in several arrays:
+* LanguageGetMagic hook, by adding a new key in $magicWords array. You can get
+  language code in the $lang parameter. Use both the localized name and the 
+  English name.
+* By adding a file to $wgExtensionMessagesFiles and defining there $magicWords.
+  This array is associative with the language code in the first dimension key
+  and then a "normal" array of magic words.
+* Localized arrays (languages/messages/LanguageXX.php) include their different 
+  names to be used by the users.
+
+To add a new variable, you should use the "MagicWordwgVariableIDs" hook to add
+the internal name to the $magicWords array. You'll need to define the value of
+the variable with the "ParserGetVariableValueSwitch" hook.
 
-For example:
+For example to add a new variable:
 
-$wgHooks['MagicWordMagicWords'][] = 'wfAddCustomMagicWord';
 $wgHooks['MagicWordwgVariableIDs'][] = 'wfAddCustomMagicWordID';
 $wgHooks['LanguageGetMagic'][] = 'wfAddCustomMagicWordLang';
+$wgHooks['ParserGetVariableValueSwitch'][] = 'wfGetCustomMagicWordValue';
 
-function wfAddCustomMagicWord( &$magicWords ) {
-	$magicWords[] = 'MAG_CUSTOM';
+function wfAddCustomMagicWordID( &$magicWords ) {
+	$magicWords[] = 'mag_custom';
 	return true;
 }
 
-function wfAddCustomMagicWordID( &$magicWords ) {
-	$magicWords[] = MAG_CUSTOM;
+function wfAddCustomMagicWordLang( &$magicWords, $langCode ) {
+	switch ( $langCode ) {
+		case 'es':
+			$magicWords['mag_custom'] = array( 1, "ADUANERO", "CUSTOM" );
+			break;
+		default:
+			$magicWords['mag_custom'] = array( 1, "CUSTOM" );
+	}
+	return true;
+}
+
+function wfGetCustomMagicWordValue( &$parser, &$varCache, &$index, &$ret ){
+	if( $index == 'mag_custom' ){
+		$ret = $varCache['mag_custom'] = "Custom value";
+	}
 	return true;
 }
 
+And to add a new parser function:
+
+$wgHooks['LanguageGetMagic'][] = 'wfAddCustomMagicWordLang';
+$wgHooks['ParserFirstCallInit'][] = 'wfRegisterCustomMagicWord';
+
 function wfAddCustomMagicWordLang( &$magicWords, $langCode ) {
 	switch ( $langCode ) {
 		case 'es':
-			$magicWords[MAG_CUSTOM] = array( 0, "#aduanero", "#custom" );
+			$magicWords['mag_custom'] = array( 0, "aduanero", "custom" );
 			break;
 		default:
-			$magicWords[MAG_CUSTOM] = array( 0, "#custom" );
+			$magicWords['mag_custom'] = array( 0, "custom" );
 	}
 	return true;
 }
+
+function wfRegisterCustomMagicWord( &$parser ){
+	$parser->setFunctionHook( 'mag_custom', 'wfGetCustomMagicWordValue' );
+	return true;
+}
+
+function wfGetCustomMagicWordValue( &$parser, $var1, $var2 ){
+	return "custom: var1 is $var1, var2 is $var2";
+}
+
+Note: the 'ParserFirstCallInit' hook is only aviable since 1.12. To work with
+an older version, you'll need to use an extension function.
+
+Online documentation (contains more informations):
+Magic words: http://www.mediawiki.org/wiki/Manual:Magic_words
+Variables: http://www.mediawiki.org/wiki/Manual:Variable
+Parser functions: http://www.mediawiki.org/wiki/Manual:Parser_functions
\ No newline at end of file
diff -pruN 1:1.12.0-2/docs/memcached.txt 1:1.13.0-1/docs/memcached.txt
--- 1:1.12.0-2/docs/memcached.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/memcached.txt	2008-07-09 06:50:05.000000000 +0100
@@ -1,15 +1,19 @@
-memcached support for MediaWiki:
-
-From ca August 2003, MediaWiki has optional support for memcached, a
-"high-performance, distributed memory object caching system".
-For general information on it, see: http://www.danga.com/memcached/
+MediaWiki has optional support for memcached, a "high-performance, 
+distributed memory object caching system". For general information 
+on it, see: http://www.danga.com/memcached/
 
 Memcached is likely more trouble than a small site will need, but
 for a larger site with heavy load, like Wikipedia, it should help
 lighten the load on the database servers by caching data and objects
 in memory.
 
-== Requirements ==
+== Installation ==
+
+Packages are available for Fedora, Debian, Ubuntu and probably other
+Linux distributions. If you there's no package available for your 
+distribution, you can compile it from source.
+
+== Compilation ==
 
 * PHP must be compiled with --enable-sockets
 
@@ -35,18 +39,21 @@ server is appropriately firewalled, and 
 memcached servers are not publicly accessible. Otherwise, anyone on
 the internet can put data into and read data from your cache.
 
-An attacker familiar with MediaWiki internals could use this to give
-themselves developer access and delete all data from the wiki's
-database, as well as getting all users' password hashes and e-mail
-addresses.
+An attacker familiar with MediaWiki internals could use this to steal
+passwords and email addresses, or to make themselves a sysop and 
+install malicious javascript on the site. There may be other types 
+of vulnerability, no audit has been done -- so be safe and keep it 
+behind a firewall.
 ********************* W A R N I N G ! ! ! ! ! ***********************
 
 == Setup ==
 
-If you want to start small, just run one memcached on your web
-server:
+If you installed memcached using a distro, the daemon should be started
+automatically using /etc/init.d/memcached.
 
-  memcached -d -l 127.0.0.1 -p 11000 -m 64
+To start the daemon manually, use something like:
+
+  memcached -d -l 127.0.0.1 -p 11211 -m 64
 
 (to run in daemon mode, accessible only via loopback interface,
 on port 11000, using up to 64MB of memory)
@@ -54,7 +61,7 @@ on port 11000, using up to 64MB of memor
 In your LocalSettings.php file, set:
 
 	$wgMainCacheType = CACHE_MEMCACHED;
-	$wgMemCachedServers = array( "127.0.0.1:11000" );
+	$wgMemCachedServers = array( "127.0.0.1:11211" );
 
 The wiki should then use memcached to cache various data. To use
 multiple servers (physically separate boxes or multiple caches
@@ -70,61 +77,175 @@ usage evenly), make its entry a subarray
 
 == PHP client for memcached ==
 
-As of this writing, MediaWiki includes version 1.0.10 of the PHP
-memcached client by Ryan Gilfether <hotrodder@rocketmail.com>.
-You'll find some documentation for it in the 'php-memcached'
-subdirectory under the present one.
-
-We intend to track updates, but if you want to check for the lastest
-released version, see http://www.danga.com/memcached/apis.bml
+MediaWiki uses a fork of Ryan T. Dean's pure-PHP memcached client.
+The newer PECL module is not yet supported.
 
-If you don't set $wgUseMemCached, we still create a MemCacheClient,
+MediaWiki uses three object for object caching:
+* $wgMemc, controlled by $wgMainCacheType
+* $parserMemc, controlled by $wgParserCacheType
+* $messageMemc, controlled by $wgMessageCacheType
+If you set CACHE_NONE to one of the three control variable, (default
+value for $wgMainCacheType), MediaWiki still create a MemCacheClient,
 but requests to it are no-ops and we always fall through to the
 database. If the cache daemon can't be contacted, it should also
 disable itself fairly smoothly.
 
+By default, $wgMemc is used but when it is $parserMemc or $messageMemc
+this is mentionned below.
+
 == Keys used ==
 
-User:
-	key: $wgDBname:user:id:$sId
-	ex: wikidb:user:id:51
-	stores: instance of class User
-	set in: User::loadFromSession()
-	cleared by: User::saveSettings(), UserTalkUpdate::doUpdate()
-	
+(incomplete, out of date)
+
+Ajax Search:
+	key: $wgDBname:ajaxsearch:md5( $search )
+	ex: wikidb:ajaxsearch:9565814d5d564fa898dd6111b94fae0b
+	stores: array with the result of research of a given text
+	cleared by: nothing
+	expiry: 30 minutes
+
+Date Formatter:
+	key: $wgDBname:dateformatter
+	ex: wikidb:dateformatter
+	stores: a single instance of the DateFormatter class
+	cleared by: nothing
+	expiry: one hour
+
+Difference Engine:
+	key: $wgDBname:diff:version:{MW_DIFF_VERSION}:oldid:$old:newid:$new
+	ex: wikidb:diff:version:1.11a:oldid:1:newid:2
+	stores: body of a difference
+	cleared by: nothing
+	expiry: one week
+
+Interwiki:
+	key: $wgDBname:interwiki:$prefix
+	ex: wikidb:interwiki:w
+	stores: object from the interwiki table of the database
+	expiry: $wgInterwikiExpiry
+	cleared by: nothing
+
+Lag time of the databases:
+	key: $wgDBname:lag_times
+	ex: wikidb:lag_times
+	stores: array mapping the database id to its lag time
+	expriy: 5 secondes
+	cleared by: nothing
+
+Localisation:
+	key: $wgDBname:localisation:$lang
+	ex: wikidb:localisation:de
+	stores: array of localisation settings
+	set in: Language::loadLocalisation()
+	expiry: none
+	cleared by: Language::loadLocalisation()
+
+Message Cache:
+	stored in: $messageMemc
+	key: $wgDBname:messages, $wgDBname:messages-hash, $wgDBname:messages-status
+	ex: wikidb:messages, wikidb:messages-hash, wikidb:messages-status
+	stores: an array where the keys are DB keys and the values are messages
+	set in: wfMsg(), Article::editUpdates() both call wfLoadAllMessages()
+	expriy: $wgMsgCacheExpiry
+	cleared by: nothing
+
 Newtalk:
 	key: $wgDBname:newtalk:ip:$ip
 	ex: wikidb:newtalk:ip:123.45.67.89
 	stores: integer, 0 or 1
 	set in: User::loadFromDatabase()
 	cleared by: User::saveSettings() # ?
-	expiry set to 30 minutes
+	expiry: 30 minutes
 
-LinkCache:
-	key: $wgDBname:lc:title:$title
-	ex: wikidb:lc:title:Wikipedia:Welcome,_Newcomers!
-	stores: cur_id of page, or 0 if page does not exist
-	set in: LinkCache::addLink()
-	cleared by: LinkCache::clearBadLink()
-		should be cleared on page deletion and rename
-MediaWiki namespace:
-	key: $wgDBname:messages
-	ex: wikidb:messages
-	stores: an array where the keys are DB keys and the values are messages
-	set in: wfMsg(), Article::editUpdates() both call wfLoadAllMessages()
+Parser Cache:
+	stored in: $parserMemc
+	controlled by: $wgEnableParserCache
+	key: $wgDBname:pcache:idhash:$pageid-$renderkey!$hash$edit
+		$pageid: id of the page
+		$renderkey: 1 if action=render, 0 otherwise
+		$hash: hash of user options, see User::getPageRenderingHash()
+		$edit: '!edit=0' if the user can't edit the page, '' otherwise
+	ex: wikidb:pcache:idhash:1-0!1!0!!en!2
+	stores: ParserOutput object
+	modified by: Article::editUpdates()
+	expriy: $wgParserCacheExpireTime or one hour if it contains specific magic
+		words
+
+Ping limiter:
+	controlled by: $wgRateLimits
+	key: $wgDBname:limiter:action:$action:ip:$ip,
+		$wgDBname:limiter:action:$action:user:$id,
+		mediawiki:limiter:action:$action:ip:$ip and
+		mediawiki:limiter:action:$action:subnet:$sub
+	ex: wikidb:limiter:action:edit:ip:123.45.67.89,
+		wikidb:limiter:action:edit:user:1012
+		mediawiki:limiter:action:edit:ip:123.45.67.89 and
+		mediawiki:limiter:action:$action:subnet:123.45.67
+	stores: number of action made by user/ip/subnet
+	cleared by: nothing
+	expiry: expiry set for the action and group in $wgRateLimits
+
+
+Proxy Check: (deprecated)
+	key: $wgDBname:proxy:ip:$ip
+	ex: wikidb:proxy:ip:123.45.67.89
+	stores: 1 if the ip is a proxy
+	cleared by: nothing
+	expiry: $wgProxyMemcExpiry
+
+Revision text:
+	key: $wgDBname:revisiontext:textid:$id
+	ex: wikidb:revisiontext:textid:1012
+	stores: text of a revision
+	cleared by: nothing
+	expriry: $wgRevisionCacheExpiry
+
+Sessions:
+	controlled by: $wgSessionsInMemcached
+	key: $wgBDname:session:$id
+	ex: wikidb:session:38d7c5b8d3bfc51egf40c69bc40f8be3
+	stores: $SESSION, useful when using a multi-sever wiki
+	expriy: one hour
+	cleared by: session_destroy()
+
+Sidebar:
+	stored in: $parserMemc
+	controlled by: $wgEnableSidebarCache
+	key: $wgDBname:sidebar
+	ex: wikidb:sidebar
+	stores: the html output of the sidebar
+	expriy: $wgSidebarCacheExpiry
+	cleared by: MessageCache::replace()
+
+Special:Allpages:
+	key: $wgDBname:allpages:ns:$ns
+	ex: wikidb:allpages:ns:0
+	stores: array of pages in a namespace
+	expiry: one hour
 	cleared by: nothing
 
-Watchlist:
-	key: $wgDBname:watchlist:id:$userID
-	ex: wikidb:watchlist:id:4635
-	stores: HTML string
-	cleared by: nothing, expiry time $wgWLCacheTimeout (1 hour)
-	note: emergency optimisation only
-
-IP blocks:
-	key: $wgDBname:ipblocks
-	ex: wikidb:ipblocks
-	stores: array of arrays, for the BlockCache class
-	cleared by: BlockCache:clear()
+Special:Recentchanges (feed):
+	stored in: $messageMemc
+	key: $wgDBname:rcfeed:$format:limit:$imit:minor:$hideminor and
+		rcfeed:$format:timestamp
+	ex: wikidb:rcfeed:rss:limit:50:minor:0 and rcfeed:rss:timestamp
+	stores: xml output of feed
+	expiry: one day
+	clear by: calling Special:Recentchanges?action=purge
+
+Statistics:
+	controlled by: $wgStatsMethod
+	key: $wgDBname:stats:$key
+	ex: wikibd:stats:request_with_session
+	stores: counter for statistics (see maintenance/stats.php script)
+	expiry: none (?)
+	cleared by: maintenance/clear_stats.php script
+
+User:
+	key: $wgDBname:user:id:$sId
+	ex: wikidb:user:id:51
+	stores: instance of class User
+	set in: User::saveToCache()
+	cleared by: User::saveSettings(), User::clearSharedCache()
 	
-... more to come ...
\ No newline at end of file
+... more to come ...
diff -pruN 1:1.12.0-2/docs/README 1:1.13.0-1/docs/README
--- 1:1.12.0-2/docs/README	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/README	2008-07-23 02:43:57.000000000 +0100
@@ -1,17 +1,16 @@
-[May 31st 2007]
+[July 22nd 2008]
 
-The 'docs' directory contain various text files that should help you
-understand the most importants classes in MediaWiki.
+The 'docs' directory contain various text files that should help you understand
+the most important parts of the code of MediaWiki. More in-depth documentation
+can be found at http://www.mediawiki.org/wiki/Manual:Code.
 
 API documentation is automatically generated and updated daily at:
   http://svn.wikimedia.org/doc/
 
-You can get a fresh version using 'make doc' or mwdocgen.php
-in the ../maintenance/ directory.
+You can get a fresh version using 'make doc' or mwdocgen.php in the
+../maintenance/ directory.
 
 
-
-For end user / administrators, most of the documentation
-is located online at:
-  http://www.mediawiki.org/wiki/Project:Help
-
+For end user / administrators, most of the documentation is located online at:
+  http://www.mediawiki.org/wiki/Help:Contents
+  http://www.mediawiki.org/wiki/Manual:Contents
diff -pruN 1:1.12.0-2/docs/scripts.txt 1:1.13.0-1/docs/scripts.txt
--- 1:1.12.0-2/docs/scripts.txt	1970-01-01 01:00:00.000000000 +0100
+++ 1:1.13.0-1/docs/scripts.txt	2008-07-20 20:15:47.000000000 +0100
@@ -0,0 +1,63 @@
+scripts.txt
+
+MediaWiki primary scripts are in the root directory of the software. Users
+should only use these scripts to access the wiki. There are also some .php that
+aren't primary scripts but helper files and won't work if they are accessed
+directly by the web.
+
+Primary scripts:
+
+  index.php
+    Main access point. It handles the most of requests.
+    See http://www.mediawiki.org/wiki/Manual:Index.php
+
+  api.php
+    Script to provide an API for bots to fetch content and informations about
+    the site and also modify it. See http://www.mediawiki.org/wiki/API
+    for more informations.
+
+  img_auth.php
+    Script that only serve images to logged in users. To configure the wiki
+    to use that script, see http://www.mediawiki.org/wiki/Manual:Image_Authorisation.
+
+  opensearch_desc.php
+    Returns a OpenSearch description document (see http://www.opensearch.org/)
+    that points to the search engines of the wiki.
+
+  profileinfo.php
+    Allow users to see the profiling information that are stored in the
+    database.
+
+    To save the profiling information in the database (required to use this
+    script), you have to modify StartProfiler.php to use the Profiler class and
+    not the stub profiler which is enabled by default.
+    You will also need to set $wgProfileToDatabase to true in LocalSettings.php
+    to force the profiler to save the informations in the database and apply the
+    maintenance/archives/patch-profiling.sql patch to the database.
+
+    To enable the profileinfo.php itself, you'll need to create the
+    AdminSettings.php file (see AdminSettings.sample for more information) and
+    set $wgEnableProfileInfo to true in that file. See also
+    http://www.mediawiki.org/wiki/How_to_debug#Profiling.
+
+  redirect.php
+    Script that only redirect to the article passed in the wpDropdown parameter
+    of the request. Used by the nostalgia skin to access special pages with the
+    dropdown box at the top of the page.
+
+  thumb.php
+    Script used to resize images if it is configured to be done when the web
+    browser requests the image and not when generating the page. This script can
+    be used as a 404 handler to generate image thumbs when they don't exist.
+
+  trackback.php
+    Allow to add a new trackback to the database. This script returns XML
+    and require a POST request to work, thus it should only be accessed by some
+    specific programs and won't work with normal web browsers.
+
+There is also a file with a .php5 extension for each script. They can be used if
+the web server needs a .php5 to run the file with the PHP 5 engine and runs .php
+scripts with PHP 4. To use these files, you have to modify $wgScriptExtension to
+'.php5' is LocalSettings.php but it is already done by the config script if you
+used the config/index.php5 script.
+
diff -pruN 1:1.12.0-2/docs/skin.txt 1:1.13.0-1/docs/skin.txt
--- 1:1.12.0-2/docs/skin.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/skin.txt	2008-07-09 06:50:05.000000000 +0100
@@ -1,48 +1,84 @@
-
 skin.txt
 
-This document describes the overall architecture of MediaWiki's HTML rendering
-code as well as some history about the skin system. It is placed here rather
-than in comments in the code itself to help reduce the code size.
+MediaWiki's default skin is called Monobook, after the black-and-white photo of
+a book, in the page background. This skin has been the default since MediaWiki 
+1.3 (2004). It is used on Wikipedia, and is popular on other sites.
+
+There are three legacy skins which were introduced before MediaWiki 1.3:
+
+* Standard (a.k.a. Classic): The old default skin written by Lee Crocker 
+during the phase 3 rewrite, in 2002.
+
+* Nostalgia: A skin which looks like Wikipedia did in its first year (2001). 
+This skin is now used for the old Wikipedia snapshot at
+http://nostalgia.wikipedia.org/
+
+* Cologne Blue: A nicer-looking alternative to Standard.
+
+
+And there are four Monobook-derived skins which have been introduced since 1.3:
+
+* MySkin: Monobook without the CSS. The idea is that you customise it using user
+or site CSS (see below)
+
+* Chick: A lightweight Monobook skin with no sidebar, the sidebar links are
+given at the bottom of the page instead, as in the unstyled MySkin.
+
+* Simple: A lightweight skin with a simple white-background sidebar and no
+top bar.
+
+* Modern: An attractive blue/grey theme with sidebar and top bar.
+
+
+== Custom CSS/JS ==
+
+It is possible to customise the site CSS and JavaScript without editing any
+source files. This is done by editing some pages on the wiki:
+
+* [[MediaWiki:Common.css]] -- for skin-independent CSS
+* [[MediaWiki:Monobook.css]], [[MediaWiki:Simple.css]], etc. -- for
+skin-dependent CSS
+* [[MediaWiki:Common.js]], [[MediaWiki:Monobook.js]], etc. -- for custom
+site JavaScript
+
+These can also be customised on a per-user basis, by editing
+[[User:<name>/monobook.css]], [[User:<name>/monobook.js]], etc.
+
+This feature has led to a wide variety of "user styles" becoming available,
+which change the appearance of Monobook or MySkin:
+
+http://meta.wikimedia.org/wiki/Gallery_of_user_styles
 
-== Version 1.4 ==
+If you want a different look for your wiki, that gallery is a good place to start.
 
-MediaWiki still use the PHPTal skin system introduced in version 1.3 but some
-changes have been made to the file organisation.
+== Drop-in custom skins ==
 
-PHP class and PHPTal templates have been moved to /skins/ (respectivly from
-/includes/ and /templates/). This way skin designer and end user just stick to
-one directory.
+If you put a file in MediaWiki's skins directory, ending in .php, the name of 
+the file will automatically be added as a skin name, and the file will be
+expected to contain a class called Skin<name> with the skin class. You can then
+make that skin the default by adding to LocalSettings.php:
 
-Two samples are provided to start with, one for PHPTal use (SkinPHPTal.sample)
-and one without (Skin.sample).
+$wgDefaultSkin = '<name>';
 
+You can also disable dropped-in or core skins using:
 
-== Version 1.3 ==
+$wgSkipSkins[] = '<name>';
 
-The following might help a bit though.
+This technique is used by the more ambitious MediaWiki site operators, to 
+create complex custom skins for their wikis. It should be preferred over 
+editing the core Monobook skin directly. 
 
-Firstly, there's Skin.php; this file will check various settings, and it 
-contains a base class from which new skins can be derived.
+See http://www.mediawiki.org/wiki/Manual:Skinning for more information.
 
-Before version 1.3, each skin had its own PHP file (with a sub-class to Skin) 
-to generate the output. The files are:
- * SkinCologneBlue.php
- * SkinNostalgia.php
- * SkinStandard.php
- * SkinWikimediaWiki.php
-If you want to change those skins, you have to edit these PHP files.
- 
-Since 1.3 a new special skin file is available: SkinPHPTal.php. It makes use of
-the PHPTal template engine and allows you to separate code and layout of the
-pages. The default 1.3 skin is MonoBook and it uses the SkinPHPTAL class.
+== Extension skins ==
 
-To change the layout, just edit the PHPTal template (templates/xhtml_slim.pt) 
-as well as the stylesheets (stylesheets/monobook/*).
+It is now possible (since MediaWiki 1.12) to write a skin as a standard
+MediaWiki extension, enabled via LocalSettings.php. This is done by adding 
+it to $wgValidSkinNames, for example:
 
+$wgValidSkinNames['mycoolskin'] = 'My cool skin';
 
-== pre 1.3 version ==
+and then registering a class in $wgAutoloadClasses called SkinMycoolskin, which 
+derives from Skin. This technique is apparently not yet used (as of 2008) 
+outside the DumpHTML extension.
 
-Unfortunately there isn't any documentation, and the code's in a bit of a mess
-right now during the transition from the old skin code to the new template-based
-skin code in 1.3.
diff -pruN 1:1.12.0-2/docs/title.txt 1:1.13.0-1/docs/title.txt
--- 1:1.12.0-2/docs/title.txt	2008-03-20 22:08:45.000000000 +0000
+++ 1:1.13.0-1/docs/title.txt	2008-07-17 19:12:20.000000000 +0100
@@ -1,76 +1,67 @@
 title.txt
 
-The MediaWiki software's "Title" class represents article
-titles, which are used for many purposes: as the human-readable
-text title of the article, in the URL used to access the article,
-the wikitext link to the article, the key into the article
-database, and so on. The class in instantiated from one of
-these forms and can be queried for the others, and for other
-attributes of the title. This is intended to be an
-immutable "value" class, so there are no mutator functions.
-
-To get a new instance, call one of the static factory
-methods Title::newFromURL(), Title::newFromDBKey(),
-or Title::newFromText(). Once instantiated, the
-other non-static accessor methods can be used, such as
-getText(), getDBKey(), getNamespace(), etc.
-
-The prefix rules: a title consists of an optional interwiki
-prefix (such as "m:" for meta or "de:" for German), followed
-by an optional namespace, followed by the remainder of the
-title. Both interwiki prefixes and namespace prefixes have
-the same rules: they contain only letters, digits, space, and
-underscore, must start with a letter, are case insensitive,
-and spaces and underscores are interchangeable.  Prefixes end
-with a ":". A prefix is only recognized if it is one of those
-specifically allowed by the software. For example, "de:name"
-is a link to the article "name" in the German Wikipedia, because
-"de" is recognized as one of the allowable interwikis. The
-title "talk:name" is a link to the article "name" in the "talk"
-namespace of the current wiki, because "talk" is a recognized
-namespace. Both may be present, and if so, the interwiki must
-come first, for example, "m:talk:name". If a title begins with
-a colon as its first character, no prefixes are scanned for,
-and the colon is just removed. Note that because of these
-rules, it is possible to have articles with colons in their
-names. "E. Coli 0157:H7" is a valid title, as is "2001: A Space
-Odyssey", because "E. Coli 0157" and "2001" are not valid
-interwikis or namespaces.
-
-It is not possible to have an article whose bare name includes
-a namespace or interwiki prefix.
-
-An initial colon in a title listed in wiki text may however
-suppress special handling for interlanguage links, image links,
-and category links.
-
-Character mapping rules: Once prefixes have been stripped, the
-rest of the title processed this way: spaces and underscores are
-treated as equivalent and each is converted to the other in the
-appropriate context (underscore in URL and database keys, spaces
-in plain text). "Extended" characters in the 0x80..0xFF range
-are allowed in all places, and are valid characters. They are
-encoded in URLs.  Other characters may be ASCII letters, digits,
-hyphen, comma, period, apostrophe, parentheses, and colon. No
-other ASCII characters are allowed, and will be deleted if found
-(they will probably cause a browser to misinterpret the URL).
-Extended characters are _not_ urlencoded when used as text or
-database keys.
-
-Character encoding rules: TODO
-
-Canonical forms: the canonical form of a title will always be
-returned by the object. In this form, the first (and only the
-first) character of the namespace and title will be uppercased;
-the rest of the namespace will be lowercased, while the title
-will be left as is. The text form will use spaces, the URL and
-DBkey forms will use underscores. Interwiki prefixes are all
-lowercase. The namespace will use underscores when returned
-alone; it will use spaces only when attached to the text title.
-
-getArticleID() needs some explanation: for "internal" articles,
-it should return the "page_id" field if the article exists, else
-it returns 0. For all external articles it returns 0. All of
-the IDs for all instances of Title created during a request are
-cached, so they can be looked up quickly while rendering wiki
-text with lots of internal links.
+The MediaWiki software's "Title" class represents article titles, which are used
+for many purposes: as the human-readable text title of the article, in the URL
+used to access the article, the wikitext link to the article, the key into the
+article database, and so on. The class in instantiated from one of these forms
+and can be queried for the others, and for other attributes of the title. This
+is intended to be an immutable "value" class, so there are no mutator functions.
+
+To get a new instance, call Title::newFromText(). Once instantiated, the
+non-static accessor methods can be used, such as getText(), getDBKey(),
+getNamespace(), etc. Note that Title::newFromText() may return false if the text
+is illegal according to the rules below.
+
+The prefix rules: a title consists of an optional interwiki prefix (such as "m:"
+for meta or "de:" for German), followed by an optional namespace, followed by
+the remainder of the title. Both interwiki prefixes and namespace prefixes have
+the same rules: they contain only letters, digits, space, and underscore, must
+start with a letter, are case insensitive, and spaces and underscores are
+interchangeable. Prefixes end with a ":". A prefix is only recognized if it is
+one of those specifically allowed by the software. For example, "de:name" is a
+link to the article "name" in the German Wikipedia, because "de" is recognized
+as one of the allowable interwikis. The title "talk:name" is a link to the
+article "name" in the "talk" namespace of the current wiki, because "talk" is a
+recognized namespace. Both may be present, and if so, the interwiki must
+come first, for example, "m:talk:name". If a title begins with a colon as its
+first character, no prefixes are scanned for, and the colon is just removed.
+Note that because of these rules, it is possible to have articles with colons in
+their names. "E. Coli 0157:H7" is a valid title, as is "2001: A Space Odyssey",
+because "E. Coli 0157" and "2001" are not valid interwikis or namespaces.
+
+It is not possible to have an article whose bare name includes a namespace or
+interwiki prefix.
+
+An initial colon in a title listed in wiki text may however suppress special
+handling for interlanguage links, image links, and category links. It is also
+used to indicate the main namespace in template inclusions.
+
+Once prefixes have been stripped, the rest of the title processed this way: 
+
+* Spaces and underscores are treated as equivalent and each  is converted to the
+  other in the appropriate context (underscore in URL and database keys, spaces
+  in plain text). 
+* Multiple consecutive spaces are converted to a single space.
+* Leading or trailing space is removed.
+* If $wgCapitalLinks is enabled (the default), the first letter is  capitalised,
+  using the capitalisation function of the content language object.
+* The unicode characters LRM (U+200E) and RLM (U+200F) are silently stripped.
+* Invalid UTF-8 sequences or instances of the replacement character (U+FFFD) are
+  considered illegal.
+* A percent sign followed by two hexadecimal characters is illegal
+* Anything that looks like an XML/HTML character reference is illegal
+* Any character not matched by the $wgLegalTitleChars regex is illegal
+* Zero-length titles (after whitespace stripping) are illegal
+
+All titles except special pages must be less than 255 bytes when encoded with
+UTF-8, because that is the size of the database field. Special page titles may
+be up to 512 bytes.
+
+Note that Unicode Normal Form C (NFC) is enforced by MediaWiki's user interface
+input functions, and so titles will typically be in this form. 
+
+getArticleID() needs some explanation: for "internal" articles, it should return
+the "page_id" field if the article exists, else it returns 0. For all external
+articles it returns 0. All of the IDs for all instances of Title created during
+a request are cached, so they can be looked up quickly while rendering wiki text
+with lots of internal links. See linkcache.txt.
diff -pruN 1:1.12.0-2/HISTORY 1:1.13.0-1/HISTORY
--- 1:1.12.0-2/HISTORY	2008-03-20 22:09:08.000000000 +0000
+++ 1:1.13.0-1/HISTORY	2008-03-26 00:26:12.000000000 +0000
@@ -1,5 +1,557 @@
 Change notes from older releases. For current info see RELEASE-NOTES.
 
+== MediaWiki 1.12 ==
+
+This is the Winter 2007 quarterly release.
+
+MediaWiki is now using a "continuous integration" development model with
+quarterly snapshot releases. The latest development code is always kept
+"ready to run", and in fact runs our own sites on Wikipedia.
+
+Release branches will continue to receive security updates for about a year
+from first release, but nonessential bugfixes and feature developments
+will be made on the development trunk and appear in the next quarterly release.
+
+Those wishing to use the latest code instead of a branch release can obtain
+it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
+
+=== Configuration changes in 1.12 ===
+* Marking edits as bot edits with Special:Contributions?bot=1 now requires the
+  markbotedit permission, rather than the rollback permission previously used.
+  This permission is assigned by default to the sysop group.
+* MediaWiki now checks if serialized files are out of date. New configuration
+  variable $wgCheckSerialized can be set to false to enable old behavior (i.e.
+  to not check and assume they are always up to date)
+* The rollback permission can now be rate-limited using the normal mechanism.
+* New configuration variable $wgExtraLanguageNames
+* Behaviour of $wgAddGroups and $wgRemoveGroups changed. New behaviour:
+* * Granting the userrights privilege allows arbitrary changing of rights.
+* * Without the userrights privilege, a user will be able to add and/or
+     remove the groups specified in $wgAddGroups and $wgRemoveGroups for
+     any groups they are in.
+* New permission userrights-interwiki for changing user rights on foreign wikis.
+* $wgImplictGroups for groups that are hidden from Special:Listusers, etc.
+* $wgAutopromote: automatically promote users who match specified criteria
+* $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf: allow users to add or remove
+  themselves from specified groups via Special:Userrights.
+* When $wgUseTidy has been enabled, PHP's Tidy module is now used if it is
+  present, in preference to an external Tidy executable which may or may not
+  be present. To force use of external Tidy even when the PHP module is
+  available, set $wgTidyInternal to false.
+
+
+=== New features in 1.12 ===
+* (bug 10735) Add a warning for non-descriptive filenames at Special:Upload
+* Add {{filepath:}} parser function to get full path to an uploaded file,
+  complementing {{fullurl:}} for pages.
+* (bug 11136) If using Postgres, search path is explicitly set if wgDBmwschema
+  is not set to 'mediawiki', allowing multiple mediawiki instances per user.
+* (bug 11151) Add descriptive <title> to revision history page
+* (bug 5412) Add feed links for the site to all pages
+* (bug 11353) Add ability to retrieve raw section content via action=raw
+* (bug 6909) Show relevant deletion log lines when uploading a previously
+  deleted file
+* On SkinTemplate based skins (like MonoBook), omit confusing "edit"/"view
+  source" tab entirely if the page doesn't exist and the user isn't allowed to
+  create it
+* Clarify instructions given when an exception is thrown
+* AuthPlugin added strictUserAuth() method to allow per-user override
+  of the strict() authentication behavior.
+* (bug 7872) Deleted revisions can now be viewed as diffs showing changes
+  against the previous revision, whether currently deleted or live.
+* Added tooltips for the "Go" and "Search" buttons
+* (bug 11649) Show input form when Special:Whatlinkshere has no parameters
+* isValidEmailAddr hook added to User method of that name, to allow, e.g., re-
+  stricting e-mail addresses to a specific domain
+* Removed "Clear" link in watchlist editor tools, as people were afraid to
+  click it. Existing clear links will fall back to the raw editor, which is
+  very easy to clear your watchlist with.
+* (bug 1405) Add wgUseNPPatrol option to control patroling for new articles
+  on Special:Newpages
+* LogLine hook added to allow formatting custom entries in Special:Log.
+* Support for Iranian calendar
+* (bug 1401) Allow hiding logged-in users, bots and patrolled pages on
+  Special:Newpages
+* ChangesListInsertArticleLink hook added for adding extra article info to RC.
+* MediaWikiPerformAction hook added for diverting control after the main
+  globals have been set up but before any actions have been taken.
+* BeforeWatchlist hook added for filtering or replacing watchlist.
+* SkinTemplateTabAction hook added for altering the properties of tab links.
+* OutputPage::getRedirect public method added.
+* (bug 11848, 12506) Allow URL parameters 'section', 'editintro' and 'preload'
+  in Special:Mypage and Special:Mytalk
+* Add ot=raw to Special:Allmessages
+* Support for Hebrew calendar
+* Support for Hebrew numerals in dates and times
+* (bug 11315) Signatures can be configured in [[MediaWiki:Signature]] and
+  [[MediaWiki:Signature-anon]]
+* Signatures for anonymous users link to Special:Contributions page rather than
+  user page
+* Added --override switch for disabled pages in updateSpecialPages.php
+* Provide a unique message (ipb_blocked_as_range) if unblock of a single IP
+  fails
+  because it is part of a blocked range.
+* (bug 3973) Use a separate message for the email content when an account is
+  created by another user
+* dumpTextPass.php can spawn fetchText.php as a subprocess, which should restart
+  cleanly if database connections fail unpleasantly.
+* (bug 12028) Add Special:Listbots as shortcut for Special:Listusers/bot
+* (bug 9633) Add a predefined list of delete reasons to the deletion form
+* Show a warning message when creating/editing a user (talk) page but the user
+  does not exists
+* (bug 8396) Ignore out-of-date serialised message caches
+* (bug 12195) Undeleting pages now requires 'undelete' permission
+* (bug 11810) Localize displayed semicolons
+* (bug 11657) Support for Thai solar calendar
+* (bug 943) RSS feed for Recentchangeslinked
+* Introduced AbortMove hook
+* (bug 2919) Protection of nonexistent pages with regular protection interface.
+* Special:Upload now lists permitted/prohibited file extensions.
+* Split ambiguous filetype-badtype message into two new messages,
+  filetype-unwanted-type and filetype-banned-type.
+* Added link to the old title in Special:Movepage
+* On Special:Movepage, errors are now more noticeable.
+* It is now possible to change rights on other local wikis without the MakeSysop
+  extension
+* Add HTML ID's mw-read-only-warning and mw-anon-edit-warning to warnings when
+  editing to allow CSS styling.
+* Parser now returns list of sections
+* When a user is prohibited from creating a page, a title of "View source"
+  makes no sense, and there should be no "Return to [[Page]]" link.
+* (bug 12486) Protected titles now give a warning for privileged editors.
+* (bug 9939) Special:Search now sets focus to search input box when no existing
+  search is active
+* For Special:Userrights, use GET instead of POST to search for users.
+* Allow subpage syntax for Special:Userrights, i.e., Special:Userrights/Name.
+* When submitting changes on Special:Userrights, show the full form again, not
+  just the search box.
+* Added exception hooks
+* (bug 12574) Allow bots to specify whether an edit should be marked as a bot
+  edit, via the parameter 'bot'. (Default: '1')
+* (bug 12536) User should be able to get MediaWiki version from any page
+* (bug 12622) A JavaScript constant to declare whether api.php is available
+* Add caching to the AJAX search
+* Add APCOND_INGROUPS
+* Add DBA caching to installer
+* (bug 12585) Added a bunch of parameters to the revertpage message
+* Support redirects in image namespace
+* (bug 10049) Prefix index search and namespaces in Special:Withoutinterwiki
+* (bug 12668) Support for custom iPhone bookmark icon via $wgAppleTouchIcon
+* Add option to include templates in Special:Export.
+* (bug 12655) Added $wgUserEmailUseReplyTo config option to put sender
+  address in Reply-To instead of From for user-to-user emails.
+  This protects against SPF problems and privacy-leaking bounce messages
+  when using mailers that set the envelope sender to the From header value.
+* (bug 11897) Add alias [[Special:CreateAccount]] & [[Special:Userlogin/signup]]
+  for Special:Userlogin?type=signup
+* (bug 12214) Add a predefined list of delete reasons to the file deletion form
+* Merged backends for OpenSearch suggestions and AJAX search.
+  Both now accept namespace prefixes, handle 'Media:' and 'Special:' pages,
+  and reject interwiki prefixes. PrefixSearch class centralizes this code,
+  and the backend part can be overridden by the PrefixSearchBackend hook.
+* (bug 10365) Localization of Special:Version
+* When installing using Postgres, the Pl/Pgsql language is now checked for 
+  and installed when at the superuser level.
+* The default robot policy for the entire wiki is now configurable via the
+  $wgDefaultRobotPolicy setting.
+* (bug 12239) Use different separators for autocomments
+* (bug 12857) Patrol link on new pages should clear floats
+* (bug 12968) Render redirect wikilinks in a redirect class for customization
+  via user/site CSS.
+* EditPageBeforeEditButtons hook added for altering the edit buttons below the edit box
+
+=== Bug fixes in 1.12 ===
+
+* Subpages are now indexed for searching properly when using PostgreSQL
+* (bug 3846) Suppress warnings from, e.g. open_basedir when scanning for
+  ImageMagick, diff3 et al. during installation [patch by Jan Reininghaus]
+* (bug 7027) Shift handling of deletion permissions-checking to
+  getUserPermissionsErrors.
+* Login and signup forms are now more correct for right-to-left languages.
+* (bug 5387) Block log items on RecentChanges don't make use of possible
+  translations
+* (bug 11211) Pass, as a parameter to the protectedpagetext interface
+  message, the level of protection.
+* (bug 9611) Supply the blocker and reason for the cantcreateaccounttext
+  message.
+* (bug 8759) Fixed bug where rollback was allowed on protected pages for wikis
+  where rollback is given to non-sysops.
+* (bug 8834) Split off permission for editing user JavaScript and CSS from
+  editinterface to a new permission key editusercssjs.
+* (bug 11266) Set fallback language for Fulfulde (ff) to French
+* (bug 11179) Include image version deletion comment in public log
+* Fixed notice when accessing special page without read permission and whitelist
+  is not defined
+* (bug 9252) Fix for tidy funkiness when using editintro mode
+* (bug 4021) Fix for MySQL wildcard search
+* (bug 10699) Fix for MySQL phrase search
+* (bug 11321) Fix width of gallerybox when option "width=xxx" is used
+* (bug 7890) Special:BrokenRedirects links deleted redirects to a non-existent
+  page
+* Fix initial statistics when installing: add correct values
+* (bug 11342) Fix several 'returnto' links in permissions/error pages which
+  linked to the main page instead of targetted page
+* Strike the link to the redirect rather than using an asterisk in
+  Special:Listredirects
+* (bug 11355) Fix false positives in Safe Mode and other config detection
+  when boolean settings are disabled with 'Off' via php_admin_value/php_value
+* (bug 11292) Fixed unserialize errors with Postgres by creating special Blob
+  object.
+* (bug 11363) Make all metadata fields bytea when using Postgres.
+* (bug 11331) Add buildConcat() and use CASE not IF for DB compatibility. Make
+  oldimage cascade delete via image table for Postgres, change fa_storage_key
+  TEXT.
+* (bug 11438) Live Preview chops returned text
+* Show the right message on account creation when the user is blocked
+* (bug 11450) Fix creation of objectcache table on upgrade
+* Fix namespace selection after submit of Special:Newpages
+* Make input form of Special:Newpages nicer for RTL wikis
+* (bug 11462) Fix typo in LanguageGetSpecialPageAliases hook name
+* (bug 11474) Fix unintentional fall-through in math error handling
+* (bug 11478) Fix undefined method call in file deletion interface
+* (bug 278) Search results no longer highlight incorrect partial word matches
+* Compatibility with incorrectly detected old-style DJVU mime types
+* (bug 11560) Fix broken HTML output from weird link nesting in edit comments.
+  Nested links (as in image caption text) still don't work _right_ but they're
+  less wrong
+* (bug 9718) Remove unnecessary css from main.css causing spacing issues on
+  some browsers.
+* (bug 11574) Add an interface message loginstart, which, similarly to loginend,
+  appears just before the login form. Patch by MinuteElectron.
+* Do not cache category pages if using 'from' or 'until'
+* Created new hook getUserPermissionsErrors, to go with userCan changes.
+* Diff pages did not properly display css/js pages.
+* (bug 11620) Add call to User::isValidEmailAddr during accout creation.
+* (bug 11629) If $wgEmailConfirmToEdit is true, require people to supply an
+  email address when registering.
+* (bug 11612) Days to show in recent changes cannot be larger than 7
+* (bug 11131) Change filearchive width/height columns to int for Postgres
+* Support plural in undeleted{revisions,revisions-files,files}
+* (bug 11343) If the database is read-only, ensure that undelete fails.
+* (bug 11690) Show revert link for page moves in Special:Log to allowed users
+  only
+* Initial-lowercase prefix checks in namespaceDupes.php now actually work.
+* Fix regression in LinkBatch.php breaking PHP 5.0
+* (bug 11452) wfMsgExt uses sometimes wrong language object for parsing magic
+  words when called with options ''parsemag'' or ''content''.
+* (bug 11727) Support plural in 'historysize' message
+* (bug 11744) Incorrect return value from Title::getParentCategories()
+* (bug 11762) Fix native language name of Akan (ak)
+* (bug 11722) Fix inconsistent case in unprotect tabs
+* (bug 11795) Be more paranoid about confirming accept-encoding header is
+  present
+* (bug 11809) Use formatNum() for more numbers
+* (bug 11818) Fix native language name of Inuktitut (iu)
+* Remove all commas when parsing float numbers in sorted tables
+* Limit text field of deletion, protection and user rights changes reasons to
+  255 characters (already restricted in the database)
+* In the deletion default reasons, calculate how much text to get from the
+  article text, rather than getting 150 characters (which may be too much)
+* Add two messages for Special:Blockme which were used but undefined
+* (bug 11921) Support plural in message number_of_watching_users_pageview
+* If an IP address is blocked as part of a rangeblock, attempting to unblock
+  the single IP should not unblock the entire range.
+* (bug 6695) Fix native language name of Southern Sotho (Sesotho) (st)
+* Make action=render follow redirects by default
+* If restricted read access was enabled, whitelist didn't work with special
+  pages which had spaces in theirs names
+* If restricted read access was enabled, requests for non-existing special pages
+  threw an exception
+* Feeds for recent changes now provide correct URLs for the change, not just
+  the page
+* Check for if IP is blocked as part of a range when unblocking (see above bug-
+  fix) was faulty. Now fixed.
+* Fixed wpReason URL parameter to action=delete.
+* Do not force a password for account creation by email
+* Ensure that rate-limiting is applied to rollbacks.
+* Make a better rate-limiting error message (i.e. a normal MW error,
+  rather than an "Internal Server Error").
+* Do not present an image bigger than the source when 'frameless' option is used
+  (to be consistent with the 'thumb' option now)
+* Support {{PLURAL}} for import log
+* Make sure that the correct log entries are shown on Special:Userrights even
+  for users with special characters in their names
+* The number of watching users in watchlists was always reported as 1
+* namespaceDupes.php no longer dies when coming across an illegal title
+* (bug 12143) Do not show a link to patrol new pages for non existent pages
+* (bug 12166) Fix XHTML validity for Special:Emailuser
+* (bug 11346) Users who cannot edit a page can now no longer unprotect it.
+* (bug 451) Add a generic Traditional / Simplified Chinese conversion table,
+  instead of a Traditional conversion with Taiwan variant, and a Simplified
+  conversion with China variant.
+* (bug 12178) Fix wpReason parameter to action=delete, again.
+* Graceful behavior for updateRestrictions.php if a page already has records
+  in the page_restrictions matching its old page_restrictions field.
+  May help with odd upgrade issues or race condition.
+* (bug 11993) Remove contentsub "revision history"
+* (bug 11952) Ensure we quote_ident() all schema names as needed
+   inside of the DatabasePostgres.php file.
+* (bug 12184) Exceptions now sent to stderr instead of stdout for command-line
+  scripts, making for cleaner reporting during batch jobs. PHP errors will also
+  be redirected in most cases on PHP 5.2.4 and later, switching 'display_errors'
+  to 'stderr' at runtime.
+* (bug 12148) Text highlight wasn't applied to cleanly deleted and added
+  lines in diff output
+* (bug 10166) Fix a PHP warning in Language::getMagic
+* Only mark rollback edits as minor if the user can normally mark edits minor
+* Escape page names in the move successful page (e.g. for pages with two
+  apostrophes).
+* (bug 12145) Add localized names of kk-variants
+* (bug 12259) Localize the numbers in deleted pages on the sysop view
+* Set proper page title for successful file deletion
+* (bug 11221) Do not show 'Compare selected versions' button for a history page
+  with one revision only
+* (bug 12267) Set the default date format to Thai solar calender for the Thai
+  language
+* (bug 10184) Extensions' stylesheets and scripts should be loaded before
+  user-customized ones (like Common.css, Common.js)
+* (bug 12283) Special:Newpages forgets parameters
+* (bug 12031) All namespaces doesn't work in Special:Newpages
+* (bug 585) Only create searchindex replica table for parser tests if db is
+  MySQL
+* Allow --record option if parserTests.php to work when using Postgres
+* (bug 12296) Simplify cache epoch in default LocalSettings.php
+* (bug 12346) XML fix when body double-click and click handlers are present
+* Fix regression -- missing feed links in sidebar on Special:Recentchanges
+* (bug 12371) Handle more namespace case variants in namespaceDupes.php
+* (bug 12380) Bot-friendly EditPage::spamPage
+* (bug 8066) Spaces can't be entered in special page aliases
+* Hide undo link if user can't edit article
+* (bug 12416) Fix password setting for createAndPromote.php
+* (bug 3097) Inconsistently usable titles containing HTML character entities
+  are now forbidden. A run of cleanupTitles.php will fix up existing pages.
+* (bug 12446) Permissions check fix for undelete link
+* (bug 12451) AJAX title normalization tweaks
+* When a user creating a page is not allowed to either create the page nor edit
+  it, all applicable reasons are now shown.
+* (bug 11428) Allow $wgScript inside $wgArticlePath when emulating PATH_INFO
+  Fixes 'root'-style rewrite configurations
+* (bug 12493) Removed hardcoded MAX_FILE_SIZE from Special:Import upload form
+* (bug 12489) Special:Userrights listed in restricted section again
+* (bug 12553) Fixed invalid XHTML in edit conflict screen
+* (bug 12505) Fixed section=0 with action=raw
+* (bug 12614) Do not log user rights change that didn't change anything
+* (bug 12584) Don't reset cl_timestamp when auto-updating sort key on move
+* (bug 12588) Fix selection in namespace selector on Special:Newpages
+* Use only default options when generating RSS and Atom syndication links.
+  This should help prevent infinite link loops that some software may follow,
+  and will generally keep feed behavior cleaner.
+* (bug 12608) Unifying the spelling of getDBkey() in the code.
+* (bug 12611) Bot flag ignored in recent changes
+* (bug 12617) Decimal and thousands separators for Romanian
+* (bug 12567) Fix for misformatted read-only messages on edit, protect.
+  Also added proper read-only checks to several special pages.
+  Have removed read-only checks from the general user permission framework.
+* Creating a site with a name containing '#' is no longer permitted, since the
+  name will not work (but $wgSiteName is not checked if manually set).
+* (bug 12695) Suppress dvips verbiage from web server error log
+* (bug 12716) Unprotecting a non-protected page leaves a log entry
+* Log username blocks with canonical form of name instead of input form
+* (bug 11593, 12719) Fixes for overzealous invocation of thumb.php.
+  Non-image handlers and full-size images may now decline it, fixing
+  mystery failures when using $wgThumbnailScriptPath.
+* (bug 12327) Comma in username no longer disrupts mail headers
+* (bug 6436) Localization of Special:Import XML parser Error message(s).
+* Security fix for API on MSIE
+* (bug 12768) Database query syntax error in maintenance/storage/compressOld.inc
+* (bug 12753) Empty captions in MediaWiki:Sidebar result in PHP errors
+* (bug 12790) Page protection is not logged when edit-protection is used
+  and move-protection is not
+* (bug 12793) Fix for restricted namespaces/pages in Special:Export
+* Fix for Special:Export so it doesn't ignore the page named '0'
+* Don't display rollback link if the user doesn't have all required permissions
+* The comment of a time-limited protection now contains the date in the default
+  format
+* (bug 12880) wfLoadExtensionMessages does not use $fallback from MessagesXx.php
+* (bug 12885) Correction for Russian convertPlural function
+* (bug 12768) Make DatabasePostgres->hasContraint() schema aware.
+* (bug 12735) Truncate usernames in comments using mb_ functions.
+* (bug 12892) Poor tab indexing on "delete file" form
+* (bug 12660) When creating an account by e-mail, do not send the creator's IP
+  address
+* (bug 12931) Fix wrong global variable in SpecialVersion
+* (bug 12919) Use 'deletedrevision' message as content when deleting an old file
+  version
+* (bug 12952) Using Nosuchusershort instead of Nosuchuser when account creation
+  is disabled
+* (bug 12869) Magnify icon alignment should be adjusted using linked CSS
+* Fixing message cache updates for MediaWiki messages moves
+* (bug 12815) Signature timestamps were always in UTC, even if the timezone code
+  in parentheses after them claimed otherwise
+* (bug 12732) Fix installer and searching to handle built-in tsearch2 for Postgres.
+* (bug 12784) Change "bool" types to smallint to handle Postgres 8.3 strictness.
+* (bug 12301) Allow maintenance/findhooks.php to search hooks in multiple directories.
+* (bug 7681, 11559) Cookie values no longer override GET and POST variables.
+* (bug 5262) Fully-qualified $wgStylePath no longer corrupted on XML feeds
+* (bug 3269) Inaccessible titles ending in '/.' or '/..' now forbidden.
+* (bug 12935, 12981) Fully-qualify archive URLs in delete, revert messages
+* (bug 12938) Fix template expansion and 404 returns for action=raw with section
+* (bug 11567) Fix error checking for PEAR::Mail. UserMailer::send() now returns
+  true-or-WikiError, which seems to be the calling convention expected by half
+  its callers already
+* (bug 12846) IE rtl.css issue in RTL wikis special:Preferences when selecting an
+  LTR user language
+* (bug 13005) DISPLAYTITLE does not work on preview
+* (bug 13004) Fix error on Postgres searches that return too many results.
+
+== Parser changes in 1.12 ==
+
+For help with migration to the MediaWiki 1.12 parser, please visit:
+
+http://meta.wikimedia.org/wiki/Migration_to_the_new_preprocessor
+
+The parser pass order has changed from
+
+   * Extension tag strip and render
+   * HTML normalisation and security
+   * Template expansion
+   * Main section...
+
+to
+
+   * Template and extension tag parse to intermediate representation
+   * Template expansion and extension rendering
+   * HTML normalisation and security
+   * Main section...
+
+The main effect of this for the user is that the rules for uncovered syntax
+have changed.
+
+Uncovered main-pass syntax, such as HTML tags, are now generally valid, whereas
+previously in some cases they were escaped. For example, you could have "<ta" in
+one template, and "ble>" in another template, and put them together to make a
+valid <table> tag. Previously the result would have been "&lt;table&gt;".
+
+Uncovered preprocessor syntax is generally not recognised. For example, if you
+have "{{a" in Template:A and "b}}" in Template:B, then "{{a}}{{b}}" will be
+converted to a literal "{{ab}}" rather than the contents of Template:Ab. This
+was the case previously in HTML output mode, and is now uniformly the case in
+the other modes as well. HTML-style comments uncovered by template expansion
+will not be recognised by the preprocessor and hence will not prevent template
+expansion within them, but they will be stripped by the following HTML security
+pass.
+
+Bug 5678 has been fixed. This has a number of user-visible effects related to
+the removal of this double-parse. Please see the wiki page for examples. 
+
+Message transformation mode has been removed, and replaced with "preprocess"
+mode. This means that some MediaWiki namespace messages may need to be updated,
+especially ones which took advantage of the terribly counterintuitive behaviour
+of the former message mode. 
+
+The header identification routines for section edit and for numbering section
+edit links have been merged. This removes a significant failure mode and fixes a
+whole category of bugs (tracked by bug #4899). Wikitext headings uncovered by
+template expansion will still be rendered into a heading tag, and will get an 
+entry in the TOC, but will not have a section edit link. HTML-style headings 
+will also not have a section edit link. Valid wikitext headings present in the 
+template source text will get a template section edit link. This is a major 
+break from previous behaviour, but I believe the effects are almost entirely 
+beneficial.
+
+The main motivation for making these changes was performance. The new two-pass
+preprocessor can skip "dead branches" in template expansion, such as unfollowed
+#switch cases and unused defaults for template arguments. This provides a
+significant performance improvement in template-heavy test cases taken from
+Wikipedia. Parser function hooks can participate in this performance improvement
+by using the new SFH_OBJECT_ARGS flag during registration.
+
+The pre-expand include size limit has been removed, since there's no efficient
+way to calculate such a figure, and it would now be meaningless for performance
+anyway. The "preprocessor node count" takes its place, with a generous default
+limit.
+
+The context in which XML-style extension tags are called has changed, so
+extensions which make use of the parser state may need compatibility changes.
+
+The new preprocessor syntax has been documented in Backus-Naur Form at:
+
+http://www.mediawiki.org/wiki/Preprocessor_ABNF
+
+The ExpandTemplates extension now has the ability to generate an XML parse 
+tree from wikitext source. This parse tree corresponds closely to the grammar
+documented on that page.
+
+=== API changes in 1.12 ===
+
+Full API documentation is available at http://www.mediawiki.org/wiki/API
+
+* (bug 11275) Enable descending sort in categorymembers
+* (bug 11308) Allow the API to output the image metadata
+* (bug 11296) Temporary fix for escaping of ampersands inside links in
+  pretty-printed
+  help document.
+* (bug 11405) Expand templates implementation in the API
+* (bug 11218) Add option to feedwatchlist to display multiple revisions for each
+  page.
+* (bug 11404) Provide name of exception caught in error code field of internal
+  api error messages.
+* (bug 11534) rvendid doesn't work
+* Fixed rvlimit of the revisions query to only enforce the lower query limit if
+  revision content is requested.
+* Include svn revision number (if install is checked-out from svn) in siteinfo
+  query.
+* (bug 11173) Allow limited wikicode rendering via api.php
+* (bug 11572) API should provide interface for expanding templates
+* (bug 11569) Login should return the cookie prefix
+* (bug 11632) Breaking change: Specify the type of a change in the recentchanges
+  list as 'edit', 'new', 'log' instead of 0, 1, 2, respectively.
+* Compatibility fix for PHP 5.0.x.
+* Add rctype parameter to list=recentchanges that filters by type
+* Add apprtype and apprlevel parameters to filter list=allpages by protection
+  types and levels
+* Add apdir parameter to enable listing all pages from Z to A
+* (bug 11721) Use a different title for results than for the help page.
+* (bug 11562) Added a user_registration parameter/field to the list=allusers
+  query.
+* (bug 11588) Preserve document structure for empty dataset in backlinks query.
+* Outputting list of all user preferences rather than having to request them by
+  name
+* (bug 11206) api.php should honor maxlag
+* Make prop=info check for restrictions in the old format too.
+* Add apihighlimits permission, default for sysops and bots
+* Add limit=max to use maximal limit
+* Add action=parse to render parser output. Use it instead of action=render
+  which has been removed
+* Add rvtoken=rollback to prop=revisions
+* Add meta=allmessages to get messages from site's messages cache.
+* Use bold and italics highlighting only in API help
+* Added action={block,delete,move,protect,rollback,unblock,undelete} and
+  list={blocks,deletedrevs}
+* Fixed sessionid attribute in action=login
+* Standardized limits. Revisions and Deletedrevisions formerly using
+  200 / 10000, now 500 / 5000, in line with other modules.
+* Added list=allcategories module
+* (bug 12321) API list=blocks reveals private data
+* Fix output of wfSajaxSearch
+* (bug 12413) meta=userinfo missing <query> tag
+* Add list of sections to action=parse output
+* Added action=logout
+* Added cascade flag to prop=info&inprop=protections
+* Added wlshow parameter to list=watchlist, similar to rcshow
+  (list=recentchanges)
+* Added support for image thumbnailing to prop=imageinfo
+* action={login,block,delete,move,protect,rollback,unblock,undelete} now must be
+  POSTed
+* prop=imageinfo interface changed: iihistory replaced by iilimit, iistart and
+  iiend parameters
+* Added amlang parameter to meta=allmessages
+* Added apfilterlanglinks parameter to list=allpages, replacing
+  query.php?what=nolanglinks
+* (bug 12718) Added action=paraminfo module that provides information about API
+  modules and their parameters
+* Added iiurlwidth and iiurlheight parameters to prop=imageinfo
+* Added format=txt and format=dbg, imported from query.php
+* Added uiprop=editcount to meta=userinfo
+* Added list=users which fetches user information
+* Added list=random which fetches a list of random pages
+* Added page parameter to action=parse to facilitate parsing of existing pages
+* Added uiprop=ratelimits to meta=userinfo
+* Added siprop=namespacealiases to meta=siteinfo
+* Made multiple values for ucuser possible in list=usercontribs
+* (bug 12944) Added cmstart and cmend parameters to list=categorymembers
+* Allow queries to have a where range that does not match the range field
+
 == MediaWiki 1.11 ==
 
 This is the Summer 2007 branch release of MediaWiki.
diff -pruN 1:1.12.0-2/includes/AjaxDispatcher.php 1:1.13.0-1/includes/AjaxDispatcher.php
--- 1:1.12.0-2/includes/AjaxDispatcher.php	2008-03-20 22:08:48.000000000 +0000
+++ 1:1.13.0-1/includes/AjaxDispatcher.php	2008-06-28 20:40:14.000000000 +0100
@@ -1,5 +1,9 @@
 <?php
 /**
+ * @defgroup Ajax Ajax
+ *
+ * @file
+ * @ingroup Ajax
  * Handle ajax requests and send them to the proper handler.
  */
 
@@ -11,7 +15,7 @@ require_once( 'AjaxFunctions.php' );
 
 /**
  * Object-Oriented Ajax functions.
- * @addtogroup Ajax
+ * @ingroup Ajax
  */
 class AjaxDispatcher {
 	/** The way the request was made, either a 'get' or a 'post' */
@@ -58,6 +62,7 @@ class AjaxDispatcher {
 		break;
 
 		default:
+			wfProfileOut( __METHOD__ );
 			return;
 			# Or we could throw an exception:
 			#throw new MWException( __METHOD__ . ' called without any data (mode empty).' );
@@ -81,9 +86,13 @@ class AjaxDispatcher {
 		wfProfileIn( __METHOD__ );
 
 		if (! in_array( $this->func_name, $wgAjaxExportList ) ) {
+			wfDebug( __METHOD__ . ' Bad Request for unknown function ' . $this->func_name . "\n" );
+
 			wfHttpError( 400, 'Bad Request',
 				"unknown function " . (string) $this->func_name );
 		} else {
+			wfDebug( __METHOD__ . ' dispatching ' . $this->func_name . "\n" );
+
 			if ( strpos( $this->func_name, '::' ) !== false ) {
 				$func = explode( '::', $this->func_name, 2 );
 			} else {
@@ -93,6 +102,10 @@ class AjaxDispatcher {
 				$result = call_user_func_array($func, $this->args);
 
 				if ( $result === false || $result === NULL ) {
+					wfDebug( __METHOD__ . ' ERROR while dispatching ' 
+							. $this->func_name . "(" . var_export( $this->args, true ) . "): " 
+							. "no data returned\n" );
+
 					wfHttpError( 500, 'Internal Error',
 						"{$this->func_name} returned no data" );
 				}
@@ -103,9 +116,15 @@ class AjaxDispatcher {
 
 					$result->sendHeaders();
 					$result->printText();
+
+					wfDebug( __METHOD__ . ' dispatch complete for ' . $this->func_name . "\n" );
 				}
 
 			} catch (Exception $e) {
+				wfDebug( __METHOD__ . ' ERROR while dispatching ' 
+						. $this->func_name . "(" . var_export( $this->args, true ) . "): " 
+						. get_class($e) . ": " . $e->getMessage() . "\n" );
+
 				if (!headers_sent()) {
 					wfHttpError( 500, 'Internal Error',
 						$e->getMessage() );
@@ -119,5 +138,3 @@ class AjaxDispatcher {
 		$wgOut = null;
 	}
 }
-
-
diff -pruN 1:1.12.0-2/includes/AjaxFunctions.php 1:1.13.0-1/includes/AjaxFunctions.php
--- 1:1.12.0-2/includes/AjaxFunctions.php	2008-03-20 22:08:48.000000000 +0000
+++ 1:1.13.0-1/includes/AjaxFunctions.php	2008-06-27 11:24:00.000000000 +0100
@@ -1,8 +1,7 @@
 <?php
-
-/** 
- * @package MediaWiki
- * @addtogroup Ajax
+/**
+ * @file
+ * @ingroup Ajax
  */
 
 if( !defined( 'MEDIAWIKI' ) ) {
@@ -56,7 +55,7 @@ function js_unescape($source, $iconv_to 
 
 /**
  * Function coverts number of utf char into that character.
- * Function taken from: http://sk2.php.net/manual/en/function.utf8-encode.php#49336
+ * Function taken from: http://www.php.net/manual/en/function.utf8-encode.php#49336
  *
  * @param $num Integer
  * @return utf8char
@@ -76,7 +75,7 @@ function code2utf($num){
 define( 'AJAX_SEARCH_VERSION', 2 );	//AJAX search cache version
 
 function wfSajaxSearch( $term ) {
-	global $wgContLang, $wgOut, $wgUser, $wgCapitalLinks, $wgMemc;
+	global $wgContLang, $wgUser, $wgCapitalLinks, $wgMemc;
 	$limit = 16;
 	$sk = $wgUser->getSkin();
 	$output = '';
@@ -84,7 +83,7 @@ function wfSajaxSearch( $term ) {
 	$term = trim( $term );
 	$term = $wgContLang->checkTitleEncoding( $wgContLang->recodeInput( js_unescape( $term ) ) );
 	if ( $wgCapitalLinks )
-		$term = $wgContLang->ucfirst( $term ); 
+		$term = $wgContLang->ucfirst( $term );
 	$term_title = Title::newFromText( $term );
 
 	$memckey = $term_title ? wfMemcKey( 'ajaxsearch', md5( $term_title->getFullText() ) ) : wfMemcKey( 'ajaxsearch', md5( $term ) );
@@ -97,12 +96,12 @@ function wfSajaxSearch( $term ) {
 
 	$r = $more = '';
 	$canSearch = true;
-	
+
 	$results = PrefixSearch::titleSearch( $term, $limit + 1 );
 	foreach( array_slice( $results, 0, $limit ) as $titleText ) {
 		$r .= '<li>' . $sk->makeKnownLink( $titleText ) . "</li>\n";
 	}
-	
+
 	// Hack to check for specials
 	if( $results ) {
 		$t = Title::newFromText( $results[0] );
@@ -128,9 +127,10 @@ function wfSajaxSearch( $term ) {
 
 	$valid = (bool) $term_title;
 	$term_url = urlencode( $term );
-	$term_diplay = htmlspecialchars( $valid ? $term_title->getFullText() : $term );
+	$term_normalized = $valid ? $term_title->getFullText() : $term;
+	$term_display = htmlspecialchars( $term );
 	$subtitlemsg = ( $valid ? 'searchsubtitle' : 'searchsubtitleinvalid' );
-	$subtitle = wfMsgWikiHtml( $subtitlemsg, $term_diplay );
+	$subtitle = wfMsgExt( $subtitlemsg, array( 'parse' ), wfEscapeWikiText( $term_normalized ) );
 	$html = '<div id="searchTargetHide"><a onclick="Searching_Hide_Results();">'
 		. wfMsgHtml( 'hideresults' ) . '</a></div>'
 		. '<h1 class="firstHeading">'.wfMsgHtml('search')
@@ -138,15 +138,15 @@ function wfSajaxSearch( $term ) {
 	if( $canSearch ) {
 		$html .= '<ul><li>'
 			. $sk->makeKnownLink( $wgContLang->specialPage( 'Search' ),
-						wfMsgHtml( 'searchcontaining', $term_diplay ),
+						wfMsgHtml( 'searchcontaining', $term_display ),
 						"search={$term_url}&fulltext=Search" )
 			. '</li><li>' . $sk->makeKnownLink( $wgContLang->specialPage( 'Search' ),
-						wfMsgHtml( 'searchnamed', $term_diplay ) ,
+						wfMsgHtml( 'searchnamed', $term_display ) ,
 						"search={$term_url}&go=Go" )
 			. "</li></ul>";
 	}
 	if( $r ) {
-		$html .= "<h2>" . wfMsgHtml( 'articletitles', $term_diplay ) . "</h2>"
+		$html .= "<h2>" . wfMsgHtml( 'articletitles', $term_display ) . "</h2>"
 			. '<ul>' .$r .'</ul>' . $more;
 	}
 
@@ -161,7 +161,7 @@ function wfSajaxSearch( $term ) {
  * Called for AJAX watch/unwatch requests.
  * @param $pagename Prefixed title string for page to watch/unwatch
  * @param $watch String 'w' to watch, 'u' to unwatch
- * @return String '<w#>' or '<u#>' on successful watch or unwatch, 
+ * @return String '<w#>' or '<u#>' on successful watch or unwatch,
  *   respectively, followed by an HTML message to display in the alert box; or
  *   '<err#>' on error
  */
@@ -169,7 +169,7 @@ function wfAjaxWatch($pagename = "", $wa
 	if(wfReadOnly()) {
 		// redirect to action=(un)watch, which will display the database lock
 		// message
-		return '<err#>'; 
+		return '<err#>';
 	}
 
 	if('w' !== $watch && 'u' !== $watch) {
@@ -206,4 +206,3 @@ function wfAjaxWatch($pagename = "", $wa
 		return '<u#>'.wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() );
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/AjaxResponse.php 1:1.13.0-1/includes/AjaxResponse.php
--- 1:1.12.0-2/includes/AjaxResponse.php	2008-03-20 22:08:49.000000000 +0000
+++ 1:1.13.0-1/includes/AjaxResponse.php	2008-05-20 18:13:28.000000000 +0100
@@ -1,11 +1,16 @@
 <?php
+/**
+ * @file
+ * @ingroup Ajax
+ */
+
 if( !defined( 'MEDIAWIKI' ) ) {
 	die( 1 );
 }
 
 /**
  * @todo document
- * @addtogroup Ajax
+ * @ingroup Ajax
  */
 class AjaxResponse {
 
@@ -99,7 +104,7 @@ class AjaxResponse {
 
 		if ( $this->mCacheDuration ) {
 
-			# If squid caches are configured, tell them to cache the response, 
+			# If squid caches are configured, tell them to cache the response,
 			# and tell the client to always check with the squid. Otherwise,
 			# tell the client to use a cached copy, without a way to purge it.
 
@@ -220,4 +225,3 @@ class AjaxResponse {
 		return true;
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiBase.php 1:1.13.0-1/includes/api/ApiBase.php
--- 1:1.12.0-2/includes/api/ApiBase.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiBase.php	2008-06-15 21:37:28.000000000 +0100
@@ -26,15 +26,15 @@
 /**
  * This abstract class implements many basic API functions, and is the base of all API classes.
  * The class functions are divided into several areas of functionality:
- * 
+ *
  * Module parameters: Derived classes can define getAllowedParams() to specify which parameters to expect,
  * 	how to parse and validate them.
- * 
+ *
  * Profiling: various methods to allow keeping tabs on various tasks and their time costs
- * 
+ *
  * Self-documentation: code to allow api to document its own state.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 abstract class ApiBase {
 
@@ -68,18 +68,18 @@ abstract class ApiBase {
 	 *****************************************************************************/
 
 	/**
-	 * Evaluates the parameters, performs the requested query, and sets up the 
-	 * result. Concrete implementations of ApiBase must override this method to 
+	 * Evaluates the parameters, performs the requested query, and sets up the
+	 * result. Concrete implementations of ApiBase must override this method to
 	 * provide whatever functionality their module offers. Implementations must
 	 * not produce any output on their own and are not expected to handle any
-	 * errors. 
+	 * errors.
 	 *
 	 * The execute method will be invoked directly by ApiMain immediately before
 	 * the result of the module is output. Aside from the constructor, implementations
 	 * should assume that no other methods will be called externally on the module
 	 * before the result is processed.
 	 *
-	 * The result data should be stored in the result object referred to by 
+	 * The result data should be stored in the result object referred to by
 	 * "getResult()". Refer to ApiResult.php for details on populating a result
 	 * object.
 	 */
@@ -93,21 +93,21 @@ abstract class ApiBase {
 	public abstract function getVersion();
 
 	/**
-	 * Get the name of the module being executed by this instance 
+	 * Get the name of the module being executed by this instance
 	 */
 	public function getModuleName() {
 		return $this->mModuleName;
 	}
 
 	/**
-	 * Get parameter prefix (usually two letters or an empty string). 
+	 * Get parameter prefix (usually two letters or an empty string).
 	 */
 	public function getModulePrefix() {
 		return $this->mModulePrefix;
-	}	
+	}
 
 	/**
-	 * Get the name of the module as shown in the profiler log 
+	 * Get the name of the module as shown in the profiler log
 	 */
 	public function getModuleProfileName($db = false) {
 		if ($db)
@@ -124,7 +124,7 @@ abstract class ApiBase {
 	}
 
 	/**
-	 * Returns true if this module is the main module ($this === $this->mMainModule), 
+	 * Returns true if this module is the main module ($this === $this->mMainModule),
 	 * false otherwise.
 	 */
 	public function isMain() {
@@ -151,10 +151,17 @@ abstract class ApiBase {
 	}
 
 	/**
-	 * Set warning section for this module. Users should monitor this section to 
+	 * Set warning section for this module. Users should monitor this section to
 	 * notice any changes in API.
 	 */
 	public function setWarning($warning) {
+		# If there is a warning already, append it to the existing one
+		$data =& $this->getResult()->getData();
+		if(isset($data['warnings'][$this->getModuleName()]))
+		{
+			$warning = "{$data['warnings'][$this->getModuleName()]['*']}\n$warning";
+			unset($data['warnings'][$this->getModuleName()]);
+		}
 		$msg = array();
 		ApiResult :: setContent($msg, $warning);
 		$this->getResult()->addValue('warnings', $this->getModuleName(), $msg);
@@ -163,7 +170,7 @@ abstract class ApiBase {
 	/**
 	 * If the module may only be used with a certain format module,
 	 * it should override this method to return an instance of that formatter.
-	 * A value of null means the default format will be used.  
+	 * A value of null means the default format will be used.
 	 */
 	public function getCustomPrinter() {
 		return null;
@@ -186,6 +193,9 @@ abstract class ApiBase {
 				);
 			$msg = $lnPrfx . implode($lnPrfx, $msg) . "\n";
 
+			if ($this->mustBePosted())
+				$msg .= "\nThis module only accepts POST requests.\n";
+
 			// Parameters
 			$paramsMsg = $this->makeHelpMsgParameters();
 			if ($paramsMsg !== false) {
@@ -207,7 +217,7 @@ abstract class ApiBase {
 				$versions = $this->getVersion();
 				$pattern = '(\$.*) ([0-9a-z_]+\.php) (.*\$)';
 				$replacement = '\\0' . "\n    " . 'http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/api/\\2';
-				
+
 				if (is_array($versions)) {
 					foreach ($versions as &$v)
 						$v = eregi_replace($pattern, $replacement, $v);
@@ -223,7 +233,7 @@ abstract class ApiBase {
 		return $msg;
 	}
 
-	/** 
+	/**
 	 * Generates the parameter descriptions for this module, to be displayed in the
 	 * module's help.
 	 */
@@ -239,7 +249,7 @@ abstract class ApiBase {
 				if (is_array($desc))
 					$desc = implode($paramPrefix, $desc);
 
-				$type = $paramSettings[self :: PARAM_TYPE];
+				$type = isset($paramSettings[self :: PARAM_TYPE])? $paramSettings[self :: PARAM_TYPE] : null;
 				if (isset ($type)) {
 					if (isset ($paramSettings[self :: PARAM_ISMULTI]))
 						$prompt = 'Values (separate with \'|\'): ';
@@ -274,7 +284,7 @@ abstract class ApiBase {
 										$intRangeStr = "The value must be no more than {$paramSettings[self :: PARAM_MAX]}";
 									else
 										$intRangeStr = "The value must be between {$paramSettings[self :: PARAM_MIN]} and {$paramSettings[self :: PARAM_MAX]}";
-										
+
 									$desc .= $paramPrefix . $intRangeStr;
 								}
 								break;
@@ -324,7 +334,7 @@ abstract class ApiBase {
 
 	/**
 	 * This method mangles parameter name based on the prefix supplied to the constructor.
-	 * Override this method to change parameter name during runtime 
+	 * Override this method to change parameter name during runtime
 	 */
 	public function encodeParamName($paramName) {
 		return $this->mModulePrefix . $paramName;
@@ -348,12 +358,12 @@ abstract class ApiBase {
 	}
 
 	/**
-	 * Get a value for the given parameter 
+	 * Get a value for the given parameter
 	 */
-	protected function getParameter($paramName) {
+	protected function getParameter($paramName, $parseMaxLimit = true) {
 		$params = $this->getAllowedParams();
 		$paramSettings = $params[$paramName];
-		return $this->getParameterFromSettings($paramName, $paramSettings);
+		return $this->getParameterFromSettings($paramName, $paramSettings, $parseMaxLimit);
 	}
 
 	/**
@@ -435,7 +445,7 @@ abstract class ApiBase {
 						$value = is_array($value) ? array_map('intval', $value) : intval($value);
 						$min = isset ($paramSettings[self :: PARAM_MIN]) ? $paramSettings[self :: PARAM_MIN] : null;
 						$max = isset ($paramSettings[self :: PARAM_MAX]) ? $paramSettings[self :: PARAM_MAX] : null;
-						
+
 						if (!is_null($min) || !is_null($max)) {
 							$values = is_array($value) ? $value : array($value);
 							foreach ($values as $v) {
@@ -495,24 +505,41 @@ abstract class ApiBase {
 	/**
 	* Return an array of values that were given in a 'a|b|c' notation,
 	* after it optionally validates them against the list allowed values.
-	* 
+	*
 	* @param valueName - The name of the parameter (for error reporting)
 	* @param value - The value being parsed
 	* @param allowMultiple - Can $value contain more than one value separated by '|'?
 	* @param allowedValues - An array of values to check against. If null, all values are accepted.
-	* @return (allowMultiple ? an_array_of_values : a_single_value) 
+	* @return (allowMultiple ? an_array_of_values : a_single_value)
 	*/
 	protected function parseMultiValue($valueName, $value, $allowMultiple, $allowedValues) {
-		$valuesList = explode('|', $value);
+		if( trim($value) === "" )
+			return array();
+		$sizeLimit = $this->mMainModule->canApiHighLimits() ? 501 : 51;
+		$valuesList = explode('|', $value,$sizeLimit);
+		if( count($valuesList) == $sizeLimit ) {
+			$junk = array_pop($valuesList); // kill last jumbled param
+		}
 		if (!$allowMultiple && count($valuesList) != 1) {
 			$possibleValues = is_array($allowedValues) ? "of '" . implode("', '", $allowedValues) . "'" : '';
 			$this->dieUsage("Only one $possibleValues is allowed for parameter '$valueName'", "multival_$valueName");
 		}
 		if (is_array($allowedValues)) {
-			$unknownValues = array_diff($valuesList, $allowedValues);
-			if ($unknownValues) {
-				$this->dieUsage('Unrecognised value' . (count($unknownValues) > 1 ? "s" : "") . " for parameter '$valueName'", "unknown_$valueName");
+			# Check for unknown values
+			$unknown = array_diff($valuesList, $allowedValues);
+			if(!empty($unknown))
+			{
+				if($allowMultiple)
+				{
+					$s = count($unknown) > 1 ? "s" : "";
+					$vals = implode(", ", $unknown); 
+					$this->setWarning("Unrecognized value$s for parameter '$valueName': $vals");
+				}
+				else
+					$this->dieUsage("Unrecognized value for parameter '$valueName': {$valuesList[0]}", "unknown_$valueName");
 			}
+			# Now throw them out
+			$valuesList = array_intersect($valuesList, $allowedValues);
 		}
 
 		return $allowMultiple ? $valuesList : $valuesList[0];
@@ -544,12 +571,12 @@ abstract class ApiBase {
 	}
 
 	/**
-	 * Call main module's error handler 
+	 * Call main module's error handler
 	 */
 	public function dieUsage($description, $errorCode, $httpRespCode = 0) {
 		throw new UsageException($description, $this->encodeParamName($errorCode), $httpRespCode);
 	}
-	
+
 	/**
 	 * Array that maps message keys to error messages. $1 and friends are replaced.
 	 */
@@ -557,7 +584,7 @@ abstract class ApiBase {
 		// This one MUST be present, or dieUsageMsg() will recurse infinitely
 		'unknownerror' => array('code' => 'unknownerror', 'info' => "Unknown error: ``\$1''"),
 		'unknownerror-nocode' => array('code' => 'unknownerror', 'info' => 'Unknown error'),
-		
+
 		// Messages from Title::getUserPermissionsErrors()
 		'ns-specialprotected' => array('code' => 'unsupportednamespace', 'info' => "Pages in the Special namespace can't be edited"),
 		'protectedinterface' => array('code' => 'protectednamespace-interface', 'info' => "You're not allowed to edit interface messages"),
@@ -578,11 +605,11 @@ abstract class ApiBase {
 		'confirmedittext' => array('code' => 'confirmemail', 'info' => "You must confirm your e-mail address before you can edit"),
 		'blockedtext' => array('code' => 'blocked', 'info' => "You have been blocked from editing"),
 		'autoblockedtext' => array('code' => 'autoblocked', 'info' => "Your IP address has been blocked automatically, because it was used by a blocked user"),
-		
+
 		// Miscellaneous interface messages
 		'actionthrottledtext' => array('code' => 'ratelimited', 'info' => "You've exceeded your rate limit. Please wait some time and try again"),
 		'alreadyrolled' => array('code' => 'alreadyrolled', 'info' => "The page you tried to rollback was already rolled back"),
-		'cantrollback' => array('code' => 'onlyauthor', 'info' => "The page you tried to rollback only has one author"), 
+		'cantrollback' => array('code' => 'onlyauthor', 'info' => "The page you tried to rollback only has one author"),
 		'readonlytext' => array('code' => 'readonly', 'info' => "The wiki is currently in read-only mode"),
 		'sessionfailure' => array('code' => 'badtoken', 'info' => "Invalid token"),
 		'cannotdelete' => array('code' => 'cantdelete', 'info' => "Couldn't delete ``\$1''. Maybe it was deleted already by someone else"),
@@ -593,6 +620,8 @@ abstract class ApiBase {
 		'protectedpage' => array('code' => 'protectedpage', 'info' => "You don't have permission to perform this move"),
 		'hookaborted' => array('code' => 'hookaborted', 'info' => "The modification you tried to make was aborted by an extension hook"),
 		'cantmove-titleprotected' => array('code' => 'protectedtitle', 'info' => "The destination article has been protected from creation"),
+		'imagenocrossnamespace' => array('code' => 'nonfilenamespace', 'info' => "Can't move a file to a non-file namespace"),
+		'imagetypemismatch' => array('code' => 'filetypemismatch', 'info' => "The new file extension doesn't match its type"),
 		// 'badarticleerror' => shouldn't happen
 		// 'badtitletext' => shouldn't happen
 		'ip_range_invalid' => array('code' => 'invalidrange', 'info' => "Invalid IP range"),
@@ -603,7 +632,7 @@ abstract class ApiBase {
 		'ipb_already_blocked' => array('code' => 'alreadyblocked', 'info' => "The user you tried to block was already blocked"),
 		'ipb_blocked_as_range' => array('code' => 'blockedasrange', 'info' => "IP address ``\$1'' was blocked as part of range ``\$2''. You can't unblock the IP invidually, but you can unblock the range as a whole."),
 		'ipb_cant_unblock' => array('code' => 'cantunblock', 'info' => "The block you specified was not found. It may have been unblocked already"),
-		
+
 		// API-specific messages
 		'missingparam' => array('code' => 'no$1', 'info' => "The \$1 parameter must be set"),
 		'invalidtitle' => array('code' => 'invalidtitle', 'info' => "Bad title ``\$1''"),
@@ -616,12 +645,28 @@ abstract class ApiBase {
 		'canthide' => array('code' => 'canthide', 'info' => "You don't have permission to hide user names from the block log"),
 		'cantblock-email' => array('code' => 'cantblock-email', 'info' => "You don't have permission to block users from sending e-mail through the wiki"),
 		'unblock-notarget' => array('code' => 'notarget', 'info' => "Either the id or the user parameter must be set"),
-		'unblock-idanduser' => array('code' => 'idanduser', 'info' => "The id and user parameters can\'t be used together"),
+		'unblock-idanduser' => array('code' => 'idanduser', 'info' => "The id and user parameters can't be used together"),
 		'cantunblock' => array('code' => 'permissiondenied', 'info' => "You don't have permission to unblock users"),
 		'cannotundelete' => array('code' => 'cantundelete', 'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already"),
 		'permdenied-undelete' => array('code' => 'permissiondenied', 'info' => "You don't have permission to restore deleted revisions"),
+		'createonly-exists' => array('code' => 'articleexists', 'info' => "The article you tried to create has been created already"),
+		'nocreate-missing' => array('code' => 'missingtitle', 'info' => "The article you tried to edit doesn't exist"),
+
+		// ApiEditPage messages
+		'noimageredirect-anon' => array('code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects"),
+		'noimageredirect-logged' => array('code' => 'noimageredirect', 'info' => "You don't have permission to create image redirects"),
+		'spamdetected' => array('code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: ``\$1''"),
+		'filtered' => array('code' => 'filtered', 'info' => "The filter callback function refused your edit"),
+		'contenttoobig' => array('code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 bytes"),
+		'noedit-anon' => array('code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages"),
+		'noedit' => array('code' => 'noedit', 'info' => "You don't have permission to edit pages"),
+		'wasdeleted' => array('code' => 'pagedeleted', 'info' => "The page has been deleted since you fetched its timestamp"),
+		'blankpage' => array('code' => 'emptypage', 'info' => "Creating new, empty pages is not allowed"),
+		'editconflict' => array('code' => 'editconflict', 'info' => "Edit conflict detected"),
+		'hashcheckfailed' => array('code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect"),
+		'missingtext' => array('code' => 'notext', 'info' => "One of the text, appendtext and prependtext parameters must be set"),
 	);
-	
+
 	/**
 	 * Output the error message related to a certain array
 	 * @param array $error Element of a getUserPermissionsErrors()
@@ -654,7 +699,7 @@ abstract class ApiBase {
 	public function isEditMode() {
 		return false;
 	}
-	
+
 	/**
 	 * Indicates whether this module must be called with a POST request
 	 */
@@ -694,7 +739,7 @@ abstract class ApiBase {
 
 	/**
 	 * When modules crash, sometimes it is needed to do a profileOut() regardless
-	 * of the profiling state the module was in. This method does such cleanup. 
+	 * of the profiling state the module was in. This method does such cleanup.
 	 */
 	public function safeProfileOut() {
 		if ($this->mTimeIn !== 0) {
@@ -755,7 +800,7 @@ abstract class ApiBase {
 			ApiBase :: dieDebug(__METHOD__, 'called without calling profileDBOut() first');
 		return $this->mDBTime;
 	}
-	
+
 	public static function debugPrint($value, $name = 'unknown', $backtrace = false) {
 		print "\n\n<pre><b>Debuging value '$name':</b>\n\n";
 		var_export($value);
@@ -769,7 +814,6 @@ abstract class ApiBase {
 	 * Returns a String that identifies the version of this class.
 	 */
 	public static function getBaseVersion() {
-		return __CLASS__ . ': $Id: ApiBase.php 31259 2008-02-25 14:14:55Z catrope $';
-	} 
+		return __CLASS__ . ': $Id: ApiBase.php 36309 2008-06-15 20:37:28Z catrope $';
+	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiBlock.php 1:1.13.0-1/includes/api/ApiBlock.php
--- 1:1.12.0-2/includes/api/ApiBlock.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiBlock.php	2008-05-27 11:18:28.000000000 +0100
@@ -31,7 +31,7 @@ if (!defined('MEDIAWIKI')) {
 * API module that facilitates the blocking of users. Requires API write mode
 * to be enabled.
 *
- * @addtogroup API
+ * @ingroup API
  */
 class ApiBlock extends ApiBase {
 
@@ -87,17 +87,15 @@ class ApiBlock extends ApiBase {
 		$form->BlockEmail = $params['noemail'];
 		$form->BlockHideName = $params['hidename'];
 
-		$dbw = wfGetDb(DB_MASTER);
-		$dbw->begin();
+		$userID = $expiry = null;
 		$retval = $form->doBlock($userID, $expiry);
 		if(!empty($retval))
 			// We don't care about multiple errors, just report one of them
 			$this->dieUsageMsg($retval);
 
-		$dbw->commit();
 		$res['user'] = $params['user'];
 		$res['userID'] = $userID;
-		$res['expiry'] = ($expiry == Block::infinity() ? 'infinite' : $expiry);
+		$res['expiry'] = ($expiry == Block::infinity() ? 'infinite' : wfTimestamp(TS_ISO_8601, $expiry));
 		$res['reason'] = $params['reason'];
 		if($params['anononly'])
 			$res['anononly'] = '';
@@ -159,6 +157,6 @@ class ApiBlock extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiBlock.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiBlock.php 35388 2008-05-27 10:18:28Z catrope $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiChangeRights.php 1:1.13.0-1/includes/api/ApiChangeRights.php
--- 1:1.12.0-2/includes/api/ApiChangeRights.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiChangeRights.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,155 +0,0 @@
-<?php
-
-/*
- * Created on Sep 11, 2007
- * API for MediaWiki 1.8+
- *
- * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- * http://www.gnu.org/copyleft/gpl.html
- */
-
-if (!defined('MEDIAWIKI')) {
-	// Eclipse helper - will be ignored in production
-	require_once ("ApiBase.php");
-}
-
-/** 
- * API module that facilitates the changing of user rights. The API eqivalent of
- * Special:Userrights. Requires API write mode to be enabled.
- *
- * @addtogroup API
- */
-class ApiChangeRights extends ApiBase {
-
-	public function __construct($main, $action) {
-		parent :: __construct($main, $action);
-	}
-
-	public function execute() {
-		global $wgUser, $wgRequest;
-		$this->getMain()->requestWriteMode();
-		
-		if(wfReadOnly())
-			$this->dieUsage('The wiki is in read-only mode', 'readonly');
-		$params = $this->extractRequestParams();
-
-		$ur = new UserrightsPage($wgRequest);
-		$allowed = $ur->changeableGroups();
-		$res = array();
-		
-		$u = $ur->fetchUser_real($params['user']);
-		if(is_array($u))
-			switch($u[0])
-			{
-				case UserrightsPage::FETCHUSER_NO_INTERWIKI:
-					$this->dieUsage("You don't have permission to change users' rights on other wikis", 'nointerwiki');
-				case UserrightsPage::FETCHUSER_NO_DATABASE:
-					$this->dieUsage("Database ``{$u[1]}'' does not exist or is not local", 'nosuchdatabase');
-				case UserrightsPage::FETCHUSER_NO_USER:
-					$this->dieUsage("You specified an empty username, or none at all", 'emptyuser');
-				case UserrightsPage::FETCHUSER_NOSUCH_USERID:
-					$this->dieUsage("There is no user with ID ``{$u[1]}''", 'nosuchuserid');
-				case UserrightsPage::FETCHUSER_NOSUCH_USERNAME:
-					$this->dieUsage("There is no user with username ``{$u[1]}''", 'nosuchusername');
-				default:
-					$this->dieDebug(__METHOD__, "UserrightsPage::fetchUser_real() returned an unknown error ({$u[0]})");
-			}
-
-		$curgroups = $u->getGroups();
-		if($params['listgroups'])
-		{
-			$res['user'] = $u->getName();
-			$res['allowedgroups'] = $allowed;
-			$res['ingroups'] = $curgroups;
-			$this->getResult()->setIndexedTagName($res['ingroups'], 'group');
-			$this->getResult()->setIndexedTagName($res['allowedgroups']['add'], 'group');
-			$this->getResult()->setIndexedTagName($res['allowedgroups']['remove'], 'group');
-		}
-;
-		if($params['gettoken'])
-		{
-			$res['changerightstoken'] = $wgUser->editToken($u->getName());
-			$this->getResult()->addValue(null, $this->getModuleName(), $res);
-			return;
-		}
-
-		if(empty($params['addto']) && empty($params['rmfrom']))
-			$this->dieUsage('At least one of the addto and rmfrom parameters must be set', 'noaddrm');
-		if(is_null($params['token']))
-			$this->dieUsage('The token parameter must be set', 'notoken');
-		if(!$wgUser->matchEditToken($params['token'], $u->getName()))
-			$this->dieUsage('Invalid token', 'badtoken');
-
-		$dbw = wfGetDb(DB_MASTER);
-		$dbw->begin();
-		$ur->saveUserGroups($u, $params['rmfrom'], $params['addto'], $params['reason']);
-		$dbw->commit();
-		$res['user'] = $u->getName();
-		$res['addedto'] = (array)$params['addto'];
-		$res['removedfrom'] = (array)$params['rmfrom'];
-		$res['reason'] = $params['reason'];
-
-		$this->getResult()->setIndexedTagName($res['addedto'], 'group');
-		$this->getResult()->setIndexedTagName($res['removedfrom'], 'group');
-		$this->getResult()->addValue(null, $this->getModuleName(), $res);
-	}
-
-	public function getAllowedParams() {
-		return array (
-			'user' => null,
-			'token' => null,
-			'gettoken' => false,
-			'listgroups' => false,
-			'addto' => array(
-				ApiBase :: PARAM_ISMULTI => true,
-			),
-			'rmfrom' => array(
-				ApiBase :: PARAM_ISMULTI => true,
-			),
-			'reason' => ''
-		);
-	}
-
-	public function getParamDescription() {
-		return array (
-			'user' => 'The user you want to add to or remove from groups.',
-			'token' => 'A changerights token previously obtained through the gettoken parameter.',
-			'gettoken' => 'Output a token. Note that the user parameter still has to be set.',
-			'listgroups' => 'List the groups the user is in, and the ones you can add them to and remove them from.',
-			'addto' => 'Pipe-separated list of groups to add this user to',
-			'rmfrom' => 'Pipe-separated list of groups to remove this user from',
-			'reason' => 'Reason for change (optional)'
-		);
-	}
-
-	public function getDescription() {
-		return array(
-			'Add or remove a user from certain groups.'
-		);
-	}
-
-	protected function getExamples() {
-		return array (
-			'api.php?action=changerights&user=Bob&gettoken&listgroups',
-			'api.php?action=changerights&user=Bob&token=123ABC&addto=sysop&reason=Promoting%20per%20RFA'
-		);
-	}
-
-	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiChangeRights.php 28216 2007-12-06 18:33:18Z vasilievvv $';
-	}
-}
diff -pruN 1:1.12.0-2/includes/api/ApiDelete.php 1:1.13.0-1/includes/api/ApiDelete.php
--- 1:1.12.0-2/includes/api/ApiDelete.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiDelete.php	2008-05-26 13:15:21.000000000 +0100
@@ -29,10 +29,10 @@ if (!defined('MEDIAWIKI')) {
 
 
 /**
- * API module that facilitates deleting pages. The API eqivalent of action=delete. 
+ * API module that facilitates deleting pages. The API eqivalent of action=delete.
  * Requires API write mode to be enabled.
  *
- * @addtogroup API
+ * @ingroup API
  */
 class ApiDelete extends ApiBase {
 
@@ -42,16 +42,16 @@ class ApiDelete extends ApiBase {
 
 	/**
 	 * Extracts the title, token, and reason from the request parameters and invokes
-	 * the local delete() function with these as arguments. It does not make use of 
-	 * the delete function specified by Article.php. If the deletion succeeds, the 
-	 * details of the article deleted and the reason for deletion are added to the 
+	 * the local delete() function with these as arguments. It does not make use of
+	 * the delete function specified by Article.php. If the deletion succeeds, the
+	 * details of the article deleted and the reason for deletion are added to the
 	 * result object.
 	 */
 	public function execute() {
 		global $wgUser;
 		$this->getMain()->requestWriteMode();
 		$params = $this->extractRequestParams();
-		
+
 		$titleObj = NULL;
 		if(!isset($params['title']))
 			$this->dieUsageMsg(array('missingparam', 'title'));
@@ -64,21 +64,45 @@ class ApiDelete extends ApiBase {
 		if(!$titleObj->exists())
 			$this->dieUsageMsg(array('notanarticle'));
 
-		$articleObj = new Article($titleObj);
 		$reason = (isset($params['reason']) ? $params['reason'] : NULL);
-		$dbw = wfGetDb(DB_MASTER);
-		$dbw->begin();
-		$retval = self::delete($articleObj, $params['token'], 	$reason);
-		
-		if(!empty($retval))
-			// We don't care about multiple errors, just report one of them
-			$this->dieUsageMsg(current($retval));
+		if ($titleObj->getNamespace() == NS_IMAGE) {
+			$retval = self::deletefile($params['token'], $titleObj, $params['oldimage'], $reason, false);
+			if(!empty($retval))
+				// We don't care about multiple errors, just report one of them
+				$this->dieUsageMsg(current($retval));
+		} else {
+			$articleObj = new Article($titleObj);
+			$retval = self::delete($articleObj, $params['token'], $reason);
+			
+			if(!empty($retval))
+				// We don't care about multiple errors, just report one of them
+				$this->dieUsageMsg(current($retval));
+			
+			if($params['watch'] || $wgUser->getOption('watchdeletion'))
+				$articleObj->doWatch();
+			else if($params['unwatch'])
+				$articleObj->doUnwatch();
+		}
 
-		$dbw->commit();
 		$r = array('title' => $titleObj->getPrefixedText(), 'reason' => $reason);
 		$this->getResult()->addValue(null, $this->getModuleName(), $r);
 	}
 
+	private static function getPermissionsError(&$title, $token) {
+		global $wgUser;
+		// Check wiki readonly
+		if (wfReadOnly()) return array(array('readonlytext'));
+		
+		// Check permissions
+		$errors = $title->getUserPermissionsErrors('delete', $wgUser);
+		if (count($errors) > 0) return $errors;
+		
+		// Check token
+		if(!$wgUser->matchEditToken($token))
+			return array(array('sessionfailure'));
+		return array();
+	}
+
 	/**
 	 * We have our own delete() function, since Article.php's implementation is split in two phases
 	 *
@@ -90,41 +114,67 @@ class ApiDelete extends ApiBase {
 	public static function delete(&$article, $token, &$reason = NULL)
 	{
 		global $wgUser;
-
-		// Check permissions
-		$errors = $article->mTitle->getUserPermissionsErrors('delete', $wgUser);
-		if(!empty($errors))
-			return $errors;
-		if(wfReadOnly())
-			return array(array('readonlytext'));
-		if($wgUser->isBlocked())
-			return array(array('blocked'));
-
-		// Check token
-		if(!$wgUser->matchEditToken($token))
-			return array(array('sessionfailure'));
+		
+		$errors = self::getPermissionsError($article->getTitle(), $token);
+		if (count($errors)) return $errors;
 
 		// Auto-generate a summary, if necessary
 		if(is_null($reason))
 		{
+			# Need to pass a throwaway variable because generateReason expects
+			# a reference
+			$hasHistory = false;
 			$reason = $article->generateReason($hasHistory);
 			if($reason === false)
 				return array(array('cannotdelete'));
 		}
+		
+		if (!wfRunHooks('ArticleDelete', array(&$article, &$wgUser, &$reason)))
+			$this->dieUsageMsg(array('hookaborted'));
 
 		// Luckily, Article.php provides a reusable delete function that does the hard work for us
-		if($article->doDeleteArticle($reason))
+		if($article->doDeleteArticle($reason)) {
+			wfRunHooks('ArticleDeleteComplete', array(&$article, &$wgUser, $reason, $article->getId()));
 			return array();
+		}
 		return array(array('cannotdelete', $article->mTitle->getPrefixedText()));
 	}
+
+	public static function deleteFile($token, &$title, $oldimage, &$reason = NULL, $suppress = false)
+	{
+		$errors = self::getPermissionsError($title, $token);
+		if (count($errors)) return $errors;
+
+		if( $oldimage && !FileDeleteForm::isValidOldSpec($oldimage) )
+			return array(array('invalidoldimage'));
+
+		$file = wfFindFile($title, false, FileRepo::FIND_IGNORE_REDIRECT);
+		$oldfile = false;
+		
+		if( $oldimage )
+			$oldfile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $oldimage );
+			
+		if( !FileDeleteForm::haveDeletableFile($file, $oldfile, $oldimage) )
+			return array(array('nofile'));
+
+		$status = FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, $suppress );
+				
+		if( !$status->isGood() )
+			return array(array('cannotdelete', $title->getPrefixedText()));
+			
+		return array();
+	}
 	
 	public function mustBePosted() { return true; }
-	
+
 	public function getAllowedParams() {
 		return array (
 			'title' => null,
 			'token' => null,
 			'reason' => null,
+			'watch' => false,
+			'unwatch' => false,
+			'oldimage' => null
 		);
 	}
 
@@ -132,7 +182,10 @@ class ApiDelete extends ApiBase {
 		return array (
 			'title' => 'Title of the page you want to delete.',
 			'token' => 'A delete token previously retrieved through prop=info',
-			'reason' => 'Reason for the deletion. If not set, an automatically generated reason will be used.'
+			'reason' => 'Reason for the deletion. If not set, an automatically generated reason will be used.',
+			'watch' => 'Add the page to your watchlist',
+			'unwatch' => 'Remove the page from your watchlist',
+			'oldimage' => 'The name of the old image to delete as provided by iiprop=archivename'
 		);
 	}
 
@@ -150,6 +203,6 @@ class ApiDelete extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiDelete.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiDelete.php 35350 2008-05-26 12:15:21Z simetrical $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiEditPage.php 1:1.13.0-1/includes/api/ApiEditPage.php
--- 1:1.12.0-2/includes/api/ApiEditPage.php	1970-01-01 01:00:00.000000000 +0100
+++ 1:1.13.0-1/includes/api/ApiEditPage.php	2008-06-15 21:37:28.000000000 +0100
@@ -0,0 +1,299 @@
+<?php
+
+/*
+ * Created on August 16, 2007
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2007 Iker Labarga <Firstname><Lastname>@gmail.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+    // Eclipse helper - will be ignored in production
+    require_once ("ApiBase.php");
+}
+
+/**
+ * A query module to list all external URLs found on a given set of pages.
+ *
+ * @ingroup API
+ */
+class ApiEditPage extends ApiBase {
+
+	public function __construct($query, $moduleName) {
+		parent :: __construct($query, $moduleName);
+	}
+
+	public function execute() {
+		global $wgUser;
+		$this->getMain()->requestWriteMode();
+
+		$params = $this->extractRequestParams();
+		if(is_null($params['title']))
+			$this->dieUsageMsg(array('missingparam', 'title'));
+		if(is_null($params['text']) && is_null($params['appendtext']) && is_null($params['prependtext']))
+			$this->dieUsageMsg(array('missingtext'));
+		if(is_null($params['token']))
+			$this->dieUsageMsg(array('missingparam', 'token'));
+		if(!$wgUser->matchEditToken($params['token']))
+			$this->dieUsageMsg(array('sessionfailure'));
+
+		$titleObj = Title::newFromText($params['title']);
+		if(!$titleObj)
+			$this->dieUsageMsg(array('invalidtitle', $params['title']));
+
+		if($params['createonly'] && $titleObj->exists())
+			$this->dieUsageMsg(array('createonly-exists'));
+		if($params['nocreate'] && !$titleObj->exists())
+			$this->dieUsageMsg(array('nocreate-missing'));
+
+		// Now let's check whether we're even allowed to do this
+		$errors = $titleObj->getUserPermissionsErrors('edit', $wgUser);
+		if(!$titleObj->exists())
+			$errors = array_merge($errors, $titleObj->getUserPermissionsErrors('create', $wgUser));
+		if(!empty($errors))
+			$this->dieUsageMsg($errors[0]);
+
+		$articleObj = new Article($titleObj);
+		$toMD5 = $params['text'];
+		if(!is_null($params['appendtext']) || !is_null($params['prependtext']))
+		{
+			$content = $articleObj->getContent();
+			$params['text'] = $params['prependtext'] . $content . $params['appendtext'];
+			$toMD5 = $params['prependtext'] . $params['appendtext'];
+		}
+
+		# See if the MD5 hash checks out
+		if(isset($params['md5']))
+			if(md5($toMD5) !== $params['md5'])
+				$this->dieUsageMsg(array('hashcheckfailed'));
+		
+		$ep = new EditPage($articleObj);
+		// EditPage wants to parse its stuff from a WebRequest
+		// That interface kind of sucks, but it's workable
+		$reqArr = array('wpTextbox1' => $params['text'],
+				'wpEdittoken' => $params['token'],
+				'wpIgnoreBlankSummary' => ''
+		);
+		if(!is_null($params['summary']))
+			$reqArr['wpSummary'] = $params['summary'];
+		# Watch out for basetimestamp == ''
+		# wfTimestamp() treats it as NOW, almost certainly causing an edit conflict
+		if(!is_null($params['basetimestamp']) && $params['basetimestamp'] != '')
+			$reqArr['wpEdittime'] = wfTimestamp(TS_MW, $params['basetimestamp']);
+		else
+			$reqArr['wpEdittime'] = $articleObj->getTimestamp();
+		# Fake wpStartime
+		$reqArr['wpStarttime'] = $reqArr['wpEdittime'];
+		if($params['minor'] || (!$params['notminor'] && $wgUser->getOption('minordefault')))
+			$reqArr['wpMinoredit'] = '';
+		if($params['recreate'])
+			$reqArr['wpRecreate'] = '';
+		if(!is_null($params['section']))
+		{
+			$section = intval($params['section']);
+			if($section == 0 && $params['section'] != '0' && $params['section'] != 'new')
+				$this->dieUsage("The section parameter must be set to an integer or 'new'", "invalidsection");
+			$reqArr['wpSection'] = $params['section'];
+		}
+
+		if($params['watch'])
+			$watch = true;
+		else if($params['unwatch'])
+			$watch = false;
+		else if($titleObj->userIsWatching())
+			$watch = true;
+		else if($wgUser->getOption('watchdefault'))
+			$watch = true;
+		else if($wgUser->getOption('watchcreations') && !$titleObj->exists())
+			$watch = true;
+		else
+			$watch = false;
+		if($watch)
+			$reqArr['wpWatchthis'] = '';
+
+		$req = new FauxRequest($reqArr, true);
+		$ep->importFormData($req);
+
+		# Run hooks
+		# Handle CAPTCHA parameters
+		global $wgRequest;
+		if(isset($params['captchaid']))
+			$wgRequest->data['wpCaptchaId'] = $params['captchaid'];
+		if(isset($params['captchaword']))
+			$wgRequest->data['wpCaptchaWord'] = $params['captchaword'];
+		$r = array();
+		if(!wfRunHooks('APIEditBeforeSave', array(&$ep, $ep->textbox1, &$r)))
+		{
+			if(!empty($r))
+			{
+				$r['result'] = "Failure";
+				$this->getResult()->addValue(null, $this->getModuleName(), $r);
+				return;
+			}
+			else
+				$this->dieUsageMsg(array('hookaborted'));
+		}
+
+		# Do the actual save
+		$oldRevId = $articleObj->getRevIdFetched();
+		$result = null;
+		# *Something* is setting $wgTitle to a title corresponding to "Msg",
+		# but that breaks API mode detection through is_null($wgTitle)
+		global $wgTitle;
+		$wgTitle = null;
+		# Fake $wgRequest for some hooks inside EditPage
+		# FIXME: This interface SUCKS
+		$oldRequest = $wgRequest;
+		$wgRequest = $req;
+
+		$retval = $ep->internalAttemptSave($result, $wgUser->isAllowed('bot') && $params['bot']);
+		$wgRequest = $oldRequest;
+		switch($retval)
+		{
+			case EditPage::AS_HOOK_ERROR:
+			case EditPage::AS_HOOK_ERROR_EXPECTED:
+				$this->dieUsageMsg(array('hookaborted'));
+			case EditPage::AS_IMAGE_REDIRECT_ANON:
+				$this->dieUsageMsg(array('noimageredirect-anon'));
+			case EditPage::AS_IMAGE_REDIRECT_LOGGED:
+				$this->dieUsageMsg(array('noimageredirect-logged'));
+			case EditPage::AS_SPAM_ERROR:
+				$this->dieUsageMsg(array('spamdetected', $result['spam']));
+			case EditPage::AS_FILTERING:
+				$this->dieUsageMsg(array('filtered'));
+			case EditPage::AS_BLOCKED_PAGE_FOR_USER:
+				$this->dieUsageMsg(array('blockedtext'));
+			case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
+			case EditPage::AS_CONTENT_TOO_BIG:
+				global $wgMaxArticleSize;
+				$this->dieUsageMsg(array('contenttoobig', $wgMaxArticleSize));
+			case EditPage::AS_READ_ONLY_PAGE_ANON:
+				$this->dieUsageMsg(array('noedit-anon'));
+			case EditPage::AS_READ_ONLY_PAGE_LOGGED:
+				$this->dieUsageMsg(array('noedit'));
+			case EditPage::AS_READ_ONLY_PAGE:
+				$this->dieUsageMsg(array('readonlytext'));
+			case EditPage::AS_RATE_LIMITED:
+				$this->dieUsageMsg(array('actionthrottledtext'));
+			case EditPage::AS_ARTICLE_WAS_DELETED:
+				$this->dieUsageMsg(array('wasdeleted'));
+			case EditPage::AS_NO_CREATE_PERMISSION:
+				$this->dieUsageMsg(array('nocreate-loggedin'));
+			case EditPage::AS_BLANK_ARTICLE:
+				$this->dieUsageMsg(array('blankpage'));
+			case EditPage::AS_CONFLICT_DETECTED:
+				$this->dieUsageMsg(array('editconflict'));
+			#case EditPage::AS_SUMMARY_NEEDED: Can't happen since we set wpIgnoreBlankSummary
+			#case EditPage::AS_TEXTBOX_EMPTY: Can't happen since we don't do sections
+			case EditPage::AS_END:
+				# This usually means some kind of race condition
+				# or DB weirdness occurred. Throw an unknown error here.
+				$this->dieUsageMsg(array('unknownerror', 'AS_END'));
+			case EditPage::AS_SUCCESS_NEW_ARTICLE:
+				$r['new'] = '';
+			case EditPage::AS_SUCCESS_UPDATE:
+				$r['result'] = "Success";
+				$r['pageid'] = $titleObj->getArticleID();
+				$r['title'] = $titleObj->getPrefixedText();
+				$newRevId = $titleObj->getLatestRevId();
+				if($newRevId == $oldRevId)
+					$r['nochange'] = '';
+				else
+				{
+					$r['oldrevid'] = $oldRevId;
+					$r['newrevid'] = $newRevId;
+				}
+				break;
+			default:
+				$this->dieUsageMsg(array('unknownerror', $retval));
+		}
+		$this->getResult()->addValue(null, $this->getModuleName(), $r);
+	}
+
+	public function mustBePosted() {
+		return true;
+	}
+
+	protected function getDescription() {
+		return 'Create and edit pages.';
+	}
+
+	protected function getAllowedParams() {
+		return array (
+			'title' => null,
+			'section' => null,
+			'text' => null,
+			'token' => null,
+			'summary' => null,
+			'minor' => false,
+			'notminor' => false,
+			'bot' => false,
+			'basetimestamp' => null,
+			'recreate' => false,
+			'createonly' => false,
+			'nocreate' => false,
+			'captchaword' => null,
+			'captchaid' => null,
+			'watch' => false,
+			'unwatch' => false,
+			'md5' => null,
+			'prependtext' => null,
+			'appendtext' => null,
+		);
+	}
+
+	protected function getParamDescription() {
+		return array (
+			'title' => 'Page title',
+			'section' => 'Section number. 0 for the top section, \'new\' for a new section',
+			'text' => 'Page content',
+			'token' => 'Edit token. You can get one of these through prop=info',
+			'summary' => 'Edit summary. Also section title when section=new',
+			'minor' => 'Minor edit',
+			'notminor' => 'Non-minor edit',
+			'bot' => 'Mark this edit as bot',
+			'basetimestamp' => array('Timestamp of the base revision (gotten through prop=revisions&rvprop=timestamp).',
+						'Used to detect edit conflicts; leave unset to ignore conflicts.'
+			),
+			'recreate' => 'Override any errors about the article having been deleted in the meantime',
+			'createonly' => 'Don\'t edit the page if it exists already',
+			'nocreate' => 'Throw an error if the page doesn\'t exist',
+			'watch' => 'Add the page to your watchlist',
+			'unwatch' => 'Remove the page from your watchlist',
+			'captchaid' => 'CAPTCHA ID from previous request',
+			'captchaword' => 'Answer to the CAPTCHA',
+			'md5' => array(	'The MD5 hash of the text parameter, or the prependtext and appendtext parameters concatenated.',
+				 	'If set, the edit won\'t be done unless the hash is correct'),
+			'prependtext' => array( 'Add this text to the beginning of the page. Overrides text.',
+						'Don\'t use together with section: that won\'t do what you expect.'),
+			'appendtext' => 'Add this text to the end of the page. Overrides text',
+		);
+	}
+
+	protected function getExamples() {
+		return array (
+			"Edit a page (anonymous user):",
+			"    api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\"
+		);
+	}
+
+	public function getVersion() {
+		return __CLASS__ . ': $Id: ApiEditPage.php 36309 2008-06-15 20:37:28Z catrope $';
+	}
+}
diff -pruN 1:1.12.0-2/includes/api/ApiEmailUser.php 1:1.13.0-1/includes/api/ApiEmailUser.php
--- 1:1.12.0-2/includes/api/ApiEmailUser.php	1970-01-01 01:00:00.000000000 +0100
+++ 1:1.13.0-1/includes/api/ApiEmailUser.php	2008-06-02 21:15:22.000000000 +0100
@@ -0,0 +1,114 @@
+<?php
+
+/*
+ * Created on June 1, 2008
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2008 Bryan Tong Minh <Bryan.TongMinh@Gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+	// Eclipse helper - will be ignored in production
+	require_once ("ApiBase.php");
+}
+
+
+/**
+ * @ingroup API
+ */
+class ApiEmailUser extends ApiBase {
+
+	public function __construct($main, $action) {
+		parent :: __construct($main, $action);
+	}
+
+	public function execute() {
+		global $wgUser;
+		$this->getMain()->requestWriteMode();
+		$params = $this->extractRequestParams();
+		
+		// Check required parameters
+		if ( !isset( $params['target'] ) )
+			$this->dieUsageMsg( array( 'missingparam', 'target' ) );
+		if ( !isset( $params['text'] ) )
+			$this->dieUsageMsg( array( 'missingparam', 'text' ) );
+		if ( !isset( $params['token'] ) )
+			$this->dieUsageMsg( array( 'missingparam', 'token' ) );	
+		
+		// Validate target 
+		$targetUser = EmailUserForm::validateEmailTarget( $params['target'] );
+		if ( !( $targetUser instanceof User ) )
+			$this->dieUsageMsg( array( $targetUser[0] ) );
+		
+		// Check permissions
+		$error = EmailUserForm::getPermissionsError( $wgUser, $params['token'] );
+		if ( $error )
+			$this->dieUsageMsg( array( $error[0] ) );
+		
+			
+		$form = new EmailUserForm( $targetUser, $params['text'], $params['subject'], $params['ccme'] );
+		$retval = $form->doSubmit();
+		if ( is_null( $retval ) )
+			$result = array( 'result' => 'Success' );
+		else
+			$result = array( 'result' => 'Failure',
+				 'message' => $retval->getMessage() );
+		
+		$this->getResult()->addValue( null, $this->getModuleName(), $result );
+	}
+	
+	public function mustBePosted() { return true; }
+
+	public function getAllowedParams() {
+		return array (
+			'target' => null,
+			'subject' => null,
+			'text' => null,
+			'token' => null,
+			'ccme' => false,
+		);
+	}
+
+	public function getParamDescription() {
+		return array (
+			'target' => 'User to send email to',
+			'subject' => 'Subject header',
+			'text' => 'Mail body',
+			// FIXME: How to properly get a token?
+			'token' => 'A token previously acquired via prop=info',
+			'ccme' => 'Send a copy of this mail to me',
+		);
+	}
+
+	public function getDescription() {
+		return array(
+			'Emails a user.'
+		);
+	}
+
+	protected function getExamples() {
+		return array (
+			'api.php?action=emailuser&target=WikiSysop&text=Content'
+		);
+	}
+
+	public function getVersion() {
+		return __CLASS__ . ': $Id: $';
+	}
+}	
+	
\ No newline at end of file
diff -pruN 1:1.12.0-2/includes/api/ApiExpandTemplates.php 1:1.13.0-1/includes/api/ApiExpandTemplates.php
--- 1:1.12.0-2/includes/api/ApiExpandTemplates.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiExpandTemplates.php	2008-05-20 18:13:28.000000000 +0100
@@ -33,7 +33,7 @@ if (!defined('MEDIAWIKI')) {
  * any templates in a provided string, and returns the result of this expansion
  * to the caller.
  *
- * @addtogroup API
+ * @ingroup API
  */
 class ApiExpandTemplates extends ApiBase {
 
@@ -43,22 +43,35 @@ class ApiExpandTemplates extends ApiBase
 
 	public function execute() {
 		// Get parameters
-		$params = $this->extractRequestParams();
-		$text = $params['text'];
-		$title = $params['title'];
+		extract( $this->extractRequestParams() );
 		$retval = '';
 
 		//Create title for parser
-		$title_obj = Title :: newFromText($params['title']);
+		$title_obj = Title :: newFromText( $title );
 		if(!$title_obj)
-			$title_obj = Title :: newFromText("API");	//  Default title is "API". For example, ExpandTemplates uses "ExpendTemplates" for it
+			$title_obj = Title :: newFromText( "API" );	//  Default title is "API". For example, ExpandTemplates uses "ExpendTemplates" for it
+
+		$result = $this->getResult();
 
 		// Parse text
 		global $wgParser;
-		$retval = $wgParser->preprocess( $text, $title_obj, new ParserOptions() );
+		$options = new ParserOptions();
+		if ( $generatexml )
+		{
+			$wgParser->startExternalParse( $title_obj, $options, OT_PREPROCESS );
+			$dom = $wgParser->preprocessToDom( $text );
+			if ( is_callable( array( $dom, 'saveXML' ) ) ) {
+				$xml = $dom->saveXML();
+			} else {
+				$xml = $dom->__toString();
+			}
+			$xml_result = array();
+			$result->setContent( $xml_result, $xml );
+            $result->addValue( null, 'parsetree', $xml_result);
+		}
+		$retval = $wgParser->preprocess( $text, $title_obj, $options );
 
 		// Return result
-		$result = $this->getResult();
 		$retval_array = array();
 		$result->setContent( $retval_array, $retval );
 		$result->addValue( null, $this->getModuleName(), $retval_array );
@@ -66,10 +79,11 @@ class ApiExpandTemplates extends ApiBase
 
 	public function getAllowedParams() {
 		return array (
-			'title' => array( 
+			'title' => array(
 				ApiBase :: PARAM_DFLT => 'API',
 			),
-			'text' => null
+			'text' => null,
+			'generatexml' => false,
 		);
 	}
 
@@ -77,6 +91,7 @@ class ApiExpandTemplates extends ApiBase
 		return array (
 			'text' => 'Wikitext to convert',
 			'title' => 'Title of page',
+			'generatexml' => 'Generate XML parse tree',
 		);
 	}
 
@@ -91,7 +106,6 @@ class ApiExpandTemplates extends ApiBase
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiExpandTemplates.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiExpandTemplates.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiFeedWatchlist.php 1:1.13.0-1/includes/api/ApiFeedWatchlist.php
--- 1:1.12.0-2/includes/api/ApiFeedWatchlist.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFeedWatchlist.php	2008-05-20 18:13:28.000000000 +0100
@@ -32,8 +32,8 @@ if (!defined('MEDIAWIKI')) {
  * This action allows users to get their watchlist items in RSS/Atom formats.
  * When executed, it performs a nested call to the API to get the needed data,
  * and formats it in a proper format.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiFeedWatchlist extends ApiBase {
 
@@ -53,15 +53,15 @@ class ApiFeedWatchlist extends ApiBase {
 	 * Wrap the result as an RSS/Atom feed.
 	 */
 	public function execute() {
-		
-		global $wgFeedClasses, $wgSitename, $wgContLanguageCode;
+
+		global $wgFeedClasses, $wgFeedLimit, $wgSitename, $wgContLanguageCode;
 
 		try {
 			$params = $this->extractRequestParams();
-			
+
 			// limit to the number of hours going from now back
 			$endTime = wfTimestamp(TS_MW, time() - intval($params['hours'] * 60 * 60));
-	
+
 			$dbr = wfGetDB( DB_SLAVE );
 			// Prepare parameters for nested request
 			$fauxReqArr = array (
@@ -72,7 +72,7 @@ class ApiFeedWatchlist extends ApiBase {
 				'wlprop' => 'title|user|comment|timestamp',
 				'wldir' => 'older',		// reverse order - from newest to oldest
 				'wlend' => $dbr->timestamp($endTime),	// stop at this time
-				'wllimit' => 50
+				'wllimit' => (50 > $wgFeedLimit) ? $wgFeedLimit : 50
 			);
 
 			// Check for 'allrev' parameter, and if found, show all revisions to each page on wl.
@@ -80,35 +80,35 @@ class ApiFeedWatchlist extends ApiBase {
 
 			// Create the request
 			$fauxReq = new FauxRequest ( $fauxReqArr );
-	
+
 			// Execute
 			$module = new ApiMain($fauxReq);
 			$module->execute();
 
 			// Get data array
 			$data = $module->getResultData();
-	
+
 			$feedItems = array ();
 			foreach ($data['query']['watchlist'] as $info) {
 				$feedItems[] = $this->createFeedItem($info);
 			}
-	
+
 			$feedTitle = $wgSitename . ' - ' . wfMsgForContent('watchlist') . ' [' . $wgContLanguageCode . ']';
 			$feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullUrl();
-	
+
 			$feed = new $wgFeedClasses[$params['feedformat']] ($feedTitle, htmlspecialchars(wfMsgForContent('watchlist')), $feedUrl);
-	
+
 			ApiFormatFeedWrapper :: setResult($this->getResult(), $feed, $feedItems);
 
 		} catch (Exception $e) {
 
 			// Error results should not be cached
 			$this->getMain()->setCacheMaxAge(0);
-	
+
 			$feedTitle = $wgSitename . ' - Error - ' . wfMsgForContent('watchlist') . ' [' . $wgContLanguageCode . ']';
 			$feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullUrl();
-	
-			$feedFormat = isset($params['feedformat']) ? $params['feedformat'] : 'rss'; 
+
+			$feedFormat = isset($params['feedformat']) ? $params['feedformat'] : 'rss';
 			$feed = new $wgFeedClasses[$feedFormat] ($feedTitle, htmlspecialchars(wfMsgForContent('watchlist')), $feedUrl);
 
 
@@ -175,7 +175,6 @@ class ApiFeedWatchlist extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiFeedWatchlist.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiFeedWatchlist.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiFormatBase.php 1:1.13.0-1/includes/api/ApiFormatBase.php
--- 1:1.12.0-2/includes/api/ApiFormatBase.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFormatBase.php	2008-06-10 16:20:22.000000000 +0100
@@ -30,12 +30,12 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * This is the abstract base class for API formatters.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 abstract class ApiFormatBase extends ApiBase {
 
-	private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp;
+	private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp, $mCleared;
 
 	/**
 	* Create a new instance of the formatter.
@@ -50,6 +50,7 @@ abstract class ApiFormatBase extends Api
 		else
 			$this->mFormat = $format;
 		$this->mFormat = strtoupper($this->mFormat);
+		$this->mCleared = false;
 	}
 
 	/**
@@ -62,7 +63,7 @@ abstract class ApiFormatBase extends Api
 	/**
 	 * If formatter outputs data results as is, the results must first be sanitized.
 	 * An XML formatter on the other hand uses special tags, such as "_element" for special handling,
-	 * and thus needs to override this function to return true.  
+	 * and thus needs to override this function to return true.
 	 */
 	public function getNeedsRawData() {
 		return false;
@@ -82,8 +83,8 @@ abstract class ApiFormatBase extends Api
 
 	/**
 	 * Returns true when an HTML filtering printer should be used.
-	 * The default implementation assumes that formats ending with 'fm' 
-	 * should be formatted in HTML. 
+	 * The default implementation assumes that formats ending with 'fm'
+	 * should be formatted in HTML.
 	 */
 	public function getIsHtml() {
 		return $this->mIsHtml;
@@ -111,7 +112,7 @@ abstract class ApiFormatBase extends Api
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 <html>
 <head>
-<?php if ($this->mUnescapeAmps) { 
+<?php if ($this->mUnescapeAmps) {
 ?>	<title>MediaWiki API</title>
 <?php } else {
 ?>	<title>MediaWiki API Result</title>
@@ -127,7 +128,7 @@ abstract class ApiFormatBase extends Api
 <small>
 You are looking at the HTML representation of the <?php echo( $this->mFormat ); ?> format.<br/>
 HTML is good for debugging, but probably is not suitable for your application.<br/>
-See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or 
+See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
 <a href='<?php echo( $script ); ?>'>API help</a> for more information.
 </small>
 <?php
@@ -166,7 +167,17 @@ See <a href='http://www.mediawiki.org/wi
 		if ($this->getIsHtml())
 			echo $this->formatHTML($text);
 		else
+		{
+			// For non-HTML output, clear all errors that might have been
+			// displayed if display_errors=On
+			// Do this only once, of course
+			if(!$this->mCleared)
+			{
+				ob_clean();
+				$this->mCleared = true;
+			}
 			echo $text;
+		}
 	}
 
 	/**
@@ -175,7 +186,7 @@ See <a href='http://www.mediawiki.org/wi
 	public function setHelp( $help = true ) {
 		$this->mHelp = true;
 	}
-	
+
 	/**
 	* Prety-print various elements in HTML format, such as xml tags and URLs.
 	* This method also replaces any '<' with &lt;
@@ -188,16 +199,17 @@ See <a href='http://www.mediawiki.org/wi
 		$text = preg_replace('/\&lt;(!--.*?--|.*?)\&gt;/', '<span style="color:blue;">&lt;\1&gt;</span>', $text);
 		// identify URLs
 		$protos = "http|https|ftp|gopher";
-		$text = ereg_replace("($protos)://[^ \\'\"()<\n]+", '<a href="\\0">\\0</a>', $text);
+		# This regex hacks around bug 13218 (&quot; included in the URL)
+		$text = preg_replace("#(($protos)://.*?)(&quot;)?([ \\'\"()<\n])#", '<a href="\\1">\\1</a>\\3\\4', $text);
 		// identify requests to api.php
-		$text = ereg_replace("api\\.php\\?[^ \\()<\n\t]+", '<a href="\\0">\\0</a>', $text);
+		$text = preg_replace("#api\\.php\\?[^ \\()<\n\t]+#", '<a href="\\0">\\0</a>', $text);
 		if( $this->mHelp ) {
 			// make strings inside * bold
 			$text = ereg_replace("\\*[^<>\n]+\\*", '<b>\\0</b>', $text);
 			// make strings inside $ italic
 			$text = ereg_replace("\\$[^<>\n]+\\$", '<b><i>\\0</i></b>', $text);
 		}
-		
+
 		/* Temporary fix for bad links in help messages. As a special case,
 		 * XML-escaped metachars are de-escaped one level in the help message
 		 * for legibility. Should be removed once we have completed a fully-html
@@ -220,13 +232,13 @@ See <a href='http://www.mediawiki.org/wi
 	}
 
 	public static function getBaseVersion() {
-		return __CLASS__ . ': $Id: ApiFormatBase.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiFormatBase.php 36153 2008-06-10 15:20:22Z tstarling $';
 	}
 }
 
 /**
- * This printer is used to wrap an instance of the Feed class 
- * @addtogroup API
+ * This printer is used to wrap an instance of the Feed class
+ * @ingroup API
  */
 class ApiFormatFeedWrapper extends ApiFormatBase {
 
@@ -275,13 +287,12 @@ class ApiFormatFeedWrapper extends ApiFo
 				$feed->outItem($item);
 			$feed->outFooter();
 		} else {
-			// Error has occured, print something usefull
-			// TODO: make this error more informative using ApiBase :: dieDebug() or similar
-			wfHttpError(500, 'Internal Server Error', '');
+			// Error has occured, print something useful
+			ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' );
 		}
 	}
-	
+
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiFormatBase.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiFormatBase.php 36153 2008-06-10 15:20:22Z tstarling $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiFormatDbg.php 1:1.13.0-1/includes/api/ApiFormatDbg.php
--- 1:1.12.0-2/includes/api/ApiFormatDbg.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFormatDbg.php	2008-05-20 18:13:28.000000000 +0100
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiFormatDbg extends ApiFormatBase {
 
@@ -53,7 +53,6 @@ class ApiFormatDbg extends ApiFormatBase
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiFormatPhp.php 23531 2007-06-29 01:19:14Z simetrical $';
+		return __CLASS__ . ': $Id: ApiFormatDbg.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiFormatJson_json.php 1:1.13.0-1/includes/api/ApiFormatJson_json.php
--- 1:1.12.0-2/includes/api/ApiFormatJson_json.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFormatJson_json.php	2008-05-20 18:13:28.000000000 +0100
@@ -45,12 +45,12 @@
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
-* @addtogroup  API
+* @ingroup     API
 * @author      Michal Migurski <mike-json@teczno.com>
 * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
 * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
 * @copyright   2005 Michal Migurski
-* @version     CVS: $Id: JSON.php,v 1.30 2006/03/08 16:10:20 migurski Exp $
+* @version     CVS: $Id: ApiFormatJson_json.php 35098 2008-05-20 17:13:28Z ialex $
 * @license     http://www.opensource.org/licenses/bsd-license.php
 * @see         http://pear.php.net/pepr/pepr-proposal-show.php?id=198
 */
@@ -111,7 +111,7 @@ define('SERVICES_JSON_SUPPRESS_ERRORS', 
  * $value = $json->decode($input);
  * </code>
  *
- * @addtogroup API
+ * @ingroup API
  */
 class Services_JSON
 {
@@ -257,7 +257,7 @@ class Services_JSON
     */
     function encode2($var)
     {
-        if ($this->pretty) { 
+        if ($this->pretty) {
             $close = "\n" . str_repeat("\t", $this->indent);
             $open = $close . "\t";
             $mid = ',' . $open;
@@ -426,7 +426,7 @@ class Services_JSON
                 $this->indent++;
                 $elements = array_map(array($this, 'encode2'), $var);
                 $this->indent--;
-                
+
                 foreach($elements as $element) {
                     if(Services_JSON::isError($element)) {
                         return $element;
@@ -703,7 +703,7 @@ class Services_JSON
                                 // element in an associative array,
                                 // for now
                                 $parts = array();
-                                
+
                                 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
                                     // "name":value pair
                                     $key = $this->decode($parts[1]);
@@ -815,7 +815,7 @@ class Services_JSON
 if (class_exists('PEAR_Error')) {
 
     /**
-     * @addtogroup API
+     * @ingroup API
      */
     class Services_JSON_Error extends PEAR_Error
     {
@@ -830,7 +830,7 @@ if (class_exists('PEAR_Error')) {
 
     /**
      * @todo Ultimately, this class shall be descended from PEAR_Error
-     * @addtogroup API
+     * @ingroup API
      */
     class Services_JSON_Error
     {
@@ -840,7 +840,4 @@ if (class_exists('PEAR_Error')) {
 
         }
     }
-
 }
-    
-
diff -pruN 1:1.12.0-2/includes/api/ApiFormatJson.php 1:1.13.0-1/includes/api/ApiFormatJson.php
--- 1:1.12.0-2/includes/api/ApiFormatJson.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFormatJson.php	2008-05-20 18:13:28.000000000 +0100
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiFormatJson extends ApiFormatBase {
 
@@ -54,7 +54,7 @@ class ApiFormatJson extends ApiFormatBas
 		$params = $this->extractRequestParams();
 		$callback = $params['callback'];
 		if(!is_null($callback)) {
-			$prefix = ereg_replace("[^_A-Za-z0-9]", "", $callback ) . "(";
+			$prefix = preg_replace("/[^][.\\'\\\"_A-Za-z0-9]/", "", $callback ) . "(";
 			$suffix = ")";
 		}
 
@@ -86,7 +86,6 @@ class ApiFormatJson extends ApiFormatBas
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiFormatJson.php 31484 2008-03-03 05:46:20Z brion $';
+		return __CLASS__ . ': $Id: ApiFormatJson.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiFormatPhp.php 1:1.13.0-1/includes/api/ApiFormatPhp.php
--- 1:1.12.0-2/includes/api/ApiFormatPhp.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFormatPhp.php	2008-05-20 18:13:28.000000000 +0100
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiFormatPhp extends ApiFormatBase {
 
@@ -50,7 +50,6 @@ class ApiFormatPhp extends ApiFormatBase
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiFormatPhp.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiFormatPhp.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiFormatTxt.php 1:1.13.0-1/includes/api/ApiFormatTxt.php
--- 1:1.12.0-2/includes/api/ApiFormatTxt.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFormatTxt.php	2008-05-20 18:13:28.000000000 +0100
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiFormatTxt extends ApiFormatBase {
 
@@ -53,7 +53,6 @@ class ApiFormatTxt extends ApiFormatBase
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiFormatPhp.php 23531 2007-06-29 01:19:14Z simetrical $';
+		return __CLASS__ . ': $Id: ApiFormatTxt.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiFormatWddx.php 1:1.13.0-1/includes/api/ApiFormatWddx.php
--- 1:1.12.0-2/includes/api/ApiFormatWddx.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFormatWddx.php	2008-05-20 18:13:28.000000000 +0100
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiFormatWddx extends ApiFormatBase {
 
@@ -85,7 +85,6 @@ class ApiFormatWddx extends ApiFormatBas
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiFormatWddx.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiFormatWddx.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiFormatXml.php 1:1.13.0-1/includes/api/ApiFormatXml.php
--- 1:1.12.0-2/includes/api/ApiFormatXml.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFormatXml.php	2008-07-04 23:44:57.000000000 +0100
@@ -29,11 +29,12 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiFormatXml extends ApiFormatBase {
 
 	private $mRootElemName = 'api';
+	private $mDoubleQuote = false;
 
 	public function __construct($main, $format) {
 		parent :: __construct($main, $format);
@@ -46,12 +47,15 @@ class ApiFormatXml extends ApiFormatBase
 	public function getNeedsRawData() {
 		return true;
 	}
-	
+
 	public function setRootElement($rootElemName) {
 		$this->mRootElemName = $rootElemName;
 	}
 
 	public function execute() {
+		$params = $this->extractRequestParams();
+		$this->mDoubleQuote = $params['xmldoublequote'];
+
 		$this->printText('<?xml version="1.0" encoding="utf-8"?>');
 		$this->recXmlPrint($this->mRootElemName, $this->getResultData(), $this->getIsHtml() ? -2 : null);
 	}
@@ -79,9 +83,10 @@ class ApiFormatXml extends ApiFormatBase
 
 		switch (gettype($elemValue)) {
 			case 'array' :
-
 				if (isset ($elemValue['*'])) {
 					$subElemContent = $elemValue['*'];
+					if ($this->mDoubleQuote)
+						$subElemContent = $this->doubleQuote($subElemContent);
 					unset ($elemValue['*']);
 				} else {
 					$subElemContent = null;
@@ -97,6 +102,9 @@ class ApiFormatXml extends ApiFormatBase
 				$indElements = array ();
 				$subElements = array ();
 				foreach ($elemValue as $subElemId => & $subElemValue) {
+					if (is_string($subElemValue) && $this->mDoubleQuote)
+						$subElemValue = $this->doubleQuote($subElemValue);
+
 					if (gettype($subElemId) === 'integer') {
 						$indElements[] = $subElemValue;
 						unset ($elemValue[$subElemId]);
@@ -136,12 +144,28 @@ class ApiFormatXml extends ApiFormatBase
 				break;
 		}
 	}
+	private function doubleQuote( $text ) {
+		return Sanitizer::encodeAttribute( $text );
+	}
+
+	public function getAllowedParams() {
+		return array (
+			'xmldoublequote' => false
+		);
+	}
+
+	public function getParamDescription() {
+		return array (
+			'xmldoublequote' => 'If specified, double quotes all attributes and content.',
+		);
+	}
+
+
 	public function getDescription() {
 		return 'Output data in XML format' . parent :: getDescription();
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiFormatXml.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiFormatXml.php 37075 2008-07-04 22:44:57Z brion $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiFormatYaml.php 1:1.13.0-1/includes/api/ApiFormatYaml.php
--- 1:1.12.0-2/includes/api/ApiFormatYaml.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFormatYaml.php	2008-05-20 18:13:28.000000000 +0100
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiFormatYaml extends ApiFormatBase {
 
@@ -50,7 +50,6 @@ class ApiFormatYaml extends ApiFormatBas
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiFormatYaml.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiFormatYaml.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiFormatYaml_spyc.php 1:1.13.0-1/includes/api/ApiFormatYaml_spyc.php
--- 1:1.12.0-2/includes/api/ApiFormatYaml_spyc.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiFormatYaml_spyc.php	2008-05-20 18:13:28.000000000 +0100
@@ -1,5 +1,5 @@
 <?php
-  /** 
+  /**
    * Spyc -- A Simple PHP YAML Class
    * @version 0.2.3 -- 2006-02-04
    * @author Chris Wanstrath <chris@ozmm.org>
@@ -8,29 +8,29 @@
    * @license http://www.opensource.org/licenses/mit-license.php MIT License
    */
 
-  /** 
+  /**
    * A node, used by Spyc for parsing YAML.
-   * @addtogroup API
+   * @ingroup API
    */
   class YAMLNode {
     /**#@+
      * @access public
      * @var string
-     */ 
+     */
     var $parent;
     var $id;
     /**#@-*/
-    /** 
+    /**
      * @access public
      * @var mixed
      */
     var $data;
-    /** 
+    /**
      * @access public
      * @var int
      */
     var $indent;
-    /** 
+    /**
      * @access public
      * @var bool
      */
@@ -58,17 +58,17 @@
    *   $parser = new Spyc;
    *   $array  = $parser->load($file);
    * </code>
-   * @addtogroup API
+   * @ingroup API
    */
   class Spyc {
-    
+
     /**
      * Load YAML into a PHP array statically
      *
-     * The load method, when supplied with a YAML stream (string or file), 
-     * will do its best to convert YAML in a file into a PHP array.  Pretty 
+     * The load method, when supplied with a YAML stream (string or file),
+     * will do its best to convert YAML in a file into a PHP array.  Pretty
      * simple.
-     *  Usage: 
+     *  Usage:
      *  <code>
      *   $array = Spyc::YAMLLoad('lucky.yml');
      *   print_r($array);
@@ -81,7 +81,7 @@
       $spyc = new Spyc;
       return $spyc->load($input);
     }
-    
+
     /**
      * Dump YAML from PHP array statically
      *
@@ -90,7 +90,7 @@
      * save the returned string as nothing.yml and pass it around.
      *
      * Oh, and you can decide how big the indent is and what the wordwrap
-     * for folding is.  Pretty cool -- just pass in 'false' for either if 
+     * for folding is.  Pretty cool -- just pass in 'false' for either if
      * you want to use the default.
      *
      * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
@@ -100,20 +100,20 @@
      * @static
      * @return string
      * @param array $array PHP array
-     * @param int $indent Pass in false to use the default, which is 2 
+     * @param int $indent Pass in false to use the default, which is 2
      * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
      */
     public static function YAMLDump($array,$indent = false,$wordwrap = false) {
       $spyc = new Spyc;
       return $spyc->dump($array,$indent,$wordwrap);
     }
-  
+
     /**
      * Load YAML into a PHP array from an instantiated object
      *
-     * The load method, when supplied with a YAML stream (string or file path), 
+     * The load method, when supplied with a YAML stream (string or file path),
      * will do its best to convert the YAML into a PHP array.  Pretty simple.
-     *  Usage: 
+     *  Usage:
      *  <code>
      *   $parser = new Spyc;
      *   $array  = $parser->load('lucky.yml');
@@ -126,7 +126,7 @@
     function load($input) {
       // See what type of input we're talking about
       // If it's not a file, assume it's a string
-      if (!empty($input) && (strpos($input, "\n") === false) 
+      if (!empty($input) && (strpos($input, "\n") === false)
           && file_exists($input)) {
         $yaml = file($input);
       } else {
@@ -139,7 +139,7 @@
       $this->_lastNode   = $base->id;
       $this->_inBlock    = false;
       $this->_isInline   = false;
-  
+
       foreach ($yaml as $linenum => $line) {
         $ifchk = trim($line);
 
@@ -149,7 +149,7 @@
                  ' with a tab.  YAML only recognizes spaces.  Please reformat.';
           die($err);
         }
-        
+
         if ($this->_inBlock === false && empty($ifchk)) {
           continue;
         } elseif ($this->_inBlock == true && empty($ifchk)) {
@@ -159,7 +159,7 @@
           // Create a new node and get its indent
           $node         = new YAMLNode;
           $node->indent = $this->_getIndent($line);
-          
+
           // Check where the node lies in the hierarchy
           if ($this->_lastIndent == $node->indent) {
             // If we're in a block, add the text to the parent's data
@@ -172,16 +172,16 @@
                 $node->parent = $this->_allNodes[$this->_lastNode]->parent;
               }
             }
-          } elseif ($this->_lastIndent < $node->indent) {            
+          } elseif ($this->_lastIndent < $node->indent) {
             if ($this->_inBlock === true) {
               $parent =& $this->_allNodes[$this->_lastNode];
               $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd;
             } elseif ($this->_inBlock === false) {
               // The current node's parent is the previous node
               $node->parent = $this->_lastNode;
-              
-              // If the value of the last node's data was > or | we need to 
-              // start blocking i.e. taking in all lines as a text value until 
+
+              // If the value of the last node's data was > or | we need to
+              // start blocking i.e. taking in all lines as a text value until
               // we drop our indent.
               $parent =& $this->_allNodes[$node->parent];
               $this->_allNodes[$node->parent]->children = true;
@@ -190,7 +190,7 @@
                 if ($chk === '>') {
                   $this->_inBlock  = true;
                   $this->_blockEnd = ' ';
-                  $parent->data[key($parent->data)] = 
+                  $parent->data[key($parent->data)] =
                         str_replace('>','',$parent->data[key($parent->data)]);
                   $parent->data[key($parent->data)] .= trim($line).' ';
                   $this->_allNodes[$node->parent]->children = false;
@@ -198,7 +198,7 @@
                 } elseif ($chk === '|') {
                   $this->_inBlock  = true;
                   $this->_blockEnd = "\n";
-                  $parent->data[key($parent->data)] =               
+                  $parent->data[key($parent->data)] =
                         str_replace('|','',$parent->data[key($parent->data)]);
                   $parent->data[key($parent->data)] .= trim($line)."\n";
                   $this->_allNodes[$node->parent]->children = false;
@@ -212,11 +212,11 @@
               $this->_inBlock = false;
               if ($this->_blockEnd = "\n") {
                 $last =& $this->_allNodes[$this->_lastNode];
-                $last->data[key($last->data)] = 
+                $last->data[key($last->data)] =
                       trim($last->data[key($last->data)]);
               }
             }
-            
+
             // We don't know the parent of the node so we have to find it
             // foreach ($this->_allNodes as $n) {
             foreach ($this->_indentSort[$node->indent] as $n) {
@@ -225,7 +225,7 @@
               }
             }
           }
-        
+
           if ($this->_inBlock === false) {
             // Set these properties with information from our current node
             $this->_lastIndent = $node->indent;
@@ -239,13 +239,13 @@
             $this->_indentSort[$node->indent][] =& $this->_allNodes[$node->id];
             // Add a reference to the node in a References array if this node
             // has a YAML reference in it.
-            if ( 
+            if (
               ( (is_array($node->data)) &&
                 isset($node->data[key($node->data)]) &&
                 (!is_array($node->data[key($node->data)])) )
               &&
-              ( (preg_match('/^&([^ ]+)/',$node->data[key($node->data)])) 
-                || 
+              ( (preg_match('/^&([^ ]+)/',$node->data[key($node->data)]))
+                ||
                 (preg_match('/^\*([^ ]+)/',$node->data[key($node->data)])) )
             ) {
                 $this->_haveRefs[] =& $this->_allNodes[$node->id];
@@ -256,9 +256,9 @@
             ) {
               // Incomplete reference making code.  Ugly, needs cleaned up.
               foreach ($node->data[key($node->data)] as $d) {
-                if ( !is_array($d) && 
-                  ( (preg_match('/^&([^ ]+)/',$d)) 
-                    || 
+                if ( !is_array($d) &&
+                  ( (preg_match('/^&([^ ]+)/',$d))
+                    ||
                     (preg_match('/^\*([^ ]+)/',$d)) )
                   ) {
                     $this->_haveRefs[] =& $this->_allNodes[$node->id];
@@ -269,15 +269,15 @@
         }
       }
       unset($node);
-      
+
       // Here we travel through node-space and pick out references (& and *)
       $this->_linkReferences();
-      
+
       // Build the PHP array out of node-space
       $trunk = $this->_buildArray();
       return $trunk;
     }
-  
+
     /**
      * Dump PHP array to YAML
      *
@@ -286,7 +286,7 @@
      * save the returned string as tasteful.yml and pass it around.
      *
      * Oh, and you can decide how big the indent is and what the wordwrap
-     * for folding is.  Pretty cool -- just pass in 'false' for either if 
+     * for folding is.  Pretty cool -- just pass in 'false' for either if
      * you want to use the default.
      *
      * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
@@ -295,7 +295,7 @@
      * @access public
      * @return string
      * @param array $array PHP array
-     * @param int $indent Pass in false to use the default, which is 2 
+     * @param int $indent Pass in false to use the default, which is 2
      * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
      */
     function dump($array,$indent = false,$wordwrap = false) {
@@ -308,29 +308,29 @@
       } else {
         $this->_dumpIndent = $indent;
       }
-      
+
       if ($wordwrap === false or !is_numeric($wordwrap)) {
         $this->_dumpWordWrap = 40;
       } else {
         $this->_dumpWordWrap = $wordwrap;
       }
-      
+
       // New YAML document
       $string = "---\n";
-      
+
       // Start at the base of the array and move through it.
       foreach ($array as $key => $value) {
         $string .= $this->_yamlize($key,$value,0);
       }
       return $string;
     }
-  
+
     /**** Private Properties ****/
-    
+
     /**#@+
      * @access private
      * @var mixed
-     */ 
+     */
     var $_haveRefs;
     var $_allNodes;
     var $_lastIndent;
@@ -342,7 +342,7 @@
     /**#@-*/
 
     /**** Private Methods ****/
-    
+
     /**
      * Attempts to convert a key / value array item to YAML
      * @access private
@@ -350,7 +350,7 @@
      * @param $key The name of the key
      * @param $value The value of the item
      * @param $indent The indent of the current node
-     */    
+     */
     function _yamlize($key,$value,$indent) {
       if (is_array($value)) {
         // It has children.  What to do?
@@ -366,14 +366,14 @@
       }
       return $string;
     }
-    
+
     /**
      * Attempts to convert an array to YAML
      * @access private
      * @return string
      * @param $array The array you want to convert
      * @param $indent The indent of the current level
-     */ 
+     */
     function _yamlizeArray($array,$indent) {
       if (is_array($array)) {
         $string = '';
@@ -395,9 +395,15 @@
     function _needLiteral($value) {
       # Check whether the string contains # or : or begins with any of:
       # [ - ? , [ ] { } ! * & | > ' " % @ ` ]
-      return (bool)(preg_match("/[#:]/", $value) || preg_match("/^[-?,[\]{}!*&|>'\"%@`]/", $value));
+      # or is a number or contains newlines
+      return (bool)(gettype($value) == "string" &&
+	(is_numeric($value)  ||
+	strpos($value, "\n") ||
+	preg_match("/[#:]/", $value) ||
+	preg_match("/^[-?,[\]{}!*&|>'\"%@`]/", $value)));
+
     }
-  
+
     /**
      * Returns YAML from a key and a value
      * @access private
@@ -405,23 +411,29 @@
      * @param $key The name of the key
      * @param $value The value of the item
      * @param $indent The indent of the current node
-     */ 
+     */
     function _dumpNode($key,$value,$indent) {
       // do some folding here, for blocks
-      if (strpos($value,"\n") || $this->_needLiteral($value)) {
+      if ($this->_needLiteral($value)) {
         $value = $this->_doLiteralBlock($value,$indent);
-      } else {  
+      } else {
         $value  = $this->_doFolding($value,$indent);
       }
-      
+
       $spaces = str_repeat(' ',$indent);
 
       if (is_int($key)) {
         // It's a sequence
-        $string = $spaces.'- '.$value."\n";
+		if ($value)
+			$string = $spaces.'- '.$value."\n";
+		else
+			$string = $spaces . "-\n";
       } else {
-        // It's mapped
-        $string = $spaces.$key.': '.$value."\n";
+		// It's mapped
+		if ($value)
+	        $string = $spaces.$key.': '.$value."\n";
+		else
+			$string = $spaces . $key . ":\n";
       }
       return $string;
     }
@@ -430,9 +442,9 @@
      * Creates a literal block for dumping
      * @access private
      * @return string
-     * @param $value 
+     * @param $value
      * @param $indent int The value of the indent
-     */ 
+     */
     function _doLiteralBlock($value,$indent) {
       $exploded = explode("\n",$value);
       $newValue = '|';
@@ -443,7 +455,7 @@
       }
       return $newValue;
     }
-    
+
     /**
      * Folds a string of text, if necessary
      * @access private
@@ -455,7 +467,7 @@
       if ($this->_dumpWordWrap === 0) {
         return $value;
       }
-      
+
       if (strlen($value) > $this->_dumpWordWrap) {
         $indent += $this->_dumpIndent;
         $indent = str_repeat(' ',$indent);
@@ -464,9 +476,9 @@
       }
       return $value;
     }
-  
+
     /* Methods used in loading */
-    
+
     /**
      * Finds and returns the indentation of a YAML line
      * @access private
@@ -491,7 +503,7 @@
      * @param string $line A line from the YAML file
      */
     function _parseLine($line) {
-      $line = trim($line);  
+      $line = trim($line);
 
       $array = array();
 
@@ -534,7 +546,7 @@
       }
       return $array;
     }
-    
+
     /**
      * Finds the type of the passed value, returns the value as the new type.
      * @access private
@@ -543,7 +555,7 @@
      */
     function _toType($value) {
       $matches = array();
-      if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) {        
+      if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) {
        $value = (string)preg_replace('/(\'\'|\\\\\')/',"'",end($matches));
        $value = preg_replace('/\\\\"/','"',$value);
       } elseif (preg_match('/^\\[(.+)\\]$/',$value,$matches)) {
@@ -551,7 +563,7 @@
 
         // Take out strings sequences and mappings
         $explode = $this->_inlineEscape($matches[1]);
-        
+
         // Propogate value array
         $value  = array();
         foreach ($explode as $v) {
@@ -581,10 +593,10 @@
         $value = NULL;
       } elseif (ctype_digit($value)) {
         $value = (int)$value;
-      } elseif (in_array(strtolower($value), 
+      } elseif (in_array(strtolower($value),
                   array('true', 'on', '+', 'yes', 'y'))) {
         $value = TRUE;
-      } elseif (in_array(strtolower($value), 
+      } elseif (in_array(strtolower($value),
                   array('false', 'off', '-', 'no', 'n'))) {
         $value = FALSE;
       } elseif (is_numeric($value)) {
@@ -593,10 +605,10 @@
         // Just a normal string, right?
         $value = trim(preg_replace('/#(.+)$/','',$value));
       }
-      
+
       return $value;
     }
-    
+
     /**
      * Used in inlines to check for more inlines or quoted strings
      * @access private
@@ -607,13 +619,13 @@
       // While pure sequences seem to be nesting just fine,
       // pure mappings and mappings with sequences inside can't go very
       // deep.  This needs to be fixed.
-      
-      // Check for strings      
+
+      // Check for strings
       $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
       $strings = array();
       if (preg_match_all($regex,$inline,$strings)) {
         $saved_strings[] = $strings[0][0];
-        $inline  = preg_replace($regex,'YAMLString',$inline); 
+        $inline  = preg_replace($regex,'YAMLString',$inline);
       }
       unset($regex);
 
@@ -623,14 +635,14 @@
         $inline = preg_replace('/\[(.+)\]/U','YAMLSeq',$inline);
         $seqs   = $seqs[0];
       }
-      
+
       // Check for mappings
       $maps = array();
       if (preg_match_all('/{(.+)}/U',$inline,$maps)) {
         $inline = preg_replace('/{(.+)}/U','YAMLMap',$inline);
         $maps   = $maps[0];
       }
-      
+
       $explode = explode(', ',$inline);
 
       // Re-add the strings
@@ -654,7 +666,7 @@
           }
         }
       }
-      
+
       // Re-add the mappings
       if (!empty($maps)) {
         $i = 0;
@@ -668,7 +680,7 @@
 
       return $explode;
     }
-  
+
     /**
      * Builds the PHP array from all the YAML nodes we've gathered
      * @access private
@@ -690,10 +702,10 @@
           $trunk = $this->_array_kmerge($trunk,$n->data);
         }
       }
-      
+
       return $trunk;
     }
-  
+
     /**
      * Traverses node-space and sets references (& and *) accordingly
      * @access private
@@ -705,7 +717,7 @@
           if (!empty($node->data)) {
             $key = key($node->data);
             // If it's an array, don't check.
-            if (is_array($node->data[$key])) {  
+            if (is_array($node->data[$key])) {
               foreach ($node->data[$key] as $k => $v) {
                 $this->_linkRef($node,$key,$k,$v);
               }
@@ -713,11 +725,11 @@
               $this->_linkRef($node,$key);
             }
           }
-        } 
+        }
       }
       return true;
     }
-    
+
     function _linkRef(&$n,$key,$k = NULL,$v = NULL) {
       if (empty($k) && empty($v)) {
         // Look for &refs
@@ -725,7 +737,7 @@
         if (preg_match('/^&([^ ]+)/',$n->data[$key],$matches)) {
           // Flag the node so we know it's a reference
           $this->_allNodes[$n->id]->ref = substr($matches[0],1);
-          $this->_allNodes[$n->id]->data[$key] = 
+          $this->_allNodes[$n->id]->data[$key] =
                    substr($n->data[$key],strlen($matches[0])+1);
         // Look for *refs
         } elseif (preg_match('/^\*([^ ]+)/',$n->data[$key],$matches)) {
@@ -737,7 +749,7 @@
         if (preg_match('/^&([^ ]+)/',$v,$matches)) {
           // Flag the node so we know it's a reference
           $this->_allNodes[$n->id]->ref = substr($matches[0],1);
-          $this->_allNodes[$n->id]->data[$key][$k] = 
+          $this->_allNodes[$n->id]->data[$key][$k] =
                               substr($v,strlen($matches[0])+1);
         // Look for *refs
         } elseif (preg_match('/^\*([^ ]+)/',$v,$matches)) {
@@ -747,7 +759,7 @@
         }
       }
     }
-  
+
     /**
      * Finds the children of a node and aids in the building of the PHP array
      * @access private
@@ -770,7 +782,7 @@
       }
       return $return;
     }
-  
+
     /**
      * Turns a node's data and its children's data into a PHP array
      *
@@ -824,7 +836,7 @@
       }
       return true;
     }
-  
+
 
     /**
      * Merges arrays and maintains numeric keys.
@@ -832,29 +844,29 @@
      * An ever-so-slightly modified version of the array_kmerge() function posted
      * to php.net by mail at nospam dot iaindooley dot com on 2004-04-08.
      *
-     * http://us3.php.net/manual/en/function.array-merge.php#41394
+     * http://www.php.net/manual/en/function.array-merge.php#41394
      *
      * @access private
      * @param array $arr1
      * @param array $arr2
      * @return array
      */
-    function _array_kmerge($arr1,$arr2) { 
-      if(!is_array($arr1)) 
-        $arr1 = array(); 
+    function _array_kmerge($arr1,$arr2) {
+      if(!is_array($arr1))
+        $arr1 = array();
 
       if(!is_array($arr2))
-        $arr2 = array(); 
-    
-      $keys1 = array_keys($arr1); 
-      $keys2 = array_keys($arr2); 
-      $keys  = array_merge($keys1,$keys2); 
-      $vals1 = array_values($arr1); 
-      $vals2 = array_values($arr2); 
-      $vals  = array_merge($vals1,$vals2); 
-      $ret   = array(); 
+        $arr2 = array();
+
+      $keys1 = array_keys($arr1);
+      $keys2 = array_keys($arr2);
+      $keys  = array_merge($keys1,$keys2);
+      $vals1 = array_values($arr1);
+      $vals2 = array_values($arr2);
+      $vals  = array_merge($vals1,$vals2);
+      $ret   = array();
 
-      foreach($keys as $key) { 
+      foreach($keys as $key) {
         list( /* unused */ ,$val) = each($vals);
         // This is the good part!  If a key already exists, but it's part of a
         // sequence (an int), just keep addin numbers until we find a fresh one.
@@ -862,11 +874,10 @@
           while (array_key_exists($key, $ret)) {
             $key++;
           }
-        }  
-        $ret[$key] = $val; 
-      } 
+        }
+        $ret[$key] = $val;
+      }
 
-      return $ret; 
+      return $ret;
     }
   }
-
diff -pruN 1:1.12.0-2/includes/api/ApiHelp.php 1:1.13.0-1/includes/api/ApiHelp.php
--- 1:1.12.0-2/includes/api/ApiHelp.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiHelp.php	2008-05-20 18:13:28.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * This is a simple class to handle action=help
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiHelp extends ApiBase {
 
@@ -57,7 +57,6 @@ class ApiHelp extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiHelp.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiHelp.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiLogin.php 1:1.13.0-1/includes/api/ApiLogin.php
--- 1:1.12.0-2/includes/api/ApiLogin.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiLogin.php	2008-05-29 20:23:37.000000000 +0100
@@ -32,10 +32,10 @@ if (!defined('MEDIAWIKI')) {
 /**
  * Unit to authenticate log-in attempts to the current wiki.
  *
- * @addtogroup API
+ * @ingroup API
  */
 class ApiLogin extends ApiBase {
-	
+
 	/**
 	 * Time (in seconds) a user must wait after submitting
 	 * a bad login (will be multiplied by the THROTTLE_FACTOR for each bad attempt)
@@ -47,12 +47,12 @@ class ApiLogin extends ApiBase {
 	 * attempts is increased every failed attempt.
 	 */
 	const THROTTLE_FACTOR = 2;
-	
+
 	/**
-	 * The maximum number of failed logins after which the wait increase stops. 
+	 * The maximum number of failed logins after which the wait increase stops.
 	 */
 	const THOTTLE_MAX_COUNT = 10;
-	
+
 	public function __construct($main, $action) {
 		parent :: __construct($main, $action, 'lg');
 	}
@@ -104,10 +104,15 @@ class ApiLogin extends ApiBase {
 				$wgUser->setOption('rememberpassword', 1);
 				$wgUser->setCookies();
 
+				// Run hooks. FIXME: split back and frontend from this hook.
+				// FIXME: This hook should be placed in the backend
+				$injected_html = '';
+				wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
+
 				$result['result'] = 'Success';
-				$result['lguserid'] = $_SESSION['wsUserID'];
-				$result['lgusername'] = $_SESSION['wsUserName'];
-				$result['lgtoken'] = $_SESSION['wsToken'];
+				$result['lguserid'] = $wgUser->getId();
+				$result['lgusername'] = $wgUser->getName();
+				$result['lgtoken'] = $wgUser->getToken();
 				$result['cookieprefix'] = $wgCookiePrefix;
 				$result['sessionid'] = session_id();
 				break;
@@ -130,34 +135,39 @@ class ApiLogin extends ApiBase {
 			case LoginForm :: EMPTY_PASS :
 				$result['result'] = 'EmptyPass';
 				break;
+			case LoginForm :: CREATE_BLOCKED :
+				$result['result'] = 'CreateBlocked';
+				$result['details'] = 'Your IP address is blocked from account creation';
+				break;
 			default :
 				ApiBase :: dieDebug(__METHOD__, 'Unhandled case value');
 		}
 
-		if ($result['result'] != 'Success') {
-			$result['wait'] = $this->cacheBadLogin();
-			$result['details'] = "Please wait " . self::THROTTLE_TIME . " seconds before next log-in attempt";
+		if ($result['result'] != 'Success' && !isset( $result['details'] ) ) {
+			$delay = $this->cacheBadLogin();
+			$result['wait'] = $delay;
+			$result['details'] = "Please wait " . $delay . " seconds before next log-in attempt";
 		}
 		// if we were allowed to try to login, memcache is fine
-		
+
 		$this->getResult()->addValue(null, 'login', $result);
 	}
 
-	
+
 	/**
-	 * Caches a bad-login attempt associated with the host and with an 
-	 * expiry of $this->mLoginThrottle. These are cached by a key 
+	 * Caches a bad-login attempt associated with the host and with an
+	 * expiry of $this->mLoginThrottle. These are cached by a key
 	 * separate from that used by the captcha system--as such, logging
 	 * in through the standard interface will get you a legal session
 	 * and cookies to prove it, but will not remove this entry.
 	 *
-	 * Returns the number of seconds until next login attempt will be allowed. 
+	 * Returns the number of seconds until next login attempt will be allowed.
 	 *
 	 * @access private
 	 */
 	private function cacheBadLogin() {
 		global $wgMemc;
-		
+
 		$key = $this->getMemCacheKey();
 		$val = $wgMemc->get( $key );
 
@@ -167,24 +177,24 @@ class ApiLogin extends ApiBase {
 		} else {
 			$val['count'] = 1 + $val['count'];
 		}
-		
+
 		$delay = ApiLogin::calculateDelay($val['count']);
-		
+
 		$wgMemc->delete($key);
 		// Cache expiration should be the maximum timeout - to prevent a "try and wait" attack
-		$wgMemc->add( $key, $val, ApiLogin::calculateDelay(ApiLogin::THOTTLE_MAX_COUNT) );	
-		
+		$wgMemc->add( $key, $val, ApiLogin::calculateDelay(ApiLogin::THOTTLE_MAX_COUNT) );
+
 		return $delay;
 	}
-	
+
 	/**
-	 * How much time the client must wait before it will be 
+	 * How much time the client must wait before it will be
 	 * allowed to try to log-in next.
 	 * The return value is 0 if no wait is required.
 	 */
 	private function getNextLoginTimeout() {
 		global $wgMemc;
-		
+
 		$val = $wgMemc->get($this->getMemCacheKey());
 
 		$elapse = (time() - $val['lastReqTime']);  // in seconds
@@ -192,7 +202,7 @@ class ApiLogin extends ApiBase {
 
 		return $canRetryIn < 0 ? 0 : $canRetryIn;
 	}
-	
+
 	/**
 	 * Based on the number of previously attempted logins, returns
 	 * the delay (in seconds) when the next login attempt will be allowed.
@@ -204,10 +214,10 @@ class ApiLogin extends ApiBase {
 		$count = $count > self::THOTTLE_MAX_COUNT ? self::THOTTLE_MAX_COUNT : $count;
 
 		return self::THROTTLE_TIME + self::THROTTLE_TIME * ($count - 1) * self::THROTTLE_FACTOR;
-	} 
+	}
 
 	/**
-	* Internal cache key for badlogin checks. Robbed from the 
+	* Internal cache key for badlogin checks. Robbed from the
 	* ConfirmEdit extension and modified to use a key unique to the
 	* API login.3
 	*
@@ -217,7 +227,7 @@ class ApiLogin extends ApiBase {
 	private function getMemCacheKey() {
 		return wfMemcKey( 'apilogin', 'badlogin', 'ip', wfGetIP() );
 	}
-	
+
 	public function mustBePosted() { return true; }
 
 	public function getAllowedParams() {
@@ -241,11 +251,11 @@ class ApiLogin extends ApiBase {
 			'This module is used to login and get the authentication tokens. ',
 			'In the event of a successful log-in, a cookie will be attached',
 			'to your session. In the event of a failed log-in, you will not ',
-			'be able to attempt another log-in through this method for 60 seconds.',
+			'be able to attempt another log-in through this method for 5 seconds.',
 			'This is to prevent password guessing by automated password crackers.'
 		);
 	}
-	
+
 	protected function getExamples() {
 		return array(
 			'api.php?action=login&lgname=user&lgpassword=password'
@@ -253,7 +263,6 @@ class ApiLogin extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiLogin.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiLogin.php 35565 2008-05-29 19:23:37Z btongminh $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiLogout.php 1:1.13.0-1/includes/api/ApiLogout.php
--- 1:1.12.0-2/includes/api/ApiLogout.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiLogout.php	2008-05-24 21:44:49.000000000 +0100
@@ -29,10 +29,10 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * API module to allow users to log out of the wiki. API equivalent of 
+ * API module to allow users to log out of the wiki. API equivalent of
  * Special:Userlogout.
  *
- * @addtogroup API
+ * @ingroup API
  */
 class ApiLogout extends ApiBase {
 
@@ -43,6 +43,10 @@ class ApiLogout extends ApiBase {
 	public function execute() {
 		global $wgUser;
 		$wgUser->logout();
+		
+		// Give extensions to do something after user logout
+		$injected_html = '';
+		wfRunHooks( 'UserLogoutComplete', array(&$wgUser, &$injected_html) );
 	}
 
 	public function getAllowedParams() {
@@ -66,6 +70,6 @@ class ApiLogout extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id$';
+		return __CLASS__ . ': $Id: ApiLogout.php 35294 2008-05-24 20:44:49Z btongminh $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiMain.php 1:1.13.0-1/includes/api/ApiMain.php
--- 1:1.12.0-2/includes/api/ApiMain.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiMain.php	2008-07-08 21:53:41.000000000 +0100
@@ -29,6 +29,10 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
+ * @defgroup API API
+ */
+
+/**
  * This is the main API class, used for both external and internal processing.
  * When executed, it will create the requested formatter object,
  * instantiate and execute an object associated with the needed action,
@@ -37,9 +41,9 @@ if (!defined('MEDIAWIKI')) {
  *
  * To use API from another application, run it using FauxRequest object, in which
  * case any internal exceptions will not be handled but passed up to the caller.
- * After successful execution, use getResult() for the resulting data.   
- *  
- * @addtogroup API
+ * After successful execution, use getResult() for the resulting data.
+ *
+ * @ingroup API
  */
 class ApiMain extends ApiBase {
 
@@ -62,7 +66,7 @@ class ApiMain extends ApiBase {
 		'help' => 'ApiHelp',
 		'paraminfo' => 'ApiParamInfo',
 	);
-	
+
 	private static $WriteModules = array (
 		'rollback' => 'ApiRollback',
 		'delete' => 'ApiDelete',
@@ -71,8 +75,8 @@ class ApiMain extends ApiBase {
 		'block' => 'ApiBlock',
 		'unblock' => 'ApiUnblock',
 		'move' => 'ApiMove',
-		#'changerights' => 'ApiChangeRights'
-		# Disabled for now
+		'edit' => 'ApiEditPage',
+		'emailuser' => 'ApiEmailUser',
 	);
 
 	/**
@@ -113,25 +117,25 @@ class ApiMain extends ApiBase {
 		parent :: __construct($this, $this->mInternalMode ? 'main_int' : 'main');
 
 		if (!$this->mInternalMode) {
-			
+
 			// Impose module restrictions.
-			// If the current user cannot read, 
+			// If the current user cannot read,
 			// Remove all modules other than login
 			global $wgUser;
-			
+
 			if( $request->getVal( 'callback' ) !== null ) {
 				// JSON callback allows cross-site reads.
 				// For safety, strip user credentials.
 				wfDebug( "API: stripping user credentials for JSON callback\n" );
 				$wgUser = new User();
 			}
-			
+
 			if (!$wgUser->isAllowed('read')) {
 				self::$Modules = array(
 					'login'  => self::$Modules['login'],
 					'logout' => self::$Modules['logout'],
 					'help'   => self::$Modules['help'],
-					); 
+					);
 			}
 		}
 
@@ -150,7 +154,8 @@ class ApiMain extends ApiBase {
 
 		$this->mRequest = & $request;
 
-		$this->mSquidMaxage = 0;
+		$this->mSquidMaxage = -1; // flag for executeActionWithErrorHandling()
+		$this->mCommit = false;
 	}
 
 	/**
@@ -175,12 +180,19 @@ class ApiMain extends ApiBase {
 	}
 
 	/**
-	 * This method will simply cause an error if the write mode was disabled for this api.
+	 * This method will simply cause an error if the write mode was disabled
+	 * or if the current user doesn't have the right to use it
 	 */
 	public function requestWriteMode() {
+		global $wgUser;
 		if (!$this->mEnableWrite)
-			$this->dieUsage('Editing of this site is disabled. Make sure the $wgEnableWriteAPI=true; ' .
-			'statement is included in the site\'s LocalSettings.php file', 'noapiwrite');
+			$this->dieUsage('Editing of this wiki through the API' .
+			' is disabled. Make sure the $wgEnableWriteAPI=true; ' .
+			'statement is included in the wiki\'s ' .
+			'LocalSettings.php file', 'noapiwrite');
+		if (!$wgUser->isAllowed('writeapi'))
+			$this->dieUsage('You\'re not allowed to edit this ' .
+			'wiki through the API', 'writeapidenied');
 	}
 
 	/**
@@ -198,7 +210,7 @@ class ApiMain extends ApiBase {
 	}
 
 	/**
-	 * Execute api request. Any errors will be handled if the API was called by the remote client. 
+	 * Execute api request. Any errors will be handled if the API was called by the remote client.
 	 */
 	public function execute() {
 		$this->profileIn();
@@ -206,6 +218,7 @@ class ApiMain extends ApiBase {
 			$this->executeAction();
 		else
 			$this->executeActionWithErrorHandling();
+	
 		$this->profileOut();
 	}
 
@@ -247,11 +260,22 @@ class ApiMain extends ApiBase {
 			$this->printResult(true);
 		}
 
+		global $wgRequest;
+		if($this->mSquidMaxage == -1)
+		{
+			# Nobody called setCacheMaxAge(), use the (s)maxage parameters
+			$smaxage = $wgRequest->getVal('smaxage', 0);
+			$maxage = $wgRequest->getVal('maxage', 0);
+		}
+		else
+			$smaxage = $maxage = $this->mSquidMaxage;
+
 		// Set the cache expiration at the last moment, as any errors may change the expiration.
 		// if $this->mSquidMaxage == 0, the expiry time is set to the first second of unix epoch
-		$expires = $this->mSquidMaxage == 0 ? 1 : time() + $this->mSquidMaxage;
+		$exp = min($smaxage, $maxage);
+		$expires = ($exp == 0 ? 1 : time() + $exp);
 		header('Expires: ' . wfTimestamp(TS_RFC2822, $expires));
-		header('Cache-Control: s-maxage=' . $this->mSquidMaxage . ', must-revalidate, max-age=0');
+		header('Cache-Control: s-maxage=' . $smaxage . ', must-revalidate, max-age=' . $maxage);
 
 		if($this->mPrinter->getIsHtml())
 			echo wfReportTime();
@@ -261,10 +285,10 @@ class ApiMain extends ApiBase {
 
 	/**
 	 * Replace the result data with the information about an exception.
-	 * Returns the error code 
+	 * Returns the error code
 	 */
 	protected function substituteResultWithError($e) {
-	
+
 			// Printer may not be initialized if the extractRequestParams() fails for the main module
 			if (!isset ($this->mPrinter)) {
 				// The printer has not been created yet. Try to manually get formatter value.
@@ -284,20 +308,27 @@ class ApiMain extends ApiBase {
 				$errMessage = array (
 				'code' => $e->getCodeString(),
 				'info' => $e->getMessage());
-				
+
 				// Only print the help message when this is for the developer, not runtime
 				if ($this->mPrinter->getIsHtml() || $this->mAction == 'help')
 					ApiResult :: setContent($errMessage, $this->makeHelpMsg());
 
 			} else {
+				global $wgShowSQLErrors, $wgShowExceptionDetails;
 				//
 				// Something is seriously wrong
 				//
+				if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) {
+					$info = "Database query error";
+				} else {
+					$info = "Exception Caught: {$e->getMessage()}";
+				}
+
 				$errMessage = array (
 					'code' => 'internal_api_error_'. get_class($e),
-					'info' => "Exception Caught: {$e->getMessage()}"
+					'info' => $info,
 				);
-				ApiResult :: setContent($errMessage, "\n\n{$e->getTraceAsString()}\n\n");
+				ApiResult :: setContent($errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : "" );				
 			}
 
 			$this->getResult()->reset();
@@ -318,12 +349,12 @@ class ApiMain extends ApiBase {
 
 		// Instantiate the module requested by the user
 		$module = new $this->mModules[$this->mAction] ($this, $this->mAction);
-		
+
 		if( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
 			// Check for maxlag
-			global $wgLoadBalancer, $wgShowHostnames;
+			global $wgShowHostnames;
 			$maxLag = $params['maxlag'];
-			list( $host, $lag ) = $wgLoadBalancer->getMaxLag();
+			list( $host, $lag ) = wfGetLB()->getMaxLag();
 			if ( $lag > $maxLag ) {
 				if( $wgShowHostnames ) {
 					ApiBase :: dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
@@ -333,7 +364,7 @@ class ApiMain extends ApiBase {
 				return;
 			}
 		}
-		
+
 		if (!$this->mInternalMode) {
 			// Ignore mustBePosted() for internal calls
 			if($module->mustBePosted() && !$this->mRequest->wasPosted())
@@ -367,13 +398,12 @@ class ApiMain extends ApiBase {
 	protected function printResult($isError) {
 		$printer = $this->mPrinter;
 		$printer->profileIn();
-	
+
 		/* If the help message is requested in the default (xmlfm) format,
 		 * tell the printer not to escape ampersands so that our links do
 		 * not break. */
-		$params = $this->extractRequestParams();
-		$printer->setUnescapeAmps ( ( $this->mAction == 'help' || $isError ) 
-				&& $params['format'] == ApiMain::API_DEFAULT_FORMAT );
+		$printer->setUnescapeAmps ( ( $this->mAction == 'help' || $isError )
+				&& $this->getParameter('format') == ApiMain::API_DEFAULT_FORMAT );
 
 		$printer->initPrinter($isError);
 
@@ -399,6 +429,14 @@ class ApiMain extends ApiBase {
 			'maxlag'  => array (
 				ApiBase :: PARAM_TYPE => 'integer'
 			),
+			'smaxage' => array (
+				ApiBase :: PARAM_TYPE => 'integer',
+				ApiBase :: PARAM_DFLT => 0
+			),
+			'maxage' => array (
+				ApiBase :: PARAM_TYPE => 'integer',
+				ApiBase :: PARAM_DFLT => 0
+			),
 		);
 	}
 
@@ -410,7 +448,9 @@ class ApiMain extends ApiBase {
 			'format' => 'The format of the output',
 			'action' => 'What action you would like to perform',
 			'version' => 'When showing help, include version for each module',
-			'maxlag' => 'Maximum lag'
+			'maxlag' => 'Maximum lag',
+			'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached',
+			'maxage' => 'Set the max-age header to this many seconds. Errors are never cached',
 		);
 	}
 
@@ -444,13 +484,17 @@ class ApiMain extends ApiBase {
 			'',
 		);
 	}
-	
+
 	/**
 	 * Returns an array of strings with credits for the API
 	 */
 	protected function getCredits() {
 		return array(
-			'This API is being implemented by Roan Kattouw <Firstname>.<Lastname>@home.nl',
+			'API developers:',
+			'    Roan Kattouw <Firstname>.<Lastname>@home.nl (lead developer Sep 2007-present)',
+			'    Victor Vasiliev - vasilvv at gee mail dot com',
+			'    Yuri Astrakhan <Firstname><Lastname>@gmail.com (creator, lead developer Sep 2006-Sep 2007)',
+			'',
 			'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org',
 			'or file a bug report at http://bugzilla.wikimedia.org/'
 		);
@@ -460,7 +504,7 @@ class ApiMain extends ApiBase {
 	 * Override the parent to generate help messages for all available modules.
 	 */
 	public function makeHelpMsg() {
-		
+
 		$this->mPrinter->setHelp();
 
 		// Use parent to make default message for the main module
@@ -486,9 +530,9 @@ class ApiMain extends ApiBase {
 				$msg .= $msg2;
 			$msg .= "\n";
 		}
-		
+
 		$msg .= "\n*** Credits: ***\n   " . implode("\n   ", $this->getCredits()) . "\n";
-		
+
 
 		return $msg;
 	}
@@ -496,15 +540,15 @@ class ApiMain extends ApiBase {
 	public static function makeHelpMsgHeader($module, $paramName) {
 		$modulePrefix = $module->getModulePrefix();
 		if (!empty($modulePrefix))
-			$modulePrefix = "($modulePrefix) "; 
-		
+			$modulePrefix = "($modulePrefix) ";
+
 		return "* $paramName={$module->getModuleName()} $modulePrefix*";
-	} 
+	}
 
 	private $mIsBot = null;
 	private $mIsSysop = null;
 	private $mCanApiHighLimits = null;
-	
+
 	/**
 	 * Returns true if the currently logged in user is a bot, false otherwise
 	 * OBSOLETE, use canApiHighLimits() instead
@@ -516,7 +560,7 @@ class ApiMain extends ApiBase {
 		}
 		return $this->mIsBot;
 	}
-	
+
 	/**
 	 * Similar to isBot(), this method returns true if the logged in user is
 	 * a sysop, and false if not.
@@ -530,7 +574,11 @@ class ApiMain extends ApiBase {
 
 		return $this->mIsSysop;
 	}
-	
+
+	/**
+	 * Check whether the current user is allowed to use high limits
+	 * @return bool
+	 */
 	public function canApiHighLimits() {
 		if (!isset($this->mCanApiHighLimits)) {
 			global $wgUser;
@@ -540,6 +588,10 @@ class ApiMain extends ApiBase {
 		return $this->mCanApiHighLimits;
 	}
 
+	/**
+	 * Check whether the user wants us to show version information in the API help
+	 * @return bool
+	 */
 	public function getShowVersions() {
 		return $this->mShowVersions;
 	}
@@ -551,7 +603,7 @@ class ApiMain extends ApiBase {
 	public function getVersion() {
 		$vers = array ();
 		$vers[] = 'MediaWiki ' . SpecialVersion::getVersion();
-		$vers[] = __CLASS__ . ': $Id: ApiMain.php 31484 2008-03-03 05:46:20Z brion $';
+		$vers[] = __CLASS__ . ': $Id: ApiMain.php 37349 2008-07-08 20:53:41Z catrope $';
 		$vers[] = ApiBase :: getBaseVersion();
 		$vers[] = ApiFormatBase :: getBaseVersion();
 		$vers[] = ApiQueryBase :: getBaseVersion();
@@ -561,7 +613,7 @@ class ApiMain extends ApiBase {
 
 	/**
 	 * Add or overwrite a module in this ApiMain instance. Intended for use by extending
-	 * classes who wish to add their own modules to their lexicon or override the 
+	 * classes who wish to add their own modules to their lexicon or override the
 	 * behavior of inherent ones.
 	 *
 	 * @access protected
@@ -583,7 +635,7 @@ class ApiMain extends ApiBase {
 	protected function addFormat( $fmtName, $fmtClass ) {
 		$this->mFormats[$fmtName] = $fmtClass;
 	}
-	
+
 	/**
 	 * Get the array mapping module names to class names
 	 */
@@ -595,8 +647,8 @@ class ApiMain extends ApiBase {
 /**
  * This exception will be thrown when dieUsage is called to stop module execution.
  * The exception handling code will print a help screen explaining how this API may be used.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class UsageException extends Exception {
 
@@ -613,5 +665,3 @@ class UsageException extends Exception {
 		return "{$this->getCodeString()}: {$this->getMessage()}";
 	}
 }
-
-
diff -pruN 1:1.12.0-2/includes/api/ApiMove.php 1:1.13.0-1/includes/api/ApiMove.php
--- 1:1.12.0-2/includes/api/ApiMove.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiMove.php	2008-05-30 20:59:47.000000000 +0100
@@ -29,21 +29,21 @@ if (!defined('MEDIAWIKI')) {
 
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiMove extends ApiBase {
 
 	public function __construct($main, $action) {
 		parent :: __construct($main, $action);
 	}
-	
+
 	public function execute() {
 		global $wgUser;
 		$this->getMain()->requestWriteMode();
 		$params = $this->extractRequestParams();
 		if(is_null($params['reason']))
 			$params['reason'] = '';
-	
+
 		$titleObj = NULL;
 		if(!isset($params['from']))
 			$this->dieUsageMsg(array('missingparam', 'from'));
@@ -69,7 +69,7 @@ class ApiMove extends ApiBase {
 		// Run getUserPermissionsErrors() here so we get message arguments too,
 		// rather than just a message key. The latter is troublesome for messages
 		// that use arguments.
-		// FIXME: moveTo() should really return an array, requires some 
+		// FIXME: moveTo() should really return an array, requires some
 		//	  refactoring of other code, though (mainly SpecialMovepage.php)
 		$errors = array_merge($fromTitle->getUserPermissionsErrors('move', $wgUser),
 					$fromTitle->getUserPermissionsErrors('edit', $wgUser),
@@ -79,16 +79,19 @@ class ApiMove extends ApiBase {
 			// We don't care about multiple errors, just report one of them
 			$this->dieUsageMsg(current($errors));
 
-		$dbw = wfGetDB(DB_MASTER);
-		$dbw->begin();		
+		$hookErr = null;
+
 		$retval = $fromTitle->moveTo($toTitle, true, $params['reason'], !$params['noredirect']);
 		if($retval !== true)
-			$this->dieUsageMsg(array($retval));
+		{
+			# FIXME: Title::moveTo() sometimes returns a string
+			$this->dieUsageMsg(reset($retval));
+		}
 
 		$r = array('from' => $fromTitle->getPrefixedText(), 'to' => $toTitle->getPrefixedText(), 'reason' => $params['reason']);
-		if(!$params['noredirect'])
+		if(!$params['noredirect'] || !$wgUser->isAllowed('suppressredirect'))
 			$r['redirectcreated'] = '';
-	
+
 		if($params['movetalk'] && $fromTalk->exists() && !$fromTitle->isTalkPage())
 		{
 			// We need to move the talk page as well
@@ -104,14 +107,25 @@ class ApiMove extends ApiBase {
 			{
 				$r['talkmove-error-code'] = ApiBase::$messageMap[$retval]['code'];
 				$r['talkmove-error-info'] = ApiBase::$messageMap[$retval]['info'];
-			}	
+			}
+		}
+
+		# Watch pages
+		if($params['watch'] || $wgUser->getOption('watchmoves'))
+		{
+			$wgUser->addWatch($fromTitle);
+			$wgUser->addWatch($toTitle);
+		}
+		else if($params['unwatch'])
+		{
+			$wgUser->removeWatch($fromTitle);
+			$wgUser->removeWatch($toTitle);
 		}
-		$dbw->commit(); // Make sure all changes are really written to the DB
 		$this->getResult()->addValue(null, $this->getModuleName(), $r);
 	}
-	
+
 	public function mustBePosted() { return true; }
-	
+
 	public function getAllowedParams() {
 		return array (
 			'from' => null,
@@ -119,7 +133,9 @@ class ApiMove extends ApiBase {
 			'token' => null,
 			'reason' => null,
 			'movetalk' => false,
-			'noredirect' => false
+			'noredirect' => false,
+			'watch' => false,
+			'unwatch' => false
 		);
 	}
 
@@ -130,7 +146,9 @@ class ApiMove extends ApiBase {
 			'token' => 'A move token previously retrieved through prop=info',
 			'reason' => 'Reason for the move (optional).',
 			'movetalk' => 'Move the talk page, if it exists.',
-			'noredirect' => 'Don\'t create a redirect'
+			'noredirect' => 'Don\'t create a redirect',
+			'watch' => 'Add the page and the redirect to your watchlist',
+			'unwatch' => 'Remove the page and the redirect from your watchlist'
 		);
 	}
 
@@ -147,6 +165,6 @@ class ApiMove extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiMove.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiMove.php 35619 2008-05-30 19:59:47Z btongminh $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiOpenSearch.php 1:1.13.0-1/includes/api/ApiOpenSearch.php
--- 1:1.12.0-2/includes/api/ApiOpenSearch.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiOpenSearch.php	2008-05-20 18:13:28.000000000 +0100
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiOpenSearch extends ApiBase {
 
@@ -45,11 +45,12 @@ class ApiOpenSearch extends ApiBase {
 		$params = $this->extractRequestParams();
 		$search = $params['search'];
 		$limit = $params['limit'];
-
+		$namespaces = $params['namespace'];
+		
 		// Open search results may be stored for a very long time
 		$this->getMain()->setCacheMaxAge(1200);
-		
-		$srchres = PrefixSearch::titleSearch( $search, $limit );
+
+		$srchres = PrefixSearch::titleSearch( $search, $limit, $namespaces );
 
 		// Set top level elements
 		$result = $this->getResult();
@@ -66,14 +67,20 @@ class ApiOpenSearch extends ApiBase {
 				ApiBase :: PARAM_MIN => 1,
 				ApiBase :: PARAM_MAX => 100,
 				ApiBase :: PARAM_MAX2 => 100
-			)
+			),
+			'namespace' => array(
+				ApiBase :: PARAM_DFLT => NS_MAIN,
+				ApiBase :: PARAM_TYPE => 'namespace',
+				ApiBase :: PARAM_ISMULTI => true
+			),
 		);
 	}
 
 	public function getParamDescription() {
 		return array (
 			'search' => 'Search string',
-			'limit' => 'Maximum amount of results to return'
+			'limit' => 'Maximum amount of results to return',
+			'namespace' => 'Namespaces to search',
 		);
 	}
 
@@ -88,7 +95,6 @@ class ApiOpenSearch extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiOpenSearch.php 30275 2008-01-30 01:07:49Z brion $';
+		return __CLASS__ . ': $Id: ApiOpenSearch.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiPageSet.php 1:1.13.0-1/includes/api/ApiPageSet.php
--- 1:1.12.0-2/includes/api/ApiPageSet.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiPageSet.php	2008-05-20 18:13:28.000000000 +0100
@@ -33,17 +33,18 @@ if (!defined('MEDIAWIKI')) {
  * Initially, when the client passes in titles=, pageids=, or revisions= parameter,
  * an instance of the ApiPageSet class will normalize titles,
  * determine if the pages/revisions exist, and prefetch any additional data page data requested.
- * 
+ *
  * When generator is used, the result of the generator will become the input for the
  * second instance of this class, and all subsequent actions will go use the second instance
- * for all their work. 
- * 
- * @addtogroup API
+ * for all their work.
+ *
+ * @ingroup API
  */
 class ApiPageSet extends ApiQueryBase {
 
-	private $mAllPages; // [ns][dbkey] => page_id or 0 when missing
-	private $mTitles, $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles;
+	private $mAllPages; // [ns][dbkey] => page_id or negative when missing
+	private $mTitles, $mGoodTitles, $mMissingTitles, $mInvalidTitles;
+	private $mMissingPageIDs, $mRedirectTitles;
 	private $mNormalizedTitles, $mInterwikiTitles;
 	private $mResolveRedirects, $mPendingRedirectIDs;
 	private $mGoodRevIDs, $mMissingRevIDs;
@@ -58,6 +59,7 @@ class ApiPageSet extends ApiQueryBase {
 		$this->mTitles = array();
 		$this->mGoodTitles = array ();
 		$this->mMissingTitles = array ();
+		$this->mInvalidTitles = array ();
 		$this->mMissingPageIDs = array ();
 		$this->mRedirectTitles = array ();
 		$this->mNormalizedTitles = array ();
@@ -69,7 +71,7 @@ class ApiPageSet extends ApiQueryBase {
 		$this->mResolveRedirects = $resolveRedirects;
 		if($resolveRedirects)
 			$this->mPendingRedirectIDs = array();
-			
+
 		$this->mFakePageId = -1;
 	}
 
@@ -107,8 +109,9 @@ class ApiPageSet extends ApiQueryBase {
 	}
 
 	/**
-	 * Returns an array [ns][dbkey] => page_id for all requested titles
-	 * page_id is a unique negative number in case title was not found
+	 * Returns an array [ns][dbkey] => page_id for all requested titles.
+	 * page_id is a unique negative number in case title was not found.
+	 * Invalid titles will also have negative page IDs and will be in namespace 0
 	 */
 	public function getAllTitlesByNamespace() {
 		return $this->mAllPages;
@@ -154,6 +157,15 @@ class ApiPageSet extends ApiQueryBase {
 	}
 
 	/**
+	 * Titles that were deemed invalid by Title::newFromText()
+	 * The array's index will be unique and negative for each item
+	 * @return array of strings (not Title objects)
+	 */
+	public function getInvalidTitles() {
+		return $this->mInvalidTitles;
+	}
+
+	/**
 	 * Page IDs that were not found in the database
 	 * @return array of page IDs
 	 */
@@ -170,18 +182,18 @@ class ApiPageSet extends ApiQueryBase {
 	}
 
 	/**
-	 * Get a list of title normalizations - maps the title given 
+	 * Get a list of title normalizations - maps the title given
 	 * with its normalized version.
-	 * @return array raw_prefixed_title (string) => prefixed_title (string) 
+	 * @return array raw_prefixed_title (string) => prefixed_title (string)
 	 */
 	public function getNormalizedTitles() {
 		return $this->mNormalizedTitles;
 	}
 
 	/**
-	 * Get a list of interwiki titles - maps the title given 
+	 * Get a list of interwiki titles - maps the title given
 	 * with to the interwiki prefix.
-	 * @return array raw_prefixed_title (string) => interwiki_prefix (string) 
+	 * @return array raw_prefixed_title (string) => interwiki_prefix (string)
 	 */
 	public function getInterwikiTitles() {
 		return $this->mInterwikiTitles;
@@ -293,11 +305,11 @@ class ApiPageSet extends ApiQueryBase {
 	 * Extract all requested fields from the row received from the database
 	 */
 	public function processDbRow($row) {
-	
+
 		// Store Title object in various data structures
 		$title = Title :: makeTitle($row->page_namespace, $row->page_title);
-	
-		$pageId = intval($row->page_id);	
+
+		$pageId = intval($row->page_id);
 		$this->mAllPages[$row->page_namespace][$row->page_title] = $pageId;
 		$this->mTitles[] = $title;
 
@@ -310,26 +322,26 @@ class ApiPageSet extends ApiQueryBase {
 		foreach ($this->mRequestedPageFields as $fieldName => & $fieldValues)
 			$fieldValues[$pageId] = $row-> $fieldName;
 	}
-	
+
 	public function finishPageSetGeneration() {
 		$this->profileIn();
 		$this->resolvePendingRedirects();
 		$this->profileOut();
 	}
-	
+
 	/**
 	 * This method populates internal variables with page information
 	 * based on the given array of title strings.
-	 * 
+	 *
 	 * Steps:
 	 * #1 For each title, get data from `page` table
 	 * #2 If page was not found in the DB, store it as missing
-	 * 
+	 *
 	 * Additionally, when resolving redirects:
 	 * #3 If no more redirects left, stop.
 	 * #4 For each redirect, get its links from `pagelinks` table.
 	 * #5 Substitute the original LinkBatch object with the new list
-	 * #6 Repeat from step #1     
+	 * #6 Repeat from step #1
 	 */
 	private function initFromTitles($titles) {
 
@@ -337,7 +349,7 @@ class ApiPageSet extends ApiQueryBase {
 		$linkBatch = $this->processTitlesArray($titles);
 		if($linkBatch->isEmpty())
 			return;
-			
+
 		$db = $this->getDB();
 		$set = $linkBatch->constructSet('page', $db);
 
@@ -368,13 +380,14 @@ class ApiPageSet extends ApiQueryBase {
 		$this->profileDBIn();
 		$res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__);
 		$this->profileDBOut();
-		
-		$this->initFromQueryResult($db, $res, array_flip($pageids), false);	// process PageIDs
+
+		$remaining = array_flip($pageids);
+		$this->initFromQueryResult($db, $res, $remaining, false);	// process PageIDs
 
 		// Resolve any found redirects
 		$this->resolvePendingRedirects();
 	}
-	
+
 	/**
 	 * Iterate through the result of the query on 'page' table,
 	 * and for each row create and store title object and save any extra fields requested.
@@ -390,7 +403,7 @@ class ApiPageSet extends ApiQueryBase {
 	private function initFromQueryResult($db, $res, &$remaining = null, $processTitles = null) {
 		if (!is_null($remaining) && is_null($processTitles))
 			ApiBase :: dieDebug(__METHOD__, 'Missing $processTitles parameter when $remaining is provided');
-			
+
 		while ($row = $db->fetchObject($res)) {
 
 			$pageId = intval($row->page_id);
@@ -402,12 +415,12 @@ class ApiPageSet extends ApiQueryBase {
 				else
 					unset ($remaining[$pageId]);
 			}
-			
+
 			// Store any extra fields requested by modules
 			$this->processDbRow($row);
 		}
 		$db->freeResult($res);
-		
+
 		if(isset($remaining)) {
 			// Any items left in the $remaining list are added as missing
 			if($processTitles) {
@@ -437,15 +450,15 @@ class ApiPageSet extends ApiQueryBase {
 
 		if(empty($revids))
 			return;
-			
+
 		$db = $this->getDB();
 		$pageids = array();
 		$remaining = array_flip($revids);
-		
+
 		$tables = array('revision');
 		$fields = array('rev_id','rev_page');
 		$where = array('rev_deleted' => 0, 'rev_id' => $revids);
-		
+
 		// Get pageIDs data from the `page` table
 		$this->profileDBIn();
 		$res = $db->select( $tables, $fields, $where,  __METHOD__ );
@@ -472,27 +485,27 @@ class ApiPageSet extends ApiQueryBase {
 		if($this->mResolveRedirects) {
 			$db = $this->getDB();
 			$pageFlds = $this->getPageTableFields();
-	
+
 			// Repeat until all redirects have been resolved
 			// The infinite loop is prevented by keeping all known pages in $this->mAllPages
-			while (!empty ($this->mPendingRedirectIDs)) {			
-	
+			while (!empty ($this->mPendingRedirectIDs)) {
+
 				// Resolve redirects by querying the pagelinks table, and repeat the process
 				// Create a new linkBatch object for the next pass
 				$linkBatch = $this->getRedirectTargets();
-	
+
 				if ($linkBatch->isEmpty())
 					break;
-					
+
 				$set = $linkBatch->constructSet('page', $db);
 				if(false === $set)
 					break;
-		
+
 				// Get pageIDs data from the `page` table
 				$this->profileDBIn();
 				$res = $db->select('page', $pageFlds, $set, __METHOD__);
 				$this->profileDBOut();
-			
+
 				// Hack: get the ns:titles stored in array(ns => array(titles)) format
 				$this->initFromQueryResult($db, $res, $linkBatch->data, true);
 			}
@@ -500,76 +513,55 @@ class ApiPageSet extends ApiQueryBase {
 	}
 
 	private function getRedirectTargets() {
-
-		$linkBatch = new LinkBatch();
+		$lb = new LinkBatch();
 		$db = $this->getDB();
 
-		// find redirect targets for all redirect pages
 		$this->profileDBIn();
-		$res = $db->select('pagelinks', array (
-			'pl_from',
-			'pl_namespace',
-			'pl_title'
-		), array (
-			'pl_from' => array_keys($this->mPendingRedirectIDs
-		)), __METHOD__);
+		$res = $db->select('redirect', array(
+				'rd_from',
+				'rd_namespace',
+				'rd_title'
+			), array('rd_from' => array_keys($this->mPendingRedirectIDs)),
+			__METHOD__
+		);
 		$this->profileDBOut();
 
-		while ($row = $db->fetchObject($res)) {
-
-			$plfrom = intval($row->pl_from);
-
-			// Bug 7304 workaround 
-			// ( http://bugzilla.wikipedia.org/show_bug.cgi?id=7304 )
-			// A redirect page may have more than one link.
-			// This code will only use the first link returned. 
-			if (isset ($this->mPendingRedirectIDs[$plfrom])) { // remove line when bug 7304 is fixed 
-
-				$titleStrFrom = $this->mPendingRedirectIDs[$plfrom]->getPrefixedText();
-				$titleStrTo = Title :: makeTitle($row->pl_namespace, $row->pl_title)->getPrefixedText();
-				unset ($this->mPendingRedirectIDs[$plfrom]); // remove line when bug 7304 is fixed
-
-				// Avoid an infinite loop by checking if we have already processed this target
-				if (!isset ($this->mAllPages[$row->pl_namespace][$row->pl_title])) {
-					$linkBatch->add($row->pl_namespace, $row->pl_title);
-				}
-			} else {
-				// This redirect page has more than one link.
-				// This is very slow, but safer until bug 7304 is resolved
-				$title = Title :: newFromID($plfrom);
-				$titleStrFrom = $title->getPrefixedText();
-
+		while($row = $db->fetchObject($res))
+		{
+			$rdfrom = intval($row->rd_from);
+			$from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
+			$to = Title::makeTitle($row->rd_namespace, $row->rd_title)->getPrefixedText();
+			unset($this->mPendingRedirectIDs[$rdfrom]);
+			if(!isset($this->mAllPages[$row->rd_namespace][$row->rd_title]))
+				$lb->add($row->rd_namespace, $row->rd_title);
+			$this->mRedirectTitles[$from] = $to;
+		}
+		$db->freeResult($res);
+		if(!empty($this->mPendingRedirectIDs))
+		{
+			# We found pages that aren't in the redirect table
+			# Add them
+			foreach($this->mPendingRedirectIDs as $id => $title)
+			{
 				$article = new Article($title);
-				$text = $article->getContent();
-				$titleTo = Title :: newFromRedirect($text);
-				$titleStrTo = $titleTo->getPrefixedText();
-
-				if (is_null($titleStrTo))
-					ApiBase :: dieDebug(__METHOD__, 'Bug7304 workaround: redir target from {$title->getPrefixedText()} not found');
-
-				// Avoid an infinite loop by checking if we have already processed this target
-				if (!isset ($this->mAllPages[$titleTo->getNamespace()][$titleTo->getDBkey()])) {
-					$linkBatch->addObj($titleTo);
-				}
+				$rt = $article->insertRedirect();
+				if(!$rt)
+					# What the hell. Let's just ignore this
+					continue;
+				$lb->addObj($rt);
+				$this->mRedirectTitles[$title->getPrefixedText()] = $rt->getPrefixedText();
+				unset($this->mPendingRedirectIDs[$id]);
 			}
-
-			$this->mRedirectTitles[$titleStrFrom] = $titleStrTo;
 		}
-		$db->freeResult($res);
-
-		// All IDs must exist in the page table
-		if (!empty($this->mPendingRedirectIDs[$plfrom]))
-			ApiBase :: dieDebug(__METHOD__, 'Invalid redirect IDs were found');
-
-		return $linkBatch;
+		return $lb;
 	}
 
 	/**
 	 * Given an array of title strings, convert them into Title objects.
 	 * Alternativelly, an array of Title objects may be given.
-	 * This method validates access rights for the title, 
+	 * This method validates access rights for the title,
 	 * and appends normalization values to the output.
-	 * 
+	 *
 	 * @return LinkBatch of title objects.
 	 */
 	private function processTitlesArray($titles) {
@@ -577,16 +569,21 @@ class ApiPageSet extends ApiQueryBase {
 		$linkBatch = new LinkBatch();
 
 		foreach ($titles as $title) {
-			
+
 			$titleObj = is_string($title) ? Title :: newFromText($title) : $title;
 			if (!$titleObj)
-				$this->dieUsage("bad title", 'invalidtitle');
-
+			{
+				# Handle invalid titles gracefully
+				$this->mAllpages[0][$title] = $this->mFakePageId;
+				$this->mInvalidTitles[$this->mFakePageId] = $title;
+				$this->mFakePageId--;
+				continue; // There's nothing else we can do
+			}
 			$iw = $titleObj->getInterwiki();
 			if (!empty($iw)) {
 				// This title is an interwiki link.
 				$this->mInterwikiTitles[$titleObj->getPrefixedText()] = $iw;
-			} else { 
+			} else {
 
 				// Validation
 				if ($titleObj->getNamespace() < 0)
@@ -594,7 +591,7 @@ class ApiPageSet extends ApiQueryBase {
 
 				$linkBatch->addObj($titleObj);
 			}
-			
+
 			// Make sure we remember the original title that was given to us
 			// This way the caller can correlate new titles with the originally requested,
 			// i.e. namespace is localized or capitalization is different
@@ -605,7 +602,7 @@ class ApiPageSet extends ApiQueryBase {
 
 		return $linkBatch;
 	}
-	
+
 	protected function getAllowedParams() {
 		return array (
 			'titles' => array (
@@ -631,7 +628,6 @@ class ApiPageSet extends ApiQueryBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiPageSet.php 24935 2007-08-20 08:13:16Z nickj $';
+		return __CLASS__ . ': $Id: ApiPageSet.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiParamInfo.php 1:1.13.0-1/includes/api/ApiParamInfo.php
--- 1:1.12.0-2/includes/api/ApiParamInfo.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiParamInfo.php	2008-05-20 18:13:28.000000000 +0100
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiParamInfo extends ApiBase {
 
@@ -55,7 +55,7 @@ class ApiParamInfo extends ApiBase {
 				$obj = new $modArr[$m]($this->getMain(), $m);
 				$a = $this->getClassInfo($obj);
 				$a['name'] = $m;
-				$r['modules'][] = $a;				
+				$r['modules'][] = $a;
 			}
 			$result->setIndexedTagName($r['modules'], 'module');
 		}
@@ -106,7 +106,7 @@ class ApiParamInfo extends ApiBase {
 				$retval['parameters'][] = $a;
 				continue;
 			}
-					
+
 			if(isset($p[ApiBase::PARAM_DFLT]))
 				$a['default'] = $p[ApiBase::PARAM_DFLT];
 			if(isset($p[ApiBase::PARAM_ISMULTI]))
@@ -131,7 +131,7 @@ class ApiParamInfo extends ApiBase {
 		$result->setIndexedTagName($retval['parameters'], 'param');
 		return $retval;
 	}
-	
+
 	public function getAllowedParams() {
 		return array (
 			'modules' => array(
@@ -161,7 +161,6 @@ class ApiParamInfo extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiParse.php 29810 2008-01-15 21:33:08Z catrope $';
+		return __CLASS__ . ': $Id: ApiParamInfo.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiParse.php 1:1.13.0-1/includes/api/ApiParse.php
--- 1:1.12.0-2/includes/api/ApiParse.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiParse.php	2008-07-03 16:01:50.000000000 +0100
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiParse extends ApiBase {
 
@@ -43,31 +43,56 @@ class ApiParse extends ApiBase {
 		$text = $params['text'];
 		$title = $params['title'];
 		$page = $params['page'];
+		$oldid = $params['oldid'];
 		if(!is_null($page) && (!is_null($text) || $title != "API"))
 			$this->dieUsage("The page parameter cannot be used together with the text and title parameters", 'params');
 		$prop = array_flip($params['prop']);
-		
-		global $wgParser, $wgUser;
-		if(!is_null($page)) {
-			$titleObj = Title::newFromText($page);
-			if(!$titleObj)
-				$this->dieUsageMsg(array('missingtitle', $page));
+		$revid = false;
 
-			// Try the parser cache first
-			$articleObj = new Article($titleObj);
-			$pcache =& ParserCache::singleton();
-			$p_result = $pcache->get($articleObj, $wgUser);
-			if(!$p_result) {
-				$p_result = $wgParser->parse($articleObj->getContent(), $titleObj, new ParserOptions());
-				global $wgUseParserCache;
-				if($wgUseParserCache)
-					$pcache->save($p_result, $articleObj, $wgUser);
+		global $wgParser, $wgUser;
+		$popts = new ParserOptions();
+		$popts->setTidy(true);
+		$popts->enableLimitReport();
+		if(!is_null($oldid) || !is_null($page))
+		{
+			if(!is_null($oldid))
+			{
+				# Don't use the parser cache
+				$rev = Revision::newFromID($oldid);
+				if(!$rev)
+					$this->dieUsage("There is no revision ID $oldid", 'missingrev');
+				if(!$rev->userCan(Revision::DELETED_TEXT))
+					$this->dieUsage("You don't have permission to view deleted revisions", 'permissiondenied');
+				$text = $rev->getRawText();
+				$titleObj = $rev->getTitle();
+				$p_result = $wgParser->parse($text, $titleObj, $popts);
 			}
-		} else {
+			else
+			{
+				$titleObj = Title::newFromText($page);
+				if(!$titleObj)
+					$this->dieUsage("The page you specified doesn't exist", 'missingtitle');
+
+				// Try the parser cache first
+				$articleObj = new Article($titleObj);
+				if(isset($prop['revid']))
+					$oldid = $articleObj->getRevIdFetched();
+				$pcache = ParserCache::singleton();
+				$p_result = $pcache->get($articleObj, $wgUser);
+				if(!$p_result) {
+					$p_result = $wgParser->parse($articleObj->getContent(), $titleObj, $popts);
+					global $wgUseParserCache;
+					if($wgUseParserCache)
+						$pcache->save($p_result, $articleObj, $wgUser);
+				}
+			}
+		}
+		else
+		{
 			$titleObj = Title::newFromText($title);
 			if(!$titleObj)
 				$titleObj = Title::newFromText("API");
-			$p_result = $wgParser->parse($text, $titleObj, new ParserOptions());
+			$p_result = $wgParser->parse($text, $titleObj, $popts);
 		}
 
 		// Return result
@@ -91,6 +116,8 @@ class ApiParse extends ApiBase {
 			$result_array['externallinks'] = array_keys($p_result->getExternalLinks());
 		if(isset($prop['sections']))
 			$result_array['sections'] = $p_result->getSections();
+		if(!is_null($oldid))
+			$result_array['revid'] = $oldid;
 
 		$result_mapping = array(
 			'langlinks' => 'll',
@@ -104,7 +131,7 @@ class ApiParse extends ApiBase {
 		$this->setIndexedTagNames( $result_array, $result_mapping );
 		$result->addValue( null, $this->getModuleName(), $result_array );
 	}
-	
+
 	private function formatLangLinks( $links ) {
 		$result = array();
 		foreach( $links as $link ) {
@@ -116,7 +143,7 @@ class ApiParse extends ApiBase {
 		}
 		return $result;
 	}
-	
+
 	private function formatCategoryLinks( $links ) {
 		$result = array();
 		foreach( $links as $link => $sortkey ) {
@@ -127,7 +154,7 @@ class ApiParse extends ApiBase {
 		}
 		return $result;
 	}
-	
+
 	private function formatLinks( $links ) {
 		$result = array();
 		foreach( $links as $ns => $nslinks ) {
@@ -142,7 +169,7 @@ class ApiParse extends ApiBase {
 		}
 		return $result;
 	}
-	
+
 	private function setIndexedTagNames( &$array, $mapping ) {
 		foreach( $mapping as $key => $name ) {
 			if( isset( $array[$key] ) )
@@ -152,13 +179,14 @@ class ApiParse extends ApiBase {
 
 	public function getAllowedParams() {
 		return array (
-			'title' => array( 
+			'title' => array(
 				ApiBase :: PARAM_DFLT => 'API',
 			),
 			'text' => null,
 			'page' => null,
+			'oldid' => null,
 			'prop' => array(
-				ApiBase :: PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections',
+				ApiBase :: PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid',
 				ApiBase :: PARAM_ISMULTI => true,
 				ApiBase :: PARAM_TYPE => array(
 					'text',
@@ -168,7 +196,8 @@ class ApiParse extends ApiBase {
 					'templates',
 					'images',
 					'externallinks',
-					'sections'
+					'sections',
+					'revid'
 				)
 			)
 		);
@@ -179,6 +208,7 @@ class ApiParse extends ApiBase {
 			'text' => 'Wikitext to parse',
 			'title' => 'Title of page the text belongs to',
 			'page' => 'Parse the content of this page. Cannot be used together with text and title',
+			'oldid' => 'Parse the content of this revision. Overrides page',
 			'prop' => array('Which pieces of information to get.',
 					'NOTE: Section tree is only generated if there are more than 4 sections, or if the __TOC__ keyword is present'
 			),
@@ -196,7 +226,6 @@ class ApiParse extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiParse.php 30262 2008-01-29 14:47:27Z catrope $';
+		return __CLASS__ . ': $Id: ApiParse.php 36983 2008-07-03 15:01:50Z catrope $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiProtect.php 1:1.13.0-1/includes/api/ApiProtect.php
--- 1:1.12.0-2/includes/api/ApiProtect.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiProtect.php	2008-05-20 18:13:28.000000000 +0100
@@ -28,7 +28,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiProtect extends ApiBase {
 
@@ -40,7 +40,7 @@ class ApiProtect extends ApiBase {
 		global $wgUser;
 		$this->getMain()->requestWriteMode();
 		$params = $this->extractRequestParams();
-		
+
 		$titleObj = NULL;
 		if(!isset($params['title']))
 			$this->dieUsageMsg(array('missingparam', 'title'));
@@ -55,12 +55,12 @@ class ApiProtect extends ApiBase {
 		$titleObj = Title::newFromText($params['title']);
 		if(!$titleObj)
 			$this->dieUsageMsg(array('invalidtitle', $params['title']));
-			
+
 		$errors = $titleObj->getUserPermissionsErrors('protect', $wgUser);
 		if(!empty($errors))
 			// We don't care about multiple errors, just report one of them
 			$this->dieUsageMsg(current($errors));
-		
+
 		if(in_array($params['expiry'], array('infinite', 'indefinite', 'never')))
 			$expiry = Block::infinity();
 		else
@@ -68,7 +68,7 @@ class ApiProtect extends ApiBase {
 			$expiry = strtotime($params['expiry']);
 			if($expiry < 0 || $expiry == false)
 				$this->dieUsageMsg(array('invalidexpiry'));
-			
+
 			$expiry = wfTimestamp(TS_MW, $expiry);
 			if($expiry < wfTimestampNow())
 				$this->dieUsageMsg(array('pastexpiry'));
@@ -85,8 +85,6 @@ class ApiProtect extends ApiBase {
 				$this->dieUsageMsg(array('missingtitles-createonly'));
 		}
 
-		$dbw = wfGetDb(DB_MASTER);
-		$dbw->begin();
 		if($titleObj->exists()) {
 			$articleObj = new Article($titleObj);
 			$ok = $articleObj->updateRestrictions($protections, $params['reason'], $params['cascade'], $expiry);
@@ -96,7 +94,6 @@ class ApiProtect extends ApiBase {
 			// This is very weird. Maybe the article was deleted or the user was blocked/desysopped in the meantime?
 			// Just throw an unknown error in this case, as it's very likely to be a race condition
 			$this->dieUsageMsg(array());
-		$dbw->commit();
 		$res = array('title' => $titleObj->getPrefixedText(), 'reason' => $params['reason']);
 		if($expiry == Block::infinity())
 			$res['expiry'] = 'infinity';
@@ -149,6 +146,6 @@ class ApiProtect extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiProtect.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiProtect.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryAllCategories.php 1:1.13.0-1/includes/api/ApiQueryAllCategories.php
--- 1:1.12.0-2/includes/api/ApiQueryAllCategories.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryAllCategories.php	2008-06-29 23:26:23.000000000 +0100
@@ -31,8 +31,8 @@ if (!defined('MEDIAWIKI')) {
 /**
  * Query module to enumerate all categories, even the ones that don't have
  * category pages.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryAllCategories extends ApiQueryGeneratorBase {
 
@@ -53,44 +53,58 @@ class ApiQueryAllCategories extends ApiQ
 		$db = $this->getDB();
 		$params = $this->extractRequestParams();
 
-		$this->addTables('categorylinks');
-		$this->addFields('cl_to');
-		
+		$this->addTables('category');
+		$this->addFields('cat_title');
+
 		if (!is_null($params['from']))
-			$this->addWhere('cl_to>=' . $db->addQuotes(ApiQueryBase :: titleToKey($params['from'])));
+			$this->addWhere('cat_title>=' . $db->addQuotes($this->titleToKey($params['from'])));
 		if (isset ($params['prefix']))
-			$this->addWhere("cl_to LIKE '" . $db->escapeLike(ApiQueryBase :: titleToKey($params['prefix'])) . "%'");
+			$this->addWhere("cat_title LIKE '" . $db->escapeLike($this->titleToKey($params['prefix'])) . "%'");
 
 		$this->addOption('LIMIT', $params['limit']+1);
-		$this->addOption('ORDER BY', 'cl_to' . ($params['dir'] == 'descending' ? ' DESC' : ''));
-		$this->addOption('DISTINCT');
+		$this->addOption('ORDER BY', 'cat_title' . ($params['dir'] == 'descending' ? ' DESC' : ''));
+
+		$prop = array_flip($params['prop']);
+		$this->addFieldsIf( array( 'cat_pages', 'cat_subcats', 'cat_files' ), isset($prop['size']) );
+		$this->addFieldsIf( 'cat_hidden', isset($prop['hidden']) );
 
 		$res = $this->select(__METHOD__);
 
 		$pages = array();
+		$categories = array();
+		$result = $this->getResult();
 		$count = 0;
 		while ($row = $db->fetchObject($res)) {
 			if (++ $count > $params['limit']) {
 				// We've reached the one extra which shows that there are additional cats to be had. Stop here...
 				// TODO: Security issue - if the user has no right to view next title, it will still be shown
-				$this->setContinueEnumParameter('from', ApiQueryBase :: keyToTitle($row->cl_to));
+				$this->setContinueEnumParameter('from', $this->keyToTitle($row->cat_title));
 				break;
 			}
-			
+
 			// Normalize titles
-			$titleObj = Title::makeTitle(NS_CATEGORY, $row->cl_to);
+			$titleObj = Title::makeTitle(NS_CATEGORY, $row->cat_title);
 			if(!is_null($resultPageSet))
 				$pages[] = $titleObj->getPrefixedText();
-			else
-				// Don't show "Category:" everywhere in non-generator mode
-				$pages[] = $titleObj->getText();
+			else {
+				$item = array();
+				$result->setContent( $item, $titleObj->getText() );
+				if( isset( $prop['size'] ) ) {
+					$item['size'] = $row->cat_pages;
+					$item['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files;
+					$item['files'] = $row->cat_files;
+					$item['subcats'] = $row->cat_subcats;
+				}
+				if( isset( $prop['hidden'] ) && $row->cat_hidden )
+					$item['hidden'] = '';
+				$categories[] = $item;
+			}
 		}
 		$db->freeResult($res);
 
 		if (is_null($resultPageSet)) {
-			$result = $this->getResult();
-			$result->setIndexedTagName($pages, 'c');
-			$result->addValue('query', $this->getModuleName(), $pages);
+			$result->setIndexedTagName($categories, 'c');
+			$result->addValue('query', $this->getModuleName(), $categories);
 		} else {
 			$resultPageSet->populateFromTitles($pages);
 		}
@@ -113,7 +127,12 @@ class ApiQueryAllCategories extends ApiQ
 				ApiBase :: PARAM_MIN => 1,
 				ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
 				ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
-			)
+			),
+			'prop' => array (
+				ApiBase :: PARAM_TYPE => array( 'size', 'hidden' ),
+				ApiBase :: PARAM_DFLT => '',
+				ApiBase :: PARAM_ISMULTI => true
+			),
 		);
 	}
 
@@ -122,7 +141,8 @@ class ApiQueryAllCategories extends ApiQ
 			'from' => 'The category to start enumerating from.',
 			'prefix' => 'Search for all category titles that begin with this value.',
 			'dir' => 'Direction to sort in.',
-			'limit' => 'How many categories to return.'
+			'limit' => 'How many categories to return.',
+			'prop' => 'Which properties to get',
 		);
 	}
 
@@ -132,11 +152,12 @@ class ApiQueryAllCategories extends ApiQ
 
 	protected function getExamples() {
 		return array (
+			'api.php?action=query&list=allcategories&acprop=size',
 			'api.php?action=query&generator=allcategories&gacprefix=List&prop=info',
 		);
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryAllLinks.php 28216 2007-12-06 18:33:18Z vasilievvv $';
+		return __CLASS__ . ': $Id: ApiQueryAllCategories.php 36790 2008-06-29 22:26:23Z catrope $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryAllimages.php 1:1.13.0-1/includes/api/ApiQueryAllimages.php
--- 1:1.12.0-2/includes/api/ApiQueryAllimages.php	1970-01-01 01:00:00.000000000 +0100
+++ 1:1.13.0-1/includes/api/ApiQueryAllimages.php	2008-07-22 14:26:15.000000000 +0100
@@ -0,0 +1,205 @@
+<?php
+
+/*
+ * Created on Mar 16, 2008
+ *
+ * API for MediaWiki 1.12+
+ *
+ * Copyright (C) 2008 Vasiliev Victor vasilvv@gmail.com,
+ * based on ApiQueryAllpages.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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+	// Eclipse helper - will be ignored in production
+	require_once ('ApiQueryBase.php');
+}
+
+/**
+ * Query module to enumerate all available pages.
+ *
+ * @ingroup API
+ */
+class ApiQueryAllimages extends ApiQueryGeneratorBase {
+
+	public function __construct($query, $moduleName) {
+		parent :: __construct($query, $moduleName, 'ai');
+	}
+
+	public function execute() {
+		$this->run();
+	}
+
+	public function executeGenerator($resultPageSet) {
+		if ($resultPageSet->isResolvingRedirects())
+			$this->dieUsage('Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator', 'params');
+
+		$this->run($resultPageSet);
+	}
+
+	private function run($resultPageSet = null) {
+		$repo = RepoGroup::singleton()->getLocalRepo();
+		if ( !$repo instanceof LocalRepo )
+			$this->dieUsage('Local file repository does not support querying all images', 'unsupportedrepo');
+
+		$db = $this->getDB();
+
+		$params = $this->extractRequestParams();
+
+		// Image filters
+		if (!is_null($params['from']))
+			$this->addWhere('img_name>=' . $db->addQuotes($this->titleToKey($params['from'])));
+		if (isset ($params['prefix']))
+			$this->addWhere("img_name LIKE '" . $db->escapeLike($this->titleToKey($params['prefix'])) . "%'");
+
+		if (isset ($params['minsize'])) {
+			$this->addWhere('img_size>=' . intval($params['minsize']));
+		}
+
+		if (isset ($params['maxsize'])) {
+			$this->addWhere('img_size<=' . intval($params['maxsize']));
+		}
+
+		$sha1 = false;
+		if( isset( $params['sha1'] ) ) {
+			$sha1 = wfBaseConvert( $params['sha1'], 16, 36, 31 );
+		} elseif( isset( $params['sha1base36'] ) ) {
+			$sha1 = $params['sha1base36'];
+		}
+		if( $sha1 ) {
+			$this->addWhere( 'img_sha1=' . $db->addQuotes( $sha1 ) );
+		}
+
+		$this->addTables('image');
+
+		$prop = array_flip($params['prop']);
+		$this->addFields( LocalFile::selectFields() );
+
+		$limit = $params['limit'];
+		$this->addOption('LIMIT', $limit+1);
+		$this->addOption('ORDER BY', 'img_name' .
+						($params['dir'] == 'descending' ? ' DESC' : ''));
+
+		$res = $this->select(__METHOD__);
+
+		$data = array ();
+		$count = 0;
+		$result = $this->getResult();
+		while ($row = $db->fetchObject($res)) {
+			if (++ $count > $limit) {
+				// We've reached the one extra which shows that there are additional pages to be had. Stop here...
+				// TODO: Security issue - if the user has no right to view next title, it will still be shown
+				$this->setContinueEnumParameter('from', $this->keyToTitle($row->img_name));
+				break;
+			}
+
+			if (is_null($resultPageSet)) {
+				$file = $repo->newFileFromRow( $row );
+
+				$data[] = ApiQueryImageInfo::getInfo( $file, $prop, $result );
+			} else {
+				$data[] = Title::makeTitle( NS_IMAGE, $row->img_name );
+			}
+		}
+		$db->freeResult($res);
+
+		if (is_null($resultPageSet)) {
+			$result = $this->getResult();
+			$result->setIndexedTagName($data, 'img');
+			$result->addValue('query', $this->getModuleName(), $data);
+		} else {
+			$resultPageSet->populateFromTitles( $data );
+		}
+	}
+
+	public function getAllowedParams() {
+		return array (
+			'from' => null,
+			'prefix' => null,
+			'minsize' => array (
+				ApiBase :: PARAM_TYPE => 'integer',
+			),
+			'maxsize' => array (
+				ApiBase :: PARAM_TYPE => 'integer',
+			),
+			'limit' => array (
+				ApiBase :: PARAM_DFLT => 10,
+				ApiBase :: PARAM_TYPE => 'limit',
+				ApiBase :: PARAM_MIN => 1,
+				ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
+				ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+			),
+			'dir' => array (
+				ApiBase :: PARAM_DFLT => 'ascending',
+				ApiBase :: PARAM_TYPE => array (
+					'ascending',
+					'descending'
+				)
+			),
+			'sha1' => null,
+			'sha1base36' => null,
+			'prop' => array (
+				ApiBase :: PARAM_TYPE => array(
+					'timestamp',
+					'user',
+					'comment',
+					'url',
+					'size',
+					'dimensions', // Obsolete
+					'mime',
+					'sha1',
+					'metadata'
+				),
+				ApiBase :: PARAM_DFLT => 'timestamp|url',
+				ApiBase :: PARAM_ISMULTI => true
+			)
+		);
+	}
+
+	public function getParamDescription() {
+		return array (
+			'from' => 'The image title to start enumerating from.',
+			'prefix' => 'Search for all image titles that begin with this value.',
+			'dir' => 'The direction in which to list',
+			'minsize' => 'Limit to images with at least this many bytes',
+			'maxsize' => 'Limit to images with at most this many bytes',
+			'limit' => 'How many total images to return.',
+			'sha1' => 'SHA1 hash of image',
+			'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki)',
+			'prop' => 'Which properties to get',
+		);
+	}
+
+	public function getDescription() {
+		return 'Enumerate all images sequentially';
+	}
+
+	protected function getExamples() {
+		return array (
+			'Simple Use',
+			' Show a list of images starting at the letter "B"',
+			'  api.php?action=query&list=allimages&aifrom=B',
+			'Using as Generator',
+			' Show info about 4 images starting at the letter "T"',
+			'  api.php?action=query&generator=allimages&gailimit=4&gaifrom=T&prop=imageinfo',
+		);
+	}
+
+	public function getVersion() {
+		return __CLASS__ . ': $Id: ApiQueryAllimages.php 37909 2008-07-22 13:26:15Z catrope $';
+	}
+}
diff -pruN 1:1.12.0-2/includes/api/ApiQueryAllLinks.php 1:1.13.0-1/includes/api/ApiQueryAllLinks.php
--- 1:1.12.0-2/includes/api/ApiQueryAllLinks.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryAllLinks.php	2008-07-07 15:48:40.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * Query module to enumerate links from all pages together.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryAllLinks extends ApiQueryGeneratorBase {
 
@@ -67,26 +67,37 @@ class ApiQueryAllLinks extends ApiQueryG
 		$this->addTables('pagelinks');
 		$this->addWhereFld('pl_namespace', $params['namespace']);
 		
+		if (!is_null($params['from']) && !is_null($params['continue']))
+			$this->dieUsage('alcontinue and alfrom cannot be used together', 'params');
+		if (!is_null($params['continue']))
+		{
+			$arr = explode('|', $params['continue']);
+			if(count($arr) != 2)
+				$this->dieUsage("Invalid continue parameter", 'badcontinue');
+			$params['from'] = $arr[0]; // Handled later
+			$id = intval($arr[1]);
+			$this->addWhere("pl_from >= $id");
+		}		
+
 		if (!is_null($params['from']))
-			$this->addWhere('pl_title>=' . $db->addQuotes(ApiQueryBase :: titleToKey($params['from'])));
+			$this->addWhere('pl_title>=' . $db->addQuotes($this->titleToKey($params['from'])));
 		if (isset ($params['prefix']))
-			$this->addWhere("pl_title LIKE '" . $db->escapeLike(ApiQueryBase :: titleToKey($params['prefix'])) . "%'");
+			$this->addWhere("pl_title LIKE '" . $db->escapeLike($this->titleToKey($params['prefix'])) . "%'");
 
-		if (is_null($resultPageSet)) {
-			$this->addFields(array (
-				'pl_namespace',
-				'pl_title'
-			));
-			$this->addFieldsIf('pl_from', $fld_ids);
-		} else {
-			$this->addFields('pl_from');
-			$pageids = array();
-		}
+		$this->addFields(array (
+			'pl_namespace',
+			'pl_title',
+			'pl_from'
+		));
 
 		$this->addOption('USE INDEX', 'pl_namespace');
 		$limit = $params['limit'];
 		$this->addOption('LIMIT', $limit+1);
-		$this->addOption('ORDER BY', 'pl_namespace, pl_title');
+		# Only order by pl_namespace if it isn't constant in the WHERE clause
+		if(count($params['namespace']) != 1)
+			$this->addOption('ORDER BY', 'pl_namespace, pl_title');
+		else
+			$this->addOption('ORDER BY', 'pl_title');
 
 		$res = $this->select(__METHOD__);
 
@@ -96,7 +107,7 @@ class ApiQueryAllLinks extends ApiQueryG
 			if (++ $count > $limit) {
 				// We've reached the one extra which shows that there are additional pages to be had. Stop here...
 				// TODO: Security issue - if the user has no right to view next title, it will still be shown
-				$this->setContinueEnumParameter('from', ApiQueryBase :: keyToTitle($row->pl_title));
+				$this->setContinueEnumParameter('continue', $this->keyToTitle($row->pl_title) . "|" . $row->pl_from);
 				break;
 			}
 
@@ -127,6 +138,7 @@ class ApiQueryAllLinks extends ApiQueryG
 
 	public function getAllowedParams() {
 		return array (
+			'continue' => null,
 			'from' => null,
 			'prefix' => null,
 			'unique' => false,
@@ -159,7 +171,8 @@ class ApiQueryAllLinks extends ApiQueryG
 			'unique' => 'Only show unique links. Cannot be used with generator or prop=ids',
 			'prop' => 'What pieces of information to include',
 			'namespace' => 'The namespace to enumerate.',
-			'limit' => 'How many total links to return.'
+			'limit' => 'How many total links to return.',
+			'continue' => 'When more results are available, use this to continue.',
 		);
 	}
 
@@ -174,6 +187,6 @@ class ApiQueryAllLinks extends ApiQueryG
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryAllLinks.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryAllLinks.php 37258 2008-07-07 14:48:40Z catrope $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryAllmessages.php 1:1.13.0-1/includes/api/ApiQueryAllmessages.php
--- 1:1.12.0-2/includes/api/ApiQueryAllmessages.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryAllmessages.php	2008-07-10 15:28:09.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * A query action to return messages from site message cache
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryAllmessages extends ApiQueryBase {
 
@@ -42,13 +42,13 @@ class ApiQueryAllmessages extends ApiQue
 	public function execute() {
 		global $wgMessageCache;
 		$params = $this->extractRequestParams();
-		
+
 		if(!is_null($params['lang']))
 		{
 			global $wgLang;
 			$wgLang = Language::factory($params['lang']);
 		}
-			
+
 
 		//Determine which messages should we print
 		$messages_target = array();
@@ -60,7 +60,7 @@ class ApiQueryAllmessages extends ApiQue
 		} else {
 			$messages_target = explode( '|', $params['messages'] );
 		}
-		
+
 		//Filter messages
 		if( isset( $params['filter'] ) ) {
 			$messages_filtered = array();
@@ -72,12 +72,9 @@ class ApiQueryAllmessages extends ApiQue
 			$messages_target = $messages_filtered;
 		}
 
-		$wgMessageCache->disableTransform();
-
 		//Get all requested messages
 		$messages = array();
 		foreach( $messages_target as $message ) {
-			$message = trim( $message );	//Message list can be formatted like "msg1 | msg2 | msg3", so let's trim() it
 			$messages[$message] = wfMsg( $message );
 		}
 
@@ -87,7 +84,11 @@ class ApiQueryAllmessages extends ApiQue
 		foreach( $messages as $name => $value ) {
 			$message = array();
 			$message['name'] = $name;
-			$result->setContent( $message, $value );
+			if( wfEmptyMsg( $name, $value ) ) {
+				$message['missing'] = '';
+			} else {
+				$result->setContent( $message, $value );
+			}
 			$messages_out[] = $message;
 		}
 		$result->setIndexedTagName( $messages_out, 'message' );
@@ -107,8 +108,8 @@ class ApiQueryAllmessages extends ApiQue
 	public function getParamDescription() {
 		return array (
 			'messages' => 'Which messages to output. "*" means all messages',
-			'filter' => 'Return only messages that contains specified string',
-			'lang' => 'Language code',
+			'filter' => 'Return only messages that contain this string',
+			'lang' => 'Return messages in this language',
 		);
 	}
 
@@ -124,6 +125,6 @@ class ApiQueryAllmessages extends ApiQue
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryAllmessages.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryAllmessages.php 37504 2008-07-10 14:28:09Z catrope $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryAllpages.php 1:1.13.0-1/includes/api/ApiQueryAllpages.php
--- 1:1.12.0-2/includes/api/ApiQueryAllpages.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryAllpages.php	2008-07-17 10:26:01.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * Query module to enumerate all available pages.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryAllpages extends ApiQueryGeneratorBase {
 
@@ -55,27 +55,29 @@ class ApiQueryAllpages extends ApiQueryG
 		$db = $this->getDB();
 
 		$params = $this->extractRequestParams();
-		
+
 		// Page filters
+		$this->addTables('page');
 		if (!$this->addWhereIf('page_is_redirect = 1', $params['filterredir'] === 'redirects'))
 			$this->addWhereIf('page_is_redirect = 0', $params['filterredir'] === 'nonredirects');
 		$this->addWhereFld('page_namespace', $params['namespace']);
-		if (!is_null($params['from']))
-			$this->addWhere('page_title>=' . $db->addQuotes(ApiQueryBase :: titleToKey($params['from'])));
+		$dir = ($params['dir'] == 'descending' ? 'older' : 'newer');
+		$from = (is_null($params['from']) ? null : $this->titleToKey($params['from']));
+		$this->addWhereRange('page_title', $dir, $from, null);
 		if (isset ($params['prefix']))
-			$this->addWhere("page_title LIKE '" . $db->escapeLike(ApiQueryBase :: titleToKey($params['prefix'])) . "%'");
+			$this->addWhere("page_title LIKE '" . $db->escapeLike($this->titleToKey($params['prefix'])) . "%'");
 
 		$forceNameTitleIndex = true;
 		if (isset ($params['minsize'])) {
 			$this->addWhere('page_len>=' . intval($params['minsize']));
 			$forceNameTitleIndex = false;
 		}
-		
+
 		if (isset ($params['maxsize'])) {
 			$this->addWhere('page_len<=' . intval($params['maxsize']));
 			$forceNameTitleIndex = false;
 		}
-	
+
 		// Page protection filtering
 		if (isset ($params['prtype'])) {
 			$this->addTables('page_restrictions');
@@ -86,7 +88,7 @@ class ApiQueryAllpages extends ApiQueryG
 			$prlevel = $params['prlevel'];
 			if (!is_null($prlevel) && $prlevel != '' && $prlevel != '*')
 				$this->addWhereFld('pr_level', $prlevel);
-				
+
 			$this->addOption('DISTINCT');
 
 			$forceNameTitleIndex = false;
@@ -94,20 +96,16 @@ class ApiQueryAllpages extends ApiQueryG
 		} else if (isset ($params['prlevel'])) {
 			$this->dieUsage('prlevel may not be used without prtype', 'params');
 		}
-		
+
 		if($params['filterlanglinks'] == 'withoutlanglinks') {
-			$pageName = $this->getDB()->tableName('page');
-			$llName = $this->getDB()->tableName('langlinks');
-			$tables = "$pageName LEFT JOIN $llName ON page_id=ll_from";
+			$this->addTables('langlinks');
+			$this->addJoinConds(array('langlinks' => array('LEFT JOIN', 'page_id=ll_from')));
 			$this->addWhere('ll_from IS NULL');
-			$this->addTables($tables);
 			$forceNameTitleIndex = false;
 		} else if($params['filterlanglinks'] == 'withlanglinks') {
-			$this->addTables(array('page', 'langlinks'));
+			$this->addTables('langlinks');
 			$this->addWhere('page_id=ll_from');
-			$forceNameTitleIndex = false;		
-		} else {
-			$this->addTables('page');
+			$forceNameTitleIndex = false;
 		}
 		if ($forceNameTitleIndex)
 			$this->addOption('USE INDEX', 'name_title');
@@ -124,9 +122,6 @@ class ApiQueryAllpages extends ApiQueryG
 
 		$limit = $params['limit'];
 		$this->addOption('LIMIT', $limit+1);
-		$this->addOption('ORDER BY', 'page_namespace, page_title' .
-						($params['dir'] == 'descending' ? ' DESC' : ''));
-
 		$res = $this->select(__METHOD__);
 
 		$data = array ();
@@ -135,7 +130,7 @@ class ApiQueryAllpages extends ApiQueryG
 			if (++ $count > $limit) {
 				// We've reached the one extra which shows that there are additional pages to be had. Stop here...
 				// TODO: Security issue - if the user has no right to view next title, it will still be shown
-				$this->setContinueEnumParameter('from', ApiQueryBase :: keyToTitle($row->page_title));
+				$this->setContinueEnumParameter('from', $this->keyToTitle($row->page_title));
 				break;
 			}
 
@@ -160,7 +155,7 @@ class ApiQueryAllpages extends ApiQueryG
 
 	public function getAllowedParams() {
 		global $wgRestrictionTypes, $wgRestrictionLevels;
-		
+
 		return array (
 			'from' => null,
 			'prefix' => null,
@@ -178,10 +173,10 @@ class ApiQueryAllpages extends ApiQueryG
 			),
 			'minsize' => array (
 				ApiBase :: PARAM_TYPE => 'integer',
-			), 
+			),
 			'maxsize' => array (
 				ApiBase :: PARAM_TYPE => 'integer',
-			), 
+			),
 			'prtype' => array (
 				ApiBase :: PARAM_TYPE => $wgRestrictionTypes,
 				ApiBase :: PARAM_ISMULTI => true
@@ -249,7 +244,6 @@ class ApiQueryAllpages extends ApiQueryG
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryAllpages.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryAllpages.php 37775 2008-07-17 09:26:01Z brion $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryAllUsers.php 1:1.13.0-1/includes/api/ApiQueryAllUsers.php
--- 1:1.12.0-2/includes/api/ApiQueryAllUsers.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryAllUsers.php	2008-06-29 23:26:23.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * Query module to enumerate all registered users.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryAllUsers extends ApiQueryBase {
 
@@ -46,26 +46,27 @@ class ApiQueryAllUsers extends ApiQueryB
 		$prop = $params['prop'];
 		if (!is_null($prop)) {
 			$prop = array_flip($prop);
+			$fld_blockinfo = isset($prop['blockinfo']);
 			$fld_editcount = isset($prop['editcount']);
 			$fld_groups = isset($prop['groups']);
 			$fld_registration = isset($prop['registration']);
-		} else {
-			$fld_editcount = $fld_groups = $fld_registration = false;
+		} else { 
+			$fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration = false;
 		}
 
 		$limit = $params['limit'];
-		$tables = $db->tableName('user');
-		
+		$this->addTables('user', 'u1');
+
 		if( !is_null( $params['from'] ) )
-			$this->addWhere( 'user_name >= ' . $db->addQuotes( self::keyToTitle( $params['from'] ) ) );
-		
+			$this->addWhere( 'u1.user_name >= ' . $db->addQuotes( $this->keyToTitle( $params['from'] ) ) );
+
 		if( isset( $params['prefix'] ) )
-			$this->addWhere( 'user_name LIKE "' . $db->escapeLike( self::keyToTitle( $params['prefix'] ) ) . '%"' );
+			$this->addWhere( 'u1.user_name LIKE "' . $db->escapeLike( $this->keyToTitle( $params['prefix'] ) ) . '%"' );
 
 		if (!is_null($params['group'])) {
 			// Filter only users that belong to a given group
-			$tblName = $db->tableName('user_groups');
-			$tables = "$tables INNER JOIN $tblName ug1 ON ug1.ug_user=user_id";
+			$this->addTables('user_groups', 'ug1');
+			$this->addWhere('ug1.ug_user=u1.user_id');
 			$this->addWhereFld('ug1.ug_group', $params['group']);
 		}
 
@@ -75,23 +76,30 @@ class ApiQueryAllUsers extends ApiQueryB
 			$groupCount = count(User::getAllGroups());
 			$sqlLimit = $limit+$groupCount+1;
 
-			$tblName = $db->tableName('user_groups');
-			$tables = "$tables LEFT JOIN $tblName ug2 ON ug2.ug_user=user_id";
+			$this->addTables('user_groups', 'ug2');
+			$tname = $this->getAliasedName('user_groups', 'ug2');
+			$this->addJoinConds(array($tname => array('LEFT JOIN', 'ug2.ug_user=u1.user_id')));
 			$this->addFields('ug2.ug_group ug_group2');
 		} else {
 			$sqlLimit = $limit+1;
 		}
-		
-		if ($fld_registration)
-			$this->addFields('user_registration');
+		if ($fld_blockinfo) {
+			$this->addTables('ipblocks');
+			$this->addTables('user', 'u2');
+			$u2 = $this->getAliasedName('user', 'u2');
+			$this->addJoinConds(array(
+				'ipblocks' => array('LEFT JOIN', 'ipb_user=u1.user_id'),
+				$u2 => array('LEFT JOIN', 'ipb_by=u2.user_id')));
+			$this->addFields(array('ipb_reason', 'u2.user_name blocker_name'));
+		}
 
 		$this->addOption('LIMIT', $sqlLimit);
-		$this->addTables($tables);
 
-		$this->addFields('user_name');
-		$this->addFieldsIf('user_editcount', $fld_editcount);
+		$this->addFields('u1.user_name');
+		$this->addFieldsIf('u1.user_editcount', $fld_editcount);
+		$this->addFieldsIf('u1.user_registration', $fld_registration);
 
-		$this->addOption('ORDER BY', 'user_name');
+		$this->addOption('ORDER BY', 'u1.user_name');
 
 		$res = $this->select(__METHOD__);
 
@@ -100,7 +108,7 @@ class ApiQueryAllUsers extends ApiQueryB
 		$lastUserData = false;
 		$lastUser = false;
 		$result = $this->getResult();
-				
+
 		//
 		// This loop keeps track of the last entry.
 		// For each new row, if the new row is for different user then the last, the last entry is added to results.
@@ -109,49 +117,53 @@ class ApiQueryAllUsers extends ApiQueryB
 		// to make sure all rows that belong to the same user are received.
 		//
 		while (true) {
-			
+
 			$row = $db->fetchObject($res);
 			$count++;
-			
+
 			if (!$row || $lastUser != $row->user_name) {
 				// Save the last pass's user data
 				if (is_array($lastUserData))
 					$data[] = $lastUserData;
-				
+
 				// No more rows left
 				if (!$row)
 					break;
 
 				if ($count > $limit) {
 					// We've reached the one extra which shows that there are additional pages to be had. Stop here...
-					$this->setContinueEnumParameter('from', ApiQueryBase :: keyToTitle($row->user_name));
+					$this->setContinueEnumParameter('from', $this->keyToTitle($row->user_name));
 					break;
 				}
 
 				// Record new user's data
 				$lastUser = $row->user_name;
 				$lastUserData = array( 'name' => $lastUser );
+				if ($fld_blockinfo) {
+					$lastUserData['blockedby'] = $row->blocker_name;
+					$lastUserData['blockreason'] = $row->ipb_reason;
+				}
 				if ($fld_editcount)
 					$lastUserData['editcount'] = intval($row->user_editcount);
 				if ($fld_registration)
 					$lastUserData['registration'] = wfTimestamp(TS_ISO_8601, $row->user_registration);
-					
+
 			}
-			
+
 			if ($sqlLimit == $count) {
 				// BUG!  database contains group name that User::getAllGroups() does not return
 				// TODO: should handle this more gracefully
-				ApiBase :: dieDebug(__METHOD__, 
+				ApiBase :: dieDebug(__METHOD__,
 					'MediaWiki configuration error: the database contains more user groups than known to User::getAllGroups() function');
 			}
-								
+
 			// Add user's group info
 			if ($fld_groups && !is_null($row->ug_group2)) {
 				$lastUserData['groups'][] = $row->ug_group2;
 				$result->setIndexedTagName($lastUserData['groups'], 'g');
 			}
 		}
-		
+
 		$db->freeResult($res);
 
 		$result->setIndexedTagName($data, 'u');
@@ -166,11 +178,12 @@ class ApiQueryAllUsers extends ApiQueryB
 				ApiBase :: PARAM_TYPE => User::getAllGroups()
 			),
 			'prop' => array (
-				ApiBase :: PARAM_ISMULTI => true, 
+				ApiBase :: PARAM_ISMULTI => true,
 				ApiBase :: PARAM_TYPE => array (
-					'editcount',
+					'blockinfo',
 					'groups',
-					'registration',
+					'editcount',
+					'registration'
 				)
 			),
 			'limit' => array (
@@ -206,6 +219,6 @@ class ApiQueryAllUsers extends ApiQueryB
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryAllUsers.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryAllUsers.php 36790 2008-06-29 22:26:23Z catrope $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryBacklinks.php 1:1.13.0-1/includes/api/ApiQueryBacklinks.php
--- 1:1.12.0-2/includes/api/ApiQueryBacklinks.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryBacklinks.php	2008-07-10 15:28:09.000000000 +0100
@@ -29,18 +29,18 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * This is three-in-one module to query:
+ * This is a three-in-one module to query:
  *   * backlinks  - links pointing to the given page,
  *   * embeddedin - what pages transclude the given page within themselves,
  *   * imageusage - what pages use the given image
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryBacklinks extends ApiQueryGeneratorBase {
 
-	private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID;
+	private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID, $redirID;
 
-	// output element name, database column field prefix, database table 
+	// output element name, database column field prefix, database table
 	private $backlinksSettings = array (
 		'backlinks' => array (
 			'code' => 'bl',
@@ -66,10 +66,7 @@ class ApiQueryBacklinks extends ApiQuery
 		parent :: __construct($query, $moduleName, $code);
 		$this->bl_ns = $prefix . '_namespace';
 		$this->bl_from = $prefix . '_from';
-		$this->bl_tables = array (
-			$linktbl,
-			'page'
-		);
+		$this->bl_table = $linktbl;
 		$this->bl_code = $code;
 
 		$this->hasNS = $moduleName !== 'imageusage';
@@ -97,207 +94,219 @@ class ApiQueryBacklinks extends ApiQuery
 		$this->run($resultPageSet);
 	}
 
-	private function run($resultPageSet = null) {
-		$this->params = $this->extractRequestParams();
-		
-		$redirect = $this->params['redirect'];
-		if ($redirect)
-			$this->dieDebug('Redirect has not been implemented', 'notimplemented');
-
-		$this->processContinue();
-
-		$this->addFields($this->bl_fields);
-		if (is_null($resultPageSet))
-			$this->addFields(array (
-				'page_id',
-				'page_namespace',
-				'page_title'
-			));
+	private function prepareFirstQuery($resultPageSet = null) {
+		/* SELECT page_id, page_title, page_namespace, page_is_redirect
+		 * FROM pagelinks, page WHERE pl_from=page_id
+		 * AND pl_title='Foo' AND pl_namespace=0
+		 * LIMIT 11 ORDER BY pl_from
+		 */
+		$db = $this->getDb();
+		$this->addTables(array('page', $this->bl_table));
+		$this->addWhere("{$this->bl_from}=page_id");
+		if(is_null($resultPageSet))
+			$this->addFields(array('page_id', 'page_title', 'page_namespace'));
 		else
-			$this->addFields($resultPageSet->getPageTableFields()); // will include page_id
-
-		$this->addTables($this->bl_tables);
-		$this->addWhere($this->bl_from . '=page_id');
-
-		if ($this->hasNS)
+			$this->addFields($resultPageSet->getPageTableFields());
+		$this->addFields('page_is_redirect');
+		$this->addWhereFld($this->bl_title, $this->rootTitle->getDbKey());
+		if($this->hasNS)
 			$this->addWhereFld($this->bl_ns, $this->rootTitle->getNamespace());
-		$this->addWhereFld($this->bl_title, $this->rootTitle->getDBkey());
 		$this->addWhereFld('page_namespace', $this->params['namespace']);
-
+		if(!is_null($this->contID))
+			$this->addWhere("page_id>={$this->contID}");
 		if($this->params['filterredir'] == 'redirects')
 			$this->addWhereFld('page_is_redirect', 1);
 		if($this->params['filterredir'] == 'nonredirects')
 			$this->addWhereFld('page_is_redirect', 0);
+		$this->addOption('LIMIT', $this->params['limit'] + 1);
+		$this->addOption('ORDER BY', $this->bl_from);
+	}
 
-		$limit = $this->params['limit'];
-		$this->addOption('LIMIT', $limit +1);
+	private function prepareSecondQuery($resultPageSet = null) {
+		/* SELECT page_id, page_title, page_namespace, page_is_redirect, pl_title, pl_namespace
+		 * FROM pagelinks, page WHERE pl_from=page_id
+		 * AND (pl_title='Foo' AND pl_namespace=0) OR (pl_title='Bar' AND pl_namespace=1)
+		 * LIMIT 11 ORDER BY pl_namespace, pl_title, pl_from
+		 */
+		$db = $this->getDb();
+		$this->addTables(array('page', $this->bl_table));
+		$this->addWhere("{$this->bl_from}=page_id");
+		if(is_null($resultPageSet))
+			$this->addFields(array('page_id', 'page_title', 'page_namespace', 'page_is_redirect'));
+		else
+			$this->addFields($resultPageSet->getPageTableFields());
+		$this->addFields($this->bl_title);
+		if($this->hasNS)
+			$this->addFields($this->bl_ns);
+		$titleWhere = '';
+		foreach($this->redirTitles as $t)
+			$titleWhere .= ($titleWhere != '' ? " OR " : '') .
+					"({$this->bl_title} = ".$db->addQuotes($t->getDBKey()).
+					($this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : "") .
+					")";
+		$this->addWhere($titleWhere);
+		$this->addWhereFld('page_namespace', $this->params['namespace']);
+		if(!is_null($this->redirID))
+			$this->addWhere("page_id>={$this->redirID}");
+		if($this->params['filterredir'] == 'redirects')
+			$this->addWhereFld('page_is_redirect', 1);
+		if($this->params['filterredir'] == 'nonredirects')
+			$this->addWhereFld('page_is_redirect', 0);
+		$this->addOption('LIMIT', $this->params['limit'] + 1);
 		$this->addOption('ORDER BY', $this->bl_sort);
+	}
 
-		$db = $this->getDB();
-		if (!is_null($this->params['continue'])) {
-			$plfrm = intval($this->contID);
-			if ($this->contLevel == 0) {
-				// For the first level, there is only one target title, so no need for complex filtering
-				$this->addWhere($this->bl_from . '>=' . $plfrm);
-			} else {
-				$ns = $this->contTitle->getNamespace();
-				$t = $db->addQuotes($this->contTitle->getDBkey());
-				$whereWithoutNS = "{$this->bl_title}>$t OR ({$this->bl_title}=$t AND {$this->bl_from}>=$plfrm))";
-
-				if ($this->hasNS)
-					$this->addWhere("{$this->bl_ns}>$ns OR ({$this->bl_ns}=$ns AND ($whereWithoutNS)");
-				else
-					$this->addWhere($whereWithoutNS);
-			}
+	private function run($resultPageSet = null) {
+		$this->params = $this->extractRequestParams(false);
+		$this->redirect = isset($this->params['redirect']) && $this->params['redirect'];
+		$userMax = ( $this->redirect ? ApiBase::LIMIT_BIG1/2 : ApiBase::LIMIT_BIG1 );
+		$botMax  = ( $this->redirect ? ApiBase::LIMIT_BIG2/2 : ApiBase::LIMIT_BIG2 );
+		if( $this->params['limit'] == 'max' ) {
+			$this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
+			$this->getResult()->addValue( 'limits', $this->getModuleName(), $this->params['limit'] );
 		}
 
+		$this->processContinue();
+		$this->prepareFirstQuery($resultPageSet);
+
+		$db = $this->getDB();
 		$res = $this->select(__METHOD__);
 
 		$count = 0;
-		$data = array ();
+		$this->data = array ();
+		$this->continueStr = null;
+		$this->redirTitles = array();
 		while ($row = $db->fetchObject($res)) {
-			if (++ $count > $limit) {
+			if (++ $count > $this->params['limit']) {
 				// We've reached the one extra which shows that there are additional pages to be had. Stop here...
-				if ($redirect) {
-					$ns = $row-> { $this->bl_ns };
-					$t = $row-> { $this->bl_title };
-					$continue = $this->getContinueRedirStr(false, 0, $ns, $t, $row->page_id);
-				} else
-					$continue = $this->getContinueStr($row->page_id);
-				// TODO: Security issue - if the user has no right to view next title, it will still be shown
-				$this->setContinueEnumParameter('continue', $continue);
+				// Continue string preserved in case the redirect query doesn't pass the limit
+				$this->continueStr = $this->getContinueStr($row->page_id);
 				break;
 			}
 
-			if (is_null($resultPageSet)) {
-				$vals = $this->extractRowInfo($row);
-				if ($vals)
-					$data[] = $vals;
-			} else {
+			if (is_null($resultPageSet))
+				$this->extractRowInfo($row);
+			else
+			{
+				if($row->page_is_redirect)
+					$this->redirTitles[] = Title::makeTitle($row->page_namespace, $row->page_title);
 				$resultPageSet->processDbRow($row);
 			}
 		}
 		$db->freeResult($res);
 
+		if($this->redirect && !empty($this->redirTitles))
+		{
+			$this->resetQueryParams();
+			$this->prepareSecondQuery($resultPageSet);
+			$res = $this->select(__METHOD__);
+			$count = 0;
+			while($row = $db->fetchObject($res))
+			{
+				if(++$count > $this->params['limit'])
+				{
+					// We've reached the one extra which shows that there are additional pages to be had. Stop here...
+					// We need to keep the parent page of this redir in
+					if($this->hasNS)
+						$contTitle = Title::makeTitle($row->{$this->bl_ns}, $row->{$this->bl_title});
+					else
+						$contTitle = Title::makeTitle(NS_IMAGE, $row->{$this->bl_title});
+					$this->continueStr = $this->getContinueRedirStr($contTitle->getArticleID(), $row->page_id);
+					break;
+				}
+
+				if(is_null($resultPageSet))
+					$this->extractRedirRowInfo($row);
+				else
+					$resultPageSet->processDbRow($row);
+			}
+			$db->freeResult($res);
+		}
+		if(!is_null($this->continueStr))
+			$this->setContinueEnumParameter('continue', $this->continueStr);
+
 		if (is_null($resultPageSet)) {
+			$resultData = array();
+			foreach($this->data as $ns => $a)
+				foreach($a as $title => $arr)
+					$resultData[$arr['pageid']] = $arr;
 			$result = $this->getResult();
-			$result->setIndexedTagName($data, $this->bl_code);
-			$result->addValue('query', $this->getModuleName(), $data);
+			$result->setIndexedTagName($resultData, $this->bl_code);
+			$result->addValue('query', $this->getModuleName(), $resultData);
 		}
 	}
 
 	private function extractRowInfo($row) {
+		if(!isset($this->data[$row->page_namespace][$row->page_title])) {
+			$this->data[$row->page_namespace][$row->page_title]['pageid'] = $row->page_id;
+			ApiQueryBase::addTitleInfo($this->data[$row->page_namespace][$row->page_title], Title::makeTitle($row->page_namespace, $row->page_title));
+			if($row->page_is_redirect)
+			{
+				$this->data[$row->page_namespace][$row->page_title]['redirect'] = '';
+				$this->redirTitles[] = Title::makeTitle($row->page_namespace, $row->page_title);
+			}
+		}
+	}
 
-		$vals = array();
-		$vals['pageid'] = intval($row->page_id);
-		ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle($row->page_namespace, $row->page_title));
-
-		return $vals;
+	private function extractRedirRowInfo($row)
+	{
+		$a['pageid'] = $row->page_id;
+		ApiQueryBase::addTitleInfo($a, Title::makeTitle($row->page_namespace, $row->page_title));
+		if($row->page_is_redirect)
+			$a['redirect'] = '';
+		$ns = $this->hasNS ? $row->{$this->bl_ns} : NS_IMAGE;
+		$this->data[$ns][$row->{$this->bl_title}]['redirlinks'][] = $a;
+		$this->getResult()->setIndexedTagName($this->data[$ns][$row->{$this->bl_title}]['redirlinks'], $this->bl_code);
 	}
 
 	protected function processContinue() {
-		$pageSet = $this->getPageSet();
-		$count = $pageSet->getTitleCount();
-		
-		if (!is_null($this->params['continue'])) {
+		if (!is_null($this->params['continue']))
 			$this->parseContinueParam();
-
-			// Skip all completed links
-
-		} else {
-			$title = $this->params['title'];
-			if (!is_null($title)) {
-				$this->rootTitle = Title :: newFromText($title);
-			} else {  // This case is obsolete. Will support this for a while
-				if ($count !== 1)
-					$this->dieUsage("The {$this->getModuleName()} query requires one title to start", 'bad_title_count');
-				$this->rootTitle = current($pageSet->getTitles()); // only one title there
-				$this->setWarning('Using titles parameter is obsolete for this list. Use ' . $this->encodeParamName('title') . ' instead.');
+		else {
+			if ( $this->params['title'] !== "" ) {
+				$title = Title::newFromText( $this->params['title'] );
+				if ( !$title ) {
+					$this->dieUsageMsg(array('invalidtitle', $this->params['title']));
+				} else {
+					$this->rootTitle = $title;
+				}
+			} else {
+				$this->dieUsageMsg(array('missingparam', 'title'));
 			}
 		}
 
-		// only image titles are allowed for the root 
+		// only image titles are allowed for the root in imageinfo mode
 		if (!$this->hasNS && $this->rootTitle->getNamespace() !== NS_IMAGE)
 			$this->dieUsage("The title for {$this->getModuleName()} query must be an image", 'bad_image_title');
 	}
 
 	protected function parseContinueParam() {
 		$continueList = explode('|', $this->params['continue']);
-		if ($this->params['redirect']) {
-			//
-			// expected redirect-mode parameter:
-			// ns|db_key|step|level|ns|db_key|id
-			// ns+db_key -- the root title
-			// step = 1 or 2 - which step to continue from - 1-titles, 2-redirects
-			// level -- how many levels to follow before starting enumerating.
-			// if level > 0 -- ns+title to continue from, otherwise skip these 
-			// id = last page_id to continue from
-			//
-			if (count($continueList) > 4) {
-				$rootNs = intval($continueList[0]);
-				if (($rootNs !== 0 || $continueList[0] === '0') && !empty ($continueList[1])) {
-					$this->rootTitle = Title :: makeTitleSafe($rootNs, $continueList[1]);
-					if ($this->rootTitle) {
-
-						$step = intval($continueList[2]);
-						if ($step === 1 || $step === 2) {
-							$this->contRedirs = ($step === 2);
-
-							$level = intval($continueList[3]);
-							if ($level !== 0 || $continueList[3] === '0') {
-								$this->contLevel = $level;
-
-								if ($level === 0) {
-									if (count($continueList) === 5) {
-										$contID = intval($continueList[4]);
-										if ($contID !== 0 || $continueList[4] === '0') {
-											$this->contID = $contID;
-											return; // done
-										}
-									}
-								} else {
-									if (count($continueList) === 7) {
-										$contNs = intval($continueList[4]);
-										if (($contNs !== 0 || $continueList[4] === '0') && !empty ($continueList[5])) {
-											$this->contTitle = Title :: makeTitleSafe($contNs, $continueList[5]);
-
-											$contID = intval($continueList[6]);
-											if ($contID !== 0 || $continueList[6] === '0') {
-												$this->contID = $contID;
-												return; // done
-											}
-										}
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		} else {
-			//
-			// expected non-redirect-mode parameter:
-			// ns|db_key|id
-			// ns+db_key -- the root title
-			// id = last page_id to continue from
-			//
-			if (count($continueList) === 3) {
-				$rootNs = intval($continueList[0]);
-				if (($rootNs !== 0 || $continueList[0] === '0') && !empty ($continueList[1])) {
-					$this->rootTitle = Title :: makeTitleSafe($rootNs, $continueList[1]);
-					if ($this->rootTitle) {
-
-						$contID = intval($continueList[2]);
-						if ($contID !== 0) {
-							$this->contID = $contID;
-							return; // done
-						}
-					}
-				}
-			}
-		}
+		// expected format:
+		// ns | key | id1 [| id2]
+		// ns+key: root title
+		// id1: first-level page ID to continue from
+		// id2: second-level page ID to continue from
+
+		// null stuff out now so we know what's set and what isn't
+		$this->rootTitle = $this->contID = $this->redirID = null;
+		$rootNs = intval($continueList[0]);
+		if($rootNs === 0 && $continueList[0] !== '0')
+			// Illegal continue parameter
+			$this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
+		$this->rootTitle = Title::makeTitleSafe($rootNs, $continueList[1]);
+		if(!$this->rootTitle)
+			$this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
+		$contID = intval($continueList[2]);
+		if($contID === 0 && $continueList[2] !== '0')
+			$this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
+		$this->contID = $contID;
+		$redirID = intval(@$continueList[3]);
+		if($redirID === 0 && @$continueList[3] !== '0')
+			// This one isn't required
+			return;
+		$this->redirID = $redirID;
 
-		$this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
 	}
 
 	protected function getContinueStr($lastPageID) {
@@ -306,18 +315,12 @@ class ApiQueryBacklinks extends ApiQuery
 		'|' . $lastPageID;
 	}
 
-	protected function getContinueRedirStr($isRedirPhase, $level, $ns, $title, $lastPageID) {
-		return $this->rootTitle->getNamespace() .
-		'|' . $this->rootTitle->getDBkey() .
-		'|' . ($isRedirPhase ? 1 : 2) .
-		'|' . $level .
-		 ($level > 0 ? ('|' . $ns . '|' . $title) : '') .
-		'|' . $lastPageID;
+	protected function getContinueRedirStr($lastPageID, $lastRedirID) {
+		return $this->getContinueStr($lastPageID) . '|' . $lastRedirID;
 	}
 
 	public function getAllowedParams() {
-
-		return array (
+		$retval =  array (
 			'title' => null,
 			'continue' => null,
 			'namespace' => array (
@@ -332,7 +335,6 @@ class ApiQueryBacklinks extends ApiQuery
 					'nonredirects'
 				)
 			),
-			'redirect' => false,
 			'limit' => array (
 				ApiBase :: PARAM_DFLT => 10,
 				ApiBase :: PARAM_TYPE => 'limit',
@@ -341,17 +343,27 @@ class ApiQueryBacklinks extends ApiQuery
 				ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
 			)
 		);
+		if($this->getModuleName() == 'embeddedin')
+			return $retval;
+		$retval['redirect'] = false;
+		return $retval;
 	}
 
 	public function getParamDescription() {
-		return array (
+		$retval = array (
 			'title' => 'Title to search. If null, titles= parameter will be used instead, but will be obsolete soon.',
 			'continue' => 'When more results are available, use this to continue.',
 			'namespace' => 'The namespace to enumerate.',
-			'filterredir' => 'How to filter for redirects',
-			'redirect' => 'If linking page is a redirect, find all pages that link to that redirect (not implemented)',
-			'limit' => 'How many total pages to return.'
+			'filterredir' => 'How to filter for redirects'
 		);
+		if($this->getModuleName() != 'embeddedin')
+			return array_merge($retval, array(
+				'redirect' => 'If linking page is a redirect, find all pages that link to that redirect as well. Maximum limit is halved.',
+				'limit' => "How many total pages to return. If {$this->bl_code}redirect is enabled, limit applies to each level separately."
+			));
+		return array_merge($retval, array(
+			'limit' => "How many total pages to return."
+		));
 	}
 
 	public function getDescription() {
@@ -387,7 +399,6 @@ class ApiQueryBacklinks extends ApiQuery
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryBacklinks.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryBacklinks.php 37504 2008-07-10 14:28:09Z catrope $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryBase.php 1:1.13.0-1/includes/api/ApiQueryBase.php
--- 1:1.12.0-2/includes/api/ApiQueryBase.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryBase.php	2008-07-05 12:18:50.000000000 +0100
@@ -31,12 +31,12 @@ if (!defined('MEDIAWIKI')) {
 /**
  * This is a base class for all Query modules.
  * It provides some common functionality such as constructing various SQL queries.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 abstract class ApiQueryBase extends ApiBase {
 
-	private $mQueryModule, $mDb, $tables, $where, $fields, $options;
+	private $mQueryModule, $mDb, $tables, $where, $fields, $options, $join_conds;
 
 	public function __construct($query, $moduleName, $paramPrefix = '') {
 		parent :: __construct($query->getMain(), $moduleName, $paramPrefix);
@@ -45,13 +45,22 @@ abstract class ApiQueryBase extends ApiB
 		$this->resetQueryParams();
 	}
 
+	/**
+	 * Blank the internal arrays with query parameters
+	 */
 	protected function resetQueryParams() {
 		$this->tables = array ();
 		$this->where = array ();
 		$this->fields = array ();
 		$this->options = array ();
+		$this->join_conds = array ();
 	}
 
+	/**
+	 * Add a set of tables to the internal array
+	 * @param mixed $tables Table name or array of table names
+	 * @param mixed $alias Table alias, or null for no alias. Cannot be used with multiple tables
+	 */
 	protected function addTables($tables, $alias = null) {
 		if (is_array($tables)) {
 			if (!is_null($alias))
@@ -59,11 +68,38 @@ abstract class ApiQueryBase extends ApiB
 			$this->tables = array_merge($this->tables, $tables);
 		} else {
 			if (!is_null($alias))
-				$tables = $this->getDB()->tableName($tables) . ' ' . $alias;
+				$tables = $this->getAliasedName($tables, $alias);
 			$this->tables[] = $tables;
 		}
 	}
+	
+	/**
+	 * Get the SQL for a table name with alias
+	 * @param string $table Table name
+	 * @param string $alias Alias
+	 * @return string SQL
+	 */
+	protected function getAliasedName($table, $alias) {
+		return $this->getDB()->tableName($table) . ' ' . $alias;
+	}
+	
+	/**
+	 * Add a set of JOIN conditions to the internal array
+	 *
+	 * JOIN conditions are formatted as array( tablename => array(jointype, conditions)
+	 * e.g. array('page' => array('LEFT JOIN', 'page_id=rev_page'))
+	 * @param array $join_conds JOIN conditions
+	 */
+	protected function addJoinConds($join_conds) {
+		if(!is_array($join_conds))
+			ApiBase::dieDebug(__METHOD__, 'Join conditions have to be arrays');
+		$this->join_conds = array_merge($this->join_conds, $join_conds);
+	}
 
+	/**
+	 * Add a set of fields to select to the internal array
+	 * @param mixed $value Field name or array of field names
+	 */
 	protected function addFields($value) {
 		if (is_array($value))
 			$this->fields = array_merge($this->fields, $value);
@@ -71,6 +107,12 @@ abstract class ApiQueryBase extends ApiB
 			$this->fields[] = $value;
 	}
 
+	/**
+	 * Same as addFields(), but add the fields only if a condition is met
+	 * @param mixed $value See addFields()
+	 * @param bool $condition If false, do nothing
+	 * @return bool $condition
+	 */
 	protected function addFieldsIf($value, $condition) {
 		if ($condition) {
 			$this->addFields($value);
@@ -79,6 +121,15 @@ abstract class ApiQueryBase extends ApiB
 		return false;
 	}
 
+	/**
+	 * Add a set of WHERE clauses to the internal array.
+	 * Clauses can be formatted as 'foo=bar' or array('foo' => 'bar'),
+	 * the latter only works if the value is a constant (i.e. not another field)
+	 *
+	 * For example, array('foo=bar', 'baz' => 3, 'bla' => 'foo') translates
+	 * to "foo=bar AND baz='3' AND bla='foo'"
+	 * @param mixed $value String or array
+	 */
 	protected function addWhere($value) {
 		if (is_array($value))
 			$this->where = array_merge($this->where, $value);
@@ -86,6 +137,12 @@ abstract class ApiQueryBase extends ApiB
 			$this->where[] = $value;
 	}
 
+	/**
+	 * Same as addWhere(), but add the WHERE clauses only if a condition is met
+	 * @param mixed $value See addWhere()
+	 * @param bool $condition If false, do nothing
+	 * @return bool $condition
+	 */
 	protected function addWhereIf($value, $condition) {
 		if ($condition) {
 			$this->addWhere($value);
@@ -94,11 +151,24 @@ abstract class ApiQueryBase extends ApiB
 		return false;
 	}
 
+	/**
+	 * Equivalent to addWhere(array($field => $value))
+	 * @param string $field Field name
+	 * @param string $value Value; ignored if nul;
+	 */
 	protected function addWhereFld($field, $value) {
 		if (!is_null($value))
 			$this->where[$field] = $value;
 	}
 
+	/**
+	 * Add a WHERE clause corresponding to a range, and an ORDER BY
+	 * clause to sort in the right direction
+	 * @param string $field Field name
+	 * @param string $dir If 'newer', sort in ascending order, otherwise sort in descending order
+	 * @param string $start Value to start the list at. If $dir == 'newer' this is the lower boundary, otherwise it's the upper boundary
+	 * @param string $end Value to end the list at. If $dir == 'newer' this is the upper boundary, otherwise it's the lower boundary
+	 */
 	protected function addWhereRange($field, $dir, $start, $end) {
 		$isDirNewer = ($dir === 'newer');
 		$after = ($isDirNewer ? '>=' : '<=');
@@ -110,11 +180,19 @@ abstract class ApiQueryBase extends ApiB
 
 		if (!is_null($end))
 			$this->addWhere($field . $before . $db->addQuotes($end));
-		
+
+		$order = $field . ($isDirNewer ? '' : ' DESC');
 		if (!isset($this->options['ORDER BY']))
-			$this->addOption('ORDER BY', $field . ($isDirNewer ? '' : ' DESC'));
+			$this->addOption('ORDER BY', $order);
+		else
+			$this->addOption('ORDER BY', $this->options['ORDER BY'] . ', ' . $order);
 	}
 
+	/**
+	 * Add an option such as LIMIT or USE INDEX
+	 * @param string $name Option name
+	 * @param string $value Option value
+	 */
 	protected function addOption($name, $value = null) {
 		if (is_null($value))
 			$this->options[] = $name;
@@ -122,39 +200,71 @@ abstract class ApiQueryBase extends ApiB
 			$this->options[$name] = $value;
 	}
 
+	/**
+	 * Execute a SELECT query based on the values in the internal arrays
+	 * @param string $method Function the query should be attributed to. You should usually use __METHOD__ here
+	 * @return ResultWrapper
+	 */
 	protected function select($method) {
 
 		// getDB has its own profileDBIn/Out calls
 		$db = $this->getDB();
 
 		$this->profileDBIn();
-		$res = $db->select($this->tables, $this->fields, $this->where, $method, $this->options);
+		$res = $db->select($this->tables, $this->fields, $this->where, $method, $this->options, $this->join_conds);
 		$this->profileDBOut();
 
 		return $res;
 	}
 
+	/**
+	 * Estimate the row count for the SELECT query that would be run if we
+	 * called select() right now, and check if it's acceptable.
+	 * @return bool true if acceptable, false otherwise
+	 */
+	protected function checkRowCount() {
+		$db = $this->getDB();
+		$this->profileDBIn();
+		$rowcount = $db->estimateRowCount($this->tables, $this->fields, $this->where, __METHOD__, $this->options);
+		$this->profileDBOut();
+
+		global $wgAPIMaxDBRows;
+		if($rowcount > $wgAPIMaxDBRows)
+			return false;
+		return true;
+	}
+
+	/**
+	 * Add information (title and namespace) about a Title object to a result array
+	 * @param array $arr Result array à la ApiResult
+	 * @param Title $title Title object
+	 * @param string $prefix Module prefix
+	 */
 	public static function addTitleInfo(&$arr, $title, $prefix='') {
 		$arr[$prefix . 'ns'] = intval($title->getNamespace());
 		$arr[$prefix . 'title'] = $title->getPrefixedText();
 	}
-	
+
 	/**
 	 * Override this method to request extra fields from the pageSet
 	 * using $pageSet->requestField('fieldName')
+	 * @param ApiPageSet $pageSet
 	 */
 	public function requestExtraData($pageSet) {
 	}
 
 	/**
 	 * Get the main Query module
+	 * @return ApiQuery
 	 */
 	public function getQuery() {
 		return $this->mQueryModule;
 	}
 
 	/**
-	 * Add sub-element under the page element with the given pageId. 
+	 * Add a sub-element under the page element with the given page ID
+	 * @param int $pageId Page ID
+	 * @param array $data Data array à la ApiResult 
 	 */
 	protected function addPageSubItems($pageId, $data) {
 		$result = $this->getResult();
@@ -164,19 +274,21 @@ abstract class ApiQueryBase extends ApiB
 			$data);
 	}
 
+	/**
+	 * Set a query-continue value
+	 * @param $paramName Parameter name
+	 * @param $paramValue Parameter value
+	 */
 	protected function setContinueEnumParameter($paramName, $paramValue) {
-		
+
 		$paramName = $this->encodeParamName($paramName);
 		$msg = array( $paramName => $paramValue );
-
-//		This is an alternative continue format as a part of the URL string
-//		ApiResult :: setContent($msg, $paramName . '=' . urlencode($paramValue));
-		
 		$this->getResult()->addValue('query-continue', $this->getModuleName(), $msg);
 	}
 
 	/**
 	 * Get the Query database connection (readonly)
+	 * @return Database
 	 */
 	protected function getDB() {
 		if (is_null($this->mDb))
@@ -186,57 +298,62 @@ abstract class ApiQueryBase extends ApiB
 
 	/**
 	 * Selects the query database connection with the given name.
-	 * If no such connection has been requested before, it will be created. 
-	 * Subsequent calls with the same $name will return the same connection 
-	 * as the first, regardless of $db or $groups new values. 
+	 * If no such connection has been requested before, it will be created.
+	 * Subsequent calls with the same $name will return the same connection
+	 * as the first, regardless of $db or $groups new values.
+	 * @param string $name Name to assign to the database connection
+	 * @param int $db One of the DB_* constants
+	 * @param array $groups Query groups
+	 * @return Database 
 	 */
 	public function selectNamedDB($name, $db, $groups) {
-		$this->mDb = $this->getQuery()->getNamedDB($name, $db, $groups);	
+		$this->mDb = $this->getQuery()->getNamedDB($name, $db, $groups);
 	}
 
 	/**
 	 * Get the PageSet object to work on
-	 * @return ApiPageSet data
+	 * @return ApiPageSet
 	 */
 	protected function getPageSet() {
 		return $this->getQuery()->getPageSet();
 	}
 
 	/**
-	 * This is a very simplistic utility function
-	 * to convert a non-namespaced title string to a db key.
-	 * It will replace all ' ' with '_'
+	 * Convert a title to a DB key
+	 * @param string $title Page title with spaces
+	 * @return string Page title with underscores
 	 */
-	public static function titleToKey($title) {
-		return str_replace(' ', '_', $title);
+	public function titleToKey($title) {
+		$t = Title::newFromText($title);
+		if(!$t)
+			$this->dieUsageMsg(array('invalidtitle', $title));
+		return $t->getDbKey();
 	}
 
-	public static function keyToTitle($key) {
-		return str_replace('_', ' ', $key);
+	/**
+	 * The inverse of titleToKey()
+	 * @param string $key Page title with underscores
+	 * @return string Page title with spaces
+	 */
+	public function keyToTitle($key) {
+		$t = Title::newFromDbKey($key);
+		# This really shouldn't happen but we gotta check anyway
+		if(!$t)
+			$this->dieUsageMsg(array('invalidtitle', $key));
+		return $t->getPrefixedText();
 	}
 
-	public function getTokenFlag($tokenArr, $action) {
-		if ($this->getMain()->getRequest()->getVal('callback') !== null) {
-			// Don't do any session-specific data.
-			return false;
-		}
-		if (in_array($action, $tokenArr)) {
-			global $wgUser;
-			if ($wgUser->isAllowed($action))
-				return true;
-			else
-				$this->dieUsage("Action '$action' is not allowed for the current user", 'permissiondenied');
-		}
-		return false;
-	}
-	
+	/**
+	 * Get version string for use in the API help output
+	 * @return string
+	 */
 	public static function getBaseVersion() {
-		return __CLASS__ . ': $Id: ApiQueryBase.php 31484 2008-03-03 05:46:20Z brion $';
+		return __CLASS__ . ': $Id: ApiQueryBase.php 37083 2008-07-05 11:18:50Z catrope $';
 	}
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 abstract class ApiQueryGeneratorBase extends ApiQueryBase {
 
@@ -247,6 +364,10 @@ abstract class ApiQueryGeneratorBase ext
 		$this->mIsGenerator = false;
 	}
 
+	/**
+	 * Switch this module to generator mode. By default, generator mode is
+	 * switched off and the module acts like a normal query module.
+	 */
 	public function setGeneratorMode() {
 		$this->mIsGenerator = true;
 	}
@@ -267,4 +388,3 @@ abstract class ApiQueryGeneratorBase ext
 	 */
 	public abstract function executeGenerator($resultPageSet);
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryBlocks.php 1:1.13.0-1/includes/api/ApiQueryBlocks.php
--- 1:1.12.0-2/includes/api/ApiQueryBlocks.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryBlocks.php	2008-07-21 22:37:11.000000000 +0100
@@ -30,10 +30,12 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * Query module to enumerate all available pages.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryBlocks extends ApiQueryBase {
+	
+	var $users;
 
 	public function __construct($query, $moduleName) {
 		parent :: __construct($query, $moduleName, 'bk');
@@ -47,6 +49,9 @@ class ApiQueryBlocks extends ApiQueryBas
 		global $wgUser;
 
 		$params = $this->extractRequestParams();
+		if(isset($params['users']) && isset($params['ip']))
+			$this->dieUsage('bkusers and bkip cannot be used together', 'usersandip');
+
 		$prop = array_flip($params['prop']);
 		$fld_id = isset($prop['id']);
 		$fld_user = isset($prop['user']);
@@ -66,7 +71,7 @@ class ApiQueryBlocks extends ApiQueryBas
 		if($fld_id)
 			$this->addFields('ipb_id');
 		if($fld_user)
-			$this->addFields(array('ipb_address', 'ipb_user'));
+			$this->addFields(array('ipb_address', 'ipb_user', 'ipb_auto'));
 		if($fld_by)
 		{
 			$this->addTables('user');
@@ -89,8 +94,32 @@ class ApiQueryBlocks extends ApiQueryBas
 		if(isset($params['ids']))
 			$this->addWhere(array('ipb_id' => $params['ids']));
 		if(isset($params['users']))
-			$this->addWhere(array('ipb_address' => $params['users']));
-		if(!$wgUser->isAllowed('oversight'))
+		{
+			foreach((array)$params['users'] as $u)
+				$this->prepareUsername($u);
+			$this->addWhere(array('ipb_address' => $this->usernames));
+		}
+		if(isset($params['ip']))
+		{
+			list($ip, $range) = IP::parseCIDR($params['ip']);
+			if($ip && $range)
+			{
+				# We got a CIDR range
+				if($range < 16)
+					$this->dieUsage('CIDR ranges broader than /16 are not accepted', 'cidrtoobroad');
+				$lower = wfBaseConvert($ip, 10, 16, 8, false);
+				$upper = wfBaseConvert($ip + pow(2, 32 - $range) - 1, 10, 16, 8, false);
+			}
+			else
+				$lower = $upper = IP::toHex($params['ip']);
+			$prefix = substr($lower, 0, 4);
+			$this->addWhere(array(
+				"ipb_range_start LIKE '$prefix%'",
+				"ipb_range_start <= '$lower'",
+				"ipb_range_end >= '$upper'"
+			));
+		}
+		if(!$wgUser->isAllowed('suppress'))
 			$this->addWhere(array('ipb_deleted' => 0));
 
 		// Purge expired entries on one in every 10 queries
@@ -152,6 +181,18 @@ class ApiQueryBlocks extends ApiQueryBas
 		$result->setIndexedTagName($data, 'block');
 		$result->addValue('query', $this->getModuleName(), $data);
 	}
+	
+	protected function prepareUsername($user)
+	{
+		if(!$user)
+			$this->dieUsage('User parameter may not be empty', 'param_user');
+		$name = User::isIP($user)
+			? $user
+			: User::getCanonicalName($user, 'valid');
+		if($name === false)
+			$this->dieUsage("User name {$user} is not valid", 'param_user');
+		$this->usernames[] = $name;
+	}
 
 	protected function convertHexIP($ip)
 	{
@@ -188,6 +229,7 @@ class ApiQueryBlocks extends ApiQueryBas
 			'users' => array(
 				ApiBase :: PARAM_ISMULTI => true
 			),
+			'ip' => null,
 			'limit' => array(
 				ApiBase :: PARAM_DFLT => 10,
 				ApiBase :: PARAM_TYPE => 'limit',
@@ -219,6 +261,8 @@ class ApiQueryBlocks extends ApiQueryBas
 			'dir' => 'The direction in which to enumerate',
 			'ids' => 'Pipe-separated list of block IDs to list (optional)',
 			'users' => 'Pipe-separated list of users to search for (optional)',
+			'ip' => array(	'Get all blocks applying to this IP or CIDR range, including range blocks.',
+					'Cannot be used together with bkusers. CIDR ranges broader than /16 are not accepted.'),
 			'limit' => 'The maximum amount of blocks to list',
 			'prop' => 'Which properties to get',
 		);
@@ -229,11 +273,12 @@ class ApiQueryBlocks extends ApiQueryBas
 	}
 
 	protected function getExamples() {
-		return array (
+		return array (	'api.php?action=query&list=blocks',
+				'api.php?action=query&list=blocks&bkusers=Alice|Bob'
 		);
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryBlocks.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryBlocks.php 37892 2008-07-21 21:37:11Z catrope $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryCategories.php 1:1.13.0-1/includes/api/ApiQueryCategories.php
--- 1:1.12.0-2/includes/api/ApiQueryCategories.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryCategories.php	2008-07-22 14:26:15.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * A query module to enumerate categories the set of pages belong to.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryCategories extends ApiQueryGeneratorBase {
 
@@ -59,8 +59,8 @@ class ApiQueryCategories extends ApiQuer
 			'cl_from',
 			'cl_to'
 		));
-		
-		$fld_sortkey = false;
+
+		$fld_sortkey = $fld_timestamp = false;
 		if (!is_null($prop)) {
 			foreach($prop as $p) {
 				switch ($p) {
@@ -68,24 +68,51 @@ class ApiQueryCategories extends ApiQuer
 						$this->addFields('cl_sortkey');
 						$fld_sortkey = true;
 						break;
+					case 'timestamp':
+						$this->addFields('cl_timestamp');
+						$fld_timestamp = true;
+						break;
 					default :
 						ApiBase :: dieDebug(__METHOD__, "Unknown prop=$p");
 				}
 			}
 		}
-		
+
 		$this->addTables('categorylinks');
 		$this->addWhereFld('cl_from', array_keys($this->getPageSet()->getGoodTitles()));
-		$this->addOption('ORDER BY', "cl_from, cl_to");
+		if(!is_null($params['continue'])) {
+			$cont = explode('|', $params['continue']);
+			if(count($cont) != 2)
+				$this->dieUsage("Invalid continue param. You should pass the " .
+					"original value returned by the previous query", "_badcontinue");
+			$clfrom = intval($cont[0]);
+			$clto = $this->getDb()->strencode($this->titleToKey($cont[1]));
+			$this->addWhere("cl_from > $clfrom OR ".
+					"(cl_from = $clfrom AND ".
+					"cl_to >= '$clto')");
+		}
+		# Don't order by cl_from if it's constant in the WHERE clause
+		if(count($this->getPageSet()->getGoodTitles()) == 1)
+			$this->addOption('ORDER BY', 'cl_to');
+		else
+			$this->addOption('ORDER BY', "cl_from, cl_to");
 
 		$db = $this->getDB();
 		$res = $this->select(__METHOD__);
 
 		if (is_null($resultPageSet)) {
-			
+
 			$data = array();
-			$lastId = 0;	// database has no ID 0	
+			$lastId = 0;	// database has no ID 0
+			$count = 0;
 			while ($row = $db->fetchObject($res)) {
+				if (++$count > $params['limit']) {
+					// We've reached the one extra which shows that
+					// there are additional pages to be had. Stop here...
+					$this->setContinueEnumParameter('continue', $row->cl_from .
+							'|' . $this->keyToTitle($row->cl_to));
+					break;
+				}
 				if ($lastId != $row->cl_from) {
 					if($lastId != 0) {
 						$this->addPageSubItems($lastId, $data);
@@ -93,13 +120,15 @@ class ApiQueryCategories extends ApiQuer
 					}
 					$lastId = $row->cl_from;
 				}
-				
+
 				$title = Title :: makeTitle(NS_CATEGORY, $row->cl_to);
-				
+
 				$vals = array();
 				ApiQueryBase :: addTitleInfo($vals, $title);
 				if ($fld_sortkey)
 					$vals['sortkey'] = $row->cl_sortkey;
+				if ($fld_timestamp)
+					$vals['timestamp'] = $row->cl_timestamp;
 
 				$data[] = $vals;
 			}
@@ -112,6 +141,14 @@ class ApiQueryCategories extends ApiQuer
 
 			$titles = array();
 			while ($row = $db->fetchObject($res)) {
+				if (++$count > $params['limit']) {
+					// We've reached the one extra which shows that
+					// there are additional pages to be had. Stop here...
+					$this->setContinueEnumParameter('continue', $row->cl_from .
+							'|' . $this->keyToTitle($row->cl_to));
+					break;
+				}
+
 				$titles[] = Title :: makeTitle(NS_CATEGORY, $row->cl_to);
 			}
 			$resultPageSet->populateFromTitles($titles);
@@ -126,14 +163,25 @@ class ApiQueryCategories extends ApiQuer
 				ApiBase :: PARAM_ISMULTI => true,
 				ApiBase :: PARAM_TYPE => array (
 					'sortkey',
+					'timestamp',
 				)
-			)
+			),
+			'limit' => array(
+				ApiBase :: PARAM_DFLT => 10,
+				ApiBase :: PARAM_TYPE => 'limit',
+				ApiBase :: PARAM_MIN => 1,
+				ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
+				ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+			),
+			'continue' => null,
 		);
 	}
 
 	public function getParamDescription() {
 		return array (
 			'prop' => 'Which additional properties to get for each category.',
+			'limit' => 'How many categories to return',
+			'continue' => 'When more results are available, use this to continue',
 		);
 	}
 
@@ -151,7 +199,6 @@ class ApiQueryCategories extends ApiQuer
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryCategories.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryCategories.php 37909 2008-07-22 13:26:15Z catrope $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryCategoryInfo.php 1:1.13.0-1/includes/api/ApiQueryCategoryInfo.php
--- 1:1.12.0-2/includes/api/ApiQueryCategoryInfo.php	1970-01-01 01:00:00.000000000 +0100
+++ 1:1.13.0-1/includes/api/ApiQueryCategoryInfo.php	2008-07-10 15:28:09.000000000 +0100
@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * Created on May 13, 2007
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+	// Eclipse helper - will be ignored in production
+	require_once ("ApiQueryBase.php");
+}
+
+/**
+ * This query adds <categories> subelement to all pages with the list of images embedded into those pages.
+ *
+ * @ingroup API
+ */
+class ApiQueryCategoryInfo extends ApiQueryBase {
+
+	public function __construct($query, $moduleName) {
+		parent :: __construct($query, $moduleName, 'ci');
+	}
+
+	public function execute() {			
+		$alltitles = $this->getPageSet()->getAllTitlesByNamespace();
+		$categories = $alltitles[NS_CATEGORY];
+		if(empty($categories))
+			return;
+
+		$titles = $this->getPageSet()->getGoodTitles() +
+					$this->getPageSet()->getMissingTitles();
+		$cattitles = array();
+		foreach($categories as $c)
+		{
+			$t = $titles[$c];
+			$cattitles[$c] = $t->getDbKey();
+		}
+
+		$this->addTables('category');
+		$this->addFields(array('cat_title', 'cat_pages', 'cat_subcats', 'cat_files', 'cat_hidden'));
+		$this->addWhere(array('cat_title' => $cattitles));			
+
+		$db = $this->getDB();
+		$res = $this->select(__METHOD__);
+
+		$data = array();
+		$catids = array_flip($cattitles);
+		while($row = $db->fetchObject($res))
+		{
+			$vals = array();
+			$vals['size'] = $row->cat_pages;
+			$vals['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files;
+			$vals['files'] = $row->cat_files;
+			$vals['subcats'] = $row->cat_subcats;
+			if($row->cat_hidden)
+				$vals['hidden'] = '';
+			$this->addPageSubItems($catids[$row->cat_title], $vals);
+		}
+		$db->freeResult($res);
+	}
+
+	public function getDescription() {
+		return 'Returns information about the given categories';
+	}
+
+	protected function getExamples() {
+		return "api.php?action=query&prop=categoryinfo&titles=Category:Foo|Category:Bar";
+	}
+
+	public function getVersion() {
+		return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 37504 2008-07-10 14:28:09Z catrope $';
+	}
+}
diff -pruN 1:1.12.0-2/includes/api/ApiQueryCategoryMembers.php 1:1.13.0-1/includes/api/ApiQueryCategoryMembers.php
--- 1:1.12.0-2/includes/api/ApiQueryCategoryMembers.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryCategoryMembers.php	2008-05-20 18:13:28.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * A query module to enumerate pages that belong to a category.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
 
@@ -51,19 +51,13 @@ class ApiQueryCategoryMembers extends Ap
 
 		$params = $this->extractRequestParams();
 
-		if (is_null($params['category'])) {
-			if (is_null($params['title']))
-				$this->dieUsage("Either the cmcategory or the cmtitle parameter is required", 'notitle');
-			else
-				$categoryTitle = Title::newFromText($params['title']);
-		} else if(is_null($params['title']))
-			$categoryTitle = Title::makeTitleSafe(NS_CATEGORY, $params['category']);
-		else
-			$this->dieUsage("The cmcategory and cmtitle parameters can't be used together", 'titleandcategory');
+		if ( !isset($params['title']) || is_null($params['title']) )
+			$this->dieUsage("The cmtitle parameter is required", 'notitle');
+		$categoryTitle = Title::newFromText($params['title']);
 
 		if ( is_null( $categoryTitle ) || $categoryTitle->getNamespace() != NS_CATEGORY )
 			$this->dieUsage("The category name you entered is not valid", 'invalidcategory');
-		
+
 		$prop = array_flip($params['prop']);
 		$fld_ids = isset($prop['ids']);
 		$fld_title = isset($prop['title']);
@@ -78,26 +72,29 @@ class ApiQueryCategoryMembers extends Ap
 			$this->addFields(array('cl_from', 'cl_sortkey'));
 		}
 
-		$this->addFieldsIf('cl_timestamp', $fld_timestamp);
-		$this->addTables(array('page','categorylinks'));	// must be in this order for 'USE INDEX' 
+		$this->addFieldsIf('cl_timestamp', $fld_timestamp || $params['sort'] == 'timestamp');
+		$this->addTables(array('page','categorylinks'));	// must be in this order for 'USE INDEX'
 									// Not needed after bug 10280 is applied to servers
 		if($params['sort'] == 'timestamp')
 		{
 			$this->addOption('USE INDEX', 'cl_timestamp');
-			$this->addOption('ORDER BY', 'cl_to, cl_timestamp' . ($params['dir'] == 'desc' ? ' DESC' : ''));
+			// cl_timestamp will be added by addWhereRange() later
+			$this->addOption('ORDER BY', 'cl_to');
 		}
 		else
 		{
+			$dir = ($params['dir'] == 'desc' ? ' DESC' : '');
 			$this->addOption('USE INDEX', 'cl_sortkey');
-			$this->addOption('ORDER BY', 'cl_to, cl_sortkey' . ($params['dir'] == 'desc' ? ' DESC' : '') . ', cl_from');
+			$this->addOption('ORDER BY', 'cl_to, cl_sortkey' . $dir . ', cl_from' . $dir);
 		}
 
 		$this->addWhere('cl_from=page_id');
-		$this->setContinuation($params['continue']);		
+		$this->setContinuation($params['continue'], $params['dir']);
 		$this->addWhereFld('cl_to', $categoryTitle->getDBkey());
 		$this->addWhereFld('page_namespace', $params['namespace']);
-		$this->addWhereRange('cl_timestamp', ($params['dir'] == 'asc' ? 'newer' : 'older'), $params['start'], $params['end']);
-		
+		if($params['sort'] == 'timestamp')
+			$this->addWhereRange('cl_timestamp', ($params['dir'] == 'asc' ? 'newer' : 'older'), $params['start'], $params['end']);
+
 		$limit = $params['limit'];
 		$this->addOption('LIMIT', $limit +1);
 
@@ -111,16 +108,19 @@ class ApiQueryCategoryMembers extends Ap
 			if (++ $count > $limit) {
 				// We've reached the one extra which shows that there are additional pages to be had. Stop here...
 				// TODO: Security issue - if the user has no right to view next title, it will still be shown
-				$this->setContinueEnumParameter('continue', $this->getContinueStr($row, $lastSortKey));
+				if ($params['sort'] == 'timestamp')
+					$this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->cl_timestamp));
+				else
+					$this->setContinueEnumParameter('continue', $this->getContinueStr($row, $lastSortKey));
 				break;
 			}
 
-			$lastSortKey = $row->cl_sortkey;	// detect duplicate sortkeys 
-			
+			$lastSortKey = $row->cl_sortkey;	// detect duplicate sortkeys
+
 			if (is_null($resultPageSet)) {
 				$vals = array();
 				if ($fld_ids)
-					$vals['pageid'] = intval($row->page_id); 
+					$vals['pageid'] = intval($row->page_id);
 				if ($fld_title) {
 					$title = Title :: makeTitle($row->page_namespace, $row->page_title);
 					$vals['ns'] = intval($title->getNamespace());
@@ -142,47 +142,48 @@ class ApiQueryCategoryMembers extends Ap
 			$this->getResult()->addValue('query', $this->getModuleName(), $data);
 		}
 	}
-	
+
 	private function getContinueStr($row, $lastSortKey) {
 		$ret = $row->cl_sortkey . '|';
 		if ($row->cl_sortkey == $lastSortKey)	// duplicate sort key, add cl_from
 			$ret .= $row->cl_from;
 		return $ret;
 	}
-	
+
 	/**
-	 * Add DB WHERE clause to continue previous query based on 'continue' parameter 
+	 * Add DB WHERE clause to continue previous query based on 'continue' parameter
 	 */
-	private function setContinuation($continue) {
+	private function setContinuation($continue, $dir) {
 		if (is_null($continue))
 			return;	// This is not a continuation request
-			
+
 		$continueList = explode('|', $continue);
 		$hasError = count($continueList) != 2;
 		$from = 0;
 		if (!$hasError && strlen($continueList[1]) > 0) {
 			$from = intval($continueList[1]);
-			$hasError = ($from == 0); 
+			$hasError = ($from == 0);
 		}
-		
+
 		if ($hasError)
 			$this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "badcontinue");
 
 		$encSortKey = $this->getDB()->addQuotes($continueList[0]);
 		$encFrom = $this->getDB()->addQuotes($from);
+		
+		$op = ($dir == 'desc' ? '<' : '>');
 
 		if ($from != 0) {
 			// Duplicate sort key continue
-			$this->addWhere( "cl_sortkey>$encSortKey OR (cl_sortkey=$encSortKey AND cl_from>=$encFrom)" );
+			$this->addWhere( "cl_sortkey$op$encSortKey OR (cl_sortkey=$encSortKey AND cl_from$op=$encFrom)" );
 		} else {
-			$this->addWhere( "cl_sortkey>=$encSortKey" );
+			$this->addWhere( "cl_sortkey$op=$encSortKey" );
 		}
 	}
 
 	public function getAllowedParams() {
 		return array (
 			'title' => null,
-			'category' => null, // DEPRECATED, will be removed in early March
 			'prop' => array (
 				ApiBase :: PARAM_DFLT => 'ids|title',
 				ApiBase :: PARAM_ISMULTI => true,
@@ -235,11 +236,10 @@ class ApiQueryCategoryMembers extends Ap
 			'namespace' => 'Only include pages in these namespaces',
 			'sort' => 'Property to sort by',
 			'dir' => 'In which direction to sort',
-			'start' => 'Timestamp to start listing from',
-			'end' => 'Timestamp to end listing at',
+			'start' => 'Timestamp to start listing from. Can only be used with cmsort=timestamp',
+			'end' => 'Timestamp to end listing at. Can only be used with cmsort=timestamp',
 			'continue' => 'For large categories, give the value retured from previous query',
 			'limit' => 'The maximum number of pages to return.',
-			'category' => 'DEPRECATED. Like title, but without the Category: prefix.',
 		);
 	}
 
@@ -257,7 +257,6 @@ class ApiQueryCategoryMembers extends Ap
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 30670 2008-02-07 15:17:42Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryDeletedrevs.php 1:1.13.0-1/includes/api/ApiQueryDeletedrevs.php
--- 1:1.12.0-2/includes/api/ApiQueryDeletedrevs.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryDeletedrevs.php	2008-07-10 15:13:11.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * Query module to enumerate all available pages.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryDeletedrevs extends ApiQueryBase {
 
@@ -87,11 +87,16 @@ class ApiQueryDeletedrevs extends ApiQue
 		// Check limits
 		$userMax = $fld_content ? ApiBase :: LIMIT_SML1 : ApiBase :: LIMIT_BIG1;
 		$botMax  = $fld_content ? ApiBase :: LIMIT_SML2 : ApiBase :: LIMIT_BIG2;
+
+		$limit = $params['limit'];
+
 		if( $limit == 'max' ) {
 			$limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
-			$this->getResult()->addValue( 'limits', 'limit', $limit );
+			$this->getResult()->addValue( 'limits', $this->getModuleName(), $limit );
 		}
-		$this->validateLimit('limit', $params['limit'], 1, $userMax, $botMax);
+
+		$this->validateLimit('limit', $limit, 1, $userMax, $botMax);
+
 		if($fld_token)
 			// Undelete tokens are identical for all pages, so we cache one here
 			$token = $wgUser->editToken();
@@ -104,17 +109,15 @@ class ApiQueryDeletedrevs extends ApiQue
 			$this->addWhere($where);
 		}
 
-		$this->addOption('LIMIT', $params['limit'] + 1);
+		$this->addOption('LIMIT', $limit + 1);
 		$this->addWhereRange('ar_timestamp', $params['dir'], $params['start'], $params['end']);
-		if(isset($params['namespace']))
-			$this->addWhereFld('ar_namespace', $params['namespace']);
 		$res = $this->select(__METHOD__);
 		$pages = array();
 		$count = 0;
 		// First populate the $pages array
 		while($row = $db->fetchObject($res))
 		{
-			if($count++ == $params['limit'])
+			if(++$count > $limit)
 			{
 				// We've had enough
 				$this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp));
@@ -178,10 +181,6 @@ class ApiQueryDeletedrevs extends ApiQue
 				),
 				ApiBase :: PARAM_DFLT => 'older'
 			),
-			'namespace' => array(
-				ApiBase :: PARAM_ISMULTI => true,
-				ApiBase :: PARAM_TYPE => 'namespace'
-			),
 			'limit' => array(
 				ApiBase :: PARAM_DFLT => 10,
 				ApiBase :: PARAM_TYPE => 'limit',
@@ -210,7 +209,6 @@ class ApiQueryDeletedrevs extends ApiQue
 			'start' => 'The timestamp to start enumerating from',
 			'end' => 'The timestamp to stop enumerating at',
 			'dir' => 'The direction in which to enumerate',
-			'namespace' => 'The namespaces to search in',
 			'limit' => 'The maximum amount of revisions to list',
 			'prop' => 'Which properties to get'
 		);
@@ -222,14 +220,14 @@ class ApiQueryDeletedrevs extends ApiQue
 
 	protected function getExamples() {
 		return array (
-			'List the first 50 deleted revisions in the Category and Category talk namespaces',
-			'  api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=14|15',
+			'List the first 50 deleted revisions',
+			'  api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50',
 			'List the last deleted revisions of Main Page and Talk:Main Page, with content:',
 			'  api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&drprop=user|comment|content'
 		);
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 37502 2008-07-10 14:13:11Z catrope $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryExternalLinks.php 1:1.13.0-1/includes/api/ApiQueryExternalLinks.php
--- 1:1.12.0-2/includes/api/ApiQueryExternalLinks.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryExternalLinks.php	2008-07-07 18:32:22.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * A query module to list all external URLs found on a given set of pages.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryExternalLinks extends ApiQueryBase {
 
@@ -40,21 +40,37 @@ class ApiQueryExternalLinks extends ApiQ
 	}
 
 	public function execute() {
+		if ( $this->getPageSet()->getGoodTitleCount() == 0 )
+			return;
 
+		$params = $this->extractRequestParams();
 		$this->addFields(array (
 			'el_from',
 			'el_to'
 		));
-		
+
 		$this->addTables('externallinks');
 		$this->addWhereFld('el_from', array_keys($this->getPageSet()->getGoodTitles()));
+		# Don't order by el_from if it's constant in the WHERE clause
+		if(count($this->getPageSet()->getGoodTitles()) != 1)
+			$this->addOption('ORDER BY', 'el_from');
+		$this->addOption('LIMIT', $params['limit'] + 1);
+		if(!is_null($params['offset']))
+			$this->addOption('OFFSET', $params['offset']);
 
 		$db = $this->getDB();
 		$res = $this->select(__METHOD__);
-			
+
 		$data = array();
-		$lastId = 0;	// database has no ID 0	
+		$lastId = 0;	// database has no ID 0
+		$count = 0;
 		while ($row = $db->fetchObject($res)) {
+			if (++$count > $params['limit']) {
+				// We've reached the one extra which shows that
+				// there are additional pages to be had. Stop here...
+				$this->setContinueEnumParameter('offset', @$params['offset'] + $params['limit']);
+				break;
+			}
 			if ($lastId != $row->el_from) {
 				if($lastId != 0) {
 					$this->addPageSubItems($lastId, $data);
@@ -62,7 +78,7 @@ class ApiQueryExternalLinks extends ApiQ
 				}
 				$lastId = $row->el_from;
 			}
-			
+
 			$entry = array();
 			ApiResult :: setContent($entry, $row->el_to);
 			$data[] = $entry;
@@ -75,6 +91,26 @@ class ApiQueryExternalLinks extends ApiQ
 		$db->freeResult($res);
 	}
 
+	public function getAllowedParams() {
+		return array(
+				'limit' => array(
+					ApiBase :: PARAM_DFLT => 10,
+					ApiBase :: PARAM_TYPE => 'limit',
+					ApiBase :: PARAM_MIN => 1,
+					ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
+					ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+				),
+				'offset' => null,
+		);
+	}
+
+	public function getParamDescription () {
+		return array(
+			'limit' => 'How many links to return',
+			'offset' => 'When more results are available, use this to continue',
+		);
+	}
+
 	public function getDescription() {
 		return 'Returns all external urls (not interwikies) from the given page(s)';
 	}
@@ -87,7 +123,6 @@ class ApiQueryExternalLinks extends ApiQ
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 37270 2008-07-07 17:32:22Z catrope $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryExtLinksUsage.php 1:1.13.0-1/includes/api/ApiQueryExtLinksUsage.php
--- 1:1.12.0-2/includes/api/ApiQueryExtLinksUsage.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryExtLinksUsage.php	2008-07-22 14:26:15.000000000 +0100
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
 
@@ -51,43 +51,53 @@ class ApiQueryExtLinksUsage extends ApiQ
 
 		$protocol = $params['protocol'];
 		$query = $params['query'];
-		if (is_null($query))
-			$this->dieUsage('Missing required query parameter', 'params');
-		
+
 		// Find the right prefix
 		global $wgUrlProtocols;
-		foreach ($wgUrlProtocols as $p) {
-			if( substr( $p, 0, strlen( $protocol ) ) === $protocol ) {
-				$protocol = $p;
-				break;
+		if(!is_null($protocol) && !empty($protocol) && !in_array($protocol, $wgUrlProtocols))
+		{
+			foreach ($wgUrlProtocols as $p) {
+				if( substr( $p, 0, strlen( $protocol ) ) === $protocol ) {
+					$protocol = $p;
+					break;
+				}
 			}
 		}
-		
-		$likeQuery = LinkFilter::makeLike($query , $protocol);
-		if (!$likeQuery)
-			$this->dieUsage('Invalid query', 'bad_query');
-		$likeQuery = substr($likeQuery, 0, strpos($likeQuery,'%')+1);
+		else
+			$protocol = null;
 
-		$this->addTables(array('page','externallinks'));	// must be in this order for 'USE INDEX' 
+		$db = $this->getDb();
+		$this->addTables(array('page','externallinks'));	// must be in this order for 'USE INDEX'
 		$this->addOption('USE INDEX', 'el_index');
-
-		$db = $this->getDB();
 		$this->addWhere('page_id=el_from');
-		$this->addWhere('el_index LIKE ' . $db->addQuotes( $likeQuery ));
 		$this->addWhereFld('page_namespace', $params['namespace']);
 
+		if(!is_null($query) || $query != '')
+		{
+			if(is_null($protocol))
+				$protocol = 'http://';
+
+			$likeQuery = LinkFilter::makeLike($query, $protocol);
+			if (!$likeQuery)
+				$this->dieUsage('Invalid query', 'bad_query');
+			$likeQuery = substr($likeQuery, 0, strpos($likeQuery,'%')+1);
+			$this->addWhere('el_index LIKE ' . $db->addQuotes( $likeQuery ));
+		}
+		else if(!is_null($protocol))
+			$this->addWhere('el_index LIKE ' . $db->addQuotes( "$protocol%" ));
+
 		$prop = array_flip($params['prop']);
 		$fld_ids = isset($prop['ids']);
 		$fld_title = isset($prop['title']);
 		$fld_url = isset($prop['url']);
-		
+
 		if (is_null($resultPageSet)) {
 			$this->addFields(array (
 				'page_id',
 				'page_namespace',
 				'page_title'
 			));
-			$this->addFieldsIf('el_to', $fld_url);			
+			$this->addFieldsIf('el_to', $fld_url);
 		} else {
 			$this->addFields($resultPageSet->getPageTableFields());
 		}
@@ -105,7 +115,7 @@ class ApiQueryExtLinksUsage extends ApiQ
 		while ($row = $db->fetchObject($res)) {
 			if (++ $count > $limit) {
 				// We've reached the one extra which shows that there are additional pages to be had. Stop here...
-				$this->setContinueEnumParameter('offset', $offset+$limit+1);
+				$this->setContinueEnumParameter('offset', $offset+$limit);
 				break;
 			}
 
@@ -136,11 +146,11 @@ class ApiQueryExtLinksUsage extends ApiQ
 
 	public function getAllowedParams() {
 		global $wgUrlProtocols;
-		$protocols = array();
+		$protocols = array('');
 		foreach ($wgUrlProtocols as $p) {
 			$protocols[] = substr($p, 0, strpos($p,':'));
 		}
-		
+
 		return array (
 			'prop' => array (
 				ApiBase :: PARAM_ISMULTI => true,
@@ -156,7 +166,7 @@ class ApiQueryExtLinksUsage extends ApiQ
 			),
 			'protocol' => array (
 				ApiBase :: PARAM_TYPE => $protocols,
-				ApiBase :: PARAM_DFLT => 'http',
+				ApiBase :: PARAM_DFLT => '',
 			),
 			'query' => null,
 			'namespace' => array (
@@ -177,10 +187,11 @@ class ApiQueryExtLinksUsage extends ApiQ
 		return array (
 			'prop' => 'What pieces of information to include',
 			'offset' => 'Used for paging. Use the value returned for "continue"',
-			'protocol' => 'Protocol of the url',
-			'query' => 'Search string without protocol. See [[Special:LinkSearch]]',
+			'protocol' => array(	'Protocol of the url. If empty and euquery set, the protocol is http.',
+						'Leave both this and euquery empty to list all external links'),
+			'query' => 'Search string without protocol. See [[Special:LinkSearch]]. Leave empty to list all external links',
 			'namespace' => 'The page namespace(s) to enumerate.',
-			'limit' => 'How many entries to return.'
+			'limit' => 'How many pages to return.'
 		);
 	}
 
@@ -195,6 +206,6 @@ class ApiQueryExtLinksUsage extends ApiQ
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 37909 2008-07-22 13:26:15Z catrope $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryImageInfo.php 1:1.13.0-1/includes/api/ApiQueryImageInfo.php
--- 1:1.12.0-2/includes/api/ApiQueryImageInfo.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryImageInfo.php	2008-07-10 15:28:09.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * A query action to get image information and upload history.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryImageInfo extends ApiQueryBase {
 
@@ -42,65 +42,63 @@ class ApiQueryImageInfo extends ApiQuery
 	public function execute() {
 		$params = $this->extractRequestParams();
 
-		$prop = array_flip($params['prop']);		
-		$this->fld_timestamp = isset($prop['timestamp']);
-		$this->fld_user = isset($prop['user']);
-		$this->fld_comment = isset($prop['comment']);
-		$this->fld_url = isset($prop['url']);
-		$this->fld_size = isset($prop['size']);
-		$this->fld_sha1 = isset($prop['sha1']);
-		$this->fld_metadata = isset($prop['metadata']);
-		
+		$prop = array_flip($params['prop']);
+
 		if($params['urlheight'] != -1 && $params['urlwidth'] == -1)
 			$this->dieUsage("iiurlheight cannot be used without iiurlwidth", 'iiurlwidth');
-		$this->scale = ($params['urlwidth'] != -1);
-		$this->urlwidth = $params['urlwidth'];
-		$this->urlheight = $params['urlheight'];
+		
+		if ( $params['urlwidth'] != -1 ) {
+			$scale = array();
+			$scale['width'] = $params['urlwidth'];
+			$scale['height'] = $params['urlheight'];
+		} else {
+			$scale = null;
+		}
 
 		$pageIds = $this->getPageSet()->getAllTitlesByNamespace();
 		if (!empty($pageIds[NS_IMAGE])) {
-			foreach ($pageIds[NS_IMAGE] as $dbKey => $pageId) {
-								
-				$title = Title :: makeTitle(NS_IMAGE, $dbKey);
-				$img = wfFindFile($title);
-
+			
+			$result = $this->getResult();
+			$images = RepoGroup::singleton()->findFiles( array_keys( $pageIds[NS_IMAGE] ) );
+			foreach ( $images as $img ) {
 				$data = array();
-				if ( !$img ) {
-					$repository = '';
-				} else {
-
-					$repository = $img->getRepoName();
-					
-					// Get information about the current version first
-					// Check that the current version is within the start-end boundaries
-					if((is_null($params['start']) || $img->getTimestamp() <= $params['start']) &&
-							(is_null($params['end']) || $img->getTimestamp() >= $params['end'])) {
-						$data[] = $this->getInfo($img);
-					}
-					
-					// Now get the old revisions
-					// Get one more to facilitate query-continue functionality
-					$count = count($data);
-					$oldies = $img->getHistory($params['limit'] - $count + 1, $params['start'], $params['end']);
-					foreach($oldies as $oldie) {
-						if(++$count > $params['limit']) {
-							// We've reached the extra one which shows that there are additional pages to be had. Stop here...
-							// Only set a query-continue if there was only one title
-							if(count($pageIds[NS_IMAGE]) == 1)
-								$this->setContinueEnumParameter('start', $oldie->getTimestamp());
-							break;
-						}
-						$data[] = $this->getInfo($oldie);	
+				
+				// Get information about the current version first
+				// Check that the current version is within the start-end boundaries
+				if((is_null($params['start']) || $img->getTimestamp() <= $params['start']) &&
+						(is_null($params['end']) || $img->getTimestamp() >= $params['end'])) {
+					$data[] = self::getInfo( $img, $prop, $result, $scale );
+				}
+
+				// Now get the old revisions
+				// Get one more to facilitate query-continue functionality
+				$count = count($data);
+				$oldies = $img->getHistory($params['limit'] - $count + 1, $params['start'], $params['end']);
+				foreach($oldies as $oldie) {
+					if(++$count > $params['limit']) {
+						// We've reached the extra one which shows that there are additional pages to be had. Stop here...
+						// Only set a query-continue if there was only one title
+						if(count($pageIds[NS_IMAGE]) == 1)
+							$this->setContinueEnumParameter('start', $oldie->getTimestamp());
+						break;
 					}
+					$data[] = self::getInfo( $oldie, $prop, $result );
 				}
 
-				$this->getResult()->addValue(array(
-						'query', 'pages', intval($pageId)),
-						'imagerepository', $repository
+				$pageId = $pageIds[NS_IMAGE][ $img->getOriginalTitle()->getDBkey() ];
+				$result->addValue(
+					array( 'query', 'pages', intval( $pageId ) ),
+					'imagerepository', $img->getRepoName()
 				);
-				if (!empty($data))
-					$this->addPageSubItems($pageId, $data);
+				$this->addPageSubItems($pageId, $data);
 			}
+			
+			$missing = array_diff( array_keys( $pageIds[NS_IMAGE] ), array_keys( $images ) );
+			foreach ( $missing as $title )
+				$result->addValue(
+					array( 'query', 'pages', intval( $pageIds[NS_IMAGE][$title] ) ),
+					'imagerepository', ''
+				);
 		}
 	}
 
@@ -109,41 +107,48 @@ class ApiQueryImageInfo extends ApiQuery
 	 * @param File f The image
 	 * @return array Result array
 	 */
-	protected function getInfo($f) {
+	static function getInfo($file, $prop, $result, $scale = null) {
 		$vals = array();
-		if($this->fld_timestamp)
-			$vals['timestamp'] = wfTimestamp(TS_ISO_8601, $f->getTimestamp());
-		if($this->fld_user) {
-			$vals['user'] = $f->getUser();
-			if(!$f->getUser('id'))
+		if( isset( $prop['timestamp'] ) )
+			$vals['timestamp'] = wfTimestamp(TS_ISO_8601, $file->getTimestamp());
+		if( isset( $prop['user'] ) ) {
+			$vals['user'] = $file->getUser();
+			if( !$file->getUser( 'id' ) )
 				$vals['anon'] = '';
 		}
-		if($this->fld_size) {
-			$vals['size'] = intval($f->getSize());
-			$vals['width'] = intval($f->getWidth());
-			$vals['height'] = intval($f->getHeight());
-		}
-		if($this->fld_url) {
-			if($this->scale && !$f->isOld()) {
-				$thumb = $f->getThumbnail($this->urlwidth, $this->urlheight);
-				if($thumb)
+		if( isset( $prop['size'] ) || isset( $prop['dimensions'] ) ) {
+			$vals['size'] = intval( $file->getSize() );
+			$vals['width'] = intval( $file->getWidth() );
+			$vals['height'] = intval( $file->getHeight() );
+		}
+		if( isset( $prop['url'] ) ) {
+			if( !is_null( $scale ) && !$file->isOld() ) {
+				$thumb = $file->getThumbnail( $scale['width'], $scale['height'] );
+				if( $thumb )
 				{
-					$vals['thumburl'] = $thumb->getURL();
+					$vals['thumburl'] = wfExpandUrl( $thumb->getURL() );
 					$vals['thumbwidth'] = $thumb->getWidth();
 					$vals['thumbheight'] = $thumb->getHeight();
 				}
 			}
-			$vals['url'] = $f->getURL();
+			$vals['url'] = $file->getFullURL();
+			$vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl() );
 		}
-		if($this->fld_comment) 
-			$vals['comment'] = $f->getDescription();
-		if($this->fld_sha1) 
-			$vals['sha1'] = wfBaseConvert($f->getSha1(), 36, 16, 40);
-		if($this->fld_metadata) {
-			$metadata = unserialize($f->getMetadata());
-			$vals['metadata'] = $metadata ? $metadata : null;
-			$this->getResult()->setIndexedTagName_recursive($vals['metadata'], 'meta');
+		if( isset( $prop['comment'] ) )
+			$vals['comment'] = $file->getDescription();
+		if( isset( $prop['sha1'] ) )
+			$vals['sha1'] = wfBaseConvert( $file->getSha1(), 36, 16, 40 );
+		if( isset( $prop['metadata'] ) ) {
+			$metadata = $file->getMetadata();
+			$vals['metadata'] = $metadata ? unserialize( $metadata ) : null;
+			$result->setIndexedTagName_recursive( $vals['metadata'], 'meta' );
 		}
+		if( isset( $prop['mime'] ) ) 
+			$vals['mime'] = $file->getMimeType();
+		
+		if( isset( $prop['archivename'] ) && $file->isOld() )
+			$vals['archivename'] = $file->getArchiveName();
+
 		return $vals;
 	}
 
@@ -159,7 +164,9 @@ class ApiQueryImageInfo extends ApiQuery
 					'url',
 					'size',
 					'sha1',
-					'metadata'
+					'mime',
+					'metadata',
+					'archivename'
 				)
 			),
 			'limit' => array(
@@ -192,7 +199,8 @@ class ApiQueryImageInfo extends ApiQuery
 			'limit' => 'How many image revisions to return',
 			'start' => 'Timestamp to start listing from',
 			'end' => 'Timestamp to stop listing at',
-			'urlwidth' => 'If iiprop=url is set, a URL to an image scaled to this width will be returned. Only the current version of the image can be scaled.',
+			'urlwidth' => array('If iiprop=url is set, a URL to an image scaled to this width will be returned.',
+					    'Only the current version of the image can be scaled.'),
 			'urlheight' => 'Similar to iiurlwidth. Cannot be used without iiurlwidth',
 		);
 	}
@@ -211,6 +219,6 @@ class ApiQueryImageInfo extends ApiQuery
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryImageInfo.php 30665 2008-02-07 12:21:48Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryImageInfo.php 37504 2008-07-10 14:28:09Z catrope $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryImages.php 1:1.13.0-1/includes/api/ApiQueryImages.php
--- 1:1.12.0-2/includes/api/ApiQueryImages.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryImages.php	2008-07-10 22:20:43.000000000 +0100
@@ -29,9 +29,9 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * This query adds <images> subelement to all pages with the list of images embedded into those pages.
- * 
- * @addtogroup API
+ * This query adds an <images> subelement to all pages with the list of images embedded into those pages.
+ *
+ * @ingroup API
  */
 class ApiQueryImages extends ApiQueryGeneratorBase {
 
@@ -52,6 +52,7 @@ class ApiQueryImages extends ApiQueryGen
 		if ($this->getPageSet()->getGoodTitleCount() == 0)
 			return;	// nothing to do
 
+		$params = $this->extractRequestParams();
 		$this->addFields(array (
 			'il_from',
 			'il_to'
@@ -59,16 +60,40 @@ class ApiQueryImages extends ApiQueryGen
 
 		$this->addTables('imagelinks');
 		$this->addWhereFld('il_from', array_keys($this->getPageSet()->getGoodTitles()));
-		$this->addOption('ORDER BY', "il_from, il_to");
+		if(!is_null($params['continue'])) {
+			$cont = explode('|', $params['continue']);
+			if(count($cont) != 2)
+				$this->dieUsage("Invalid continue param. You should pass the " .
+					"original value returned by the previous query", "_badcontinue");
+			$ilfrom = intval($cont[0]);
+			$ilto = $this->getDb()->strencode($this->titleToKey($cont[1]));
+			$this->addWhere("il_from > $ilfrom OR ".
+					"(il_from = $ilfrom AND ".
+					"il_to >= '$ilto')");
+		}
+		# Don't order by il_from if it's constant in the WHERE clause
+		if(count($this->getPageSet()->getGoodTitles()) == 1)
+			$this->addOption('ORDER BY', 'il_to');
+		else
+			$this->addOption('ORDER BY', 'il_from, il_to');
+		$this->addOption('LIMIT', $params['limit'] + 1);
 
 		$db = $this->getDB();
 		$res = $this->select(__METHOD__);
 
 		if (is_null($resultPageSet)) {
-			
+
 			$data = array();
-			$lastId = 0;	// database has no ID 0	
+			$lastId = 0;	// database has no ID 0
+			$count = 0;
 			while ($row = $db->fetchObject($res)) {
+				if (++$count > $params['limit']) {
+					// We've reached the one extra which shows that
+					// there are additional pages to be had. Stop here...
+					$this->setContinueEnumParameter('continue', $row->il_from .
+							'|' . $this->keyToTitle($row->il_to));
+					break;
+				}
 				if ($lastId != $row->il_from) {
 					if($lastId != 0) {
 						$this->addPageSubItems($lastId, $data);
@@ -76,7 +101,7 @@ class ApiQueryImages extends ApiQueryGen
 					}
 					$lastId = $row->il_from;
 				}
-				
+
 				$vals = array();
 				ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle(NS_IMAGE, $row->il_to));
 				$data[] = $vals;
@@ -89,7 +114,15 @@ class ApiQueryImages extends ApiQueryGen
 		} else {
 
 			$titles = array();
+			$count = 0;
 			while ($row = $db->fetchObject($res)) {
+				if (++$count > $params['limit']) {
+					// We've reached the one extra which shows that
+					// there are additional pages to be had. Stop here...
+					$this->setContinueEnumParameter('continue', $row->il_from .
+							'|' . $this->keyToTitle($row->il_to));
+					break;
+				}
 				$titles[] = Title :: makeTitle(NS_IMAGE, $row->il_to);
 			}
 			$resultPageSet->populateFromTitles($titles);
@@ -98,6 +131,26 @@ class ApiQueryImages extends ApiQueryGen
 		$db->freeResult($res);
 	}
 
+	public function getAllowedParams() {
+		return array(
+				'limit' => array(
+					ApiBase :: PARAM_DFLT => 10,
+					ApiBase :: PARAM_TYPE => 'limit',
+					ApiBase :: PARAM_MIN => 1,
+					ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
+					ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+				),
+				'continue' => null,
+		);
+	}
+
+	public function getParamDescription () {
+		return array(
+			'limit' => 'How many images to return',
+			'continue' => 'When more results are available, use this to continue',
+		);
+	}
+
 	public function getDescription() {
 		return 'Returns all images contained on the given page(s)';
 	}
@@ -112,7 +165,6 @@ class ApiQueryImages extends ApiQueryGen
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryImages.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryImages.php 37535 2008-07-10 21:20:43Z catrope $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryInfo.php 1:1.13.0-1/includes/api/ApiQueryInfo.php
--- 1:1.12.0-2/includes/api/ApiQueryInfo.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryInfo.php	2008-07-06 19:43:06.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * A query module to show basic page information.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryInfo extends ApiQueryBase {
 
@@ -49,27 +49,123 @@ class ApiQueryInfo extends ApiQueryBase 
 		$pageSet->requestField('page_len');
 	}
 
+	protected function getTokenFunctions() {
+		// tokenname => function
+		// function prototype is func($pageid, $title)
+		// should return token or false
+		
+		// Don't call the hooks twice
+		if(isset($this->tokenFunctions))
+			return $this->tokenFunctions;
+
+		// If we're in JSON callback mode, no tokens can be obtained
+		if(!is_null($this->getMain()->getRequest()->getVal('callback')))
+			return array();
+
+		$this->tokenFunctions = array(
+			'edit' => array( 'ApiQueryInfo', 'getEditToken' ),
+			'delete' => array( 'ApiQueryInfo', 'getDeleteToken' ),
+			'protect' => array( 'ApiQueryInfo', 'getProtectToken' ),
+			'move' => array( 'ApiQueryInfo', 'getMoveToken' ),
+			'block' => array( 'ApiQueryInfo', 'getBlockToken' ),
+			'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' )
+		);
+		wfRunHooks('APIQueryInfoTokens', array(&$this->tokenFunctions));
+		return $this->tokenFunctions;
+	}
+
+	public static function getEditToken($pageid, $title)
+	{
+		// We could check for $title->userCan('edit') here,
+		// but that's too expensive for this purpose
+		global $wgUser;
+		if(!$wgUser->isAllowed('edit'))
+			return false;
+		
+		// The edit token is always the same, let's exploit that
+		static $cachedEditToken = null;
+		if(!is_null($cachedEditToken))
+			return $cachedEditToken;
+
+		$cachedEditToken = $wgUser->editToken();
+		return $cachedEditToken;
+	}
+	
+	public static function getDeleteToken($pageid, $title)
+	{
+		global $wgUser;
+		if(!$wgUser->isAllowed('delete'))
+			return false;			
+
+		static $cachedDeleteToken = null;
+		if(!is_null($cachedDeleteToken))
+			return $cachedDeleteToken;
+
+		$cachedDeleteToken = $wgUser->editToken();
+		return $cachedDeleteToken;
+	}
+
+	public static function getProtectToken($pageid, $title)
+	{
+		global $wgUser;
+		if(!$wgUser->isAllowed('protect'))
+			return false;
+
+		static $cachedProtectToken = null;
+		if(!is_null($cachedProtectToken))
+			return $cachedProtectToken;
+
+		$cachedProtectToken = $wgUser->editToken();
+		return $cachedProtectToken;
+	}
+
+	public static function getMoveToken($pageid, $title)
+	{
+		global $wgUser;
+		if(!$wgUser->isAllowed('move'))
+			return false;
+
+		static $cachedMoveToken = null;
+		if(!is_null($cachedMoveToken))
+			return $cachedMoveToken;
+
+		$cachedMoveToken = $wgUser->editToken();
+		return $cachedMoveToken;
+	}
+
+	public static function getBlockToken($pageid, $title)
+	{
+		global $wgUser;
+		if(!$wgUser->isAllowed('block'))
+			return false;
+
+		static $cachedBlockToken = null;
+		if(!is_null($cachedBlockToken))
+			return $cachedBlockToken;
+
+		$cachedBlockToken = $wgUser->editToken();
+		return $cachedBlockToken;
+	}
+
+	public static function getUnblockToken($pageid, $title)
+	{
+		// Currently, this is exactly the same as the block token
+		return self::getBlockToken($pageid, $title);
+	}
+
 	public function execute() {
 
 		global $wgUser;
 
 		$params = $this->extractRequestParams();
-		$fld_protection = false;
+		$fld_protection = $fld_talkid = $fld_subjectid = false;
 		if(!is_null($params['prop'])) {
 			$prop = array_flip($params['prop']);
 			$fld_protection = isset($prop['protection']);
+			$fld_talkid = isset($prop['talkid']);
+			$fld_subjectid = isset($prop['subjectid']);
 		}
-		if(!is_null($params['token'])) {
-			$token = $params['token'];
-			$tok_edit = $this->getTokenFlag($token, 'edit');
-			$tok_delete = $this->getTokenFlag($token, 'delete');
-			$tok_protect = $this->getTokenFlag($token, 'protect');
-			$tok_move = $this->getTokenFlag($token, 'move');
-		}
-		else
-			// Fix E_NOTICEs about unset variables
-			$token = $tok_edit = $tok_delete = $tok_protect = $tok_move = null;
-		
+
 		$pageSet = $this->getPageSet();
 		$titles = $pageSet->getGoodTitles();
 		$missing = $pageSet->getMissingTitles();
@@ -101,7 +197,64 @@ class ApiQueryInfo extends ApiQueryBase 
 				$protections[$row->pr_page][] = $a;
 			}
 			$db->freeResult($res);
+			
+			$imageIds = array();
+			foreach ($titles as $id => $title)
+				if ($title->getNamespace() == NS_IMAGE)
+					$imageIds[] = $id;
+			// To avoid code duplication
+			$cascadeTypes = array(
+				array(
+					'prefix' => 'tl',
+					'table' => 'templatelinks',
+					'ns' => 'tl_namespace',
+					'title' => 'tl_title',
+					'ids' => array_diff(array_keys($titles), $imageIds)
+				),
+				array(
+				 	'prefix' => 'il',
+				 	'table' => 'imagelinks',
+				 	'ns' => NS_IMAGE,
+				 	'title' => 'il_to',
+				 	'ids' => $imageIds
+				)
+			);
+			
+			foreach ($cascadeTypes as $type)
+			{
+				if (count($type['ids']) != 0) {
+					$this->resetQueryParams();
+					$this->addTables(array('page_restrictions', $type['table']));
+					$this->addTables('page', 'page_source');
+					$this->addTables('page', 'page_target');
+					$this->addFields(array('pr_type', 'pr_level', 'pr_expiry', 
+							'page_target.page_id AS page_target_id',
+							'page_source.page_namespace AS page_source_namespace',
+							'page_source.page_title AS page_source_title'));
+					$this->addWhere(array("{$type['prefix']}_from = pr_page", 
+							'page_target.page_namespace = '.$type['ns'], 
+							'page_target.page_title = '.$type['title'],
+							'page_source.page_id = pr_page'
+					));
+					$this->addWhereFld('pr_cascade', 1);
+					$this->addWhereFld('page_target.page_id', $type['ids']);
+				
+					$res = $this->select(__METHOD__);
+					while($row = $db->fetchObject($res)) {
+						$source = Title::makeTitle($row->page_source_namespace, $row->page_source_title);
+						$a = array(
+							'type' => $row->pr_type,
+							'level' => $row->pr_level,
+							'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
+							'source' => $source->getPrefixedText()
+						);
+						$protections[$row->page_target_id][] = $a;
+					}
+					$db->freeResult($res);
+				}
+			}
 		}
+
 		// We don't need to check for pt stuff if there are no nonexistent titles
 		if($fld_protection && !empty($missing))
 		{
@@ -114,13 +267,107 @@ class ApiQueryInfo extends ApiQueryBase 
 			$res = $this->select(__METHOD__);
 			$prottitles = array();
 			while($row = $db->fetchObject($res)) {
-				$prottitles[$row->pt_namespace][$row->pt_title] = array(
+				$prottitles[$row->pt_namespace][$row->pt_title][] = array(
 					'type' => 'create',
 					'level' => $row->pt_create_perm,
 					'expiry' => Block::decodeExpiry($row->pt_expiry, TS_ISO_8601)
 				);
 			}
 			$db->freeResult($res);
+			
+			$images = array();
+			$others = array();
+			foreach ($missing as $title)
+				if ($title->getNamespace() == NS_IMAGE)
+					$images[] = $title->getDbKey();
+				else
+					$others[] = $title;					
+			
+			if (count($others) != 0) {
+				$lb = new LinkBatch($others);
+				$this->resetQueryParams();
+				$this->addTables(array('page_restrictions', 'page', 'templatelinks'));
+				$this->addFields(array('pr_type', 'pr_level', 'pr_expiry', 
+						'page_title', 'page_namespace',
+						'tl_title', 'tl_namespace'));
+				$this->addWhere($lb->constructSet('tl', $db));
+				$this->addWhere('pr_page = page_id');
+				$this->addWhere('pr_page = tl_from');
+				$this->addWhereFld('pr_cascade', 1);
+				
+				$res = $this->select(__METHOD__);
+				while($row = $db->fetchObject($res)) {
+					$source = Title::makeTitle($row->page_namespace, $row->page_title);
+					$a = array(
+						'type' => $row->pr_type,
+						'level' => $row->pr_level,
+						'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
+						'source' => $source->getPrefixedText()
+					);
+					$prottitles[$row->tl_namespace][$row->tl_title][] = $a;
+				}
+				$db->freeResult($res);
+			}
+			
+			if (count($images) != 0) {
+				$this->resetQueryParams();
+				$this->addTables(array('page_restrictions', 'page', 'imagelinks'));
+				$this->addFields(array('pr_type', 'pr_level', 'pr_expiry', 
+						'page_title', 'page_namespace', 'il_to'));
+				$this->addWhere('pr_page = page_id');
+				$this->addWhere('pr_page = il_from');
+				$this->addWhereFld('pr_cascade', 1);
+				$this->addWhereFld('il_to', $images);
+				
+				$res = $this->select(__METHOD__);
+				while($row = $db->fetchObject($res)) {
+					$source = Title::makeTitle($row->page_namespace, $row->page_title);
+					$a = array(
+						'type' => $row->pr_type,
+						'level' => $row->pr_level,
+						'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
+						'source' => $source->getPrefixedText()
+					);
+					$prottitles[NS_IMAGE][$row->il_to][] = $a;
+				}
+				$db->freeResult($res);
+			}
+		}
+
+		// Run the talkid/subjectid query
+		if($fld_talkid || $fld_subjectid)
+		{
+			$talktitles = $subjecttitles =
+				$talkids = $subjectids = array();
+			$everything = array_merge($titles, $missing);
+			foreach($everything as $t)
+			{
+				if(MWNamespace::isTalk($t->getNamespace()))
+				{
+					if($fld_subjectid)
+						$subjecttitles[] = $t->getSubjectPage();
+				}
+				else if($fld_talkid)
+					$talktitles[] = $t->getTalkPage();
+			}
+			if(!empty($talktitles) || !empty($subjecttitles))
+			{
+				// Construct a custom WHERE clause that matches
+				// all titles in $talktitles and $subjecttitles
+				$lb = new LinkBatch(array_merge($talktitles, $subjecttitles));
+				$this->resetQueryParams();
+				$this->addTables('page');
+				$this->addFields(array('page_title', 'page_namespace', 'page_id'));
+				$this->addWhere($lb->constructSet('page', $db));
+				$res = $this->select(__METHOD__);
+				while($row = $db->fetchObject($res))
+				{
+					if(MWNamespace::isTalk($row->page_namespace))
+						$talkids[MWNamespace::getSubject($row->page_namespace)][$row->page_title] = $row->page_id;
+					else
+						$subjectids[MWNamespace::getTalk($row->page_namespace)][$row->page_title] = $row->page_id;
+				}
+			}
 		}
 
 		foreach ( $titles as $pageid => $title ) {
@@ -137,18 +384,18 @@ class ApiQueryInfo extends ApiQueryBase 
 			if ($pageIsNew[$pageid])
 				$pageInfo['new'] = '';
 
-			if (!is_null($token)) {
-				// Currently all tokens are generated the same way, but it might change
-				if ($tok_edit)
-					$pageInfo['edittoken'] = $wgUser->editToken();
-				if ($tok_delete)
-					$pageInfo['deletetoken'] = $wgUser->editToken();
-				if ($tok_protect)
-					$pageInfo['protecttoken'] = $wgUser->editToken();
-				if ($tok_move)
-					$pageInfo['movetoken'] = $wgUser->editToken();
+			if (!is_null($params['token'])) {
+				$tokenFunctions = $this->getTokenFunctions();
+				foreach($params['token'] as $t)
+				{
+					$val = call_user_func($tokenFunctions[$t], $pageid, $title);
+					if($val === false)
+						$this->setWarning("Action '$t' is not allowed for the current user");
+					else
+						$pageInfo[$t . 'token'] = $val;
+				}
 			}
-			
+
 			if($fld_protection) {
 				if (isset($protections[$pageid])) {
 					$pageInfo['protection'] = $protections[$pageid];
@@ -186,6 +433,10 @@ class ApiQueryInfo extends ApiQueryBase 
 					}
 				}
 			}
+			if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDbKey()]))
+				$pageInfo['talkid'] = $talkids[$title->getNamespace()][$title->getDbKey()];
+			if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDbKey()]))
+				$pageInfo['subjectid'] = $subjectids[$title->getNamespace()][$title->getDbKey()];
 
 			$result->addValue(array (
 				'query',
@@ -195,24 +446,34 @@ class ApiQueryInfo extends ApiQueryBase 
 
 		// Get edit/protect tokens and protection data for missing titles if requested
 		// Delete and move tokens are N/A for missing titles anyway
-		if($tok_edit || $tok_protect || $fld_protection)
+		if(!is_null($params['token']) || $fld_protection || $fld_talkid || $fld_subjectid)
 		{
 			$res = &$result->getData();
 			foreach($missing as $pageid => $title) {
-				if($tok_edit)
-					$res['query']['pages'][$pageid]['edittoken'] = $wgUser->editToken();
-				if($tok_protect)
-					$res['query']['pages'][$pageid]['protecttoken'] = $wgUser->editToken();
+				if(!is_null($params['token'])) 
+				{
+					$tokenFunctions = $this->getTokenFunctions();
+					foreach($params['token'] as $t)
+					{
+						$val = call_user_func($tokenFunctions[$t], $pageid, $title);
+						if($val !== false)
+							$res['query']['pages'][$pageid][$t . 'token'] = $val;
+					}
+				}
 				if($fld_protection)
 				{
 					// Apparently the XML formatting code doesn't like array(null)
 					// This is painful to fix, so we'll just work around it
 					if(isset($prottitles[$title->getNamespace()][$title->getDBkey()]))
-						$res['query']['pages'][$pageid]['protection'][] = $prottitles[$title->getNamespace()][$title->getDBkey()];
+						$res['query']['pages'][$pageid]['protection'] = $prottitles[$title->getNamespace()][$title->getDBkey()];
 					else
 						$res['query']['pages'][$pageid]['protection'] = array();
 					$result->setIndexedTagName($res['query']['pages'][$pageid]['protection'], 'pr');
 				}
+				if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDbKey()]))
+					$res['query']['pages'][$pageid]['talkid'] = $talkids[$title->getNamespace()][$title->getDbKey()];
+				if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDbKey()]))
+					$res['query']['pages'][$pageid]['subjectid'] = $subjectids[$title->getNamespace()][$title->getDbKey()];
 			}
 		}
 	}
@@ -223,17 +484,15 @@ class ApiQueryInfo extends ApiQueryBase 
 				ApiBase :: PARAM_DFLT => NULL,
 				ApiBase :: PARAM_ISMULTI => true,
 				ApiBase :: PARAM_TYPE => array (
-					'protection'
+					'protection',
+					'talkid',
+					'subjectid'
 				)),
 			'token' => array (
 				ApiBase :: PARAM_DFLT => NULL,
 				ApiBase :: PARAM_ISMULTI => true,
-				ApiBase :: PARAM_TYPE => array (
-					'edit',
-					'delete',
-					'protect',
-					'move',
-				)),
+				ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions())
+			)
 		);
 	}
 
@@ -241,7 +500,9 @@ class ApiQueryInfo extends ApiQueryBase 
 		return array (
 			'prop' => array (
 				'Which additional properties to get:',
-				' "protection"   - List the protection level of each page'
+				' "protection"   - List the protection level of each page',
+				' "talkid"       - The page ID of the talk page for each non-talk page',
+				' "subjectid"     - The page ID of the parent page for each talk page'
 			),
 			'token' => 'Request a token to perform a data-modifying action on a page',
 		);
@@ -260,7 +521,6 @@ class ApiQueryInfo extends ApiQueryBase 
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryInfo.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryInfo.php 37191 2008-07-06 18:43:06Z brion $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryLangLinks.php 1:1.13.0-1/includes/api/ApiQueryLangLinks.php
--- 1:1.12.0-2/includes/api/ApiQueryLangLinks.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryLangLinks.php	2008-07-10 22:08:37.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * A query module to list all langlinks (links to correspanding foreign language pages).
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryLangLinks extends ApiQueryBase {
 
@@ -40,6 +40,10 @@ class ApiQueryLangLinks extends ApiQuery
 	}
 
 	public function execute() {
+		if ( $this->getPageSet()->getGoodTitleCount() == 0 )
+			return;
+
+		$params = $this->extractRequestParams();
 		$this->addFields(array (
 			'll_from',
 			'll_lang',
@@ -48,14 +52,36 @@ class ApiQueryLangLinks extends ApiQuery
 
 		$this->addTables('langlinks');
 		$this->addWhereFld('ll_from', array_keys($this->getPageSet()->getGoodTitles()));
-		$this->addOption('ORDER BY', "ll_from, ll_lang");
+		if(!is_null($params['continue'])) {
+			$cont = explode('|', $params['continue']);
+			if(count($cont) != 2)
+				$this->dieUsage("Invalid continue param. You should pass the " .
+					"original value returned by the previous query", "_badcontinue");
+			$llfrom = intval($cont[0]);
+			$lllang = $this->getDb()->strencode($cont[1]);
+			$this->addWhere("ll_from > $llfrom OR ".
+					"(ll_from = $llfrom AND ".
+					"ll_lang >= '$lllang')");
+		}
+		# Don't order by ll_from if it's constant in the WHERE clause
+		if(count($this->getPageSet()->getGoodTitles()) == 1)
+			$this->addOption('ORDER BY', 'll_lang');
+		else
+			$this->addOption('ORDER BY', 'll_from, ll_lang');
+		$this->addOption('LIMIT', $params['limit'] + 1);
 		$res = $this->select(__METHOD__);
 
 		$data = array();
-		$lastId = 0;	// database has no ID 0	
+		$lastId = 0;	// database has no ID 0
+		$count = 0;
 		$db = $this->getDB();
 		while ($row = $db->fetchObject($res)) {
-
+			if (++$count > $params['limit']) {
+				// We've reached the one extra which shows that
+				// there are additional pages to be had. Stop here...
+				$this->setContinueEnumParameter('continue', "{$row->ll_from}|{$row->ll_lang}");
+				break;
+			}
 			if ($lastId != $row->ll_from) {
 				if($lastId != 0) {
 					$this->addPageSubItems($lastId, $data);
@@ -64,7 +90,7 @@ class ApiQueryLangLinks extends ApiQuery
 				$lastId = $row->ll_from;
 			}
 
-			$entry = array('lang'=>$row->ll_lang);
+			$entry = array('lang' => $row->ll_lang);
 			ApiResult :: setContent($entry, $row->ll_title);
 			$data[] = $entry;
 		}
@@ -76,6 +102,26 @@ class ApiQueryLangLinks extends ApiQuery
 		$db->freeResult($res);
 	}
 
+	public function getAllowedParams() {
+		return array(
+				'limit' => array(
+					ApiBase :: PARAM_DFLT => 10,
+					ApiBase :: PARAM_TYPE => 'limit',
+					ApiBase :: PARAM_MIN => 1,
+					ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
+					ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+				),
+				'continue' => null,
+		);
+	}
+
+	public function getParamDescription () {
+		return array(
+			'limit' => 'How many langlinks to return',
+			'continue' => 'When more results are available, use this to continue',
+		);
+	}
+
 	public function getDescription() {
 		return 'Returns all interlanguage links from the given page(s)';
 	}
@@ -88,7 +134,6 @@ class ApiQueryLangLinks extends ApiQuery
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryLangLinks.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryLangLinks.php 37534 2008-07-10 21:08:37Z brion $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryLinks.php 1:1.13.0-1/includes/api/ApiQueryLinks.php
--- 1:1.12.0-2/includes/api/ApiQueryLinks.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryLinks.php	2008-07-22 14:26:15.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * A query module to list all wiki links on a given set of pages.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryLinks extends ApiQueryGeneratorBase {
 
@@ -41,7 +41,7 @@ class ApiQueryLinks extends ApiQueryGene
 	private $table, $prefix, $description;
 
 	public function __construct($query, $moduleName) {
-		
+
 		switch ($moduleName) {
 			case self::LINKS :
 				$this->table = 'pagelinks';
@@ -84,16 +84,54 @@ class ApiQueryLinks extends ApiQueryGene
 		$this->addTables($this->table);
 		$this->addWhereFld($this->prefix . '_from', array_keys($this->getPageSet()->getGoodTitles()));
 		$this->addWhereFld($this->prefix . '_namespace', $params['namespace']);
-		$this->addOption('ORDER BY', str_replace('pl_', $this->prefix . '_', 'pl_from, pl_namespace, pl_title'));
+
+		if(!is_null($params['continue'])) {
+			$cont = explode('|', $params['continue']);
+			if(count($cont) != 3)
+				$this->dieUsage("Invalid continue param. You should pass the " .
+					"original value returned by the previous query", "_badcontinue");
+			$plfrom = intval($cont[0]);
+			$plns = intval($cont[1]);
+			$pltitle = $this->getDb()->strencode($this->titleToKey($cont[2]));
+			$this->addWhere("{$this->prefix}_from > $plfrom OR ".
+					"({$this->prefix}_from = $plfrom AND ".
+					"({$this->prefix}_namespace > $plns OR ".
+					"({$this->prefix}_namespace = $plns AND ".
+					"{$this->prefix}_title >= '$pltitle')))");
+		}
+
+		# Here's some MySQL craziness going on: if you use WHERE foo='bar'
+		# and later ORDER BY foo MySQL doesn't notice the ORDER BY is pointless
+		# but instead goes and filesorts, because the index for foo was used
+		# already. To work around this, we drop constant fields in the WHERE
+		# clause from the ORDER BY clause
+		$order = array();
+		if(count($this->getPageSet()->getGoodTitles()) != 1)
+			$order[] = "{$this->prefix}_from";
+		if(count($params['namespace']) != 1)
+			$order[] = "{$this->prefix}_namespace";
+		$order[] = "{$this->prefix}_title";
+		$this->addOption('ORDER BY', implode(", ", $order));
+		$this->addOption('USE INDEX', "{$this->prefix}_from");
+		$this->addOption('LIMIT', $params['limit'] + 1);
 
 		$db = $this->getDB();
 		$res = $this->select(__METHOD__);
 
 		if (is_null($resultPageSet)) {
-			
+
 			$data = array();
-			$lastId = 0;	// database has no ID 0	
+			$lastId = 0;	// database has no ID 0
+			$count = 0;
 			while ($row = $db->fetchObject($res)) {
+				if(++$count > $params['limit']) {
+					// We've reached the one extra which shows that
+					// there are additional pages to be had. Stop here...
+					$this->setContinueEnumParameter('continue',
+						"{$row->pl_from}|{$row->pl_namespace}|" .
+						$this->keyToTitle($row->pl_title));
+					break;
+				}
 				if ($lastId != $row->pl_from) {
 					if($lastId != 0) {
 						$this->addPageSubItems($lastId, $data);
@@ -114,7 +152,16 @@ class ApiQueryLinks extends ApiQueryGene
 		} else {
 
 			$titles = array();
+			$count = 0;
 			while ($row = $db->fetchObject($res)) {
+				if(++$count > $params['limit']) {
+					// We've reached the one extra which shows that
+					// there are additional pages to be had. Stop here...
+					$this->setContinueEnumParameter('continue',
+						"{$row->pl_from}|{$row->pl_namespace}|" .
+						$this->keyToTitle($row->pl_title));
+					break;
+				}
 				$titles[] = Title :: makeTitle($row->pl_namespace, $row->pl_title);
 			}
 			$resultPageSet->populateFromTitles($titles);
@@ -129,15 +176,25 @@ class ApiQueryLinks extends ApiQueryGene
 				'namespace' => array(
 					ApiBase :: PARAM_TYPE => 'namespace',
 					ApiBase :: PARAM_ISMULTI => true
-				)
+				),
+				'limit' => array(
+					ApiBase :: PARAM_DFLT => 10,
+					ApiBase :: PARAM_TYPE => 'limit',
+					ApiBase :: PARAM_MIN => 1,
+					ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
+					ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+				),
+				'continue' => null,
 			);
 	}
 
 	public function getParamDescription()
 	{
 		return array(
-				'namespace' => "Show {$this->description}s in this namespace(s) only"
-			);
+				'namespace' => "Show {$this->description}s in this namespace(s) only",
+				'limit' => "How many {$this->description}s to return",
+				'continue' => 'When more results are available, use this to continue',
+		);
 	}
 
 	public function getDescription() {
@@ -156,7 +213,6 @@ class ApiQueryLinks extends ApiQueryGene
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryLinks.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryLinks.php 37909 2008-07-22 13:26:15Z catrope $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryLogEvents.php 1:1.13.0-1/includes/api/ApiQueryLogEvents.php
--- 1:1.12.0-2/includes/api/ApiQueryLogEvents.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryLogEvents.php	2008-05-20 18:13:28.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * Query action to List the log events, with optional filtering by various parameters.
- *  
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryLogEvents extends ApiQueryBase {
 
@@ -40,7 +40,7 @@ class ApiQueryLogEvents extends ApiQuery
 	}
 
 	public function execute() {
-		$params = $this->extractRequestParams();		
+		$params = $this->extractRequestParams();
 		$db = $this->getDB();
 
 		$prop = $params['prop'];
@@ -54,19 +54,26 @@ class ApiQueryLogEvents extends ApiQuery
 
 		list($tbl_logging, $tbl_page, $tbl_user) = $db->tableNamesN('logging', 'page', 'user');
 
-		$this->addOption('STRAIGHT_JOIN');
-		$this->addTables("$tbl_logging LEFT OUTER JOIN $tbl_page ON " .
-		"log_namespace=page_namespace AND log_title=page_title " .
-		"INNER JOIN $tbl_user ON user_id=log_user");
+		$hideLogs = LogEventsList::getExcludeClause($db);
+		if($hideLogs !== false)
+			$this->addWhere($hideLogs);
+
+		// Order is significant here
+		$this->addTables(array('user', 'page', 'logging'));
+		$this->addJoinConds(array(
+			'page' => array('LEFT JOIN',
+				array(	'log_namespace=page_namespace',
+					'log_title=page_title'))));
+		$this->addWhere('user_id=log_user');
+		$this->addOption('USE INDEX', array('logging' => 'times')); // default, may change
 
 		$this->addFields(array (
 			'log_type',
 			'log_action',
 			'log_timestamp',
 		));
-		
-		// FIXME: Fake out log_id for now until the column is live on Wikimedia
-		// $this->addFieldsIf('log_id', $this->fld_ids);
+
+		$this->addFieldsIf('log_id', $this->fld_ids);
 		$this->addFieldsIf('page_id', $this->fld_ids);
 		$this->addFieldsIf('log_user', $this->fld_user);
 		$this->addFieldsIf('user_name', $this->fld_user);
@@ -74,10 +81,14 @@ class ApiQueryLogEvents extends ApiQuery
 		$this->addFieldsIf('log_title', $this->fld_title);
 		$this->addFieldsIf('log_comment', $this->fld_comment);
 		$this->addFieldsIf('log_params', $this->fld_details);
-		
 
 		$this->addWhereFld('log_deleted', 0);
-		$this->addWhereFld('log_type', $params['type']);
+		
+		if( !is_null($params['type']) ) {
+			$this->addWhereFld('log_type', $params['type']);
+			$this->addOption('USE INDEX', array('logging' => array('type_time')));
+		}
+		
 		$this->addWhereRange('log_timestamp', $params['dir'], $params['start'], $params['end']);
 
 		$limit = $params['limit'];
@@ -91,6 +102,7 @@ class ApiQueryLogEvents extends ApiQuery
 			if (!$userid)
 				$this->dieUsage("User name $user not found", 'param_user');
 			$this->addWhereFld('log_user', $userid);
+			$this->addOption('USE INDEX', array('logging' => array('user_time','page_time')));
 		}
 
 		$title = $params['title'];
@@ -100,6 +112,7 @@ class ApiQueryLogEvents extends ApiQuery
 				$this->dieUsage("Bad title value '$title'", 'param_title');
 			$this->addWhereFld('log_namespace', $titleObj->getNamespace());
 			$this->addWhereFld('log_title', $titleObj->getDBkey());
+			$this->addOption('USE INDEX', array('logging' => array('user_time','page_time')));
 		}
 
 		$data = array ();
@@ -126,26 +139,24 @@ class ApiQueryLogEvents extends ApiQuery
 		$vals = array();
 
 		if ($this->fld_ids) {
-			// FIXME: Fake out log_id for now until the column is live on Wikimedia
-			// $vals['logid'] = intval($row->log_id);
-			$vals['logid'] = 0;
+			$vals['logid'] = intval($row->log_id);
 			$vals['pageid'] = intval($row->page_id);
 		}
-		
+
 		if ($this->fld_title) {
 			$title = Title :: makeTitle($row->log_namespace, $row->log_title);
 			ApiQueryBase :: addTitleInfo($vals, $title);
 		}
-		
+
 		if ($this->fld_type) {
 			$vals['type'] = $row->log_type;
 			$vals['action'] = $row->log_action;
 		}
-		
+
 		if ($this->fld_details && $row->log_params !== '') {
 			$params = explode("\n", $row->log_params);
 			switch ($row->log_type) {
-				case 'move': 
+				case 'move':
 					if (isset ($params[0])) {
 						$title = Title :: newFromText($params[0]);
 						if ($title) {
@@ -175,7 +186,7 @@ class ApiQueryLogEvents extends ApiQuery
 					$params = null;
 					break;
 			}
-			
+
 			if (isset($params)) {
 				$this->getResult()->setIndexedTagName($params, 'param');
 				$vals = array_merge($vals, $params);
@@ -193,7 +204,7 @@ class ApiQueryLogEvents extends ApiQuery
 		if ($this->fld_comment && !empty ($row->log_comment)) {
 			$vals['comment'] = $row->log_comment;
 		}
-			
+
 		return $vals;
 	}
 
@@ -215,7 +226,6 @@ class ApiQueryLogEvents extends ApiQuery
 				)
 			),
 			'type' => array (
-				ApiBase :: PARAM_ISMULTI => true,
 				ApiBase :: PARAM_TYPE => $wgLogTypes
 			),
 			'start' => array (
@@ -267,7 +277,6 @@ class ApiQueryLogEvents extends ApiQuery
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryLogEvents.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryLogEvents.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQuery.php 1:1.13.0-1/includes/api/ApiQuery.php
--- 1:1.12.0-2/includes/api/ApiQuery.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQuery.php	2008-05-20 18:13:28.000000000 +0100
@@ -33,11 +33,11 @@ if (!defined('MEDIAWIKI')) {
  * it will create a list of titles to work on (an instance of the ApiPageSet object)
  * instantiate and execute various property/list/meta modules,
  * and assemble all resulting data into a single ApiResult object.
- * 
+ *
  * In the generator mode, a generator will be first executed to populate a second ApiPageSet object,
  * and that object will be used for all subsequent modules.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQuery extends ApiBase {
 
@@ -55,9 +55,11 @@ class ApiQuery extends ApiBase {
 		'templates' => 'ApiQueryLinks',
 		'categories' => 'ApiQueryCategories',
 		'extlinks' => 'ApiQueryExternalLinks',
+		'categoryinfo' => 'ApiQueryCategoryInfo',
 	);
 
 	private $mQueryListModules = array (
+		'allimages' => 'ApiQueryAllimages',
 		'allpages' => 'ApiQueryAllpages',
 		'alllinks' => 'ApiQueryAllLinks',
 		'allcategories' => 'ApiQueryAllCategories',
@@ -90,7 +92,7 @@ class ApiQuery extends ApiBase {
 	public function __construct($main, $action) {
 		parent :: __construct($main, $action);
 
-		// Allow custom modules to be added in LocalSettings.php		
+		// Allow custom modules to be added in LocalSettings.php
 		global $wgApiQueryPropModules, $wgApiQueryListModules, $wgApiQueryMetaModules;
 		self :: appendUserModules($this->mQueryPropModules, $wgApiQueryPropModules);
 		self :: appendUserModules($this->mQueryListModules, $wgApiQueryListModules);
@@ -122,7 +124,7 @@ class ApiQuery extends ApiBase {
 	public function getDB() {
 		if (!isset ($this->mSlaveDB)) {
 			$this->profileDBIn();
-			$this->mSlaveDB = wfGetDB(DB_SLAVE);
+			$this->mSlaveDB = wfGetDB(DB_SLAVE,'api');
 			$this->profileDBOut();
 		}
 		return $this->mSlaveDB;
@@ -130,9 +132,9 @@ class ApiQuery extends ApiBase {
 
 	/**
 	 * Get the query database connection with the given name.
-	 * If no such connection has been requested before, it will be created. 
-	 * Subsequent calls with the same $name will return the same connection 
-	 * as the first, regardless of $db or $groups new values. 
+	 * If no such connection has been requested before, it will be created.
+	 * Subsequent calls with the same $name will return the same connection
+	 * as the first, regardless of $db or $groups new values.
 	 */
 	public function getNamedDB($name, $db, $groups) {
 		if (!array_key_exists($name, $this->mNamedDB)) {
@@ -149,7 +151,7 @@ class ApiQuery extends ApiBase {
 	public function getPageSet() {
 		return $this->mPageSet;
 	}
-	
+
 	/**
 	 * Get the array mapping module names to class names
 	 */
@@ -161,17 +163,17 @@ class ApiQuery extends ApiBase {
 	 * Query execution happens in the following steps:
 	 * #1 Create a PageSet object with any pages requested by the user
 	 * #2 If using generator, execute it to get a new PageSet object
-	 * #3 Instantiate all requested modules. 
+	 * #3 Instantiate all requested modules.
 	 *    This way the PageSet object will know what shared data is required,
-	 *    and minimize DB calls. 
+	 *    and minimize DB calls.
 	 * #4 Output all normalization and redirect resolution information
 	 * #5 Execute all requested modules
 	 */
 	public function execute() {
-		
+
 		$this->params = $this->extractRequestParams();
 		$this->redirects = $this->params['redirects'];
-		
+
 		//
 		// Create PageSet
 		//
@@ -186,7 +188,7 @@ class ApiQuery extends ApiBase {
 		$this->InstantiateModules($modules, 'meta', $this->mQueryMetaModules);
 
 		//
-		// If given, execute generator to substitute user supplied data with generated data.  
+		// If given, execute generator to substitute user supplied data with generated data.
 		//
 		if (isset ($this->params['generator'])) {
 			$this->executeGeneratorModule($this->params['generator'], $modules);
@@ -210,21 +212,21 @@ class ApiQuery extends ApiBase {
 			$module->profileOut();
 		}
 	}
-	
+
 	/**
 	 * Query modules may optimize data requests through the $this->getPageSet() object
 	 * by adding extra fields from the page table.
-	 * This function will gather all the extra request fields from the modules. 
+	 * This function will gather all the extra request fields from the modules.
 	 */
 	private function addCustomFldsToPageSet($modules, $pageSet) {
-		// Query all requested modules. 
+		// Query all requested modules.
 		foreach ($modules as $module) {
 			$module->requestExtraData($pageSet);
 		}
 	}
 
 	/**
-	 * Create instances of all modules requested by the client 
+	 * Create instances of all modules requested by the client
 	 */
 	private function InstantiateModules(&$modules, $param, $moduleList) {
 		$list = $this->params[$param];
@@ -235,7 +237,7 @@ class ApiQuery extends ApiBase {
 
 	/**
 	 * Appends an element for each page in the current pageSet with the most general
-	 * information (id, title), plus any title normalizations and missing title/pageids/revids.
+	 * information (id, title), plus any title normalizations and missing or invalid title/pageids/revids.
 	 */
 	private function outputGeneralPageInfo() {
 
@@ -255,7 +257,7 @@ class ApiQuery extends ApiBase {
 			$result->setIndexedTagName($normValues, 'n');
 			$result->addValue('query', 'normalized', $normValues);
 		}
-		
+
 		// Interwiki titles
 		$intrwValues = array ();
 		foreach ($pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr) {
@@ -269,12 +271,12 @@ class ApiQuery extends ApiBase {
 			$result->setIndexedTagName($intrwValues, 'i');
 			$result->addValue('query', 'interwiki', $intrwValues);
 		}
-		
+
 		// Show redirect information
 		$redirValues = array ();
 		foreach ($pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo) {
 			$redirValues[] = array (
-				'from' => $titleStrFrom,
+				'from' => strval($titleStrFrom),
 				'to' => $titleStrTo
 			);
 		}
@@ -311,7 +313,9 @@ class ApiQuery extends ApiBase {
 			$vals['missing'] = '';
 			$pages[$fakeId] = $vals;
 		}
-
+		// Report any invalid titles
+		foreach ($pageSet->getInvalidTitles() as $fakeId => $title)
+			$pages[$fakeId] = array('title' => $title, 'invalid' => '');
 		// Report any missing page ids
 		foreach ($pageSet->getMissingPageIDs() as $pageid) {
 			$pages[$pageid] = array (
@@ -329,7 +333,7 @@ class ApiQuery extends ApiBase {
 		}
 
 		if (!empty ($pages)) {
-			
+
 			if ($this->params['indexpageids']) {
 				$pageIDs = array_keys($pages);
 				// json treats all map keys as strings - converting to match
@@ -337,14 +341,14 @@ class ApiQuery extends ApiBase {
 				$result->setIndexedTagName($pageIDs, 'id');
 				$result->addValue('query', 'pageids', $pageIDs);
 			}
-						
+
 			$result->setIndexedTagName($pages, 'page');
 			$result->addValue('query', 'pages', $pages);
 		}
 	}
 
 	/**
-	 * For generator mode, execute generator, and use its output as new pageSet 
+	 * For generator mode, execute generator, and use its output as new pageSet
 	 */
 	protected function executeGeneratorModule($generatorName, $modules) {
 
@@ -357,7 +361,7 @@ class ApiQuery extends ApiBase {
 			ApiBase :: dieDebug(__METHOD__, "Unknown generator=$generatorName");
 		}
 
-		// Generator results 
+		// Generator results
 		$resultPageSet = new ApiPageSet($this, $this->redirects);
 
 		// Create and execute the generator
@@ -386,7 +390,7 @@ class ApiQuery extends ApiBase {
 
 	/**
 	 * Returns the list of allowed parameters for this module.
-	 * Qurey module also lists all ApiPageSet parameters as its own. 
+	 * Qurey module also lists all ApiPageSet parameters as its own.
 	 */
 	public function getAllowedParams() {
 		return array (
@@ -423,12 +427,14 @@ class ApiQuery extends ApiBase {
 		$this->mAllowedGenerators = array();	// Will be repopulated
 
 		$astriks = str_repeat('--- ', 8);
+		$astriks2 = str_repeat('*** ', 10);
 		$msg .= "\n$astriks Query: Prop  $astriks\n\n";
 		$msg .= $this->makeHelpMsgHelper($this->mQueryPropModules, 'prop');
 		$msg .= "\n$astriks Query: List  $astriks\n\n";
 		$msg .= $this->makeHelpMsgHelper($this->mQueryListModules, 'list');
 		$msg .= "\n$astriks Query: Meta  $astriks\n\n";
 		$msg .= $this->makeHelpMsgHelper($this->mQueryMetaModules, 'meta');
+		$msg .= "\n\n$astriks2 Modules: continuation  $astriks2\n\n";
 
 		// Perform the base call last because the $this->mAllowedGenerators
 		// will be updated inside makeHelpMsgHelper()
@@ -469,7 +475,7 @@ class ApiQuery extends ApiBase {
 		$psModule = new ApiPageSet($this);
 		return $psModule->makeHelpMsgParameters() . parent :: makeHelpMsgParameters();
 	}
-	
+
 	// @todo should work correctly
 	public function shouldCheckMaxlag() {
 		return true;
@@ -503,9 +509,8 @@ class ApiQuery extends ApiBase {
 	public function getVersion() {
 		$psModule = new ApiPageSet($this);
 		$vers = array ();
-		$vers[] = __CLASS__ . ': $Id: ApiQuery.php 30222 2008-01-28 19:05:26Z catrope $';
+		$vers[] = __CLASS__ . ': $Id: ApiQuery.php 35098 2008-05-20 17:13:28Z ialex $';
 		$vers[] = $psModule->getVersion();
 		return $vers;
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryRandom.php 1:1.13.0-1/includes/api/ApiQueryRandom.php
--- 1:1.12.0-2/includes/api/ApiQueryRandom.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryRandom.php	2008-05-20 18:13:28.000000000 +0100
@@ -30,24 +30,24 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * Query module to get list of random pages
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
- 
+
  class ApiQueryRandom extends ApiQueryGeneratorBase {
 
 	public function __construct($query, $moduleName) {
 		parent :: __construct($query, $moduleName, 'rn');
 	}
-	
+
 	public function execute() {
 		$this->run();
 	}
-	
+
 	public function executeGenerator($resultPageSet) {
 		$this->run($resultPageSet);
 	}
-	
+
 	protected function prepareQuery($randstr, $limit, $namespace, &$resultPageSet) {
 		$this->resetQueryParams();
 		$this->addTables('page');
@@ -104,7 +104,7 @@ if (!defined('MEDIAWIKI')) {
 		if(is_null($resultPageSet)) {
 			$result->setIndexedTagName($data, 'page');
 			$result->addValue('query', $this->getModuleName(), $data);
-		}		
+		}
 	}
 
 	private function extractRowInfo($row) {
@@ -115,7 +115,7 @@ if (!defined('MEDIAWIKI')) {
 		$vals['id'] = $row->page_id;
 		return $vals;
 	}
-		
+
 	public function getAllowedParams() {
 		return array (
 			'namespace' => array(
diff -pruN 1:1.12.0-2/includes/api/ApiQueryRecentChanges.php 1:1.13.0-1/includes/api/ApiQueryRecentChanges.php
--- 1:1.12.0-2/includes/api/ApiQueryRecentChanges.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryRecentChanges.php	2008-07-22 14:26:15.000000000 +0100
@@ -31,8 +31,8 @@ if (!defined('MEDIAWIKI')) {
 /**
  * A query action to enumerate the recent changes that were done to the wiki.
  * Various filters are supported.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryRecentChanges extends ApiQueryBase {
 
@@ -43,26 +43,48 @@ class ApiQueryRecentChanges extends ApiQ
 	private $fld_comment = false, $fld_user = false, $fld_flags = false,
 			$fld_timestamp = false, $fld_title = false, $fld_ids = false,
 			$fld_sizes = false;
-	 
+
 	/**
 	 * Generates and outputs the result of this query based upon the provided parameters.
 	 */
 	public function execute() {
 		/* Initialize vars */
-		$limit = $prop = $namespace = $show = $type = $dir = $start = $end = null;
-		
+		$limit = $prop = $namespace = $titles = $show = $type = $dir = $start = $end = null;
+
 		/* Get the parameters of the request. */
 		extract($this->extractRequestParams());
 
 		/* Build our basic query. Namely, something along the lines of:
-		 * SELECT * from recentchanges WHERE rc_timestamp > $start 
-		 * 		AND rc_timestamp < $end AND rc_namespace = $namespace 
+		 * SELECT * FROM recentchanges WHERE rc_timestamp > $start
+		 * 		AND rc_timestamp < $end AND rc_namespace = $namespace
 		 * 		AND rc_deleted = '0'
 		 */
+		$db = $this->getDB();
 		$this->addTables('recentchanges');
+		$this->addOption('USE INDEX', array('recentchanges' => 'rc_timestamp'));
 		$this->addWhereRange('rc_timestamp', $dir, $start, $end);
 		$this->addWhereFld('rc_namespace', $namespace);
 		$this->addWhereFld('rc_deleted', 0);
+		if(!empty($titles))
+		{
+			$lb = new LinkBatch;
+			foreach($titles as $t)
+			{
+				$obj = Title::newFromText($t);
+				$lb->addObj($obj);
+				if($obj->getNamespace() < 0)
+				{
+					// LinkBatch refuses these, but we need them anyway
+					if(!array_key_exists($obj->getNamespace(), $lb->data))
+						$lb->data[$obj->getNamespace()] = array();
+					$lb->data[$obj->getNamespace()][$obj->getDbKey()] = 1;
+				}
+			}
+			$where = $lb->constructSet('rc', $this->getDb());
+			if($where != '')
+				$this->addWhere($where);
+		}
+
 		if(!is_null($type))
 				$this->addWhereFld('rc_type', $this->parseRCType($type));
 
@@ -70,12 +92,19 @@ class ApiQueryRecentChanges extends ApiQ
 			$show = array_flip($show);
 
 			/* Check for conflicting parameters. */
-			if ((isset ($show['minor']) && isset ($show['!minor'])) 
-					|| (isset ($show['bot']) && isset ($show['!bot'])) 
-					|| (isset ($show['anon']) && isset ($show['!anon']))) {
-						
+			if ((isset ($show['minor']) && isset ($show['!minor']))
+					|| (isset ($show['bot']) && isset ($show['!bot']))
+					|| (isset ($show['anon']) && isset ($show['!anon']))
+					|| (isset ($show['redirect']) && isset ($show['!redirect']))
+					|| (isset ($show['patrolled']) && isset ($show['!patrolled']))) {
+
 				$this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show');
 			}
+			
+			// Check permissions
+			global $wgUser;
+			if((isset($show['patrolled']) || isset($show['!patrolled'])) && !$wgUser->isAllowed('patrol'))
+				$this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied');
 
 			/* Add additional conditions to query depending upon parameters. */
 			$this->addWhereIf('rc_minor = 0', isset ($show['!minor']));
@@ -84,6 +113,11 @@ class ApiQueryRecentChanges extends ApiQ
 			$this->addWhereIf('rc_bot != 0', isset ($show['bot']));
 			$this->addWhereIf('rc_user = 0', isset ($show['anon']));
 			$this->addWhereIf('rc_user != 0', isset ($show['!anon']));
+			$this->addWhereIf('rc_patrolled = 0', isset($show['!patrolled']));
+			$this->addWhereIf('rc_patrolled != 0', isset($show['patrolled']));
+			$this->addWhereIf('page_is_redirect = 1', isset ($show['redirect']));
+			// Don't throw log entries out the window here
+			$this->addWhereIf('page_is_redirect = 0 OR page_is_redirect IS NULL', isset ($show['!redirect']));
 		}
 
 		/* Add the fields we're concerned with to out query. */
@@ -108,13 +142,19 @@ class ApiQueryRecentChanges extends ApiQ
 			$this->fld_title = isset ($prop['title']);
 			$this->fld_ids = isset ($prop['ids']);
 			$this->fld_sizes = isset ($prop['sizes']);
+			$this->fld_redirect = isset($prop['redirect']);
+			$this->fld_patrolled = isset($prop['patrolled']);
+
+			global $wgUser;
+			if($this->fld_patrolled && !$wgUser->isAllowed('patrol'))
+				$this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied');
 
 			/* Add fields to our query if they are specified as a needed parameter. */
-			$this->addFieldsIf('rc_id', $this->fld_ids);			
-			$this->addFieldsIf('rc_cur_id', $this->fld_ids);			
-			$this->addFieldsIf('rc_this_oldid', $this->fld_ids);			
-			$this->addFieldsIf('rc_last_oldid', $this->fld_ids);			
-			$this->addFieldsIf('rc_comment', $this->fld_comment);			
+			$this->addFieldsIf('rc_id', $this->fld_ids);
+			$this->addFieldsIf('rc_cur_id', $this->fld_ids);
+			$this->addFieldsIf('rc_this_oldid', $this->fld_ids);
+			$this->addFieldsIf('rc_last_oldid', $this->fld_ids);
+			$this->addFieldsIf('rc_comment', $this->fld_comment);
 			$this->addFieldsIf('rc_user', $this->fld_user);
 			$this->addFieldsIf('rc_user_text', $this->fld_user);
 			$this->addFieldsIf('rc_minor', $this->fld_flags);
@@ -122,15 +162,18 @@ class ApiQueryRecentChanges extends ApiQ
 			$this->addFieldsIf('rc_new', $this->fld_flags);
 			$this->addFieldsIf('rc_old_len', $this->fld_sizes);
 			$this->addFieldsIf('rc_new_len', $this->fld_sizes);
+			$this->addFieldsIf('rc_patrolled', $this->fld_patrolled);
+			if($this->fld_redirect || isset($show['redirect']) || isset($show['!redirect']))
+			{
+				$this->addTables('page');
+				$this->addJoinConds(array('page' => array('LEFT JOIN', array('rc_namespace=page_namespace', 'rc_title=page_title'))));
+				$this->addFields('page_is_redirect');
+			}
 		}
-
-		/* Specify the limit for our query. It's $limit+1 because we (possibly) need to 
+		/* Specify the limit for our query. It's $limit+1 because we (possibly) need to
 		 * generate a "continue" parameter, to allow paging. */
 		$this->addOption('LIMIT', $limit +1);
 
-		/* Specify the index to use in the query as rc_timestamp, instead of rc_revid (default). */
-		$this->addOption('USE INDEX', 'rc_timestamp');
-
 		$data = array ();
 		$count = 0;
 
@@ -148,7 +191,7 @@ class ApiQueryRecentChanges extends ApiQ
 
 			/* Extract the data from a single row. */
 			$vals = $this->extractRowInfo($row);
-			
+
 			/* Add that row's data to our final output. */
 			if($vals)
 				$data[] = $vals;
@@ -240,9 +283,17 @@ class ApiQueryRecentChanges extends ApiQ
 			$vals['comment'] = $row->rc_comment;
 		}
 
+		if ($this->fld_redirect)
+			if($row->page_is_redirect)
+				$vals['redirect'] = '';
+
+		/* Add the patrolled flag */
+		if ($this->fld_patrolled && $row->rc_patrolled == 1)
+			$vals['patrolled'] = '';
+
 		return $vals;
 	}
-	
+
 	private function parseRCType($type)
 	{
 			if(is_array($type))
@@ -258,7 +309,7 @@ class ApiQueryRecentChanges extends ApiQ
 					case 'new': return RC_NEW;
 					case 'log': return RC_LOG;
 			}
-	}			
+	}
 
 	public function getAllowedParams() {
 		return array (
@@ -279,6 +330,9 @@ class ApiQueryRecentChanges extends ApiQ
 				ApiBase :: PARAM_ISMULTI => true,
 				ApiBase :: PARAM_TYPE => 'namespace'
 			),
+			'titles' => array(
+				ApiBase :: PARAM_ISMULTI => true
+			),
 			'prop' => array (
 				ApiBase :: PARAM_ISMULTI => true,
 				ApiBase :: PARAM_DFLT => 'title|timestamp|ids',
@@ -289,7 +343,9 @@ class ApiQueryRecentChanges extends ApiQ
 					'timestamp',
 					'title',
 					'ids',
-					'sizes'
+					'sizes',
+					'redirect',
+					'patrolled'
 				)
 			),
 			'show' => array (
@@ -300,7 +356,11 @@ class ApiQueryRecentChanges extends ApiQ
 					'bot',
 					'!bot',
 					'anon',
-					'!anon'
+					'!anon',
+					'redirect',
+					'!redirect',
+					'patrolled',
+					'!patrolled'
 				)
 			),
 			'limit' => array (
@@ -314,7 +374,7 @@ class ApiQueryRecentChanges extends ApiQ
 				ApiBase :: PARAM_ISMULTI => true,
 				ApiBase :: PARAM_TYPE => array (
 					'edit',
-					'new', 
+					'new',
 					'log'
 				)
 			)
@@ -327,13 +387,14 @@ class ApiQueryRecentChanges extends ApiQ
 			'end' => 'The timestamp to end enumerating.',
 			'dir' => 'In which direction to enumerate.',
 			'namespace' => 'Filter log entries to only this namespace(s)',
+			'titles' => 'Filter log entries to only these page titles',
 			'prop' => 'Include additional pieces of information',
 			'show' => array (
 				'Show only items that meet this criteria.',
 				'For example, to see only minor edits done by logged-in users, set show=minor|!anon'
 			),
 			'type' => 'Which types of changes to show.',
-			'limit' => 'How many total pages to return.'
+			'limit' => 'How many total changes to return.'
 		);
 	}
 
@@ -348,7 +409,6 @@ class ApiQueryRecentChanges extends ApiQ
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 37909 2008-07-22 13:26:15Z catrope $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryRevisions.php 1:1.13.0-1/includes/api/ApiQueryRevisions.php
--- 1:1.12.0-2/includes/api/ApiQueryRevisions.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryRevisions.php	2008-07-08 09:42:27.000000000 +0100
@@ -30,10 +30,10 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * A query action to enumerate revisions of a given page, or show top revisions of multiple pages.
- * Various pieces of information may be shown - flags, comments, and the actual wiki markup of the rev. 
- * In the enumeration mode, ranges of revisions may be requested and filtered. 
- * 
- * @addtogroup API
+ * Various pieces of information may be shown - flags, comments, and the actual wiki markup of the rev.
+ * In the enumeration mode, ranges of revisions may be requested and filtered.
+ *
+ * @ingroup API
  */
 class ApiQueryRevisions extends ApiQueryBase {
 
@@ -44,16 +44,45 @@ class ApiQueryRevisions extends ApiQuery
 	private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false,
 			$fld_comment = false, $fld_user = false, $fld_content = false;
 
+	protected function getTokenFunctions() {
+		// tokenname => function
+		// function prototype is func($pageid, $title, $rev)
+		// should return token or false
+
+		// Don't call the hooks twice
+		if(isset($this->tokenFunctions))
+			return $this->tokenFunctions;
+
+		// If we're in JSON callback mode, no tokens can be obtained
+		if(!is_null($this->getMain()->getRequest()->getVal('callback')))
+			return array();
+
+		$this->tokenFunctions = array(
+			'rollback' => array( 'ApiQueryRevisions','getRollbackToken' )
+		);
+		wfRunHooks('APIQueryRevisionsTokens', array(&$this->tokenFunctions));
+		return $this->tokenFunctions;
+	}
+
+	public static function getRollbackToken($pageid, $title, $rev)
+	{
+		global $wgUser;
+		if(!$wgUser->isAllowed('rollback'))
+			return false;
+		return $wgUser->editToken(array($title->getPrefixedText(),
+						$rev->getUserText()));
+	}
+
 	public function execute() {
-		$limit = $startid = $endid = $start = $end = $dir = $prop = $user = $excludeuser = $token = null;
+		$limit = $startid = $endid = $start = $end = $dir = $prop = $user = $excludeuser = $expandtemplates = $section = $token = null;
 		extract($this->extractRequestParams(false));
 
 		// If any of those parameters are used, work in 'enumeration' mode.
 		// Enum mode can only be used when exactly one page is provided.
-		// Enumerating revisions on multiple pages make it extremely 
-		// difficult to manage continuations and require additional SQL indexes  
+		// Enumerating revisions on multiple pages make it extremely
+		// difficult to manage continuations and require additional SQL indexes
 		$enumRevMode = (!is_null($user) || !is_null($excludeuser) || !is_null($limit) || !is_null($startid) || !is_null($endid) || $dir === 'newer' || !is_null($start) || !is_null($end));
-		
+
 
 		$pageSet = $this->getPageSet();
 		$pageCount = $pageSet->getGoodTitleCount();
@@ -70,35 +99,26 @@ class ApiQueryRevisions extends ApiQuery
 			$this->dieUsage('titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.', 'multpages');
 
 		$this->addTables('revision');
-		$this->addWhere('rev_deleted=0');
+		$this->addFields( Revision::selectFields() );
 
 		$prop = array_flip($prop);
 
-		// These field are needed regardless of the client requesting them
-		$this->addFields('rev_id');
-		$this->addFields('rev_page');
-
 		// Optional fields
 		$this->fld_ids = isset ($prop['ids']);
 		// $this->addFieldsIf('rev_text_id', $this->fld_ids); // should this be exposed?
-		$this->fld_flags = $this->addFieldsIf('rev_minor_edit', isset ($prop['flags']));
-		$this->fld_timestamp = $this->addFieldsIf('rev_timestamp', isset ($prop['timestamp']));
-		$this->fld_comment = $this->addFieldsIf('rev_comment', isset ($prop['comment']));
-		$this->fld_size = $this->addFieldsIf('rev_len', isset ($prop['size']));
-		$this->tok_rollback = false; // Prevent PHP undefined property notice
-		if(!is_null($token))
-		{
-			$this->tok_rollback = $this->getTokenFlag($token, 'rollback');
-		}
+		$this->fld_flags = isset ($prop['flags']);
+		$this->fld_timestamp = isset ($prop['timestamp']);
+		$this->fld_comment = isset ($prop['comment']);
+		$this->fld_size = isset ($prop['size']);
+		$this->fld_user = isset ($prop['user']);
+		$this->token = $token;
 
-		if (isset ($prop['user'])) {
-			$this->addFields('rev_user');
-			$this->addFields('rev_user_text');
-			$this->fld_user = true;
+		if ( !is_null($this->token) || ( $this->fld_content && $this->expandTemplates ) || $pageCount > 0) {
+			$this->addTables( 'page' );
+			$this->addWhere('page_id=rev_page');
+			$this->addFields( Revision::selectPageFields() );
 		}
-		else if($this->tok_rollback)
-			$this->addFields('rev_user_text');
-		
+
 		if (isset ($prop['content'])) {
 
 			// For each page we will request, the user must have read rights for that page
@@ -112,12 +132,15 @@ class ApiQueryRevisions extends ApiQuery
 			$this->addTables('text');
 			$this->addWhere('rev_text_id=old_id');
 			$this->addFields('old_id');
-			$this->addFields('old_text');
-			$this->addFields('old_flags');
+			$this->addFields( Revision::selectTextFields() );
 
 			$this->fld_content = true;
-			
+
 			$this->expandTemplates = $expandtemplates;
+			if(isset($section))
+				$this->section = $section;
+			else
+				$this->section = false;
 		}
 
 		$userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
@@ -137,13 +160,13 @@ class ApiQueryRevisions extends ApiQuery
 				$this->dieUsage('end and endid cannot be used together', 'badparams');
 
 			if(!is_null($user) && !is_null( $excludeuser))
-				$this->dieUsage('user and excludeuser cannot be used together', 'badparams');			
-			
+				$this->dieUsage('user and excludeuser cannot be used together', 'badparams');
+
 			// This code makes an assumption that sorting by rev_id and rev_timestamp produces
 			// the same result. This way users may request revisions starting at a given time,
 			// but to page through results use the rev_id returned after each page.
-			// Switching to rev_id removes the potential problem of having more than 
-			// one row with the same timestamp for the same page. 
+			// Switching to rev_id removes the potential problem of having more than
+			// one row with the same timestamp for the same page.
 			// The order needs to be the same as start parameter to avoid SQL filesort.
 
 			if (is_null($startid) && is_null($endid))
@@ -158,7 +181,7 @@ class ApiQueryRevisions extends ApiQuery
 
 			// There is only one ID, use it
 			$this->addWhereFld('rev_page', current(array_keys($pageSet->getGoodTitles())));
-			
+
 			if(!is_null($user)) {
 				$this->addWhereFld('rev_user_text', $user);
 			} elseif (!is_null( $excludeuser)) {
@@ -177,7 +200,6 @@ class ApiQueryRevisions extends ApiQuery
 		elseif ($pageCount > 0) {
 			// When working in multi-page non-enumeration mode,
 			// limit to the latest revision only
-			$this->addTables('page');
 			$this->addWhere('page_id=rev_page');
 			$this->addWhere('page_latest=rev_id');
 			$this->validateLimit('page_count', $pageCount, 1, $userMax, $botMax);
@@ -207,17 +229,18 @@ class ApiQueryRevisions extends ApiQuery
 				break;
 			}
 
+			$revision = new Revision( $row );
 			$this->getResult()->addValue(
 				array (
 					'query',
 					'pages',
-					intval($row->rev_page),
+					$revision->getPage(),
 					'revisions'),
 				null,
-				$this->extractRowInfo($row));
+				$this->extractRowInfo( $revision ));
 		}
 		$db->freeResult($res);
-		
+
 		// Ensure that all revisions are shown as '<rev>' elements
 		$result = $this->getResult();
 		if ($result->getIsRawMode()) {
@@ -230,53 +253,70 @@ class ApiQueryRevisions extends ApiQuery
 		}
 	}
 
-	private function extractRowInfo($row) {
+	private function extractRowInfo( $revision ) {
 
 		$vals = array ();
 
 		if ($this->fld_ids) {
-			$vals['revid'] = intval($row->rev_id);
+			$vals['revid'] = $revision->getId();
 			// $vals['oldid'] = intval($row->rev_text_id);	// todo: should this be exposed?
 		}
-		
-		if ($this->fld_flags && $row->rev_minor_edit)
+
+		if ($this->fld_flags && $revision->isMinor())
 			$vals['minor'] = '';
 
 		if ($this->fld_user) {
-			$vals['user'] = $row->rev_user_text;
-			if (!$row->rev_user)
+			$vals['user'] = $revision->getUserText();
+			if (!$revision->getUser())
 				$vals['anon'] = '';
 		}
 
 		if ($this->fld_timestamp) {
-			$vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rev_timestamp);
+			$vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp());
+		}
+
+		if ($this->fld_size && !is_null($revision->getSize())) {
+			$vals['size'] = $revision->getSize();
+		}
+
+		if ($this->fld_comment) {
+			$comment = $revision->getComment();
+			if (!empty($comment))		
+				$vals['comment'] = $comment;
 		}
-		
-		if ($this->fld_size && !is_null($row->rev_len)) {
-			$vals['size'] = intval($row->rev_len);
-		}
-
-		if ($this->fld_comment && !empty ($row->rev_comment)) {
-			$vals['comment'] = $row->rev_comment;
-		}
-		
-		if($this->tok_rollback || ($this->fld_content && $this->expandTemplates))
-			$title = Title::newFromID($row->rev_page);
-		
-		if($this->tok_rollback) {
-			global $wgUser;
-			$vals['rollbacktoken'] = $wgUser->editToken(array($title->getPrefixedText(), $row->rev_user_text));
+
+		if(!is_null($this->token) || ($this->fld_content && $this->expandTemplates))
+			$title = $revision->getTitle();
+
+		if(!is_null($this->token))
+		{
+			$tokenFunctions = $this->getTokenFunctions();
+			foreach($this->token as $t)
+			{
+				$val = call_user_func($tokenFunctions[$t], $title->getArticleID(), $title, $revision);
+				if($val === false)
+					$this->setWarning("Action '$t' is not allowed for the current user");
+				else
+					$vals[$t . 'token'] = $val;
+			}
 		}
-		
-		
+
 		if ($this->fld_content) {
-			$text = Revision :: getRevisionText($row);			
+			global $wgParser;
+			$text = $revision->getText();
+			# Expand templates after getting section content because
+			# template-added sections don't count and Parser::preprocess()
+			# will have less input
+			if ($this->section !== false) {
+				$text = $wgParser->getSection( $text, $this->section, false);
+				if($text === false)
+					$this->dieUsage("There is no section {$this->section} in r".$revision->getId(), 'nosuchsection');
+			}
 			if ($this->expandTemplates) {
-				global $wgParser;
 				$text = $wgParser->preprocess( $text, $title, new ParserOptions() );
 			}
 			ApiResult :: setContent($vals, $text);
-		}		
+		}
 		return $vals;
 	}
 
@@ -326,12 +366,13 @@ class ApiQueryRevisions extends ApiQuery
 			'excludeuser' => array(
 				ApiBase :: PARAM_TYPE => 'user'
 			),
-			
+
 			'expandtemplates' => false,
+			'section' => array(
+				ApiBase :: PARAM_TYPE => 'integer'
+			),
 			'token' => array(
-				ApiBase :: PARAM_TYPE => array(
-					'rollback'
-				),
+				ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()),
 				ApiBase :: PARAM_ISMULTI => true
 			),
 		);
@@ -349,6 +390,7 @@ class ApiQueryRevisions extends ApiQuery
 			'user' => 'only include revisions made by user',
 			'excludeuser' => 'exclude revisions made by user',
 			'expandtemplates' => 'expand templates in revision content',
+			'section' => 'only retrieve the content of this section',
 			'token' => 'Which tokens to obtain for each revision',
 		);
 	}
@@ -382,7 +424,6 @@ class ApiQueryRevisions extends ApiQuery
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryRevisions.php 31259 2008-02-25 14:14:55Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryRevisions.php 37300 2008-07-08 08:42:27Z btongminh $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQuerySearch.php 1:1.13.0-1/includes/api/ApiQuerySearch.php
--- 1:1.12.0-2/includes/api/ApiQuerySearch.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQuerySearch.php	2008-05-20 18:13:28.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * Query module to perform full text search within wiki titles and content
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQuerySearch extends ApiQueryGeneratorBase {
 
@@ -52,7 +52,7 @@ class ApiQuerySearch extends ApiQueryGen
 		$params = $this->extractRequestParams();
 
 		$limit = $params['limit'];
-		$query = $params['search'];		
+		$query = $params['search'];
 		if (is_null($query) || empty($query))
 			$this->dieUsage("empty search string is not allowed", 'param-search');
 
@@ -60,11 +60,14 @@ class ApiQuerySearch extends ApiQueryGen
 		$search->setLimitOffset( $limit+1, $params['offset'] );
 		$search->setNamespaces( $params['namespace'] );
 		$search->showRedirects = $params['redirects'];
-		
+
 		if ($params['what'] == 'text')
 			$matches = $search->searchText( $query );
 		else
 			$matches = $search->searchTitle( $query );
+		if (is_null($matches))
+			$this->dieUsage("{$params['what']} search is disabled",
+					"search-{$params['what']}-disabled");
 
 		$data = array ();
 		$count = 0;
@@ -75,6 +78,9 @@ class ApiQuerySearch extends ApiQueryGen
 				break;
 			}
 
+			// Silently skip broken titles
+			if ($result->isBrokenTitle()) continue;
+			
 			$title = $result->getTitle();
 			if (is_null($resultPageSet)) {
 				$data[] = array(
@@ -100,7 +106,7 @@ class ApiQuerySearch extends ApiQueryGen
 			'namespace' => array (
 				ApiBase :: PARAM_DFLT => 0,
 				ApiBase :: PARAM_TYPE => 'namespace',
-				ApiBase :: PARAM_ISMULTI => true, 
+				ApiBase :: PARAM_ISMULTI => true,
 			),
 			'what' => array (
 				ApiBase :: PARAM_DFLT => 'title',
@@ -145,7 +151,6 @@ class ApiQuerySearch extends ApiQueryGen
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQuerySearch.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQuerySearch.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQuerySiteinfo.php 1:1.13.0-1/includes/api/ApiQuerySiteinfo.php
--- 1:1.12.0-2/includes/api/ApiQuerySiteinfo.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQuerySiteinfo.php	2008-07-04 10:21:11.000000000 +0100
@@ -23,218 +23,278 @@
  * http://www.gnu.org/copyleft/gpl.html
  */
 
-if (!defined('MEDIAWIKI')) {
+if( !defined('MEDIAWIKI') ) {
 	// Eclipse helper - will be ignored in production
-	require_once ('ApiQueryBase.php');
+	require_once( 'ApiQueryBase.php' );
 }
 
 /**
  * A query action to return meta information about the wiki site.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQuerySiteinfo extends ApiQueryBase {
 
-	public function __construct($query, $moduleName) {
-		parent :: __construct($query, $moduleName, 'si');
+	public function __construct( $query, $moduleName ) {
+		parent :: __construct( $query, $moduleName, 'si' );
 	}
 
 	public function execute() {
-
 		$params = $this->extractRequestParams();
-
-		foreach ($params['prop'] as $p) {
-			switch ($p) {
-				default :
-					ApiBase :: dieDebug(__METHOD__, "Unknown prop=$p");
-				case 'general' :
-					$this->appendGeneralInfo($p);
+		foreach( $params['prop'] as $p )
+		{
+			switch ( $p )
+			{
+				case 'general':
+					$this->appendGeneralInfo( $p );
 					break;
-				case 'namespaces' :
-					$this->appendNamespaces($p);
+				case 'namespaces':
+					$this->appendNamespaces( $p );
 					break;
-				case 'namespacealiases' :
-					$this->appendNamespaceAliases($p);
+				case 'namespacealiases':
+					$this->appendNamespaceAliases( $p );
 					break;
-				case 'interwikimap' :
-					$filteriw = isset($params['filteriw']) ? $params['filteriw'] : false; 
-					$this->appendInterwikiMap($p, $filteriw);
+				case 'specialpagealiases':
+					$this->appendSpecialPageAliases( $p );
 					break;
-				case 'dbrepllag' :
-					$this->appendDbReplLagInfo($p, $params['showalldb']);
+				case 'interwikimap':
+					$filteriw = isset( $params['filteriw'] ) ? $params['filteriw'] : false;
+					$this->appendInterwikiMap( $p, $filteriw );
 					break;
-				case 'statistics' :
-					$this->appendStatistics($p);
+				case 'dbrepllag':
+					$this->appendDbReplLagInfo( $p, $params['showalldb'] );
 					break;
+				case 'statistics':
+					$this->appendStatistics( $p );
+					break;
+				case 'usergroups':
+					$this->appendUserGroups( $p );
+					break;
+				default :
+					ApiBase :: dieDebug( __METHOD__, "Unknown prop=$p" );
 			}
 		}
 	}
 
-	protected function appendGeneralInfo($property) {
-		global $wgSitename, $wgVersion, $wgCapitalLinks, $wgRightsCode, $wgRightsText, $wgLanguageCode, $IP;
-		
-		$data = array ();
+	protected function appendGeneralInfo( $property ) {
+		global $wgSitename, $wgVersion, $wgCapitalLinks, $wgRightsCode, $wgRightsText, $wgContLang;
+		global $wgLanguageCode, $IP, $wgEnableWriteAPI, $wgLang, $wgLocaltimezone, $wgLocalTZoffset;
+
+		$data = array();
 		$mainPage = Title :: newFromText(wfMsgForContent('mainpage'));
-		$data['mainpage'] = $mainPage->getText();
+		$data['mainpage'] = $mainPage->getPrefixedText();
 		$data['base'] = $mainPage->getFullUrl();
 		$data['sitename'] = $wgSitename;
 		$data['generator'] = "MediaWiki $wgVersion";
 
-		$svn = SpecialVersion::getSvnRevision ( $IP );
-		if ( $svn ) $data['rev'] = $svn;
+		$svn = SpecialVersion::getSvnRevision( $IP );
+		if( $svn )
+			$data['rev'] = $svn;
 
 		$data['case'] = $wgCapitalLinks ? 'first-letter' : 'case-sensitive'; // 'case-insensitive' option is reserved for future
-		if (isset($wgRightsCode))
+
+		if( isset( $wgRightsCode ) )
 			$data['rightscode'] = $wgRightsCode;
 		$data['rights'] = $wgRightsText;
 		$data['lang'] = $wgLanguageCode;
+		if( $wgContLang->isRTL() ) 
+			$data['rtl'] = '';
+		$data['fallback8bitEncoding'] = $wgLang->fallback8bitEncoding();
 		
-		$this->getResult()->addValue('query', $property, $data);
+		if( wfReadOnly() )
+			$data['readonly'] = '';
+		if( $wgEnableWriteAPI )
+			$data['writeapi'] = '';
+
+		$tz = $wgLocaltimezone;
+		$offset = $wgLocalTZoffset;
+		if( is_null( $tz ) ) {
+			$tz = 'UTC';
+			$offset = 0;
+		} elseif( is_null( $offset ) ) {
+			$offset = 0;
+		}
+		$data['timezone'] = $tz;
+		$data['timeoffset'] = $offset;
+
+		$this->getResult()->addValue( 'query', $property, $data );
 	}
-	
-	protected function appendNamespaces($property) {
+
+	protected function appendNamespaces( $property ) {
 		global $wgContLang;
-		
-		$data = array ();
-		foreach ($wgContLang->getFormattedNamespaces() as $ns => $title) {
-			$data[$ns] = array (
+		$data = array();
+		foreach( $wgContLang->getFormattedNamespaces() as $ns => $title )
+		{
+			$data[$ns] = array(
 				'id' => $ns
 			);
-			ApiResult :: setContent($data[$ns], $title);
+			ApiResult :: setContent( $data[$ns], $title );
+			if( MWNamespace::hasSubpages($ns) )
+				$data[$ns]['subpages'] = '';
 		}
-		
-		$this->getResult()->setIndexedTagName($data, 'ns');
-		$this->getResult()->addValue('query', $property, $data);
+
+		$this->getResult()->setIndexedTagName( $data, 'ns' );
+		$this->getResult()->addValue( 'query', $property, $data );
 	}
-	
-	protected function appendNamespaceAliases($property) {
+
+	protected function appendNamespaceAliases( $property ) {
 		global $wgNamespaceAliases;
-		
-		$data = array ();
-		foreach ($wgNamespaceAliases as $title => $ns) {
-			$item = array (
+		$data = array();
+		foreach( $wgNamespaceAliases as $title => $ns ) {
+			$item = array(
 				'id' => $ns
 			);
-			ApiResult :: setContent($item, strtr($title, '_', ' '));
+			ApiResult :: setContent( $item, strtr( $title, '_', ' ' ) );
 			$data[] = $item;
 		}
-		
-		$this->getResult()->setIndexedTagName($data, 'ns');
-		$this->getResult()->addValue('query', $property, $data);
+
+		$this->getResult()->setIndexedTagName( $data, 'ns' );
+		$this->getResult()->addValue( 'query', $property, $data );
 	}
-	
-	protected function appendInterwikiMap($property, $filter) {
 
+	protected function appendSpecialPageAliases( $property ) {
+		global $wgLang;
+		$data = array();
+		foreach( $wgLang->getSpecialPageAliases() as $specialpage => $aliases )
+		{
+			$arr = array( 'realname' => $specialpage, 'aliases' => $aliases );
+			$this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
+			$data[] = $arr;
+		}
+		$this->getResult()->setIndexedTagName( $data, 'specialpage' );
+		$this->getResult()->addValue( 'query', $property, $data );
+	}
+
+	protected function appendInterwikiMap( $property, $filter ) {
 		$this->resetQueryParams();
-		$this->addTables('interwiki');
-		$this->addFields(array('iw_prefix', 'iw_local', 'iw_url'));
+		$this->addTables( 'interwiki' );
+		$this->addFields( array( 'iw_prefix', 'iw_local', 'iw_url' ) );
 
-		if($filter === 'local') {
-			$this->addWhere('iw_local = 1');
-		} elseif($filter === '!local') {
-			$this->addWhere('iw_local = 0');
-		} elseif($filter !== false) {
-			ApiBase :: dieDebug(__METHOD__, "Unknown filter=$filter");
-		}
+		if( $filter === 'local' )
+			$this->addWhere( 'iw_local = 1' );
+		elseif( $filter === '!local' )
+			$this->addWhere( 'iw_local = 0' );
+		elseif( $filter !== false )
+			ApiBase :: dieDebug( __METHOD__, "Unknown filter=$filter" );
+
+		$this->addOption( 'ORDER BY', 'iw_prefix' );
 
-		$this->addOption('ORDER BY', 'iw_prefix');
-		
 		$db = $this->getDB();
-		$res = $this->select(__METHOD__);
+		$res = $this->select( __METHOD__ );
 
 		$data = array();
-		while($row = $db->fetchObject($res))
+		$langNames = Language::getLanguageNames();
+		while( $row = $db->fetchObject($res) )
 		{
+			$val = array();
 			$val['prefix'] = $row->iw_prefix;
-			if ($row->iw_local == '1')
+			if( $row->iw_local == '1' )
 				$val['local'] = '';
 //			$val['trans'] = intval($row->iw_trans);	// should this be exposed?
+			if( isset( $langNames[$row->iw_prefix] ) )
+				$val['language'] = $langNames[$row->iw_prefix];
 			$val['url'] = $row->iw_url;
-				
+
 			$data[] = $val;
 		}
-		$db->freeResult($res);
-		
-		$this->getResult()->setIndexedTagName($data, 'iw');
-		$this->getResult()->addValue('query', $property, $data);
+		$db->freeResult( $res );
+
+		$this->getResult()->setIndexedTagName( $data, 'iw' );
+		$this->getResult()->addValue( 'query', $property, $data );
 	}
-	
-	protected function appendDbReplLagInfo($property, $includeAll) {
-		global $wgLoadBalancer, $wgShowHostnames;
 
+	protected function appendDbReplLagInfo( $property, $includeAll ) {
+		global $wgShowHostnames;
 		$data = array();
-		
-		if ($includeAll) {
-			if (!$wgShowHostnames)
+		if( $includeAll ) {
+			if ( !$wgShowHostnames )
 				$this->dieUsage('Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied');
-			
-			global $wgDBservers;
-			$lags = $wgLoadBalancer->getLagTimes();
+
+			$lb = wfGetLB();
+			$lags = $lb->getLagTimes();
 			foreach( $lags as $i => $lag ) {
-				$data[] = array (
-					'host' => $wgDBservers[$i]['host'],
-					'lag' => $lag);
+				$data[] = array(
+					'host' => $lb->getServerName( $i ),
+					'lag' => $lag
+				);
 			}
 		} else {
-			list( $host, $lag ) = $wgLoadBalancer->getMaxLag();
-			$data[] = array (
+			list( $host, $lag ) = wfGetLB()->getMaxLag();
+			$data[] = array(
 				'host' => $wgShowHostnames ? $host : '',
-				'lag' => $lag);
-		}					
+				'lag' => $lag
+			);
+		}
 
 		$result = $this->getResult();
-		$result->setIndexedTagName($data, 'db');
-		$result->addValue('query', $property, $data);
-	}	
-
-	protected function appendStatistics($property) {
-		$data = array ();
-		$data['pages'] = intval(SiteStats::pages());
-		$data['articles'] = intval(SiteStats::articles());
-		$data['views'] = intval(SiteStats::views());
-		$data['edits'] = intval(SiteStats::edits());
-		$data['images'] = intval(SiteStats::images());
-		$data['users'] = intval(SiteStats::users());
-		$data['admins'] = intval(SiteStats::admins());
-		$data['jobs'] = intval(SiteStats::jobs());
-		$this->getResult()->addValue('query', $property, $data);
-	}	
-	
+		$result->setIndexedTagName( $data, 'db' );
+		$result->addValue( 'query', $property, $data );
+	}
+
+	protected function appendStatistics( $property ) {
+		$data = array();
+		$data['pages'] = intval( SiteStats::pages() );
+		$data['articles'] = intval( SiteStats::articles() );
+		$data['views'] = intval( SiteStats::views() );
+		$data['edits'] = intval( SiteStats::edits() );
+		$data['images'] = intval( SiteStats::images() );
+		$data['users'] = intval( SiteStats::users() );
+		$data['admins'] = intval( SiteStats::admins() );
+		$data['jobs'] = intval( SiteStats::jobs() );
+		$this->getResult()->addValue( 'query', $property, $data );
+	}
+
+	protected function appendUserGroups( $property ) {
+		global $wgGroupPermissions;
+		$data = array();
+		foreach( $wgGroupPermissions as $group => $permissions ) {
+			$arr = array( 'name' => $group, 'rights' => array_keys( $permissions, true ) );
+			$this->getResult()->setIndexedTagName( $arr['rights'], 'permission' );
+			$data[] = $arr;
+		}
+
+		$this->getResult()->setIndexedTagName( $data, 'group' );
+		$this->getResult()->addValue( 'query', $property, $data );
+	}
+
 	public function getAllowedParams() {
-		return array (
-		
-			'prop' => array (
+		return array(
+			'prop' => array(
 				ApiBase :: PARAM_DFLT => 'general',
 				ApiBase :: PARAM_ISMULTI => true,
-				ApiBase :: PARAM_TYPE => array (
+				ApiBase :: PARAM_TYPE => array(
 					'general',
 					'namespaces',
 					'namespacealiases',
+					'specialpagealiases',
 					'interwikimap',
 					'dbrepllag',
 					'statistics',
-				)),
-
-			'filteriw' => array (
-				ApiBase :: PARAM_TYPE => array (
+					'usergroups',
+				)
+			),
+			'filteriw' => array(
+				ApiBase :: PARAM_TYPE => array(
 					'local',
 					'!local',
-				)),
-				
+				)
+			),
 			'showalldb' => false,
 		);
 	}
 
 	public function getParamDescription() {
-		return array (
-			'prop' => array (
+		return array(
+			'prop' => array(
 				'Which sysinfo properties to get:',
 				' "general"      - Overall system information',
 				' "namespaces"   - List of registered namespaces (localized)',
 				' "namespacealiases" - List of registered namespace aliases',
+				' "specialpagealiases" - List of special page aliases',
 				' "statistics"   - Returns site statistics',
 				' "interwikimap" - Returns interwiki map (optionally filtered)',
 				' "dbrepllag"    - Returns database server with the highest replication lag',
+				' "usergroups"   - Returns user groups and the associated permissions',
 			),
 			'filteriw' =>  'Return only local or only nonlocal entries of the interwiki map',
 			'showalldb' => 'List all database servers, not just the one lagging the most',
@@ -254,6 +314,6 @@ class ApiQuerySiteinfo extends ApiQueryB
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 30484 2008-02-03 19:29:59Z btongminh $';
+		return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 37034 2008-07-04 09:21:11Z vasilievvv $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryUserContributions.php 1:1.13.0-1/includes/api/ApiQueryUserContributions.php
--- 1:1.12.0-2/includes/api/ApiQueryUserContributions.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryUserContributions.php	2008-07-09 12:44:49.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * This query action adds a list of a specified user's contributions to the output.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryContributions extends ApiQueryBase {
 
@@ -48,7 +48,7 @@ class ApiQueryContributions extends ApiQ
 		// Parse some parameters
 		$this->params = $this->extractRequestParams();
 
-		$prop = array_flip($this->params['prop']);		
+		$prop = array_flip($this->params['prop']);
 		$this->fld_ids = isset($prop['ids']);
 		$this->fld_title = isset($prop['title']);
 		$this->fld_comment = isset($prop['comment']);
@@ -59,12 +59,20 @@ class ApiQueryContributions extends ApiQ
 		$this->selectNamedDB('contributions', DB_SLAVE, 'contributions');
 		$db = $this->getDB();
 
-		// Prepare query
-		$this->usernames = array();
-		if(!is_array($this->params['user']))
-			$this->params['user'] = array($this->params['user']);
-		foreach($this->params['user'] as $u)
-			$this->prepareUsername($u);
+		if(isset($this->params['userprefix']))
+		{
+			$this->prefixMode = true;
+			$this->userprefix = $this->params['userprefix'];
+		}
+		else
+		{
+			$this->usernames = array();
+			if(!is_array($this->params['user']))
+				$this->params['user'] = array($this->params['user']);
+			foreach($this->params['user'] as $u)
+				$this->prepareUsername($u);
+			$this->prefixMode = false;
+		}
 		$this->prepareQuery();
 
 		//Do the actual query.
@@ -114,7 +122,7 @@ class ApiQueryContributions extends ApiQ
 			$this->dieUsage( 'User parameter may not be empty', 'param_user' );
 		}
 	}
-	
+
 	/**
 	 * Prepares the query and returns the limit of rows requested
 	 */
@@ -122,14 +130,20 @@ class ApiQueryContributions extends ApiQ
 
 		//We're after the revision table, and the corresponding page row for
 		//anything we retrieve.
-		list ($tbl_page, $tbl_revision) = $this->getDB()->tableNamesN('page', 'revision');
-		$this->addTables("$tbl_revision LEFT OUTER JOIN $tbl_page ON page_id=rev_page");
-		
+		$this->addTables(array('revision', 'page'));
+		$this->addWhere('page_id=rev_page');
+
 		$this->addWhereFld('rev_deleted', 0);
 		// We only want pages by the specified users.
-		$this->addWhereFld( 'rev_user_text', $this->usernames );
+		if($this->prefixMode)
+			$this->addWhere("rev_user_text LIKE '" . $this->getDb()->escapeLike($this->userprefix) . "%'");
+		else
+			$this->addWhereFld( 'rev_user_text', $this->usernames );
 		// ... and in the specified timeframe.
-		$this->addWhereRange('rev_timestamp', 
+		// Ensure the same sort order for rev_user_text and rev_timestamp
+		// so our query is indexed
+		$this->addWhereRange('rev_user_text', $this->params['dir'], null, null);
+		$this->addWhereRange('rev_timestamp',
 			$this->params['dir'], $this->params['start'], $this->params['end'] );
 		$this->addWhereFld('page_namespace', $this->params['namespace']);
 
@@ -146,22 +160,23 @@ class ApiQueryContributions extends ApiQ
 
 		// Mandatory fields: timestamp allows request continuation
 		// ns+title checks if the user has access rights for this page
-		// user_text is necessary if multiple users were specified  
+		// user_text is necessary if multiple users were specified
 		$this->addFields(array(
 			'rev_timestamp',
 			'page_namespace',
 			'page_title',
 			'rev_user_text',
 			));
-				
+
 		$this->addFieldsIf('rev_page', $this->fld_ids);
-		$this->addFieldsIf('rev_id', $this->fld_ids);
+		$this->addFieldsIf('rev_id', $this->fld_ids || $this->fld_flags);
+		$this->addFieldsIf('page_latest', $this->fld_flags);
 		// $this->addFieldsIf('rev_text_id', $this->fld_ids); // Should this field be exposed?
 		$this->addFieldsIf('rev_comment', $this->fld_comment);
 		$this->addFieldsIf('rev_minor_edit', $this->fld_flags);
 		$this->addFieldsIf('page_is_new', $this->fld_flags);
 	}
-	
+
 	/**
 	 * Extract fields from the database row and append them to a result array
 	 */
@@ -172,12 +187,12 @@ class ApiQueryContributions extends ApiQ
 		$vals['user'] = $row->rev_user_text;
 		if ($this->fld_ids) {
 			$vals['pageid'] = intval($row->rev_page);
-			$vals['revid'] = intval($row->rev_id); 
+			$vals['revid'] = intval($row->rev_id);
 			// $vals['textid'] = intval($row->rev_text_id);	// todo: Should this field be exposed?
 		}
-		
+
 		if ($this->fld_title)
-			ApiQueryBase :: addTitleInfo($vals, 
+			ApiQueryBase :: addTitleInfo($vals,
 				Title :: makeTitle($row->page_namespace, $row->page_title));
 
 		if ($this->fld_timestamp)
@@ -188,6 +203,8 @@ class ApiQueryContributions extends ApiQ
 				$vals['new'] = '';
 			if ($row->rev_minor_edit)
 				$vals['minor'] = '';
+			if ($row->page_latest == $row->rev_id)
+				$vals['top'] = '';
 		}
 
 		if ($this->fld_comment && !empty ($row->rev_comment))
@@ -214,6 +231,7 @@ class ApiQueryContributions extends ApiQ
 			'user' => array (
 				ApiBase :: PARAM_ISMULTI => true
 			),
+			'userprefix' => null,
 			'dir' => array (
 				ApiBase :: PARAM_DFLT => 'older',
 				ApiBase :: PARAM_TYPE => array (
@@ -252,6 +270,7 @@ class ApiQueryContributions extends ApiQ
 			'start' => 'The start timestamp to return from.',
 			'end' => 'The end timestamp to return to.',
 			'user' => 'The user to retrieve contributions for.',
+			'userprefix' => 'Retrieve contibutions for all users whose names begin with this value. Overrides ucuser.',
 			'dir' => 'The direction to search (older or newer).',
 			'namespace' => 'Only list contributions in these namespaces',
 			'prop' => 'Include additional pieces of information',
@@ -265,12 +284,12 @@ class ApiQueryContributions extends ApiQ
 
 	protected function getExamples() {
 		return array (
-			'api.php?action=query&list=usercontribs&ucuser=YurikBot'
+			'api.php?action=query&list=usercontribs&ucuser=YurikBot',
+			'api.php?action=query&list=usercontribs&ucuserprefix=217.121.114.',
 		);
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryUserContributions.php 30578 2008-02-05 15:40:58Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryUserContributions.php 37383 2008-07-09 11:44:49Z btongminh $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiQueryUserInfo.php 1:1.13.0-1/includes/api/ApiQueryUserInfo.php
--- 1:1.12.0-2/includes/api/ApiQueryUserInfo.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryUserInfo.php	2008-05-22 17:39:43.000000000 +0100
@@ -30,8 +30,8 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * Query module to get information about the currently logged-in user
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryUserInfo extends ApiQueryBase {
 
@@ -52,7 +52,7 @@ class ApiQueryUserInfo extends ApiQueryB
 		$r = $this->getCurrentUserInfo();
 		$result->addValue("query", $this->getModuleName(), $r);
 	}
-	
+
 	protected function getCurrentUserInfo() {
 		global $wgUser;
 		$result = $this->getResult();
@@ -67,7 +67,7 @@ class ApiQueryUserInfo extends ApiQueryB
 				$vals['blockedby'] = User::whoIs($wgUser->blockedBy());
 				$vals['blockreason'] = $wgUser->blockedFor();
 			}
-		}		
+		}
 		if (isset($this->prop['hasmsg']) && $wgUser->getNewtalk()) {
 			$vals['messages'] = '';
 		}
@@ -90,13 +90,13 @@ class ApiQueryUserInfo extends ApiQueryB
 		}
 		return $vals;
 	}
-	
+
 	protected function getRateLimits()
 	{
 		global $wgUser, $wgRateLimits;
 		if(!$wgUser->isPingLimitable())
 			return array(); // No limits
-		
+
 		// Find out which categories we belong to
 		$categories = array();
 		if($wgUser->isAnon())
@@ -110,7 +110,7 @@ class ApiQueryUserInfo extends ApiQueryB
 			if(!$wgUser->isAnon())
 				$categories[] = 'newbie';
 		}
-		
+
 		// Now get the actual limits
 		$retval = array();
 		foreach($wgRateLimits as $action => $limits)
@@ -121,7 +121,7 @@ class ApiQueryUserInfo extends ApiQueryB
 					$retval[$action][$cat]['seconds'] = $limits[$cat][1];
 				}
 		return $retval;
-	}			
+	}
 
 	public function getAllowedParams() {
 		return array (
@@ -168,6 +168,6 @@ class ApiQueryUserInfo extends ApiQueryB
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryUserInfo.php 30395 2008-02-01 14:46:46Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryUserInfo.php 35186 2008-05-22 16:39:43Z brion $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryUsers.php 1:1.13.0-1/includes/api/ApiQueryUsers.php
--- 1:1.12.0-2/includes/api/ApiQueryUsers.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryUsers.php	2008-07-29 13:58:04.000000000 +0100
@@ -30,10 +30,10 @@ if (!defined('MEDIAWIKI')) {
 
 /**
  * Query module to get information about a list of users
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
- 
+
  class ApiQueryUsers extends ApiQueryBase {
 
 	public function __construct($query, $moduleName) {
@@ -50,7 +50,7 @@ if (!defined('MEDIAWIKI')) {
 		} else {
 			$this->prop = array();
 		}
-	
+
 		if(is_array($params['users'])) {
 			$r = $this->getOtherUsersInfo($params['users']);
 			$result->setIndexedTagName($r, 'user');
@@ -63,38 +63,44 @@ if (!defined('MEDIAWIKI')) {
 		// Canonicalize user names
 		foreach($users as $u) {
 			$n = User::getCanonicalName($u);
-			if($n === false) 
+			if($n === false || $n === '')
 				$retval[] = array('name' => $u, 'invalid' => '');
 			 else
 				$goodNames[] = $n;
 		}
+		if(empty($goodNames))
+			return $retval;
 
 		$db = $this->getDb();
-		$userTable = $db->tableName('user');
-		$tables = "$userTable AS u1";
+		$this->addTables('user', 'u1');
 		$this->addFields('u1.user_name');
 		$this->addWhereFld('u1.user_name', $goodNames);
 		$this->addFieldsIf('u1.user_editcount', isset($this->prop['editcount']));
-		
+		$this->addFieldsIf('u1.user_registration', isset($this->prop['registration']));
+
 		if(isset($this->prop['groups'])) {
-			$ug = $db->tableName('user_groups');
-			$tables = "$tables LEFT JOIN $ug ON ug_user=u1.user_id";
+			$this->addTables('user_groups');
+			$this->addJoinConds(array('user_groups' => array('LEFT JOIN', 'ug_user=u1.user_id')));
 			$this->addFields('ug_group');
 		}
 		if(isset($this->prop['blockinfo'])) {
-			$ipb = $db->tableName('ipblocks');
-			$tables = "$tables LEFT JOIN $ipb ON ipb_user=u1.user_id";
-			$tables = "$tables LEFT JOIN $userTable AS u2 ON ipb_by=u2.user_id";
-			$this->addFields(array('ipb_reason', 'u2.user_name AS blocker_name'));
+			$this->addTables('ipblocks');
+			$this->addTables('user', 'u2');
+			$u2 = $this->getAliasedName('user', 'u2');
+			$this->addJoinConds(array(
+				'ipblocks' => array('LEFT JOIN', 'ipb_user=u1.user_id'),
+				$u2 => array('LEFT JOIN', 'ipb_by=u2.user_id')));
+			$this->addFields(array('ipb_reason', 'u2.user_name blocker_name'));
 		}
-		$this->addTables($tables);
-		
+
 		$data = array();
 		$res = $this->select(__METHOD__);
 		while(($r = $db->fetchObject($res))) {
 			$data[$r->user_name]['name'] = $r->user_name;
 			if(isset($this->prop['editcount']))
 				$data[$r->user_name]['editcount'] = $r->user_editcount;
+			if(isset($this->prop['registration']))
+				$data[$r->user_name]['registration'] = wfTimestampOrNull(TS_ISO_8601, $r->user_registration);
 			if(isset($this->prop['groups']))
 				// This row contains only one group, others will be added from other rows
 				if(!is_null($r->ug_group))
@@ -105,7 +111,7 @@ if (!defined('MEDIAWIKI')) {
 					$data[$r->user_name]['blockreason'] = $r->ipb_reason;
 				}
 		}
-		
+
 		// Second pass: add result data to $retval
 		foreach($goodNames as $u) {
 			if(!isset($data[$u]))
@@ -116,7 +122,7 @@ if (!defined('MEDIAWIKI')) {
 				$retval[] = $data[$u];
 			}
 		}
-		return $retval;		
+		return $retval;
 	}
 
 	public function getAllowedParams() {
@@ -127,7 +133,8 @@ if (!defined('MEDIAWIKI')) {
 				ApiBase :: PARAM_TYPE => array (
 					'blockinfo',
 					'groups',
-					'editcount'
+					'editcount',
+					'registration'
 				)
 			),
 			'users' => array(
@@ -157,6 +164,6 @@ if (!defined('MEDIAWIKI')) {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryUserInfo.php 30128 2008-01-24 17:59:07Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryUsers.php 38183 2008-07-29 12:58:04Z rotem $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiQueryWatchlist.php 1:1.13.0-1/includes/api/ApiQueryWatchlist.php
--- 1:1.12.0-2/includes/api/ApiQueryWatchlist.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiQueryWatchlist.php	2008-07-22 14:26:15.000000000 +0100
@@ -31,8 +31,8 @@ if (!defined('MEDIAWIKI')) {
 /**
  * This query action allows clients to retrieve a list of recently modified pages
  * that are part of the logged-in user's watchlist.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 class ApiQueryWatchlist extends ApiQueryGeneratorBase {
 
@@ -50,7 +50,7 @@ class ApiQueryWatchlist extends ApiQuery
 
 	private $fld_ids = false, $fld_title = false, $fld_patrol = false, $fld_flags = false,
 			$fld_timestamp = false, $fld_user = false, $fld_comment = false, $fld_sizes = false;
-	
+
 	private function run($resultPageSet = null) {
 		global $wgUser, $wgDBtype;
 
@@ -122,7 +122,7 @@ class ApiQueryWatchlist extends ApiQuery
 			'recentchanges'
 		));
 
-		$userId = $wgUser->getID();
+		$userId = $wgUser->getId();
 		$this->addWhere(array (
 			'wl_namespace = rc_namespace',
 			'wl_title = rc_title',
@@ -134,15 +134,15 @@ class ApiQueryWatchlist extends ApiQuery
 		$this->addWhereRange('rc_timestamp', $dir, $start, $end);
 		$this->addWhereFld('wl_namespace', $namespace);
 		$this->addWhereIf('rc_this_oldid=page_latest', !$allrev);
-		
+
 		if (!is_null($show)) {
 			$show = array_flip($show);
 
 			/* Check for conflicting parameters. */
-			if ((isset ($show['minor']) && isset ($show['!minor'])) 
-					|| (isset ($show['bot']) && isset ($show['!bot'])) 
+			if ((isset ($show['minor']) && isset ($show['!minor']))
+					|| (isset ($show['bot']) && isset ($show['!bot']))
 					|| (isset ($show['anon']) && isset ($show['!anon']))) {
-						
+
 				$this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show');
 			}
 
@@ -155,7 +155,7 @@ class ApiQueryWatchlist extends ApiQuery
 			$this->addWhereIf('rc_user != 0', isset ($show['!anon']));
 		}
 
-		
+
 		# This is an index optimization for mysql, as done in the Special:Watchlist page
 		$this->addWhereIf("rc_timestamp > ''", !isset ($start) && !isset ($end) && $wgDBtype == 'mysql');
 
@@ -205,9 +205,9 @@ class ApiQueryWatchlist extends ApiQuery
 
 		if ($this->fld_ids) {
 			$vals['pageid'] = intval($row->rc_cur_id);
-			$vals['revid'] = intval($row->rc_this_oldid); 
+			$vals['revid'] = intval($row->rc_this_oldid);
 		}
-		
+
 		if ($this->fld_title)
 			ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle($row->rc_namespace, $row->rc_title));
 
@@ -305,7 +305,7 @@ class ApiQueryWatchlist extends ApiQuery
 			'end' => 'The timestamp to end enumerating.',
 			'namespace' => 'Filter changes to only the given namespace(s).',
 			'dir' => 'In which direction to enumerate pages.',
-			'limit' => 'How many total pages to return per request.',
+			'limit' => 'How many total results to return per request.',
 			'prop' => 'Which additional items to get (non-generator mode only).',
 			'show' => array (
 				'Show only items that meet this criteria.',
@@ -315,7 +315,7 @@ class ApiQueryWatchlist extends ApiQuery
 	}
 
 	public function getDescription() {
-		return '';
+		return "Get all recent changes to pages in the logged in user's watchlist";
 	}
 
 	protected function getExamples() {
@@ -329,7 +329,6 @@ class ApiQueryWatchlist extends ApiQuery
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiQueryWatchlist.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiQueryWatchlist.php 37909 2008-07-22 13:26:15Z catrope $';
 	}
 }
-
diff -pruN 1:1.12.0-2/includes/api/ApiResult.php 1:1.13.0-1/includes/api/ApiResult.php
--- 1:1.12.0-2/includes/api/ApiResult.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiResult.php	2008-05-20 18:13:28.000000000 +0100
@@ -33,17 +33,17 @@ if (!defined('MEDIAWIKI')) {
  * It simply wraps a nested array() structure, adding some functions to simplify array's modifications.
  * As various modules execute, they add different pieces of information to this result,
  * structuring it as it will be given to the client.
- * 
+ *
  * Each subarray may either be a dictionary - key-value pairs with unique keys,
  * or lists, where the items are added using $data[] = $value notation.
- * 
+ *
  * There are two special key values that change how XML output is generated:
  *   '_element' This key sets the tag name for the rest of the elements in the current array.
  *              It is only inserted if the formatter returned true for getNeedsRawData()
  *   '*'        This key has special meaning only to the XML formatter, and is outputed as is
- * 				for all others. In XML it becomes the content of the current element.          
- * 
- * @addtogroup API
+ * 				for all others. In XML it becomes the content of the current element.
+ *
+ * @ingroup API
  */
 class ApiResult extends ApiBase {
 
@@ -64,15 +64,15 @@ class ApiResult extends ApiBase {
 	public function reset() {
 		$this->mData = array ();
 	}
-	
+
 	/**
-	 * Call this function when special elements such as '_element' 
-	 * are needed by the formatter, for example in XML printing. 
+	 * Call this function when special elements such as '_element'
+	 * are needed by the formatter, for example in XML printing.
 	 */
 	public function setRawMode() {
 		$this->mIsRawMode = true;
 	}
-	
+
 	/**
 	 * Returns true if the result is being created for the formatter that requested raw data.
 	 */
@@ -139,7 +139,7 @@ class ApiResult extends ApiBase {
 		// Do not use setElement() as it is ok to call this more than once
 		$arr['_element'] = $tag;
 	}
-	
+
 	/**
 	 * Calls setIndexedTagName() on $arr and each sub-array
 	 */
@@ -147,7 +147,7 @@ class ApiResult extends ApiBase {
 	{
 			if(!is_array($arr))
 					return;
-			foreach($arr as $a)
+			foreach($arr as &$a)
 			{
 					if(!is_array($a))
 							continue;
@@ -160,7 +160,7 @@ class ApiResult extends ApiBase {
 	 * Add value to the output data at the given path.
 	 * Path is an indexed array, each element specifing the branch at which to add the new value
 	 * Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value
-	 * If $name is empty, the $value is added as a next list element data[] = $value  
+	 * If $name is empty, the $value is added as a next list element data[] = $value
 	 */
 	public function addValue($path, $name, $value) {
 
@@ -191,7 +191,7 @@ class ApiResult extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiResult.php 26855 2007-10-20 18:27:39Z catrope $';
+		return __CLASS__ . ': $Id: ApiResult.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
 
@@ -199,17 +199,17 @@ class ApiResult extends ApiBase {
 if (!function_exists('array_intersect_key')) {
 	function array_intersect_key($isec, $keys) {
 		$argc = func_num_args();
-		
+
 		if ($argc > 2) {
 			for ($i = 1; !empty($isec) && $i < $argc; $i++) {
 				$arr = func_get_arg($i);
-				
+
 				foreach (array_keys($isec) as $key) {
-					if (!isset($arr[$key])) 
+					if (!isset($arr[$key]))
 						unset($isec[$key]);
 				}
 			}
-			
+
 			return $isec;
 		} else {
 			$res = array();
@@ -217,7 +217,7 @@ if (!function_exists('array_intersect_ke
 				if (isset($keys[$key]))
 					$res[$key] = $isec[$key];
 			}
-		
+
 			return $res;
 		}
 	}
diff -pruN 1:1.12.0-2/includes/api/ApiRollback.php 1:1.13.0-1/includes/api/ApiRollback.php
--- 1:1.12.0-2/includes/api/ApiRollback.php	2008-03-20 22:08:46.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiRollback.php	2008-05-20 18:13:28.000000000 +0100
@@ -28,7 +28,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiRollback extends ApiBase {
 
@@ -40,7 +40,7 @@ class ApiRollback extends ApiBase {
 		global $wgUser;
 		$this->getMain()->requestWriteMode();
 		$params = $this->extractRequestParams();
-		
+
 		$titleObj = NULL;
 		if(!isset($params['title']))
 			$this->dieUsageMsg(array('missingparam', 'title'));
@@ -62,15 +62,12 @@ class ApiRollback extends ApiBase {
 		$articleObj = new Article($titleObj);
 		$summary = (isset($params['summary']) ? $params['summary'] : "");
 		$details = null;
-		$dbw = wfGetDb(DB_MASTER);
-		$dbw->begin();
 		$retval = $articleObj->doRollback($username, $summary, $params['token'], $params['markbot'], $details);
 
 		if(!empty($retval))
 			// We don't care about multiple errors, just report one of them
 			$this->dieUsageMsg(current($retval));
 
-		$dbw->commit();
 		$current = $target = $summary = NULL;
 		extract($details);
 
@@ -85,9 +82,9 @@ class ApiRollback extends ApiBase {
 
 		$this->getResult()->addValue(null, $this->getModuleName(), $info);
 	}
-	
+
 	public function mustBePosted() { return true; }
-	
+
 	public function getAllowedParams() {
 		return array (
 			'title' => null,
@@ -123,6 +120,6 @@ class ApiRollback extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiRollback.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiRollback.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiUnblock.php 1:1.13.0-1/includes/api/ApiUnblock.php
--- 1:1.12.0-2/includes/api/ApiUnblock.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiUnblock.php	2008-05-20 18:13:28.000000000 +0100
@@ -31,7 +31,7 @@ if (!defined('MEDIAWIKI')) {
  * API module that facilitates the unblocking of users. Requires API write mode
  * to be enabled.
  *
- * @addtogroup API
+ * @ingroup API
  */
 class ApiUnblock extends ApiBase {
 
@@ -41,7 +41,7 @@ class ApiUnblock extends ApiBase {
 
 	/**
 	 * Unblocks the specified user or provides the reason the unblock failed.
-	 */	
+	 */
 	public function execute() {
 		global $wgUser;
 		$this->getMain()->requestWriteMode();
@@ -70,19 +70,16 @@ class ApiUnblock extends ApiBase {
 		$id = $params['id'];
 		$user = $params['user'];
 		$reason = (is_null($params['reason']) ? '' : $params['reason']);
-		$dbw = wfGetDb(DB_MASTER);
-		$dbw->begin();
 		$retval = IPUnblockForm::doUnblock($id, $user, $reason, $range);
 		if(!empty($retval))
 			$this->dieUsageMsg($retval);
 
-		$dbw->commit();
 		$res['id'] = $id;
 		$res['user'] = $user;
 		$res['reason'] = $reason;
 		$this->getResult()->addValue(null, $this->getModuleName(), $res);
 	}
-	
+
 	public function mustBePosted() { return true; }
 
 	public function getAllowedParams() {
@@ -119,6 +116,6 @@ class ApiUnblock extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiUnblock.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiUnblock.php 35098 2008-05-20 17:13:28Z ialex $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/api/ApiUndelete.php 1:1.13.0-1/includes/api/ApiUndelete.php
--- 1:1.12.0-2/includes/api/ApiUndelete.php	2008-03-20 22:08:47.000000000 +0000
+++ 1:1.13.0-1/includes/api/ApiUndelete.php	2008-05-26 11:51:31.000000000 +0100
@@ -28,7 +28,7 @@ if (!defined('MEDIAWIKI')) {
 }
 
 /**
- * @addtogroup API
+ * @ingroup API
  */
 class ApiUndelete extends ApiBase {
 
@@ -40,7 +40,7 @@ class ApiUndelete extends ApiBase {
 		global $wgUser;
 		$this->getMain()->requestWriteMode();
 		$params = $this->extractRequestParams();
-		
+
 		$titleObj = NULL;
 		if(!isset($params['title']))
 			$this->dieUsageMsg(array('missingparam', 'title'));
@@ -61,6 +61,8 @@ class ApiUndelete extends ApiBase {
 			$this->dieUsageMsg(array('invalidtitle', $params['title']));
 
 		// Convert timestamps
+		if(!isset($params['timestamps']))
+			$params['timestamps'] = array();
 		if(!is_array($params['timestamps']))
 			$params['timestamps'] = array($params['timestamps']);
 		foreach($params['timestamps'] as $i => $ts)
@@ -73,16 +75,19 @@ class ApiUndelete extends ApiBase {
 		if(!is_array($retval))
 			$this->dieUsageMsg(array('cannotundelete'));
 
-		$dbw->commit();
+		if($retval[1])
+			wfRunHooks( 'FileUndeleteComplete', 
+				array($titleObj, array(), $wgUser, $params['reason']) );
+
 		$info['title'] = $titleObj->getPrefixedText();
 		$info['revisions'] = $retval[0];
 		$info['fileversions'] = $retval[1];
 		$info['reason'] = $retval[2];
 		$this->getResult()->addValue(null, $this->getModuleName(), $info);
 	}
-	
+
 	public function mustBePosted() { return true; }
-	
+
 	public function getAllowedParams() {
 		return array (
 			'title' => null,
@@ -118,6 +123,6 @@ class ApiUndelete extends ApiBase {
 	}
 
 	public function getVersion() {
-		return __CLASS__ . ': $Id: ApiUndelete.php 30222 2008-01-28 19:05:26Z catrope $';
+		return __CLASS__ . ': $Id: ApiUndelete.php 35348 2008-05-26 10:51:31Z catrope $';
 	}
 }
diff -pruN 1:1.12.0-2/includes/Article.php 1:1.13.0-1/includes/Article.php
--- 1:1.12.0-2/includes/Article.php	2008-03-20 22:08:48.000000000 +0000
+++ 1:1.13.0-1/includes/Article.php	2008-07-09 12:30:34.000000000 +0100
@@ -1,6 +1,7 @@
 <?php
 /**
  * File for articles
+ * @file
  */
 
 /**
@@ -34,6 +35,8 @@ class Article {
 	var $mTouched;			//!<
 	var $mUser;				//!<
 	var $mUserText;			//!<
+	var $mRedirectTarget;		//!<
+	var $mIsRedirect;
 	/**@}}*/
 
 	/**
@@ -57,12 +60,69 @@ class Article {
 	}
 
 	/**
+	 * If this page is a redirect, get its target
+	 *
+	 * The target will be fetched from the redirect table if possible.
+	 * If this page doesn't have an entry there, call insertRedirect()
+	 * @return mixed Title object, or null if this page is not a redirect
+	 */
+	public function getRedirectTarget() {
+		if(!$this->mTitle || !$this->mTitle->isRedirect())
+			return null;
+		if(!is_null($this->mRedirectTarget))
+			return $this->mRedirectTarget;
+
+		# Query the redirect table
+		$dbr = wfGetDB(DB_SLAVE);
+		$res = $dbr->select('redirect',
+				array('rd_namespace', 'rd_title'),
+				array('rd_from' => $this->getID()),
+				__METHOD__
+		);
+		$row = $dbr->fetchObject($res);
+		if($row)
+			return $this->mRedirectTarget = Title::makeTitle($row->rd_namespace, $row->rd_title);
+
+		# This page doesn't have an entry in the redirect table
+		return $this->mRedirectTarget = $this->insertRedirect();
+	}
+
+	/**
+	 * Insert an entry for this page into the redirect table.
+	 *
+	 * Don't call this function directly unless you know what you're doing.
+	 * @return Title object
+	 */
+	public function insertRedirect() {
+		$retval = Title::newFromRedirect($this->getContent());
+		if(!$retval)
+			return null;
+		$dbw = wfGetDB(DB_MASTER);
+