summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/AjaxDispatcher.php60
-rw-r--r--includes/AjaxFunctions.php43
-rw-r--r--includes/AjaxResponse.php203
-rw-r--r--includes/Article.php90
-rw-r--r--includes/AutoLoader.php18
-rw-r--r--includes/BagOStuff.php149
-rw-r--r--includes/Block.php289
-rw-r--r--includes/CategoryPage.php265
-rw-r--r--includes/ChangesList.php3
-rw-r--r--includes/CoreParserFunctions.php32
-rw-r--r--includes/Credits.php6
-rw-r--r--includes/Database.php101
-rw-r--r--includes/DatabaseOracle.php5
-rw-r--r--includes/DatabasePostgres.php336
-rw-r--r--includes/DateFormatter.php131
-rw-r--r--includes/DefaultSettings.php169
-rw-r--r--includes/Defines.php36
-rw-r--r--includes/DifferenceEngine.php24
-rw-r--r--includes/DjVuImage.php18
-rw-r--r--includes/EditPage.php33
-rw-r--r--includes/Exception.php54
-rw-r--r--includes/Exif.php2
-rw-r--r--includes/Export.php77
-rw-r--r--includes/ExternalEdit.php2
-rw-r--r--includes/ExternalStoreDB.php1
-rw-r--r--includes/FileStore.php32
-rw-r--r--includes/GlobalFunctions.php263
-rw-r--r--includes/HTMLForm.php29
-rw-r--r--includes/HistoryBlob.php1
-rw-r--r--includes/Hooks.php16
-rw-r--r--includes/IP.php211
-rw-r--r--includes/Image.php320
-rw-r--r--includes/ImageFunctions.php479
-rw-r--r--includes/ImageGallery.php8
-rw-r--r--includes/ImagePage.php72
-rw-r--r--includes/LinkBatch.php2
-rw-r--r--includes/LinkCache.php5
-rw-r--r--includes/Linker.php16
-rw-r--r--includes/LoadBalancer.php74
-rw-r--r--includes/LogPage.php4
-rw-r--r--includes/MagicWord.php322
-rw-r--r--includes/Math.php2
-rw-r--r--includes/MemcachedSessions.php3
-rw-r--r--includes/MessageCache.php140
-rw-r--r--includes/MimeMagic.php30
-rw-r--r--includes/Namespace.php16
-rw-r--r--includes/ObjectCache.php11
-rw-r--r--includes/OutputPage.php197
-rw-r--r--includes/PageHistory.php330
-rw-r--r--includes/Pager.php656
-rw-r--r--includes/Parser.php1410
-rw-r--r--includes/ParserCache.php6
-rw-r--r--includes/Profiler.php369
-rw-r--r--includes/ProfilerSimple.php19
-rw-r--r--includes/ProfilerSimpleUDP.php11
-rw-r--r--includes/ProfilerStub.php2
-rw-r--r--includes/ProtectionForm.php3
-rw-r--r--includes/ProxyTools.php81
-rw-r--r--includes/QueryPage.php4
-rw-r--r--includes/RawPage.php27
-rw-r--r--includes/RecentChange.php35
-rw-r--r--includes/Revision.php101
-rw-r--r--includes/Sanitizer.php224
-rw-r--r--includes/SearchEngine.php93
-rw-r--r--includes/SearchPostgres.php6
-rw-r--r--includes/Setup.php205
-rw-r--r--includes/SiteStatsUpdate.php10
-rw-r--r--includes/Skin.php221
-rw-r--r--includes/SkinTemplate.php124
-rw-r--r--includes/SpecialAllmessages.php22
-rw-r--r--includes/SpecialAllpages.php16
-rw-r--r--includes/SpecialBlockip.php70
-rw-r--r--includes/SpecialBrokenRedirects.php2
-rw-r--r--includes/SpecialCategories.php2
-rw-r--r--includes/SpecialConfirmemail.php6
-rw-r--r--includes/SpecialDeadendpages.php4
-rw-r--r--includes/SpecialDisambiguations.php78
-rw-r--r--includes/SpecialDoubleRedirects.php2
-rw-r--r--includes/SpecialEmailuser.php6
-rw-r--r--includes/SpecialExport.php55
-rw-r--r--includes/SpecialImagelist.php220
-rw-r--r--includes/SpecialImport.php91
-rw-r--r--includes/SpecialIpblocklist.php270
-rw-r--r--includes/SpecialListredirects.php6
-rw-r--r--includes/SpecialListusers.php21
-rw-r--r--includes/SpecialLockdb.php28
-rw-r--r--includes/SpecialLog.php4
-rw-r--r--includes/SpecialLonelypages.php3
-rw-r--r--includes/SpecialLongpages.php5
-rw-r--r--includes/SpecialMostcategories.php2
-rw-r--r--includes/SpecialMostimages.php2
-rw-r--r--includes/SpecialMostlinked.php2
-rw-r--r--includes/SpecialMostlinkedcategories.php2
-rw-r--r--includes/SpecialMostrevisions.php4
-rw-r--r--includes/SpecialMovepage.php21
-rw-r--r--includes/SpecialNewimages.php34
-rw-r--r--includes/SpecialNewpages.php60
-rw-r--r--includes/SpecialPage.php51
-rw-r--r--includes/SpecialPreferences.php70
-rw-r--r--includes/SpecialRecentchanges.php9
-rw-r--r--includes/SpecialRecentchangeslinked.php24
-rw-r--r--includes/SpecialRevisiondelete.php19
-rw-r--r--includes/SpecialSearch.php5
-rw-r--r--includes/SpecialShortpages.php3
-rw-r--r--includes/SpecialSpecialpages.php19
-rw-r--r--includes/SpecialStatistics.php26
-rw-r--r--includes/SpecialUndelete.php59
-rw-r--r--includes/SpecialUnlockdb.php11
-rw-r--r--includes/SpecialUpload.php177
-rw-r--r--includes/SpecialUploadMogile.php1
-rw-r--r--includes/SpecialUserlogin.php147
-rw-r--r--includes/SpecialUserrights.php2
-rw-r--r--includes/SpecialVersion.php66
-rw-r--r--includes/SpecialWantedcategories.php2
-rw-r--r--includes/SpecialWantedpages.php2
-rw-r--r--includes/SpecialWatchlist.php66
-rw-r--r--includes/SpecialWhatlinkshere.php6
-rw-r--r--includes/StreamFile.php8
-rw-r--r--includes/StubObject.php130
-rw-r--r--includes/Title.php91
-rw-r--r--includes/User.php366
-rw-r--r--includes/UserMailer.php17
-rw-r--r--includes/Utf8Case.php2
-rw-r--r--includes/WatchedItem.php3
-rw-r--r--includes/WebRequest.php25
-rw-r--r--includes/WebResponse.php18
-rw-r--r--includes/WebStart.php82
-rw-r--r--includes/Wiki.php17
-rw-r--r--includes/Xml.php580
-rw-r--r--includes/XmlFunctions.php14
-rw-r--r--includes/api/ApiBase.php441
-rw-r--r--includes/api/ApiFormatBase.php161
-rw-r--r--includes/api/ApiFormatJson.php56
-rw-r--r--includes/api/ApiFormatJson_json.php841
-rw-r--r--includes/api/ApiFormatXml.php161
-rw-r--r--includes/api/ApiFormatYaml.php55
-rw-r--r--includes/api/ApiFormatYaml_spyc.php854
-rw-r--r--includes/api/ApiHelp.php55
-rw-r--r--includes/api/ApiLogin.php122
-rw-r--r--includes/api/ApiMain.php226
-rw-r--r--includes/api/ApiPageSet.php514
-rw-r--r--includes/api/ApiQuery.php354
-rw-r--r--includes/api/ApiQueryAllpages.php183
-rw-r--r--includes/api/ApiQueryBase.php112
-rw-r--r--includes/api/ApiQueryInfo.php82
-rw-r--r--includes/api/ApiQueryRevisions.php320
-rw-r--r--includes/api/ApiQuerySiteinfo.php113
-rw-r--r--includes/api/ApiResult.php153
-rw-r--r--includes/memcached-client.php42
-rw-r--r--includes/normal/CleanUpTest.php2
-rw-r--r--includes/normal/RandomTest.php6
-rw-r--r--includes/normal/UtfNormal.php2
-rw-r--r--includes/templates/NoLocalSettings.php48
-rw-r--r--includes/templates/Userlogin.php24
154 files changed, 12483 insertions, 3937 deletions
diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php
index 2084c366..618c2736 100644
--- a/includes/AjaxDispatcher.php
+++ b/includes/AjaxDispatcher.php
@@ -1,36 +1,22 @@
<?php
-//$wgRequestTime = microtime();
-
-// unset( $IP );
-// @ini_set( 'allow_url_fopen', 0 ); # For security...
-
-# Valid web server entry point, enable includes.
-# Please don't move this line to includes/Defines.php. This line essentially defines
-# a valid entry point. If you put it in includes/Defines.php, then any script that includes
-# it becomes an entry point, thereby defeating its purpose.
-// define( 'MEDIAWIKI', true );
-// require_once( './includes/Defines.php' );
-// require_once( './LocalSettings.php' );
-// require_once( 'includes/Setup.php' );
-require_once( 'AjaxFunctions.php' );
+if( !defined( 'MEDIAWIKI' ) )
+ die( 1 );
if ( ! $wgUseAjax ) {
die( 1 );
}
+require_once( 'AjaxFunctions.php' );
+
class AjaxDispatcher {
var $mode;
var $func_name;
var $args;
function AjaxDispatcher() {
- global $wgAjaxCachePolicy;
-
wfProfileIn( 'AjaxDispatcher::AjaxDispatcher' );
- $wgAjaxCachePolicy = new AjaxCachePolicy();
-
$this->mode = "";
if (! empty($_GET["rs"])) {
@@ -60,23 +46,45 @@ class AjaxDispatcher {
}
function performAction() {
- global $wgAjaxCachePolicy, $wgAjaxExportList;
+ global $wgAjaxExportList, $wgOut;
+
if ( empty( $this->mode ) ) {
return;
}
wfProfileIn( 'AjaxDispatcher::performAction' );
if (! in_array( $this->func_name, $wgAjaxExportList ) ) {
- echo "-:{$this->func_name} not callable";
+ header( 'Status: 400 Bad Request', true, 400 );
+ echo "unknown function {$this->func_name}";
} else {
- echo "+:";
- $result = call_user_func_array($this->func_name, $this->args);
- header( 'Content-Type: text/html; charset=utf-8', true );
- $wgAjaxCachePolicy->writeHeader();
- echo $result;
+ try {
+ $result = call_user_func_array($this->func_name, $this->args);
+
+ if ( $result === false || $result === NULL ) {
+ header( 'Status: 500 Internal Error', true, 500 );
+ echo "{$this->func_name} returned no data";
+ }
+ else {
+ if ( is_string( $result ) ) {
+ $result= new AjaxResponse( $result );
+ }
+
+ $result->sendHeaders();
+ $result->printText();
+ }
+
+ } catch (Exception $e) {
+ if (!headers_sent()) {
+ header( 'Status: 500 Internal Error', true, 500 );
+ print $e->getMessage();
+ } else {
+ print $e->getMessage();
+ }
+ }
}
+
wfProfileOut( 'AjaxDispatcher::performAction' );
- exit;
+ $wgOut = null;
}
}
diff --git a/includes/AjaxFunctions.php b/includes/AjaxFunctions.php
index 4387a607..9f7a332f 100644
--- a/includes/AjaxFunctions.php
+++ b/includes/AjaxFunctions.php
@@ -3,8 +3,6 @@
if( !defined( 'MEDIAWIKI' ) )
die( 1 );
-require_once('WebRequest.php');
-
/**
* Function converts an Javascript escaped string back into a string with
* specified charset (default is UTF-8).
@@ -70,35 +68,8 @@ function code2utf($num){
return '';
}
-class AjaxCachePolicy {
- var $policy;
-
- function AjaxCachePolicy( $policy = null ) {
- $this->policy = $policy;
- }
-
- function setPolicy( $policy ) {
- $this->policy = $policy;
- }
-
- function writeHeader() {
- header ("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
- if ( is_null( $this->policy ) ) {
- // Bust cache in the head
- header ("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
- // always modified
- header ("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
- header ("Pragma: no-cache"); // HTTP/1.0
- } else {
- header ("Expires: " . gmdate( "D, d M Y H:i:s", time() + $this->policy ) . " GMT");
- header ("Cache-Control: s-max-age={$this->policy},public,max-age={$this->policy}");
- }
- }
-}
-
-
function wfSajaxSearch( $term ) {
- global $wgContLang, $wgAjaxCachePolicy, $wgOut;
+ global $wgContLang, $wgOut;
$limit = 16;
$l = new Linker;
@@ -110,8 +81,6 @@ function wfSajaxSearch( $term ) {
if ( strlen( str_replace( '_', '', $term ) )<3 )
return;
- $wgAjaxCachePolicy->setPolicy( 30*60 );
-
$db =& wfGetDB( DB_SLAVE );
$res = $db->select( 'page', 'page_title',
array( 'page_namespace' => 0,
@@ -137,10 +106,10 @@ function wfSajaxSearch( $term ) {
}
$subtitlemsg = ( Title::newFromText($term) ? 'searchsubtitle' : 'searchsubtitleinvalid' );
- $subtitle = $wgOut->parse( wfMsg( $subtitlemsg, wfEscapeWikiText($term) ) );
+ $subtitle = $wgOut->parse( wfMsg( $subtitlemsg, wfEscapeWikiText($term) ) ); #FIXME: parser is missing mTitle !
$term = htmlspecialchars( $term );
- return '<div style="float:right; border:solid 1px black;background:gainsboro;padding:2px;"><a onclick="Searching_Hide_Results();">'
+ $html = '<div style="float:right; border:solid 1px black;background:gainsboro;padding:2px;"><a onclick="Searching_Hide_Results();">'
. wfMsg( 'hideresults' ) . '</a></div>'
. '<h1 class="firstHeading">'.wfMsg('search')
. '</h1><div id="contentSub">'. $subtitle . '</div><ul><li>'
@@ -152,6 +121,12 @@ function wfSajaxSearch( $term ) {
"search=$term&go=Go" )
. "</li></ul><h2>" . wfMsg( 'articletitles', $term ) . "</h2>"
. '<ul>' .$r .'</ul>'.$more;
+
+ $response = new AjaxResponse( $html );
+
+ $response->setCacheDuration( 30*60 );
+
+ return $response;
}
?>
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php
new file mode 100644
index 00000000..40f50876
--- /dev/null
+++ b/includes/AjaxResponse.php
@@ -0,0 +1,203 @@
+<?php
+
+if( !defined( 'MEDIAWIKI' ) )
+ die( 1 );
+
+class AjaxResponse {
+ var $mCacheDuration;
+ var $mVary;
+
+ var $mDisabled;
+ var $mText;
+ var $mResponseCode;
+ var $mLastModified;
+ var $mContentType;
+
+ function AjaxResponse( $text = NULL ) {
+ $this->mCacheDuration = NULL;
+ $this->mVary = NULL;
+
+ $this->mDisabled = false;
+ $this->mText = '';
+ $this->mResponseCode = '200 OK';
+ $this->mLastModified = false;
+ $this->mContentType= 'text/html; charset=utf-8';
+
+ if ( $text ) {
+ $this->addText( $text );
+ }
+ }
+
+ function setCacheDuration( $duration ) {
+ $this->mCacheDuration = $duration;
+ }
+
+ function setVary( $vary ) {
+ $this->mVary = $vary;
+ }
+
+ function setResponseCode( $code ) {
+ $this->mResponseCode = $code;
+ }
+
+ function setContentType( $type ) {
+ $this->mContentType = $type;
+ }
+
+ function disable() {
+ $this->mDisabled = true;
+ }
+
+ function addText( $text ) {
+ if ( ! $this->mDisabled && $text ) {
+ $this->mText .= $text;
+ }
+ }
+
+ function printText() {
+ if ( ! $this->mDisabled ) {
+ print $this->mText;
+ }
+ }
+
+ function sendHeaders() {
+ global $wgUseSquid, $wgUseESI, $wgSquidMaxage;
+
+ if ( $this->mResponseCode ) {
+ $n = preg_replace( '/^ *(\d+)/', '\1', $this->mResponseCode );
+ header( "Status: " . $this->mResponseCode, true, (int)$n );
+ }
+
+ header ("Content-Type: " . $this->mContentType );
+
+ if ( $this->mLastModified ) {
+ header ("Last-Modified: " . $this->mLastModified );
+ }
+ else {
+ header ("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
+ }
+
+ if ( $this->mCacheDuration ) {
+
+ # 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.
+
+ if( $wgUseSquid ) {
+
+ # Expect explicite purge of the proxy cache, but require end user agents
+ # to revalidate against the proxy on each visit.
+ # Surrogate-Control controls our Squid, Cache-Control downstream caches
+
+ if ( $wgUseESI ) {
+ header( 'Surrogate-Control: max-age='.$this->mCacheDuration.', content="ESI/1.0"');
+ header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
+ } else {
+ header( 'Cache-Control: s-maxage='.$this->mCacheDuration.', must-revalidate, max-age=0' );
+ }
+
+ } else {
+
+ # Let the client do the caching. Cache is not purged.
+ header ("Expires: " . gmdate( "D, d M Y H:i:s", time() + $this->mCacheDuration ) . " GMT");
+ header ("Cache-Control: s-max-age={$this->mCacheDuration},public,max-age={$this->mCacheDuration}");
+ }
+
+ } else {
+ # always expired, always modified
+ header ("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
+ header ("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
+ header ("Pragma: no-cache"); // HTTP/1.0
+ }
+
+ if ( $this->mVary ) {
+ header ( "Vary: " . $this->mVary );
+ }
+ }
+
+ /**
+ * checkLastModified tells the client to use the client-cached response if
+ * possible. If sucessful, the AjaxResponse is disabled so that
+ * any future call to AjaxResponse::printText() have no effect. The method
+ * returns true iff the response code was set to 304 Not Modified.
+ */
+ function checkLastModified ( $timestamp ) {
+ global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest;
+ $fname = 'AjaxResponse::checkLastModified';
+
+ if ( !$timestamp || $timestamp == '19700101000000' ) {
+ wfDebug( "$fname: CACHE DISABLED, NO TIMESTAMP\n" );
+ return;
+ }
+ if( !$wgCachePages ) {
+ wfDebug( "$fname: CACHE DISABLED\n", false );
+ return;
+ }
+ if( $wgUser->getOption( 'nocache' ) ) {
+ wfDebug( "$fname: USER DISABLED CACHE\n", false );
+ return;
+ }
+
+ $timestamp = wfTimestamp( TS_MW, $timestamp );
+ $lastmod = wfTimestamp( TS_RFC2822, max( $timestamp, $wgUser->mTouched, $wgCacheEpoch ) );
+
+ if( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
+ # IE sends sizes after the date like this:
+ # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
+ # this breaks strtotime().
+ $modsince = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
+ $modsinceTime = strtotime( $modsince );
+ $ismodsince = wfTimestamp( TS_MW, $modsinceTime ? $modsinceTime : 1 );
+ wfDebug( "$fname: -- client send If-Modified-Since: " . $modsince . "\n", false );
+ wfDebug( "$fname: -- we might send Last-Modified : $lastmod\n", false );
+ if( ($ismodsince >= $timestamp ) && $wgUser->validateCache( $ismodsince ) && $ismodsince >= $wgCacheEpoch ) {
+ $this->setResponseCode( "304 Not Modified" );
+ $this->disable();
+ $this->mLastModified = $lastmod;
+
+ wfDebug( "$fname: CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
+
+ return true;
+ } else {
+ wfDebug( "$fname: READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
+ $this->mLastModified = $lastmod;
+ }
+ } else {
+ wfDebug( "$fname: client did not send If-Modified-Since header\n", false );
+ $this->mLastModified = $lastmod;
+ }
+ }
+
+ function loadFromMemcached( $mckey, $touched ) {
+ global $wgMemc;
+ if ( !$touched ) return false;
+
+ $mcvalue = $wgMemc->get( $mckey );
+ if ( $mcvalue ) {
+ # Check to see if the value has been invalidated
+ if ( $touched <= $mcvalue['timestamp'] ) {
+ wfDebug( "Got $mckey from cache\n" );
+ $this->mText = $mcvalue['value'];
+ return true;
+ } else {
+ wfDebug( "$mckey has expired\n" );
+ }
+ }
+
+ return false;
+ }
+
+ function storeInMemcached( $mckey, $expiry = 86400 ) {
+ global $wgMemc;
+
+ $wgMemc->set( $mckey,
+ array(
+ 'timestamp' => wfTimestampNow(),
+ 'value' => $this->mText
+ ), $expiry
+ );
+
+ return true;
+ }
+}
+?>
diff --git a/includes/Article.php b/includes/Article.php
index b1e1f620..8c07b06c 100644
--- a/includes/Article.php
+++ b/includes/Article.php
@@ -5,11 +5,6 @@
*/
/**
- * Need the CacheManager to be loaded
- */
-require_once( 'CacheManager.php' );
-
-/**
* Class representing a MediaWiki article and history.
*
* See design.txt for an overview.
@@ -651,15 +646,16 @@ class Article {
# diff page instead of the article.
if ( !is_null( $diff ) ) {
- require_once( 'DifferenceEngine.php' );
$wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
$de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid );
// DifferenceEngine directly fetched the revision:
$this->mRevIdFetched = $de->mNewid;
$de->showDiffPage();
-
- if( $diff == 0 ) {
+
+ // Needed to get the page's current revision
+ $this->loadPageData();
+ if( $diff == 0 || $diff == $this->mLatest ) {
# Run view updates for current revision only
$this->viewUpdates();
}
@@ -719,6 +715,7 @@ class Article {
$outputDone = false;
if ( $pcache ) {
if ( $wgOut->tryParserCache( $this, $wgUser ) ) {
+ wfRunHooks( 'ArticleViewHeader', array( &$this ) );
$outputDone = true;
}
}
@@ -804,13 +801,13 @@ class Article {
# Display content, don't attempt to save to parser cache
# Don't show section-edit links on old revisions... this way lies madness.
if( !$this->isCurrent() ) {
- $oldEditSectionSetting = $wgOut->mParserOptions->setEditSection( false );
+ $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
}
# Display content and don't save to parser cache
$wgOut->addPrimaryWikiText( $text, $this, false );
if( !$this->isCurrent() ) {
- $wgOut->mParserOptions->setEditSection( $oldEditSectionSetting );
+ $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
}
}
}
@@ -886,7 +883,7 @@ class Article {
}
if ((!$wgUser->isAllowed('delete'))) {
- $wgOut->sysopRequired();
+ $wgOut->permissionRequired( 'delete' );
return;
}
@@ -1216,7 +1213,7 @@ class Article {
# Silently ignore EDIT_MINOR if not allowed
$isminor = ( $flags & EDIT_MINOR ) && $wgUser->isAllowed('minoredit');
- $bot = $wgUser->isBot() || ( $flags & EDIT_FORCE_BOT );
+ $bot = $wgUser->isAllowed( 'bot' ) || ( $flags & EDIT_FORCE_BOT );
$text = $this->preSaveTransform( $text );
@@ -1447,7 +1444,7 @@ class Article {
$wgOut->setPagetitle( wfMsg( 'addedwatch' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
- $link = $this->mTitle->getPrefixedText();
+ $link = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
$text = wfMsg( 'addedwatchtext', $link );
$wgOut->addWikiText( $text );
}
@@ -1467,7 +1464,6 @@ class Article {
if (wfRunHooks('WatchArticle', array(&$wgUser, &$this))) {
$wgUser->addWatch( $this->mTitle );
- $wgUser->saveSettings();
return wfRunHooks('WatchArticleComplete', array(&$wgUser, &$this));
}
@@ -1495,7 +1491,7 @@ class Article {
$wgOut->setPagetitle( wfMsg( 'removedwatch' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
- $link = $this->mTitle->getPrefixedText();
+ $link = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
$text = wfMsg( 'removedwatchtext', $link );
$wgOut->addWikiText( $text );
}
@@ -1515,7 +1511,6 @@ class Article {
if (wfRunHooks('UnwatchArticle', array(&$wgUser, &$this))) {
$wgUser->removeWatch( $this->mTitle );
- $wgUser->saveSettings();
return wfRunHooks('UnwatchArticleComplete', array(&$wgUser, &$this));
}
@@ -1527,7 +1522,6 @@ class Article {
* action=protect handler
*/
function protect() {
- require_once 'ProtectionForm.php';
$form = new ProtectionForm( $this );
$form->show();
}
@@ -1641,7 +1635,7 @@ class Article {
# Check permissions
if( $wgUser->isAllowed( 'delete' ) ) {
- if( $wgUser->isBlocked() ) {
+ if( $wgUser->isBlocked( !$confirm ) ) {
$wgOut->blockedPage();
return;
}
@@ -1842,7 +1836,7 @@ class Article {
if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) {
if ( $this->doDeleteArticle( $reason ) ) {
- $deleted = $this->mTitle->getPrefixedText();
+ $deleted = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
$wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
@@ -1910,29 +1904,35 @@ class Article {
);
# Now that it's safely backed up, delete it
- $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
$dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__);
- if ($wgUseTrackbacks)
- $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
+ # If using cascading deletes, we can skip some explicit deletes
+ if ( !$dbw->cascadingDeletes() ) {
+
+ $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
+
+ if ($wgUseTrackbacks)
+ $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
- # Clean up recentchanges entries...
- $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), __METHOD__ );
+ # Delete outgoing links
+ $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
+ $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
+ $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
+ $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
+ $dbw->delete( 'externallinks', array( 'el_from' => $id ) );
+ $dbw->delete( 'langlinks', array( 'll_from' => $id ) );
+ }
+
+ # If using cleanup triggers, we can skip some manual deletes
+ if ( !$dbw->cleanupTriggers() ) {
- # Finally, clean up the link tables
- $t = $this->mTitle->getPrefixedDBkey();
+ # Clean up recentchanges entries...
+ $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), __METHOD__ );
+ }
# Clear caches
Article::onArticleDelete( $this->mTitle );
- # Delete outgoing links
- $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
- $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
- $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
- $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
- $dbw->delete( 'externallinks', array( 'el_from' => $id ) );
- $dbw->delete( 'langlinks', array( 'll_from' => $id ) );
-
# Log the deletion
$log = new LogPage( 'delete' );
$log->addEntry( 'delete', $this->mTitle, $reason );
@@ -2141,7 +2141,7 @@ class Article {
# If this is another user's talk page, update newtalk
# Don't do this if $changed = false otherwise some idiot can null-edit a
# load of user talk pages and piss people off
- if( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getName() && $changed ) {
+ if( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed ) {
if (wfRunHooks('ArticleEditUpdateNewTalk', array(&$this)) ) {
$other = User::newFromName( $shortTitle );
if( is_null( $other ) && User::isIP( $shortTitle ) ) {
@@ -2161,6 +2161,22 @@ class Article {
wfProfileOut( __METHOD__ );
}
+
+ /**
+ * Perform article updates on a special page creation.
+ *
+ * @param Revision $rev
+ *
+ * @fixme This is a shitty interface function. Kill it and replace the
+ * other shitty functions like editUpdates and such so it's not needed
+ * anymore.
+ */
+ function createUpdates( $rev ) {
+ $this->mGoodAdjustment = $this->isCountable( $rev->getText() );
+ $this->mTotalAdjustment = 1;
+ $this->editUpdates( $rev->getText(), $rev->getComment(),
+ $rev->isMinor(), wfTimestamp(), $rev->getId(), true );
+ }
/**
* Generate the navigation links when browsing through an article revisions
@@ -2174,6 +2190,10 @@ class Article {
function setOldSubtitle( $oldid=0 ) {
global $wgLang, $wgOut, $wgUser;
+ if ( !wfRunHooks( 'DisplayOldSubtitle', array(&$this, &$oldid) ) ) {
+ return;
+ }
+
$revision = Revision::newFromId( $oldid );
$current = ( $oldid == $this->mLatest );
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 7d09d5b6..810a448e 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -10,6 +10,7 @@ function __autoload($className) {
static $localClasses = array(
'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
'AjaxCachePolicy' => 'includes/AjaxFunctions.php',
+ 'AjaxResponse' => 'includes/AjaxResponse.php',
'Article' => 'includes/Article.php',
'AuthPlugin' => 'includes/AuthPlugin.php',
'BagOStuff' => 'includes/BagOStuff.php',
@@ -19,9 +20,11 @@ function __autoload($className) {
'TurckBagOStuff' => 'includes/BagOStuff.php',
'APCBagOStuff' => 'includes/BagOStuff.php',
'eAccelBagOStuff' => 'includes/BagOStuff.php',
+ 'DBABagOStuff' => 'includes/BagOStuff.php',
'Block' => 'includes/Block.php',
'CacheManager' => 'includes/CacheManager.php',
'CategoryPage' => 'includes/CategoryPage.php',
+ 'CategoryViewer' => 'includes/CategoryPage.php',
'Categoryfinder' => 'includes/Categoryfinder.php',
'RCCacheEntry' => 'includes/ChangesList.php',
'ChangesList' => 'includes/ChangesList.php',
@@ -89,6 +92,7 @@ function __autoload($className) {
'HTMLCacheUpdateJob' => 'includes/HTMLCacheUpdate.php',
'Http' => 'includes/HttpFunctions.php',
'Image' => 'includes/Image.php',
+ 'IP' => 'includes/IP.php',
'ThumbnailImage' => 'includes/Image.php',
'ImageGallery' => 'includes/ImageGallery.php',
'ImagePage' => 'includes/ImagePage.php',
@@ -113,16 +117,16 @@ function __autoload($className) {
'FakeMemCachedClient' => 'includes/ObjectCache.php',
'OutputPage' => 'includes/OutputPage.php',
'PageHistory' => 'includes/PageHistory.php',
+ 'IndexPager' => 'includes/Pager.php',
+ 'ReverseChronologicalPager' => 'includes/Pager.php',
+ 'TablePager' => 'includes/Pager.php',
'Parser' => 'includes/Parser.php',
'ParserOutput' => 'includes/Parser.php',
'ParserOptions' => 'includes/Parser.php',
'ParserCache' => 'includes/ParserCache.php',
- 'element' => 'includes/ParserXML.php',
- 'xml2php' => 'includes/ParserXML.php',
- 'ParserXML' => 'includes/ParserXML.php',
'ProfilerSimple' => 'includes/ProfilerSimple.php',
'ProfilerSimpleUDP' => 'includes/ProfilerSimpleUDP.php',
- 'Profiler' => 'includes/Profiling.php',
+ 'Profiler' => 'includes/Profiler.php',
'ProxyTools' => 'includes/ProxyTools.php',
'ProtectionForm' => 'includes/ProtectionForm.php',
'QueryPage' => 'includes/QueryPage.php',
@@ -213,6 +217,7 @@ function __autoload($className) {
'EmailNotification' => 'includes/UserMailer.php',
'WatchedItem' => 'includes/WatchedItem.php',
'WebRequest' => 'includes/WebRequest.php',
+ 'WebResponse' => 'includes/WebResponse.php',
'FauxRequest' => 'includes/WebRequest.php',
'MediaWiki' => 'includes/Wiki.php',
'WikiError' => 'includes/WikiError.php',
@@ -221,7 +226,10 @@ function __autoload($className) {
'Xml' => 'includes/Xml.php',
'ZhClient' => 'includes/ZhClient.php',
'memcached' => 'includes/memcached-client.php',
- 'UtfNormal' => 'includes/normal/UtfNormal.php'
+ 'UtfNormal' => 'includes/normal/UtfNormal.php',
+ 'UsercreateTemplate' => 'includes/templates/Userlogin.php',
+ 'UserloginTemplate' => 'includes/templates/Userlogin.php',
+ 'Language' => 'languages/Language.php',
);
if ( isset( $localClasses[$className] ) ) {
$filename = $localClasses[$className];
diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php
index 182756ab..1dc93a2f 100644
--- a/includes/BagOStuff.php
+++ b/includes/BagOStuff.php
@@ -146,6 +146,17 @@ class BagOStuff {
if($this->debugmode)
wfDebug("BagOStuff debug: $text\n");
}
+
+ /**
+ * Convert an optionally relative time to an absolute time
+ */
+ static function convertExpiry( $exptime ) {
+ if(($exptime != 0) && ($exptime < 3600*24*30)) {
+ return time() + $exptime;
+ } else {
+ return $exptime;
+ }
+ }
}
@@ -183,9 +194,7 @@ class HashBagOStuff extends BagOStuff {
}
function set($key,$value,$exptime=0) {
- if(($exptime != 0) && ($exptime < 3600*24*30))
- $exptime = time() + $exptime;
- $this->bag[$key] = array( $value, $exptime );
+ $this->bag[$key] = array( $value, BagOStuff::convertExpiry( $exptime ) );
}
function delete($key,$time=0) {
@@ -491,7 +500,7 @@ class APCBagOStuff extends BagOStuff {
return true;
}
- function delete($key) {
+ function delete($key, $time=0) {
apc_delete($key);
return true;
}
@@ -535,4 +544,136 @@ class eAccelBagOStuff extends BagOStuff {
return true;
}
}
+
+class DBABagOStuff extends BagOStuff {
+ var $mHandler, $mFile, $mReader, $mWriter, $mDisabled;
+
+ function __construct( $handler = 'db3', $dir = false ) {
+ if ( $dir === false ) {
+ global $wgTmpDirectory;
+ $dir = $wgTmpDirectory;
+ }
+ $this->mFile = "$dir/mw-cache-" . wfWikiID();
+ $this->mFile .= '.db';
+ $this->mHandler = $handler;
+ }
+
+ /**
+ * Encode value and expiry for storage
+ */
+ function encode( $value, $expiry ) {
+ # Convert to absolute time
+ $expiry = BagOStuff::convertExpiry( $expiry );
+ return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value );
+ }
+
+ /**
+ * @return list containing value first and expiry second
+ */
+ function decode( $blob ) {
+ if ( !is_string( $blob ) ) {
+ return array( null, 0 );
+ } else {
+ return array(
+ unserialize( substr( $blob, 11 ) ),
+ intval( substr( $blob, 0, 10 ) )
+ );
+ }
+ }
+
+ function getReader() {
+ if ( file_exists( $this->mFile ) ) {
+ $handle = dba_open( $this->mFile, 'rl', $this->mHandler );
+ } else {
+ $handle = $this->getWriter();
+ }
+ if ( !$handle ) {
+ wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
+ }
+ return $handle;
+ }
+
+ function getWriter() {
+ $handle = dba_open( $this->mFile, 'cl', $this->mHandler );
+ if ( !$handle ) {
+ wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
+ }
+ return $handle;
+ }
+
+ function get( $key ) {
+ wfProfileIn( __METHOD__ );
+ wfDebug( __METHOD__."($key)\n" );
+ $handle = $this->getReader();
+ if ( !$handle ) {
+ return null;
+ }
+ $val = dba_fetch( $key, $handle );
+ list( $val, $expiry ) = $this->decode( $val );
+ # Must close ASAP because locks are held
+ dba_close( $handle );
+
+ if ( !is_null( $val ) && $expiry && $expiry < time() ) {
+ # Key is expired, delete it
+ $handle = $this->getWriter();
+ dba_delete( $key, $handle );
+ dba_close( $handle );
+ wfDebug( __METHOD__.": $key expired\n" );
+ $val = null;
+ }
+ wfProfileOut( __METHOD__ );
+ return $val;
+ }
+
+ function set( $key, $value, $exptime=0 ) {
+ wfProfileIn( __METHOD__ );
+ wfDebug( __METHOD__."($key)\n" );
+ $blob = $this->encode( $value, $exptime );
+ $handle = $this->getWriter();
+ if ( !$handle ) {
+ return false;
+ }
+ $ret = dba_replace( $key, $blob, $handle );
+ dba_close( $handle );
+ wfProfileOut( __METHOD__ );
+ return $ret;
+ }
+
+ function delete( $key, $time = 0 ) {
+ wfProfileIn( __METHOD__ );
+ $handle = $this->getWriter();
+ if ( !$handle ) {
+ return false;
+ }
+ $ret = dba_delete( $key, $handle );
+ dba_close( $handle );
+ wfProfileOut( __METHOD__ );
+ return $ret;
+ }
+
+ function add( $key, $value, $exptime = 0 ) {
+ wfProfileIn( __METHOD__ );
+ $blob = $this->encode( $value, $exptime );
+ $handle = $this->getWriter();
+ if ( !$handle ) {
+ return false;
+ }
+ $ret = dba_insert( $key, $blob, $handle );
+ # Insert failed, check to see if it failed due to an expired key
+ if ( !$ret ) {
+ list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) );
+ if ( $expiry < time() ) {
+ # Yes expired, delete and try again
+ dba_delete( $key, $handle );
+ $ret = dba_insert( $key, $blob, $handle );
+ # This time if it failed then it will be handled by the caller like any other race
+ }
+ }
+
+ dba_close( $handle );
+ wfProfileOut( __METHOD__ );
+ return $ret;
+ }
+}
+
?>
diff --git a/includes/Block.php b/includes/Block.php
index 26fa444d..b11df22c 100644
--- a/includes/Block.php
+++ b/includes/Block.php
@@ -9,7 +9,6 @@
* All the functions in this class assume the object is either explicitly
* loaded or filled. It is not load-on-demand. There are no accessors.
*
- * To use delete(), you only need to fill $mAddress
* Globals used: $wgAutoblockExpiry, $wgAntiLockFlags
*
* @todo This could be used everywhere, but it isn't.
@@ -18,27 +17,26 @@
class Block
{
/* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
- $mRangeStart, $mRangeEnd;
+ $mRangeStart, $mRangeEnd, $mAnonOnly;
/* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName;
const EB_KEEP_EXPIRED = 1;
const EB_FOR_UPDATE = 2;
const EB_RANGE_ONLY = 4;
- function Block( $address = '', $user = '', $by = 0, $reason = '',
- $timestamp = '' , $auto = 0, $expiry = '' )
+ function Block( $address = '', $user = 0, $by = 0, $reason = '',
+ $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0 )
{
+ $this->mId = 0;
$this->mAddress = $address;
$this->mUser = $user;
$this->mBy = $by;
$this->mReason = $reason;
$this->mTimestamp = wfTimestamp(TS_MW,$timestamp);
$this->mAuto = $auto;
- if( empty( $expiry ) ) {
- $this->mExpiry = $expiry;
- } else {
- $this->mExpiry = wfTimestamp( TS_MW, $expiry );
- }
+ $this->mAnonOnly = $anonOnly;
+ $this->mCreateAccount = $createAccount;
+ $this->mExpiry = self::decodeExpiry( $expiry );
$this->mForUpdate = false;
$this->mFromMaster = false;
@@ -46,19 +44,36 @@ class Block
$this->initialiseRange();
}
- /*static*/ function newFromDB( $address, $user = 0, $killExpired = true )
+ static function newFromDB( $address, $user = 0, $killExpired = true )
{
- $ban = new Block();
- $ban->load( $address, $user, $killExpired );
- return $ban;
+ $block = new Block();
+ $block->load( $address, $user, $killExpired );
+ if ( $block->isValid() ) {
+ return $block;
+ } else {
+ return null;
+ }
+ }
+
+ static function newFromID( $id )
+ {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $res = $dbr->resultObject( $dbr->select( 'ipblocks', '*',
+ array( 'ipb_id' => $id ), __METHOD__ ) );
+ $block = new Block;
+ if ( $block->loadFromResult( $res ) ) {
+ return $block;
+ } else {
+ return null;
+ }
}
function clear()
{
$this->mAddress = $this->mReason = $this->mTimestamp = '';
- $this->mUser = $this->mBy = 0;
+ $this->mId = $this->mAnonOnly = $this->mCreateAccount =
+ $this->mAuto = $this->mUser = $this->mBy = 0;
$this->mByName = false;
-
}
/**
@@ -70,56 +85,103 @@ class Block
if ( $this->mForUpdate || $this->mFromMaster ) {
$db =& wfGetDB( DB_MASTER );
if ( !$this->mForUpdate || ($wgAntiLockFlags & ALF_NO_BLOCK_LOCK) ) {
- $options = '';
+ $options = array();
} else {
- $options = 'FOR UPDATE';
+ $options = array( 'FOR UPDATE' );
}
} else {
$db =& wfGetDB( DB_SLAVE );
- $options = '';
+ $options = array();
}
return $db;
}
/**
* Get a ban from the DB, with either the given address or the given username
+ *
+ * @param string $address The IP address of the user, or blank to skip IP blocks
+ * @param integer $user The user ID, or zero for anonymous users
+ * @param bool $killExpired Whether to delete expired rows while loading
+ *
*/
function load( $address = '', $user = 0, $killExpired = true )
{
- $fname = 'Block::load';
wfDebug( "Block::load: '$address', '$user', $killExpired\n" );
- $options = '';
+ $options = array();
$db =& $this->getDBOptions( $options );
$ret = false;
$killed = false;
- $ipblocks = $db->tableName( 'ipblocks' );
if ( 0 == $user && $address == '' ) {
# Invalid user specification, not blocked
$this->clear();
return false;
- } elseif ( $address == '' ) {
- $sql = "SELECT * FROM $ipblocks WHERE ipb_user={$user} $options";
- } elseif ( $user == '' ) {
- $sql = "SELECT * FROM $ipblocks WHERE ipb_address=" . $db->addQuotes( $address ) . " $options";
- } elseif ( $options == '' ) {
- # If there are no options (e.g. FOR UPDATE), use a UNION
- # so that the query can make efficient use of indices
- $sql = "SELECT * FROM $ipblocks WHERE ipb_address='" . $db->strencode( $address ) .
- "' UNION SELECT * FROM $ipblocks WHERE ipb_user={$user}";
- } else {
- # If there are options, a UNION can not be used, use one
- # SELECT instead. Will do a full table scan.
- $sql = "SELECT * FROM $ipblocks WHERE (ipb_address='" . $db->strencode( $address ) .
- "' OR ipb_user={$user}) $options";
}
- $res = $db->query( $sql, $fname );
- if ( 0 != $db->numRows( $res ) ) {
+ # Try user block
+ if ( $user ) {
+ $res = $db->resultObject( $db->select( 'ipblocks', '*', array( 'ipb_user' => $user ),
+ __METHOD__, $options ) );
+ if ( $this->loadFromResult( $res, $killExpired ) ) {
+ return true;
+ }
+ }
+
+ # Try IP block
+ # TODO: improve performance by merging this query with the autoblock one
+ # Slightly tricky while handling killExpired as well
+ if ( $address ) {
+ $conds = array( 'ipb_address' => $address, 'ipb_auto' => 0 );
+ $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
+ if ( $this->loadFromResult( $res, $killExpired ) ) {
+ if ( $user && $this->mAnonOnly ) {
+ # Block is marked anon-only
+ # Whitelist this IP address against autoblocks and range blocks
+ $this->clear();
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ # Try range block
+ if ( $this->loadRange( $address, $killExpired, $user == 0 ) ) {
+ if ( $user && $this->mAnonOnly ) {
+ $this->clear();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ # Try autoblock
+ if ( $address ) {
+ $conds = array( 'ipb_address' => $address, 'ipb_auto' => 1 );
+ if ( $user ) {
+ $conds['ipb_anon_only'] = 0;
+ }
+ $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
+ if ( $this->loadFromResult( $res, $killExpired ) ) {
+ return true;
+ }
+ }
+
+ # Give up
+ $this->clear();
+ return false;
+ }
+
+ /**
+ * Fill in member variables from a result wrapper
+ */
+ function loadFromResult( ResultWrapper $res, $killExpired = true ) {
+ $ret = false;
+ if ( 0 != $res->numRows() ) {
# Get first block
- $row = $db->fetchObject( $res );
+ $row = $res->fetchObject();
$this->initFromRow( $row );
if ( $killExpired ) {
@@ -127,7 +189,7 @@ class Block
do {
$killed = $this->deleteIfExpired();
if ( $killed ) {
- $row = $db->fetchObject( $res );
+ $row = $res->fetchObject();
if ( $row ) {
$this->initFromRow( $row );
}
@@ -135,26 +197,14 @@ class Block
} while ( $killed && $row );
# If there were any left after the killing finished, return true
- if ( !$row ) {
- $ret = false;
- $this->clear();
- } else {
+ if ( $row ) {
$ret = true;
}
} else {
$ret = true;
}
}
- $db->freeResult( $res );
-
- # No blocks found yet? Try looking for range blocks
- if ( !$ret && $address != '' ) {
- $ret = $this->loadRange( $address, $killExpired );
- }
- if ( !$ret ) {
- $this->clear();
- }
-
+ $res->free();
return $ret;
}
@@ -164,9 +214,7 @@ class Block
*/
function loadRange( $address, $killExpired = true )
{
- $fname = 'Block::loadRange';
-
- $iaddr = wfIP2Hex( $address );
+ $iaddr = IP::toHex( $address );
if ( $iaddr === false ) {
# Invalid address
return false;
@@ -176,27 +224,16 @@ class Block
# Blocks should not cross a /16 boundary.
$range = substr( $iaddr, 0, 4 );
- $options = '';
+ $options = array();
$db =& $this->getDBOptions( $options );
- $ipblocks = $db->tableName( 'ipblocks' );
- $sql = "SELECT * FROM $ipblocks WHERE ipb_range_start LIKE '$range%' ".
- "AND ipb_range_start <= '$iaddr' AND ipb_range_end >= '$iaddr' $options";
- $res = $db->query( $sql, $fname );
- $row = $db->fetchObject( $res );
-
- $success = false;
- if ( $row ) {
- # Found a row, initialise this object
- $this->initFromRow( $row );
-
- # Is it expired?
- if ( !$killExpired || !$this->deleteIfExpired() ) {
- # No, return true
- $success = true;
- }
- }
+ $conds = array(
+ "ipb_range_start LIKE '$range%'",
+ "ipb_range_start <= '$iaddr'",
+ "ipb_range_end >= '$iaddr'"
+ );
- $db->freeResult( $res );
+ $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
+ $success = $this->loadFromResult( $res, $killExpired );
return $success;
}
@@ -220,10 +257,10 @@ class Block
$this->mUser = $row->ipb_user;
$this->mBy = $row->ipb_by;
$this->mAuto = $row->ipb_auto;
+ $this->mAnonOnly = $row->ipb_anon_only;
+ $this->mCreateAccount = $row->ipb_create_account;
$this->mId = $row->ipb_id;
- $this->mExpiry = $row->ipb_expiry ?
- wfTimestamp(TS_MW,$row->ipb_expiry) :
- $row->ipb_expiry;
+ $this->mExpiry = self::decodeExpiry( $row->ipb_expiry );
if ( isset( $row->user_name ) ) {
$this->mByName = $row->user_name;
} else {
@@ -304,24 +341,32 @@ class Block
function delete()
{
- $fname = 'Block::delete';
if (wfReadOnly()) {
- return;
+ return false;
}
- $dbw =& wfGetDB( DB_MASTER );
-
- if ( $this->mAddress == '' ) {
- $condition = array( 'ipb_id' => $this->mId );
- } else {
- $condition = array( 'ipb_address' => $this->mAddress );
+ if ( !$this->mId ) {
+ throw new MWException( "Block::delete() now requires that the mId member be filled\n" );
}
- return( $dbw->delete( 'ipblocks', $condition, $fname ) > 0 ? true : false );
+
+ $dbw =& wfGetDB( DB_MASTER );
+ $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->mId ), __METHOD__ );
+ return $dbw->affectedRows() > 0;
}
function insert()
{
wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
$dbw =& wfGetDB( DB_MASTER );
+ $dbw->begin();
+
+ # Unset ipb_anon_only for user blocks, makes no sense
+ if ( $this->mUser ) {
+ $this->mAnonOnly = 0;
+ }
+
+ # Don't collide with expired blocks
+ Block::purgeExpired();
+
$ipb_id = $dbw->nextSequenceValue('ipblocks_ipb_id_val');
$dbw->insert( 'ipblocks',
array(
@@ -332,13 +377,16 @@ class Block
'ipb_reason' => $this->mReason,
'ipb_timestamp' => $dbw->timestamp($this->mTimestamp),
'ipb_auto' => $this->mAuto,
- 'ipb_expiry' => $this->mExpiry ?
- $dbw->timestamp($this->mExpiry) :
- $this->mExpiry,
+ 'ipb_anon_only' => $this->mAnonOnly,
+ 'ipb_create_account' => $this->mCreateAccount,
+ 'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
'ipb_range_start' => $this->mRangeStart,
'ipb_range_end' => $this->mRangeEnd,
- ), 'Block::insert'
+ ), 'Block::insert', array( 'IGNORE' )
);
+ $affected = $dbw->affectedRows();
+ $dbw->commit();
+ return $affected;
}
function deleteIfExpired()
@@ -417,18 +465,48 @@ class Block
return wfSetVar( $this->mFromMaster, $x );
}
- /* static */ function getAutoblockExpiry( $timestamp )
+ function getRedactedName() {
+ if ( $this->mAuto ) {
+ return '#' . $this->mId;
+ } else {
+ return $this->mAddress;
+ }
+ }
+
+ /**
+ * Encode expiry for DB
+ */
+ static function encodeExpiry( $expiry, $db ) {
+ if ( $expiry == '' || $expiry == Block::infinity() ) {
+ return Block::infinity();
+ } else {
+ return $db->timestamp( $expiry );
+ }
+ }
+
+ /**
+ * Decode expiry which has come from the DB
+ */
+ static function decodeExpiry( $expiry ) {
+ if ( $expiry == '' || $expiry == Block::infinity() ) {
+ return Block::infinity();
+ } else {
+ return wfTimestamp( TS_MW, $expiry );
+ }
+ }
+
+ static function getAutoblockExpiry( $timestamp )
{
global $wgAutoblockExpiry;
return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
}
- /* static */ function normaliseRange( $range )
+ static function normaliseRange( $range )
{
$parts = explode( '/', $range );
if ( count( $parts ) == 2 ) {
$shift = 32 - $parts[1];
- $ipint = wfIP2Unsigned( $parts[0] );
+ $ipint = IP::toUnsigned( $parts[0] );
$ipint = $ipint >> $shift << $shift;
$newip = long2ip( $ipint );
$range = "$newip/{$parts[1]}";
@@ -436,5 +514,28 @@ class Block
return $range;
}
+ /**
+ * Purge expired blocks from the ipblocks table
+ */
+ static function purgeExpired() {
+ $dbw =& wfGetDB( DB_MASTER );
+ $dbw->delete( 'ipblocks', array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
+ }
+
+ static function infinity() {
+ # This is a special keyword for timestamps in PostgreSQL, and
+ # works with CHAR(14) as well because "i" sorts after all numbers.
+ return 'infinity';
+
+ /*
+ static $infinity;
+ if ( !isset( $infinity ) ) {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $infinity = $dbr->bigTimestamp();
+ }
+ return $infinity;
+ */
+ }
+
}
?>
diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php
index 53d69971..e55d2976 100644
--- a/includes/CategoryPage.php
+++ b/includes/CategoryPage.php
@@ -13,7 +13,6 @@ if( !defined( 'MEDIAWIKI' ) )
* @package MediaWiki
*/
class CategoryPage extends Article {
-
function view() {
if(!wfRunHooks('CategoryPageView', array(&$this))) return;
@@ -40,10 +39,27 @@ class CategoryPage extends Article {
global $wgOut, $wgRequest;
$from = $wgRequest->getVal( 'from' );
$until = $wgRequest->getVal( 'until' );
-
- $wgOut->addHTML( $this->doCategoryMagic( $from, $until ) );
+
+ $viewer = new CategoryViewer( $this->mTitle, $from, $until );
+ $wgOut->addHTML( $viewer->getHTML() );
}
+}
+class CategoryViewer {
+ var $title, $limit, $from, $until,
+ $articles, $articles_start_char,
+ $children, $children_start_char,
+ $showGallery, $gallery,
+ $skin;
+
+ function __construct( $title, $from = '', $until = '' ) {
+ global $wgCategoryPagingLimit;
+ $this->title = $title;
+ $this->from = $from;
+ $this->until = $until;
+ $this->limit = $wgCategoryPagingLimit;
+ }
+
/**
* Format the category data list.
*
@@ -52,130 +68,205 @@ class CategoryPage extends Article {
* @return string HTML output
* @private
*/
- function doCategoryMagic( $from = '', $until = '' ) {
- global $wgOut;
- global $wgContLang,$wgUser, $wgCategoryMagicGallery, $wgCategoryPagingLimit;
- $fname = 'CategoryPage::doCategoryMagic';
- wfProfileIn( $fname );
-
- $articles = array();
- $articles_start_char = array();
- $children = array();
- $children_start_char = array();
+ function getHTML() {
+ global $wgOut, $wgCategoryMagicGallery, $wgCategoryPagingLimit;
+ wfProfileIn( __METHOD__ );
+
+ $this->showGallery = $wgCategoryMagicGallery && !$wgOut->mNoGallery;
+
+ $this->clearCategoryState();
+ $this->doCategoryQuery();
+ $this->finaliseCategoryState();
+
+ $r = $this->getCategoryTop() .
+ $this->getSubcategorySection() .
+ $this->getPagesSection() .
+ $this->getImageSection() .
+ $this->getCategoryBottom();
+
+ wfProfileOut( __METHOD__ );
+ return $r;
+ }
+
+ function clearCategoryState() {
+ $this->articles = array();
+ $this->articles_start_char = array();
+ $this->children = array();
+ $this->children_start_char = array();
+ if( $this->showGallery ) {
+ $this->gallery = new ImageGallery();
+ $this->gallery->setParsing();
+ }
+ }
+
+ function getSkin() {
+ if ( !$this->skin ) {
+ global $wgUser;
+ $this->skin = $wgUser->getSkin();
+ }
+ return $this->skin;
+ }
+
+ /**
+ * Add a subcategory to the internal lists
+ */
+ function addSubcategory( $title, $sortkey, $pageLength ) {
+ global $wgContLang;
+ // Subcategory; strip the 'Category' namespace from the link text.
+ $this->children[] = $this->getSkin()->makeKnownLinkObj(
+ $title, $wgContLang->convertHtml( $title->getText() ) );
+
+ $this->children_start_char[] = $this->getSubcategorySortChar( $title, $sortkey );
+ }
+
+ /**
+ * Get the character to be used for sorting subcategories.
+ * If there's a link from Category:A to Category:B, the sortkey of the resulting
+ * entry in the categorylinks table is Category:A, not A, which it SHOULD be.
+ * Workaround: If sortkey == "Category:".$title, than use $title for sorting,
+ * else use sortkey...
+ */
+ function getSubcategorySortChar( $title, $sortkey ) {
+ global $wgContLang;
+
+ if( $title->getPrefixedText() == $sortkey ) {
+ $firstChar = $wgContLang->firstChar( $title->getDBkey() );
+ } else {
+ $firstChar = $wgContLang->firstChar( $sortkey );
+ }
- $showGallery = $wgCategoryMagicGallery && !$wgOut->mNoGallery;
- if( $showGallery ) {
- $ig = new ImageGallery();
- $ig->setParsing();
+ return $wgContLang->convert( $firstChar );
+ }
+
+ /**
+ * Add a page in the image namespace
+ */
+ function addImage( $title, $sortkey, $pageLength ) {
+ if ( $this->showGallery ) {
+ $image = new Image( $title );
+ if( $this->flip ) {
+ $this->gallery->insert( $image );
+ } else {
+ $this->gallery->add( $image );
+ }
+ } else {
+ $this->addPage( $title, $sortkey, $pageLength );
}
+ }
+ /**
+ * Add a miscellaneous page
+ */
+ function addPage( $title, $sortkey, $pageLength ) {
+ global $wgContLang;
+ $this->articles[] = $this->getSkin()->makeSizeLinkObj(
+ $pageLength, $title, $wgContLang->convert( $title->getPrefixedText() )
+ );
+ $this->articles_start_char[] = $wgContLang->convert( $wgContLang->firstChar( $sortkey ) );
+ }
+
+ function finaliseCategoryState() {
+ if( $this->flip ) {
+ $this->children = array_reverse( $this->children );
+ $this->children_start_char = array_reverse( $this->children_start_char );
+ $this->articles = array_reverse( $this->articles );
+ $this->articles_start_char = array_reverse( $this->articles_start_char );
+ }
+ }
+
+ function doCategoryQuery() {
$dbr =& wfGetDB( DB_SLAVE );
- if( $from != '' ) {
- $pageCondition = 'cl_sortkey >= ' . $dbr->addQuotes( $from );
- $flip = false;
- } elseif( $until != '' ) {
- $pageCondition = 'cl_sortkey < ' . $dbr->addQuotes( $until );
- $flip = true;
+ if( $this->from != '' ) {
+ $pageCondition = 'cl_sortkey >= ' . $dbr->addQuotes( $this->from );
+ $this->flip = false;
+ } elseif( $this->until != '' ) {
+ $pageCondition = 'cl_sortkey < ' . $dbr->addQuotes( $this->until );
+ $this->flip = true;
} else {
$pageCondition = '1 = 1';
- $flip = false;
+ $this->flip = false;
}
- $limit = $wgCategoryPagingLimit;
$res = $dbr->select(
array( 'page', 'categorylinks' ),
array( 'page_title', 'page_namespace', 'page_len', 'cl_sortkey' ),
array( $pageCondition,
'cl_from = page_id',
- 'cl_to' => $this->mTitle->getDBKey()),
+ 'cl_to' => $this->title->getDBKey()),
#'page_is_redirect' => 0),
#+ $pageCondition,
- $fname,
- array( 'ORDER BY' => $flip ? 'cl_sortkey DESC' : 'cl_sortkey',
- 'LIMIT' => $limit + 1 ) );
+ __METHOD__,
+ array( 'ORDER BY' => $this->flip ? 'cl_sortkey DESC' : 'cl_sortkey',
+ 'LIMIT' => $this->limit + 1 ) );
- $sk =& $wgUser->getSkin();
- $r = "<br style=\"clear:both;\"/>\n";
$count = 0;
- $nextPage = null;
+ $this->nextPage = null;
while( $x = $dbr->fetchObject ( $res ) ) {
- if( ++$count > $limit ) {
+ if( ++$count > $this->limit ) {
// We've reached the one extra which shows that there are
// additional pages to be had. Stop here...
- $nextPage = $x->cl_sortkey;
+ $this->nextPage = $x->cl_sortkey;
break;
}
$title = Title::makeTitle( $x->page_namespace, $x->page_title );
if( $title->getNamespace() == NS_CATEGORY ) {
- // Subcategory; strip the 'Category' namespace from the link text.
- array_push( $children, $sk->makeKnownLinkObj( $title, $wgContLang->convertHtml( $title->getText() ) ) );
-
- // If there's a link from Category:A to Category:B, the sortkey of the resulting
- // entry in the categorylinks table is Category:A, not A, which it SHOULD be.
- // Workaround: If sortkey == "Category:".$title, than use $title for sorting,
- // else use sortkey...
- $sortkey='';
- if( $title->getPrefixedText() == $x->cl_sortkey ) {
- $sortkey=$wgContLang->firstChar( $x->page_title );
- } else {
- $sortkey=$wgContLang->firstChar( $x->cl_sortkey );
- }
- array_push( $children_start_char, $wgContLang->convert( $sortkey ) ) ;
- } elseif( $showGallery && $title->getNamespace() == NS_IMAGE ) {
- // Show thumbnails of categorized images, in a separate chunk
- if( $flip ) {
- $ig->insert( Image::newFromTitle( $title ) );
- } else {
- $ig->add( Image::newFromTitle( $title ) );
- }
+ $this->addSubcategory( $title, $x->cl_sortkey, $x->page_len );
+ } elseif( $title->getNamespace() == NS_IMAGE ) {
+ $this->addImage( $title, $x->cl_sortkey, $x->page_len );
} else {
- // Page in this category
- array_push( $articles, $sk->makeSizeLinkObj( $x->page_len, $title, $wgContLang->convert( $title->getPrefixedText() ) ) ) ;
- array_push( $articles_start_char, $wgContLang->convert( $wgContLang->firstChar( $x->cl_sortkey ) ) );
+ $this->addPage( $title, $x->cl_sortkey, $x->page_len );
}
}
$dbr->freeResult( $res );
+ }
- if( $flip ) {
- $children = array_reverse( $children );
- $children_start_char = array_reverse( $children_start_char );
- $articles = array_reverse( $articles );
- $articles_start_char = array_reverse( $articles_start_char );
- }
-
- if( $until != '' ) {
- $r .= $this->pagingLinks( $this->mTitle, $nextPage, $until, $limit );
- } elseif( $nextPage != '' || $from != '' ) {
- $r .= $this->pagingLinks( $this->mTitle, $from, $nextPage, $limit );
+ function getCategoryTop() {
+ $r = "<br style=\"clear:both;\"/>\n";
+ if( $this->until != '' ) {
+ $r .= $this->pagingLinks( $this->title, $this->nextPage, $this->until, $this->limit );
+ } elseif( $this->nextPage != '' || $this->from != '' ) {
+ $r .= $this->pagingLinks( $this->title, $this->from, $this->nextPage, $this->limit );
}
+ return $r;
+ }
+ function getSubcategorySection() {
# Don't show subcategories section if there are none.
- if( count( $children ) > 0 ) {
+ $r = '';
+ if( count( $this->children ) > 0 ) {
# Showing subcategories
$r .= '<h2>' . wfMsg( 'subcategories' ) . "</h2>\n";
- $r .= wfMsgExt( 'subcategorycount', array( 'parse' ), count( $children) );
- $r .= $this->formatList( $children, $children_start_char );
+ $r .= wfMsgExt( 'subcategorycount', array( 'parse' ), count( $this->children) );
+ $r .= $this->formatList( $this->children, $this->children_start_char );
}
+ return $r;
+ }
- # Showing articles in this category
- $ti = htmlspecialchars( $this->mTitle->getText() );
- $r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n";
- $r .= wfMsgExt( 'categoryarticlecount', array( 'parse' ), count( $articles) );
- $r .= $this->formatList( $articles, $articles_start_char );
+ function getPagesSection() {
+ $ti = htmlspecialchars( $this->title->getText() );
+ $r = '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n";
+ $r .= wfMsgExt( 'categoryarticlecount', array( 'parse' ), count( $this->articles) );
+ $r .= $this->formatList( $this->articles, $this->articles_start_char );
+ return $r;
+ }
- if( $showGallery && ! $ig->isEmpty() ) {
- $r.= $ig->toHTML();
+ function getImageSection() {
+ if( $this->showGallery && ! $this->gallery->isEmpty() ) {
+ return $this->gallery->toHTML();
+ } else {
+ return '';
}
+ }
- if( $until != '' ) {
- $r .= $this->pagingLinks( $this->mTitle, $nextPage, $until, $limit );
- } elseif( $nextPage != '' || $from != '' ) {
- $r .= $this->pagingLinks( $this->mTitle, $from, $nextPage, $limit );
+ function getCategoryBottom() {
+ if( $this->until != '' ) {
+ return $this->pagingLinks( $this->title, $this->nextPage, $this->until, $this->limit );
+ } elseif( $this->nextPage != '' || $this->from != '' ) {
+ return $this->pagingLinks( $this->title, $this->from, $this->nextPage, $this->limit );
+ } else {
+ return '';
}
-
- wfProfileOut( $fname );
- return $r;
}
/**
@@ -293,7 +384,7 @@ class CategoryPage extends Article {
*/
function pagingLinks( $title, $first, $last, $limit, $query = array() ) {
global $wgUser, $wgLang;
- $sk =& $wgUser->getSkin();
+ $sk =& $this->getSkin();
$limitText = $wgLang->formatNum( $limit );
$prevLink = htmlspecialchars( wfMsg( 'prevn', $limitText ) );
diff --git a/includes/ChangesList.php b/includes/ChangesList.php
index b2c1abe2..6797bb41 100644
--- a/includes/ChangesList.php
+++ b/includes/ChangesList.php
@@ -394,6 +394,7 @@ class EnhancedChangesList extends ChangesList {
* Enhanced RC group
*/
function recentChangesBlockGroup( $block ) {
+ global $wgContLang;
$r = '';
# Collate list of users
@@ -423,6 +424,7 @@ class EnhancedChangesList extends ChangesList {
$users = array();
foreach( $userlinks as $userlink => $count) {
$text = $userlink;
+ $text .= $wgContLang->getDirMark();
if( $count > 1 ) {
$text .= ' ('.$count.'&times;)';
}
@@ -450,6 +452,7 @@ class EnhancedChangesList extends ChangesList {
# Article link
$r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
+ $r .= $wgContLang->getDirMark();
$curIdEq = 'curid=' . $block[0]->mAttribs['rc_cur_id'];
$currentRevision = $block[0]->mAttribs['rc_this_oldid'];
diff --git a/includes/CoreParserFunctions.php b/includes/CoreParserFunctions.php
index d6578abf..2081b3f2 100644
--- a/includes/CoreParserFunctions.php
+++ b/includes/CoreParserFunctions.php
@@ -5,6 +5,15 @@
*/
class CoreParserFunctions {
+ static function intFunction( $parser, $part1 = '' /*, ... */ ) {
+ if ( strval( $part1 ) !== '' ) {
+ $args = array_slice( func_get_args(), 2 );
+ return wfMsgReal( $part1, $args, true );
+ } else {
+ return array( 'found' => false );
+ }
+ }
+
static function ns( $parser, $part1 = '' ) {
global $wgContLang;
$found = false;
@@ -106,7 +115,7 @@ class CoreParserFunctions {
function isRaw( $param ) {
static $mwRaw;
if ( !$mwRaw ) {
- $mwRaw =& MagicWord::get( MAG_RAWSUFFIX );
+ $mwRaw =& MagicWord::get( 'rawsuffix' );
}
if ( is_null( $param ) ) {
return false;
@@ -145,6 +154,27 @@ class CoreParserFunctions {
$lang = $wgContLang->getLanguageName( strtolower( $arg ) );
return $lang != '' ? $lang : $arg;
}
+
+ function pad( $string = '', $length = 0, $char = 0, $direction = STR_PAD_RIGHT ) {
+ $length = min( max( $length, 0 ), 500 );
+ $char = substr( $char, 0, 1 );
+ return ( $string && (int)$length > 0 && strlen( trim( (string)$char ) ) > 0 )
+ ? str_pad( $string, $length, (string)$char, $direction )
+ : $string;
+ }
+
+ function padleft( $parser, $string = '', $length = 0, $char = 0 ) {
+ return self::pad( $string, $length, $char, STR_PAD_LEFT );
+ }
+
+ function padright( $parser, $string = '', $length = 0, $char = 0 ) {
+ return self::pad( $string, $length, $char );
+ }
+
+ function anchorencode( $parser, $text ) {
+ return str_replace( '%', '.', str_replace('+', '_', urlencode( $text ) ) );
+ }
+
}
?>
diff --git a/includes/Credits.php b/includes/Credits.php
index ff33de74..62f0b256 100644
--- a/includes/Credits.php
+++ b/includes/Credits.php
@@ -87,11 +87,13 @@ function getAuthorCredits($article) {
$timestamp = $article->getTimestamp();
if ($timestamp) {
- $d = $wgLang->timeanddate($article->getTimestamp(), true);
+ $d = $wgLang->date($article->getTimestamp(), true);
+ $t = $wgLang->time($article->getTimestamp(), true);
} else {
$d = '';
+ $t = '';
}
- return wfMsg('lastmodifiedby', $d, $author_credit);
+ return wfMsg('lastmodifiedatby', $d, $t, $author_credit);
}
/**
diff --git a/includes/Database.php b/includes/Database.php
index f8e579b4..53e59968 100644
--- a/includes/Database.php
+++ b/includes/Database.php
@@ -5,13 +5,6 @@
* @package MediaWiki
*/
-/** See Database::makeList() */
-define( 'LIST_COMMA', 0 );
-define( 'LIST_AND', 1 );
-define( 'LIST_SET', 2 );
-define( 'LIST_NAMES', 3);
-define( 'LIST_OR', 4);
-
/** Number of times to re-try an operation in case of deadlock */
define( 'DEADLOCK_TRIES', 4 );
/** Minimum time to wait before retry, in microseconds */
@@ -86,6 +79,11 @@ class DBConnectionError extends DBError {
return $this->getMessage() . "\n";
}
+ function getLogMessage() {
+ # Don't send to the exception log
+ return false;
+ }
+
function getPageTitle() {
global $wgSitename;
return "$wgSitename has a problem";
@@ -205,6 +203,11 @@ class DBQueryError extends DBError {
}
}
+ function getLogMessage() {
+ # Don't send to the exception log
+ return false;
+ }
+
function getPageTitle() {
return $this->msg( 'databaseerror', 'Database error' );
}
@@ -244,6 +247,9 @@ class Database {
protected $mTrxLevel = 0;
protected $mErrorCount = 0;
protected $mLBInfo = array();
+ protected $mCascadingDeletes = false;
+ protected $mCleanupTriggers = false;
+ protected $mStrictIPs = false;
#------------------------------------------------------------------------------
# Accessors
@@ -334,6 +340,28 @@ class Database {
}
}
+ /**
+ * Returns true if this database supports (and uses) cascading deletes
+ */
+ function cascadingDeletes() {
+ return $this->mCascadingDeletes;
+ }
+
+ /**
+ * Returns true if this database supports (and uses) triggers (e.g. on the page table)
+ */
+ function cleanupTriggers() {
+ return $this->mCleanupTriggers;
+ }
+
+ /**
+ * Returns true if this database is strict about what can be put into an IP field.
+ * Specifically, it uses a NULL value instead of an empty string.
+ */
+ function strictIPs() {
+ return $this->mStrictIPs;
+ }
+
/**#@+
* Get function
*/
@@ -433,6 +461,7 @@ class Database {
*/
function open( $server, $user, $password, $dbName ) {
global $wguname;
+ wfProfileIn( __METHOD__ );
# Test for missing mysql.so
# First try to load it
@@ -454,12 +483,28 @@ class Database {
$success = false;
- if ( $this->mFlags & DBO_PERSISTENT ) {
- @/**/$this->mConn = mysql_pconnect( $server, $user, $password );
- } else {
- # Create a new connection...
- @/**/$this->mConn = mysql_connect( $server, $user, $password, true );
+ wfProfileIn("dbconnect-$server");
+
+ # LIVE PATCH by Tim, ask Domas for why: retry loop
+ $this->mConn = false;
+ $max = 3;
+ for ( $i = 0; $i < $max && !$this->mConn; $i++ ) {
+ if ( $i > 1 ) {
+ usleep( 1000 );
+ }
+ if ( $this->mFlags & DBO_PERSISTENT ) {
+ @/**/$this->mConn = mysql_pconnect( $server, $user, $password );
+ } else {
+ # Create a new connection...
+ @/**/$this->mConn = mysql_connect( $server, $user, $password, true );
+ }
+ if ($this->mConn === false) {
+ $iplus = $i + 1;
+ wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
+ }
}
+
+ wfProfileOut("dbconnect-$server");
if ( $dbName != '' ) {
if ( $this->mConn !== false ) {
@@ -467,6 +512,7 @@ class Database {
if ( !$success ) {
$error = "Error selecting database $dbName on server {$this->mServer} " .
"from client host {$wguname['nodename']}\n";
+ wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n");
wfDebug( $error );
}
} else {
@@ -480,18 +526,19 @@ class Database {
$success = (bool)$this->mConn;
}
- if ( !$success ) {
+ if ( $success ) {
+ global $wgDBmysql5;
+ if( $wgDBmysql5 ) {
+ // Tell the server we're communicating with it in UTF-8.
+ // This may engage various charset conversions.
+ $this->query( 'SET NAMES utf8' );
+ }
+ } else {
$this->reportConnectionError();
}
- global $wgDBmysql5;
- if( $wgDBmysql5 ) {
- // Tell the server we're communicating with it in UTF-8.
- // This may engage various charset conversions.
- $this->query( 'SET NAMES utf8' );
- }
-
$this->mOpened = $success;
+ wfProfileOut( __METHOD__ );
return $success;
}
/**@}}*/
@@ -760,8 +807,8 @@ class Database {
*/
function fetchObject( $res ) {
@/**/$row = mysql_fetch_object( $res );
- if( mysql_errno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( mysql_error() ) );
+ if( $this->lastErrno() ) {
+ throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
}
return $row;
}
@@ -772,8 +819,8 @@ class Database {
*/
function fetchRow( $res ) {
@/**/$row = mysql_fetch_array( $res );
- if (mysql_errno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( mysql_error() ) );
+ if ( $this->lastErrno() ) {
+ throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
}
return $row;
}
@@ -783,8 +830,8 @@ class Database {
*/
function numRows( $res ) {
@/**/$n = mysql_num_rows( $res );
- if( mysql_errno() ) {
- throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( mysql_error() ) );
+ if( $this->lastErrno() ) {
+ throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
}
return $n;
}
@@ -1865,7 +1912,7 @@ class Database {
function sourceFile( $filename ) {
$fp = fopen( $filename, 'r' );
if ( false === $fp ) {
- return "Could not open \"{$fname}\".\n";
+ return "Could not open \"{$filename}\".\n";
}
$cmd = "";
diff --git a/includes/DatabaseOracle.php b/includes/DatabaseOracle.php
index d5d7379d..aa1e329e 100644
--- a/includes/DatabaseOracle.php
+++ b/includes/DatabaseOracle.php
@@ -6,11 +6,6 @@
* @package MediaWiki
*/
-/**
- * Depends on database
- */
-require_once( 'Database.php' );
-
class OracleBlob extends DBObject {
function isLOB() {
return true;
diff --git a/includes/DatabasePostgres.php b/includes/DatabasePostgres.php
index 5897386f..a5e02e77 100644
--- a/includes/DatabasePostgres.php
+++ b/includes/DatabasePostgres.php
@@ -1,7 +1,7 @@
<?php
/**
- * This is PostgreSQL database abstraction layer.
+ * This is Postgres database abstraction layer.
*
* As it includes more generic version for DB functions,
* than MySQL ones, some of them should be moved to parent
@@ -10,11 +10,6 @@
* @package MediaWiki
*/
-/**
- * Depends on database
- */
-require_once( 'Database.php' );
-
class DatabasePostgres extends Database {
var $mInsertId = NULL;
var $mLastResult = NULL;
@@ -30,8 +25,10 @@ class DatabasePostgres extends Database {
}
$this->mOut =& $wgOut;
$this->mFailFunction = $failFunction;
+ $this->mCascadingDeletes = true;
+ $this->mCleanupTriggers = true;
+ $this->mStrictIPs = true;
$this->mFlags = $flags;
-
$this->open( $server, $user, $password, $dbName);
}
@@ -47,11 +44,12 @@ class DatabasePostgres extends Database {
* If the failFunction is set to a non-zero integer, returns success
*/
function open( $server, $user, $password, $dbName ) {
- # Test for PostgreSQL support, to avoid suppressed fatal error
+ # Test for Postgres support, to avoid suppressed fatal error
if ( !function_exists( 'pg_connect' ) ) {
- throw new DBConnectionError( $this, "PostgreSQL functions missing, have you compiled PHP with the --with-pgsql option?\n" );
+ throw new DBConnectionError( $this, "Postgres functions missing, have you compiled PHP with the --with-pgsql option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
}
+
global $wgDBport;
$this->close();
@@ -62,7 +60,6 @@ class DatabasePostgres extends Database {
$this->mDBname = $dbName;
$success = false;
-
$hstring="";
if ($server!=false && $server!="") {
$hstring="host=$server ";
@@ -71,8 +68,11 @@ class DatabasePostgres extends Database {
$hstring .= "port=$port ";
}
- error_reporting( E_ALL );
+ if (!strlen($user)) { ## e.g. the class is being loaded
+ return;
+ }
+ error_reporting( E_ALL );
@$this->mConn = pg_connect("$hstring dbname=$dbName user=$user password=$password");
if ( $this->mConn == false ) {
@@ -83,12 +83,153 @@ class DatabasePostgres extends Database {
}
$this->mOpened = true;
- ## If this is the initial connection, setup the schema stuff
- if (defined('MEDIAWIKI_INSTALL') and !defined('POSTGRES_SEARCHPATH')) {
- global $wgDBmwschema, $wgDBts2schema, $wgDBname;
+ ## If this is the initial connection, setup the schema stuff and possibly create the user
+ if (defined('MEDIAWIKI_INSTALL')) {
+ global $wgDBname, $wgDBuser, $wgDBpass, $wgDBsuperuser, $wgDBmwschema,
+ $wgDBts2schema, $wgDBts2locale;
+ print "OK</li>\n";
+
+ print "<li>Checking the version of Postgres...";
+ $version = pg_fetch_result($this->doQuery("SELECT version()"),0,0);
+ if (!preg_match("/PostgreSQL (\d+\.\d+)(\S+)/", $version, $thisver)) {
+ print "<b>FAILED</b> (could not determine the version)</li>\n";
+ dieout("</ul>");
+ }
+ $PGMINVER = "8.1";
+ if ($thisver[1] < $PGMINVER) {
+ print "<b>FAILED</b>. Required version is $PGMINVER. You have $thisver[1]$thisver[2]</li>\n";
+ dieout("</ul>");
+ }
+ print "version $thisver[1]$thisver[2] is OK.</li>\n";
+
+ $safeuser = $this->quote_ident($wgDBuser);
+ ## Are we connecting as a superuser for the first time?
+ if ($wgDBsuperuser) {
+ ## Are we really a superuser? Check out our rights
+ $SQL = "SELECT
+ CASE WHEN usesuper IS TRUE THEN
+ CASE WHEN usecreatedb IS TRUE THEN 3 ELSE 1 END
+ ELSE CASE WHEN usecreatedb IS TRUE THEN 2 ELSE 0 END
+ END AS rights
+ FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBsuperuser);
+ $rows = $this->numRows($res = $this->doQuery($SQL));
+ if (!$rows) {
+ print "<li>ERROR: Could not read permissions for user \"$wgDBsuperuser\"</li>\n";
+ dieout('</ul>');
+ }
+ $perms = pg_fetch_result($res, 0, 0);
+
+ $SQL = "SELECT 1 FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBuser);
+ $rows = $this->numRows($this->doQuery($SQL));
+ if ($rows) {
+ print "<li>User \"$wgDBuser\" already exists, skipping account creation.</li>";
+ }
+ else {
+ if ($perms != 1 and $perms != 3) {
+ print "<li>ERROR: the user \"$wgDBsuperuser\" cannot create other users. ";
+ print 'Please use a different Postgres user.</li>';
+ dieout('</ul>');
+ }
+ print "<li>Creating user <b>$wgDBuser</b>...";
+ $safepass = $this->addQuotes($wgDBpass);
+ $SQL = "CREATE USER $safeuser NOCREATEDB PASSWORD $safepass";
+ $this->doQuery($SQL);
+ print "OK</li>\n";
+ }
+ ## User now exists, check out the database
+ if ($dbName != $wgDBname) {
+ $SQL = "SELECT 1 FROM pg_catalog.pg_database WHERE datname = " . $this->addQuotes($wgDBname);
+ $rows = $this->numRows($this->doQuery($SQL));
+ if ($rows) {
+ print "<li>Database \"$wgDBname\" already exists, skipping database creation.</li>";
+ }
+ else {
+ if ($perms < 2) {
+ print "<li>ERROR: the user \"$wgDBsuperuser\" cannot create databases. ";
+ print 'Please use a different Postgres user.</li>';
+ dieout('</ul>');
+ }
+ print "<li>Creating database <b>$wgDBname</b>...";
+ $safename = $this->quote_ident($wgDBname);
+ $SQL = "CREATE DATABASE $safename OWNER $safeuser ";
+ $this->doQuery($SQL);
+ print "OK</li>\n";
+ ## Hopefully tsearch2 and plpgsql are in template1...
+ }
+
+ ## Reconnect to check out tsearch2 rights for this user
+ print "<li>Connecting to \"$wgDBname\" as superuser \"$wgDBsuperuser\" to check rights...";
+ @$this->mConn = pg_connect("$hstring dbname=$wgDBname user=$user password=$password");
+ if ( $this->mConn == false ) {
+ print "<b>FAILED TO CONNECT!</b></li>";
+ dieout("</ul>");
+ }
+ print "OK</li>\n";
+ }
+
+ ## Tsearch2 checks
+ print "<li>Checking that tsearch2 is installed in the database \"$wgDBname\"...";
+ if (! $this->tableExists("pg_ts_cfg", $wgDBts2schema)) {
+ print "<b>FAILED</b>. tsearch2 must be installed in the database \"$wgDBname\".";
+ print "Please see <a href='http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a>";
+ print " for instructions or ask on #postgresql on irc.freenode.net</li>\n";
+ dieout("</ul>");
+ }
+ print "OK</li>\n";
+ print "<li>Ensuring that user \"$wgDBuser\" has select rights on the tsearch2 tables...";
+ foreach (array('cfg','cfgmap','dict','parser') as $table) {
+ $SQL = "GRANT SELECT ON pg_ts_$table TO $safeuser";
+ $this->doQuery($SQL);
+ }
+ print "OK</li>\n";
+
+
+ ## Setup the schema for this user if needed
+ $result = $this->schemaExists($wgDBmwschema);
+ $safeschema = $this->quote_ident($wgDBmwschema);
+ if (!$result) {
+ print "<li>Creating schema <b>$wgDBmwschema</b> ...";
+ $result = $this->doQuery("CREATE SCHEMA $safeschema AUTHORIZATION $safeuser");
+ if (!$result) {
+ print "<b>FAILED</b>.</li>\n";
+ dieout("</ul>");
+ }
+ print "OK</li>\n";
+ }
+ else {
+ print "<li>Schema already exists, explicitly granting rights...\n";
+ $safeschema2 = $this->addQuotes($wgDBmwschema);
+ $SQL = "SELECT 'GRANT ALL ON '||pg_catalog.quote_ident(relname)||' TO $safeuser;'\n".
+ "FROM pg_catalog.pg_class p, pg_catalog.pg_namespace n\n".
+ "WHERE relnamespace = n.oid AND n.nspname = $safeschema2\n".
+ "AND p.relkind IN ('r','S','v')\n";
+ $SQL .= "UNION\n";
+ $SQL .= "SELECT 'GRANT ALL ON FUNCTION '||pg_catalog.quote_ident(proname)||'('||\n".
+ "pg_catalog.oidvectortypes(p.proargtypes)||') TO $safeuser;'\n".
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n\n".
+ "WHERE p.pronamespace = n.oid AND n.nspname = $safeschema2";
+ $res = $this->doQuery($SQL);
+ if (!$res) {
+ print "<b>FAILED</b>. Could not set rights for the user.</li>\n";
+ dieout("</ul>");
+ }
+ $this->doQuery("SET search_path = $safeschema");
+ $rows = $this->numRows($res);
+ while ($rows) {
+ $rows--;
+ $this->doQuery(pg_fetch_result($res, $rows, 0));
+ }
+ print "OK</li>";
+ }
+
+ $wgDBsuperuser = '';
+ return true; ## Reconnect as regular user
+ }
+
+ if (!defined('POSTGRES_SEARCHPATH')) {
## Do we have the basic tsearch2 table?
- print "<li>Checking for tsearch2 ...";
+ print "<li>Checking for tsearch2 in the schema \"$wgDBts2schema\"...";
if (! $this->tableExists("pg_ts_dict", $wgDBts2schema)) {
print "<b>FAILED</b>. Make sure tsearch2 is installed. See <a href=";
print "'http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a>";
@@ -97,15 +238,78 @@ class DatabasePostgres extends Database {
}
print "OK</li>\n";
+ ## Does this user have the rights to the tsearch2 tables?
+ $ctype = pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0);
+ print "<li>Checking tsearch2 permissions...";
+ $SQL = "SELECT ts_name FROM $wgDBts2schema.pg_ts_cfg WHERE locale = '$ctype'";
+ $SQL .= " ORDER BY CASE WHEN ts_name <> 'default' THEN 1 ELSE 0 END";
+ error_reporting( 0 );
+ $res = $this->doQuery($SQL);
+ error_reporting( E_ALL );
+ if (!$res) {
+ print "<b>FAILED</b>. Make sure that the user \"$wgDBuser\" has SELECT access to the tsearch2 tables</li>\n";
+ dieout("</ul>");
+ }
+ print "OK</li>";
+
+ ## Will the current locale work? Can we force it to?
+ print "<li>Verifying tsearch2 locale with $ctype...";
+ $rows = $this->numRows($res);
+ $resetlocale = 0;
+ if (!$rows) {
+ print "<b>not found</b></li>\n";
+ print "<li>Attempting to set default tsearch2 locale to \"$ctype\"...";
+ $resetlocale = 1;
+ }
+ else {
+ $tsname = pg_fetch_result($res, 0, 0);
+ if ($tsname != 'default') {
+ print "<b>not set to default ($tsname)</b>";
+ print "<li>Attempting to change tsearch2 default locale to \"$ctype\"...";
+ $resetlocale = 1;
+ }
+ }
+ if ($resetlocale) {
+ $SQL = "UPDATE $wgDBts2schema.pg_ts_cfg SET locale = '$ctype' WHERE ts_name = 'default'";
+ $res = $this->doQuery($SQL);
+ if (!$res) {
+ print "<b>FAILED</b>. ";
+ print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"ctype\"</li>\n";
+ dieout("</ul>");
+ }
+ print "OK</li>";
+ }
+
+ ## Final test: try out a simple tsearch2 query
+ $SQL = "SELECT $wgDBts2schema.to_tsvector('default','MediaWiki tsearch2 testing')";
+ $res = $this->doQuery($SQL);
+ if (!$res) {
+ print "<b>FAILED</b>. Specifically, \"$SQL\" did not work.</li>";
+ dieout("</ul>");
+ }
+ print "OK</li>";
+
## Do we have plpgsql installed?
- print "<li>Checking for plpgsql ...";
+ print "<li>Checking for Pl/Pgsql ...";
$SQL = "SELECT 1 FROM pg_catalog.pg_language WHERE lanname = 'plpgsql'";
- $res = $this->doQuery($SQL);
$rows = $this->numRows($this->doQuery($SQL));
if ($rows < 1) {
- print "<b>FAILED</b>. Make sure the language plpgsql is installed for the database <tt>$wgDBname</tt>t</li>";
- ## XXX Better help
- dieout("</ul>");
+ // plpgsql is not installed, but if we have a pg_pltemplate table, we should be able to create it
+ print "not installed. Attempting to install Pl/Pgsql ...";
+ $SQL = "SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) ".
+ "WHERE relname = 'pg_pltemplate' AND nspname='pg_catalog'";
+ $rows = $this->numRows($this->doQuery($SQL));
+ if ($rows >= 1) {
+ $result = $this->doQuery("CREATE LANGUAGE plpgsql");
+ if (!$result) {
+ print "<b>FAILED</b>. You need to install the language plpgsql in the database <tt>$wgDBname</tt></li>";
+ dieout("</ul>");
+ }
+ }
+ else {
+ print "<b>FAILED</b>. You need to install the language plpgsql in the database <tt>$wgDBname</tt></li>";
+ dieout("</ul>");
+ }
}
print "OK</li>\n";
@@ -115,40 +319,46 @@ class DatabasePostgres extends Database {
print "<li>Creating schema <b>$wgDBmwschema</b> ...";
$result = $this->doQuery("CREATE SCHEMA $wgDBmwschema");
if (!$result) {
- print "FAILED.</li>\n";
- return false;
+ print "<b>FAILED</b>.</li>\n";
+ dieout("</ul>");
}
- print "ok</li>\n";
+ print "OK</li>\n";
}
else if ($result != $user) {
- print "<li>Schema <b>$wgDBmwschema</b> exists but is not owned by <b>$user</b>. Not ideal.</li>\n";
+ print "<li>Schema \"$wgDBmwschema\" exists but is not owned by \"$user\". Not ideal.</li>\n";
}
else {
- print "<li>Schema <b>$wgDBmwschema</b> exists and is owned by <b>$user ($result)</b>. Excellent.</li>\n";
+ print "<li>Schema \"$wgDBmwschema\" exists and is owned by \"$user\". Excellent.</li>\n";
}
## Fix up the search paths if needed
- print "<li>Setting the search path for user <b>$user</b> ...";
- $path = "$wgDBmwschema";
+ print "<li>Setting the search path for user \"$user\" ...";
+ $path = $this->quote_ident($wgDBmwschema);
if ($wgDBts2schema !== $wgDBmwschema)
- $path .= ", $wgDBts2schema";
+ $path .= ", ". $this->quote_ident($wgDBts2schema);
if ($wgDBmwschema !== 'public' and $wgDBts2schema !== 'public')
$path .= ", public";
- $SQL = "ALTER USER $user SET search_path = $path";
+ $SQL = "ALTER USER $safeuser SET search_path = $path";
$result = pg_query($this->mConn, $SQL);
if (!$result) {
- print "FAILED.</li>\n";
- return false;
+ print "<b>FAILED</b>.</li>\n";
+ dieout("</ul>");
}
- print "ok</li>\n";
+ print "OK</li>\n";
## Set for the rest of this session
$SQL = "SET search_path = $path";
$result = pg_query($this->mConn, $SQL);
if (!$result) {
print "<li>Failed to set search_path</li>\n";
- return false;
+ dieout("</ul>");
}
define( "POSTGRES_SEARCHPATH", $path );
+ }}
+
+ global $wgCommandLineMode;
+ ## If called from the command-line (e.g. importDump), only show errors
+ if ($wgCommandLineMode) {
+ $this->doQuery("SET client_min_messages = 'ERROR'");
}
return $this->mConn;
@@ -177,7 +387,7 @@ class DatabasePostgres extends Database {
function freeResult( $res ) {
if ( !@pg_free_result( $res ) ) {
- throw new DBUnexpectedError($this, "Unable to free PostgreSQL result\n" );
+ throw new DBUnexpectedError($this, "Unable to free Postgres result\n" );
}
}
@@ -228,7 +438,9 @@ class DatabasePostgres extends Database {
return "No database connection";
}
}
- function lastErrno() { return 1; }
+ function lastErrno() {
+ return pg_last_error() ? 1 : 0;
+ }
function affectedRows() {
return pg_affected_rows( $this->mLastResult );
@@ -266,7 +478,7 @@ class DatabasePostgres extends Database {
}
function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
- # PostgreSQL doesn't support options
+ # Postgres doesn't support options
# We have a go at faking one of them
# TODO: DELAYED, LOW_PRIORITY
@@ -295,16 +507,12 @@ class DatabasePostgres extends Database {
}
function tableName( $name ) {
- # Replace backticks into double quotes
- $name = strtr($name,'`','"');
-
- # Now quote PG reserved keywords
+ # Replace reserved words with better ones
switch( $name ) {
case 'user':
- case 'old':
- case 'group':
- return '"' . $name . '"';
-
+ return 'mwuser';
+ case 'text':
+ return 'pagecontent';
default:
return $name;
}
@@ -323,15 +531,14 @@ class DatabasePostgres extends Database {
}
/**
- * USE INDEX clause
- * PostgreSQL doesn't have them and returns ""
+ * Postgres does not have a "USE INDEX" clause, so return an empty string
*/
function useIndexClause( $index ) {
return '';
}
# REPLACE query wrapper
- # PostgreSQL simulates this with a DELETE followed by INSERT
+ # Postgres simulates this with a DELETE followed by INSERT
# $row is the row to insert, an associative array
# $uniqueIndexes is an array of indexes. Each element may be either a
# field name or an array of field names
@@ -433,7 +640,7 @@ class DatabasePostgres extends Database {
/**
* Returns an SQL expression for a simple conditional.
- * Uses CASE on PostgreSQL.
+ * Uses CASE on Postgres
*
* @param string $cond SQL expression which will result in a boolean value
* @param string $trueVal SQL expression to return if true
@@ -449,9 +656,8 @@ class DatabasePostgres extends Database {
return false;
}
- # Return DB-style timestamp used for MySQL schema
function timestamp( $ts=0 ) {
- return wfTimestamp(TS_DB,$ts);
+ return wfTimestamp(TS_POSTGRES,$ts);
}
/**
@@ -499,7 +705,8 @@ class DatabasePostgres extends Database {
$etable = preg_replace("/'/", "''", $table);
$eschema = preg_replace("/'/", "''", $schema);
$SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
- . "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema'";
+ . "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema' "
+ . "AND c.relkind IN ('r','v')";
$res = $this->query( $SQL );
$count = $res ? pg_num_rows($res) : 0;
if ($res)
@@ -563,9 +770,28 @@ class DatabasePostgres extends Database {
return $sql;
}
- function update_interwiki() {
+ function setup_database() {
+ global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport;
+
+ dbsource( "../maintenance/postgres/tables.sql", $this);
+
+ ## Update version information
+ $mwv = $this->addQuotes($wgVersion);
+ $pgv = $this->addQuotes($this->getServerVersion());
+ $pgu = $this->addQuotes($this->mUser);
+ $mws = $this->addQuotes($wgDBmwschema);
+ $tss = $this->addQuotes($wgDBts2schema);
+ $pgp = $this->addQuotes($wgDBport);
+ $dbn = $this->addQuotes($this->mDBname);
+ $ctype = pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0);
+
+ $SQL = "UPDATE mediawiki_version SET mw_version=$mwv, pg_version=$pgv, pg_user=$pgu, ".
+ "mw_schema = $mws, ts2_schema = $tss, pg_port=$pgp, pg_dbname=$dbn, ".
+ "ctype = '$ctype' ".
+ "WHERE type = 'Creation'";
+ $this->query($SQL);
+
## Avoid the non-standard "REPLACE INTO" syntax
- ## Called by config/index.php
$f = fopen( "../maintenance/interwiki.sql", 'r' );
if ($f == false ) {
dieout( "<li>Could not find the interwiki.sql file");
@@ -604,6 +830,10 @@ class DatabasePostgres extends Database {
return "E'" . pg_escape_string($s) . "'";
}
+ function quote_ident( $s ) {
+ return '"' . preg_replace( '/"/', '""', $s) . '"';
+ }
+
}
?>
diff --git a/includes/DateFormatter.php b/includes/DateFormatter.php
index 02acac73..dc077fdc 100644
--- a/includes/DateFormatter.php
+++ b/includes/DateFormatter.php
@@ -1,25 +1,11 @@
<?php
/**
- * Contain things
- * @todo document
+ * Date formatter, recognises dates in plain text and formats them accoding to user preferences.
+ *
* @package MediaWiki
* @subpackage Parser
*/
-/** */
-define('DF_ALL', -1);
-define('DF_NONE', 0);
-define('DF_MDY', 1);
-define('DF_DMY', 2);
-define('DF_YMD', 3);
-define('DF_ISO1', 4);
-define('DF_LASTPREF', 4);
-define('DF_ISO2', 5);
-define('DF_YDM', 6);
-define('DF_DM', 7);
-define('DF_MD', 8);
-define('DF_LAST', 8);
-
/**
* @todo preferences, OutputPage
* @package MediaWiki
@@ -31,7 +17,20 @@ class DateFormatter
var $monthNames = '', $rxDM, $rxMD, $rxDMY, $rxYDM, $rxMDY, $rxYMD;
var $regexes, $pDays, $pMonths, $pYears;
- var $rules, $xMonths;
+ var $rules, $xMonths, $preferences;
+
+ const ALL = -1;
+ const NONE = 0;
+ const MDY = 1;
+ const DMY = 2;
+ const YMD = 3;
+ const ISO1 = 4;
+ const LASTPREF = 4;
+ const ISO2 = 5;
+ const YDM = 6;
+ const DM = 7;
+ const MD = 8;
+ const LAST = 8;
/**
* @todo document
@@ -55,75 +54,87 @@ class DateFormatter
$this->prxISO2 = '\[\[(-?\d{4})-(\d{2})-(\d{2})]]';
# Real regular expressions
- $this->regexes[DF_DMY] = "/{$this->prxDM} *,? *{$this->prxY}{$this->regexTrail}";
- $this->regexes[DF_YDM] = "/{$this->prxY} *,? *{$this->prxDM}{$this->regexTrail}";
- $this->regexes[DF_MDY] = "/{$this->prxMD} *,? *{$this->prxY}{$this->regexTrail}";
- $this->regexes[DF_YMD] = "/{$this->prxY} *,? *{$this->prxMD}{$this->regexTrail}";
- $this->regexes[DF_DM] = "/{$this->prxDM}{$this->regexTrail}";
- $this->regexes[DF_MD] = "/{$this->prxMD}{$this->regexTrail}";
- $this->regexes[DF_ISO1] = "/{$this->prxISO1}{$this->regexTrail}";
- $this->regexes[DF_ISO2] = "/{$this->prxISO2}{$this->regexTrail}";
+ $this->regexes[self::DMY] = "/{$this->prxDM} *,? *{$this->prxY}{$this->regexTrail}";
+ $this->regexes[self::YDM] = "/{$this->prxY} *,? *{$this->prxDM}{$this->regexTrail}";
+ $this->regexes[self::MDY] = "/{$this->prxMD} *,? *{$this->prxY}{$this->regexTrail}";
+ $this->regexes[self::YMD] = "/{$this->prxY} *,? *{$this->prxMD}{$this->regexTrail}";
+ $this->regexes[self::DM] = "/{$this->prxDM}{$this->regexTrail}";
+ $this->regexes[self::MD] = "/{$this->prxMD}{$this->regexTrail}";
+ $this->regexes[self::ISO1] = "/{$this->prxISO1}{$this->regexTrail}";
+ $this->regexes[self::ISO2] = "/{$this->prxISO2}{$this->regexTrail}";
# Extraction keys
# See the comments in replace() for the meaning of the letters
- $this->keys[DF_DMY] = 'jFY';
- $this->keys[DF_YDM] = 'Y jF';
- $this->keys[DF_MDY] = 'FjY';
- $this->keys[DF_YMD] = 'Y Fj';
- $this->keys[DF_DM] = 'jF';
- $this->keys[DF_MD] = 'Fj';
- $this->keys[DF_ISO1] = 'ymd'; # y means ISO year
- $this->keys[DF_ISO2] = 'ymd';
+ $this->keys[self::DMY] = 'jFY';
+ $this->keys[self::YDM] = 'Y jF';
+ $this->keys[self::MDY] = 'FjY';
+ $this->keys[self::YMD] = 'Y Fj';
+ $this->keys[self::DM] = 'jF';
+ $this->keys[self::MD] = 'Fj';
+ $this->keys[self::ISO1] = 'ymd'; # y means ISO year
+ $this->keys[self::ISO2] = 'ymd';
# Target date formats
- $this->targets[DF_DMY] = '[[F j|j F]] [[Y]]';
- $this->targets[DF_YDM] = '[[Y]], [[F j|j F]]';
- $this->targets[DF_MDY] = '[[F j]], [[Y]]';
- $this->targets[DF_YMD] = '[[Y]] [[F j]]';
- $this->targets[DF_DM] = '[[F j|j F]]';
- $this->targets[DF_MD] = '[[F j]]';
- $this->targets[DF_ISO1] = '[[Y|y]]-[[F j|m-d]]';
- $this->targets[DF_ISO2] = '[[y-m-d]]';
+ $this->targets[self::DMY] = '[[F j|j F]] [[Y]]';
+ $this->targets[self::YDM] = '[[Y]], [[F j|j F]]';
+ $this->targets[self::MDY] = '[[F j]], [[Y]]';
+ $this->targets[self::YMD] = '[[Y]] [[F j]]';
+ $this->targets[self::DM] = '[[F j|j F]]';
+ $this->targets[self::MD] = '[[F j]]';
+ $this->targets[self::ISO1] = '[[Y|y]]-[[F j|m-d]]';
+ $this->targets[self::ISO2] = '[[y-m-d]]';
# Rules
# pref source target
- $this->rules[DF_DMY][DF_MD] = DF_DM;
- $this->rules[DF_ALL][DF_MD] = DF_MD;
- $this->rules[DF_MDY][DF_DM] = DF_MD;
- $this->rules[DF_ALL][DF_DM] = DF_DM;
- $this->rules[DF_NONE][DF_ISO2] = DF_ISO1;
+ $this->rules[self::DMY][self::MD] = self::DM;
+ $this->rules[self::ALL][self::MD] = self::MD;
+ $this->rules[self::MDY][self::DM] = self::MD;
+ $this->rules[self::ALL][self::DM] = self::DM;
+ $this->rules[self::NONE][self::ISO2] = self::ISO1;
+
+ $this->preferences = array(
+ 'default' => self::NONE,
+ 'dmy' => self::DMY,
+ 'mdy' => self::MDY,
+ 'ymd' => self::YMD,
+ 'ISO 8601' => self::ISO1,
+ );
}
/**
* @static
*/
function &getInstance() {
- global $wgDBname, $wgMemc;
+ global $wgMemc;
static $dateFormatter = false;
if ( !$dateFormatter ) {
- $dateFormatter = $wgMemc->get( "$wgDBname:dateformatter" );
+ $dateFormatter = $wgMemc->get( wfMemcKey( 'dateformatter' ) );
if ( !$dateFormatter ) {
$dateFormatter = new DateFormatter;
- $wgMemc->set( "$wgDBname:dateformatter", $dateFormatter, 3600 );
+ $wgMemc->set( wfMemcKey( 'dateformatter' ), $dateFormatter, 3600 );
}
}
return $dateFormatter;
}
/**
- * @param $preference
- * @param $text
+ * @param string $preference User preference
+ * @param string $text Text to reformat
*/
function reformat( $preference, $text ) {
- if ($preference == 'ISO 8601') $preference = 4; # The ISO 8601 option used to be 4
- for ( $i=1; $i<=DF_LAST; $i++ ) {
+ if ( isset( $this->preferences[$preference] ) ) {
+ $preference = $this->preferences[$preference];
+ } else {
+ $preference = self::NONE;
+ }
+ for ( $i=1; $i<=self::LAST; $i++ ) {
$this->mSource = $i;
if ( @$this->rules[$preference][$i] ) {
# Specific rules
$this->mTarget = $this->rules[$preference][$i];
- } elseif ( @$this->rules[DF_ALL][$i] ) {
+ } elseif ( @$this->rules[self::ALL][$i] ) {
# General rules
- $this->mTarget = $this->rules[DF_ALL][$i];
+ $this->mTarget = $this->rules[self::ALL][$i];
} elseif ( $preference ) {
# User preference
$this->mTarget = $preference;
@@ -131,7 +142,7 @@ class DateFormatter
# Default
$this->mTarget = $i;
}
- $text = preg_replace_callback( $this->regexes[$i], 'wfMainDateReplace', $text );
+ $text = preg_replace_callback( $this->regexes[$i], array( &$this, 'replace' ), $text );
}
return $text;
}
@@ -277,12 +288,4 @@ class DateFormatter
}
}
-/**
- * @todo document
- */
-function wfMainDateReplace( $matches ) {
- $df =& DateFormatter::getInstance();
- return $df->replace( $matches );
-}
-
?>
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 1964aaf2..e4ce8e5e 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -32,13 +32,22 @@ require_once( 'includes/SiteConfiguration.php' );
$wgConf = new SiteConfiguration;
/** MediaWiki version number */
-$wgVersion = '1.7.1';
+$wgVersion = '1.8.1';
/** Name of the site. It must be changed in LocalSettings.php */
$wgSitename = 'MediaWiki';
-/** Will be same as you set @see $wgSitename */
-$wgMetaNamespace = FALSE;
+/**
+ * Name of the project namespace. If left set to false, $wgSitename will be
+ * used instead.
+ */
+$wgMetaNamespace = false;
+
+/**
+ * Name of the project talk namespace. If left set to false, a name derived
+ * from the name of the project namespace will be used.
+ */
+$wgMetaNamespaceTalk = false;
/** URL of the server. It will be automatically built including https mode */
@@ -115,8 +124,8 @@ $wgStylePath = "{$wgScriptPath}/skins";
$wgStyleDirectory = "{$IP}/skins";
$wgStyleSheetPath = &$wgStylePath;
$wgArticlePath = "{$wgScript}?title=$1";
-$wgUploadPath = "{$wgScriptPath}/upload";
-$wgUploadDirectory = "{$IP}/upload";
+$wgUploadPath = "{$wgScriptPath}/images";
+$wgUploadDirectory = "{$IP}/images";
$wgHashedUploadDirectory = true;
$wgLogo = "{$wgUploadPath}/wiki.png";
$wgFavicon = '/favicon.ico';
@@ -156,7 +165,7 @@ $wgFileStore['deleted']['hash'] = 3; // 3-level subdirectory split
* Problematic punctuation:
* []{}|# Are needed for link syntax, never enable these
* % Enabled by default, minor problems with path to query rewrite rules, see below
- * + Doesn't work with path to query rewrite rules, corrupted by apache
+ * + Enabled by default, but doesn't work with path to query rewrite rules, corrupted by apache
* ? Enabled by default, but doesn't work with path to PATH_INFO rewrites
*
* All three of these punctuation problems can be avoided by using an alias, instead of a
@@ -169,10 +178,12 @@ $wgFileStore['deleted']['hash'] = 3; // 3-level subdirectory split
* passed in the query string rather than the path. This is a minor security issue
* because articles can be created such that they are hard to view or edit.
*
+ * In some rare cases you may wish to remove + for compatibility with old links.
+ *
* Theoretically 0x80-0x9F of ISO 8859-1 should be disallowed, but
* this breaks interlanguage links
*/
-$wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF";
+$wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+";
/**
@@ -328,6 +339,10 @@ $wgSharedUploadDBname = false;
$wgSharedUploadDBprefix = '';
/** Cache shared metadata in memcached. Don't do this if the commons wiki is in a different memcached domain */
$wgCacheSharedUploads = true;
+/** Allow for upload to be copied from an URL. Requires Special:Upload?source=web */
+$wgAllowCopyUploads = false;
+/** Max size for uploads, in bytes */
+$wgMaxUploadSize = 1024*1024*100; # 100MB
/**
* Point the upload navigation link to an external URL
@@ -440,7 +455,6 @@ $wgDBconnection = '';
/** Database username */
$wgDBuser = 'wikiuser';
/** Database type
- * "mysql" for working code and "PostgreSQL" for development/broken code
*/
$wgDBtype = "mysql";
/** Search type
@@ -472,7 +486,7 @@ $wgSharedDB = null;
# dbname: Default database name
# user: DB user
# password: DB password
-# type: "mysql" or "pgsql"
+# type: "mysql" or "postgres"
# load: ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0
# groupLoads: array of load ratios, the key is the query group name. A query may belong
# to several groups, the most specific group defined here is used.
@@ -776,6 +790,14 @@ $wgShowSQLErrors = false;
$wgColorErrors = true;
/**
+ * If set to true, uncaught exceptions will print a complete stack trace
+ * to output. This should only be used for debugging, as it may reveal
+ * private information in function parameters due to PHP's backtrace
+ * formatting.
+ */
+$wgShowExceptionDetails = false;
+
+/**
* disable experimental dmoz-like category browsing. Output things like:
* Encyclopedia > Music > Style of Music > Jazz
*/
@@ -895,7 +917,7 @@ $wgGroupPermissions['bot' ]['autoconfirmed'] = true;
$wgGroupPermissions['sysop']['block'] = true;
$wgGroupPermissions['sysop']['createaccount'] = true;
$wgGroupPermissions['sysop']['delete'] = true;
-$wgGroupPermissions['sysop']['deletedhistory'] = true; // can view deleted history entries, but not see or restore the text
+$wgGroupPermissions['sysop']['deletedhistory'] = true; // can view deleted history entries, but not see or restore the text
$wgGroupPermissions['sysop']['editinterface'] = true;
$wgGroupPermissions['sysop']['import'] = true;
$wgGroupPermissions['sysop']['importupload'] = true;
@@ -908,8 +930,9 @@ $wgGroupPermissions['sysop']['trackback'] = true;
$wgGroupPermissions['sysop']['upload'] = true;
$wgGroupPermissions['sysop']['reupload'] = true;
$wgGroupPermissions['sysop']['reupload-shared'] = true;
-$wgGroupPermissions['sysop']['unwatchedpages'] = true;
+$wgGroupPermissions['sysop']['unwatchedpages'] = true;
$wgGroupPermissions['sysop']['autoconfirmed'] = true;
+$wgGroupPermissions['sysop']['upload_by_url'] = true;
// Permission to change users' group assignments
$wgGroupPermissions['bureaucrat']['userrights'] = true;
@@ -1022,6 +1045,9 @@ $wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
*/
$wgUseGzip = false;
+/** Whether MediaWiki should send an ETag header */
+$wgUseETag = false;
+
# Email notification settings
#
@@ -1153,21 +1179,17 @@ $wgTexvc = './math/texvc';
#
# You have to create a 'profiling' table in your database before using
# profiling see maintenance/archives/patch-profiling.sql .
+#
+# To enable profiling, edit StartProfiler.php
-/** Enable for more detailed by-function times in debug log */
-$wgProfiling = false;
/** Only record profiling info for pages that took longer than this */
$wgProfileLimit = 0.0;
/** Don't put non-profiling info into log file */
$wgProfileOnly = false;
/** Log sums from profiling into "profiling" table in db. */
$wgProfileToDatabase = false;
-/** Only profile every n requests when profiling is turned on */
-$wgProfileSampleRate = 1;
/** If true, print a raw call tree instead of per-function report */
$wgProfileCallTree = false;
-/** If not empty, specifies profiler type to load */
-$wgProfilerType = '';
/** Should application server host be put into profiling table */
$wgProfilePerHost = false;
@@ -1251,7 +1273,7 @@ $wgFileBlacklist = array(
# HTML may contain cookie-stealing JavaScript and web bugs
'html', 'htm', 'js', 'jsb',
# PHP scripts may execute arbitrary code on the server
- 'php', 'phtml', 'php3', 'php4', 'phps',
+ 'php', 'phtml', 'php3', 'php4', 'php5', 'phps',
# Other types that may be interpreted by some servers
'shtml', 'jhtml', 'pl', 'py', 'cgi',
# May contain harmful executables for Windows victims
@@ -1280,7 +1302,7 @@ $wgCheckFileExtensions = true;
*/
$wgStrictFileExtensions = true;
-/** Warn if uploaded files are larger than this */
+/** Warn if uploaded files are larger than this (in bytes)*/
$wgUploadSizeWarning = 150 * 1024;
/** For compatibility with old installations set to false */
@@ -1551,21 +1573,53 @@ $wgTidyInternal = function_exists( 'tidy_load_config' );
$wgDefaultSkin = 'monobook';
/**
- * Settings added to this array will override the language globals for the user
- * preferences used by anonymous visitors and newly created accounts. (See names
- * and sample values in languages/Language.php)
+ * Settings added to this array will override the default globals for the user
+ * preferences used by anonymous visitors and newly created accounts.
* For instance, to disable section editing links:
- * $wgDefaultUserOptions ['editsection'] = 0;
- *
- */
-$wgDefaultUserOptions = array();
+ *  $wgDefaultUserOptions ['editsection'] = 0;
+ *
+ */
+$wgDefaultUserOptions = array(
+ 'quickbar' => 1,
+ 'underline' => 2,
+ 'cols' => 80,
+ 'rows' => 25,
+ 'searchlimit' => 20,
+ 'contextlines' => 5,
+ 'contextchars' => 50,
+ 'skin' => false,
+ 'math' => 1,
+ 'rcdays' => 7,
+ 'rclimit' => 50,
+ 'wllimit' => 250,
+ 'highlightbroken' => 1,
+ 'stubthreshold' => 0,
+ 'previewontop' => 1,
+ 'editsection' => 1,
+ 'editsectiononrightclick'=> 0,
+ 'showtoc' => 1,
+ 'showtoolbar' => 1,
+ 'date' => 'default',
+ 'imagesize' => 2,
+ 'thumbsize' => 2,
+ 'rememberpassword' => 0,
+ 'enotifwatchlistpages' => 0,
+ 'enotifusertalkpages' => 1,
+ 'enotifminoredits' => 0,
+ 'enotifrevealaddr' => 0,
+ 'shownumberswatching' => 1,
+ 'fancysig' => 0,
+ 'externaleditor' => 0,
+ 'externaldiff' => 0,
+ 'showjumplinks' => 1,
+ 'numberheadings' => 0,
+ 'uselivepreview' => 0,
+ 'watchlistdays' => 3.0,
+);
/** Whether or not to allow and use real name fields. Defaults to true. */
$wgAllowRealName = true;
-/** Use XML parser? */
-$wgUseXMLparser = false ;
-
/*****************************************************************************
* Extensions
*/
@@ -2072,6 +2126,14 @@ $wgExternalServers = array();
$wgDefaultExternalStore = false;
/**
+ * Revision text may be cached in $wgMemc to reduce load on external storage
+ * servers and object extraction overhead for frequently-loaded revisions.
+ *
+ * Set to 0 to disable, or number of seconds before cache expiry.
+ */
+$wgRevisionCacheExpiry = 0;
+
+/**
* list of trusted media-types and mime types.
* Use the MEDIATYPE_xxx constants to represent media types.
* This list is used by Image::isSafeFile
@@ -2144,14 +2206,22 @@ $wgUpdateRowsPerJob = 500;
$wgUpdateRowsPerQuery = 10;
/**
- * Enable use of AJAX features, currently auto suggestion for the search bar
+ * Enable AJAX framework
*/
$wgUseAjax = false;
/**
- * List of Ajax-callable functions
+ * Enable auto suggestion for the search bar
+ * Requires $wgUseAjax to be true too.
+ * Causes wfSajaxSearch to be added to $wgAjaxExportList
*/
-$wgAjaxExportList = array( 'wfSajaxSearch' );
+$wgAjaxSearch = false;
+
+/**
+ * List of Ajax-callable functions.
+ * Extensions acting as Ajax callbacks must register here
+ */
+$wgAjaxExportList = array( );
/**
* Allow DISPLAYTITLE to change title display
@@ -2186,4 +2256,39 @@ $wgContentNamespaces = array( NS_MAIN );
*/
$wgMaxShellMemory = 102400;
+/**
+ * Maximum file size created by shell processes under linux, in KB
+ * ImageMagick convert for example can be fairly hungry for scratch space
+ */
+$wgMaxShellFileSize = 102400;
+
+/**
+ * DJVU settings
+ * Path of the djvutoxml executable
+ * Enable this and $wgDjvuRenderer to enable djvu rendering
+ */
+# $wgDjvuToXML = 'djvutoxml';
+$wgDjvuToXML = null;
+
+/**
+ * Path of the ddjvu DJVU renderer
+ * Enable this and $wgDjvuToXML to enable djvu rendering
+ */
+# $wgDjvuRenderer = 'ddjvu';
+$wgDjvuRenderer = null;
+
+/**
+ * Path of the DJVU post processor
+ * May include command line options
+ * Default: ppmtojpeg, since ddjvu generates ppm output
+ */
+$wgDjvuPostProcessor = 'ppmtojpeg';
+
+/**
+* Enable direct access to the data API
+* through api.php
+*/
+$wgEnableAPI = false;
+$wgEnableWriteAPI = false;
+
?>
diff --git a/includes/Defines.php b/includes/Defines.php
index 9ff8303b..40727485 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -20,6 +20,17 @@ define( 'DBO_DEFAULT', 16 );
define( 'DBO_PERSISTENT', 32 );
/**#@-*/
+# Valid database indexes
+# Operation-based indexes
+define( 'DB_SLAVE', -1 ); # Read from the slave (or only server)
+define( 'DB_MASTER', -2 ); # Write to master (or only server)
+define( 'DB_LAST', -3 ); # Whatever database was used last
+
+# Obsolete aliases
+define( 'DB_READ', -1 );
+define( 'DB_WRITE', -2 );
+
+
/**#@+
* Virtual namespaces; don't appear in the page database
*/
@@ -75,10 +86,8 @@ define( 'MW_MATH_MATHML', 5 );
/**#@-*/
/**
- * User rights management
- * a big array of string defining a right, that's how they are saved in the
- * database.
- * @todo Is this necessary?
+ * User rights list
+ * @deprecated
*/
$wgAvailableRights = array(
'block',
@@ -108,6 +117,7 @@ define( 'CACHE_NONE', 0 ); // Do not cache
define( 'CACHE_DB', 1 ); // Store cache objects in the DB
define( 'CACHE_MEMCACHED', 2 ); // MemCached, must specify servers in $wgMemCacheServers
define( 'CACHE_ACCEL', 3 ); // eAccelerator or Turck, whichever is available
+define( 'CACHE_DBA', 4 ); // Use PHP's DBA extension to store in a DBM-style database
/**#@-*/
@@ -151,10 +161,15 @@ define( 'ALF_NO_BLOCK_LOCK', 8 );
* Date format selectors; used in user preference storage and by
* Language::date() and co.
*/
-define( 'MW_DATE_DEFAULT', '0' );
+/*define( 'MW_DATE_DEFAULT', '0' );
define( 'MW_DATE_MDY', '1' );
define( 'MW_DATE_DMY', '2' );
define( 'MW_DATE_YMD', '3' );
+define( 'MW_DATE_ISO', 'ISO 8601' );*/
+define( 'MW_DATE_DEFAULT', 'default' );
+define( 'MW_DATE_MDY', 'mdy' );
+define( 'MW_DATE_DMY', 'dmy' );
+define( 'MW_DATE_YMD', 'ymd' );
define( 'MW_DATE_ISO', 'ISO 8601' );
/**#@-*/
@@ -180,4 +195,15 @@ define( 'EDIT_FORCE_BOT', 16 );
define( 'EDIT_DEFER_UPDATES', 32 );
/**#@-*/
+/**
+ * Flags for Database::makeList()
+ * These are also available as Database class constants
+ */
+define( 'LIST_COMMA', 0 );
+define( 'LIST_AND', 1 );
+define( 'LIST_SET', 2 );
+define( 'LIST_NAMES', 3);
+define( 'LIST_OR', 4);
+
+
?>
diff --git a/includes/DifferenceEngine.php b/includes/DifferenceEngine.php
index 741b7199..448bcb5d 100644
--- a/includes/DifferenceEngine.php
+++ b/includes/DifferenceEngine.php
@@ -5,10 +5,6 @@
* @subpackage DifferenceEngine
*/
-/** */
-define( 'MAX_DIFF_LINE', 10000 );
-define( 'MAX_DIFF_XREF_LENGTH', 10000 );
-
/**
* @todo document
* @public
@@ -188,7 +184,7 @@ CONTROL;
$wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" );
if( !$this->mNewRev->isCurrent() ) {
- $oldEditSectionSetting = $wgOut->mParserOptions->setEditSection( false );
+ $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
}
$this->loadNewText();
@@ -198,7 +194,7 @@ CONTROL;
$wgOut->addSecondaryWikiText( $this->mNewtext );
if( !$this->mNewRev->isCurrent() ) {
- $wgOut->mParserOptions->setEditSection( $oldEditSectionSetting );
+ $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
}
wfProfileOut( $fname );
@@ -301,7 +297,7 @@ CONTROL;
* Returns false on error
*/
function getDiffBody() {
- global $wgMemc, $wgDBname;
+ global $wgMemc;
$fname = 'DifferenceEngine::getDiffBody';
wfProfileIn( $fname );
@@ -309,7 +305,7 @@ CONTROL;
$key = false;
if ( $this->mOldid && $this->mNewid ) {
// Try cache
- $key = "$wgDBname:diff:oldid:{$this->mOldid}:newid:{$this->mNewid}";
+ $key = wfMemcKey( 'diff', 'oldid', $this->mOldid, 'newid', $this->mNewid );
$difftext = $wgMemc->get( $key );
if ( $difftext ) {
wfIncrStats( 'diff_cache_hit' );
@@ -411,8 +407,8 @@ CONTROL;
# Native PHP diff
$ota = explode( "\n", $wgContLang->segmentForDiff( $otext ) );
$nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) );
- $diffs =& new Diff( $ota, $nta );
- $formatter =& new TableDiffFormatter();
+ $diffs = new Diff( $ota, $nta );
+ $formatter = new TableDiffFormatter();
return $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) );
}
@@ -724,6 +720,8 @@ class _DiffOp_Change extends _DiffOp {
*/
class _DiffEngine
{
+ const MAX_XREF_LENGTH = 10000;
+
function diff ($from_lines, $to_lines) {
$fname = '_DiffEngine::diff';
wfProfileIn( $fname );
@@ -821,7 +819,7 @@ class _DiffEngine
* Returns the whole line if it's small enough, or the MD5 hash otherwise
*/
function _line_hash( $line ) {
- if ( strlen( $line ) > MAX_DIFF_XREF_LENGTH ) {
+ if ( strlen( $line ) > self::MAX_XREF_LENGTH ) {
return md5( $line );
} else {
return $line;
@@ -1576,6 +1574,8 @@ class _HWLDF_WordAccumulator {
*/
class WordLevelDiff extends MappedDiff
{
+ const MAX_LINE_LENGTH = 10000;
+
function WordLevelDiff ($orig_lines, $closing_lines) {
$fname = 'WordLevelDiff::WordLevelDiff';
wfProfileIn( $fname );
@@ -1604,7 +1604,7 @@ class WordLevelDiff extends MappedDiff
$words[] = "\n";
$stripped[] = "\n";
}
- if ( strlen( $line ) > MAX_DIFF_LINE ) {
+ if ( strlen( $line ) > self::MAX_LINE_LENGTH ) {
$words[] = $line;
$stripped[] = $line;
} else {
diff --git a/includes/DjVuImage.php b/includes/DjVuImage.php
index b857fa66..871c563b 100644
--- a/includes/DjVuImage.php
+++ b/includes/DjVuImage.php
@@ -208,7 +208,23 @@ class DjVuImage {
'resolution' => $resolution,
'gamma' => $gamma / 10.0 );
}
+
+ /**
+ * Return an XML string describing the DjVu image
+ * @return string
+ */
+ function retrieveMetaData() {
+ global $wgDjvuToXML;
+ if ( isset( $wgDjvuToXML ) ) {
+ $cmd = $wgDjvuToXML . ' --without-anno --without-text ' . $this->mFilename;
+ $xml = wfShellExec( $cmd, $retval );
+ } else {
+ $xml = null;
+ }
+ return $xml;
+ }
+
}
-?> \ No newline at end of file
+?>
diff --git a/includes/EditPage.php b/includes/EditPage.php
index d43a1202..a1207d10 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -845,7 +845,7 @@ class EditPage {
$s = wfMsg('editingcomment', $this->mTitle->getPrefixedText() );
} else {
$s = wfMsg('editingsection', $this->mTitle->getPrefixedText() );
- if( !$this->preview && !$this->diff ) {
+ if( !$this->summary && !$this->preview && !$this->diff ) {
preg_match( "/^(=+)(.+)\\1/mi",
$this->textbox1,
$matches );
@@ -942,7 +942,7 @@ class EditPage {
$cancel = $sk->makeKnownLink( $this->mTitle->getPrefixedText(),
wfMsgExt('cancel', array('parseinline')) );
- $edithelpurl = $sk->makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ));
+ $edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ));
$edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'.
htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '.
htmlspecialchars( wfMsg( 'newwindow' ) );
@@ -1199,7 +1199,6 @@ END
$wgOut->addHtml( wfHidden( 'wpAutoSummary', $autosumm ) );
if ( $this->isConflict ) {
- require_once( "DifferenceEngine.php" );
$wgOut->addWikiText( '==' . wfMsg( "yourdiff" ) . '==' );
$de = new DifferenceEngine( $this->mTitle );
@@ -1380,11 +1379,6 @@ END
wfProfileOut( $fname );
return $previewhead;
} else {
- # if user want to see preview when he edit an article
- if( $wgUser->getOption('previewonfirst') and ($this->textbox1 == '')) {
- $this->textbox1 = $this->getContent();
- }
-
$toparse = $this->textbox1;
# If we're adding a comment, we need to show the
@@ -1416,15 +1410,21 @@ END
# If the user made changes, preserve them when showing the markup
# (This happens when a user is blocked during edit, for instance)
$first = $this->firsttime || ( !$this->save && $this->textbox1 == '' );
- $source = $first ? $this->getContent() : $this->textbox1;
-
+ if( $first ) {
+ $source = $this->mTitle->exists() ? $this->getContent() : false;
+ } else {
+ $source = $this->textbox1;
+ }
+
# Spit out the source or the user's modified version
- $rows = $wgUser->getOption( 'rows' );
- $cols = $wgUser->getOption( 'cols' );
- $attribs = array( 'id' => 'wpTextbox1', 'name' => 'wpTextbox1', 'cols' => $cols, 'rows' => $rows, 'readonly' => 'readonly' );
- $wgOut->addHtml( '<hr />' );
- $wgOut->addWikiText( wfMsg( $first ? 'blockedoriginalsource' : 'blockededitsource', $this->mTitle->getPrefixedText() ) );
- $wgOut->addHtml( wfElement( 'textarea', $attribs, $source ) );
+ if( $source !== false ) {
+ $rows = $wgUser->getOption( 'rows' );
+ $cols = $wgUser->getOption( 'cols' );
+ $attribs = array( 'id' => 'wpTextbox1', 'name' => 'wpTextbox1', 'cols' => $cols, 'rows' => $rows, 'readonly' => 'readonly' );
+ $wgOut->addHtml( '<hr />' );
+ $wgOut->addWikiText( wfMsg( $first ? 'blockedoriginalsource' : 'blockededitsource', $this->mTitle->getPrefixedText() ) );
+ $wgOut->addHtml( wfOpenElement( 'textarea', $attribs ) . htmlspecialchars( $source ) . wfCloseElement( 'textarea' ) );
+ }
}
/**
@@ -1720,7 +1720,6 @@ END
* @return string HTML
*/
function getDiff() {
- require_once( 'DifferenceEngine.php' );
$oldtext = $this->mArticle->fetchContent();
$newtext = $this->mArticle->replaceSection(
$this->section, $this->textbox1, $this->summary, $this->edittime );
diff --git a/includes/Exception.php b/includes/Exception.php
index 1e24515b..56f18d5a 100644
--- a/includes/Exception.php
+++ b/includes/Exception.php
@@ -3,7 +3,8 @@
class MWException extends Exception
{
function useOutputPage() {
- return !empty( $GLOBALS['wgFullyInitialised'] );
+ return !empty( $GLOBALS['wgFullyInitialised'] ) &&
+ !empty( $GLOBALS['wgArticle'] ) && !empty( $GLOBALS['wgTitle'] );
}
function useMessageCache() {
@@ -19,16 +20,28 @@ class MWException extends Exception
return wfMsgReplaceArgs( $fallback, $args );
}
}
-
+
function getHTML() {
- return '<p>' . htmlspecialchars( $this->getMessage() ) .
- '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
- "</p>\n";
+ global $wgShowExceptionDetails;
+ if( $wgShowExceptionDetails ) {
+ return '<p>' . htmlspecialchars( $this->getMessage() ) .
+ '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
+ "</p>\n";
+ } else {
+ return "<p>Set <b><tt>\$wgShowExceptionDetails = true;</tt></b> " .
+ "in LocalSettings.php to show detailed debugging information.</p>";
+ }
}
function getText() {
- return $this->getMessage() .
- "\nBacktrace:\n" . $this->getTraceAsString() . "\n";
+ global $wgShowExceptionDetails;
+ if( $wgShowExceptionDetails ) {
+ return $this->getMessage() .
+ "\nBacktrace:\n" . $this->getTraceAsString() . "\n";
+ } else {
+ return "<p>Set <tt>\$wgShowExceptionDetails = true;</tt> " .
+ "in LocalSettings.php to show detailed debugging information.</p>";
+ }
}
function getPageTitle() {
@@ -40,6 +53,13 @@ class MWException extends Exception
}
}
+ function getLogMessage() {
+ $file = $this->getFile();
+ $line = $this->getLine();
+ $message = $this->getMessage();
+ return "{$_SERVER['REQUEST_URI']} Exception from line $line of $file: $message";
+ }
+
function reportHTML() {
global $wgOut;
if ( $this->useOutputPage() ) {
@@ -67,6 +87,10 @@ class MWException extends Exception
if ( $wgCommandLineMode ) {
$this->reportText();
} else {
+ $log = $this->getLogMessage();
+ if ( $log ) {
+ wfDebugLog( 'exception', $log );
+ }
$this->reportHTML();
}
}
@@ -93,11 +117,12 @@ class MWException extends Exception
function htmlFooter() {
echo "</body></html>";
- }
+ }
+
}
/**
- * Exception class which takes an HTML error message, and does not
+ * Exception class which takes an HTML error message, and does not
* produce a backtrace. Replacement for OutputPage::fatalError().
*/
class FatalError extends MWException {
@@ -145,11 +170,11 @@ function wfReportException( Exception $e ) {
$e->report();
} catch ( Exception $e2 ) {
// Exception occurred from within exception handler
- // Show a simpler error message for the original exception,
+ // Show a simpler error message for the original exception,
// don't try to invoke report()
$message = "MediaWiki internal error.\n\n" .
- "Original exception: " . $e->__toString() .
- "\n\nException caught inside exception handler: " .
+ "Original exception: " . $e->__toString() .
+ "\n\nException caught inside exception handler: " .
$e2->__toString() . "\n";
if ( !empty( $GLOBALS['wgCommandLineMode'] ) ) {
@@ -165,7 +190,7 @@ function wfReportException( Exception $e ) {
/**
* Exception handler which simulates the appropriate catch() handling:
- *
+ *
* try {
* ...
* } catch ( MWException $e ) {
@@ -181,8 +206,7 @@ function wfExceptionHandler( $e ) {
// Final cleanup, similar to wfErrorExit()
if ( $wgFullyInitialised ) {
try {
- wfProfileClose();
- logProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
+ wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
} catch ( Exception $e ) {}
}
diff --git a/includes/Exif.php b/includes/Exif.php
index f9fb9a2c..2ab0feb1 100644
--- a/includes/Exif.php
+++ b/includes/Exif.php
@@ -293,7 +293,7 @@ class Exif {
);
$this->file = $file;
- $this->basename = basename( $this->file );
+ $this->basename = wfBaseName( $this->file );
$this->makeFlatExifTags();
diff --git a/includes/Export.php b/includes/Export.php
index da92694e..aa70e27b 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -16,46 +16,43 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# http://www.gnu.org/copyleft/gpl.html
+
/**
*
* @package MediaWiki
* @subpackage SpecialPage
*/
-/** */
-
-define( 'MW_EXPORT_FULL', 0 );
-define( 'MW_EXPORT_CURRENT', 1 );
-
-define( 'MW_EXPORT_BUFFER', 0 );
-define( 'MW_EXPORT_STREAM', 1 );
-
-define( 'MW_EXPORT_TEXT', 0 );
-define( 'MW_EXPORT_STUB', 1 );
-
-
-/**
- * @package MediaWiki
- * @subpackage SpecialPage
- */
class WikiExporter {
-
var $list_authors = false ; # Return distinct author list (when not returning full history)
var $author_list = "" ;
+ const FULL = 0;
+ const CURRENT = 1;
+
+ const BUFFER = 0;
+ const STREAM = 1;
+
+ const TEXT = 0;
+ const STUB = 1;
+
/**
- * If using MW_EXPORT_STREAM to stream a large amount of data,
+ * If using WikiExporter::STREAM to stream a large amount of data,
* provide a database connection which is not managed by
* LoadBalancer to read from: some history blob types will
* make additional queries to pull source data while the
* main query is still running.
*
* @param Database $db
- * @param int $history one of MW_EXPORT_FULL or MW_EXPORT_CURRENT
- * @param int $buffer one of MW_EXPORT_BUFFER or MW_EXPORT_STREAM
+ * @param mixed $history one of WikiExporter::FULL or WikiExporter::CURRENT, or an
+ * associative array:
+ * offset: non-inclusive offset at which to start the query
+ * limit: maximum number of rows to return
+ * dir: "asc" or "desc" timestamp order
+ * @param int $buffer one of WikiExporter::BUFFER or WikiExporter::STREAM
*/
- function WikiExporter( &$db, $history = MW_EXPORT_CURRENT,
- $buffer = MW_EXPORT_BUFFER, $text = MW_EXPORT_TEXT ) {
+ function WikiExporter( &$db, $history = WikiExporter::CURRENT,
+ $buffer = WikiExporter::BUFFER, $text = WikiExporter::TEXT ) {
$this->db =& $db;
$this->history = $history;
$this->buffer = $buffer;
@@ -168,20 +165,42 @@ class WikiExporter {
$revision = $this->db->tableName( 'revision' );
$text = $this->db->tableName( 'text' );
- if( $this->history == MW_EXPORT_FULL ) {
+ $order = 'ORDER BY page_id';
+ $limit = '';
+
+ if( $this->history == WikiExporter::FULL ) {
$join = 'page_id=rev_page';
- } elseif( $this->history == MW_EXPORT_CURRENT ) {
+ } elseif( $this->history == WikiExporter::CURRENT ) {
if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
$this->do_list_authors ( $page , $revision , $cond );
}
$join = 'page_id=rev_page AND page_latest=rev_id';
+ } elseif ( is_array( $this->history ) ) {
+ $join = 'page_id=rev_page';
+ if ( $this->history['dir'] == 'asc' ) {
+ $op = '>';
+ $order .= ', rev_timestamp';
+ } else {
+ $op = '<';
+ $order .= ', rev_timestamp DESC';
+ }
+ if ( !empty( $this->history['offset'] ) ) {
+ $join .= " AND rev_timestamp $op " . $this->db->addQuotes(
+ $this->db->timestamp( $this->history['offset'] ) );
+ }
+ if ( !empty( $this->history['limit'] ) ) {
+ $limitNum = intval( $this->history['limit'] );
+ if ( $limitNum > 0 ) {
+ $limit = "LIMIT $limitNum";
+ }
+ }
} else {
wfProfileOut( $fname );
return new WikiError( "$fname given invalid history dump type." );
}
$where = ( $cond == '' ) ? '' : "$cond AND";
- if( $this->buffer == MW_EXPORT_STREAM ) {
+ if( $this->buffer == WikiExporter::STREAM ) {
$prev = $this->db->bufferResults( false );
}
if( $cond == '' ) {
@@ -193,19 +212,19 @@ class WikiExporter {
$revindex = '';
$straight = '';
}
- if( $this->text == MW_EXPORT_STUB ) {
+ if( $this->text == WikiExporter::STUB ) {
$sql = "SELECT $straight * FROM
$page $pageindex,
$revision $revindex
WHERE $where $join
- ORDER BY page_id";
+ $order $limit";
} else {
$sql = "SELECT $straight * FROM
$page $pageindex,
$revision $revindex,
$text
WHERE $where $join AND rev_text_id=old_id
- ORDER BY page_id";
+ $order $limit";
}
$result = $this->db->query( $sql, $fname );
$wrapper = $this->db->resultObject( $result );
@@ -215,7 +234,7 @@ class WikiExporter {
$this->outputStream( $wrapper );
}
- if( $this->buffer == MW_EXPORT_STREAM ) {
+ if( $this->buffer == WikiExporter::STREAM ) {
$this->db->bufferResults( $prev );
}
diff --git a/includes/ExternalEdit.php b/includes/ExternalEdit.php
index 21f632ec..14b55fdb 100644
--- a/includes/ExternalEdit.php
+++ b/includes/ExternalEdit.php
@@ -48,7 +48,7 @@ class ExternalEdit {
$extension="wiki";
} elseif($this->mMode=="file") {
$type="Edit file";
- $image = Image::newFromTitle( $this->mTitle );
+ $image = new Image( $this->mTitle );
$img_url = $image->getURL();
if(strpos($img_url,"://")) {
$url = $img_url;
diff --git a/includes/ExternalStoreDB.php b/includes/ExternalStoreDB.php
index f610df80..861a9939 100644
--- a/includes/ExternalStoreDB.php
+++ b/includes/ExternalStoreDB.php
@@ -6,7 +6,6 @@
* DB accessable external objects
*
*/
-require_once( 'LoadBalancer.php' );
/** @package MediaWiki */
diff --git a/includes/FileStore.php b/includes/FileStore.php
index 85aaedfe..35ebd554 100644
--- a/includes/FileStore.php
+++ b/includes/FileStore.php
@@ -36,18 +36,16 @@ class FileStore {
* @fixme Probably only works on MySQL. Abstract to the Database class?
*/
static function lock() {
- $fname = __CLASS__ . '::' . __FUNCTION__;
-
$dbw = wfGetDB( DB_MASTER );
$lockname = $dbw->addQuotes( FileStore::lockName() );
- $result = $dbw->query( "SELECT GET_LOCK($lockname, 5) AS lockstatus", $fname );
+ $result = $dbw->query( "SELECT GET_LOCK($lockname, 5) AS lockstatus", __METHOD__ );
$row = $dbw->fetchObject( $result );
$dbw->freeResult( $result );
if( $row->lockstatus == 1 ) {
return true;
} else {
- wfDebug( "$fname failed to acquire lock\n" );
+ wfDebug( __METHOD__." failed to acquire lock\n" );
return false;
}
}
@@ -56,18 +54,15 @@ class FileStore {
* Release the global file store lock.
*/
static function unlock() {
- $fname = __CLASS__ . '::' . __FUNCTION__;
-
$dbw = wfGetDB( DB_MASTER );
$lockname = $dbw->addQuotes( FileStore::lockName() );
- $result = $dbw->query( "SELECT RELEASE_LOCK($lockname)", $fname );
+ $result = $dbw->query( "SELECT RELEASE_LOCK($lockname)", __METHOD__ );
$row = $dbw->fetchObject( $result );
$dbw->freeResult( $result );
}
private static function lockName() {
- global $wgDBname, $wgDBprefix;
- return "MediaWiki.{$wgDBname}.{$wgDBprefix}FileStore";
+ return 'MediaWiki.' . wfWikiID() . '.FileStore';
}
/**
@@ -103,8 +98,6 @@ class FileStore {
}
private function copyFile( $sourcePath, $destPath, $flags=0 ) {
- $fname = __CLASS__ . '::' . __FUNCTION__;
-
if( !file_exists( $sourcePath ) ) {
// Abort! Abort!
throw new FSException( "missing source file '$sourcePath'\n" );
@@ -135,11 +128,11 @@ class FileStore {
wfRestoreWarnings();
if( $ok ) {
- wfDebug( "$fname copied '$sourcePath' to '$destPath'\n" );
+ wfDebug( __METHOD__." copied '$sourcePath' to '$destPath'\n" );
$transaction->addRollback( FSTransaction::DELETE_FILE, $destPath );
} else {
throw new FSException(
- "$fname failed to copy '$sourcePath' to '$destPath'\n" );
+ __METHOD__." failed to copy '$sourcePath' to '$destPath'\n" );
}
}
@@ -239,13 +232,11 @@ class FileStore {
* @return string or false if could not open file or bad extension
*/
static function calculateKey( $path, $extension ) {
- $fname = __CLASS__ . '::' . __FUNCTION__;
-
wfSuppressWarnings();
$hash = sha1_file( $path );
wfRestoreWarnings();
if( $hash === false ) {
- wfDebug( "$fname: couldn't hash file '$path'\n" );
+ wfDebug( __METHOD__.": couldn't hash file '$path'\n" );
return false;
}
@@ -260,7 +251,7 @@ class FileStore {
if( self::validKey( $key ) ) {
return $key;
} else {
- wfDebug( "$fname: generated bad key '$key'\n" );
+ wfDebug( __METHOD__.": generated bad key '$key'\n" );
return false;
}
}
@@ -353,7 +344,6 @@ class FSTransaction {
}
private function apply( $actions ) {
- $fname = __CLASS__ . '::' . __FUNCTION__;
$result = true;
foreach( $actions as $item ) {
list( $action, $path ) = $item;
@@ -362,9 +352,9 @@ class FSTransaction {
$ok = unlink( $path );
wfRestoreWarnings();
if( $ok )
- wfDebug( "$fname: deleting file '$path'\n" );
+ wfDebug( __METHOD__.": deleting file '$path'\n" );
else
- wfDebug( "$fname: failed to delete file '$path'\n" );
+ wfDebug( __METHOD__.": failed to delete file '$path'\n" );
$result = $result && $ok;
}
}
@@ -374,4 +364,4 @@ class FSTransaction {
class FSException extends MWException { }
-?> \ No newline at end of file
+?>
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index e2033486..623f9d3b 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -33,9 +33,10 @@ require_once( 'XmlFunctions.php' );
/**
* Compatibility functions
- * PHP <4.3.x is not actively supported; 4.1.x and 4.2.x might or might not work.
- * <4.1.x will not work, as we use a number of features introduced in 4.1.0
- * such as the new autoglobals.
+ *
+ * We more or less support PHP 5.0.x and up.
+ * Re-implementations of newer functions or functions in non-standard
+ * PHP extensions may be included here.
*/
if( !function_exists('iconv') ) {
# iconv support is not in the default configuration and so may not be present.
@@ -49,22 +50,6 @@ if( !function_exists('iconv') ) {
}
}
-if( !function_exists('file_get_contents') ) {
- # Exists in PHP 4.3.0+
- function file_get_contents( $filename ) {
- return implode( '', file( $filename ) );
- }
-}
-
-if( !function_exists('is_a') ) {
- # Exists in PHP 4.2.0+
- function is_a( $object, $class_name ) {
- return
- (strcasecmp( get_class( $object ), $class_name ) == 0) ||
- is_subclass_of( $object, $class_name );
- }
-}
-
# UTF-8 substr function based on a PHP manual comment
if ( !function_exists( 'mb_substr' ) ) {
function mb_substr( $str, $start ) {
@@ -79,17 +64,6 @@ if ( !function_exists( 'mb_substr' ) ) {
}
}
-if( !function_exists( 'floatval' ) ) {
- /**
- * First defined in PHP 4.2.0
- * @param mixed $var;
- * @return float
- */
- function floatval( $var ) {
- return (float)$var;
- }
-}
-
if ( !function_exists( 'array_diff_key' ) ) {
/**
* Exists in PHP 5.1.0+
@@ -109,39 +83,25 @@ if ( !function_exists( 'array_diff_key' ) ) {
/**
- * Wrapper for clone() for PHP 4, for the moment.
+ * Wrapper for clone(), for compatibility with PHP4-friendly extensions.
* PHP 5 won't let you declare a 'clone' function, even conditionally,
* so it has to be a wrapper with a different name.
*/
function wfClone( $object ) {
- // WARNING: clone() is not a function in PHP 5, so function_exists fails.
- if( version_compare( PHP_VERSION, '5.0' ) < 0 ) {
- return $object;
- } else {
- return clone( $object );
- }
+ return clone( $object );
}
/**
* Where as we got a random seed
- * @var bool $wgTotalViews
*/
$wgRandomSeeded = false;
/**
* Seed Mersenne Twister
- * Only necessary in PHP < 4.2.0
- *
- * @return bool
+ * No-op for compatibility; only necessary in PHP < 4.2.0
*/
function wfSeedRandom() {
- global $wgRandomSeeded;
-
- if ( ! $wgRandomSeeded && version_compare( phpversion(), '4.2.0' ) < 0 ) {
- $seed = hexdec(substr(md5(microtime()),-8)) & 0x7fffffff;
- mt_srand( $seed );
- $wgRandomSeeded = true;
- }
+ /* No-op */
}
/**
@@ -190,13 +150,25 @@ function wfUrlencode ( $s ) {
*/
function wfDebug( $text, $logonly = false ) {
global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage;
+ static $recursion = 0;
# Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
if ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' && !$wgDebugRawPage ) {
return;
}
- if ( isset( $wgOut ) && $wgDebugComments && !$logonly ) {
+ if ( $wgDebugComments && !$logonly ) {
+ if ( !isset( $wgOut ) ) {
+ return;
+ }
+ if ( !StubObject::isRealObject( $wgOut ) ) {
+ if ( $recursion ) {
+ return;
+ }
+ $recursion++;
+ $wgOut->_unstub();
+ $recursion--;
+ }
$wgOut->debug( $text );
}
if ( '' != $wgDebugLogFile && !$wgProfileOnly ) {
@@ -217,11 +189,12 @@ function wfDebug( $text, $logonly = false ) {
* log file is specified, (default true)
*/
function wfDebugLog( $logGroup, $text, $public = true ) {
- global $wgDebugLogGroups, $wgDBname;
+ global $wgDebugLogGroups;
if( $text{strlen( $text ) - 1} != "\n" ) $text .= "\n";
if( isset( $wgDebugLogGroups[$logGroup] ) ) {
$time = wfTimestamp( TS_DB );
- @error_log( "$time $wgDBname: $text", 3, $wgDebugLogGroups[$logGroup] );
+ $wiki = wfWikiID();
+ @error_log( "$time $wiki: $text", 3, $wgDebugLogGroups[$logGroup] );
} else if ( $public === true ) {
wfDebug( $text, true );
}
@@ -243,13 +216,12 @@ function wfLogDBError( $text ) {
/**
* @todo document
*/
-function logProfilingData() {
+function wfLogProfilingData() {
global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
global $wgProfiling, $wgUser;
- $now = wfTime();
-
- $elapsed = $now - $wgRequestTime;
if ( $wgProfiling ) {
+ $now = wfTime();
+ $elapsed = $now - $wgRequestTime;
$prof = wfGetProfilingOutput( $wgRequestTime, $elapsed );
$forward = '';
if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
@@ -260,7 +232,8 @@ function logProfilingData() {
$forward .= ' from ' . $_SERVER['HTTP_FROM'];
if( $forward )
$forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
- if( is_object($wgUser) && $wgUser->isAnon() )
+ // Don't unstub $wgUser at this late stage just for statistics purposes
+ if( StubObject::isRealObject($wgUser) && $wgUser->isAnon() )
$forward .= ' anon';
$log = sprintf( "%s\t%04.3f\t%s\n",
gmdate( 'YmdHis' ), $elapsed,
@@ -418,12 +391,12 @@ function wfMsgReal( $key, $args, $useDB = true, $forContent=false, $transform =
function wfMsgWeirdKey ( $key ) {
$subsource = str_replace ( ' ' , '_' , $key ) ;
$source = wfMsgForContentNoTrans( $subsource ) ;
- if ( $source == "&lt;{$subsource}&gt;" ) {
+ if ( wfEmptyMsg( $subsource, $source) ) {
# Try again with first char lower case
$subsource = strtolower ( substr ( $subsource , 0 , 1 ) ) . substr ( $subsource , 1 ) ;
$source = wfMsgForContentNoTrans( $subsource ) ;
}
- if ( $source == "&lt;{$subsource}&gt;" ) {
+ if ( wfEmptyMsg( $subsource, $source ) ) {
# Didn't work either, return blank text
$source = "" ;
}
@@ -439,7 +412,7 @@ function wfMsgWeirdKey ( $key ) {
* @private
*/
function wfMsgGetKey( $key, $useDB, $forContent = false, $transform = true ) {
- global $wgParser, $wgMsgParserOptions, $wgContLang, $wgMessageCache, $wgLang;
+ global $wgParser, $wgContLang, $wgMessageCache, $wgLang;
if ( is_object( $wgMessageCache ) )
$transstat = $wgMessageCache->getTransform();
@@ -466,7 +439,7 @@ function wfMsgGetKey( $key, $useDB, $forContent = false, $transform = true ) {
if($message === false)
$message = Language::getMessage($key);
if ( $transform && strstr( $message, '{{' ) !== false ) {
- $message = $wgParser->transformMsg($message, $wgMsgParserOptions);
+ $message = $wgParser->transformMsg($message, $wgMessageCache->getParserOptions() );
}
}
@@ -621,8 +594,7 @@ function wfAbruptExit( $error = false ){
wfDebug('WARNING: Abrupt exit\n');
}
- wfProfileClose();
- logProfilingData();
+ wfLogProfilingData();
if ( !$error ) {
$wgLoadBalancer->closeAll();
@@ -867,8 +839,8 @@ function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
*/
function wfEscapeWikiText( $text ) {
$text = str_replace(
- array( '[', '|', '\'', 'ISBN ' , '://' , "\n=", '{{' ),
- array( '&#91;', '&#124;', '&#39;', 'ISBN&#32;', '&#58;//' , "\n&#61;", '&#123;&#123;' ),
+ array( '[', '|', '\'', 'ISBN ', 'RFC ', '://', "\n=", '{{' ),
+ array( '&#91;', '&#124;', '&#39;', 'ISBN&#32;', 'RFC&#32;', '&#58;//', "\n&#61;", '&#123;&#123;' ),
htmlspecialchars($text) );
return $text;
}
@@ -1296,6 +1268,11 @@ define('TS_EXIF', 5);
define('TS_ORACLE', 6);
/**
+ * Postgres format time.
+ */
+define('TS_POSTGRES', 7);
+
+/**
* @param mixed $outputtype A timestamp in one of the supported formats, the
* function will autodetect which format is supplied
* and act accordingly.
@@ -1329,6 +1306,10 @@ function wfTimestamp($outputtype=TS_UNIX,$ts=0) {
# TS_ISO_8601
$uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
(int)$da[2],(int)$da[3],(int)$da[1]);
+ } elseif (preg_match("/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)[\+\- ](\d\d)$/",$ts,$da)) {
+ # TS_POSTGRES
+ $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
+ (int)$da[2],(int)$da[3],(int)$da[1]);
} else {
# Bogus value; fall back to the epoch...
wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
@@ -1352,6 +1333,8 @@ function wfTimestamp($outputtype=TS_UNIX,$ts=0) {
return gmdate( 'D, d M Y H:i:s', $uts ) . ' GMT';
case TS_ORACLE:
return gmdate( 'd-M-y h.i.s A', $uts) . ' +00:00';
+ case TS_POSTGRES:
+ return gmdate( 'Y-m-d H:i:s', $uts) . ' GMT';
default:
throw new MWException( 'wfTimestamp() called with illegal output type.');
}
@@ -1395,18 +1378,18 @@ function swap( &$x, &$y ) {
}
function wfGetCachedNotice( $name ) {
- global $wgOut, $parserMemc, $wgDBname;
+ global $wgOut, $parserMemc;
$fname = 'wfGetCachedNotice';
wfProfileIn( $fname );
$needParse = false;
$notice = wfMsgForContent( $name );
- if( $notice == '&lt;'. $name . ';&gt' || $notice == '-' ) {
+ if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) {
wfProfileOut( $fname );
return( false );
}
- $cachedNotice = $parserMemc->get( $wgDBname . ':' . $name );
+ $cachedNotice = $parserMemc->get( wfMemcKey( $name ) );
if( is_array( $cachedNotice ) ) {
if( md5( $notice ) == $cachedNotice['hash'] ) {
$notice = $cachedNotice['html'];
@@ -1420,7 +1403,7 @@ function wfGetCachedNotice( $name ) {
if( $needParse ) {
if( is_object( $wgOut ) ) {
$parsed = $wgOut->parse( $notice );
- $parserMemc->set( $wgDBname . ':' . $name, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
+ $parserMemc->set( wfMemcKey( $name ), array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
$notice = $parsed;
} else {
wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' );
@@ -1480,37 +1463,14 @@ function wfGetSiteNotice() {
return $siteNotice;
}
-/** Global singleton instance of MimeMagic. This is initialized on demand,
-* please always use the wfGetMimeMagic() function to get the instance.
-*
-* @private
-*/
-$wgMimeMagic= NULL;
-
-/** Factory functions for the global MimeMagic object.
-* This function always returns the same singleton instance of MimeMagic.
-* That objects will be instantiated on the first call to this function.
-* If needed, the MimeMagic.php file is automatically included by this function.
-* @return MimeMagic the global MimeMagic objects.
-*/
+/**
+ * BC wrapper for MimeMagic::singleton()
+ * @deprecated
+ */
function &wfGetMimeMagic() {
- global $wgMimeMagic;
-
- if (!is_null($wgMimeMagic)) {
- return $wgMimeMagic;
- }
-
- if (!class_exists("MimeMagic")) {
- #include on demand
- require_once("MimeMagic.php");
- }
-
- $wgMimeMagic= new MimeMagic();
-
- return $wgMimeMagic;
+ return MimeMagic::singleton();
}
-
/**
* Tries to get the system directory for temporary files.
* The TMPDIR, TMP, and TEMP environment variables are checked in sequence,
@@ -1582,8 +1542,8 @@ function wfMkdirParents( $fullDir, $mode = 0777 ) {
* Increment a statistics counter
*/
function wfIncrStats( $key ) {
- global $wgDBname, $wgMemc;
- $key = "$wgDBname:stats:$key";
+ global $wgMemc;
+ $key = wfMemcKey( 'stats', $key );
if ( is_null( $wgMemc->incr( $key ) ) ) {
$wgMemc->add( $key, 1 );
}
@@ -1689,7 +1649,7 @@ function wfUrlProtocols() {
* @return collected stdout as a string (trailing newlines stripped)
*/
function wfShellExec( $cmd, &$retval=null ) {
- global $IP, $wgMaxShellMemory;
+ global $IP, $wgMaxShellMemory, $wgMaxShellFileSize;
if( ini_get( 'safe_mode' ) ) {
wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
@@ -1700,11 +1660,12 @@ function wfShellExec( $cmd, &$retval=null ) {
if ( php_uname( 's' ) == 'Linux' ) {
$time = ini_get( 'max_execution_time' );
$mem = intval( $wgMaxShellMemory );
+ $filesize = intval( $wgMaxShellFileSize );
if ( $time > 0 && $mem > 0 ) {
- $script = "$IP/bin/ulimit.sh";
+ $script = "$IP/bin/ulimit-tvf.sh";
if ( is_executable( $script ) ) {
- $cmd = escapeshellarg( $script ) . " $time $mem $cmd";
+ $cmd = escapeshellarg( $script ) . " $time $mem $filesize $cmd";
}
}
} elseif ( php_uname( 's' ) == 'Windows NT' ) {
@@ -2002,4 +1963,104 @@ function wfIsLocalURL( $url ) {
return Http::isLocalURL( $url );
}
+/**
+ * Initialise php session
+ */
+function wfSetupSession() {
+ global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
+ if( $wgSessionsInMemcached ) {
+ require_once( 'MemcachedSessions.php' );
+ } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
+ # If it's left on 'user' or another setting from another
+ # application, it will end up failing. Try to recover.
+ ini_set ( 'session.save_handler', 'files' );
+ }
+ session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
+ session_cache_limiter( 'private, must-revalidate' );
+ @session_start();
+}
+
+/**
+ * Get an object from the precompiled serialized directory
+ *
+ * @return mixed The variable on success, false on failure
+ */
+function wfGetPrecompiledData( $name ) {
+ global $IP;
+
+ $file = "$IP/serialized/$name";
+ if ( file_exists( $file ) ) {
+ $blob = file_get_contents( $file );
+ if ( $blob ) {
+ return unserialize( $blob );
+ }
+ }
+ return false;
+}
+
+function wfGetCaller( $level = 2 ) {
+ $backtrace = debug_backtrace();
+ if ( isset( $backtrace[$level] ) ) {
+ if ( isset( $backtrace[$level]['class'] ) ) {
+ $caller = $backtrace[$level]['class'] . '::' . $backtrace[$level]['function'];
+ } else {
+ $caller = $backtrace[$level]['function'];
+ }
+ } else {
+ $caller = 'unknown';
+ }
+ return $caller;
+}
+
+/** Return a string consisting all callers in stack, somewhat useful sometimes for profiling specific points */
+function wfGetAllCallers() {
+ return implode('/', array_map(
+ create_function('$frame','
+ return isset( $frame["class"] )?
+ $frame["class"]."::".$frame["function"]:
+ $frame["function"];
+ '),
+ array_reverse(debug_backtrace())));
+}
+
+/**
+ * Get a cache key
+ */
+function wfMemcKey( /*... */ ) {
+ global $wgDBprefix, $wgDBname;
+ $args = func_get_args();
+ if ( $wgDBprefix ) {
+ $key = "$wgDBname-$wgDBprefix:" . implode( ':', $args );
+ } else {
+ $key = $wgDBname . ':' . implode( ':', $args );
+ }
+ return $key;
+}
+
+/**
+ * Get a cache key for a foreign DB
+ */
+function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
+ $args = array_slice( func_get_args(), 2 );
+ if ( $prefix ) {
+ $key = "$db-$prefix:" . implode( ':', $args );
+ } else {
+ $key = $db . ':' . implode( ':', $args );
+ }
+ return $key;
+}
+
+/**
+ * Get an ASCII string identifying this wiki
+ * This is used as a prefix in memcached keys
+ */
+function wfWikiID() {
+ global $wgDBprefix, $wgDBname;
+ if ( $wgDBprefix ) {
+ return "$wgDBname-$wgDBprefix";
+ } else {
+ return $wgDBname;
+ }
+}
+
?>
diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php
index c3d74b20..3ee85859 100644
--- a/includes/HTMLForm.php
+++ b/includes/HTMLForm.php
@@ -71,7 +71,7 @@ class HTMLForm {
( $checked ? ' checked="checked"' : '' ) . " />" . wfMsg( $this->mName.'-'.$varname.'-'.$value ) .
"</label></div>\n";
}
- return $this->fieldset( $this->mName.'-'.$varname, $s );
+ return $this->fieldset( $varname, $s );
}
/**
@@ -109,10 +109,11 @@ class HTMLForm {
}
} // end class
-
-// functions used by SpecialUserrights.php
-
/** Build a select with all defined groups
+ *
+ * used by SpecialUserrights.php
+ * @todo move it to there, and don't forget to copy it for SpecialMakesysop.php
+ *
* @param $selectname String: name of this element. Name of form is automaticly prefixed.
* @param $selectmsg String: FIXME
* @param $selected Array: array of element selected when posted. Only multiples will show them.
@@ -154,24 +155,4 @@ function HTMLSelectGroups($selectname, $selectmsg, $selected=array(), $multiple=
return $out;
}
-/** Build a select with all existent rights
- * @param $selected Array: Names(?) of user rights that should be selected.
- * @return string HTML select.
- */
-function HTMLSelectRights($selected='') {
- global $wgAvailableRights;
- $out = '<select name="editgroup-getrights[]" multiple="multiple">';
- $groupRights = explode(',',$selected);
-
- foreach($wgAvailableRights as $right) {
-
- // check box when right exist
- if(in_array($right, $groupRights)) { $selected = 'selected="selected" '; }
- else { $selected = ''; }
-
- $out .= '<option value="'.$right.'" '.$selected.'>'.$right."</option>\n";
- }
- $out .= "</select>\n";
- return $out;
-}
?>
diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php
index 8f5d3624..357c1d48 100644
--- a/includes/HistoryBlob.php
+++ b/includes/HistoryBlob.php
@@ -231,7 +231,6 @@ class HistoryBlobStub {
wfProfileOut( $fname );
return false;
}
- require_once('ExternalStore.php');
$row->old_text=ExternalStore::fetchFromUrl($url);
}
diff --git a/includes/Hooks.php b/includes/Hooks.php
index 4daffaf3..575a28c5 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -64,7 +64,7 @@ function wfRunHooks($event, $args = null) {
if (count($hook) < 1) {
throw new MWException("Empty array in hooks for " . $event . "\n");
} else if (is_object($hook[0])) {
- $object =& $wgHooks[$event][$index][0];
+ $object = $wgHooks[$event][$index][0];
if (count($hook) < 2) {
$method = "on" . $event;
} else {
@@ -87,7 +87,7 @@ function wfRunHooks($event, $args = null) {
} else if (is_string($hook)) { # functions look like strings, too
$func = $hook;
} else if (is_object($hook)) {
- $object =& $wgHooks[$event][$index];
+ $object = $wgHooks[$event][$index];
$method = "on" . $event;
} else {
throw new MWException("Unknown datatype in hooks for " . $event . "\n");
@@ -101,18 +101,18 @@ function wfRunHooks($event, $args = null) {
$hook_args = $args;
}
-
if ( isset( $object ) ) {
$func = get_class( $object ) . '::' . $method;
+ $callback = array( $object, $method );
+ } elseif ( false !== ( $pos = strpos( '::', $func ) ) ) {
+ $callback = array( substr( $func, 0, $pos ), substr( $func, $pos + 2 ) );
+ } else {
+ $callback = $func;
}
/* Call the hook. */
wfProfileIn( $func );
- if( isset( $object ) ) {
- $retval = call_user_func_array(array(&$object, $method), $hook_args);
- } else {
- $retval = call_user_func_array($func, $hook_args);
- }
+ $retval = call_user_func_array( $callback, $hook_args );
wfProfileOut( $func );
/* String return is an error; false return means stop processing. */
diff --git a/includes/IP.php b/includes/IP.php
new file mode 100644
index 00000000..f3ff3427
--- /dev/null
+++ b/includes/IP.php
@@ -0,0 +1,211 @@
+<?php
+/*
+ * Collection of public static functions to play with IP address
+ * and IP blocks.
+ *
+ * @Author "Ashar Voultoiz" <hashar@altern.org>
+ * @License GPL v2 or later
+ */
+
+// Some regex definition to "play" with IP address and IP address blocks
+
+// An IP is made of 4 bytes from x00 to xFF which is d0 to d255
+define( 'RE_IP_BYTE', '(25[0-5]|2[0-4]\d|1?\d{1,2})');
+define( 'RE_IP_ADD' , RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
+// An IP block is an IP address and a prefix (d1 to d32)
+define( 'RE_IP_PREFIX' , '(3[0-2]|[12]?\d)');
+define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX);
+
+class IP {
+
+ /**
+ * Validate an IP address.
+ * @return boolean True if it is valid.
+ */
+ public static function isValid( $ip ) {
+ return preg_match( '/^' . RE_IP_ADD . '$/', $ip, $matches) ;
+ }
+
+ /**
+ * Validate an IP Block.
+ * @return boolean True if it is valid.
+ */
+ public static function isValidBlock( $ipblock ) {
+ return ( count(self::toArray($ipblock)) == 1 + 5 );
+ }
+
+ /**
+ * Determine if an IP address really is an IP address, and if it is public,
+ * i.e. not RFC 1918 or similar
+ * Comes from ProxyTools.php
+ */
+ public static function isPublic( $ip ) {
+ $n = IP::toUnsigned( $ip );
+ if ( !$n ) {
+ return false;
+ }
+
+ // ip2long accepts incomplete addresses, as well as some addresses
+ // followed by garbage characters. Check that it's really valid.
+ if( $ip != long2ip( $n ) ) {
+ return false;
+ }
+
+ static $privateRanges = false;
+ if ( !$privateRanges ) {
+ $privateRanges = array(
+ array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
+ array( '172.16.0.0', '172.31.255.255' ), # "
+ array( '192.168.0.0', '192.168.255.255' ), # "
+ array( '0.0.0.0', '0.255.255.255' ), # this network
+ array( '127.0.0.0', '127.255.255.255' ), # loopback
+ );
+ }
+
+ foreach ( $privateRanges as $r ) {
+ $start = IP::toUnsigned( $r[0] );
+ $end = IP::toUnsigned( $r[1] );
+ if ( $n >= $start && $n <= $end ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Split out an IP block as an array of 4 bytes and a mask,
+ * return false if it cant be determined
+ *
+ * @parameter $ip string A quad dotted IP address
+ * @return array
+ */
+ public static function toArray( $ipblock ) {
+ if(! preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
+ return false;
+ } else {
+ return $matches;
+ }
+ }
+
+ /**
+ * Return a zero-padded hexadecimal representation of an IP address.
+ *
+ * Hexadecimal addresses are used because they can easily be extended to
+ * IPv6 support. To separate the ranges, the return value from this
+ * function for an IPv6 address will be prefixed with "v6-", a non-
+ * hexadecimal string which sorts after the IPv4 addresses.
+ *
+ * @param $ip Quad dotted IP address.
+ */
+ public static function toHex( $ip ) {
+ $n = self::toUnsigned( $ip );
+ if ( $n !== false ) {
+ $n = sprintf( '%08X', $n );
+ }
+ return $n;
+ }
+
+ /**
+ * Given an IP address in dotted-quad notation, returns an unsigned integer.
+ * Like ip2long() except that it actually works and has a consistent error return value.
+ * Comes from ProxyTools.php
+ * @param $ip Quad dotted IP address.
+ */
+ public static function toUnsigned( $ip ) {
+ if ( $ip == '255.255.255.255' ) {
+ $n = -1;
+ } else {
+ $n = ip2long( $ip );
+ if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version
+ $n = false;
+ }
+ }
+ if ( $n < 0 ) {
+ $n += pow( 2, 32 );
+ }
+ return $n;
+ }
+
+ /**
+ * Convert a dotted-quad IP to a signed integer
+ * Returns false on failure
+ */
+ public static function toSigned( $ip ) {
+ if ( $ip == '255.255.255.255' ) {
+ $n = -1;
+ } else {
+ $n = ip2long( $ip );
+ if ( $n == -1 ) {
+ $n = false;
+ }
+ }
+ return $n;
+ }
+
+ /**
+ * Convert a network specification in CIDR notation to an integer network and a number of bits
+ */
+ public static function parseCIDR( $range ) {
+ $parts = explode( '/', $range, 2 );
+ if ( count( $parts ) != 2 ) {
+ return array( false, false );
+ }
+ $network = IP::toSigned( $parts[0] );
+ if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
+ $bits = $parts[1];
+ if ( $bits == 0 ) {
+ $network = 0;
+ } else {
+ $network &= ~((1 << (32 - $bits)) - 1);
+ }
+ # Convert to unsigned
+ if ( $network < 0 ) {
+ $network += pow( 2, 32 );
+ }
+ } else {
+ $network = false;
+ $bits = false;
+ }
+ return array( $network, $bits );
+ }
+
+ /**
+ * Given a string range in a number of formats, return the start and end of
+ * the range in hexadecimal.
+ *
+ * Formats are:
+ * 1.2.3.4/24 CIDR
+ * 1.2.3.4 - 1.2.3.5 Explicit range
+ * 1.2.3.4 Single IP
+ */
+ public static function parseRange( $range ) {
+ if ( strpos( $range, '/' ) !== false ) {
+ # CIDR
+ list( $network, $bits ) = IP::parseCIDR( $range );
+ if ( $network === false ) {
+ $start = $end = false;
+ } else {
+ $start = sprintf( '%08X', $network );
+ $end = sprintf( '%08X', $network + pow( 2, (32 - $bits) ) - 1 );
+ }
+ } elseif ( strpos( $range, '-' ) !== false ) {
+ # Explicit range
+ list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+ if ( $start > $end ) {
+ $start = $end = false;
+ } else {
+ $start = IP::toHex( $start );
+ $end = IP::toHex( $end );
+ }
+ } else {
+ # Single IP
+ $start = $end = IP::toHex( $range );
+ }
+ if ( $start === false || $end === false ) {
+ return array( false, false );
+ } else {
+ return array( $start, $end );
+ }
+ }
+}
+?>
diff --git a/includes/Image.php b/includes/Image.php
index 185d732a..55e53e26 100644
--- a/includes/Image.php
+++ b/includes/Image.php
@@ -46,6 +46,7 @@ class Image
$size, # Size in bytes (loadFromXxx)
$metadata, # Metadata
$dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
+ $page, # Page to render when creating thumbnails
$lastError; # Error string associated with a thumbnail display error
@@ -86,6 +87,7 @@ class Image
$this->extension = Image::normalizeExtension( $n ?
substr( $this->name, $n + 1 ) : '' );
$this->historyLine = 0;
+ $this->page = 1;
$this->dataLoaded = false;
}
@@ -119,12 +121,12 @@ class Image
* Returns an array, first element is the local cache key, second is the shared cache key, if there is one
*/
function getCacheKeys( ) {
- global $wgDBname, $wgUseSharedUploads, $wgSharedUploadDBname, $wgCacheSharedUploads;
+ global $wgUseSharedUploads, $wgSharedUploadDBname, $wgCacheSharedUploads;
$hashedName = md5($this->name);
- $keys = array( "$wgDBname:Image:$hashedName" );
+ $keys = array( wfMemcKey( 'Image', $hashedName ) );
if ( $wgUseSharedUploads && $wgSharedUploadDBname && $wgCacheSharedUploads ) {
- $keys[] = "$wgSharedUploadDBname:Image:$hashedName";
+ $keys[] = wfForeignMemcKey( $wgSharedUploadDBname, false, 'Image', $hashedName );
}
return $keys;
}
@@ -142,7 +144,7 @@ class Image
// Check if the key existed and belongs to this version of MediaWiki
if (!empty($cachedValues) && is_array($cachedValues)
&& isset($cachedValues['version']) && ( $cachedValues['version'] == MW_IMAGE_VERSION )
- && $cachedValues['fileExists'] && isset( $cachedValues['mime'] ) && isset( $cachedValues['metadata'] ) )
+ && isset( $cachedValues['mime'] ) && isset( $cachedValues['metadata'] ) )
{
if ( $wgUseSharedUploads && $cachedValues['fromShared']) {
# if this is shared file, we need to check if image
@@ -200,13 +202,13 @@ class Image
* Save the image metadata to memcached
*/
function saveToCache() {
- global $wgMemc;
+ global $wgMemc, $wgUseSharedUploads;
$this->load();
$keys = $this->getCacheKeys();
- if ( $this->fileExists ) {
- // We can't cache negative metadata for non-existent files,
- // because if the file later appears in commons, the local
- // keys won't be purged.
+ // We can't cache negative metadata for non-existent files,
+ // because if the file later appears in commons, the local
+ // keys won't be purged.
+ if ( $this->fileExists || !$wgUseSharedUploads ) {
$cachedValues = array(
'version' => MW_IMAGE_VERSION,
'name' => $this->name,
@@ -258,7 +260,7 @@ class Image
if ( $this->fileExists ) {
- $magic=& wfGetMimeMagic();
+ $magic=& MimeMagic::singleton();
$this->mime = $magic->guessMimeType($this->imagePath,true);
$this->type = $magic->getMediaType($this->imagePath,$this->mime);
@@ -266,7 +268,7 @@ class Image
# Get size in bytes
$this->size = filesize( $this->imagePath );
- $magic=& wfGetMimeMagic();
+ $magic=& MimeMagic::singleton();
# Height and width
wfSuppressWarnings();
@@ -307,7 +309,11 @@ class Image
$this->dataLoaded = true;
- $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) );
+ if ( $this->mime == 'image/vnd.djvu' ) {
+ $this->metadata = $deja->retrieveMetaData();
+ } else {
+ $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) );
+ }
if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits'];
else $this->bits = 0;
@@ -323,7 +329,6 @@ class Image
wfProfileIn( __METHOD__ );
$dbr =& wfGetDB( DB_SLAVE );
-
$this->checkDBSchema($dbr);
$row = $dbr->selectRow( 'image',
@@ -374,6 +379,7 @@ class Image
$this->fileExists = false;
$this->fromSharedDirectory = false;
$this->metadata = serialize ( array() ) ;
+ $this->mime = false;
}
# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
@@ -416,9 +422,12 @@ class Image
$this->loadFromDB();
if ( !$wgSharedUploadDBname && $wgUseSharedUploads ) {
$this->loadFromFile();
- } elseif ( $this->fileExists ) {
+ } elseif ( $this->fileExists || !$wgUseSharedUploads ) {
+ // We can do negative caching for local images, because the cache
+ // will be purged on upload. But we can't do it when shared images
+ // are enabled, since updates to that won't purge foreign caches.
$this->saveToCache();
- }
+ }
}
$this->dataLoaded = true;
}
@@ -601,7 +610,7 @@ class Image
* @todo remember the result of this check.
*/
function canRender() {
- global $wgUseImageMagick;
+ global $wgUseImageMagick, $wgDjvuRenderer;
if( $this->getWidth()<=0 || $this->getHeight()<=0 ) return false;
@@ -647,6 +656,7 @@ class Image
if ( $mime === 'image/vnd.wap.wbmp'
|| $mime === 'image/x-xbitmap' ) return true;
}
+ if ( $mime === 'image/vnd.djvu' && isset( $wgDjvuRenderer ) && $wgDjvuRenderer ) return true;
return false;
}
@@ -734,9 +744,16 @@ class Image
* Return the escapeLocalURL of this image
* @public
*/
- function getEscapeLocalURL() {
+ function getEscapeLocalURL( $query=false) {
$this->getTitle();
- return $this->title->escapeLocalURL();
+ if ( $query === false ) {
+ if ( $this->page != 1 ) {
+ $query = 'page=' . $this->page;
+ } else {
+ $query = '';
+ }
+ }
+ return $this->title->escapeLocalURL( $query );
}
/**
@@ -836,6 +853,9 @@ class Image
*/
function thumbName( $width ) {
$thumb = $width."px-".$this->name;
+ if ( $this->page != 1 ) {
+ $thumb = "page{$this->page}-$thumb";
+ }
if( $this->mustRender() ) {
if( $this->canRender() ) {
@@ -1123,6 +1143,7 @@ class Image
global $wgSVGConverters, $wgSVGConverter;
global $wgUseImageMagick, $wgImageMagickConvertCommand;
global $wgCustomConvertCommand;
+ global $wgDjvuRenderer, $wgDjvuPostProcessor;
$this->load();
@@ -1149,96 +1170,112 @@ class Image
$err = wfShellExec( $cmd, $retval );
wfProfileOut( 'rsvg' );
}
- } elseif ( $wgUseImageMagick ) {
- # use ImageMagick
-
- if ( $this->mime == 'image/jpeg' ) {
- $quality = "-quality 80"; // 80%
- } elseif ( $this->mime == 'image/png' ) {
- $quality = "-quality 95"; // zlib 9, adaptive filtering
- } else {
- $quality = ''; // default
- }
-
- # Specify white background color, will be used for transparent images
- # in Internet Explorer/Windows instead of default black.
-
- # Note, we specify "-size {$width}" and NOT "-size {$width}x{$height}".
- # It seems that ImageMagick has a bug wherein it produces thumbnails of
- # the wrong size in the second case.
-
- $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) .
- " {$quality} -background white -size {$width} ".
- wfEscapeShellArg($this->imagePath) .
- // Coalesce is needed to scale animated GIFs properly (bug 1017).
- ' -coalesce ' .
- // For the -resize option a "!" is needed to force exact size,
- // or ImageMagick may decide your ratio is wrong and slice off
- // a pixel.
- " -resize " . wfEscapeShellArg( "{$width}x{$height}!" ) .
- " -depth 8 " .
- wfEscapeShellArg($thumbPath) . " 2>&1";
- wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n");
- wfProfileIn( 'convert' );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'convert' );
- } elseif( $wgCustomConvertCommand ) {
- # Use a custom convert command
- # Variables: %s %d %w %h
- $src = wfEscapeShellArg( $this->imagePath );
- $dst = wfEscapeShellArg( $thumbPath );
- $cmd = $wgCustomConvertCommand;
- $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
- $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size
- wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" );
- wfProfileIn( 'convert' );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'convert' );
} else {
- # Use PHP's builtin GD library functions.
- #
- # First find out what kind of file this is, and select the correct
- # input routine for this.
-
- $typemap = array(
- 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
- 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( &$this, 'imageJpegWrapper' ) ),
- 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
- 'image/vnd.wap.wmbp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
- 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
- );
- if( !isset( $typemap[$this->mime] ) ) {
- $err = 'Image type not supported';
- wfDebug( "$err\n" );
- return $err;
- }
- list( $loader, $colorStyle, $saveType ) = $typemap[$this->mime];
+ if ( $this->mime === "image/vnd.djvu" && $wgDjvuRenderer ) {
+ // DJVU image
+ // The file contains several images. First, extract the
+ // page in hi-res, if it doesn't yet exist. Then, thumbnail
+ // it.
+
+ $cmd = "{$wgDjvuRenderer} -page={$this->page} -size=${width}x${height} " .
+ wfEscapeShellArg( $this->imagePath ) .
+ " | {$wgDjvuPostProcessor} > " . wfEscapeShellArg($thumbPath);
+ wfProfileIn( 'ddjvu' );
+ wfDebug( "reallyRenderThumb DJVU: $cmd\n" );
+ $err = wfShellExec( $cmd, $retval );
+ wfProfileOut( 'ddjvu' );
- if( !function_exists( $loader ) ) {
- $err = "Incomplete GD library configuration: missing function $loader";
- wfDebug( "$err\n" );
- return $err;
- }
- if( $colorStyle == 'palette' ) {
- $truecolor = false;
- } elseif( $colorStyle == 'truecolor' ) {
- $truecolor = true;
- } elseif( $colorStyle == 'bits' ) {
- $truecolor = ( $this->bits > 8 );
- }
+ } elseif ( $wgUseImageMagick ) {
+ # use ImageMagick
+
+ if ( $this->mime == 'image/jpeg' ) {
+ $quality = "-quality 80"; // 80%
+ } elseif ( $this->mime == 'image/png' ) {
+ $quality = "-quality 95"; // zlib 9, adaptive filtering
+ } else {
+ $quality = ''; // default
+ }
- $src_image = call_user_func( $loader, $this->imagePath );
- if ( $truecolor ) {
- $dst_image = imagecreatetruecolor( $width, $height );
+ # Specify white background color, will be used for transparent images
+ # in Internet Explorer/Windows instead of default black.
+
+ # Note, we specify "-size {$width}" and NOT "-size {$width}x{$height}".
+ # It seems that ImageMagick has a bug wherein it produces thumbnails of
+ # the wrong size in the second case.
+
+ $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) .
+ " {$quality} -background white -size {$width} ".
+ wfEscapeShellArg($this->imagePath) .
+ // Coalesce is needed to scale animated GIFs properly (bug 1017).
+ ' -coalesce ' .
+ // For the -resize option a "!" is needed to force exact size,
+ // or ImageMagick may decide your ratio is wrong and slice off
+ // a pixel.
+ " -thumbnail " . wfEscapeShellArg( "{$width}x{$height}!" ) .
+ " -depth 8 " .
+ wfEscapeShellArg($thumbPath) . " 2>&1";
+ wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n");
+ wfProfileIn( 'convert' );
+ $err = wfShellExec( $cmd, $retval );
+ wfProfileOut( 'convert' );
+ } elseif( $wgCustomConvertCommand ) {
+ # Use a custom convert command
+ # Variables: %s %d %w %h
+ $src = wfEscapeShellArg( $this->imagePath );
+ $dst = wfEscapeShellArg( $thumbPath );
+ $cmd = $wgCustomConvertCommand;
+ $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
+ $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size
+ wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" );
+ wfProfileIn( 'convert' );
+ $err = wfShellExec( $cmd, $retval );
+ wfProfileOut( 'convert' );
} else {
- $dst_image = imagecreate( $width, $height );
+ # Use PHP's builtin GD library functions.
+ #
+ # First find out what kind of file this is, and select the correct
+ # input routine for this.
+
+ $typemap = array(
+ 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
+ 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( &$this, 'imageJpegWrapper' ) ),
+ 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
+ 'image/vnd.wap.wmbp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
+ 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
+ );
+ if( !isset( $typemap[$this->mime] ) ) {
+ $err = 'Image type not supported';
+ wfDebug( "$err\n" );
+ return $err;
+ }
+ list( $loader, $colorStyle, $saveType ) = $typemap[$this->mime];
+
+ if( !function_exists( $loader ) ) {
+ $err = "Incomplete GD library configuration: missing function $loader";
+ wfDebug( "$err\n" );
+ return $err;
+ }
+ if( $colorStyle == 'palette' ) {
+ $truecolor = false;
+ } elseif( $colorStyle == 'truecolor' ) {
+ $truecolor = true;
+ } elseif( $colorStyle == 'bits' ) {
+ $truecolor = ( $this->bits > 8 );
+ }
+
+ $src_image = call_user_func( $loader, $this->imagePath );
+ if ( $truecolor ) {
+ $dst_image = imagecreatetruecolor( $width, $height );
+ } else {
+ $dst_image = imagecreate( $width, $height );
+ }
+ imagecopyresampled( $dst_image, $src_image,
+ 0,0,0,0,
+ $width, $height, $this->width, $this->height );
+ call_user_func( $saveType, $dst_image, $thumbPath );
+ imagedestroy( $dst_image );
+ imagedestroy( $src_image );
}
- imagecopyresampled( $dst_image, $src_image,
- 0,0,0,0,
- $width, $height, $this->width, $this->height );
- call_user_func( $saveType, $dst_image, $thumbPath );
- imagedestroy( $dst_image );
- imagedestroy( $src_image );
}
#
@@ -1367,14 +1404,16 @@ class Image
}
function checkDBSchema(&$db) {
+ static $checkDone = false;
global $wgCheckDBSchema;
- if (!$wgCheckDBSchema) {
+ if (!$wgCheckDBSchema || $checkDone) {
return;
}
# img_name must be unique
if ( !$db->indexUnique( 'image', 'img_name' ) && !$db->indexExists('image','PRIMARY') ) {
throw new MWException( 'Database schema not up to date, please run maintenance/archives/patch-image_name_unique.sql' );
}
+ $checkDone = true;
# new fields must exist
#
@@ -1489,7 +1528,7 @@ class Image
* @return bool
* @static
*/
- function isHashed( $shared ) {
+ public static function isHashed( $shared ) {
global $wgHashedUploadDirectory, $wgHashedSharedUploadDirectory;
return $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory;
}
@@ -1706,7 +1745,7 @@ class Image
function getExifData() {
global $wgRequest;
- if ( $this->metadata === '0' )
+ if ( $this->metadata === '0' || $this->mime == 'image/vnd.djvu' )
return array();
$purge = $wgRequest->getVal( 'action' ) == 'purge';
@@ -2095,7 +2134,7 @@ class Image
$tempFile = $store->filePath( $row->fa_storage_key );
$metadata = serialize( $this->retrieveExifData( $tempFile ) );
- $magic = wfGetMimeMagic();
+ $magic = MimeMagic::singleton();
$mime = $magic->guessMimeType( $tempFile, true );
$media_type = $magic->getMediaType( $tempFile, $mime );
list( $major_mime, $minor_mime ) = self::splitMime( $mime );
@@ -2204,6 +2243,73 @@ class Image
return $revisions;
}
+
+ /**
+ * Select a page from a multipage document. Determines the page used for
+ * rendering thumbnails.
+ *
+ * @param $page Integer: page number, starting with 1
+ */
+ function selectPage( $page ) {
+ wfDebug( __METHOD__." selecting page $page \n" );
+ $this->page = $page;
+ if ( ! $this->dataLoaded ) {
+ $this->load();
+ }
+ if ( ! isset( $this->multiPageXML ) ) {
+ $this->initializeMultiPageXML();
+ }
+ $o = $this->multiPageXML->BODY[0]->OBJECT[$page-1];
+ $this->height = intval( $o['height'] );
+ $this->width = intval( $o['width'] );
+ }
+
+ function initializeMultiPageXML() {
+ #
+ # Check for files uploaded prior to DJVU support activation
+ # They have a '0' in their metadata field.
+ #
+ if ( $this->metadata == '0' ) {
+ $deja = new DjVuImage( $this->imagePath );
+ $this->metadata = $deja->retrieveMetaData();
+ $this->purgeMetadataCache();
+
+ # Update metadata in the database
+ $dbw =& wfGetDB( DB_MASTER );
+ $dbw->update( 'image',
+ array( 'img_metadata' => $this->metadata ),
+ array( 'img_name' => $this->name ),
+ __METHOD__
+ );
+ }
+ wfSuppressWarnings();
+ $this->multiPageXML = new SimpleXMLElement( $this->metadata );
+ wfRestoreWarnings();
+ }
+
+ /**
+ * Returns 'true' if this image is a multipage document, e.g. a DJVU
+ * document.
+ *
+ * @return Bool
+ */
+ function isMultipage() {
+ return ( $this->mime == 'image/vnd.djvu' );
+ }
+
+ /**
+ * Returns the number of pages of a multipage document, or NULL for
+ * documents which aren't multipage documents
+ */
+ function pageCount() {
+ if ( ! $this->isMultipage() ) {
+ return null;
+ }
+ if ( ! isset( $this->multiPageXML ) ) {
+ $this->initializeMultiPageXML();
+ }
+ return count( $this->multiPageXML->xpath( '//OBJECT' ) );
+ }
} //class
diff --git a/includes/ImageFunctions.php b/includes/ImageFunctions.php
index a66b4d79..d182d527 100644
--- a/includes/ImageFunctions.php
+++ b/includes/ImageFunctions.php
@@ -1,223 +1,256 @@
-<?php
-
-/**
- * Returns the image directory of an image
- * The result is an absolute path.
- *
- * This function is called from thumb.php before Setup.php is included
- *
- * @param $fname String: file name of the image file.
- * @public
- */
-function wfImageDir( $fname ) {
- global $wgUploadDirectory, $wgHashedUploadDirectory;
-
- if (!$wgHashedUploadDirectory) { return $wgUploadDirectory; }
-
- $hash = md5( $fname );
- $dest = $wgUploadDirectory . '/' . $hash{0} . '/' . substr( $hash, 0, 2 );
-
- return $dest;
-}
-
-/**
- * Returns the image directory of an image's thubnail
- * The result is an absolute path.
- *
- * This function is called from thumb.php before Setup.php is included
- *
- * @param $fname String: file name of the original image file
- * @param $shared Boolean: (optional) use the shared upload directory (default: 'false').
- * @public
- */
-function wfImageThumbDir( $fname, $shared = false ) {
- $base = wfImageArchiveDir( $fname, 'thumb', $shared );
- if ( Image::isHashed( $shared ) ) {
- $dir = "$base/$fname";
- } else {
- $dir = $base;
- }
-
- return $dir;
-}
-
-/**
- * Old thumbnail directory, kept for conversion
- */
-function wfDeprecatedThumbDir( $thumbName , $subdir='thumb', $shared=false) {
- return wfImageArchiveDir( $thumbName, $subdir, $shared );
-}
-
-/**
- * Returns the image directory of an image's old version
- * The result is an absolute path.
- *
- * This function is called from thumb.php before Setup.php is included
- *
- * @param $fname String: file name of the thumbnail file, including file size prefix.
- * @param $subdir String: subdirectory of the image upload directory that should be used for storing the old version. Default is 'archive'.
- * @param $shared Boolean use the shared upload directory (only relevant for other functions which call this one). Default is 'false'.
- * @public
- */
-function wfImageArchiveDir( $fname , $subdir='archive', $shared=false ) {
- global $wgUploadDirectory, $wgHashedUploadDirectory;
- global $wgSharedUploadDirectory, $wgHashedSharedUploadDirectory;
- $dir = $shared ? $wgSharedUploadDirectory : $wgUploadDirectory;
- $hashdir = $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory;
- if (!$hashdir) { return $dir.'/'.$subdir; }
- $hash = md5( $fname );
-
- return $dir.'/'.$subdir.'/'.$hash[0].'/'.substr( $hash, 0, 2 );
-}
-
-
-/*
- * Return the hash path component of an image path (URL or filesystem),
- * e.g. "/3/3c/", or just "/" if hashing is not used.
- *
- * @param $dbkey The filesystem / database name of the file
- * @param $fromSharedDirectory Use the shared file repository? It may
- * use different hash settings from the local one.
- */
-function wfGetHashPath ( $dbkey, $fromSharedDirectory = false ) {
- if( Image::isHashed( $fromSharedDirectory ) ) {
- $hash = md5($dbkey);
- return '/' . $hash{0} . '/' . substr( $hash, 0, 2 ) . '/';
- } else {
- return '/';
- }
-}
-
-/**
- * Returns the image URL of an image's old version
- *
- * @param $name String: file name of the image file
- * @param $subdir String: (optional) subdirectory of the image upload directory that is used by the old version. Default is 'archive'
- * @public
- */
-function wfImageArchiveUrl( $name, $subdir='archive' ) {
- global $wgUploadPath, $wgHashedUploadDirectory;
-
- if ($wgHashedUploadDirectory) {
- $hash = md5( substr( $name, 15) );
- $url = $wgUploadPath.'/'.$subdir.'/' . $hash{0} . '/' .
- substr( $hash, 0, 2 ) . '/'.$name;
- } else {
- $url = $wgUploadPath.'/'.$subdir.'/'.$name;
- }
- return wfUrlencode($url);
-}
-
-/**
- * Return a rounded pixel equivalent for a labeled CSS/SVG length.
- * http://www.w3.org/TR/SVG11/coords.html#UnitIdentifiers
- *
- * @param $length String: CSS/SVG length.
- * @return Integer: length in pixels
- */
-function wfScaleSVGUnit( $length ) {
- static $unitLength = array(
- 'px' => 1.0,
- 'pt' => 1.25,
- 'pc' => 15.0,
- 'mm' => 3.543307,
- 'cm' => 35.43307,
- 'in' => 90.0,
- '' => 1.0, // "User units" pixels by default
- '%' => 2.0, // Fake it!
- );
- if( preg_match( '/^(\d+(?:\.\d+)?)(em|ex|px|pt|pc|cm|mm|in|%|)$/', $length, $matches ) ) {
- $length = floatval( $matches[1] );
- $unit = $matches[2];
- return round( $length * $unitLength[$unit] );
- } else {
- // Assume pixels
- return round( floatval( $length ) );
- }
-}
-
-/**
- * Compatible with PHP getimagesize()
- * @todo support gzipped SVGZ
- * @todo check XML more carefully
- * @todo sensible defaults
- *
- * @param $filename String: full name of the file (passed to php fopen()).
- * @return array
- */
-function wfGetSVGsize( $filename ) {
- $width = 256;
- $height = 256;
-
- // Read a chunk of the file
- $f = fopen( $filename, "rt" );
- if( !$f ) return false;
- $chunk = fread( $f, 4096 );
- fclose( $f );
-
- // Uber-crappy hack! Run through a real XML parser.
- if( !preg_match( '/<svg\s*([^>]*)\s*>/s', $chunk, $matches ) ) {
- return false;
- }
- $tag = $matches[1];
- if( preg_match( '/\bwidth\s*=\s*("[^"]+"|\'[^\']+\')/s', $tag, $matches ) ) {
- $width = wfScaleSVGUnit( trim( substr( $matches[1], 1, -1 ) ) );
- }
- if( preg_match( '/\bheight\s*=\s*("[^"]+"|\'[^\']+\')/s', $tag, $matches ) ) {
- $height = wfScaleSVGUnit( trim( substr( $matches[1], 1, -1 ) ) );
- }
-
- return array( $width, $height, 'SVG',
- "width=\"$width\" height=\"$height\"" );
-}
-
-/**
- * Determine if an image exists on the 'bad image list'.
- *
- * @param $name String: the image name to check
- * @return bool
- */
-function wfIsBadImage( $name ) {
- static $titleList = false;
- wfProfileIn( __METHOD__ );
- $bad = false;
- if( wfRunHooks( 'BadImage', array( $name, &$bad ) ) ) {
- if( !$titleList ) {
- # Build the list now
- $titleList = array();
- $lines = explode( "\n", wfMsgForContent( 'bad_image_list' ) );
- foreach( $lines as $line ) {
- if( preg_match( '/^\*\s*\[\[:?(.*?)\]\]/i', $line, $matches ) ) {
- $title = Title::newFromText( $matches[1] );
- if( is_object( $title ) && $title->getNamespace() == NS_IMAGE )
- $titleList[ $title->getDBkey() ] = true;
- }
- }
- }
- wfProfileOut( __METHOD__ );
- return array_key_exists( $name, $titleList );
- } else {
- wfProfileOut( __METHOD__ );
- return $bad;
- }
-}
-
-/**
- * Calculate the largest thumbnail width for a given original file size
- * such that the thumbnail's height is at most $maxHeight.
- * @param $boxWidth Integer Width of the thumbnail box.
- * @param $boxHeight Integer Height of the thumbnail box.
- * @param $maxHeight Integer Maximum height expected for the thumbnail.
- * @return Integer.
- */
-function wfFitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
- $idealWidth = $boxWidth * $maxHeight / $boxHeight;
- $roundedUp = ceil( $idealWidth );
- if( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight )
- return floor( $idealWidth );
- else
- return $roundedUp;
-}
-
-
-?>
+<?php
+
+/**
+ * Returns the image directory of an image
+ * The result is an absolute path.
+ *
+ * This function is called from thumb.php before Setup.php is included
+ *
+ * @param $fname String: file name of the image file.
+ * @public
+ */
+function wfImageDir( $fname ) {
+ global $wgUploadDirectory, $wgHashedUploadDirectory;
+
+ if (!$wgHashedUploadDirectory) { return $wgUploadDirectory; }
+
+ $hash = md5( $fname );
+ $dest = $wgUploadDirectory . '/' . $hash{0} . '/' . substr( $hash, 0, 2 );
+
+ return $dest;
+}
+
+/**
+ * Returns the image directory of an image's thubnail
+ * The result is an absolute path.
+ *
+ * This function is called from thumb.php before Setup.php is included
+ *
+ * @param $fname String: file name of the original image file
+ * @param $shared Boolean: (optional) use the shared upload directory (default: 'false').
+ * @public
+ */
+function wfImageThumbDir( $fname, $shared = false ) {
+ $base = wfImageArchiveDir( $fname, 'thumb', $shared );
+ if ( Image::isHashed( $shared ) ) {
+ $dir = "$base/$fname";
+ } else {
+ $dir = $base;
+ }
+
+ return $dir;
+}
+
+/**
+ * Old thumbnail directory, kept for conversion
+ */
+function wfDeprecatedThumbDir( $thumbName , $subdir='thumb', $shared=false) {
+ return wfImageArchiveDir( $thumbName, $subdir, $shared );
+}
+
+/**
+ * Returns the image directory of an image's old version
+ * The result is an absolute path.
+ *
+ * This function is called from thumb.php before Setup.php is included
+ *
+ * @param $fname String: file name of the thumbnail file, including file size prefix.
+ * @param $subdir String: subdirectory of the image upload directory that should be used for storing the old version. Default is 'archive'.
+ * @param $shared Boolean use the shared upload directory (only relevant for other functions which call this one). Default is 'false'.
+ * @public
+ */
+function wfImageArchiveDir( $fname , $subdir='archive', $shared=false ) {
+ global $wgUploadDirectory, $wgHashedUploadDirectory;
+ global $wgSharedUploadDirectory, $wgHashedSharedUploadDirectory;
+ $dir = $shared ? $wgSharedUploadDirectory : $wgUploadDirectory;
+ $hashdir = $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory;
+ if (!$hashdir) { return $dir.'/'.$subdir; }
+ $hash = md5( $fname );
+
+ return $dir.'/'.$subdir.'/'.$hash[0].'/'.substr( $hash, 0, 2 );
+}
+
+
+/*
+ * Return the hash path component of an image path (URL or filesystem),
+ * e.g. "/3/3c/", or just "/" if hashing is not used.
+ *
+ * @param $dbkey The filesystem / database name of the file
+ * @param $fromSharedDirectory Use the shared file repository? It may
+ * use different hash settings from the local one.
+ */
+function wfGetHashPath ( $dbkey, $fromSharedDirectory = false ) {
+ if( Image::isHashed( $fromSharedDirectory ) ) {
+ $hash = md5($dbkey);
+ return '/' . $hash{0} . '/' . substr( $hash, 0, 2 ) . '/';
+ } else {
+ return '/';
+ }
+}
+
+/**
+ * Returns the image URL of an image's old version
+ *
+ * @param $name String: file name of the image file
+ * @param $subdir String: (optional) subdirectory of the image upload directory that is used by the old version. Default is 'archive'
+ * @public
+ */
+function wfImageArchiveUrl( $name, $subdir='archive' ) {
+ global $wgUploadPath, $wgHashedUploadDirectory;
+
+ if ($wgHashedUploadDirectory) {
+ $hash = md5( substr( $name, 15) );
+ $url = $wgUploadPath.'/'.$subdir.'/' . $hash{0} . '/' .
+ substr( $hash, 0, 2 ) . '/'.$name;
+ } else {
+ $url = $wgUploadPath.'/'.$subdir.'/'.$name;
+ }
+ return wfUrlencode($url);
+}
+
+/**
+ * Return a rounded pixel equivalent for a labeled CSS/SVG length.
+ * http://www.w3.org/TR/SVG11/coords.html#UnitIdentifiers
+ *
+ * @param $length String: CSS/SVG length.
+ * @return Integer: length in pixels
+ */
+function wfScaleSVGUnit( $length ) {
+ static $unitLength = array(
+ 'px' => 1.0,
+ 'pt' => 1.25,
+ 'pc' => 15.0,
+ 'mm' => 3.543307,
+ 'cm' => 35.43307,
+ 'in' => 90.0,
+ '' => 1.0, // "User units" pixels by default
+ '%' => 2.0, // Fake it!
+ );
+ if( preg_match( '/^(\d+(?:\.\d+)?)(em|ex|px|pt|pc|cm|mm|in|%|)$/', $length, $matches ) ) {
+ $length = floatval( $matches[1] );
+ $unit = $matches[2];
+ return round( $length * $unitLength[$unit] );
+ } else {
+ // Assume pixels
+ return round( floatval( $length ) );
+ }
+}
+
+/**
+ * Compatible with PHP getimagesize()
+ * @todo support gzipped SVGZ
+ * @todo check XML more carefully
+ * @todo sensible defaults
+ *
+ * @param $filename String: full name of the file (passed to php fopen()).
+ * @return array
+ */
+function wfGetSVGsize( $filename ) {
+ $width = 256;
+ $height = 256;
+
+ // Read a chunk of the file
+ $f = fopen( $filename, "rt" );
+ if( !$f ) return false;
+ $chunk = fread( $f, 4096 );
+ fclose( $f );
+
+ // Uber-crappy hack! Run through a real XML parser.
+ if( !preg_match( '/<svg\s*([^>]*)\s*>/s', $chunk, $matches ) ) {
+ return false;
+ }
+ $tag = $matches[1];
+ if( preg_match( '/\bwidth\s*=\s*("[^"]+"|\'[^\']+\')/s', $tag, $matches ) ) {
+ $width = wfScaleSVGUnit( trim( substr( $matches[1], 1, -1 ) ) );
+ }
+ if( preg_match( '/\bheight\s*=\s*("[^"]+"|\'[^\']+\')/s', $tag, $matches ) ) {
+ $height = wfScaleSVGUnit( trim( substr( $matches[1], 1, -1 ) ) );
+ }
+
+ return array( $width, $height, 'SVG',
+ "width=\"$width\" height=\"$height\"" );
+}
+
+/**
+ * Determine if an image exists on the 'bad image list'.
+ *
+ * The format of MediaWiki:Bad_image_list is as follows:
+ * * Only list items (lines starting with "*") are considered
+ * * The first link on a line must be a link to a bad image
+ * * Any subsequent links on the same line are considered to be exceptions,
+ * i.e. articles where the image may occur inline.
+ *
+ * @param string $name the image name to check
+ * @param Title $contextTitle The page on which the image occurs, if known
+ * @return bool
+ */
+function wfIsBadImage( $name, $contextTitle = false ) {
+ static $badImages = false;
+ wfProfileIn( __METHOD__ );
+
+ # Run the extension hook
+ $bad = false;
+ if( !wfRunHooks( 'BadImage', array( $name, &$bad ) ) ) {
+ wfProfileOut( __METHOD__ );
+ return $bad;
+ }
+
+ if( !$badImages ) {
+ # Build the list now
+ $badImages = array();
+ $lines = explode( "\n", wfMsgForContent( 'bad_image_list' ) );
+ foreach( $lines as $line ) {
+ # List items only
+ if ( substr( $line, 0, 1 ) !== '*' ) {
+ continue;
+ }
+
+ # Find all links
+ if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) {
+ continue;
+ }
+
+ $exceptions = array();
+ $imageDBkey = false;
+ foreach ( $m[1] as $i => $titleText ) {
+ $title = Title::newFromText( $titleText );
+ if ( !is_null( $title ) ) {
+ if ( $i == 0 ) {
+ $imageDBkey = $title->getDBkey();
+ } else {
+ $exceptions[$title->getPrefixedDBkey()] = true;
+ }
+ }
+ }
+
+ if ( $imageDBkey !== false ) {
+ $badImages[$imageDBkey] = $exceptions;
+ }
+ }
+ }
+
+ $contextKey = $contextTitle ? $contextTitle->getPrefixedDBkey() : false;
+ $bad = isset( $badImages[$name] ) && !isset( $badImages[$name][$contextKey] );
+ wfProfileOut( __METHOD__ );
+ return $bad;
+}
+
+/**
+ * Calculate the largest thumbnail width for a given original file size
+ * such that the thumbnail's height is at most $maxHeight.
+ * @param $boxWidth Integer Width of the thumbnail box.
+ * @param $boxHeight Integer Height of the thumbnail box.
+ * @param $maxHeight Integer Maximum height expected for the thumbnail.
+ * @return Integer.
+ */
+function wfFitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
+ $idealWidth = $boxWidth * $maxHeight / $boxHeight;
+ $roundedUp = ceil( $idealWidth );
+ if( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight )
+ return floor( $idealWidth );
+ else
+ return $roundedUp;
+}
+
+
+?>
diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php
index 0935ac30..7ff456b6 100644
--- a/includes/ImageGallery.php
+++ b/includes/ImageGallery.php
@@ -55,7 +55,7 @@ class ImageGallery
*
* @param $skin Skin object
*/
- function useSkin( &$skin ) {
+ function useSkin( $skin ) {
$this->mSkin =& $skin;
}
@@ -82,6 +82,7 @@ class ImageGallery
*/
function add( $image, $html='' ) {
$this->mImages[] = array( &$image, $html );
+ wfDebug( "ImageGallery::add " . $image->getName() . "\n" );
}
/**
@@ -135,7 +136,7 @@ class ImageGallery
function toHTML() {
global $wgLang, $wgIgnoreImageErrors, $wgGenerateThumbnailOnParse;
- $sk =& $this->getSkin();
+ $sk = $this->getSkin();
$s = '<table class="gallery" cellspacing="0" cellpadding="0">';
if( $this->mCaption )
@@ -157,8 +158,7 @@ class ImageGallery
# The image is blacklisted, just show it as a text link.
$thumbhtml = '<div style="height: 152px;">'
. $sk->makeKnownLinkObj( $nt, htmlspecialchars( $nt->getText() ) ) . '</div>';
- }
- else if( !( $thumb = $img->getThumbnail( 120, 120, $wgGenerateThumbnailOnParse ) ) ) {
+ } else if( !( $thumb = $img->getThumbnail( 120, 120, $wgGenerateThumbnailOnParse ) ) ) {
# Error generating thumbnail.
$thumbhtml = '<div style="height: 152px;">'
. htmlspecialchars( $img->getLastError() ) . '</div>';
diff --git a/includes/ImagePage.php b/includes/ImagePage.php
index dac9602d..908dd5cc 100644
--- a/includes/ImagePage.php
+++ b/includes/ImagePage.php
@@ -9,8 +9,6 @@
if( !defined( 'MEDIAWIKI' ) )
die( 1 );
-require_once( 'Image.php' );
-
/**
* Special handling for image description pages
* @package MediaWiki
@@ -165,7 +163,7 @@ class ImagePage extends Article {
}
function openShowImage() {
- global $wgOut, $wgUser, $wgImageLimits, $wgRequest;
+ global $wgOut, $wgUser, $wgImageLimits, $wgRequest, $wgLang;
global $wgUseImageResize, $wgGenerateThumbnailOnParse;
$full_url = $this->img->getURL();
@@ -187,6 +185,12 @@ class ImagePage extends Article {
if ( $this->img->exists() ) {
# image
+ $page = $wgRequest->getIntOrNull( 'page' );
+ if ( ! is_null( $page ) ) {
+ $this->img->selectPage( $page );
+ } else {
+ $page = 1;
+ }
$width = $this->img->getWidth();
$height = $this->img->getHeight();
$showLink = false;
@@ -236,9 +240,50 @@ class ImagePage extends Article {
$url = $this->img->getViewURL();
$showLink = true;
}
+
+ if ( $this->img->isMultipage() ) {
+ $wgOut->addHTML( '<table class="multipageimage"><tr><td>' );
+ }
+
$wgOut->addHTML( '<div class="fullImageLink" id="file">' . $anchoropen .
"<img border=\"0\" src=\"{$url}\" width=\"{$width}\" height=\"{$height}\" alt=\"" .
htmlspecialchars( $wgRequest->getVal( 'image' ) ).'" />' . $anchorclose . '</div>' );
+
+ if ( $this->img->isMultipage() ) {
+ $count = $this->img->pageCount();
+
+ if ( $page > 1 ) {
+ $label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false );
+ $link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page-1) );
+ $this->img->selectPage( $page - 1 );
+ $thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' );
+ } else {
+ $thumb1 = '';
+ }
+
+ if ( $page < $count ) {
+ $label = wfMsg( 'imgmultipagenext' );
+ $this->img->selectPage( $page + 1 );
+ $link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page+1) );
+ $thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' );
+ } else {
+ $thumb2 = '';
+ }
+
+ $select = '<form name="pageselector" action="' . $this->img->getEscapeLocalUrl( '' ) . '" method="GET" onchange="document.pageselector.submit();">' ;
+ $select .= $wgOut->parse( wfMsg( 'imgmultigotopre' ), false ) .
+ ' <select id="pageselector" name="page">';
+ for ( $i=1; $i <= $count; $i++ ) {
+ $select .= Xml::option( $wgLang->formatNum( $i ), $i,
+ $i == $page );
+ }
+ $select .= '</select>' . $wgOut->parse( wfMsg( 'imgmultigotopost' ), false ) .
+ '<input type="submit" value="' .
+ htmlspecialchars( wfMsg( 'imgmultigo' ) ) . '"></form>';
+
+ $wgOut->addHTML( '</td><td><div class="multipageimagenavbox">' .
+ "$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" );
+ }
} else {
#if direct link is allowed but it's not a renderable image, show an icon.
if ($this->img->isSafeFile()) {
@@ -312,10 +357,7 @@ END
$wgOut->addHTML($sharedtext);
if ($wgRepositoryBaseUrl && $wgFetchCommonsDescriptions) {
- require_once("HttpFunctions.php");
- $ur = ini_set('allow_url_fopen', true);
- $text = wfGetHTTP($url . '?action=render');
- ini_set('allow_url_fopen', $ur);
+ $text = Http::get($url . '?action=render');
if ($text)
$this->mExtraDescription = $text;
}
@@ -373,7 +415,7 @@ END
$line = $this->img->nextHistoryLine();
if ( $line ) {
- $list =& new ImageHistoryList( $sk );
+ $list = new ImageHistoryList( $sk );
$s = $list->beginImageHistoryList() .
$list->imageHistoryLine( true, wfTimestamp(TS_MW, $line->img_timestamp),
$this->mTitle->getDBkey(), $line->img_user,
@@ -435,13 +477,14 @@ END
global $wgUser, $wgOut, $wgRequest;
$confirm = $wgRequest->wasPosted();
+ $reason = $wgRequest->getVal( 'wpReason' );
$image = $wgRequest->getVal( 'image' );
$oldimage = $wgRequest->getVal( 'oldimage' );
# Only sysops can delete images. Previously ordinary users could delete
# old revisions, but this is no longer the case.
if ( !$wgUser->isAllowed('delete') ) {
- $wgOut->sysopRequired();
+ $wgOut->permissionRequired( 'delete' );
return;
}
if ( $wgUser->isBlocked() ) {
@@ -465,7 +508,7 @@ END
# Deleting old images doesn't require confirmation
if ( !is_null( $oldimage ) || $confirm ) {
if( $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $oldimage ) ) {
- $this->doDelete();
+ $this->doDelete( $reason );
} else {
$wgOut->showFatalError( wfMsg( 'sessionfailure' ) );
}
@@ -482,13 +525,16 @@ END
return $this->confirmDelete( $q, $wgRequest->getText( 'wpReason' ) );
}
- function doDelete() {
+ /*
+ * Delete an image.
+ * @param $reason User provided reason for deletion.
+ */
+ function doDelete( $reason ) {
global $wgOut, $wgRequest, $wgUseSquid;
global $wgPostCommitUpdateList;
$fname = 'ImagePage::doDelete';
- $reason = $wgRequest->getVal( 'wpReason' );
$oldimage = $wgRequest->getVal( 'oldimage' );
$dbw =& wfGetDB( DB_MASTER );
@@ -576,7 +622,7 @@ END
return;
}
if ( ! $this->mTitle->userCanEdit() ) {
- $wgOut->sysopRequired();
+ $wgOut->readOnlyPage( $this->getContent(), true );
return;
}
if ( $wgUser->isBlocked() ) {
diff --git a/includes/LinkBatch.php b/includes/LinkBatch.php
index e0f0f6fd..061f1b19 100644
--- a/includes/LinkBatch.php
+++ b/includes/LinkBatch.php
@@ -66,7 +66,7 @@ class LinkBatch {
*/
function execute() {
$linkCache =& LinkCache::singleton();
- $this->executeInto( $linkCache );
+ return $this->executeInto( $linkCache );
}
/**
diff --git a/includes/LinkCache.php b/includes/LinkCache.php
index 451b3f0c..8e56225b 100644
--- a/includes/LinkCache.php
+++ b/includes/LinkCache.php
@@ -21,7 +21,7 @@ class LinkCache {
/**
* Get an instance of this class
*/
- function &singleton() {
+ static function &singleton() {
static $instance;
if ( !isset( $instance ) ) {
$instance = new LinkCache;
@@ -37,8 +37,7 @@ class LinkCache {
}
/* private */ function getKey( $title ) {
- global $wgDBname;
- return $wgDBname.':lc:title:'.$title;
+ return wfMemcKey( 'lc', 'title', $title );
}
/**
diff --git a/includes/Linker.php b/includes/Linker.php
index 4a0eafbd..d34971ff 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -456,11 +456,16 @@ class Linker {
/** @todo document */
function makeImageLinkObj( $nt, $label, $alt, $align = '', $width = false, $height = false, $framed = false,
- $thumb = false, $manual_thumb = '' )
+ $thumb = false, $manual_thumb = '', $page = null )
{
global $wgContLang, $wgUser, $wgThumbLimits, $wgGenerateThumbnailOnParse;
$img = new Image( $nt );
+
+ if ( ! is_null( $page ) ) {
+ $img->selectPage( $page );
+ }
+
if ( !$img->allowInlineDisplay() && $img->exists() ) {
return $this->makeKnownLinkObj( $nt );
}
@@ -468,7 +473,7 @@ class Linker {
$url = $img->getViewURL();
$error = $prefix = $postfix = '';
- wfDebug( "makeImageLinkObj: '$width'x'$height'\n" );
+ wfDebug( "makeImageLinkObj: '$width'x'$height', \"$label\"\n" );
if ( 'center' == $align )
{
@@ -564,7 +569,6 @@ class Linker {
*/
function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $boxwidth = 180, $boxheight=false, $framed=false , $manual_thumb = "" ) {
global $wgStylePath, $wgContLang, $wgGenerateThumbnailOnParse;
- $url = $img->getViewURL();
$thumbUrl = '';
$error = '';
@@ -583,7 +587,7 @@ class Linker {
// Use image dimensions, don't scale
$boxwidth = $width;
$boxheight = $height;
- $thumbUrl = $url;
+ $thumbUrl = $img->getViewURL();
} else {
if ( $boxheight === false )
$boxheight = -1;
@@ -626,7 +630,7 @@ class Linker {
$s = "<div class=\"thumb t{$align}\"><div style=\"width:{$oboxwidth}px;\">";
if( $thumbUrl == '' ) {
// Couldn't generate thumbnail? Scale the image client-side.
- $thumbUrl = $url;
+ $thumbUrl = $img->getViewURL();
}
if ( $error ) {
$s .= htmlspecialchars( $error );
@@ -1081,7 +1085,7 @@ class Linker {
*
* @static
*/
- function splitTrail( $trail ) {
+ static function splitTrail( $trail ) {
static $regex = false;
if ( $regex === false ) {
global $wgContLang;
diff --git a/includes/LoadBalancer.php b/includes/LoadBalancer.php
index f985a7b4..3e81aea9 100644
--- a/includes/LoadBalancer.php
+++ b/includes/LoadBalancer.php
@@ -4,26 +4,6 @@
* @package MediaWiki
*/
-/**
- * Depends on the database object
- */
-require_once( 'Database.php' );
-
-# Valid database indexes
-# Operation-based indexes
-define( 'DB_SLAVE', -1 ); # Read from the slave (or only server)
-define( 'DB_MASTER', -2 ); # Write to master (or only server)
-define( 'DB_LAST', -3 ); # Whatever database was used last
-
-# Obsolete aliases
-define( 'DB_READ', -1 );
-define( 'DB_WRITE', -2 );
-
-
-# Scale polling time so that under overload conditions, the database server
-# receives a SHOW STATUS query at an average interval of this many microseconds
-define( 'AVG_STATUS_POLL', 2000 );
-
/**
* Database load balancing object
@@ -38,26 +18,13 @@ class LoadBalancer {
/* private */ var $mWaitForFile, $mWaitForPos, $mWaitTimeout;
/* private */ var $mLaggedSlaveMode, $mLastError = 'Unknown error';
- function LoadBalancer()
- {
- $this->mServers = array();
- $this->mConnections = array();
- $this->mFailFunction = false;
- $this->mReadIndex = -1;
- $this->mForce = -1;
- $this->mLastIndex = -1;
- $this->mErrorConnection = false;
- $this->mAllowLag = false;
- }
-
- function newFromParams( $servers, $failFunction = false, $waitTimeout = 10 )
- {
- $lb = new LoadBalancer;
- $lb->initialise( $servers, $failFunction, $waitTimeout );
- return $lb;
- }
+ /**
+ * Scale polling time so that under overload conditions, the database server
+ * receives a SHOW STATUS query at an average interval of this many microseconds
+ */
+ const AVG_STATUS_POLL = 2000;
- function initialise( $servers, $failFunction = false, $waitTimeout = 10 )
+ function LoadBalancer( $servers, $failFunction = false, $waitTimeout = 10, $waitForMasterNow = false )
{
$this->mServers = $servers;
$this->mFailFunction = $failFunction;
@@ -71,6 +38,8 @@ class LoadBalancer {
$this->mWaitForPos = false;
$this->mWaitTimeout = $waitTimeout;
$this->mLaggedSlaveMode = false;
+ $this->mErrorConnection = false;
+ $this->mAllowLag = false;
foreach( $servers as $i => $server ) {
$this->mLoads[$i] = $server['load'];
@@ -83,6 +52,14 @@ class LoadBalancer {
}
}
}
+ if ( $waitForMasterNow ) {
+ $this->loadMasterPos();
+ }
+ }
+
+ static function newFromParams( $servers, $failFunction = false, $waitTimeout = 10 )
+ {
+ return new LoadBalancer( $servers, $failFunction, $waitTimeout );
}
/**
@@ -180,7 +157,7 @@ class LoadBalancer {
$i = $this->getRandomNonLagged( $loads );
if ( $i === false && count( $loads ) != 0 ) {
# All slaves lagged. Switch to read-only mode
- $wgReadOnly = wfMsgNoDB( 'readonly_lag' );
+ $wgReadOnly = wfMsgNoDBForContent( 'readonly_lag' );
$i = $this->pickRandom( $loads );
}
}
@@ -201,7 +178,7 @@ class LoadBalancer {
# Too much load, back off and wait for a while.
# The sleep time is scaled by the number of threads connected,
# to produce a roughly constant global poll rate.
- $sleepTime = AVG_STATUS_POLL * $status['Threads_connected'];
+ $sleepTime = self::AVG_STATUS_POLL * $status['Threads_connected'];
# If we reach the timeout and exit the loop, don't use it
$i = false;
@@ -442,9 +419,6 @@ class LoadBalancer {
extract( $server );
# Get class for this database type
$class = 'Database' . ucfirst( $type );
- if ( !class_exists( $class ) ) {
- require_once( "$class.php" );
- }
# Create object
$db = new $class( $host, $user, $password, $dbname, 1, $flags );
@@ -625,21 +599,24 @@ class LoadBalancer {
* Results are cached for a short time in memcached
*/
function getLagTimes() {
- global $wgDBname;
-
+ wfProfileIn( __METHOD__ );
$expiry = 5;
$requestRate = 10;
global $wgMemc;
- $times = $wgMemc->get( "$wgDBname:lag_times" );
+ $times = $wgMemc->get( wfMemcKey( 'lag_times' ) );
if ( $times ) {
# Randomly recache with probability rising over $expiry
$elapsed = time() - $times['timestamp'];
$chance = max( 0, ( $expiry - $elapsed ) * $requestRate );
if ( mt_rand( 0, $chance ) != 0 ) {
unset( $times['timestamp'] );
+ wfProfileOut( __METHOD__ );
return $times;
}
+ wfIncrStats( 'lag_cache_miss_expired' );
+ } else {
+ wfIncrStats( 'lag_cache_miss_absent' );
}
# Cache key missing or expired
@@ -655,10 +632,11 @@ class LoadBalancer {
# Add a timestamp key so we know when it was cached
$times['timestamp'] = time();
- $wgMemc->set( "$wgDBname:lag_times", $times, $expiry );
+ $wgMemc->set( wfMemcKey( 'lag_times' ), $times, $expiry );
# But don't give the timestamp to the caller
unset($times['timestamp']);
+ wfProfileOut( __METHOD__ );
return $times;
}
}
diff --git a/includes/LogPage.php b/includes/LogPage.php
index f588105f..954b178f 100644
--- a/includes/LogPage.php
+++ b/includes/LogPage.php
@@ -207,13 +207,13 @@ class LogPage {
* @param string $comment Description associated
* @param array $params Parameters passed later to wfMsg.* functions
*/
- function addEntry( $action, &$target, $comment, $params = array() ) {
+ function addEntry( $action, $target, $comment, $params = array() ) {
if ( !is_array( $params ) ) {
$params = array( $params );
}
$this->action = $action;
- $this->target =& $target;
+ $this->target = $target;
$this->comment = $comment;
$this->params = LogPage::makeParamBlob( $params );
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index c80d2583..68cbe345 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -6,170 +6,21 @@
*/
/**
- * private
- */
-$wgMagicFound = false;
-
-/** Actual keyword to be used is set in Language.php */
-
-$magicWords = array(
- 'MAG_REDIRECT',
- 'MAG_NOTOC',
- 'MAG_START',
- 'MAG_CURRENTMONTH',
- 'MAG_CURRENTMONTHNAME',
- 'MAG_CURRENTMONTHNAMEGEN',
- 'MAG_CURRENTMONTHABBREV',
- 'MAG_CURRENTDAY',
- 'MAG_CURRENTDAY2',
- 'MAG_CURRENTDAYNAME',
- 'MAG_CURRENTYEAR',
- 'MAG_CURRENTTIME',
- 'MAG_NUMBEROFARTICLES',
- 'MAG_SUBST',
- 'MAG_MSG',
- 'MAG_MSGNW',
- 'MAG_NOEDITSECTION',
- 'MAG_END',
- 'MAG_IMG_THUMBNAIL',
- 'MAG_IMG_RIGHT',
- 'MAG_IMG_LEFT',
- 'MAG_IMG_NONE',
- 'MAG_IMG_WIDTH',
- 'MAG_IMG_CENTER',
- 'MAG_INT',
- 'MAG_FORCETOC',
- 'MAG_SITENAME',
- 'MAG_NS',
- 'MAG_LOCALURL',
- 'MAG_LOCALURLE',
- 'MAG_SERVER',
- 'MAG_IMG_FRAMED',
- 'MAG_PAGENAME',
- 'MAG_PAGENAMEE',
- 'MAG_NAMESPACE',
- 'MAG_NAMESPACEE',
- 'MAG_TOC',
- 'MAG_GRAMMAR',
- 'MAG_NOTITLECONVERT',
- 'MAG_NOCONTENTCONVERT',
- 'MAG_CURRENTWEEK',
- 'MAG_CURRENTDOW',
- 'MAG_REVISIONID',
- 'MAG_SCRIPTPATH',
- 'MAG_SERVERNAME',
- 'MAG_NUMBEROFFILES',
- 'MAG_IMG_MANUALTHUMB',
- 'MAG_PLURAL',
- 'MAG_FULLURL',
- 'MAG_FULLURLE',
- 'MAG_LCFIRST',
- 'MAG_UCFIRST',
- 'MAG_LC',
- 'MAG_UC',
- 'MAG_FULLPAGENAME',
- 'MAG_FULLPAGENAMEE',
- 'MAG_RAW',
- 'MAG_SUBPAGENAME',
- 'MAG_SUBPAGENAMEE',
- 'MAG_DISPLAYTITLE',
- 'MAG_TALKSPACE',
- 'MAG_TALKSPACEE',
- 'MAG_SUBJECTSPACE',
- 'MAG_SUBJECTSPACEE',
- 'MAG_TALKPAGENAME',
- 'MAG_TALKPAGENAMEE',
- 'MAG_SUBJECTPAGENAME',
- 'MAG_SUBJECTPAGENAMEE',
- 'MAG_NUMBEROFUSERS',
- 'MAG_RAWSUFFIX',
- 'MAG_NEWSECTIONLINK',
- 'MAG_NUMBEROFPAGES',
- 'MAG_CURRENTVERSION',
- 'MAG_BASEPAGENAME',
- 'MAG_BASEPAGENAMEE',
- 'MAG_URLENCODE',
- 'MAG_CURRENTTIMESTAMP',
- 'MAG_DIRECTIONMARK',
- 'MAG_LANGUAGE',
- 'MAG_CONTENTLANGUAGE',
- 'MAG_PAGESINNAMESPACE',
- 'MAG_NOGALLERY',
- 'MAG_NUMBEROFADMINS',
- 'MAG_FORMATNUM',
-);
-if ( ! defined( 'MEDIAWIKI_INSTALL' ) )
- wfRunHooks( 'MagicWordMagicWords', array( &$magicWords ) );
-
-for ( $i = 0; $i < count( $magicWords ); ++$i )
- define( $magicWords[$i], $i );
-
-$wgVariableIDs = array(
- MAG_CURRENTMONTH,
- MAG_CURRENTMONTHNAME,
- MAG_CURRENTMONTHNAMEGEN,
- MAG_CURRENTMONTHABBREV,
- MAG_CURRENTDAY,
- MAG_CURRENTDAY2,
- MAG_CURRENTDAYNAME,
- MAG_CURRENTYEAR,
- MAG_CURRENTTIME,
- MAG_NUMBEROFARTICLES,
- MAG_NUMBEROFFILES,
- MAG_SITENAME,
- MAG_SERVER,
- MAG_SERVERNAME,
- MAG_SCRIPTPATH,
- MAG_PAGENAME,
- MAG_PAGENAMEE,
- MAG_FULLPAGENAME,
- MAG_FULLPAGENAMEE,
- MAG_NAMESPACE,
- MAG_NAMESPACEE,
- MAG_CURRENTWEEK,
- MAG_CURRENTDOW,
- MAG_REVISIONID,
- MAG_SUBPAGENAME,
- MAG_SUBPAGENAMEE,
- MAG_DISPLAYTITLE,
- MAG_TALKSPACE,
- MAG_TALKSPACEE,
- MAG_SUBJECTSPACE,
- MAG_SUBJECTSPACEE,
- MAG_TALKPAGENAME,
- MAG_TALKPAGENAMEE,
- MAG_SUBJECTPAGENAME,
- MAG_SUBJECTPAGENAMEE,
- MAG_NUMBEROFUSERS,
- MAG_RAWSUFFIX,
- MAG_NEWSECTIONLINK,
- MAG_NUMBEROFPAGES,
- MAG_CURRENTVERSION,
- MAG_BASEPAGENAME,
- MAG_BASEPAGENAMEE,
- MAG_URLENCODE,
- MAG_CURRENTTIMESTAMP,
- MAG_DIRECTIONMARK,
- MAG_LANGUAGE,
- MAG_CONTENTLANGUAGE,
- MAG_PAGESINNAMESPACE,
- MAG_NUMBEROFADMINS,
-);
-if ( ! defined( 'MEDIAWIKI_INSTALL' ) )
- wfRunHooks( 'MagicWordwgVariableIDs', array( &$wgVariableIDs ) );
-
-/**
* This class encapsulates "magic words" such as #redirect, __NOTOC__, etc.
* Usage:
- * if (MagicWord::get( MAG_REDIRECT )->match( $text ) )
+ * if (MagicWord::get( 'redirect' )->match( $text ) )
*
* Possible future improvements:
* * Simultaneous searching for a number of magic words
- * * $wgMagicWords in shared memory
+ * * MagicWord::$mObjects in shared memory
*
* Please avoid reading the data out of one of these objects and then writing
* special case code. If possible, add another match()-like function here.
*
+ * To add magic words in an extension, use the LanguageGetMagic hook. For
+ * magic words which are also Parser variables, add a MagicWordwgVariableIDs
+ * hook. Use string keys.
+ *
* @package MediaWiki
*/
class MagicWord {
@@ -178,7 +29,82 @@ class MagicWord {
*/
var $mId, $mSynonyms, $mCaseSensitive, $mRegex;
var $mRegexStart, $mBaseRegex, $mVariableRegex;
- var $mModified;
+ var $mModified, $mFound;
+
+ static public $mVariableIDsInitialised = false;
+ static public $mVariableIDs = array(
+ 'currentmonth',
+ 'currentmonthname',
+ 'currentmonthnamegen',
+ 'currentmonthabbrev',
+ 'currentday',
+ 'currentday2',
+ 'currentdayname',
+ 'currentyear',
+ 'currenttime',
+ 'currenthour',
+ 'localmonth',
+ 'localmonthname',
+ 'localmonthnamegen',
+ 'localmonthabbrev',
+ 'localday',
+ 'localday2',
+ 'localdayname',
+ 'localyear',
+ 'localtime',
+ 'localhour',
+ 'numberofarticles',
+ 'numberoffiles',
+ 'sitename',
+ 'server',
+ 'servername',
+ 'scriptpath',
+ 'pagename',
+ 'pagenamee',
+ 'fullpagename',
+ 'fullpagenamee',
+ 'namespace',
+ 'namespacee',
+ 'currentweek',
+ 'currentdow',
+ 'localweek',
+ 'localdow',
+ 'revisionid',
+ 'revisionday',
+ 'revisionday2',
+ 'revisionmonth',
+ 'revisionyear',
+ 'revisiontimestamp',
+ 'subpagename',
+ 'subpagenamee',
+ 'displaytitle',
+ 'talkspace',
+ 'talkspacee',
+ 'subjectspace',
+ 'subjectspacee',
+ 'talkpagename',
+ 'talkpagenamee',
+ 'subjectpagename',
+ 'subjectpagenamee',
+ 'numberofusers',
+ 'rawsuffix',
+ 'newsectionlink',
+ 'numberofpages',
+ 'currentversion',
+ 'basepagename',
+ 'basepagenamee',
+ 'urlencode',
+ 'currenttimestamp',
+ 'localtimestamp',
+ 'directionmark',
+ 'language',
+ 'contentlanguage',
+ 'pagesinnamespace',
+ 'numberofadmins',
+ );
+
+ static public $mObjects = array();
+
/**#@-*/
function MagicWord($id = 0, $syn = '', $cs = false) {
@@ -196,18 +122,32 @@ class MagicWord {
* Factory: creates an object representing an ID
* @static
*/
- function &get( $id ) {
- global $wgMagicWords;
-
- if ( !is_array( $wgMagicWords ) ) {
- throw new MWException( "Incorrect initialisation order, \$wgMagicWords does not exist\n" );
- }
- if (!array_key_exists( $id, $wgMagicWords ) ) {
+ static function &get( $id ) {
+ if (!array_key_exists( $id, self::$mObjects ) ) {
$mw = new MagicWord();
$mw->load( $id );
- $wgMagicWords[$id] = $mw;
+ self::$mObjects[$id] = $mw;
+ }
+ return self::$mObjects[$id];
+ }
+
+ /**
+ * Get an array of parser variable IDs
+ */
+ static function getVariableIDs() {
+ if ( !self::$mVariableIDsInitialised ) {
+ # Deprecated constant definition hook, available for extensions that need it
+ $magicWords = array();
+ wfRunHooks( 'MagicWordMagicWords', array( &$magicWords ) );
+ foreach ( $magicWords as $word ) {
+ define( $word, $word );
+ }
+
+ # Get variable IDs
+ wfRunHooks( 'MagicWordwgVariableIDs', array( &self::$mVariableIDs ) );
+ self::$mVariableIDsInitialised = true;
}
- return $wgMagicWords[$id];
+ return self::$mVariableIDs;
}
# Initialises this object with an ID
@@ -215,6 +155,11 @@ class MagicWord {
global $wgContLang;
$this->mId = $id;
$wgContLang->getMagic( $this );
+ if ( !$this->mSynonyms ) {
+ $this->mSynonyms = array( 'dkjsagfjsgashfajsh' );
+ #throw new MWException( "Error: invalid magic word '$id'" );
+ wfDebugLog( 'exception', "Error: invalid magic word '$id'\n" );
+ }
}
/**
@@ -233,7 +178,7 @@ class MagicWord {
$escSyn[] = preg_quote( $synonym, '/' );
$this->mBaseRegex = implode( '|', $escSyn );
- $case = $this->mCaseSensitive ? '' : 'i';
+ $case = $this->mCaseSensitive ? '' : 'iu';
$this->mRegex = "/{$this->mBaseRegex}/{$case}";
$this->mRegexStart = "/^(?:{$this->mBaseRegex})/{$case}";
$this->mVariableRegex = str_replace( "\\$1", "(.*?)", $this->mRegex );
@@ -260,7 +205,7 @@ class MagicWord {
if ( $this->mRegex === '' )
$this->initRegex();
- return $this->mCaseSensitive ? '' : 'i';
+ return $this->mCaseSensitive ? '' : 'iu';
}
/**
@@ -310,14 +255,16 @@ class MagicWord {
$matchcount = preg_match( $this->getVariableStartToEndRegex(), $text, $matches );
if ( $matchcount == 0 ) {
return NULL;
- } elseif ( count($matches) == 1 ) {
- return $matches[0];
} else {
# multiple matched parts (variable match); some will be empty because of
# synonyms. The variable will be the second non-empty one so remove any
# blank elements and re-sort the indices.
+ # See also bug 6526
+
$matches = array_values(array_filter($matches));
- return $matches[1];
+
+ if ( count($matches) == 1 ) { return $matches[0]; }
+ else { return $matches[1]; }
}
}
@@ -327,19 +274,25 @@ class MagicWord {
* input string, removing all instances of the word
*/
function matchAndRemove( &$text ) {
- global $wgMagicFound;
- $wgMagicFound = false;
- $text = preg_replace_callback( $this->getRegex(), 'pregRemoveAndRecord', $text );
- return $wgMagicFound;
+ $this->mFound = false;
+ $text = preg_replace_callback( $this->getRegex(), array( &$this, 'pregRemoveAndRecord' ), $text );
+ return $this->mFound;
}
function matchStartAndRemove( &$text ) {
- global $wgMagicFound;
- $wgMagicFound = false;
- $text = preg_replace_callback( $this->getRegexStart(), 'pregRemoveAndRecord', $text );
- return $wgMagicFound;
+ $this->mFound = false;
+ $text = preg_replace_callback( $this->getRegexStart(), array( &$this, 'pregRemoveAndRecord' ), $text );
+ return $this->mFound;
}
+ /**
+ * Used in matchAndRemove()
+ * @private
+ **/
+ function pregRemoveAndRecord( $match ) {
+ $this->mFound = true;
+ return '';
+ }
/**
* Replaces the word with something else
@@ -425,8 +378,9 @@ class MagicWord {
* lookup in a list of magic words
*/
function addToArray( &$array, $value ) {
+ global $wgContLang;
foreach ( $this->mSynonyms as $syn ) {
- $array[$syn] = $value;
+ $array[$wgContLang->lc($syn)] = $value;
}
}
@@ -435,14 +389,4 @@ class MagicWord {
}
}
-/**
- * Used in matchAndRemove()
- * @private
- **/
-function pregRemoveAndRecord( $match ) {
- global $wgMagicFound;
- $wgMagicFound = true;
- return '';
-}
-
?>
diff --git a/includes/Math.php b/includes/Math.php
index f9d6a605..a8b33984 100644
--- a/includes/Math.php
+++ b/includes/Math.php
@@ -259,7 +259,7 @@ class MathRenderer {
return $path;
}
- function renderMath( $tex ) {
+ public static function renderMath( $tex ) {
global $wgUser;
$math = new MathRenderer( $tex );
$math->setOutputMode( $wgUser->getOption('math'));
diff --git a/includes/MemcachedSessions.php b/includes/MemcachedSessions.php
index af49109c..e2dc52ca 100644
--- a/includes/MemcachedSessions.php
+++ b/includes/MemcachedSessions.php
@@ -13,8 +13,7 @@
* @todo document
*/
function memsess_key( $id ) {
- global $wgDBname;
- return "$wgDBname:session:$id";
+ return wfMemcKey( 'session', $id );
}
/**
diff --git a/includes/MessageCache.php b/includes/MessageCache.php
index c8b7124c..9cab222b 100644
--- a/includes/MessageCache.php
+++ b/includes/MessageCache.php
@@ -36,10 +36,6 @@ class MessageCache {
$this->mMemcKey = $memcPrefix.':messages';
$this->mKeys = false; # initialised on demand
$this->mInitialised = true;
-
- wfProfileIn( __METHOD__.'-parseropt' );
- $this->mParserOptions = new ParserOptions( $u=NULL );
- wfProfileOut( __METHOD__.'-parseropt' );
$this->mParser = null;
# When we first get asked for a message,
@@ -51,18 +47,25 @@ class MessageCache {
wfProfileOut( __METHOD__ );
}
+ function getParserOptions() {
+ if ( !$this->mParserOptions ) {
+ $this->mParserOptions = new ParserOptions;
+ }
+ return $this->mParserOptions;
+ }
+
/**
* Try to load the cache from a local file
*/
function loadFromLocal( $hash ) {
- global $wgLocalMessageCache, $wgDBname;
+ global $wgLocalMessageCache;
$this->mCache = false;
if ( $wgLocalMessageCache === false ) {
return;
}
- $filename = "$wgLocalMessageCache/messages-$wgDBname";
+ $filename = "$wgLocalMessageCache/messages-" . wfWikiID();
wfSuppressWarnings();
$file = fopen( $filename, 'r' );
@@ -75,7 +78,7 @@ class MessageCache {
$localHash = fread( $file, 32 );
if ( $hash == $localHash ) {
// All good, get the rest of it
- $serialized = fread( $file, 1000000 );
+ $serialized = fread( $file, 10000000 );
$this->mCache = unserialize( $serialized );
}
fclose( $file );
@@ -85,13 +88,13 @@ class MessageCache {
* Save the cache to a local file
*/
function saveToLocal( $serialized, $hash ) {
- global $wgLocalMessageCache, $wgDBname;
+ global $wgLocalMessageCache;
if ( $wgLocalMessageCache === false ) {
return;
}
- $filename = "$wgLocalMessageCache/messages-$wgDBname";
+ $filename = "$wgLocalMessageCache/messages-" . wfWikiID();
$oldUmask = umask( 0 );
wfMkdirParents( $wgLocalMessageCache, 0777 );
umask( $oldUmask );
@@ -108,12 +111,12 @@ class MessageCache {
}
function loadFromScript( $hash ) {
- global $wgLocalMessageCache, $wgDBname;
+ global $wgLocalMessageCache;
if ( $wgLocalMessageCache === false ) {
return;
}
- $filename = "$wgLocalMessageCache/messages-$wgDBname";
+ $filename = "$wgLocalMessageCache/messages-" . wfWikiID();
wfSuppressWarnings();
$file = fopen( $filename, 'r' );
@@ -126,16 +129,16 @@ class MessageCache {
if ($hash!=$localHash) {
return;
}
- require("$wgLocalMessageCache/messages-$wgDBname");
+ require("$wgLocalMessageCache/messages-" . wfWikiID());
}
function saveToScript($array, $hash) {
- global $wgLocalMessageCache, $wgDBname;
+ global $wgLocalMessageCache;
if ( $wgLocalMessageCache === false ) {
return;
}
- $filename = "$wgLocalMessageCache/messages-$wgDBname";
+ $filename = "$wgLocalMessageCache/messages-" . wfWikiID();
$oldUmask = umask( 0 );
wfMkdirParents( $wgLocalMessageCache, 0777 );
umask( $oldUmask );
@@ -190,6 +193,9 @@ class MessageCache {
} else {
$this->loadFromScript( $hash );
}
+ if ( $this->mCache ) {
+ wfDebug( "MessageCache::load(): got from local cache\n" );
+ }
}
wfProfileOut( $fname.'-fromlocal' );
@@ -197,18 +203,20 @@ class MessageCache {
if ( !$this->mCache ) {
wfProfileIn( $fname.'-fromcache' );
$this->mCache = $this->mMemc->get( $this->mMemcKey );
-
- # Save to local cache
- if ( $wgLocalMessageCache !== false ) {
- $serialized = serialize( $this->mCache );
- if ( !$hash ) {
- $hash = md5( $serialized );
- $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry );
- }
- if ($wgLocalMessageCacheSerialized) {
- $this->saveToLocal( $serialized,$hash );
- } else {
- $this->saveToScript( $this->mCache, $hash );
+ if ( $this->mCache ) {
+ wfDebug( "MessageCache::load(): got from global cache\n" );
+ # Save to local cache
+ if ( $wgLocalMessageCache !== false ) {
+ $serialized = serialize( $this->mCache );
+ if ( !$hash ) {
+ $hash = md5( $serialized );
+ $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry );
+ }
+ if ($wgLocalMessageCacheSerialized) {
+ $this->saveToLocal( $serialized,$hash );
+ } else {
+ $this->saveToScript( $this->mCache, $hash );
+ }
}
}
wfProfileOut( $fname.'-fromcache' );
@@ -283,7 +291,7 @@ class MessageCache {
* Loads all or main part of cacheable messages from the database
*/
function loadFromDB() {
- global $wgAllMessagesEn, $wgLang;
+ global $wgLang;
$fname = 'MessageCache::loadFromDB';
$dbr =& wfGetDB( DB_SLAVE );
@@ -306,7 +314,8 @@ class MessageCache {
# Negative caching
# Go through the language array and the extension array and make a note of
# any keys missing from the cache
- foreach ( $wgAllMessagesEn as $key => $value ) {
+ $allMessages = Language::getMessagesFor( 'en' );
+ foreach ( $allMessages as $key => $value ) {
$uckey = $wgLang->ucfirst( $key );
if ( !array_key_exists( $uckey, $this->mCache ) ) {
$this->mCache[$uckey] = false;
@@ -314,7 +323,7 @@ class MessageCache {
}
# Make sure all extension messages are available
- wfLoadAllExtensions();
+ MessageCache::loadAllMessages();
# Add them to the cache
foreach ( $this->mExtensionMessages as $key => $value ) {
@@ -332,10 +341,11 @@ class MessageCache {
* Not really needed anymore
*/
function getKeys() {
- global $wgAllMessagesEn, $wgContLang;
+ global $wgContLang;
if ( !$this->mKeys ) {
$this->mKeys = array();
- foreach ( $wgAllMessagesEn as $key => $value ) {
+ $allMessages = Language::getMessagesFor( 'en' );
+ foreach ( $allMessages as $key => $value ) {
$title = $wgContLang->ucfirst( $key );
array_push( $this->mKeys, $title );
}
@@ -351,11 +361,11 @@ class MessageCache {
}
function replace( $title, $text ) {
- global $wgLocalMessageCache, $wgLocalMessageCacheSerialized, $parserMemc, $wgDBname;
+ global $wgLocalMessageCache, $wgLocalMessageCacheSerialized, $parserMemc;
$this->lock();
$this->load();
- $parserMemc->delete("$wgDBname:sidebar");
+ $parserMemc->delete(wfMemcKey('sidebar'));
if ( is_array( $this->mCache ) ) {
$this->mCache[$title] = $text;
$this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry );
@@ -403,17 +413,14 @@ class MessageCache {
$this->mMemc->delete( $lockKey );
}
- function get( $key, $useDB, $forcontent=true, $isfullkey = false ) {
- global $wgContLanguageCode;
+ function get( $key, $useDB = true, $forcontent = true, $isfullkey = false ) {
+ global $wgContLanguageCode, $wgContLang, $wgLang;
if( $forcontent ) {
- global $wgContLang;
$lang =& $wgContLang;
- $langcode = $wgContLanguageCode;
} else {
- global $wgLang, $wgLanguageCode;
$lang =& $wgLang;
- $langcode = $wgLanguageCode;
}
+ $langcode = $lang->getCode();
# If uninitialised, someone is trying to call this halfway through Setup.php
if( !$this->mInitialised ) {
return '&lt;' . htmlspecialchars($key) . '&gt;';
@@ -425,7 +432,7 @@ class MessageCache {
$message = false;
if( !$this->mDisable && $useDB ) {
- $title = $lang->ucfirst( $key );
+ $title = $wgContLang->ucfirst( $key );
if(!$isfullkey && ($langcode != $wgContLanguageCode) ) {
$title .= '/' . $langcode;
}
@@ -442,6 +449,7 @@ class MessageCache {
# Try the array in the language object
if( $message === false ) {
+ #wfDebug( "Trying language object for message $key\n" );
wfSuppressWarnings();
$message = $lang->getMessage( $key );
wfRestoreWarnings();
@@ -460,11 +468,26 @@ class MessageCache {
}
}
+ # Try the array of another language
+ if( $message === false && strpos( $key, '/' ) ) {
+ $message = explode( '/', $key );
+ if ( $message[1] ) {
+ wfSuppressWarnings();
+ $message = Language::getMessageFor( $message[0], $message[1] );
+ wfRestoreWarnings();
+ if ( is_null( $message ) ) {
+ $message = false;
+ }
+ } else {
+ $message = false;
+ }
+ }
+
# Is this a custom message? Try the default language in the db...
if( ($message === false || $message === '-' ) &&
!$this->mDisable && $useDB &&
!$isfullkey && ($langcode != $wgContLanguageCode) ) {
- $message = $this->getFromCache( $lang->ucfirst( $key ) );
+ $message = $this->getFromCache( $wgContLang->ucfirst( $key ) );
}
# Final fallback
@@ -489,6 +512,7 @@ class MessageCache {
if ( $this->mUseCache ) {
$message = $this->mMemc->get( $this->mMemcKey . ':' . $title );
if ( $message == '###NONEXISTENT###' ) {
+ $this->mCache[$title] = false;
return false;
} elseif( !is_null( $message ) ) {
$this->mCache[$title] = $message;
@@ -516,6 +540,7 @@ class MessageCache {
# Negative caching
# Use some special text instead of false, because false gets converted to '' somewhere
$this->mMemc->set( $this->mMemcKey . ':' . $title, '###NONEXISTENT###', $this->mExpiry );
+ $this->mCache[$title] = false;
}
return $message;
@@ -531,7 +556,7 @@ class MessageCache {
}
if ( !$this->mDisableTransform && $this->mParser ) {
if( strpos( $message, '{{' ) !== false ) {
- $message = $this->mParser->transformMsg( $message, $this->mParserOptions );
+ $message = $this->mParser->transformMsg( $message, $this->getParserOptions() );
}
}
return $message;
@@ -570,12 +595,43 @@ class MessageCache {
}
/**
+ * Get the extension messages for a specific language
+ *
+ * @param string $lang The messages language, English by default
+ */
+ function getExtensionMessagesFor( $lang = 'en' ) {
+ wfProfileIn( __METHOD__ );
+ $messages = array();
+ foreach( $this->mExtensionMessages as $key => $message ) {
+ if ( isset( $message[$lang] ) ) {
+ $messages[$key] = $message[$lang];
+ } elseif ( isset( $message['en'] ) ) {
+ $messages[$key] = $message['en'];
+ }
+ }
+ wfProfileOut( __METHOD__ );
+ return $messages;
+ }
+
+ /**
* Clear all stored messages. Mainly used after a mass rebuild.
*/
function clear() {
+ global $wgLocalMessageCache;
if( $this->mUseCache ) {
+ # Global cache
$this->mMemc->delete( $this->mMemcKey );
+ # Invalidate all local caches
+ $this->mMemc->delete( "{$this->mMemcKey}-hash" );
}
}
+
+ static function loadAllMessages() {
+ # Some extensions will load their messages when you load their class file
+ wfLoadAllExtensions();
+ # Others will respond to this hook
+ wfRunHooks( 'LoadAllMessages' );
+ # Still others will respond to neither, they are EVIL. We sometimes need to know!
+ }
}
?>
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index 30861ba3..dd197c31 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -74,7 +74,7 @@ if ($wgLoadFileinfoExtension) {
* file extension,
*
* Instances of this class are stateles, there only needs to be one global instance
-* of MimeMagic. Please use wfGetMimeMagic to get that instance.
+* of MimeMagic. Please use MimeMagic::singleton() to get that instance.
* @package MediaWiki
*/
class MimeMagic {
@@ -97,8 +97,11 @@ class MimeMagic {
*/
var $mExtToMime= NULL;
- /** Initializes the MimeMagic object. This is called by wfGetMimeMagic when instantiation
- * the global MimeMagic singleton object.
+ /** The singleton instance
+ */
+ private static $instance;
+
+ /** Initializes the MimeMagic object. This is called by MimeMagic::singleton().
*
* This constructor parses the mime.types and mime.info files and build internal mappings.
*/
@@ -227,6 +230,16 @@ class MimeMagic {
}
+ /**
+ * Get an instance of this class
+ */
+ static function &singleton() {
+ if ( !isset( self::$instance ) ) {
+ self::$instance = new MimeMagic;
+ }
+ return self::$instance;
+ }
+
/** returns a list of file extensions for a given mime type
* as a space separated string.
*/
@@ -497,13 +510,22 @@ class MimeMagic {
# NOTE: this function is available since PHP 4.3.0, but only if
# PHP was compiled with --with-mime-magic or, before 4.3.2, with --enable-mime-magic.
#
- # On Winodws, you must set mime_magic.magicfile in php.ini to point to the mime.magic file bundeled with PHP;
+ # On Windows, you must set mime_magic.magicfile in php.ini to point to the mime.magic file bundeled with PHP;
# sometimes, this may even be needed under linus/unix.
#
# Also note that this has been DEPRECATED in favor of the fileinfo extension by PECL, see above.
# see http://www.php.net/manual/en/ref.mime-magic.php for details.
$m= mime_content_type($file);
+
+ if ( $m == 'text/plain' ) {
+ // mime_content_type sometimes considers DJVU files to be text/plain.
+ $deja = new DjVuImage( $file );
+ if( $deja->isValid() ) {
+ wfDebug("$fname: (re)detected $file as image/vnd.djvu\n");
+ $m = 'image/vnd.djvu';
+ }
+ }
}
else wfDebug("$fname: no magic mime detector found!\n");
diff --git a/includes/Namespace.php b/includes/Namespace.php
index ab7511d0..73dc2969 100644
--- a/includes/Namespace.php
+++ b/includes/Namespace.php
@@ -49,7 +49,7 @@ class Namespace {
* Check if the given namespace might be moved
* @return bool
*/
- function isMovable( $index ) {
+ static function isMovable( $index ) {
return !( $index < NS_MAIN || $index == NS_IMAGE || $index == NS_CATEGORY );
}
@@ -57,7 +57,7 @@ class Namespace {
* Check if the given namespace is not a talk page
* @return bool
*/
- function isMain( $index ) {
+ static function isMain( $index ) {
return ! Namespace::isTalk( $index );
}
@@ -65,7 +65,7 @@ class Namespace {
* Check if the give namespace is a talk page
* @return bool
*/
- function isTalk( $index ) {
+ static function isTalk( $index ) {
return ($index > NS_MAIN) // Special namespaces are negative
&& ($index % 2); // Talk namespaces are odd-numbered
}
@@ -73,7 +73,7 @@ class Namespace {
/**
* Get the talk namespace corresponding to the given index
*/
- function getTalk( $index ) {
+ static function getTalk( $index ) {
if ( Namespace::isTalk( $index ) ) {
return $index;
} else {
@@ -82,7 +82,7 @@ class Namespace {
}
}
- function getSubject( $index ) {
+ static function getSubject( $index ) {
if ( Namespace::isTalk( $index ) ) {
return $index - 1;
} else {
@@ -93,7 +93,7 @@ class Namespace {
/**
* Returns the canonical (English Wikipedia) name for a given index
*/
- function getCanonicalName( $index ) {
+ static function getCanonicalName( $index ) {
global $wgCanonicalNamespaceNames;
return $wgCanonicalNamespaceNames[$index];
}
@@ -102,7 +102,7 @@ class Namespace {
* Returns the index for a given canonical name, or NULL
* The input *must* be converted to lower case first
*/
- function getCanonicalIndex( $name ) {
+ static function getCanonicalIndex( $name ) {
global $wgCanonicalNamespaceNames;
static $xNamespaces = false;
if ( $xNamespaces === false ) {
@@ -122,7 +122,7 @@ class Namespace {
* Can this namespace ever have a talk namespace?
* @param $index Namespace index
*/
- function canTalk( $index ) {
+ static function canTalk( $index ) {
return( $index >= NS_MAIN );
}
}
diff --git a/includes/ObjectCache.php b/includes/ObjectCache.php
index fe7417d2..2b26cf4e 100644
--- a/includes/ObjectCache.php
+++ b/includes/ObjectCache.php
@@ -69,13 +69,10 @@ function &wfGetCache( $inputType ) {
} elseif ( $type == CACHE_ACCEL ) {
if ( !array_key_exists( CACHE_ACCEL, $wgCaches ) ) {
if ( function_exists( 'eaccelerator_get' ) ) {
- require_once( 'BagOStuff.php' );
$wgCaches[CACHE_ACCEL] = new eAccelBagOStuff;
} elseif ( function_exists( 'apc_fetch') ) {
- require_once( 'BagOStuff.php' );
$wgCaches[CACHE_ACCEL] = new APCBagOStuff;
} elseif ( function_exists( 'mmcache_get' ) ) {
- require_once( 'BagOStuff.php' );
$wgCaches[CACHE_ACCEL] = new TurckBagOStuff;
} else {
$wgCaches[CACHE_ACCEL] = false;
@@ -84,11 +81,15 @@ function &wfGetCache( $inputType ) {
if ( $wgCaches[CACHE_ACCEL] !== false ) {
$cache =& $wgCaches[CACHE_ACCEL];
}
+ } elseif ( $type == CACHE_DBA ) {
+ if ( !array_key_exists( CACHE_DBA, $wgCaches ) ) {
+ $wgCaches[CACHE_DBA] = new DBABagOStuff;
+ }
+ $cache =& $wgCaches[CACHE_DBA];
}
-
+
if ( $type == CACHE_DB || ( $inputType == CACHE_ANYTHING && $cache === false ) ) {
if ( !array_key_exists( CACHE_DB, $wgCaches ) ) {
- require_once( 'BagOStuff.php' );
$wgCaches[CACHE_DB] = new MediaWikiBagOStuff('objectcache');
}
$cache =& $wgCaches[CACHE_DB];
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 31a0781a..0d55c2e0 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -22,7 +22,7 @@ class OutputPage {
var $mDoNothing;
var $mContainsOldMagic, $mContainsNewMagic;
var $mIsArticleRelated;
- var $mParserOptions;
+ protected $mParserOptions; // lazy initialised, use parserOptions()
var $mShowFeedLinks = false;
var $mEnableClientCache = true;
var $mArticleBodyOnly = false;
@@ -46,7 +46,7 @@ class OutputPage {
$this->mCategoryLinks = array();
$this->mDoNothing = false;
$this->mContainsOldMagic = $this->mContainsNewMagic = 0;
- $this->mParserOptions = ParserOptions::newFromUser( $temp = NULL );
+ $this->mParserOptions = null;
$this->mSquidMaxage = 0;
$this->mScripts = '';
$this->mETag = false;
@@ -92,7 +92,7 @@ class OutputPage {
* returns true iff cache-ok headers was sent.
*/
function checkLastModified ( $timestamp ) {
- global $wgCachePages, $wgCacheEpoch, $wgUser;
+ global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest;
$fname = 'OutputPage::checkLastModified';
if ( !$timestamp || $timestamp == '19700101000000' ) {
@@ -122,7 +122,7 @@ class OutputPage {
wfDebug( "$fname: -- we might send Last-Modified : $lastmod\n", false );
if( ($ismodsince >= $timestamp ) && $wgUser->validateCache( $ismodsince ) && $ismodsince >= $wgCacheEpoch ) {
# Make sure you're in a place you can leave when you call us!
- header( "HTTP/1.0 304 Not Modified" );
+ $wgRequest->response()->header( "HTTP/1.0 304 Not Modified" );
$this->mLastModified = $lastmod;
$this->sendCacheControl();
wfDebug( "$fname: CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
@@ -255,10 +255,13 @@ class OutputPage {
/* @deprecated */
function setParserOptions( $options ) {
- return $this->ParserOptions( $options );
+ return $this->parserOptions( $options );
}
- function ParserOptions( $options = null ) {
+ function parserOptions( $options = null ) {
+ if ( !$this->mParserOptions ) {
+ $this->mParserOptions = new ParserOptions;
+ }
return wfSetVar( $this->mParserOptions, $options );
}
@@ -289,9 +292,13 @@ class OutputPage {
function addWikiTextTitle($text, &$title, $linestart) {
global $wgParser;
- $parserOutput = $wgParser->parse( $text, $title, $this->mParserOptions,
+ $fname = 'OutputPage:addWikiTextTitle';
+ wfProfileIn($fname);
+ wfIncrStats('pcache_not_possible');
+ $parserOutput = $wgParser->parse( $text, $title, $this->parserOptions(),
$linestart, true, $this->mRevisionId );
$this->addParserOutput( $parserOutput );
+ wfProfileOut($fname);
}
function addParserOutputNoText( &$parserOutput ) {
@@ -304,13 +311,19 @@ class OutputPage {
}
if ( $parserOutput->mHTMLtitle != "" ) {
$this->mPagetitle = $parserOutput->mHTMLtitle ;
+ }
+ if ( $parserOutput->mSubtitle != '' ) {
$this->mSubtitle .= $parserOutput->mSubtitle ;
}
+ $this->mNoGallery = $parserOutput->getNoGallery();
+ wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
}
function addParserOutput( &$parserOutput ) {
$this->addParserOutputNoText( $parserOutput );
- $this->addHTML( $parserOutput->getText() );
+ $text = $parserOutput->getText();
+ wfRunHooks( 'OutputPageBeforeHTML',array( &$this, &$text ) );
+ $this->addHTML( $text );
}
/**
@@ -320,21 +333,17 @@ class OutputPage {
function addPrimaryWikiText( $text, $article, $cache = true ) {
global $wgParser, $wgUser;
- $this->mParserOptions->setTidy(true);
+ $popts = $this->parserOptions();
+ $popts->setTidy(true);
$parserOutput = $wgParser->parse( $text, $article->mTitle,
- $this->mParserOptions, true, true, $this->mRevisionId );
- $this->mParserOptions->setTidy(false);
+ $popts, true, true, $this->mRevisionId );
+ $popts->setTidy(false);
if ( $cache && $article && $parserOutput->getCacheTime() != -1 ) {
$parserCache =& ParserCache::singleton();
$parserCache->save( $parserOutput, $article, $wgUser );
}
- $this->addParserOutputNoText( $parserOutput );
- $text = $parserOutput->getText();
- $this->mNoGallery = $parserOutput->getNoGallery();
- wfRunHooks( 'OutputPageBeforeHTML',array( &$this, &$text ) );
- $parserOutput->setText( $text );
- $this->addHTML( $parserOutput->getText() );
+ $this->addParserOutput( $parserOutput );
}
/**
@@ -342,9 +351,10 @@ class OutputPage {
*/
function addSecondaryWikiText( $text, $linestart = true ) {
global $wgTitle;
- $this->mParserOptions->setTidy(true);
+ $popts = $this->parserOptions();
+ $popts->setTidy(true);
$this->addWikiTextTitle($text, $wgTitle, $linestart);
- $this->mParserOptions->setTidy(false);
+ $popts->setTidy(false);
}
@@ -364,10 +374,11 @@ class OutputPage {
*/
function parse( $text, $linestart = true, $interface = false ) {
global $wgParser, $wgTitle;
- if ( $interface) { $this->mParserOptions->setInterfaceMessage(true); }
- $parserOutput = $wgParser->parse( $text, $wgTitle, $this->mParserOptions,
+ $popts = $this->parserOptions();
+ if ( $interface) { $popts->setInterfaceMessage(true); }
+ $parserOutput = $wgParser->parse( $text, $wgTitle, $popts,
$linestart, true, $this->mRevisionId );
- if ( $interface) { $this->mParserOptions->setInterfaceMessage(false); }
+ if ( $interface) { $popts->setInterfaceMessage(false); }
return $parserOutput->getText();
}
@@ -381,18 +392,7 @@ class OutputPage {
$parserCache =& ParserCache::singleton();
$parserOutput = $parserCache->get( $article, $user );
if ( $parserOutput !== false ) {
- $this->mLanguageLinks += $parserOutput->getLanguageLinks();
- $this->addCategoryLinks( $parserOutput->getCategories() );
- $this->addKeywords( $parserOutput );
- $this->mNewSectionLink = $parserOutput->getNewSection();
- $this->mNoGallery = $parserOutput->getNoGallery();
- $text = $parserOutput->getText();
- wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) );
- $this->addHTML( $text );
- $t = $parserOutput->getTitleText();
- if( !empty( $t ) ) {
- $this->setPageTitle( $t );
- }
+ $this->addParserOutput( $parserOutput );
return true;
} else {
return false;
@@ -422,15 +422,15 @@ class OutputPage {
}
function sendCacheControl() {
- global $wgUseSquid, $wgUseESI, $wgSquidMaxage;
+ global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest;
$fname = 'OutputPage::sendCacheControl';
- if ($this->mETag)
- header("ETag: $this->mETag");
+ if ($wgUseETag && $this->mETag)
+ $wgRequest->response()->header("ETag: $this->mETag");
# don't serve compressed data to clients who can't handle it
# maintain different caches for logged-in users and non-logged in ones
- header( 'Vary: Accept-Encoding, Cookie' );
+ $wgRequest->response()->header( 'Vary: Accept-Encoding, Cookie' );
if( !$this->uncacheableBecauseRequestvars() && $this->mEnableClientCache ) {
if( $wgUseSquid && ! isset( $_COOKIE[ini_get( 'session.name') ] ) &&
! $this->isPrintable() && $this->mSquidMaxage != 0 )
@@ -442,8 +442,8 @@ class OutputPage {
wfDebug( "$fname: proxy caching with ESI; {$this->mLastModified} **\n", false );
# start with a shorter timeout for initial testing
# header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
- header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"');
- header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
+ $wgRequest->response()->header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"');
+ $wgRequest->response()->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
} else {
# We'll purge the proxy cache for anons explicitly, but require end user agents
# to revalidate against the proxy on each visit.
@@ -452,24 +452,24 @@ class OutputPage {
wfDebug( "$fname: local proxy caching; {$this->mLastModified} **\n", false );
# start with a shorter timeout for initial testing
# header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
- header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' );
+ $wgRequest->response()->header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' );
}
} else {
# We do want clients to cache if they can, but they *must* check for updates
# on revisiting the page.
wfDebug( "$fname: private caching; {$this->mLastModified} **\n", false );
- header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
- header( "Cache-Control: private, must-revalidate, max-age=0" );
+ $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
+ $wgRequest->response()->header( "Cache-Control: private, must-revalidate, max-age=0" );
}
- if($this->mLastModified) header( "Last-modified: {$this->mLastModified}" );
+ if($this->mLastModified) $wgRequest->response()->header( "Last-modified: {$this->mLastModified}" );
} else {
wfDebug( "$fname: no caching **\n", false );
# In general, the absence of a last modified header should be enough to prevent
# the client from using its cache. We send a few other things just to make sure.
- header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
- header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
- header( 'Pragma: no-cache' );
+ $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
+ $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
+ $wgRequest->response()->header( 'Pragma: no-cache' );
}
}
@@ -478,9 +478,9 @@ class OutputPage {
* the object, let's actually output it:
*/
function output() {
- global $wgUser, $wgOutputEncoding;
+ global $wgUser, $wgOutputEncoding, $wgRequest;
global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType;
- global $wgJsMimeType, $wgStylePath, $wgUseAjax, $wgScriptPath, $wgServer;
+ global $wgJsMimeType, $wgStylePath, $wgUseAjax, $wgAjaxSearch, $wgScriptPath, $wgServer;
if( $this->mDoNothing ){
return;
@@ -490,13 +490,14 @@ class OutputPage {
$sk = $wgUser->getSkin();
if ( $wgUseAjax ) {
- $this->addScript( "<script type=\"{$wgJsMimeType}\">
- var wgScriptPath=\"{$wgScriptPath}\";
- var wgServer=\"{$wgServer}\";
- </script>" );
$this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajax.js\"></script>\n" );
}
+ if ( $wgUseAjax && $wgAjaxSearch ) {
+ $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxsearch.js\"></script>\n" );
+ $this->addScript( "<script type=\"{$wgJsMimeType}\">hookEvent(\"load\", sajax_onload);</script>\n" );
+ }
+
if ( '' != $this->mRedirect ) {
if( substr( $this->mRedirect, 0, 4 ) != 'http' ) {
# Standards require redirect URLs to be absolute
@@ -505,7 +506,7 @@ class OutputPage {
}
if( $this->mRedirectCode == '301') {
if( !$wgDebugRedirects ) {
- header("HTTP/1.1 {$this->mRedirectCode} Moved Permanently");
+ $wgRequest->response()->header("HTTP/1.1 {$this->mRedirectCode} Moved Permanently");
}
$this->mLastModified = wfTimestamp( TS_RFC2822 );
}
@@ -518,7 +519,7 @@ class OutputPage {
print "<p>Location: <a href=\"$url\">$url</a></p>\n";
print "</body>\n</html>\n";
} else {
- header( 'Location: '.$this->mRedirect );
+ $wgRequest->response()->header( 'Location: '.$this->mRedirect );
}
wfProfileOut( $fname );
return;
@@ -575,7 +576,7 @@ class OutputPage {
);
if ( $statusMessage[$this->mStatusCode] )
- header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $statusMessage[$this->mStatusCode] );
+ $wgRequest->response()->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $statusMessage[$this->mStatusCode] );
}
# Buffer output; final headers may depend on later processing
@@ -584,8 +585,8 @@ class OutputPage {
# Disable temporary placeholders, so that the skin produces HTML
$sk->postParseLinkColour( false );
- header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" );
- header( 'Content-language: '.$wgContLanguageCode );
+ $wgRequest->response()->header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" );
+ $wgRequest->response()->header( 'Content-language: '.$wgContLanguageCode );
if ($this->mArticleBodyOnly) {
$this->out($this->mBodytext);
@@ -617,11 +618,6 @@ class OutputPage {
$wgInputEncoding = strtolower( $wgInputEncoding );
- if( $wgUser->getOption( 'altencoding' ) ) {
- $wgContLang->setAltEncoding();
- return;
- }
-
if ( empty( $_SERVER['HTTP_ACCEPT_CHARSET'] ) ) {
$wgOutputEncoding = strtolower( $wgOutputEncoding );
return;
@@ -715,11 +711,10 @@ class OutputPage {
/**
* Display an error page noting that a given permission bit is required.
- * This should generally replace the sysopRequired, developerRequired etc.
* @param string $permission key required
*/
function permissionRequired( $permission ) {
- global $wgUser;
+ global $wgGroupPermissions, $wgUser;
$this->setPageTitle( wfMsg( 'badaccess' ) );
$this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
@@ -727,46 +722,46 @@ class OutputPage {
$this->setArticleRelated( false );
$this->mBodytext = '';
- $sk = $wgUser->getSkin();
- $ap = $sk->makeKnownLink( wfMsgForContent( 'administrators' ) );
- $this->addHTML( wfMsgHtml( 'badaccesstext', $ap, $permission ) );
- $this->returnToMain();
+ $groups = array();
+ foreach( $wgGroupPermissions as $key => $value ) {
+ if( isset( $value[$permission] ) && $value[$permission] == true ) {
+ $groupName = User::getGroupName( $key );
+ $groupPage = User::getGroupPage( $key );
+ if( $groupPage ) {
+ $skin =& $wgUser->getSkin();
+ $groups[] = '"'.$skin->makeLinkObj( $groupPage, $groupName ).'"';
+ } else {
+ $groups[] = '"'.$groupName.'"';
+ }
+ }
+ }
+ $n = count( $groups );
+ $groups = implode( ', ', $groups );
+ switch( $n ) {
+ case 0:
+ case 1:
+ case 2:
+ $message = wfMsgHtml( "badaccess-group$n", $groups );
+ break;
+ default:
+ $message = wfMsgHtml( 'badaccess-groups', $groups );
+ }
+ $this->addHtml( $message );
+ $this->returnToMain( false );
}
/**
* @deprecated
*/
function sysopRequired() {
- global $wgUser;
-
- $this->setPageTitle( wfMsg( 'sysoptitle' ) );
- $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
- $this->setRobotpolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
- $this->mBodytext = '';
-
- $sk = $wgUser->getSkin();
- $ap = $sk->makeKnownLink( wfMsgForContent( 'administrators' ), '' );
- $this->addHTML( wfMsgHtml( 'sysoptext', $ap ) );
- $this->returnToMain();
+ throw new MWException( "Call to deprecated OutputPage::sysopRequired() method\n" );
}
/**
* @deprecated
*/
function developerRequired() {
- global $wgUser;
-
- $this->setPageTitle( wfMsg( 'developertitle' ) );
- $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
- $this->setRobotpolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
- $this->mBodytext = '';
-
- $sk = $wgUser->getSkin();
- $ap = $sk->makeKnownLink( wfMsgForContent( 'administrators' ), '' );
- $this->addHTML( wfMsgHtml( 'developertext', $ap ) );
- $this->returnToMain();
+ throw new MWException( "Call to deprecated OutputPage::developerRequired() method\n" );
}
/**
@@ -774,6 +769,12 @@ class OutputPage {
*/
function loginToUse() {
global $wgUser, $wgTitle, $wgContLang;
+
+ if( $wgUser->isLoggedIn() ) {
+ $this->permissionRequired( 'read' );
+ return;
+ }
+
$skin = $wgUser->getSkin();
$this->setPageTitle( wfMsg( 'loginreqtitle' ) );
@@ -786,7 +787,11 @@ class OutputPage {
$this->addHtml( wfMsgWikiHtml( 'loginreqpagetext', $loginLink ) );
$this->addHtml( "\n<!--" . $wgTitle->getPrefixedUrl() . "-->" );
- $this->returnToMain();
+ # Don't return to the main page if the user can't read it
+ # otherwise we'll end up in a pointless loop
+ $mainPage = Title::newFromText( wfMsgForContent( 'mainpage' ) );
+ if( $mainPage->userCanRead() )
+ $this->returnToMain( true, $mainPage );
}
/** @obsolete */
@@ -828,7 +833,7 @@ class OutputPage {
if ( $wgTitle->getNamespace() == NS_MEDIAWIKI ) {
$source = wfMsgWeirdKey ( $wgTitle->getText() );
} else {
- $source = wfMsg( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon' );
+ $source = '';
}
}
$rows = $wgUser->getIntOption( 'rows' );
@@ -1045,7 +1050,7 @@ class OutputPage {
$link = $wgRequest->escapeAppendQuery( 'feed=rss' );
$ret .= "<link rel='alternate' type='application/rss+xml' title='RSS 2.0' href='$link' />\n";
$link = $wgRequest->escapeAppendQuery( 'feed=atom' );
- $ret .= "<link rel='alternate' type='application/atom+xml' title='Atom 0.3' href='$link' />\n";
+ $ret .= "<link rel='alternate' type='application/atom+xml' title='Atom 1.0' href='$link' />\n";
}
return $ret;
diff --git a/includes/PageHistory.php b/includes/PageHistory.php
index de006285..d7f426fc 100644
--- a/includes/PageHistory.php
+++ b/includes/PageHistory.php
@@ -40,8 +40,6 @@ class PageHistory {
$this->mTitle =& $article->mTitle;
$this->mNotificationTimestamp = NULL;
$this->mSkin = $wgUser->getSkin();
-
- $this->defaultLimit = 50;
}
/**
@@ -93,99 +91,30 @@ class PageHistory {
return;
}
- $dbr =& wfGetDB(DB_SLAVE);
-
- /*
- * Extract limit, the number of revisions to show, and
- * offset, the timestamp to begin at, from the URL.
- */
- $limit = $wgRequest->getInt('limit', $this->defaultLimit);
- if ( $limit <= 0 ) {
- $limit = $this->defaultLimit;
- } elseif ( $limit > 50000 ) {
- # Arbitrary maximum
- # Any more than this and we'll probably get an out of memory error
- $limit = 50000;
- }
-
- $offset = $wgRequest->getText('offset');
-
- /* Offset must be an integral. */
- if (!strlen($offset) || !preg_match("/^[0-9]+$/", $offset))
- $offset = 0;
-# $offset = $dbr->timestamp($offset);
- $dboffset = $offset === 0 ? 0 : $dbr->timestamp($offset);
+
/*
- * "go=last" means to jump to the last history page.
+ * "go=first" means to jump to the last (earliest) history page.
+ * This is deprecated, it no longer appears in the user interface
*/
- if (($gowhere = $wgRequest->getText("go")) !== NULL) {
- $gourl = null;
- switch ($gowhere) {
- case "first":
- if (($lastid = $this->getLastOffsetForPaging($this->mTitle->getArticleID(), $limit)) === NULL)
- break;
- $gourl = $wgTitle->getLocalURL("action=history&limit={$limit}&offset=".
- wfTimestamp(TS_MW, $lastid));
- break;
- }
-
- if (!is_null($gourl)) {
- $wgOut->redirect($gourl);
- return;
- }
+ if ( $wgRequest->getText("go") == 'first' ) {
+ $limit = $wgRequest->getInt( 'limit', 50 );
+ $wgOut->redirect( $wgTitle->getLocalURL( "action=history&limit={$limit}&dir=prev" ) );
+ return;
}
- /*
- * Fetch revisions.
- *
- * If the user clicked "previous", we retrieve the revisions backwards,
- * then reverse them. This is to avoid needing to know the timestamp of
- * previous revisions when generating the URL.
+ /**
+ * Do the list
*/
- $direction = $this->getDirection();
- $revisions = $this->fetchRevisions($limit, $dboffset, $direction);
- $navbar = $this->makeNavbar($revisions, $offset, $limit, $direction);
-
- /*
- * We fetch one more revision than needed to get the timestamp of the
- * one after this page (and to know if it exists).
- *
- * linesonpage stores the actual number of lines.
- */
- if (count($revisions) < $limit + 1)
- $this->linesonpage = count($revisions);
- else
- $this->linesonpage = count($revisions) - 1;
-
- /* Un-reverse revisions */
- if ($direction == PageHistory::DIR_PREV)
- $revisions = array_reverse($revisions);
-
- /*
- * Print the top navbar.
- */
- $s = $navbar;
- $s .= $this->beginHistoryList();
- $counter = 1;
-
- /*
- * Print each revision, excluding the one-past-the-end, if any.
- */
- foreach (array_slice($revisions, 0, $limit) as $i => $line) {
- $latest = !$i && $offset == 0;
- $firstInList = !$i;
- $next = isset( $revisions[$i + 1] ) ? $revisions[$i + 1 ] : null;
- $s .= $this->historyLine($line, $next, $counter, $this->getNotificationTimestamp(), $latest, $firstInList);
- $counter++;
- }
-
- /*
- * End navbar.
- */
- $s .= $this->endHistoryList();
- $s .= $navbar;
-
- $wgOut->addHTML( $s );
+ $pager = new PageHistoryPager( $this );
+ $navbar = $pager->getNavigationBar();
+ $this->linesonpage = $pager->getNumRows();
+ $wgOut->addHTML(
+ $pager->getNavigationBar() .
+ $this->beginHistoryList() .
+ $pager->getBody() .
+ $this->endHistoryList() .
+ $pager->getNavigationBar()
+ );
wfProfileOut( $fname );
}
@@ -318,16 +247,15 @@ class PageHistory {
/** @todo document */
function lastLink( $rev, $next, $counter ) {
$last = wfMsgExt( 'last', array( 'escape' ) );
- if( is_null( $next ) ) {
- if( $rev->getTimestamp() == $this->getEarliestOffset() ) {
- return $last;
- } else {
- // Cut off by paging; there are more behind us...
- return $this->mSkin->makeKnownLinkObj(
- $this->mTitle,
- $last,
- "diff=" . $rev->getId() . "&oldid=prev" );
- }
+ if ( is_null( $next ) ) {
+ # Probably no next row
+ return $last;
+ } elseif ( $next === 'unknown' ) {
+ # Next row probably exists but is unknown, use an oldid=prev link
+ return $this->mSkin->makeKnownLinkObj(
+ $this->mTitle,
+ $last,
+ "diff=" . $rev->getId() . "&oldid=prev" );
} elseif( !$rev->userCan( Revision::DELETED_TEXT ) ) {
return $last;
} else {
@@ -387,72 +315,23 @@ class PageHistory {
}
/** @todo document */
- function getLatestOffset( $id = null ) {
- if ( $id === null) $id = $this->mTitle->getArticleID();
- return $this->getExtremeOffset( $id, 'max' );
- }
-
- /** @todo document */
- function getEarliestOffset( $id = null ) {
- if ( $id === null) $id = $this->mTitle->getArticleID();
- return $this->getExtremeOffset( $id, 'min' );
- }
-
- /** @todo document */
- function getExtremeOffset( $id, $func ) {
- $db =& wfGetDB(DB_SLAVE);
- return $db->selectField( 'revision',
- "$func(rev_timestamp)",
- array( 'rev_page' => $id ),
- 'PageHistory::getExtremeOffset' );
- }
-
- /** @todo document */
function getLatestId() {
if( is_null( $this->mLatestId ) ) {
$id = $this->mTitle->getArticleID();
$db =& wfGetDB(DB_SLAVE);
- $this->mLatestId = $db->selectField( 'revision',
- "max(rev_id)",
- array( 'rev_page' => $id ),
+ $this->mLatestId = $db->selectField( 'page',
+ "page_latest",
+ array( 'page_id' => $id ),
'PageHistory::getLatestID' );
}
return $this->mLatestId;
}
- /** @todo document */
- function getLastOffsetForPaging( $id, $step ) {
- $fname = 'PageHistory::getLastOffsetForPaging';
-
- $dbr =& wfGetDB(DB_SLAVE);
- $res = $dbr->select(
- 'revision',
- 'rev_timestamp',
- "rev_page=$id",
- $fname,
- array('ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => $step));
-
- $n = $dbr->numRows( $res );
- $last = null;
- while( $obj = $dbr->fetchObject( $res ) ) {
- $last = $obj->rev_timestamp;
- }
- $dbr->freeResult( $res );
- return $last;
- }
-
/**
- * @return returns the direction of browsing watchlist
+ * Fetch an array of revisions, specified by a given limit, offset and
+ * direction. This is now only used by the feeds. It was previously
+ * used by the main UI but that's now handled by the pager.
*/
- function getDirection() {
- global $wgRequest;
- if ($wgRequest->getText("dir") == "prev")
- return PageHistory::DIR_PREV;
- else
- return PageHistory::DIR_NEXT;
- }
-
- /** @todo document */
function fetchRevisions($limit, $offset, $direction) {
$fname = 'PageHistory::fetchRevisions';
@@ -516,83 +395,6 @@ class PageHistory {
return $this->mNotificationTimestamp;
}
-
- /** @todo document */
- function makeNavbar($revisions, $offset, $limit, $direction) {
- global $wgLang;
-
- $revisions = array_slice($revisions, 0, $limit);
-
- $latestTimestamp = wfTimestamp(TS_MW, $this->getLatestOffset());
- $earliestTimestamp = wfTimestamp(TS_MW, $this->getEarliestOffset());
-
- /*
- * When we're displaying previous revisions, we need to reverse
- * the array, because it's queried in reverse order.
- */
- if ($direction == PageHistory::DIR_PREV)
- $revisions = array_reverse($revisions);
-
- /*
- * lowts is the timestamp of the first revision on this page.
- * hights is the timestamp of the last revision.
- */
-
- $lowts = $hights = 0;
-
- if( count( $revisions ) ) {
- $latestShown = wfTimestamp(TS_MW, $revisions[0]->rev_timestamp);
- $earliestShown = wfTimestamp(TS_MW, $revisions[count($revisions) - 1]->rev_timestamp);
- } else {
- $latestShown = null;
- $earliestShown = null;
- }
-
- /* Don't announce the limit everywhere if it's the default */
- $usefulLimit = $limit == $this->defaultLimit ? '' : $limit;
-
- $urls = array();
- foreach (array(20, 50, 100, 250, 500) as $num) {
- $urls[] = $this->MakeLink( $wgLang->formatNum($num),
- array('offset' => $offset == 0 ? '' : wfTimestamp(TS_MW, $offset), 'limit' => $num, ) );
- }
-
- $bits = implode($urls, ' | ');
-
- wfDebug("latestShown=$latestShown latestTimestamp=$latestTimestamp\n");
- if( $latestShown < $latestTimestamp ) {
- $prevtext = $this->MakeLink( wfMsgHtml("prevn", $limit),
- array( 'dir' => 'prev', 'offset' => $latestShown, 'limit' => $usefulLimit ) );
- $lasttext = $this->MakeLink( wfMsgHtml('histlast'),
- array( 'limit' => $usefulLimit ) );
- } else {
- $prevtext = wfMsgHtml("prevn", $limit);
- $lasttext = wfMsgHtml('histlast');
- }
-
- wfDebug("earliestShown=$earliestShown earliestTimestamp=$earliestTimestamp\n");
- if( $earliestShown > $earliestTimestamp ) {
- $nexttext = $this->MakeLink( wfMsgHtml("nextn", $limit),
- array( 'offset' => $earliestShown, 'limit' => $usefulLimit ) );
- $firsttext = $this->MakeLink( wfMsgHtml('histfirst'),
- array( 'go' => 'first', 'limit' => $usefulLimit ) );
- } else {
- $nexttext = wfMsgHtml("nextn", $limit);
- $firsttext = wfMsgHtml('histfirst');
- }
-
- $firstlast = "($lasttext | $firsttext)";
-
- return "$firstlast " . wfMsgHtml("viewprevnext", $prevtext, $nexttext, $bits);
- }
-
- function MakeLink($text, $query = NULL) {
- if ( $query === null ) return $text;
- return $this->mSkin->makeKnownLinkObj(
- $this->mTitle, $text,
- wfArrayToCGI( $query, array( 'action' => 'history' )));
- }
-
/**
* Output a subscription feed listing recent edits to this page.
@@ -678,8 +480,72 @@ class PageHistory {
function stripComment( $text ) {
return preg_replace( '/\[\[([^]]*\|)?([^]]+)\]\]/', '\2', $text );
}
+}
+
+
+class PageHistoryPager extends ReverseChronologicalPager {
+ public $mLastRow = false, $mPageHistory;
+
+ function __construct( $pageHistory ) {
+ parent::__construct();
+ $this->mPageHistory = $pageHistory;
+ }
+
+ function getQueryInfo() {
+ return array(
+ 'tables' => 'revision',
+ 'fields' => array('rev_id', 'rev_page', 'rev_text_id', 'rev_user', 'rev_comment', 'rev_user_text',
+ 'rev_timestamp', 'rev_minor_edit', 'rev_deleted'),
+ 'conds' => array('rev_page' => $this->mPageHistory->mTitle->getArticleID() ),
+ 'options' => array( 'USE INDEX' => 'page_timestamp' )
+ );
+ }
+
+ function getIndexField() {
+ return 'rev_timestamp';
+ }
+ function formatRow( $row ) {
+ if ( $this->mLastRow ) {
+ $latest = $this->mCounter == 1 && $this->mOffset == '';
+ $firstInList = $this->mCounter == 1;
+ $s = $this->mPageHistory->historyLine( $this->mLastRow, $row, $this->mCounter++,
+ $this->mPageHistory->getNotificationTimestamp(), $latest, $firstInList );
+ } else {
+ $s = '';
+ }
+ $this->mLastRow = $row;
+ return $s;
+ }
+
+ function getStartBody() {
+ $this->mLastRow = false;
+ $this->mCounter = 1;
+ return '';
+ }
+ function getEndBody() {
+ if ( $this->mLastRow ) {
+ $latest = $this->mCounter == 1 && $this->mOffset == 0;
+ $firstInList = $this->mCounter == 1;
+ if ( $this->mIsBackwards ) {
+ # Next row is unknown, but for UI reasons, probably exists if an offset has been specified
+ if ( $this->mOffset == '' ) {
+ $next = null;
+ } else {
+ $next = 'unknown';
+ }
+ } else {
+ # The next row is the past-the-end row
+ $next = $this->mPastTheEndRow;
+ }
+ $s = $this->mPageHistory->historyLine( $this->mLastRow, $next, $this->mCounter++,
+ $this->mPageHistory->getNotificationTimestamp(), $latest, $firstInList );
+ } else {
+ $s = '';
+ }
+ return $s;
+ }
}
?>
diff --git a/includes/Pager.php b/includes/Pager.php
new file mode 100644
index 00000000..b14aa8ca
--- /dev/null
+++ b/includes/Pager.php
@@ -0,0 +1,656 @@
+<?php
+
+/**
+ * Basic pager interface.
+ */
+interface Pager {
+ function getNavigationBar();
+ function getBody();
+}
+
+/**
+ * IndexPager is an efficient pager which uses a (roughly unique) index in the
+ * data set to implement paging, rather than a "LIMIT offset,limit" clause.
+ * In MySQL, such a limit/offset clause requires counting through the specified number
+ * of offset rows to find the desired data, which can be expensive for large offsets.
+ *
+ * ReverseChronologicalPager is a child class of the abstract IndexPager, and contains
+ * some formatting and display code which is specific to the use of timestamps as
+ * indexes. Here is a synopsis of its operation:
+ *
+ * * The query is specified by the offset, limit and direction (dir) parameters, in
+ * addition to any subclass-specific parameters.
+ *
+ * * The offset is the non-inclusive start of the DB query. A row with an index value
+ * equal to the offset will never be shown.
+ *
+ * * The query may either be done backwards, where the rows are returned by the database
+ * in the opposite order to which they are displayed to the user, or forwards. This is
+ * specified by the "dir" parameter, dir=prev means backwards, anything else means
+ * forwards. The offset value specifies the start of the database result set, which
+ * may be either the start or end of the displayed data set. This allows "previous"
+ * links to be implemented without knowledge of the index value at the start of the
+ * previous page.
+ *
+ * * An additional row beyond the user-specified limit is always requested. This allows
+ * us to tell whether we should display a "next" link in the case of forwards mode,
+ * or a "previous" link in the case of backwards mode. Determining whether to
+ * display the other link (the one for the page before the start of the database
+ * result set) can be done heuristically by examining the offset.
+ *
+ * * An empty offset indicates that the offset condition should be omitted from the query.
+ * This naturally produces either the first page or the last page depending on the
+ * dir parameter.
+ *
+ * Subclassing the pager to implement concrete functionality should be fairly simple,
+ * please see the examples in PageHistory.php and SpecialIpblocklist.php. You just need
+ * to override formatRow(), getQueryInfo() and getIndexField(). Don't forget to call the
+ * parent constructor if you override it.
+ */
+abstract class IndexPager implements Pager {
+ public $mRequest;
+ public $mLimitsShown = array( 20, 50, 100, 250, 500 );
+ public $mDefaultLimit = 50;
+ public $mOffset, $mLimit;
+ public $mQueryDone = false;
+ public $mDb;
+ public $mPastTheEndRow;
+
+ protected $mIndexField;
+
+ /**
+ * Default query direction. false for ascending, true for descending
+ */
+ public $mDefaultDirection = false;
+
+ /**
+ * Result object for the query. Warning: seek before use.
+ */
+ public $mResult;
+
+ function __construct() {
+ global $wgRequest;
+ $this->mRequest = $wgRequest;
+
+ # NB: the offset is quoted, not validated. It is treated as an arbitrary string
+ # to support the widest variety of index types. Be careful outputting it into
+ # HTML!
+ $this->mOffset = $this->mRequest->getText( 'offset' );
+ $this->mLimit = $this->mRequest->getInt( 'limit', $this->mDefaultLimit );
+ if ( $this->mLimit <= 0 || $this->mLimit > 50000 ) {
+ $this->mLimit = $this->mDefaultLimit;
+ }
+ $this->mIsBackwards = ( $this->mRequest->getVal( 'dir' ) == 'prev' );
+ $this->mIndexField = $this->getIndexField();
+ $this->mDb = wfGetDB( DB_SLAVE );
+ }
+
+ /**
+ * Do the query, using information from the object context. This function
+ * has been kept minimal to make it overridable if necessary, to allow for
+ * result sets formed from multiple DB queries.
+ */
+ function doQuery() {
+ # Use the child class name for profiling
+ $fname = __METHOD__ . ' (' . get_class( $this ) . ')';
+ wfProfileIn( $fname );
+
+ $descending = ( $this->mIsBackwards == $this->mDefaultDirection );
+ # Plus an extra row so that we can tell the "next" link should be shown
+ $queryLimit = $this->mLimit + 1;
+
+ $this->mResult = $this->reallyDoQuery( $this->mOffset, $queryLimit, $descending );
+ $this->extractResultInfo( $this->mOffset, $queryLimit, $this->mResult );
+ $this->mQueryDone = true;
+
+ wfProfileOut( $fname );
+ }
+
+ /**
+ * Extract some useful data from the result object for use by
+ * the navigation bar, put it into $this
+ */
+ function extractResultInfo( $offset, $limit, ResultWrapper $res ) {
+ $numRows = $res->numRows();
+ if ( $numRows ) {
+ $row = $res->fetchRow();
+ $firstIndex = $row[$this->mIndexField];
+
+ # Discard the extra result row if there is one
+ if ( $numRows > $this->mLimit && $numRows > 1 ) {
+ $res->seek( $numRows - 1 );
+ $this->mPastTheEndRow = $res->fetchObject();
+ $indexField = $this->mIndexField;
+ $this->mPastTheEndIndex = $this->mPastTheEndRow->$indexField;
+ $res->seek( $numRows - 2 );
+ $row = $res->fetchRow();
+ $lastIndex = $row[$this->mIndexField];
+ } else {
+ $this->mPastTheEndRow = null;
+ # Setting indexes to an empty string means that they will be omitted
+ # if they would otherwise appear in URLs. It just so happens that this
+ # is the right thing to do in the standard UI, in all the relevant cases.
+ $this->mPastTheEndIndex = '';
+ $res->seek( $numRows - 1 );
+ $row = $res->fetchRow();
+ $lastIndex = $row[$this->mIndexField];
+ }
+ } else {
+ $firstIndex = '';
+ $lastIndex = '';
+ $this->mPastTheEndRow = null;
+ $this->mPastTheEndIndex = '';
+ }
+
+ if ( $this->mIsBackwards ) {
+ $this->mIsFirst = ( $numRows < $limit );
+ $this->mIsLast = ( $offset == '' );
+ $this->mLastShown = $firstIndex;
+ $this->mFirstShown = $lastIndex;
+ } else {
+ $this->mIsFirst = ( $offset == '' );
+ $this->mIsLast = ( $numRows < $limit );
+ $this->mLastShown = $lastIndex;
+ $this->mFirstShown = $firstIndex;
+ }
+ }
+
+ /**
+ * Do a query with specified parameters, rather than using the object context
+ *
+ * @param string $offset Index offset, inclusive
+ * @param integer $limit Exact query limit
+ * @param boolean $descending Query direction, false for ascending, true for descending
+ * @return ResultWrapper
+ */
+ function reallyDoQuery( $offset, $limit, $ascending ) {
+ $fname = __METHOD__ . ' (' . get_class( $this ) . ')';
+ $info = $this->getQueryInfo();
+ $tables = $info['tables'];
+ $fields = $info['fields'];
+ $conds = isset( $info['conds'] ) ? $info['conds'] : array();
+ $options = isset( $info['options'] ) ? $info['options'] : array();
+ if ( $ascending ) {
+ $options['ORDER BY'] = $this->mIndexField;
+ $operator = '>';
+ } else {
+ $options['ORDER BY'] = $this->mIndexField . ' DESC';
+ $operator = '<';
+ }
+ if ( $offset != '' ) {
+ $conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset );
+ }
+ $options['LIMIT'] = intval( $limit );
+ $res = $this->mDb->select( $tables, $fields, $conds, $fname, $options );
+ return new ResultWrapper( $this->mDb, $res );
+ }
+
+ /**
+ * Get the formatted result list. Calls getStartBody(), formatRow() and
+ * getEndBody(), concatenates the results and returns them.
+ */
+ function getBody() {
+ if ( !$this->mQueryDone ) {
+ $this->doQuery();
+ }
+ # Don't use any extra rows returned by the query
+ $numRows = min( $this->mResult->numRows(), $this->mLimit );
+
+ $s = $this->getStartBody();
+ if ( $numRows ) {
+ if ( $this->mIsBackwards ) {
+ for ( $i = $numRows - 1; $i >= 0; $i-- ) {
+ $this->mResult->seek( $i );
+ $row = $this->mResult->fetchObject();
+ $s .= $this->formatRow( $row );
+ }
+ } else {
+ $this->mResult->seek( 0 );
+ for ( $i = 0; $i < $numRows; $i++ ) {
+ $row = $this->mResult->fetchObject();
+ $s .= $this->formatRow( $row );
+ }
+ }
+ } else {
+ $s .= $this->getEmptyBody();
+ }
+ $s .= $this->getEndBody();
+ return $s;
+ }
+
+ /**
+ * Make a self-link
+ */
+ function makeLink($text, $query = NULL) {
+ if ( $query === null ) {
+ return $text;
+ } else {
+ return $this->getSkin()->makeKnownLinkObj( $this->getTitle(), $text,
+ wfArrayToCGI( $query, $this->getDefaultQuery() ) );
+ }
+ }
+
+ /**
+ * Hook into getBody(), allows text to be inserted at the start. This
+ * will be called even if there are no rows in the result set.
+ */
+ function getStartBody() {
+ return '';
+ }
+
+ /**
+ * Hook into getBody() for the end of the list
+ */
+ function getEndBody() {
+ return '';
+ }
+
+ /**
+ * Hook into getBody(), for the bit between the start and the
+ * end when there are no rows
+ */
+ function getEmptyBody() {
+ return '';
+ }
+
+ /**
+ * Title used for self-links. Override this if you want to be able to
+ * use a title other than $wgTitle
+ */
+ function getTitle() {
+ return $GLOBALS['wgTitle'];
+ }
+
+ /**
+ * Get the current skin. This can be overridden if necessary.
+ */
+ function getSkin() {
+ if ( !isset( $this->mSkin ) ) {
+ global $wgUser;
+ $this->mSkin = $wgUser->getSkin();
+ }
+ return $this->mSkin;
+ }
+
+ /**
+ * Get an array of query parameters that should be put into self-links.
+ * By default, all parameters passed in the URL are used, except for a
+ * short blacklist.
+ */
+ function getDefaultQuery() {
+ if ( !isset( $this->mDefaultQuery ) ) {
+ $this->mDefaultQuery = $_GET;
+ unset( $this->mDefaultQuery['title'] );
+ unset( $this->mDefaultQuery['dir'] );
+ unset( $this->mDefaultQuery['offset'] );
+ unset( $this->mDefaultQuery['limit'] );
+ }
+ return $this->mDefaultQuery;
+ }
+
+ /**
+ * Get the number of rows in the result set
+ */
+ function getNumRows() {
+ if ( !$this->mQueryDone ) {
+ $this->doQuery();
+ }
+ return $this->mResult->numRows();
+ }
+
+ /**
+ * Get a query array for the prev, next, first and last links.
+ */
+ function getPagingQueries() {
+ if ( !$this->mQueryDone ) {
+ $this->doQuery();
+ }
+
+ # Don't announce the limit everywhere if it's the default
+ $urlLimit = $this->mLimit == $this->mDefaultLimit ? '' : $this->mLimit;
+
+ if ( $this->mIsFirst ) {
+ $prev = false;
+ $first = false;
+ } else {
+ $prev = array( 'dir' => 'prev', 'offset' => $this->mFirstShown, 'limit' => $urlLimit );
+ $first = array( 'limit' => $urlLimit );
+ }
+ if ( $this->mIsLast ) {
+ $next = false;
+ $last = false;
+ } else {
+ $next = array( 'offset' => $this->mLastShown, 'limit' => $urlLimit );
+ $last = array( 'dir' => 'prev', 'limit' => $urlLimit );
+ }
+ return compact( 'prev', 'next', 'first', 'last' );
+ }
+
+ /**
+ * Get paging links. If a link is disabled, the item from $disabledTexts will
+ * be used. If there is no such item, the unlinked text from $linkTexts will
+ * be used. Both $linkTexts and $disabledTexts are arrays of HTML.
+ */
+ function getPagingLinks( $linkTexts, $disabledTexts = array() ) {
+ $queries = $this->getPagingQueries();
+ $links = array();
+ foreach ( $queries as $type => $query ) {
+ if ( $query !== false ) {
+ $links[$type] = $this->makeLink( $linkTexts[$type], $queries[$type] );
+ } elseif ( isset( $disabledTexts[$type] ) ) {
+ $links[$type] = $disabledTexts[$type];
+ } else {
+ $links[$type] = $linkTexts[$type];
+ }
+ }
+ return $links;
+ }
+
+ function getLimitLinks() {
+ global $wgLang;
+ $links = array();
+ if ( $this->mIsBackwards ) {
+ $offset = $this->mPastTheEndIndex;
+ } else {
+ $offset = $this->mOffset;
+ }
+ foreach ( $this->mLimitsShown as $limit ) {
+ $links[] = $this->makeLink( $wgLang->formatNum( $limit ),
+ array( 'offset' => $offset, 'limit' => $limit ) );
+ }
+ return $links;
+ }
+
+ /**
+ * Abstract formatting function. This should return an HTML string
+ * representing the result row $row. Rows will be concatenated and
+ * returned by getBody()
+ */
+ abstract function formatRow( $row );
+
+ /**
+ * This function should be overridden to provide all parameters
+ * needed for the main paged query. It returns an associative
+ * array with the following elements:
+ * tables => Table(s) for passing to Database::select()
+ * fields => Field(s) for passing to Database::select(), may be *
+ * conds => WHERE conditions
+ * options => option array
+ */
+ abstract function getQueryInfo();
+
+ /**
+ * This function should be overridden to return the name of the
+ * index field.
+ */
+ abstract function getIndexField();
+}
+
+/**
+ * IndexPager with a formatted navigation bar
+ */
+abstract class ReverseChronologicalPager extends IndexPager {
+ public $mDefaultDirection = true;
+
+ function __construct() {
+ parent::__construct();
+ }
+
+ function getNavigationBar() {
+ global $wgLang;
+
+ if ( isset( $this->mNavigationBar ) ) {
+ return $this->mNavigationBar;
+ }
+ $linkTexts = array(
+ 'prev' => wfMsgHtml( "prevn", $this->mLimit ),
+ 'next' => wfMsgHtml( 'nextn', $this->mLimit ),
+ 'first' => wfMsgHtml('histlast'),
+ 'last' => wfMsgHtml( 'histfirst' )
+ );
+
+ $pagingLinks = $this->getPagingLinks( $linkTexts );
+ $limitLinks = $this->getLimitLinks();
+ $limits = implode( ' | ', $limitLinks );
+
+ $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits);
+ return $this->mNavigationBar;
+ }
+}
+
+/**
+ * Table-based display with a user-selectable sort order
+ */
+abstract class TablePager extends IndexPager {
+ var $mSort;
+ var $mCurrentRow;
+
+ function __construct() {
+ global $wgRequest;
+ $this->mSort = $wgRequest->getText( 'sort' );
+ if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) ) {
+ $this->mSort = $this->getDefaultSort();
+ }
+ if ( $wgRequest->getBool( 'asc' ) ) {
+ $this->mDefaultDirection = false;
+ } elseif ( $wgRequest->getBool( 'desc' ) ) {
+ $this->mDefaultDirection = true;
+ } /* Else leave it at whatever the class default is */
+
+ parent::__construct();
+ }
+
+ function getStartBody() {
+ global $wgStylePath;
+ $tableClass = htmlspecialchars( $this->getTableClass() );
+ $sortClass = htmlspecialchars( $this->getSortHeaderClass() );
+
+ $s = "<table border='1' class=\"$tableClass\"><thead><tr>\n";
+ $fields = $this->getFieldNames();
+
+ # Make table header
+ foreach ( $fields as $field => $name ) {
+ if ( strval( $name ) == '' ) {
+ $s .= "<th>&nbsp;</th>\n";
+ } elseif ( $this->isFieldSortable( $field ) ) {
+ $query = array( 'sort' => $field, 'limit' => $this->mLimit );
+ if ( $field == $this->mSort ) {
+ # This is the sorted column
+ # Prepare a link that goes in the other sort order
+ if ( $this->mDefaultDirection ) {
+ # Descending
+ $image = 'Arr_u.png';
+ $query['asc'] = '1';
+ $query['desc'] = '';
+ $alt = htmlspecialchars( wfMsg( 'descending_abbrev' ) );
+ } else {
+ # Ascending
+ $image = 'Arr_d.png';
+ $query['asc'] = '';
+ $query['desc'] = '1';
+ $alt = htmlspecialchars( wfMsg( 'ascending_abbrev' ) );
+ }
+ $image = htmlspecialchars( "$wgStylePath/common/images/$image" );
+ $link = $this->makeLink(
+ "<img width=\"12\" height=\"12\" alt=\"$alt\" src=\"$image\" />" .
+ htmlspecialchars( $name ), $query );
+ $s .= "<th class=\"$sortClass\">$link</th>\n";
+ } else {
+ $s .= '<th>' . $this->makeLink( htmlspecialchars( $name ), $query ) . "</th>\n";
+ }
+ } else {
+ $s .= '<th>' . htmlspecialchars( $name ) . "</th>\n";
+ }
+ }
+ $s .= "</tr></thead><tbody>\n";
+ return $s;
+ }
+
+ function getEndBody() {
+ return '</tbody></table>';
+ }
+
+ function getEmptyBody() {
+ $colspan = count( $this->getFieldNames() );
+ $msgEmpty = wfMsgHtml( 'table_pager_empty' );
+ return "<tr><td colspan=\"$colspan\">$msgEmpty</td></tr>\n";
+ }
+
+ function formatRow( $row ) {
+ $s = "<tr>\n";
+ $fieldNames = $this->getFieldNames();
+ $this->mCurrentRow = $row; # In case formatValue needs to know
+ foreach ( $fieldNames as $field => $name ) {
+ $value = isset( $row->$field ) ? $row->$field : null;
+ $formatted = strval( $this->formatValue( $field, $value ) );
+ if ( $formatted == '' ) {
+ $formatted = '&nbsp;';
+ }
+ $class = 'TablePager_col_' . htmlspecialchars( $field );
+ $s .= "<td class=\"$class\">$formatted</td>\n";
+ }
+ $s .= "</tr>\n";
+ return $s;
+ }
+
+ function getIndexField() {
+ return $this->mSort;
+ }
+
+ function getTableClass() {
+ return 'TablePager';
+ }
+
+ function getNavClass() {
+ return 'TablePager_nav';
+ }
+
+ function getSortHeaderClass() {
+ return 'TablePager_sort';
+ }
+
+ /**
+ * A navigation bar with images
+ */
+ function getNavigationBar() {
+ global $wgStylePath, $wgContLang;
+ $path = "$wgStylePath/common/images";
+ $labels = array(
+ 'first' => 'table_pager_first',
+ 'prev' => 'table_pager_prev',
+ 'next' => 'table_pager_next',
+ 'last' => 'table_pager_last',
+ );
+ $images = array(
+ 'first' => $wgContLang->isRTL() ? 'arrow_last_25.png' : 'arrow_first_25.png',
+ 'prev' => $wgContLang->isRTL() ? 'arrow_right_25.png' : 'arrow_left_25.png',
+ 'next' => $wgContLang->isRTL() ? 'arrow_left_25.png' : 'arrow_right_25.png',
+ 'last' => $wgContLang->isRTL() ? 'arrow_first_25.png' : 'arrow_last_25.png',
+ );
+ $disabledImages = array(
+ 'first' => $wgContLang->isRTL() ? 'arrow_disabled_last_25.png' : 'arrow_disabled_first_25.png',
+ 'prev' => $wgContLang->isRTL() ? 'arrow_disabled_right_25.png' : 'arrow_disabled_left_25.png',
+ 'next' => $wgContLang->isRTL() ? 'arrow_disabled_left_25.png' : 'arrow_disabled_right_25.png',
+ 'last' => $wgContLang->isRTL() ? 'arrow_disabled_first_25.png' : 'arrow_disabled_last_25.png',
+ );
+
+ $linkTexts = array();
+ $disabledTexts = array();
+ foreach ( $labels as $type => $label ) {
+ $msgLabel = wfMsgHtml( $label );
+ $linkTexts[$type] = "<img src=\"$path/{$images[$type]}\" alt=\"$msgLabel\"/><br/>$msgLabel";
+ $disabledTexts[$type] = "<img src=\"$path/{$disabledImages[$type]}\" alt=\"$msgLabel\"/><br/>$msgLabel";
+ }
+ $links = $this->getPagingLinks( $linkTexts, $disabledTexts );
+
+ $navClass = htmlspecialchars( $this->getNavClass() );
+ $s = "<table class=\"$navClass\" align=\"center\" cellpadding=\"3\"><tr>";
+ $cellAttrs = 'valign="top" align="center" width="' . 100 / count( $links ) . '%"';
+ foreach ( $labels as $type => $label ) {
+ $s .= "<td $cellAttrs>{$links[$type]}</td>\n";
+ }
+ $s .= '</tr></table>';
+ return $s;
+ }
+
+ /**
+ * Get a <select> element which has options for each of the allowed limits
+ */
+ function getLimitSelect() {
+ global $wgLang;
+ $s = "<select name=\"limit\">";
+ foreach ( $this->mLimitsShown as $limit ) {
+ $selected = $limit == $this->mLimit ? 'selected="selected"' : '';
+ $formattedLimit = $wgLang->formatNum( $limit );
+ $s .= "<option value=\"$limit\" $selected>$formattedLimit</option>\n";
+ }
+ $s .= "</select>";
+ return $s;
+ }
+
+ /**
+ * Get <input type="hidden"> elements for use in a method="get" form.
+ * Resubmits all defined elements of the $_GET array, except for a
+ * blacklist, passed in the $blacklist parameter.
+ */
+ function getHiddenFields( $blacklist = array() ) {
+ $blacklist = (array)$blacklist;
+ $query = $_GET;
+ foreach ( $blacklist as $name ) {
+ unset( $query[$name] );
+ }
+ $s = '';
+ foreach ( $query as $name => $value ) {
+ $encName = htmlspecialchars( $name );
+ $encValue = htmlspecialchars( $value );
+ $s .= "<input type=\"hidden\" name=\"$encName\" value=\"$encValue\"/>\n";
+ }
+ return $s;
+ }
+
+ /**
+ * Get a form containing a limit selection dropdown
+ */
+ function getLimitForm() {
+ # Make the select with some explanatory text
+ $url = $this->getTitle()->escapeLocalURL();
+ $msgSubmit = wfMsgHtml( 'table_pager_limit_submit' );
+ return
+ "<form method=\"get\" action=\"$url\">" .
+ wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) .
+ "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" .
+ $this->getHiddenFields( 'limit' ) .
+ "</form>\n";
+ }
+
+ /**
+ * Return true if the named field should be sortable by the UI, false otherwise
+ * @param string $field
+ */
+ abstract function isFieldSortable( $field );
+
+ /**
+ * Format a table cell. The return value should be HTML, but use an empty string
+ * not &nbsp; for empty cells. Do not include the <td> and </td>.
+ *
+ * @param string $name The database field name
+ * @param string $value The value retrieved from the database
+ *
+ * The current result row is available as $this->mCurrentRow, in case you need
+ * more context.
+ */
+ abstract function formatValue( $name, $value );
+
+ /**
+ * The database field name used as a default sort order
+ */
+ abstract function getDefaultSort();
+
+ /**
+ * An array mapping database field names to a textual description of the field
+ * name, for use in the table header. The description should be plain text, it
+ * will be HTML-escaped later.
+ */
+ abstract function getFieldNames();
+}
+?>
diff --git a/includes/Parser.php b/includes/Parser.php
index 31976baf..76783448 100644
--- a/includes/Parser.php
+++ b/includes/Parser.php
@@ -13,25 +13,13 @@
*/
define( 'MW_PARSER_VERSION', '1.6.1' );
-/**
- * Variable substitution O(N^2) attack
- *
- * Without countermeasures, it would be possible to attack the parser by saving
- * a page filled with a large number of inclusions of large pages. The size of
- * the generated page would be proportional to the square of the input size.
- * Hence, we limit the number of inclusions of any given page, thus bringing any
- * attack back to O(N).
- */
-
-define( 'MAX_INCLUDE_REPEAT', 100 );
-define( 'MAX_INCLUDE_SIZE', 1000000 ); // 1 Million
-
define( 'RLH_FOR_UPDATE', 1 );
# Allowed values for $mOutputType
define( 'OT_HTML', 1 );
define( 'OT_WIKI', 2 );
define( 'OT_MSG' , 3 );
+define( 'OT_PREPROCESS', 4 );
# Flags for setFunctionHook
define( 'SFH_NO_HASH', 1 );
@@ -90,7 +78,8 @@ define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 );
* settings:
* $wgUseTex*, $wgUseDynamicDates*, $wgInterwikiMagic*,
* $wgNamespacesWithSubpages, $wgAllowExternalImages*,
- * $wgLocaltimezone, $wgAllowSpecialInclusion*
+ * $wgLocaltimezone, $wgAllowSpecialInclusion*,
+ * $wgMaxArticleSize*
*
* * only within ParserOptions
* </pre>
@@ -109,6 +98,7 @@ class Parser
var $mOutput, $mAutonumber, $mDTopen, $mStripState = array();
var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
var $mInterwikiLinkHolders, $mLinkHolders, $mUniqPrefix;
+ var $mIncludeSizes;
var $mTemplates, // cache of already loaded templates, avoids
// multiple SQL queries for the same string
$mTemplatePath; // stores an unsorted hash of all the templates already loaded
@@ -119,6 +109,7 @@ class Parser
var $mOptions, // ParserOptions object
$mTitle, // Title context, used for self-link rendering and similar things
$mOutputType, // Output type, one of the OT_xxx constants
+ $ot, // Shortcut alias, see setOutputType()
$mRevisionId; // ID to display in {{REVISIONID}} tags
/**#@-*/
@@ -148,31 +139,35 @@ class Parser
$this->setHook( 'pre', array( $this, 'renderPreTag' ) );
- $this->setFunctionHook( MAG_NS, array( 'CoreParserFunctions', 'ns' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_URLENCODE, array( 'CoreParserFunctions', 'urlencode' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_LCFIRST, array( 'CoreParserFunctions', 'lcfirst' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_UCFIRST, array( 'CoreParserFunctions', 'ucfirst' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_LC, array( 'CoreParserFunctions', 'lc' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_UC, array( 'CoreParserFunctions', 'uc' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_LOCALURL, array( 'CoreParserFunctions', 'localurl' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_LOCALURLE, array( 'CoreParserFunctions', 'localurle' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_FULLURL, array( 'CoreParserFunctions', 'fullurl' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_FULLURLE, array( 'CoreParserFunctions', 'fullurle' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_FORMATNUM, array( 'CoreParserFunctions', 'formatnum' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_GRAMMAR, array( 'CoreParserFunctions', 'grammar' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_PLURAL, array( 'CoreParserFunctions', 'plural' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_NUMBEROFPAGES, array( 'CoreParserFunctions', 'numberofpages' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_NUMBEROFUSERS, array( 'CoreParserFunctions', 'numberofusers' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_NUMBEROFARTICLES, array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_NUMBEROFFILES, array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_NUMBEROFADMINS, array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH );
- $this->setFunctionHook( MAG_LANGUAGE, array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'int', array( 'CoreParserFunctions', 'intFunction' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'ns', array( 'CoreParserFunctions', 'ns' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'urlencode', array( 'CoreParserFunctions', 'urlencode' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'lcfirst', array( 'CoreParserFunctions', 'lcfirst' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'ucfirst', array( 'CoreParserFunctions', 'ucfirst' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'lc', array( 'CoreParserFunctions', 'lc' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'uc', array( 'CoreParserFunctions', 'uc' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'localurl', array( 'CoreParserFunctions', 'localurl' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'localurle', array( 'CoreParserFunctions', 'localurle' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'fullurl', array( 'CoreParserFunctions', 'fullurl' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'fullurle', array( 'CoreParserFunctions', 'fullurle' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'formatnum', array( 'CoreParserFunctions', 'formatnum' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'grammar', array( 'CoreParserFunctions', 'grammar' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'plural', array( 'CoreParserFunctions', 'plural' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofpages', array( 'CoreParserFunctions', 'numberofpages' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofusers', array( 'CoreParserFunctions', 'numberofusers' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofarticles', array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'anchorencode', array( 'CoreParserFunctions', 'anchorencode' ), SFH_NO_HASH );
if ( $wgAllowDisplayTitle ) {
- $this->setFunctionHook( MAG_DISPLAYTITLE, array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH );
}
if ( $wgAllowSlowParserFunctions ) {
- $this->setFunctionHook( MAG_PAGESINNAMESPACE, array( 'CoreParserFunctions', 'pagesinnamespace' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'pagesinnamespace', array( 'CoreParserFunctions', 'pagesinnamespace' ), SFH_NO_HASH );
}
$this->initialiseVariables();
@@ -187,6 +182,7 @@ class Parser
* @private
*/
function clearState() {
+ wfProfileIn( __METHOD__ );
if ( $this->mFirstCall ) {
$this->firstCallInit();
}
@@ -226,8 +222,25 @@ class Parser
$this->mShowToc = true;
$this->mForceTocPosition = false;
+ $this->mIncludeSizes = array(
+ 'pre-expand' => 0,
+ 'post-expand' => 0,
+ 'arg' => 0
+ );
wfRunHooks( 'ParserClearState', array( &$this ) );
+ wfProfileOut( __METHOD__ );
+ }
+
+ function setOutputType( $ot ) {
+ $this->mOutputType = $ot;
+ // Shortcut alias
+ $this->ot = array(
+ 'html' => $ot == OT_HTML,
+ 'wiki' => $ot == OT_WIKI,
+ 'msg' => $ot == OT_MSG,
+ 'pre' => $ot == OT_PREPROCESS,
+ );
}
/**
@@ -235,7 +248,7 @@ class Parser
*
* @public
*/
- function UniqPrefix() {
+ function uniqPrefix() {
return $this->mUniqPrefix;
}
@@ -259,7 +272,7 @@ class Parser
*/
global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang;
- $fname = 'Parser::parse';
+ $fname = 'Parser::parse-' . wfGetCaller();
wfProfileIn( $fname );
if ( $clearState ) {
@@ -268,8 +281,11 @@ class Parser
$this->mOptions = $options;
$this->mTitle =& $title;
- $this->mRevisionId = $revid;
- $this->mOutputType = OT_HTML;
+ $oldRevisionId = $this->mRevisionId;
+ if( $revid !== null ) {
+ $this->mRevisionId = $revid;
+ }
+ $this->setOutputType( OT_HTML );
//$text = $this->strip( $text, $this->mStripState );
// VOODOO MAGIC FIX! Sometimes the above segfaults in PHP5.
@@ -279,12 +295,6 @@ class Parser
$text = $this->strip( $text, $x );
wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$x ) );
- # Hook to suspend the parser in this state
- if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$x ) ) ) {
- wfProfileOut( $fname );
- return $text ;
- }
-
$text = $this->internalParse( $text );
$text = $this->unstrip( $text, $this->mStripState );
@@ -321,8 +331,8 @@ class Parser
} else {
# attempt to sanitize at least some nesting problems
# (bug #2702 and quite a few others)
- $tidyregs = array(
- # ''Something [http://www.cool.com cool''] -->
+ $tidyregs = array(
+ # ''Something [http://www.cool.com cool''] -->
# <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
'/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
'\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
@@ -337,10 +347,10 @@ class Parser
'\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
# remove empty italic or bold tag pairs, some
# introduced by rules above
- '/<([bi])><\/\\1>/' => ''
+ '/<([bi])><\/\\1>/' => '',
);
- $text = preg_replace(
+ $text = preg_replace(
array_keys( $tidyregs ),
array_values( $tidyregs ),
$text );
@@ -348,13 +358,63 @@ class Parser
wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) );
+ # Information on include size limits, for the benefit of users who try to skirt them
+ if ( max( $this->mIncludeSizes ) > 1000 ) {
+ $max = $this->mOptions->getMaxIncludeSize();
+ $text .= "<!-- \n" .
+ "Pre-expand include size: {$this->mIncludeSizes['pre-expand']} bytes\n" .
+ "Post-expand include size: {$this->mIncludeSizes['post-expand']} bytes\n" .
+ "Template argument size: {$this->mIncludeSizes['arg']} bytes\n" .
+ "Maximum: $max bytes\n" .
+ "-->\n";
+ }
$this->mOutput->setText( $text );
+ $this->mRevisionId = $oldRevisionId;
wfProfileOut( $fname );
return $this->mOutput;
}
/**
+ * Recursive parser entry point that can be called from an extension tag
+ * hook.
+ */
+ function recursiveTagParse( $text ) {
+ wfProfileIn( __METHOD__ );
+ $x =& $this->mStripState;
+ wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$x ) );
+ $text = $this->strip( $text, $x );
+ wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$x ) );
+ $text = $this->internalParse( $text );
+ wfProfileOut( __METHOD__ );
+ return $text;
+ }
+
+ /**
+ * Expand templates and variables in the text, producing valid, static wikitext.
+ * Also removes comments.
+ */
+ function preprocess( $text, $title, $options ) {
+ wfProfileIn( __METHOD__ );
+ $this->clearState();
+ $this->setOutputType( OT_PREPROCESS );
+ $this->mOptions = $options;
+ $this->mTitle = $title;
+ $x =& $this->mStripState;
+ wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$x ) );
+ $text = $this->strip( $text, $x );
+ wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$x ) );
+ if ( $this->mOptions->getRemoveComments() ) {
+ $text = Sanitizer::removeHTMLcomments( $text );
+ }
+ $text = $this->replaceVariables( $text );
+ $text = $this->unstrip( $text, $x );
+ $text = $this->unstripNowiki( $text, $x );
+ wfProfileOut( __METHOD__ );
+ return $text;
+ }
+
+ /**
* Get a random string
*
* @private
@@ -391,8 +451,7 @@ class Parser
* @static
*/
function extractTagsAndParams($elements, $text, &$matches, $uniq_prefix = ''){
- $rand = Parser::getRandomString();
- $n = 1;
+ static $n = 1;
$stripped = '';
$matches = array();
@@ -419,7 +478,7 @@ class Parser
$inside = $p[4];
}
- $marker = "$uniq_prefix-$element-$rand" . sprintf('%08X', $n++) . '-QINU';
+ $marker = "$uniq_prefix-$element-" . sprintf('%08X', $n++) . '-QINU';
$stripped .= $marker;
if ( $close === '/>' ) {
@@ -470,11 +529,10 @@ class Parser
* @private
*/
function strip( $text, &$state, $stripcomments = false , $dontstrip = array () ) {
+ wfProfileIn( __METHOD__ );
$render = ($this->mOutputType == OT_HTML);
- # Replace any instances of the placeholders
$uniq_prefix = $this->mUniqPrefix;
- #$text = str_replace( $uniq_prefix, wfHtmlEscapeFirst( $uniq_prefix ), $text );
$commentState = array();
$elements = array_merge(
@@ -501,6 +559,7 @@ class Parser
list( $element, $content, $params, $tag ) = $data;
if( $render ) {
$tagName = strtolower( $element );
+ wfProfileIn( __METHOD__."-render-$tagName" );
switch( $tagName ) {
case '!--':
// Comment
@@ -535,17 +594,25 @@ class Parser
throw new MWException( "Invalid call hook $element" );
}
}
+ wfProfileOut( __METHOD__."-render-$tagName" );
} else {
// Just stripping tags; keep the source
$output = $tag;
}
+
+ // Unstrip the output, because unstrip() is no longer recursive so
+ // it won't do it itself
+ $output = $this->unstrip( $output, $state );
+
if( !$stripcomments && $element == '!--' ) {
$commentState[$marker] = $output;
+ } elseif ( $element == 'html' || $element == 'nowiki' ) {
+ $state['nowiki'][$marker] = $output;
} else {
- $state[$element][$marker] = $output;
+ $state['general'][$marker] = $output;
}
}
-
+
# Unstrip comments unless explicitly told otherwise.
# (The comments are always stripped prior to this point, so as to
# not invoke any extension tags / parser hooks contained within
@@ -555,6 +622,7 @@ class Parser
$text = strtr( $text, $commentState );
}
+ wfProfileOut( __METHOD__ );
return $text;
}
@@ -565,20 +633,14 @@ class Parser
* @private
*/
function unstrip( $text, &$state ) {
- if ( !is_array( $state ) ) {
+ if ( !isset( $state['general'] ) ) {
return $text;
}
- $replacements = array();
- foreach( $state as $tag => $contentDict ) {
- if( $tag != 'nowiki' && $tag != 'html' ) {
- foreach( $contentDict as $uniq => $content ) {
- $replacements[$uniq] = $content;
- }
- }
- }
- $text = strtr( $text, $replacements );
-
+ wfProfileIn( __METHOD__ );
+ # TODO: good candidate for FSS
+ $text = strtr( $text, $state['general'] );
+ wfProfileOut( __METHOD__ );
return $text;
}
@@ -588,20 +650,15 @@ class Parser
* @private
*/
function unstripNoWiki( $text, &$state ) {
- if ( !is_array( $state ) ) {
+ if ( !isset( $state['nowiki'] ) ) {
return $text;
}
- $replacements = array();
- foreach( $state as $tag => $contentDict ) {
- if( $tag == 'nowiki' || $tag == 'html' ) {
- foreach( $contentDict as $uniq => $content ) {
- $replacements[$uniq] = $content;
- }
- }
- }
- $text = strtr( $text, $replacements );
-
+ wfProfileIn( __METHOD__ );
+ # TODO: good candidate for FSS
+ $text = strtr( $text, $state['nowiki'] );
+ wfProfileOut( __METHOD__ );
+
return $text;
}
@@ -617,7 +674,7 @@ class Parser
if ( !$state ) {
$state = array();
}
- $state['item'][$rnd] = $text;
+ $state['general'][$rnd] = $text;
return $rnd;
}
@@ -797,13 +854,13 @@ class Parser
}
$after = substr ( $x , 1 ) ;
if ( $fc == '!' ) $after = str_replace ( '!!' , '||' , $after ) ;
-
+
// Split up multiple cells on the same line.
// FIXME: This can result in improper nesting of tags processed
// by earlier parser steps, but should avoid splitting up eg
// attribute values containing literal "||".
$after = wfExplodeMarkup( '||', $after );
-
+
$t[$k] = '' ;
# Loop through each table cell
@@ -877,11 +934,17 @@ class Parser
$fname = 'Parser::internalParse';
wfProfileIn( $fname );
+ # Hook to suspend the parser in this state
+ if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$x ) ) ) {
+ wfProfileOut( $fname );
+ return $text ;
+ }
+
# Remove <noinclude> tags and <includeonly> sections
$text = strtr( $text, array( '<onlyinclude>' => '' , '</onlyinclude>' => '' ) );
$text = strtr( $text, array( '<noinclude>' => '', '</noinclude>' => '') );
$text = preg_replace( '/<includeonly>.*?<\/includeonly>/s', '', $text );
-
+
$text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ) );
$text = $this->replaceVariables( $text, $args );
@@ -923,9 +986,52 @@ class Parser
* @private
*/
function &doMagicLinks( &$text ) {
- $text = $this->magicISBN( $text );
- $text = $this->magicRFC( $text, 'RFC ', 'rfcurl' );
- $text = $this->magicRFC( $text, 'PMID ', 'pubmedurl' );
+ wfProfileIn( __METHOD__ );
+ $text = preg_replace_callback(
+ '!(?: # Start cases
+ <a.*?</a> | # Skip link text
+ <.*?> | # Skip stuff inside HTML elements
+ (?:RFC|PMID)\s+([0-9]+) | # RFC or PMID, capture number as m[1]
+ ISBN\s+([0-9Xx-]+) # ISBN, capture number as m[2]
+ )!x', array( &$this, 'magicLinkCallback' ), $text );
+ wfProfileOut( __METHOD__ );
+ return $text;
+ }
+
+ function magicLinkCallback( $m ) {
+ if ( substr( $m[0], 0, 1 ) == '<' ) {
+ # Skip HTML element
+ return $m[0];
+ } elseif ( substr( $m[0], 0, 4 ) == 'ISBN' ) {
+ $isbn = $m[2];
+ $num = strtr( $isbn, array(
+ '-' => '',
+ ' ' => '',
+ 'x' => 'X',
+ ));
+ $titleObj = Title::makeTitle( NS_SPECIAL, 'Booksources' );
+ $text = '<a href="' .
+ $titleObj->escapeLocalUrl( "isbn=$num" ) .
+ "\" class=\"internal\">ISBN $isbn</a>";
+ } else {
+ if ( substr( $m[0], 0, 3 ) == 'RFC' ) {
+ $keyword = 'RFC';
+ $urlmsg = 'rfcurl';
+ $id = $m[1];
+ } elseif ( substr( $m[0], 0, 4 ) == 'PMID' ) {
+ $keyword = 'PMID';
+ $urlmsg = 'pubmedurl';
+ $id = $m[1];
+ } else {
+ throw new MWException( __METHOD__.': unrecognised match type "' .
+ substr($m[0], 0, 20 ) . '"' );
+ }
+
+ $url = wfMsg( $urlmsg, $id);
+ $sk =& $this->mOptions->getSkin();
+ $la = $sk->getExternalLinkAttributes( $url, $keyword.$id );
+ $text = "<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>";
+ }
return $text;
}
@@ -1193,13 +1299,8 @@ class Parser
}
$text = $wgContLang->markNoConversion($text);
-
- # Normalize any HTML entities in input. They will be
- # re-escaped by makeExternalLink().
- $url = Sanitizer::decodeCharReferences( $url );
- # Escape any control characters introduced by the above step
- $url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F]/e', "urlencode('\\0')", $url );
+ $url = Sanitizer::cleanUrl( $url );
# Process the trail (i.e. everything after this link up until start of the next link),
# replacing any non-bracketed links
@@ -1280,12 +1381,7 @@ class Parser
$url = substr( $url, 0, -$numSepChars );
}
- # Normalize any HTML entities in input. They will be
- # re-escaped by makeExternalLink() or maybeMakeExternalImage()
- $url = Sanitizer::decodeCharReferences( $url );
-
- # Escape any control characters introduced by the above step
- $url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F]/e', "urlencode('\\0')", $url );
+ $url = Sanitizer::cleanUrl( $url );
# Is this an external image?
$text = $this->maybeMakeExternalImage( $url );
@@ -1316,7 +1412,7 @@ class Parser
* the URL differently; as a workaround, just use the output for
* statistical records, not for actual linking/output.
*/
- function replaceUnusualEscapes( $url ) {
+ static function replaceUnusualEscapes( $url ) {
return preg_replace_callback( '/%[0-9A-Fa-f]{2}/',
array( 'Parser', 'replaceUnusualEscapesCallback' ), $url );
}
@@ -1327,7 +1423,7 @@ class Parser
* @static
* @private
*/
- function replaceUnusualEscapesCallback( $matches ) {
+ private static function replaceUnusualEscapesCallback( $matches ) {
$char = urldecode( $matches[0] );
$ord = ord( $char );
// Is it an unsafe or HTTP reserved character according to RFC 1738?
@@ -1397,7 +1493,7 @@ class Parser
$useLinkPrefixExtension = $wgContLang->linkPrefixExtension();
if( is_null( $this->mTitle ) ) {
- throw new MWException( 'nooo' );
+ throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
}
$nottalk = !$this->mTitle->isTalkPage();
@@ -1412,10 +1508,8 @@ class Parser
}
$selflink = $this->mTitle->getPrefixedText();
- wfProfileOut( $fname.'-setup' );
-
- $checkVariantLink = sizeof($wgContLang->getVariants())>1;
$useSubpages = $this->areSubpagesAllowed();
+ wfProfileOut( $fname.'-setup' );
# Loop for each link
for ($k = 0; isset( $a[$k] ); $k++) {
@@ -1438,6 +1532,7 @@ class Parser
$might_be_img = false;
+ wfProfileIn( "$fname-e1" );
if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
$text = $m[2];
# If we get a ] at the beginning of $m[3] that means we have a link that's something like:
@@ -1449,27 +1544,33 @@ class Parser
# and no image is in sight. See bug 2095.
#
if( $text !== '' &&
- preg_match( "/^\](.*)/s", $m[3], $n ) &&
+ substr( $m[3], 0, 1 ) === ']' &&
strpos($text, '[') !== false
)
{
$text .= ']'; # so that replaceExternalLinks($text) works later
- $m[3] = $n[1];
+ $m[3] = substr( $m[3], 1 );
}
# fix up urlencoded title texts
- if(preg_match('/%/', $m[1] ))
+ if( strpos( $m[1], '%' ) !== false ) {
# Should anchors '#' also be rejected?
$m[1] = str_replace( array('<', '>'), array('&lt;', '&gt;'), urldecode($m[1]) );
+ }
$trail = $m[3];
} elseif( preg_match($e1_img, $line, $m) ) { # Invalid, but might be an image with a link in its caption
$might_be_img = true;
$text = $m[2];
- if(preg_match('/%/', $m[1] )) $m[1] = urldecode($m[1]);
+ if ( strpos( $m[1], '%' ) !== false ) {
+ $m[1] = urldecode($m[1]);
+ }
$trail = "";
} else { # Invalid form; output directly
$s .= $prefix . '[[' . $line ;
+ wfProfileOut( "$fname-e1" );
continue;
}
+ wfProfileOut( "$fname-e1" );
+ wfProfileIn( "$fname-misc" );
# Don't allow internal links to pages containing
# PROTO: where PROTO is a valid URL protocol; these
@@ -1492,38 +1593,37 @@ class Parser
$link = substr($link, 1);
}
+ wfProfileOut( "$fname-misc" );
+ wfProfileIn( "$fname-title" );
$nt = Title::newFromText( $this->unstripNoWiki($link, $this->mStripState) );
if( !$nt ) {
$s .= $prefix . '[[' . $line;
+ wfProfileOut( "$fname-title" );
continue;
}
- #check other language variants of the link
- #if the article does not exist
- if( $checkVariantLink
- && $nt->getArticleID() == 0 ) {
- $wgContLang->findVariantLink($link, $nt);
- }
-
$ns = $nt->getNamespace();
$iw = $nt->getInterWiki();
-
+ wfProfileOut( "$fname-title" );
+
if ($might_be_img) { # if this is actually an invalid link
+ wfProfileIn( "$fname-might_be_img" );
if ($ns == NS_IMAGE && $noforce) { #but might be an image
$found = false;
while (isset ($a[$k+1]) ) {
#look at the next 'line' to see if we can close it there
$spliced = array_splice( $a, $k + 1, 1 );
$next_line = array_shift( $spliced );
- if( preg_match("/^(.*?]].*?)]](.*)$/sD", $next_line, $m) ) {
- # the first ]] closes the inner link, the second the image
+ $m = explode( ']]', $next_line, 3 );
+ if ( count( $m ) == 3 ) {
+ # the first ]] closes the inner link, the second the image
$found = true;
- $text .= '[[' . $m[1];
+ $text .= "[[{$m[0]}]]{$m[1]}";
$trail = $m[2];
break;
- } elseif( preg_match("/^.*?]].*$/sD", $next_line, $m) ) {
+ } elseif ( count( $m ) == 2 ) {
#if there's exactly one ]] that's fine, we'll keep looking
- $text .= '[[' . $m[0];
+ $text .= "[[{$m[0]}]]{$m[1]}";
} else {
#if $next_line is invalid too, we need look no further
$text .= '[[' . $next_line;
@@ -1534,35 +1634,40 @@ class Parser
# we couldn't find the end of this imageLink, so output it raw
#but don't ignore what might be perfectly normal links in the text we've examined
$text = $this->replaceInternalLinks($text);
- $s .= $prefix . '[[' . $link . '|' . $text;
+ $s .= "{$prefix}[[$link|$text";
# note: no $trail, because without an end, there *is* no trail
+ wfProfileOut( "$fname-might_be_img" );
continue;
}
} else { #it's not an image, so output it raw
- $s .= $prefix . '[[' . $link . '|' . $text;
+ $s .= "{$prefix}[[$link|$text";
# note: no $trail, because without an end, there *is* no trail
+ wfProfileOut( "$fname-might_be_img" );
continue;
}
+ wfProfileOut( "$fname-might_be_img" );
}
$wasblank = ( '' == $text );
if( $wasblank ) $text = $link;
-
# Link not escaped by : , create the various objects
if( $noforce ) {
# Interwikis
+ wfProfileIn( "$fname-interwiki" );
if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) {
$this->mOutput->addLanguageLink( $nt->getFullText() );
$s = rtrim($s . "\n");
$s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail;
+ wfProfileOut( "$fname-interwiki" );
continue;
}
+ wfProfileOut( "$fname-interwiki" );
if ( $ns == NS_IMAGE ) {
wfProfileIn( "$fname-image" );
- if ( !wfIsBadImage( $nt->getDBkey() ) ) {
+ if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
# recursively parse links inside the image caption
# actually, this will parse them in any other parameters, too,
# but it might be hard to fix that, and it doesn't matter ATM
@@ -1630,12 +1735,13 @@ class Parser
$s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix );
continue;
} elseif( $ns == NS_IMAGE ) {
- $img = Image::newFromTitle( $nt );
+ $img = new Image( $nt );
if( $img->exists() ) {
// Force a blue link if the file exists; may be a remote
// upload on the shared repository, and we want to see its
// auto-generated page.
$s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix );
+ $this->mOutput->addLink( $nt );
continue;
}
}
@@ -1648,11 +1754,12 @@ class Parser
/**
* Make a link placeholder. The text returned can be later resolved to a real link with
* replaceLinkHolders(). This is done for two reasons: firstly to avoid further
- * parsing of interwiki links, and secondly to allow all extistence checks and
+ * parsing of interwiki links, and secondly to allow all existence checks and
* article length checks (for stub links) to be bundled into a single query.
*
*/
function makeLinkHolder( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
+ wfProfileIn( __METHOD__ );
if ( ! is_object($nt) ) {
# Fail gracefully
$retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
@@ -1674,6 +1781,7 @@ class Parser
$retVal = '<!--LINK '. ($nr-1) ."-->{$trail}";
}
}
+ wfProfileOut( __METHOD__ );
return $retVal;
}
@@ -1745,6 +1853,9 @@ class Parser
wfProfileIn( $fname );
$ret = $target; # default return value is no change
+ # bug 7425
+ $target = trim( $target );
+
# Some namespaces don't allow subpages,
# so only perform processing if subpages are allowed
if( $this->areSubpagesAllowed() ) {
@@ -2049,14 +2160,14 @@ class Parser
function findColonNoLinks($str, &$before, &$after) {
$fname = 'Parser::findColonNoLinks';
wfProfileIn( $fname );
-
+
$pos = strpos( $str, ':' );
if( $pos === false ) {
// Nothing to find!
wfProfileOut( $fname );
return false;
}
-
+
$lt = strpos( $str, '<' );
if( $lt === false || $lt > $pos ) {
// Easy; no tag nesting to worry about
@@ -2065,14 +2176,14 @@ class Parser
wfProfileOut( $fname );
return $pos;
}
-
+
// Ugly state machine to walk through avoiding tags.
$state = MW_COLON_STATE_TEXT;
$stack = 0;
$len = strlen( $str );
for( $i = 0; $i < $len; $i++ ) {
$c = $str{$i};
-
+
switch( $state ) {
// (Using the number is a performance hack for common cases)
case 0: // MW_COLON_STATE_TEXT:
@@ -2222,107 +2333,165 @@ class Parser
$ts = time();
wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
+ # Use the time zone
+ global $wgLocaltimezone;
+ if ( isset( $wgLocaltimezone ) ) {
+ $oldtz = getenv( 'TZ' );
+ putenv( 'TZ='.$wgLocaltimezone );
+ }
+ $localTimestamp = date( 'YmdHis', $ts );
+ $localMonth = date( 'm', $ts );
+ $localMonthName = date( 'n', $ts );
+ $localDay = date( 'j', $ts );
+ $localDay2 = date( 'd', $ts );
+ $localDayOfWeek = date( 'w', $ts );
+ $localWeek = date( 'W', $ts );
+ $localYear = date( 'Y', $ts );
+ $localHour = date( 'H', $ts );
+ if ( isset( $wgLocaltimezone ) ) {
+ putenv( 'TZ='.$oldtz );
+ }
+
switch ( $index ) {
- case MAG_CURRENTMONTH:
+ case 'currentmonth':
return $varCache[$index] = $wgContLang->formatNum( date( 'm', $ts ) );
- case MAG_CURRENTMONTHNAME:
+ case 'currentmonthname':
return $varCache[$index] = $wgContLang->getMonthName( date( 'n', $ts ) );
- case MAG_CURRENTMONTHNAMEGEN:
+ case 'currentmonthnamegen':
return $varCache[$index] = $wgContLang->getMonthNameGen( date( 'n', $ts ) );
- case MAG_CURRENTMONTHABBREV:
+ case 'currentmonthabbrev':
return $varCache[$index] = $wgContLang->getMonthAbbreviation( date( 'n', $ts ) );
- case MAG_CURRENTDAY:
+ case 'currentday':
return $varCache[$index] = $wgContLang->formatNum( date( 'j', $ts ) );
- case MAG_CURRENTDAY2:
+ case 'currentday2':
return $varCache[$index] = $wgContLang->formatNum( date( 'd', $ts ) );
- case MAG_PAGENAME:
+ case 'localmonth':
+ return $varCache[$index] = $wgContLang->formatNum( $localMonth );
+ case 'localmonthname':
+ return $varCache[$index] = $wgContLang->getMonthName( $localMonthName );
+ case 'localmonthnamegen':
+ return $varCache[$index] = $wgContLang->getMonthNameGen( $localMonthName );
+ case 'localmonthabbrev':
+ return $varCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName );
+ case 'localday':
+ return $varCache[$index] = $wgContLang->formatNum( $localDay );
+ case 'localday2':
+ return $varCache[$index] = $wgContLang->formatNum( $localDay2 );
+ case 'pagename':
return $this->mTitle->getText();
- case MAG_PAGENAMEE:
+ case 'pagenamee':
return $this->mTitle->getPartialURL();
- case MAG_FULLPAGENAME:
+ case 'fullpagename':
return $this->mTitle->getPrefixedText();
- case MAG_FULLPAGENAMEE:
+ case 'fullpagenamee':
return $this->mTitle->getPrefixedURL();
- case MAG_SUBPAGENAME:
+ case 'subpagename':
return $this->mTitle->getSubpageText();
- case MAG_SUBPAGENAMEE:
+ case 'subpagenamee':
return $this->mTitle->getSubpageUrlForm();
- case MAG_BASEPAGENAME:
+ case 'basepagename':
return $this->mTitle->getBaseText();
- case MAG_BASEPAGENAMEE:
+ case 'basepagenamee':
return wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) );
- case MAG_TALKPAGENAME:
+ case 'talkpagename':
if( $this->mTitle->canTalk() ) {
$talkPage = $this->mTitle->getTalkPage();
return $talkPage->getPrefixedText();
} else {
return '';
}
- case MAG_TALKPAGENAMEE:
+ case 'talkpagenamee':
if( $this->mTitle->canTalk() ) {
$talkPage = $this->mTitle->getTalkPage();
return $talkPage->getPrefixedUrl();
} else {
return '';
}
- case MAG_SUBJECTPAGENAME:
+ case 'subjectpagename':
$subjPage = $this->mTitle->getSubjectPage();
return $subjPage->getPrefixedText();
- case MAG_SUBJECTPAGENAMEE:
+ case 'subjectpagenamee':
$subjPage = $this->mTitle->getSubjectPage();
return $subjPage->getPrefixedUrl();
- case MAG_REVISIONID:
+ case 'revisionid':
return $this->mRevisionId;
- case MAG_NAMESPACE:
+ case 'revisionday':
+ return intval( substr( wfRevisionTimestamp( $this->mRevisionId ), 6, 2 ) );
+ case 'revisionday2':
+ return substr( wfRevisionTimestamp( $this->mRevisionId ), 6, 2 );
+ case 'revisionmonth':
+ return intval( substr( wfRevisionTimestamp( $this->mRevisionId ), 4, 2 ) );
+ case 'revisionyear':
+ return substr( wfRevisionTimestamp( $this->mRevisionId ), 0, 4 );
+ case 'revisiontimestamp':
+ return wfRevisionTimestamp( $this->mRevisionId );
+ case 'namespace':
return str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
- case MAG_NAMESPACEE:
+ case 'namespacee':
return wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
- case MAG_TALKSPACE:
+ case 'talkspace':
return $this->mTitle->canTalk() ? str_replace('_',' ',$this->mTitle->getTalkNsText()) : '';
- case MAG_TALKSPACEE:
+ case 'talkspacee':
return $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
- case MAG_SUBJECTSPACE:
+ case 'subjectspace':
return $this->mTitle->getSubjectNsText();
- case MAG_SUBJECTSPACEE:
+ case 'subjectspacee':
return( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
- case MAG_CURRENTDAYNAME:
+ case 'currentdayname':
return $varCache[$index] = $wgContLang->getWeekdayName( date( 'w', $ts ) + 1 );
- case MAG_CURRENTYEAR:
+ case 'currentyear':
return $varCache[$index] = $wgContLang->formatNum( date( 'Y', $ts ), true );
- case MAG_CURRENTTIME:
+ case 'currenttime':
return $varCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false );
- case MAG_CURRENTWEEK:
+ case 'currenthour':
+ return $varCache[$index] = $wgContLang->formatNum( date( 'H', $ts ), true );
+ case 'currentweek':
// @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
// int to remove the padding
return $varCache[$index] = $wgContLang->formatNum( (int)date( 'W', $ts ) );
- case MAG_CURRENTDOW:
+ case 'currentdow':
return $varCache[$index] = $wgContLang->formatNum( date( 'w', $ts ) );
- case MAG_NUMBEROFARTICLES:
+ case 'localdayname':
+ return $varCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 );
+ case 'localyear':
+ return $varCache[$index] = $wgContLang->formatNum( $localYear, true );
+ case 'localtime':
+ return $varCache[$index] = $wgContLang->time( $localTimestamp, false, false );
+ case 'localhour':
+ return $varCache[$index] = $wgContLang->formatNum( $localHour, true );
+ case 'localweek':
+ // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
+ // int to remove the padding
+ return $varCache[$index] = $wgContLang->formatNum( (int)$localWeek );
+ case 'localdow':
+ return $varCache[$index] = $wgContLang->formatNum( $localDayOfWeek );
+ case 'numberofarticles':
return $varCache[$index] = $wgContLang->formatNum( wfNumberOfArticles() );
- case MAG_NUMBEROFFILES:
+ case 'numberoffiles':
return $varCache[$index] = $wgContLang->formatNum( wfNumberOfFiles() );
- case MAG_NUMBEROFUSERS:
+ case 'numberofusers':
return $varCache[$index] = $wgContLang->formatNum( wfNumberOfUsers() );
- case MAG_NUMBEROFPAGES:
+ case 'numberofpages':
return $varCache[$index] = $wgContLang->formatNum( wfNumberOfPages() );
- case MAG_NUMBEROFADMINS:
+ case 'numberofadmins':
return $varCache[$index] = $wgContLang->formatNum( wfNumberOfAdmins() );
- case MAG_CURRENTTIMESTAMP:
+ case 'currenttimestamp':
return $varCache[$index] = wfTimestampNow();
- case MAG_CURRENTVERSION:
- global $wgVersion;
- return $wgVersion;
- case MAG_SITENAME:
+ case 'localtimestamp':
+ return $varCache[$index] = $localTimestamp;
+ case 'currentversion':
+ return $varCache[$index] = SpecialVersion::getVersion();
+ case 'sitename':
return $wgSitename;
- case MAG_SERVER:
+ case 'server':
return $wgServer;
- case MAG_SERVERNAME:
+ case 'servername':
return $wgServerName;
- case MAG_SCRIPTPATH:
+ case 'scriptpath':
return $wgScriptPath;
- case MAG_DIRECTIONMARK:
+ case 'directionmark':
return $wgContLang->getDirMark();
- case MAG_CONTENTLANGUAGE:
+ case 'contentlanguage':
global $wgContLanguageCode;
return $wgContLanguageCode;
default:
@@ -2342,10 +2511,10 @@ class Parser
function initialiseVariables() {
$fname = 'Parser::initialiseVariables';
wfProfileIn( $fname );
- global $wgVariableIDs;
+ $variableIDs = MagicWord::getVariableIDs();
$this->mVariables = array();
- foreach ( $wgVariableIDs as $id ) {
+ foreach ( $variableIDs as $id ) {
$mw =& MagicWord::get( $id );
$mw->addToArray( $this->mVariables, $id );
}
@@ -2361,172 +2530,169 @@ class Parser
* '{' => array( # opening parentheses
* 'end' => '}', # closing parentheses
* 'cb' => array(2 => callback, # replacement callback to call if {{..}} is found
- * 4 => callback # replacement callback to call if {{{{..}}}} is found
+ * 3 => callback # replacement callback to call if {{{..}}} is found
* )
* )
+ * 'min' => 2, # Minimum parenthesis count in cb
+ * 'max' => 3, # Maximum parenthesis count in cb
* @private
*/
function replace_callback ($text, $callbacks) {
- wfProfileIn( __METHOD__ . '-self' );
+ wfProfileIn( __METHOD__ );
$openingBraceStack = array(); # this array will hold a stack of parentheses which are not closed yet
$lastOpeningBrace = -1; # last not closed parentheses
- for ($i = 0; $i < strlen($text); $i++) {
- # check for any opening brace
- $rule = null;
- $nextPos = -1;
- foreach ($callbacks as $key => $value) {
- $pos = strpos ($text, $key, $i);
- if (false !== $pos && (-1 == $nextPos || $pos < $nextPos)) {
- $rule = $value;
- $nextPos = $pos;
- }
+ $validOpeningBraces = implode( '', array_keys( $callbacks ) );
+
+ $i = 0;
+ while ( $i < strlen( $text ) ) {
+ # Find next opening brace, closing brace or pipe
+ if ( $lastOpeningBrace == -1 ) {
+ $currentClosing = '';
+ $search = $validOpeningBraces;
+ } else {
+ $currentClosing = $openingBraceStack[$lastOpeningBrace]['braceEnd'];
+ $search = $validOpeningBraces . '|' . $currentClosing;
}
-
- if ($lastOpeningBrace >= 0) {
- $pos = strpos ($text, $openingBraceStack[$lastOpeningBrace]['braceEnd'], $i);
-
- if (false !== $pos && (-1 == $nextPos || $pos < $nextPos)){
- $rule = null;
- $nextPos = $pos;
- }
-
- $pos = strpos ($text, '|', $i);
-
- if (false !== $pos && (-1 == $nextPos || $pos < $nextPos)){
- $rule = null;
- $nextPos = $pos;
+ $rule = null;
+ $i += strcspn( $text, $search, $i );
+ if ( $i < strlen( $text ) ) {
+ if ( $text[$i] == '|' ) {
+ $found = 'pipe';
+ } elseif ( $text[$i] == $currentClosing ) {
+ $found = 'close';
+ } elseif ( isset( $callbacks[$text[$i]] ) ) {
+ $found = 'open';
+ $rule = $callbacks[$text[$i]];
+ } else {
+ # Some versions of PHP have a strcspn which stops on null characters
+ # Ignore and continue
+ ++$i;
+ continue;
}
- }
-
- if ($nextPos == -1)
+ } else {
+ # All done
break;
+ }
- $i = $nextPos;
-
- # found openning brace, lets add it to parentheses stack
- if (null != $rule) {
+ if ( $found == 'open' ) {
+ # found opening brace, let's add it to parentheses stack
$piece = array('brace' => $text[$i],
'braceEnd' => $rule['end'],
- 'count' => 1,
'title' => '',
'parts' => null);
- # count openning brace characters
- while ($i+1 < strlen($text) && $text[$i+1] == $piece['brace']) {
- $piece['count']++;
- $i++;
- }
-
- $piece['startAt'] = $i+1;
- $piece['partStart'] = $i+1;
+ # count opening brace characters
+ $piece['count'] = strspn( $text, $piece['brace'], $i );
+ $piece['startAt'] = $piece['partStart'] = $i + $piece['count'];
+ $i += $piece['count'];
- # we need to add to stack only if openning brace count is enough for any given rule
- foreach ($rule['cb'] as $cnt => $fn) {
- if ($piece['count'] >= $cnt) {
- $lastOpeningBrace ++;
- $openingBraceStack[$lastOpeningBrace] = $piece;
- break;
+ # we need to add to stack only if opening brace count is enough for one of the rules
+ if ( $piece['count'] >= $rule['min'] ) {
+ $lastOpeningBrace ++;
+ $openingBraceStack[$lastOpeningBrace] = $piece;
+ }
+ } elseif ( $found == 'close' ) {
+ # lets check if it is enough characters for closing brace
+ $maxCount = $openingBraceStack[$lastOpeningBrace]['count'];
+ $count = strspn( $text, $text[$i], $i, $maxCount );
+
+ # check for maximum matching characters (if there are 5 closing
+ # characters, we will probably need only 3 - depending on the rules)
+ $matchingCount = 0;
+ $matchingCallback = null;
+ $cbType = $callbacks[$openingBraceStack[$lastOpeningBrace]['brace']];
+ if ( $count > $cbType['max'] ) {
+ # The specified maximum exists in the callback array, unless the caller
+ # has made an error
+ $matchingCount = $cbType['max'];
+ } else {
+ # Count is less than the maximum
+ # Skip any gaps in the callback array to find the true largest match
+ # Need to use array_key_exists not isset because the callback can be null
+ $matchingCount = $count;
+ while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $cbType['cb'] ) ) {
+ --$matchingCount;
}
}
- continue;
- }
- else if ($lastOpeningBrace >= 0) {
- # first check if it is a closing brace
- if ($openingBraceStack[$lastOpeningBrace]['braceEnd'] == $text[$i]) {
- # lets check if it is enough characters for closing brace
- $count = 1;
- while ($i+$count < strlen($text) && $text[$i+$count] == $text[$i])
- $count++;
-
- # if there are more closing parentheses than opening ones, we parse less
- if ($openingBraceStack[$lastOpeningBrace]['count'] < $count)
- $count = $openingBraceStack[$lastOpeningBrace]['count'];
-
- # check for maximum matching characters (if there are 5 closing characters, we will probably need only 3 - depending on the rules)
- $matchingCount = 0;
- $matchingCallback = null;
- foreach ($callbacks[$openingBraceStack[$lastOpeningBrace]['brace']]['cb'] as $cnt => $fn) {
- if ($count >= $cnt && $matchingCount < $cnt) {
- $matchingCount = $cnt;
- $matchingCallback = $fn;
- }
- }
+ if ($matchingCount <= 0) {
+ $i += $count;
+ continue;
+ }
+ $matchingCallback = $cbType['cb'][$matchingCount];
- if ($matchingCount == 0) {
- $i += $count - 1;
- continue;
- }
+ # let's set a title or last part (if '|' was found)
+ if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
+ $openingBraceStack[$lastOpeningBrace]['title'] =
+ substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+ $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
+ } else {
+ $openingBraceStack[$lastOpeningBrace]['parts'][] =
+ substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+ $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
+ }
- # lets set a title or last part (if '|' was found)
- if (null === $openingBraceStack[$lastOpeningBrace]['parts'])
- $openingBraceStack[$lastOpeningBrace]['title'] = substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
- else
- $openingBraceStack[$lastOpeningBrace]['parts'][] = substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
-
- $pieceStart = $openingBraceStack[$lastOpeningBrace]['startAt'] - $matchingCount;
- $pieceEnd = $i + $matchingCount;
-
- if( is_callable( $matchingCallback ) ) {
- $cbArgs = array (
- 'text' => substr($text, $pieceStart, $pieceEnd - $pieceStart),
- 'title' => trim($openingBraceStack[$lastOpeningBrace]['title']),
- 'parts' => $openingBraceStack[$lastOpeningBrace]['parts'],
- 'lineStart' => (($pieceStart > 0) && ($text[$pieceStart-1] == "\n")),
- );
- # finally we can call a user callback and replace piece of text
- wfProfileOut( __METHOD__ . '-self' );
- $replaceWith = call_user_func( $matchingCallback, $cbArgs );
- wfProfileIn( __METHOD__ . '-self' );
- $text = substr($text, 0, $pieceStart) . $replaceWith . substr($text, $pieceEnd);
- $i = $pieceStart + strlen($replaceWith) - 1;
- }
- else {
- # null value for callback means that parentheses should be parsed, but not replaced
- $i += $matchingCount - 1;
- }
+ $pieceStart = $openingBraceStack[$lastOpeningBrace]['startAt'] - $matchingCount;
+ $pieceEnd = $i + $matchingCount;
+
+ if( is_callable( $matchingCallback ) ) {
+ $cbArgs = array (
+ 'text' => substr($text, $pieceStart, $pieceEnd - $pieceStart),
+ 'title' => trim($openingBraceStack[$lastOpeningBrace]['title']),
+ 'parts' => $openingBraceStack[$lastOpeningBrace]['parts'],
+ 'lineStart' => (($pieceStart > 0) && ($text[$pieceStart-1] == "\n")),
+ );
+ # finally we can call a user callback and replace piece of text
+ $replaceWith = call_user_func( $matchingCallback, $cbArgs );
+ $text = substr($text, 0, $pieceStart) . $replaceWith . substr($text, $pieceEnd);
+ $i = $pieceStart + strlen($replaceWith);
+ } else {
+ # null value for callback means that parentheses should be parsed, but not replaced
+ $i += $matchingCount;
+ }
- # reset last openning parentheses, but keep it in case there are unused characters
- $piece = array('brace' => $openingBraceStack[$lastOpeningBrace]['brace'],
- 'braceEnd' => $openingBraceStack[$lastOpeningBrace]['braceEnd'],
- 'count' => $openingBraceStack[$lastOpeningBrace]['count'],
- 'title' => '',
- 'parts' => null,
- 'startAt' => $openingBraceStack[$lastOpeningBrace]['startAt']);
- $openingBraceStack[$lastOpeningBrace--] = null;
-
- if ($matchingCount < $piece['count']) {
- $piece['count'] -= $matchingCount;
- $piece['startAt'] -= $matchingCount;
- $piece['partStart'] = $piece['startAt'];
- # do we still qualify for any callback with remaining count?
- foreach ($callbacks[$piece['brace']]['cb'] as $cnt => $fn) {
- if ($piece['count'] >= $cnt) {
- $lastOpeningBrace ++;
- $openingBraceStack[$lastOpeningBrace] = $piece;
- break;
- }
+ # reset last opening parentheses, but keep it in case there are unused characters
+ $piece = array('brace' => $openingBraceStack[$lastOpeningBrace]['brace'],
+ 'braceEnd' => $openingBraceStack[$lastOpeningBrace]['braceEnd'],
+ 'count' => $openingBraceStack[$lastOpeningBrace]['count'],
+ 'title' => '',
+ 'parts' => null,
+ 'startAt' => $openingBraceStack[$lastOpeningBrace]['startAt']);
+ $openingBraceStack[$lastOpeningBrace--] = null;
+
+ if ($matchingCount < $piece['count']) {
+ $piece['count'] -= $matchingCount;
+ $piece['startAt'] -= $matchingCount;
+ $piece['partStart'] = $piece['startAt'];
+ # do we still qualify for any callback with remaining count?
+ $currentCbList = $callbacks[$piece['brace']]['cb'];
+ while ( $piece['count'] ) {
+ if ( array_key_exists( $piece['count'], $currentCbList ) ) {
+ $lastOpeningBrace++;
+ $openingBraceStack[$lastOpeningBrace] = $piece;
+ break;
}
+ --$piece['count'];
}
- continue;
}
-
+ } elseif ( $found == 'pipe' ) {
# lets set a title if it is a first separator, or next part otherwise
- if ($text[$i] == '|') {
- if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
- $openingBraceStack[$lastOpeningBrace]['title'] = substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
- $openingBraceStack[$lastOpeningBrace]['parts'] = array();
- }
- else
- $openingBraceStack[$lastOpeningBrace]['parts'][] = substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
-
- $openingBraceStack[$lastOpeningBrace]['partStart'] = $i + 1;
+ if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
+ $openingBraceStack[$lastOpeningBrace]['title'] =
+ substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+ $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
+ $openingBraceStack[$lastOpeningBrace]['parts'] = array();
+ } else {
+ $openingBraceStack[$lastOpeningBrace]['parts'][] =
+ substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+ $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
}
+ $openingBraceStack[$lastOpeningBrace]['partStart'] = ++$i;
}
}
- wfProfileOut( __METHOD__ . '-self' );
+ wfProfileOut( __METHOD__ );
return $text;
}
@@ -2547,11 +2713,11 @@ class Parser
*/
function replaceVariables( $text, $args = array(), $argsOnly = false ) {
# Prevent too big inclusions
- if( strlen( $text ) > MAX_INCLUDE_SIZE ) {
+ if( strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) {
return $text;
}
- $fname = 'Parser::replaceVariables';
+ $fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/;
wfProfileIn( $fname );
# This function is called recursively. To keep track of arguments we need a stack:
@@ -2561,32 +2727,45 @@ class Parser
if ( !$argsOnly ) {
$braceCallbacks[2] = array( &$this, 'braceSubstitution' );
}
- if ( $this->mOutputType == OT_HTML || $this->mOutputType == OT_WIKI ) {
+ if ( $this->mOutputType != OT_MSG ) {
$braceCallbacks[3] = array( &$this, 'argSubstitution' );
}
- $callbacks = array();
- $callbacks['{'] = array('end' => '}', 'cb' => $braceCallbacks);
- $callbacks['['] = array('end' => ']', 'cb' => array(2=>null));
- $text = $this->replace_callback ($text, $callbacks);
-
- array_pop( $this->mArgStack );
+ if ( $braceCallbacks ) {
+ $callbacks = array(
+ '{' => array(
+ 'end' => '}',
+ 'cb' => $braceCallbacks,
+ 'min' => $argsOnly ? 3 : 2,
+ 'max' => isset( $braceCallbacks[3] ) ? 3 : 2,
+ ),
+ '[' => array(
+ 'end' => ']',
+ 'cb' => array(2=>null),
+ 'min' => 2,
+ 'max' => 2,
+ )
+ );
+ $text = $this->replace_callback ($text, $callbacks);
+ array_pop( $this->mArgStack );
+ }
wfProfileOut( $fname );
return $text;
}
-
+
/**
* Replace magic variables
* @private
*/
function variableSubstitution( $matches ) {
+ global $wgContLang;
$fname = 'Parser::variableSubstitution';
- $varname = $matches[1];
+ $varname = $wgContLang->lc($matches[1]);
wfProfileIn( $fname );
$skip = false;
if ( $this->mOutputType == OT_WIKI ) {
# Do only magic variables prefixed by SUBST
- $mwSubst =& MagicWord::get( MAG_SUBST );
+ $mwSubst =& MagicWord::get( 'subst' );
if (!$mwSubst->matchStartAndRemove( $varname ))
$skip = true;
# Note that if we don't substitute the variable below,
@@ -2595,8 +2774,14 @@ class Parser
}
if ( !$skip && array_key_exists( $varname, $this->mVariables ) ) {
$id = $this->mVariables[$varname];
- $text = $this->getVariableValue( $id );
- $this->mOutput->mContainsOldMagic = true;
+ # Now check if we did really match, case sensitive or not
+ $mw =& MagicWord::get( $id );
+ if ($mw->match($matches[1])) {
+ $text = $this->getVariableValue( $id );
+ $this->mOutput->mContainsOldMagic = true;
+ } else {
+ $text = $matches[0];
+ }
} else {
$text = $matches[0];
}
@@ -2642,8 +2827,9 @@ class Parser
*/
function braceSubstitution( $piece ) {
global $wgContLang, $wgLang, $wgAllowDisplayTitle, $action;
- $fname = 'Parser::braceSubstitution';
+ $fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/;
wfProfileIn( $fname );
+ wfProfileIn( __METHOD__.'-setup' );
# Flags
$found = false; # $text has been filled
@@ -2659,10 +2845,11 @@ class Parser
$linestart = '';
+
# $part1 is the bit before the first |, and must contain only title characters
# $args is a list of arguments, starting from index 0, not including $part1
- $part1 = $piece['title'];
+ $titleText = $part1 = $piece['title'];
# If the third subpattern matched anything, it will start with |
if (null == $piece['parts']) {
@@ -2677,11 +2864,13 @@ class Parser
$args = (null == $piece['parts']) ? array() : $piece['parts'];
$argc = count( $args );
+ wfProfileOut( __METHOD__.'-setup' );
# SUBST
+ wfProfileIn( __METHOD__.'-modifiers' );
if ( !$found ) {
- $mwSubst =& MagicWord::get( MAG_SUBST );
- if ( $mwSubst->matchStartAndRemove( $part1 ) xor ($this->mOutputType == OT_WIKI) ) {
+ $mwSubst =& MagicWord::get( 'subst' );
+ if ( $mwSubst->matchStartAndRemove( $part1 ) xor $this->ot['wiki'] ) {
# One of two possibilities is true:
# 1) Found SUBST but not in the PST phase
# 2) Didn't find SUBST and in the PST phase
@@ -2693,33 +2882,25 @@ class Parser
}
}
- # MSG, MSGNW, INT and RAW
+ # MSG, MSGNW and RAW
if ( !$found ) {
# Check for MSGNW:
- $mwMsgnw =& MagicWord::get( MAG_MSGNW );
+ $mwMsgnw =& MagicWord::get( 'msgnw' );
if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
$nowiki = true;
} else {
# Remove obsolete MSG:
- $mwMsg =& MagicWord::get( MAG_MSG );
+ $mwMsg =& MagicWord::get( 'msg' );
$mwMsg->matchStartAndRemove( $part1 );
}
# Check for RAW:
- $mwRaw =& MagicWord::get( MAG_RAW );
+ $mwRaw =& MagicWord::get( 'raw' );
if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
$forceRawInterwiki = true;
}
-
- # Check if it is an internal message
- $mwInt =& MagicWord::get( MAG_INT );
- if ( $mwInt->matchStartAndRemove( $part1 ) ) {
- if ( $this->incrementIncludeCount( 'int:'.$part1 ) ) {
- $text = $linestart . wfMsgReal( $part1, $args, true );
- $found = true;
- }
- }
}
+ wfProfileOut( __METHOD__.'-modifiers' );
# Parser functions
if ( !$found ) {
@@ -2764,7 +2945,7 @@ class Parser
}
}
}
- wfProfileOut( __METHOD__ . '-pfunc' );
+ wfProfileOut( __METHOD__ . '-pfunc' );
}
# Template table test
@@ -2780,9 +2961,8 @@ class Parser
$noargs = true;
$found = true;
$text = $linestart .
- '{{' . $part1 . '}}' .
- '<!-- WARNING: template loop detected -->';
- wfDebug( "$fname: template loop broken at '$part1'\n" );
+ "[[$part1]]<!-- WARNING: template loop detected -->";
+ wfDebug( __METHOD__.": template loop broken at '$part1'\n" );
} else {
# set $text to cached message.
$text = $linestart . $this->mTemplates[$piece['title']];
@@ -2806,6 +2986,7 @@ class Parser
if ( !is_null( $title ) ) {
+ $titleText = $title->getPrefixedText();
$checkVariantLink = sizeof($wgContLang->getVariants())>1;
# Check for language variants if the template is not found
if($checkVariantLink && $title->getArticleID() == 0){
@@ -2813,36 +2994,32 @@ class Parser
}
if ( !$title->isExternal() ) {
- # Check for excessive inclusion
- $dbk = $title->getPrefixedDBkey();
- if ( $this->incrementIncludeCount( $dbk ) ) {
- if ( $title->getNamespace() == NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->mOutputType != OT_WIKI ) {
- $text = SpecialPage::capturePath( $title );
- if ( is_string( $text ) ) {
- $found = true;
- $noparse = true;
- $noargs = true;
- $isHTML = true;
- $this->disableCache();
- }
- } else {
- $articleContent = $this->fetchTemplate( $title );
- if ( $articleContent !== false ) {
- $found = true;
- $text = $articleContent;
- $replaceHeadings = true;
- }
+ if ( $title->getNamespace() == NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html'] ) {
+ $text = SpecialPage::capturePath( $title );
+ if ( is_string( $text ) ) {
+ $found = true;
+ $noparse = true;
+ $noargs = true;
+ $isHTML = true;
+ $this->disableCache();
+ }
+ } else {
+ $articleContent = $this->fetchTemplate( $title );
+ if ( $articleContent !== false ) {
+ $found = true;
+ $text = $articleContent;
+ $replaceHeadings = true;
}
}
# If the title is valid but undisplayable, make a link to it
- if ( $this->mOutputType == OT_HTML && !$found ) {
- $text = '[['.$title->getPrefixedText().']]';
+ if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
+ $text = "[[:$titleText]]";
$found = true;
}
} elseif ( $title->isTrans() ) {
// Interwiki transclusion
- if ( $this->mOutputType == OT_HTML && !$forceRawInterwiki ) {
+ if ( $this->ot['html'] && !$forceRawInterwiki ) {
$text = $this->interwikiTransclude( $title, 'render' );
$isHTML = true;
$noparse = true;
@@ -2852,7 +3029,7 @@ class Parser
}
$found = true;
}
-
+
# Template cache array insertion
# Use the original $piece['title'] not the mangled $part1, so that
# modifiers such as RAW: produce separate cache entries
@@ -2865,14 +3042,22 @@ class Parser
$text = $linestart . $text;
}
}
- wfProfileOut( __METHOD__ . '-loadtpl' );
+ wfProfileOut( __METHOD__ . '-loadtpl' );
+ }
+
+ if ( $found && !$this->incrementIncludeSize( 'pre-expand', strlen( $text ) ) ) {
+ # Error, oversize inclusion
+ $text = $linestart .
+ "[[$titleText]]<!-- WARNING: template omitted, pre-expand include size too large -->";
+ $noparse = true;
+ $noargs = true;
}
# Recursive parsing, escaping and link table handling
# Only for HTML output
- if ( $nowiki && $found && $this->mOutputType == OT_HTML ) {
+ if ( $nowiki && $found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
$text = wfEscapeWikiText( $text );
- } elseif ( ($this->mOutputType == OT_HTML || $this->mOutputType == OT_WIKI) && $found ) {
+ } elseif ( !$this->ot['msg'] && $found ) {
if ( $noargs ) {
$assocArgs = array();
} else {
@@ -2911,16 +3096,20 @@ class Parser
$text = preg_replace( '/<noinclude>.*?<\/noinclude>/s', '', $text );
$text = strtr( $text, array( '<includeonly>' => '' , '</includeonly>' => '' ) );
- if( $this->mOutputType == OT_HTML ) {
+ if( $this->ot['html'] || $this->ot['pre'] ) {
# Strip <nowiki>, <pre>, etc.
$text = $this->strip( $text, $this->mStripState );
- $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'replaceVariables' ), $assocArgs );
+ if ( $this->ot['html'] ) {
+ $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'replaceVariables' ), $assocArgs );
+ } elseif ( $this->ot['pre'] && $this->mOptions->getRemoveComments() ) {
+ $text = Sanitizer::removeHTMLcomments( $text );
+ }
}
$text = $this->replaceVariables( $text, $assocArgs );
# If the template begins with a table or block-level
# element, it should be treated as beginning a new line.
- if (!$piece['lineStart'] && preg_match('/^({\\||:|;|#|\*)/', $text)) {
+ if (!$piece['lineStart'] && preg_match('/^({\\||:|;|#|\*)/', $text)) /*}*/{
$text = "\n" . $text;
}
} elseif ( !$noargs ) {
@@ -2933,6 +3122,14 @@ class Parser
# Prune lower levels off the recursion check path
$this->mTemplatePath = $lastPathLevel;
+ if ( $found && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
+ # Error, oversize inclusion
+ $text = $linestart .
+ "[[$titleText]]<!-- WARNING: template omitted, post-expand include size too large -->";
+ $noparse = true;
+ $noargs = true;
+ }
+
if ( !$found ) {
wfProfileOut( $fname );
return $piece['text'];
@@ -2946,7 +3143,7 @@ class Parser
} else {
# replace ==section headers==
# XXX this needs to go away once we have a better parser.
- if ( $this->mOutputType != OT_WIKI && $replaceHeadings ) {
+ if ( !$this->ot['wiki'] && !$this->ot['pre'] && $replaceHeadings ) {
if( !is_null( $title ) )
$encodedname = base64_encode($title->getPrefixedDBkey());
else
@@ -3070,25 +3267,31 @@ class Parser
if ( array_key_exists( $arg, $inputArgs ) ) {
$text = $inputArgs[$arg];
- } else if ($this->mOutputType == OT_HTML && null != $matches['parts'] && count($matches['parts']) > 0) {
+ } else if (($this->mOutputType == OT_HTML || $this->mOutputType == OT_PREPROCESS ) &&
+ null != $matches['parts'] && count($matches['parts']) > 0) {
$text = $matches['parts'][0];
}
+ if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
+ $text = $matches['text'] .
+ '<!-- WARNING: argument omitted, expansion size too large -->';
+ }
return $text;
}
/**
- * Returns true if the function is allowed to include this entity
- * @private
+ * Increment an include size counter
+ *
+ * @param string $type The type of expansion
+ * @param integer $size The size of the text
+ * @return boolean False if this inclusion would take it over the maximum, true otherwise
*/
- function incrementIncludeCount( $dbk ) {
- if ( !array_key_exists( $dbk, $this->mIncludeCount ) ) {
- $this->mIncludeCount[$dbk] = 0;
- }
- if ( ++$this->mIncludeCount[$dbk] <= MAX_INCLUDE_REPEAT ) {
- return true;
- } else {
+ function incrementIncludeSize( $type, $size ) {
+ if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
return false;
+ } else {
+ $this->mIncludeSizes[$type] += $size;
+ return true;
}
}
@@ -3098,7 +3301,7 @@ class Parser
function stripNoGallery( &$text ) {
# if the string __NOGALLERY__ (not case-sensitive) occurs in the HTML,
# do not add TOC
- $mw = MagicWord::get( MAG_NOGALLERY );
+ $mw = MagicWord::get( 'nogallery' );
$this->mOutput->mNoGallery = $mw->matchAndRemove( $text ) ;
}
@@ -3108,19 +3311,19 @@ class Parser
function stripToc( $text ) {
# if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
# do not add TOC
- $mw = MagicWord::get( MAG_NOTOC );
+ $mw = MagicWord::get( 'notoc' );
if( $mw->matchAndRemove( $text ) ) {
$this->mShowToc = false;
}
-
- $mw = MagicWord::get( MAG_TOC );
+
+ $mw = MagicWord::get( 'toc' );
if( $mw->match( $text ) ) {
$this->mShowToc = true;
$this->mForceTocPosition = true;
-
+
// Set a placeholder. At the end we'll fill it in with the TOC.
$text = $mw->replace( '<!--MWTOC-->', $text, 1 );
-
+
// Only keep the first one.
$text = $mw->replace( '', $text );
}
@@ -3152,7 +3355,7 @@ class Parser
}
# Inhibit editsection links if requested in the page
- $esw =& MagicWord::get( MAG_NOEDITSECTION );
+ $esw =& MagicWord::get( 'noeditsection' );
if( $esw->matchAndRemove( $text ) ) {
$showEditLink = 0;
}
@@ -3168,13 +3371,13 @@ class Parser
# Allow user to stipulate that a page should have a "new section"
# link added via __NEWSECTIONLINK__
- $mw =& MagicWord::get( MAG_NEWSECTIONLINK );
+ $mw =& MagicWord::get( 'newsectionlink' );
if( $mw->matchAndRemove( $text ) )
$this->mOutput->setNewSection( true );
# if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
# override above conditions and always show TOC above first header
- $mw =& MagicWord::get( MAG_FORCETOC );
+ $mw =& MagicWord::get( 'forcetoc' );
if ($mw->matchAndRemove( $text ) ) {
$this->mShowToc = true;
$enoughToc = true;
@@ -3381,137 +3584,6 @@ class Parser
}
/**
- * Return an HTML link for the "ISBN 123456" text
- * @private
- */
- function magicISBN( $text ) {
- $fname = 'Parser::magicISBN';
- wfProfileIn( $fname );
-
- $a = split( 'ISBN ', ' '.$text );
- if ( count ( $a ) < 2 ) {
- wfProfileOut( $fname );
- return $text;
- }
- $text = substr( array_shift( $a ), 1);
- $valid = '0123456789-Xx';
-
- foreach ( $a as $x ) {
- # hack: don't replace inside thumbnail title/alt
- # attributes
- if(preg_match('/<[^>]+(alt|title)="[^">]*$/', $text)) {
- $text .= "ISBN $x";
- continue;
- }
-
- $isbn = $blank = '' ;
- while ( $x !== '' && ' ' == $x{0} ) {
- $blank .= ' ';
- $x = substr( $x, 1 );
- }
- if ( $x == '' ) { # blank isbn
- $text .= "ISBN $blank";
- continue;
- }
- while ( strstr( $valid, $x{0} ) != false ) {
- $isbn .= $x{0};
- $x = substr( $x, 1 );
- }
- $num = str_replace( '-', '', $isbn );
- $num = str_replace( ' ', '', $num );
- $num = str_replace( 'x', 'X', $num );
-
- if ( '' == $num ) {
- $text .= "ISBN $blank$x";
- } else {
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Booksources' );
- $text .= '<a href="' .
- $titleObj->escapeLocalUrl( 'isbn='.$num ) .
- "\" class=\"internal\">ISBN $isbn</a>";
- $text .= $x;
- }
- }
- wfProfileOut( $fname );
- return $text;
- }
-
- /**
- * Return an HTML link for the "RFC 1234" text
- *
- * @private
- * @param string $text Text to be processed
- * @param string $keyword Magic keyword to use (default RFC)
- * @param string $urlmsg Interface message to use (default rfcurl)
- * @return string
- */
- function magicRFC( $text, $keyword='RFC ', $urlmsg='rfcurl' ) {
-
- $valid = '0123456789';
- $internal = false;
-
- $a = split( $keyword, ' '.$text );
- if ( count ( $a ) < 2 ) {
- return $text;
- }
- $text = substr( array_shift( $a ), 1);
-
- /* Check if keyword is preceed by [[.
- * This test is made here cause of the array_shift above
- * that prevent the test to be done in the foreach.
- */
- if ( substr( $text, -2 ) == '[[' ) {
- $internal = true;
- }
-
- foreach ( $a as $x ) {
- /* token might be empty if we have RFC RFC 1234 */
- if ( $x=='' ) {
- $text.=$keyword;
- continue;
- }
-
- # hack: don't replace inside thumbnail title/alt
- # attributes
- if(preg_match('/<[^>]+(alt|title)="[^">]*$/', $text)) {
- $text .= $keyword . $x;
- continue;
- }
-
- $id = $blank = '' ;
-
- /** remove and save whitespaces in $blank */
- while ( $x{0} == ' ' ) {
- $blank .= ' ';
- $x = substr( $x, 1 );
- }
-
- /** remove and save the rfc number in $id */
- while ( strstr( $valid, $x{0} ) != false ) {
- $id .= $x{0};
- $x = substr( $x, 1 );
- }
-
- if ( $id == '' ) {
- /* call back stripped spaces*/
- $text .= $keyword.$blank.$x;
- } elseif( $internal ) {
- /* normal link */
- $text .= $keyword.$id.$x;
- } else {
- /* build the external link*/
- $url = wfMsg( $urlmsg, $id);
- $sk =& $this->mOptions->getSkin();
- $la = $sk->getExternalLinkAttributes( $url, $keyword.$id );
- $text .= "<a href=\"{$url}\"{$la}>{$keyword}{$id}</a>{$x}";
- }
-
- /* Check if the next RFC keyword is preceed by [[ */
- $internal = ( substr($x,-2) == '[[' );
- }
- return $text;
- }
-
- /**
* Transform wiki markup when saving a page by doing \r\n -> \n
* conversion, substitting signatures, {{subst:}} templates, etc.
*
@@ -3526,7 +3598,7 @@ class Parser
function preSaveTransform( $text, &$title, &$user, $options, $clearState = true ) {
$this->mOptions = $options;
$this->mTitle =& $title;
- $this->mOutputType = OT_WIKI;
+ $this->setOutputType( OT_WIKI );
if ( $clearState ) {
$this->clearState();
@@ -3569,10 +3641,10 @@ class Parser
# Variable replacement
# Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
$text = $this->replaceVariables( $text );
-
+
# Strip out <nowiki> etc. added via replaceVariables
$text = $this->strip( $text, $stripState, false, array( 'gallery' ) );
-
+
# Signatures
$sigText = $this->getUserSig( $user );
$text = strtr( $text, array(
@@ -3585,35 +3657,31 @@ class Parser
#
global $wgLegalTitleChars;
$tc = "[$wgLegalTitleChars]";
- $np = str_replace( array( '(', ')' ), array( '', '' ), $tc ); # No parens
+ $nc = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
- $namespacechar = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
- $conpat = "/^({$np}+) \\(({$tc}+)\\)$/";
+ $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\))\\|]]/"; # [[ns:page (context)|]]
+ $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\)|)(, $tc+|)\\|]]/"; # [[ns:page (context), context|]]
+ $p2 = "/\[\[\\|($tc+)]]/"; # [[|page]]
- $p1 = "/\[\[({$np}+) \\(({$np}+)\\)\\|]]/"; # [[page (context)|]]
- $p2 = "/\[\[\\|({$tc}+)]]/"; # [[|page]]
- $p3 = "/\[\[(:*$namespacechar+):({$np}+)\\|]]/"; # [[namespace:page|]] and [[:namespace:page|]]
- $p4 = "/\[\[(:*$namespacechar+):({$np}+) \\(({$np}+)\\)\\|]]/"; # [[ns:page (cont)|]] and [[:ns:page (cont)|]]
- $context = '';
- $t = $this->mTitle->getText();
- if ( preg_match( $conpat, $t, $m ) ) {
- $context = $m[2];
- }
- $text = preg_replace( $p4, '[[\\1:\\2 (\\3)|\\2]]', $text );
- $text = preg_replace( $p1, '[[\\1 (\\2)|\\1]]', $text );
- $text = preg_replace( $p3, '[[\\1:\\2|\\2]]', $text );
+ # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
+ $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
+ $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
- if ( '' == $context ) {
- $text = preg_replace( $p2, '[[\\1]]', $text );
+ $t = $this->mTitle->getText();
+ if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
+ $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
+ } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && '' != "$m[1]$m[2]" ) {
+ $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
} else {
- $text = preg_replace( $p2, "[[\\1 ({$context})|\\1]]", $text );
+ # if there's no context, don't bother duplicating the title
+ $text = preg_replace( $p2, '[[\\1]]', $text );
}
# Trim trailing whitespace
- # MAG_END (__END__) tag allows for trailing
+ # __END__ tag allows for trailing
# whitespace to be deliberately included
$text = rtrim( $text );
- $mw =& MagicWord::get( MAG_END );
+ $mw =& MagicWord::get( 'end' );
$mw->matchAndRemove( $text );
return $text;
@@ -3631,7 +3699,7 @@ class Parser
$username = $user->getName();
$nickname = $user->getOption( 'nickname' );
$nickname = $nickname === '' ? $username : $nickname;
-
+
if( $user->getBoolOption( 'fancysig' ) !== false ) {
# Sig. might contain markup; validate this
if( $this->validateSig( $nickname ) !== false ) {
@@ -3661,7 +3729,7 @@ class Parser
function validateSig( $text ) {
return( wfIsWellFormedXmlFragment( $text ) ? $text : false );
}
-
+
/**
* Clean up signature text
*
@@ -3675,16 +3743,16 @@ class Parser
function cleanSig( $text, $parsing = false ) {
global $wgTitle;
$this->startExternalParse( $wgTitle, new ParserOptions(), $parsing ? OT_WIKI : OT_MSG );
-
- $substWord = MagicWord::get( MAG_SUBST );
+
+ $substWord = MagicWord::get( 'subst' );
$substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
$substText = '{{' . $substWord->getSynonym( 0 );
$text = preg_replace( $substRegex, $substText, $text );
$text = $this->cleanSigInSig( $text );
$text = $this->replaceVariables( $text );
-
- $this->clearState();
+
+ $this->clearState();
return $text;
}
@@ -3697,7 +3765,7 @@ class Parser
$text = preg_replace( '/~{3,5}/', '', $text );
return $text;
}
-
+
/**
* Set up some variables which are usually set up in parse()
* so that an external function can call some class members with confidence
@@ -3706,7 +3774,7 @@ class Parser
function startExternalParse( &$title, $options, $outputType, $clearState = true ) {
$this->mTitle =& $title;
$this->mOptions = $options;
- $this->mOutputType = $outputType;
+ $this->setOutputType( $outputType );
if ( $clearState ) {
$this->clearState();
}
@@ -3734,9 +3802,13 @@ class Parser
wfProfileIn($fname);
- $this->mTitle = $wgTitle;
+ if ( $wgTitle ) {
+ $this->mTitle = $wgTitle;
+ } else {
+ $this->mTitle = Title::newFromText('msg');
+ }
$this->mOptions = $options;
- $this->mOutputType = OT_MSG;
+ $this->setOutputType( OT_MSG );
$this->clearState();
$text = $this->replaceVariables( $text );
@@ -3785,7 +3857,7 @@ class Parser
*
* @public
*
- * @param mixed $id The magic word ID, or (deprecated) the function name. Function names are case-insensitive.
+ * @param string $id The magic word ID
* @param mixed $callback The callback function (and object) to use
* @param integer $flags a combination of the following flags:
* SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}}
@@ -3793,21 +3865,16 @@ class Parser
* @return The old callback function for this name, if any
*/
function setFunctionHook( $id, $callback, $flags = 0 ) {
- if( is_string( $id ) ) {
- $id = strtolower( $id );
- }
$oldVal = @$this->mFunctionHooks[$id];
$this->mFunctionHooks[$id] = $callback;
# Add to function cache
- if ( is_int( $id ) ) {
- $mw = MagicWord::get( $id );
- $synonyms = $mw->getSynonyms();
- $sensitive = intval( $mw->isCaseSensitive() );
- } else {
- $synonyms = array( $id );
- $sensitive = 0;
- }
+ $mw = MagicWord::get( $id );
+ if( !$mw )
+ throw new MWException( 'Parser::setFunctionHook() expecting a magic word identifier.' );
+
+ $synonyms = $mw->getSynonyms();
+ $sensitive = intval( $mw->isCaseSensitive() );
foreach ( $synonyms as $syn ) {
# Case
@@ -3828,6 +3895,15 @@ class Parser
}
/**
+ * Get all registered function hook identifiers
+ *
+ * @return array
+ */
+ function getFunctionHooks() {
+ return array_keys( $this->mFunctionHooks );
+ }
+
+ /**
* Replace <!--LINK--> link placeholders with actual links, in the buffer
* Placeholders created in Skin::makeLinkObj()
* Returns an array of links found, indexed by PDBK:
@@ -3839,6 +3915,7 @@ class Parser
function replaceLinkHolders( &$text, $options = 0 ) {
global $wgUser;
global $wgOutputReplace;
+ global $wgContLang, $wgLanguageCode;
$fname = 'Parser::replaceLinkHolders';
wfProfileIn( $fname );
@@ -3929,6 +4006,91 @@ class Parser
}
wfProfileOut( $fname.'-check' );
+ # Do a second query for different language variants of links (if needed)
+ if($wgContLang->hasVariants()){
+ $linkBatch = new LinkBatch();
+ $variantMap = array(); // maps $pdbkey_Variant => $pdbkey_original
+
+ // Add variants of links to link batch
+ foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) {
+ $title = $this->mLinkHolders['titles'][$key];
+ if ( is_null( $title ) )
+ continue;
+
+ $pdbk = $title->getPrefixedDBkey();
+
+ // generate all variants of the link title text
+ $allTextVariants = $wgContLang->convertLinkToAllVariants($title->getText());
+
+ // if link was not found (in first query), add all variants to query
+ if ( !isset($colours[$pdbk]) ){
+ foreach($allTextVariants as $textVariant){
+ $variantTitle = Title::makeTitle( $ns, $textVariant );
+ if(is_null($variantTitle)) continue;
+ $linkBatch->addObj( $variantTitle );
+ $variantMap[$variantTitle->getPrefixedDBkey()][] = $key;
+ }
+ }
+ }
+
+
+ if(!$linkBatch->isEmpty()){
+ // construct query
+ $titleClause = $linkBatch->constructSet('page', $dbr);
+
+ $variantQuery = "SELECT page_id, page_namespace, page_title";
+ if ( $threshold > 0 ) {
+ $variantQuery .= ', page_len, page_is_redirect';
+ }
+
+ $variantQuery .= " FROM $page WHERE $titleClause";
+ if ( $options & RLH_FOR_UPDATE ) {
+ $variantQuery .= ' FOR UPDATE';
+ }
+
+ $varRes = $dbr->query( $variantQuery, $fname );
+
+ // for each found variants, figure out link holders and replace
+ while ( $s = $dbr->fetchObject($varRes) ) {
+
+ $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title );
+ $varPdbk = $variantTitle->getPrefixedDBkey();
+ $linkCache->addGoodLinkObj( $s->page_id, $variantTitle );
+ $this->mOutput->addLink( $variantTitle, $s->page_id );
+
+ $holderKeys = $variantMap[$varPdbk];
+
+ // loop over link holders
+ foreach($holderKeys as $key){
+ $title = $this->mLinkHolders['titles'][$key];
+ if ( is_null( $title ) ) continue;
+
+ $pdbk = $title->getPrefixedDBkey();
+
+ if(!isset($colours[$pdbk])){
+ // found link in some of the variants, replace the link holder data
+ $this->mLinkHolders['titles'][$key] = $variantTitle;
+ $this->mLinkHolders['dbkeys'][$key] = $variantTitle->getDBkey();
+
+ // set pdbk and colour
+ $pdbks[$key] = $varPdbk;
+ if ( $threshold > 0 ) {
+ $size = $s->page_len;
+ if ( $s->page_is_redirect || $s->page_namespace != 0 || $size >= $threshold ) {
+ $colours[$varPdbk] = 1;
+ } else {
+ $colours[$varPdbk] = 2;
+ }
+ }
+ else {
+ $colours[$varPdbk] = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+
# Construct search and replace arrays
wfProfileIn( $fname.'-construct' );
$wgOutputReplace = array();
@@ -4033,13 +4195,13 @@ class Parser
function renderPreTag( $text, $attribs, $parser ) {
// Backwards-compatibility hack
$content = preg_replace( '!<nowiki>(.*?)</nowiki>!is', '\\1', $text );
-
+
$attribs = Sanitizer::validateTagAttributes( $attribs, 'pre' );
return wfOpenElement( 'pre', $attribs ) .
wfEscapeHTMLTagsOnly( $content ) .
'</pre>';
}
-
+
/**
* Renders an image gallery from a text with one line per image.
* text labels may be given by using |-style alternative text. E.g.
@@ -4058,7 +4220,7 @@ class Parser
if( isset( $params['caption'] ) )
$ig->setCaption( $params['caption'] );
-
+
$lines = explode( "\n", $text );
foreach ( $lines as $line ) {
# match lines like these:
@@ -4068,7 +4230,8 @@ class Parser
if ( count( $matches ) == 0 ) {
continue;
}
- $nt =& Title::newFromText( $matches[1] );
+ $tp = Title::newFromText( $matches[1] );
+ $nt =& $tp;
if( is_null( $nt ) ) {
# Bogus title. Ignore these so we don't bomb out later.
continue;
@@ -4101,7 +4264,7 @@ class Parser
* Parse image options text and use it to make an image
*/
function makeImage( &$nt, $options ) {
- global $wgUseImageResize;
+ global $wgUseImageResize, $wgDjvuRenderer;
$align = '';
@@ -4117,17 +4280,19 @@ class Parser
$part = explode( '|', $options);
- $mwThumb =& MagicWord::get( MAG_IMG_THUMBNAIL );
- $mwManualThumb =& MagicWord::get( MAG_IMG_MANUALTHUMB );
- $mwLeft =& MagicWord::get( MAG_IMG_LEFT );
- $mwRight =& MagicWord::get( MAG_IMG_RIGHT );
- $mwNone =& MagicWord::get( MAG_IMG_NONE );
- $mwWidth =& MagicWord::get( MAG_IMG_WIDTH );
- $mwCenter =& MagicWord::get( MAG_IMG_CENTER );
- $mwFramed =& MagicWord::get( MAG_IMG_FRAMED );
+ $mwThumb =& MagicWord::get( 'img_thumbnail' );
+ $mwManualThumb =& MagicWord::get( 'img_manualthumb' );
+ $mwLeft =& MagicWord::get( 'img_left' );
+ $mwRight =& MagicWord::get( 'img_right' );
+ $mwNone =& MagicWord::get( 'img_none' );
+ $mwWidth =& MagicWord::get( 'img_width' );
+ $mwCenter =& MagicWord::get( 'img_center' );
+ $mwFramed =& MagicWord::get( 'img_framed' );
+ $mwPage =& MagicWord::get( 'img_page' );
$caption = '';
$width = $height = $framed = $thumb = false;
+ $page = null;
$manual_thumb = '' ;
foreach( $part as $key => $val ) {
@@ -4149,8 +4314,12 @@ class Parser
} elseif ( ! is_null( $mwNone->matchVariableStartToEnd($val) ) ) {
# remember to set an alignment, don't render immediately
$align = 'none';
+ } elseif ( isset( $wgDjvuRenderer ) && $wgDjvuRenderer
+ && ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
+ # Select a page in a multipage document
+ $page = $match;
} elseif ( $wgUseImageResize && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
- wfDebug( "MAG_IMG_WIDTH match: $match\n" );
+ wfDebug( "img_width match: $match\n" );
# $match is the image width in pixels
if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
$width = intval( $m[1] );
@@ -4175,7 +4344,7 @@ class Parser
# Linker does the rest
$sk =& $this->mOptions->getSkin();
- return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $width, $height, $framed, $thumb, $manual_thumb );
+ return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $width, $height, $framed, $thumb, $manual_thumb, $page );
}
/**
@@ -4242,15 +4411,15 @@ class Parser
# strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
# comments to be stripped as well)
$striparray = array();
-
+
$oldOutputType = $this->mOutputType;
$oldOptions = $this->mOptions;
$this->mOptions = new ParserOptions();
- $this->mOutputType = OT_WIKI;
-
+ $this->setOutputType( OT_WIKI );
+
$striptext = $this->strip( $text, $striparray, true );
-
- $this->mOutputType = $oldOutputType;
+
+ $this->setOutputType( $oldOutputType );
$this->mOptions = $oldOptions;
# now that we can be sure that no pseudo-sections are in the source,
@@ -4293,7 +4462,7 @@ class Parser
/mix",
$striptext, -1,
PREG_SPLIT_DELIM_CAPTURE);
-
+
if( $mode == "get" ) {
if( $section == 0 ) {
// "Section 0" returns the content before any other section.
@@ -4360,7 +4529,7 @@ class Parser
$rv = trim( $rv );
return $rv;
}
-
+
/**
* This function returns the text of a section, specified by a number ($section).
* A section is text under a heading like == Heading == or \<h1\>Heading\</h1\>, or
@@ -4375,7 +4544,7 @@ class Parser
function getSection( $text, $section ) {
return $this->extractSections( $text, $section, "get" );
}
-
+
function replaceSection( $oldtext, $section, $text ) {
return $this->extractSections( $oldtext, $section, "replace", $text );
}
@@ -4435,6 +4604,7 @@ class ParserOutput
function &getImages() { return $this->mImages; }
function &getExternalLinks() { return $this->mExternalLinks; }
function getNoGallery() { return $this->mNoGallery; }
+ function getSubtitle() { return $this->mSubtitle; }
function containsOldMagic() { return $this->mContainsOldMagic; }
function setText( $text ) { return wfSetVar( $this->mText, $text ); }
@@ -4442,7 +4612,8 @@ class ParserOutput
function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategories, $cl ); }
function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); }
- function setTitleText( $t ) { return wfSetVar ($this->mTitleText, $t); }
+ function setTitleText( $t ) { return wfSetVar($this->mTitleText, $t); }
+ function setSubtitle( $st ) { return wfSetVar( $this->mSubtitle, $st ); }
function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; }
function addImage( $name ) { $this->mImages[$name] = 1; }
@@ -4456,12 +4627,15 @@ class ParserOutput
return (bool)$this->mNewSection;
}
- function addLink( $title, $id ) {
+ function addLink( $title, $id = null ) {
$ns = $title->getNamespace();
$dbk = $title->getDBkey();
if ( !isset( $this->mLinks[$ns] ) ) {
$this->mLinks[$ns] = array();
}
+ if ( is_null( $id ) ) {
+ $id = $title->getArticleID();
+ }
$this->mLinks[$ns][$dbk] = $id;
}
@@ -4513,6 +4687,8 @@ class ParserOptions
var $mAllowSpecialInclusion; # Allow inclusion of special pages
var $mTidy; # Ask for tidy cleanup
var $mInterfaceMessage; # Which lang to call for PLURAL and GRAMMAR
+ var $mMaxIncludeSize; # Maximum size of template expansions, in bytes
+ var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
var $mUser; # Stored user object, just used to initialise the skin
@@ -4521,12 +4697,13 @@ class ParserOptions
function getInterwikiMagic() { return $this->mInterwikiMagic; }
function getAllowExternalImages() { return $this->mAllowExternalImages; }
function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; }
- function getDateFormat() { return $this->mDateFormat; }
function getEditSection() { return $this->mEditSection; }
function getNumberHeadings() { return $this->mNumberHeadings; }
function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; }
function getTidy() { return $this->mTidy; }
function getInterfaceMessage() { return $this->mInterfaceMessage; }
+ function getMaxIncludeSize() { return $this->mMaxIncludeSize; }
+ function getRemoveComments() { return $this->mRemoveComments; }
function &getSkin() {
if ( !isset( $this->mSkin ) ) {
@@ -4535,6 +4712,13 @@ class ParserOptions
return $this->mSkin;
}
+ function getDateFormat() {
+ if ( !isset( $this->mDateFormat ) ) {
+ $this->mDateFormat = $this->mUser->getDatePreference();
+ }
+ return $this->mDateFormat;
+ }
+
function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); }
function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
@@ -4547,6 +4731,8 @@ class ParserOptions
function setTidy( $x ) { return wfSetVar( $this->mTidy, $x); }
function setSkin( &$x ) { $this->mSkin =& $x; }
function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); }
+ function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); }
+ function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); }
function ParserOptions( $user = null ) {
$this->initialiseFromUser( $user );
@@ -4556,14 +4742,14 @@ class ParserOptions
* Get parser options
* @static
*/
- function newFromUser( &$user ) {
+ static function newFromUser( $user ) {
return new ParserOptions( $user );
}
/** Get user options */
- function initialiseFromUser( &$userInput ) {
+ function initialiseFromUser( $userInput ) {
global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
- global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion;
+ global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion, $wgMaxArticleSize;
$fname = 'ParserOptions::initialiseFromUser';
wfProfileIn( $fname );
if ( !$userInput ) {
@@ -4586,12 +4772,14 @@ class ParserOptions
$this->mAllowExternalImages = $wgAllowExternalImages;
$this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
$this->mSkin = null; # Deferred
- $this->mDateFormat = $user->getOption( 'date' );
+ $this->mDateFormat = null; # Deferred
$this->mEditSection = true;
$this->mNumberHeadings = $user->getOption( 'numberheadings' );
$this->mAllowSpecialInclusion = $wgAllowSpecialInclusion;
$this->mTidy = false;
$this->mInterfaceMessage = false;
+ $this->mMaxIncludeSize = $wgMaxArticleSize * 1024;
+ $this->mRemoveComments = true;
wfProfileOut( $fname );
}
}
@@ -4711,6 +4899,26 @@ function wfLoadSiteStats() {
}
/**
+ * Get revision timestamp from the database considering timecorrection
+ *
+ * @param $id Int: page revision id
+ * @return integer
+ */
+function wfRevisionTimestamp( $id ) {
+ global $wgContLang;
+ $fname = 'wfRevisionTimestamp';
+
+ wfProfileIn( $fname );
+ $dbr =& wfGetDB( DB_SLAVE );
+ $timestamp = $dbr->selectField( 'revision', 'rev_timestamp',
+ array( 'rev_id' => $id ), __METHOD__ );
+ $timestamp = $wgContLang->userAdjust( $timestamp );
+ wfProfileOut( $fname );
+
+ return $timestamp;
+}
+
+/**
* Escape html tags
* Basically replacing " > and < with HTML entities ( &quot;, &gt;, &lt;)
*
diff --git a/includes/ParserCache.php b/includes/ParserCache.php
index 3ec7512f..1f2e2aaf 100644
--- a/includes/ParserCache.php
+++ b/includes/ParserCache.php
@@ -13,7 +13,7 @@ class ParserCache {
/**
* Get an instance of this object
*/
- function &singleton() {
+ public static function &singleton() {
static $instance;
if ( !isset( $instance ) ) {
global $parserMemc;
@@ -33,7 +33,7 @@ class ParserCache {
}
function getKey( &$article, &$user ) {
- global $wgDBname, $action;
+ global $action;
$hash = $user->getPageRenderingHash();
if( !$article->mTitle->userCanEdit() ) {
// section edit links are suppressed even if the user has them on
@@ -43,7 +43,7 @@ class ParserCache {
}
$pageid = intval( $article->getID() );
$renderkey = (int)($action == 'render');
- $key = "$wgDBname:pcache:idhash:$pageid-$renderkey!$hash$edit";
+ $key = wfMemcKey( 'pcache', 'idhash', "$pageid-$renderkey!$hash$edit" );
return $key;
}
diff --git a/includes/Profiler.php b/includes/Profiler.php
new file mode 100644
index 00000000..78003e02
--- /dev/null
+++ b/includes/Profiler.php
@@ -0,0 +1,369 @@
+<?php
+/**
+ * This file is only included if profiling is enabled
+ * @package MediaWiki
+ */
+
+$wgProfiling = true;
+
+/**
+ * @param $functioname name of the function we will profile
+ */
+function wfProfileIn($functionname) {
+ global $wgProfiler;
+ $wgProfiler->profileIn($functionname);
+}
+
+/**
+ * @param $functioname name of the function we have profiled
+ */
+function wfProfileOut($functionname = 'missing') {
+ global $wgProfiler;
+ $wgProfiler->profileOut($functionname);
+}
+
+function wfGetProfilingOutput($start, $elapsed) {
+ global $wgProfiler;
+ return $wgProfiler->getOutput($start, $elapsed);
+}
+
+function wfProfileClose() {
+ global $wgProfiler;
+ $wgProfiler->close();
+}
+
+if (!function_exists('memory_get_usage')) {
+ # Old PHP or --enable-memory-limit not compiled in
+ function memory_get_usage() {
+ return 0;
+ }
+}
+
+/**
+ * @todo document
+ * @package MediaWiki
+ */
+class Profiler {
+ var $mStack = array (), $mWorkStack = array (), $mCollated = array ();
+ var $mCalls = array (), $mTotals = array ();
+
+ function Profiler()
+ {
+ // Push an entry for the pre-profile setup time onto the stack
+ global $wgRequestTime;
+ if ( !empty( $wgRequestTime ) ) {
+ $this->mWorkStack[] = array( '-total', 0, $wgRequestTime, 0 );
+ $this->mStack[] = array( '-setup', 1, $wgRequestTime, 0, microtime(true), 0 );
+ } else {
+ $this->profileIn( '-total' );
+ }
+
+ }
+
+ function profileIn($functionname) {
+ global $wgDebugFunctionEntry;
+ if ($wgDebugFunctionEntry && function_exists('wfDebug')) {
+ wfDebug(str_repeat(' ', count($this->mWorkStack)).'Entering '.$functionname."\n");
+ }
+ $this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), $this->getTime(), memory_get_usage());
+ }
+
+ function profileOut($functionname) {
+ $memory = memory_get_usage();
+ $time = $this->getTime();
+
+ global $wgDebugFunctionEntry;
+
+ if ($wgDebugFunctionEntry && function_exists('wfDebug')) {
+ wfDebug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n");
+ }
+
+ $bit = array_pop($this->mWorkStack);
+
+ if (!$bit) {
+ wfDebug("Profiling error, !\$bit: $functionname\n");
+ } else {
+ //if ($wgDebugProfiling) {
+ if ($functionname == 'close') {
+ $message = "Profile section ended by close(): {$bit[0]}";
+ wfDebug( "$message\n" );
+ $this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 );
+ }
+ elseif ($bit[0] != $functionname) {
+ $message = "Profiling error: in({$bit[0]}), out($functionname)";
+ wfDebug( "$message\n" );
+ $this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 );
+ }
+ //}
+ $bit[] = $time;
+ $bit[] = $memory;
+ $this->mStack[] = $bit;
+ }
+ }
+
+ function close() {
+ while (count($this->mWorkStack)) {
+ $this->profileOut('close');
+ }
+ }
+
+ function getOutput() {
+ global $wgDebugFunctionEntry;
+ $wgDebugFunctionEntry = false;
+
+ if (!count($this->mStack) && !count($this->mCollated)) {
+ return "No profiling output\n";
+ }
+ $this->close();
+
+ global $wgProfileCallTree;
+ if ($wgProfileCallTree) {
+ return $this->getCallTree();
+ } else {
+ return $this->getFunctionReport();
+ }
+ }
+
+ function getCallTree($start = 0) {
+ return implode('', array_map(array (& $this, 'getCallTreeLine'), $this->remapCallTree($this->mStack)));
+ }
+
+ function remapCallTree($stack) {
+ if (count($stack) < 2) {
+ return $stack;
+ }
+ $outputs = array ();
+ for ($max = count($stack) - 1; $max > 0;) {
+ /* Find all items under this entry */
+ $level = $stack[$max][1];
+ $working = array ();
+ for ($i = $max -1; $i >= 0; $i --) {
+ if ($stack[$i][1] > $level) {
+ $working[] = $stack[$i];
+ } else {
+ break;
+ }
+ }
+ $working = $this->remapCallTree(array_reverse($working));
+ $output = array ();
+ foreach ($working as $item) {
+ array_push($output, $item);
+ }
+ array_unshift($output, $stack[$max]);
+ $max = $i;
+
+ array_unshift($outputs, $output);
+ }
+ $final = array ();
+ foreach ($outputs as $output) {
+ foreach ($output as $item) {
+ $final[] = $item;
+ }
+ }
+ return $final;
+ }
+
+ function getCallTreeLine($entry) {
+ list ($fname, $level, $start, $x, $end) = $entry;
+ $delta = $end - $start;
+ $space = str_repeat(' ', $level);
+
+ # The ugly double sprintf is to work around a PHP bug,
+ # which has been fixed in recent releases.
+ return sprintf( "%10s %s %s\n",
+ trim( sprintf( "%7.3f", $delta * 1000.0 ) ),
+ $space, $fname );
+ }
+
+ function getTime() {
+ return microtime(true);
+ #return $this->getUserTime();
+ }
+
+ function getUserTime() {
+ $ru = getrusage();
+ return $ru['ru_utime.tv_sec'].' '.$ru['ru_utime.tv_usec'] / 1e6;
+ }
+
+ function getFunctionReport() {
+ $width = 140;
+ $nameWidth = $width - 65;
+ $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n";
+ $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
+ $prof = "\nProfiling data\n";
+ $prof .= sprintf($titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem');
+ $this->mCollated = array ();
+ $this->mCalls = array ();
+ $this->mMemory = array ();
+
+ # Estimate profiling overhead
+ $profileCount = count($this->mStack);
+ wfProfileIn('-overhead-total');
+ for ($i = 0; $i < $profileCount; $i ++) {
+ wfProfileIn('-overhead-internal');
+ wfProfileOut('-overhead-internal');
+ }
+ wfProfileOut('-overhead-total');
+
+ # First, subtract the overhead!
+ foreach ($this->mStack as $entry) {
+ $fname = $entry[0];
+ $thislevel = $entry[1];
+ $start = $entry[2];
+ $end = $entry[4];
+ $elapsed = $end - $start;
+ $memory = $entry[5] - $entry[3];
+
+ if ($fname == '-overhead-total') {
+ $overheadTotal[] = $elapsed;
+ $overheadMemory[] = $memory;
+ }
+ elseif ($fname == '-overhead-internal') {
+ $overheadInternal[] = $elapsed;
+ }
+ }
+ $overheadTotal = array_sum($overheadTotal) / count($overheadInternal);
+ $overheadMemory = array_sum($overheadMemory) / count($overheadInternal);
+ $overheadInternal = array_sum($overheadInternal) / count($overheadInternal);
+
+ # Collate
+ foreach ($this->mStack as $index => $entry) {
+ $fname = $entry[0];
+ $thislevel = $entry[1];
+ $start = $entry[2];
+ $end = $entry[4];
+ $elapsed = $end - $start;
+
+ $memory = $entry[5] - $entry[3];
+ $subcalls = $this->calltreeCount($this->mStack, $index);
+
+ if (!preg_match('/^-overhead/', $fname)) {
+ # Adjust for profiling overhead (except special values with elapsed=0
+ if ( $elapsed ) {
+ $elapsed -= $overheadInternal;
+ $elapsed -= ($subcalls * $overheadTotal);
+ $memory -= ($subcalls * $overheadMemory);
+ }
+ }
+
+ if (!array_key_exists($fname, $this->mCollated)) {
+ $this->mCollated[$fname] = 0;
+ $this->mCalls[$fname] = 0;
+ $this->mMemory[$fname] = 0;
+ $this->mMin[$fname] = 1 << 24;
+ $this->mMax[$fname] = 0;
+ $this->mOverhead[$fname] = 0;
+ }
+
+ $this->mCollated[$fname] += $elapsed;
+ $this->mCalls[$fname]++;
+ $this->mMemory[$fname] += $memory;
+ $this->mMin[$fname] = min($this->mMin[$fname], $elapsed);
+ $this->mMax[$fname] = max($this->mMax[$fname], $elapsed);
+ $this->mOverhead[$fname] += $subcalls;
+ }
+
+ $total = @ $this->mCollated['-total'];
+ $this->mCalls['-overhead-total'] = $profileCount;
+
+ # Output
+ arsort($this->mCollated, SORT_NUMERIC);
+ foreach ($this->mCollated as $fname => $elapsed) {
+ $calls = $this->mCalls[$fname];
+ $percent = $total ? 100. * $elapsed / $total : 0;
+ $memory = $this->mMemory[$fname];
+ $prof .= sprintf($format, substr($fname, 0, $nameWidth), $calls, (float) ($elapsed * 1000), (float) ($elapsed * 1000) / $calls, $percent, $memory, ($this->mMin[$fname] * 1000.0), ($this->mMax[$fname] * 1000.0), $this->mOverhead[$fname]);
+
+ global $wgProfileToDatabase;
+ if ($wgProfileToDatabase) {
+ Profiler :: logToDB($fname, (float) ($elapsed * 1000), $calls);
+ }
+ }
+ $prof .= "\nTotal: $total\n\n";
+
+ return $prof;
+ }
+
+ /**
+ * Counts the number of profiled function calls sitting under
+ * the given point in the call graph. Not the most efficient algo.
+ *
+ * @param $stack Array:
+ * @param $start Integer:
+ * @return Integer
+ * @private
+ */
+ function calltreeCount(& $stack, $start) {
+ $level = $stack[$start][1];
+ $count = 0;
+ for ($i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i --) {
+ $count ++;
+ }
+ return $count;
+ }
+
+ /**
+ * @static
+ */
+ function logToDB($name, $timeSum, $eventCount) {
+ # Warning: $wguname is a live patch, it should be moved to Setup.php
+ global $wguname, $wgProfilePerHost;
+
+ $fname = 'Profiler::logToDB';
+ $dbw = & wfGetDB(DB_MASTER);
+ if (!is_object($dbw))
+ return false;
+ $errorState = $dbw->ignoreErrors( true );
+ $profiling = $dbw->tableName('profiling');
+
+ $name = substr($name, 0, 255);
+ $encname = $dbw->strencode($name);
+
+ if ($wgProfilePerHost) {
+ $pfhost = $wguname['nodename'];
+ } else {
+ $pfhost = '';
+ }
+
+ $sql = "UPDATE $profiling "."SET pf_count=pf_count+{$eventCount}, "."pf_time=pf_time + {$timeSum} ".
+ "WHERE pf_name='{$encname}' AND pf_server='{$pfhost}'";
+ $dbw->query($sql);
+
+ $rc = $dbw->affectedRows();
+ if ($rc == 0) {
+ $dbw->insert('profiling', array ('pf_name' => $name, 'pf_count' => $eventCount,
+ 'pf_time' => $timeSum, 'pf_server' => $pfhost ), $fname, array ('IGNORE'));
+ }
+ // When we upgrade to mysql 4.1, the insert+update
+ // can be merged into just a insert with this construct added:
+ // "ON DUPLICATE KEY UPDATE ".
+ // "pf_count=pf_count + VALUES(pf_count), ".
+ // "pf_time=pf_time + VALUES(pf_time)";
+ $dbw->ignoreErrors( $errorState );
+ }
+
+ /**
+ * Get the function name of the current profiling section
+ */
+ function getCurrentSection() {
+ $elt = end($this->mWorkStack);
+ return $elt[0];
+ }
+
+ static function getCaller( $level ) {
+ $backtrace = debug_backtrace();
+ if ( isset( $backtrace[$level] ) ) {
+ if ( isset( $backtrace[$level]['class'] ) ) {
+ $caller = $backtrace[$level]['class'] . '::' . $backtrace[$level]['function'];
+ } else {
+ $caller = $backtrace[$level]['function'];
+ }
+ } else {
+ $caller = 'unknown';
+ }
+ return $caller;
+ }
+
+}
+
+?>
diff --git a/includes/ProfilerSimple.php b/includes/ProfilerSimple.php
index ed058c65..d5bdaf94 100644
--- a/includes/ProfilerSimple.php
+++ b/includes/ProfilerSimple.php
@@ -8,9 +8,11 @@
* @todo document
* @package MediaWiki
*/
-require_once(dirname(__FILE__).'/Profiling.php');
+require_once(dirname(__FILE__).'/Profiler.php');
class ProfilerSimple extends Profiler {
+ var $mMinimumTime = 0;
+
function ProfilerSimple() {
global $wgRequestTime,$wgRUstart;
if (!empty($wgRequestTime) && !empty($wgRUstart)) {
@@ -33,6 +35,10 @@ class ProfilerSimple extends Profiler {
}
}
+ function setMinimum( $min ) {
+ $this->mMinimumTime = $min;
+ }
+
function profileIn($functionname) {
global $wgDebugFunctionEntry;
if ($wgDebugFunctionEntry) {
@@ -86,9 +92,14 @@ class ProfilerSimple extends Profiler {
}
function getCpuTime($ru=null) {
- if ($ru==null)
- $ru=getrusage();
- return ($ru['ru_utime.tv_sec']+$ru['ru_stime.tv_sec']+($ru['ru_utime.tv_usec']+$ru['ru_stime.tv_usec'])*1e-6);
+ if ( function_exists( 'getrusage' ) ) {
+ if ( $ru == null )
+ $ru = getrusage();
+ return ($ru['ru_utime.tv_sec'] + $ru['ru_stime.tv_sec'] + ($ru['ru_utime.tv_usec'] +
+ $ru['ru_stime.tv_usec']) * 1e-6);
+ } else {
+ return 0;
+ }
}
/* If argument is passed, it assumes that it is dual-format time string, returns proper float time value */
diff --git a/includes/ProfilerSimpleUDP.php b/includes/ProfilerSimpleUDP.php
index c395228b..e0490512 100644
--- a/includes/ProfilerSimpleUDP.php
+++ b/includes/ProfilerSimpleUDP.php
@@ -3,20 +3,25 @@
(the one from wikipedia/udpprofile CVS )
*/
-require_once(dirname(__FILE__).'/Profiling.php');
+require_once(dirname(__FILE__).'/Profiler.php');
require_once(dirname(__FILE__).'/ProfilerSimple.php');
class ProfilerSimpleUDP extends ProfilerSimple {
function getFunctionReport() {
global $wgUDPProfilerHost;
global $wgUDPProfilerPort;
- global $wgDBname;
+ if ( $this->mCollated['-total']['real'] < $this->mMinimumTime ) {
+ # Less than minimum, ignore
+ return;
+ }
+
+
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$plength=0;
$packet="";
foreach ($this->mCollated as $entry=>$pfdata) {
- $pfline=sprintf ("%s %s %d %f %f %f %f %s\n", $wgDBname,"-",$pfdata['count'],
+ $pfline=sprintf ("%s %s %d %f %f %f %f %s\n", wfWikiID(),"-",$pfdata['count'],
$pfdata['cpu'],$pfdata['cpu_sq'],$pfdata['real'],$pfdata['real_sq'],$entry);
$length=strlen($pfline);
/* printf("<!-- $pfline -->"); */
diff --git a/includes/ProfilerStub.php b/includes/ProfilerStub.php
index 3bcdaab2..4cf0aa44 100644
--- a/includes/ProfilerStub.php
+++ b/includes/ProfilerStub.php
@@ -21,6 +21,6 @@ function wfProfileOut( $fn = '' ) {
}
function wfGetProfilingOutput( $s, $e ) {}
function wfProfileClose() {}
-function wfLogProfilingData() {}
+$wgProfiling = false;
?>
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index 2a40a376..fd1bc81e 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -79,7 +79,7 @@ class ProtectionForm {
$wgOut->addWikiText(
wfMsg( $this->disabled ? "protect-viewtext" : "protect-text",
- $this->mTitle->getPrefixedText() ) );
+ wfEscapeWikiText( $this->mTitle->getPrefixedText() ) ) );
$wgOut->addHTML( $this->buildForm() );
@@ -230,7 +230,6 @@ class ProtectionForm {
function showLogExtract( &$out ) {
# Show relevant lines from the deletion log:
$out->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'protect' ) ) . "</h2>\n" );
- require_once( 'SpecialLog.php' );
$logViewer = new LogViewer(
new LogReader(
new FauxRequest(
diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php
index bed79c10..7974c882 100644
--- a/includes/ProxyTools.php
+++ b/includes/ProxyTools.php
@@ -55,7 +55,7 @@ function wfGetIP() {
# Set $ip to the IP address given by that trusted server, unless the address is not sensible (e.g. private)
foreach ( $ipchain as $i => $curIP ) {
if ( array_key_exists( $curIP, $trustedProxies ) ) {
- if ( isset( $ipchain[$i + 1] ) && wfIsIPPublic( $ipchain[$i + 1] ) ) {
+ if ( isset( $ipchain[$i + 1] ) && IP::isPublic( $ipchain[$i + 1] ) ) {
$ip = $ipchain[$i + 1];
}
} else {
@@ -70,74 +70,12 @@ function wfGetIP() {
}
/**
- * Given an IP address in dotted-quad notation, returns an unsigned integer.
- * Like ip2long() except that it actually works and has a consistent error return value.
- */
-function wfIP2Unsigned( $ip ) {
- $n = ip2long( $ip );
- if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version
- $n = false;
- } elseif ( $n < 0 ) {
- $n += pow( 2, 32 );
- }
- return $n;
-}
-
-/**
- * Return a zero-padded hexadecimal representation of an IP address
- */
-function wfIP2Hex( $ip ) {
- $n = wfIP2Unsigned( $ip );
- if ( $n !== false ) {
- $n = sprintf( '%08X', $n );
- }
- return $n;
-}
-
-/**
- * Determine if an IP address really is an IP address, and if it is public,
- * i.e. not RFC 1918 or similar
- */
-function wfIsIPPublic( $ip ) {
- $n = wfIP2Unsigned( $ip );
- if ( !$n ) {
- return false;
- }
-
- // ip2long accepts incomplete addresses, as well as some addresses
- // followed by garbage characters. Check that it's really valid.
- if( $ip != long2ip( $n ) ) {
- return false;
- }
-
- static $privateRanges = false;
- if ( !$privateRanges ) {
- $privateRanges = array(
- array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
- array( '172.16.0.0', '172.31.255.255' ), # "
- array( '192.168.0.0', '192.168.255.255' ), # "
- array( '0.0.0.0', '0.255.255.255' ), # this network
- array( '127.0.0.0', '127.255.255.255' ), # loopback
- );
- }
-
- foreach ( $privateRanges as $r ) {
- $start = wfIP2Unsigned( $r[0] );
- $end = wfIP2Unsigned( $r[1] );
- if ( $n >= $start && $n <= $end ) {
- return false;
- }
- }
- return true;
-}
-
-/**
* Forks processes to scan the originating IP for an open proxy server
* MemCached can be used to skip IPs that have already been scanned
*/
function wfProxyCheck() {
global $wgBlockOpenProxies, $wgProxyPorts, $wgProxyScriptPath;
- global $wgUseMemCached, $wgMemc, $wgDBname, $wgProxyMemcExpiry;
+ global $wgUseMemCached, $wgMemc, $wgProxyMemcExpiry;
global $wgProxyKey;
if ( !$wgBlockOpenProxies ) {
@@ -149,7 +87,7 @@ function wfProxyCheck() {
# Get MemCached key
$skip = false;
if ( $wgUseMemCached ) {
- $mcKey = "$wgDBname:proxy:ip:$ip";
+ $mcKey = wfMemcKey( 'proxy', 'ip', $ip );
$mcValue = $wgMemc->get( $mcKey );
if ( $mcValue ) {
$skip = true;
@@ -182,18 +120,7 @@ function wfProxyCheck() {
* Convert a network specification in CIDR notation to an integer network and a number of bits
*/
function wfParseCIDR( $range ) {
- $parts = explode( '/', $range, 2 );
- if ( count( $parts ) != 2 ) {
- return array( false, false );
- }
- $network = wfIP2Unsigned( $parts[0] );
- if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
- $bits = $parts[1];
- } else {
- $network = false;
- $bits = false;
- }
- return array( $network, $bits );
+ return IP::parseCIDR( $range );
}
/**
diff --git a/includes/QueryPage.php b/includes/QueryPage.php
index 53e17616..7d6dc900 100644
--- a/includes/QueryPage.php
+++ b/includes/QueryPage.php
@@ -448,10 +448,10 @@ class QueryPage {
}
function feedTitle() {
- global $wgLanguageCode, $wgSitename;
+ global $wgContLanguageCode, $wgSitename;
$page = SpecialPage::getPage( $this->getName() );
$desc = $page->getDescription();
- return "$wgSitename - $desc [$wgLanguageCode]";
+ return "$wgSitename - $desc [$wgContLanguageCode]";
}
function feedDesc() {
diff --git a/includes/RawPage.php b/includes/RawPage.php
index 3cdabfd9..a0b76886 100644
--- a/includes/RawPage.php
+++ b/includes/RawPage.php
@@ -22,6 +22,7 @@ class RawPage {
function RawPage( &$article, $request = false ) {
global $wgRequest, $wgInputEncoding, $wgSquidMaxage, $wgJsMimeType;
+ global $wgUser;
$allowedCTypes = array('text/x-wiki', $wgJsMimeType, 'text/css', 'application/x-zope-edit');
$this->mArticle =& $article;
@@ -37,6 +38,7 @@ class RawPage {
$smaxage = $this->mRequest->getIntOrNull( 'smaxage', $wgSquidMaxage );
$maxage = $this->mRequest->getInt( 'maxage', $wgSquidMaxage );
$this->mExpandTemplates = $this->mRequest->getVal( 'templates' ) === 'expand';
+ $this->mUseMessageCache = $this->mRequest->getBool( 'usemsgcache' );
$oldid = $this->mRequest->getInt( 'oldid' );
switch ( $wgRequest->getText( 'direction' ) ) {
@@ -80,6 +82,12 @@ class RawPage {
$this->mCharset = $wgInputEncoding;
$this->mSmaxage = intval( $smaxage );
$this->mMaxage = $maxage;
+
+ // Output may contain user-specific data; vary for open sessions
+ $this->mPrivateCache = ( $this->mSmaxage == 0 ) ||
+ ( isset( $_COOKIE[ini_get( 'session.name' )] ) ||
+ $wgUser->isLoggedIn() );
+
if ( $ctype == '' or ! in_array( $ctype, $allowedCTypes ) ) {
$this->mContentType = 'text/x-wiki';
} else {
@@ -127,13 +135,14 @@ class RawPage {
header( "Content-type: ".$this->mContentType.'; charset='.$this->mCharset );
# allow the client to cache this for 24 hours
- header( 'Cache-Control: s-maxage='.$this->mSmaxage.', max-age='.$this->mMaxage );
+ $mode = $this->mPrivateCache ? 'private' : 'public';
+ header( 'Cache-Control: '.$mode.', s-maxage='.$this->mSmaxage.', max-age='.$this->mMaxage );
echo $this->getRawText();
$wgOut->disable();
}
function getRawText() {
- global $wgUser, $wgOut;
+ global $wgUser, $wgOut, $wgRequest;
if($this->mGen) {
$sk = $wgUser->getSkin();
$sk->initPage($wgOut);
@@ -152,11 +161,11 @@ class RawPage {
$text = '';
if( $this->mTitle ) {
// If it's a MediaWiki message we can just hit the message cache
- if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ if ( $this->mUseMessageCache && $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
$key = $this->mTitle->getDBkey();
$text = wfMsgForContentNoTrans( $key );
# If the message doesn't exist, return a blank
- if( $text == '&lt;' . $key . '&gt;' )
+ if( wfEmptyMsg( $key, $text ) )
$text = '';
$found = true;
} else {
@@ -188,14 +197,8 @@ class RawPage {
return '';
else
if ( $this->mExpandTemplates ) {
- global $wgTitle;
-
- $parser = new Parser();
- $parser->Options( new ParserOptions() ); // We don't want this to be user-specific
- $parser->Title( $wgTitle );
- $parser->OutputType( OT_HTML );
-
- return $parser->replaceVariables( $text );
+ global $wgParser;
+ return $wgParser->preprocess( $text, $this->mTitle, new ParserOptions() );
} else
return $text;
}
diff --git a/includes/RecentChange.php b/includes/RecentChange.php
index f320a47a..ebd4b335 100644
--- a/includes/RecentChange.php
+++ b/includes/RecentChange.php
@@ -108,11 +108,21 @@ class RecentChange
$this->mAttribs['rc_ip'] = '';
}
+ ## If our database is strict about IP addresses, use NULL instead of an empty string
+ if ( $dbw->strictIPs() and $this->mAttribs['rc_ip'] == '' ) {
+ unset( $this->mAttribs['rc_ip'] );
+ }
+
# Fixup database timestamps
$this->mAttribs['rc_timestamp'] = $dbw->timestamp($this->mAttribs['rc_timestamp']);
$this->mAttribs['rc_cur_time'] = $dbw->timestamp($this->mAttribs['rc_cur_time']);
$this->mAttribs['rc_id'] = $dbw->nextSequenceValue( 'rc_rc_id_seq' );
+ ## If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
+ if ( $dbw->cascadingDeletes() and $this->mAttribs['rc_cur_id']==0 ) {
+ unset ( $this->mAttribs['rc_cur_id'] );
+ }
+
# Insert new row
$dbw->insert( 'recentchanges', $this->mAttribs, $fname );
@@ -163,7 +173,7 @@ class RecentChange
}
}
- // E-mail notifications
+ # E-mail notifications
global $wgUseEnotif;
if( $wgUseEnotif ) {
# this would be better as an extension hook
@@ -177,6 +187,8 @@ class RecentChange
$this->mAttribs['rc_last_oldid'] );
}
+ # Notify extensions
+ wfRunHooks( 'RecentChange_save', array( &$this ) );
}
# Marks a certain row as patrolled
@@ -200,8 +212,8 @@ class RecentChange
$oldId, $lastTimestamp, $bot = "default", $ip = '', $oldSize = 0, $newSize = 0,
$newId = 0)
{
- if ( $bot == 'default' ) {
- $bot = $user->isBot();
+ if ( $bot === 'default' ) {
+ $bot = $user->isAllowed( 'bot' );
}
if ( !$ip ) {
@@ -243,9 +255,14 @@ class RecentChange
return( $rc->mAttribs['rc_id'] );
}
- # Makes an entry in the database corresponding to page creation
- # Note: the title object must be loaded with the new id using resetArticleID()
- /*static*/ function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot = "default",
+ /**
+ * Makes an entry in the database corresponding to page creation
+ * Note: the title object must be loaded with the new id using resetArticleID()
+ * @todo Document parameters and return
+ * @public
+ * @static
+ */
+ public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot = "default",
$ip='', $size = 0, $newId = 0 )
{
if ( !$ip ) {
@@ -255,7 +272,7 @@ class RecentChange
}
}
if ( $bot == 'default' ) {
- $bot = $user->isBot();
+ $bot = $user->isAllowed( 'bot' );
}
$rc = new RecentChange;
@@ -314,7 +331,7 @@ class RecentChange
'rc_comment' => $comment,
'rc_this_oldid' => 0,
'rc_last_oldid' => 0,
- 'rc_bot' => $user->isBot() ? 1 : 0,
+ 'rc_bot' => $user->isAllowed( 'bot' ) ? 1 : 0,
'rc_moved_to_ns' => $newTitle->getNamespace(),
'rc_moved_to_title' => $newTitle->getDBkey(),
'rc_ip' => $ip,
@@ -364,7 +381,7 @@ class RecentChange
'rc_comment' => $comment,
'rc_this_oldid' => 0,
'rc_last_oldid' => 0,
- 'rc_bot' => $user->isBot() ? 1 : 0,
+ 'rc_bot' => $user->isAllowed( 'bot' ) ? 1 : 0,
'rc_moved_to_ns' => 0,
'rc_moved_to_title' => '',
'rc_ip' => $ip,
diff --git a/includes/Revision.php b/includes/Revision.php
index 653bacb8..bd68e05a 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -4,9 +4,6 @@
* @todo document
*/
-/** */
-require_once( 'Database.php' );
-
/**
* @package MediaWiki
* @todo document
@@ -22,10 +19,10 @@ class Revision {
* Returns null if no such revision can be found.
*
* @param int $id
- * @static
* @access public
+ * @static
*/
- function newFromId( $id ) {
+ public static function newFromId( $id ) {
return Revision::newFromConds(
array( 'page_id=rev_page',
'rev_id' => intval( $id ) ) );
@@ -42,7 +39,7 @@ class Revision {
* @access public
* @static
*/
- function newFromTitle( &$title, $id = 0 ) {
+ public static function newFromTitle( &$title, $id = 0 ) {
if( $id ) {
$matchId = intval( $id );
} else {
@@ -56,6 +53,21 @@ class Revision {
}
/**
+ * Load a page revision from a given revision ID number.
+ * Returns null if no such revision can be found.
+ *
+ * @param Database $db
+ * @param int $id
+ * @access public
+ * @static
+ */
+ public static function loadFromId( &$db, $id ) {
+ return Revision::loadFromConds( $db,
+ array( 'page_id=rev_page',
+ 'rev_id' => intval( $id ) ) );
+ }
+
+ /**
* Load either the current, or a specified, revision
* that's attached to a given page. If not attached
* to that page, will return null.
@@ -65,8 +77,9 @@ class Revision {
* @param int $id
* @return Revision
* @access public
+ * @static
*/
- function loadFromPageId( &$db, $pageid, $id = 0 ) {
+ public static function loadFromPageId( &$db, $pageid, $id = 0 ) {
$conds=array('page_id=rev_page','rev_page'=>intval( $pageid ), 'page_id'=>intval( $pageid ));
if( $id ) {
$conds['rev_id']=intval($id);
@@ -86,8 +99,9 @@ class Revision {
* @param int $id
* @return Revision
* @access public
+ * @static
*/
- function loadFromTitle( &$db, $title, $id = 0 ) {
+ public static function loadFromTitle( &$db, $title, $id = 0 ) {
if( $id ) {
$matchId = intval( $id );
} else {
@@ -113,7 +127,7 @@ class Revision {
* @access public
* @static
*/
- function loadFromTimestamp( &$db, &$title, $timestamp ) {
+ public static function loadFromTimestamp( &$db, &$title, $timestamp ) {
return Revision::loadFromConds(
$db,
array( 'rev_timestamp' => $db->timestamp( $timestamp ),
@@ -127,10 +141,10 @@ class Revision {
*
* @param array $conditions
* @return Revision
- * @static
* @access private
+ * @static
*/
- function newFromConds( $conditions ) {
+ private static function newFromConds( $conditions ) {
$db =& wfGetDB( DB_SLAVE );
$row = Revision::loadFromConds( $db, $conditions );
if( is_null( $row ) ) {
@@ -147,10 +161,10 @@ class Revision {
* @param Database $db
* @param array $conditions
* @return Revision
- * @static
* @access private
+ * @static
*/
- function loadFromConds( &$db, $conditions ) {
+ private static function loadFromConds( &$db, $conditions ) {
$res = Revision::fetchFromConds( $db, $conditions );
if( $res ) {
$row = $res->fetchObject();
@@ -171,10 +185,10 @@ class Revision {
*
* @param Title $title
* @return ResultWrapper
- * @static
* @access public
+ * @static
*/
- function fetchAllRevisions( &$title ) {
+ public static function fetchAllRevisions( &$title ) {
return Revision::fetchFromConds(
wfGetDB( DB_SLAVE ),
array( 'page_namespace' => $title->getNamespace(),
@@ -189,10 +203,10 @@ class Revision {
*
* @param Title $title
* @return ResultWrapper
- * @static
* @access public
+ * @static
*/
- function fetchRevision( &$title ) {
+ public static function fetchRevision( &$title ) {
return Revision::fetchFromConds(
wfGetDB( DB_SLAVE ),
array( 'rev_id=page_latest',
@@ -209,10 +223,10 @@ class Revision {
* @param Database $db
* @param array $conditions
* @return ResultWrapper
- * @static
* @access private
+ * @static
*/
- function fetchFromConds( &$db, $conditions ) {
+ private static function fetchFromConds( &$db, $conditions ) {
$res = $db->select(
array( 'page', 'revision' ),
array( 'page_namespace',
@@ -259,10 +273,13 @@ class Revision {
$this->mTitle = null;
}
+ // Lazy extraction...
+ $this->mText = null;
if( isset( $row->old_text ) ) {
- $this->mText = $this->getRevisionText( $row );
+ $this->mTextRow = $row;
} else {
- $this->mText = null;
+ // 'text' table row entry will be lazy-loaded
+ $this->mTextRow = null;
}
} elseif( is_array( $row ) ) {
// Build a new revision to be saved...
@@ -519,7 +536,6 @@ class Revision {
wfProfileOut( $fname );
return false;
}
- require_once('ExternalStore.php');
$text=ExternalStore::fetchFromURL($url);
}
@@ -609,7 +625,6 @@ class Revision {
} else {
$store = $wgDefaultExternalStore;
}
- require_once('ExternalStore.php');
// Store and get the URL
$data = ExternalStore::insert( $store, $data );
if ( !$data ) {
@@ -668,14 +683,37 @@ class Revision {
function loadText() {
$fname = 'Revision::loadText';
wfProfileIn( $fname );
-
- $dbr =& wfGetDB( DB_SLAVE );
- $row = $dbr->selectRow( 'text',
- array( 'old_text', 'old_flags' ),
- array( 'old_id' => $this->getTextId() ),
- $fname);
+
+ // Caching may be beneficial for massive use of external storage
+ global $wgRevisionCacheExpiry, $wgMemc;
+ $key = wfMemcKey( 'revisiontext', 'textid', $this->getTextId() );
+ if( $wgRevisionCacheExpiry ) {
+ $text = $wgMemc->get( $key );
+ if( is_string( $text ) ) {
+ wfProfileOut( $fname );
+ return $text;
+ }
+ }
+
+ // If we kept data for lazy extraction, use it now...
+ if ( isset( $this->mTextRow ) ) {
+ $row = $this->mTextRow;
+ $this->mTextRow = null;
+ } else {
+ $row = null;
+ }
+
+ if( !$row ) {
+ // Text data is immutable; check slaves first.
+ $dbr =& wfGetDB( DB_SLAVE );
+ $row = $dbr->selectRow( 'text',
+ array( 'old_text', 'old_flags' ),
+ array( 'old_id' => $this->getTextId() ),
+ $fname);
+ }
if( !$row ) {
+ // Possible slave lag!
$dbw =& wfGetDB( DB_MASTER );
$row = $dbw->selectRow( 'text',
array( 'old_text', 'old_flags' ),
@@ -684,6 +722,11 @@ class Revision {
}
$text = Revision::getRevisionText( $row );
+
+ if( $wgRevisionCacheExpiry ) {
+ $wgMemc->set( $key, $text, $wgRevisionCacheExpiry );
+ }
+
wfProfileOut( $fname );
return $text;
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index f5a24dfa..185679f6 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -327,77 +327,90 @@ class Sanitizer {
* @param array $args for the processing callback
* @return string
*/
- function removeHTMLtags( $text, $processCallback = null, $args = array() ) {
+ static function removeHTMLtags( $text, $processCallback = null, $args = array() ) {
global $wgUseTidy, $wgUserHtml;
- $fname = 'Parser::removeHTMLtags';
- wfProfileIn( $fname );
-
- if( $wgUserHtml ) {
- $htmlpairs = array( # Tags that must be closed
- 'b', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1',
- 'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's',
- 'strike', 'strong', 'tt', 'var', 'div', 'center',
- 'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre',
- 'ruby', 'rt' , 'rb' , 'rp', 'p', 'span', 'u'
- );
- $htmlsingle = array(
- 'br', 'hr', 'li', 'dt', 'dd'
- );
- $htmlsingleonly = array( # Elements that cannot have close tags
- 'br', 'hr'
- );
- $htmlnest = array( # Tags that can be nested--??
- 'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul',
- 'dl', 'font', 'big', 'small', 'sub', 'sup', 'span'
- );
- $tabletags = array( # Can only appear inside table
- 'td', 'th', 'tr',
- );
- $htmllist = array( # Tags used by list
- 'ul','ol',
- );
- $listtags = array( # Tags that can appear in a list
- 'li',
- );
- } else {
- $htmlpairs = array();
- $htmlsingle = array();
- $htmlnest = array();
- $tabletags = array();
- }
+ static $htmlpairs, $htmlsingle, $htmlsingleonly, $htmlnest, $tabletags,
+ $htmllist, $listtags, $htmlsingleallowed, $htmlelements, $staticInitialised;
+
+ wfProfileIn( __METHOD__ );
+
+ if ( !$staticInitialised ) {
+ if( $wgUserHtml ) {
+ $htmlpairs = array( # Tags that must be closed
+ 'b', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1',
+ 'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's',
+ 'strike', 'strong', 'tt', 'var', 'div', 'center',
+ 'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre',
+ 'ruby', 'rt' , 'rb' , 'rp', 'p', 'span', 'u'
+ );
+ $htmlsingle = array(
+ 'br', 'hr', 'li', 'dt', 'dd'
+ );
+ $htmlsingleonly = array( # Elements that cannot have close tags
+ 'br', 'hr'
+ );
+ $htmlnest = array( # Tags that can be nested--??
+ 'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul',
+ 'dl', 'font', 'big', 'small', 'sub', 'sup', 'span'
+ );
+ $tabletags = array( # Can only appear inside table
+ 'td', 'th', 'tr',
+ );
+ $htmllist = array( # Tags used by list
+ 'ul','ol',
+ );
+ $listtags = array( # Tags that can appear in a list
+ 'li',
+ );
+
+ } else {
+ $htmlpairs = array();
+ $htmlsingle = array();
+ $htmlnest = array();
+ $tabletags = array();
+ }
- $htmlsingleallowed = array_merge( $htmlsingle, $tabletags );
- $htmlelements = array_merge( $htmlsingle, $htmlpairs, $htmlnest );
+ $htmlsingleallowed = array_merge( $htmlsingle, $tabletags );
+ $htmlelements = array_merge( $htmlsingle, $htmlpairs, $htmlnest );
+
+ # Convert them all to hashtables for faster lookup
+ $vars = array( 'htmlpairs', 'htmlsingle', 'htmlsingleonly', 'htmlnest', 'tabletags',
+ 'htmllist', 'listtags', 'htmlsingleallowed', 'htmlelements' );
+ foreach ( $vars as $var ) {
+ $$var = array_flip( $$var );
+ }
+ $staticInitialised = true;
+ }
# Remove HTML comments
$text = Sanitizer::removeHTMLcomments( $text );
$bits = explode( '<', $text );
$text = array_shift( $bits );
if(!$wgUseTidy) {
- $tagstack = array(); $tablestack = array();
+ $tagstack = $tablestack = array();
foreach ( $bits as $x ) {
$prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
- preg_match( '/^(\\/?)(\\w+)([^>]*?)(\\/{0,1}>)([^<]*)$/',
- $x, $regs );
+ preg_match( '!^(/?)(\\w+)([^>]*?)(/{0,1}>)([^<]*)$!', $x, $regs );
list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
error_reporting( $prev );
$badtag = 0 ;
- if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
+ if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
# Check our stack
if ( $slash ) {
# Closing a tag...
- if( in_array( $t, $htmlsingleonly ) ) {
+ if( isset( $htmlsingleonly[$t] ) ) {
$badtag = 1;
} elseif ( ( $ot = @array_pop( $tagstack ) ) != $t ) {
- if ( in_array($ot, $htmlsingleallowed) ) {
+ if ( isset( $htmlsingleallowed[$ot] ) ) {
# Pop all elements with an optional close tag
# and see if we find a match below them
$optstack = array();
array_push ($optstack, $ot);
while ( ( ( $ot = @array_pop( $tagstack ) ) != $t ) &&
- in_array($ot, $htmlsingleallowed) ) {
+ isset( $htmlsingleallowed[$ot] ) )
+ {
array_push ($optstack, $ot);
}
if ( $t != $ot ) {
@@ -410,7 +423,7 @@ class Sanitizer {
} else {
@array_push( $tagstack, $ot );
# <li> can be nested in <ul> or <ol>, skip those cases:
- if(!(in_array($ot, $htmllist) && in_array($t, $listtags) )) {
+ if(!(isset( $htmllist[$ot] ) && isset( $listtags[$t] ) )) {
$badtag = 1;
}
}
@@ -422,20 +435,20 @@ class Sanitizer {
$newparams = '';
} else {
# Keep track for later
- if ( in_array( $t, $tabletags ) &&
+ if ( isset( $tabletags[$t] ) &&
! in_array( 'table', $tagstack ) ) {
$badtag = 1;
} else if ( in_array( $t, $tagstack ) &&
- ! in_array ( $t , $htmlnest ) ) {
+ ! isset( $htmlnest [$t ] ) ) {
$badtag = 1 ;
# Is it a self closed htmlpair ? (bug 5487)
} else if( $brace == '/>' &&
- in_array($t, $htmlpairs) ) {
+ isset( $htmlpairs[$t] ) ) {
$badtag = 1;
- } elseif( in_array( $t, $htmlsingleonly ) ) {
+ } elseif( isset( $htmlsingleonly[$t] ) ) {
# Hack to force empty tag for uncloseable elements
$brace = '/>';
- } else if( in_array( $t, $htmlsingle ) ) {
+ } else if( isset( $htmlsingle[$t] ) ) {
# Hack to not close $htmlsingle tags
$brace = NULL;
} else {
@@ -475,7 +488,7 @@ class Sanitizer {
preg_match( '/^(\\/?)(\\w+)([^>]*?)(\\/{0,1}>)([^<]*)$/',
$x, $regs );
@list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
- if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
+ if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
if( is_callable( $processCallback ) ) {
call_user_func_array( $processCallback, array( &$params, $args ) );
}
@@ -487,7 +500,7 @@ class Sanitizer {
}
}
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $text;
}
@@ -501,9 +514,8 @@ class Sanitizer {
* @param string $text
* @return string
*/
- function removeHTMLcomments( $text ) {
- $fname='Parser::removeHTMLcomments';
- wfProfileIn( $fname );
+ static function removeHTMLcomments( $text ) {
+ wfProfileIn( __METHOD__ );
while (($start = strpos($text, '<!--')) !== false) {
$end = strpos($text, '-->', $start + 4);
if ($end === false) {
@@ -533,7 +545,7 @@ class Sanitizer {
$text = substr_replace($text, '', $start, $end - $start);
}
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $text;
}
@@ -551,7 +563,7 @@ class Sanitizer {
* @todo Check for legal values where the DTD limits things.
* @todo Check for unique id attribute :P
*/
- function validateTagAttributes( $attribs, $element ) {
+ static function validateTagAttributes( $attribs, $element ) {
$whitelist = array_flip( Sanitizer::attributeWhitelist( $element ) );
$out = array();
foreach( $attribs as $attribute => $value ) {
@@ -626,7 +638,7 @@ class Sanitizer {
* @param string $element
* @return string
*/
- function fixTagAttributes( $text, $element ) {
+ static function fixTagAttributes( $text, $element ) {
if( trim( $text ) == '' ) {
return '';
}
@@ -649,7 +661,7 @@ class Sanitizer {
* @param $text
* @return HTML-encoded text fragment
*/
- function encodeAttribute( $text ) {
+ static function encodeAttribute( $text ) {
$encValue = htmlspecialchars( $text );
// Whitespace is normalized during attribute decoding,
@@ -670,7 +682,7 @@ class Sanitizer {
* @param $text
* @return HTML-encoded text fragment
*/
- function safeEncodeAttribute( $text ) {
+ static function safeEncodeAttribute( $text ) {
$encValue = Sanitizer::encodeAttribute( $text );
# Templates and links may be expanded in later parsing,
@@ -713,7 +725,7 @@ class Sanitizer {
* @param string $id
* @return string
*/
- function escapeId( $id ) {
+ static function escapeId( $id ) {
static $replace = array(
'%3A' => ':',
'%' => '.'
@@ -730,7 +742,7 @@ class Sanitizer {
* @return string
* @private
*/
- function armorLinksCallback( $matches ) {
+ private static function armorLinksCallback( $matches ) {
return str_replace( ':', '&#58;', $matches[1] );
}
@@ -742,7 +754,7 @@ class Sanitizer {
* @param string
* @return array
*/
- function decodeTagAttributes( $text ) {
+ static function decodeTagAttributes( $text ) {
$attribs = array();
if( trim( $text ) == '' ) {
@@ -780,7 +792,7 @@ class Sanitizer {
* @return string
* @private
*/
- function getTagAttributeCallback( $set ) {
+ private static function getTagAttributeCallback( $set ) {
if( isset( $set[6] ) ) {
# Illegal #XXXXXX color with no quotes.
return $set[6];
@@ -814,7 +826,7 @@ class Sanitizer {
* @return string
* @private
*/
- function normalizeAttributeValue( $text ) {
+ private static function normalizeAttributeValue( $text ) {
return str_replace( '"', '&quot;',
preg_replace(
'/\r\n|[\x20\x0d\x0a\x09]/',
@@ -836,7 +848,7 @@ class Sanitizer {
* @return string
* @private
*/
- function normalizeCharReferences( $text ) {
+ static function normalizeCharReferences( $text ) {
return preg_replace_callback(
MW_CHAR_REFS_REGEX,
array( 'Sanitizer', 'normalizeCharReferencesCallback' ),
@@ -846,7 +858,7 @@ class Sanitizer {
* @param string $matches
* @return string
*/
- function normalizeCharReferencesCallback( $matches ) {
+ static function normalizeCharReferencesCallback( $matches ) {
$ret = null;
if( $matches[1] != '' ) {
$ret = Sanitizer::normalizeEntity( $matches[1] );
@@ -871,8 +883,9 @@ class Sanitizer {
*
* @param string $name
* @return string
+ * @static
*/
- function normalizeEntity( $name ) {
+ static function normalizeEntity( $name ) {
global $wgHtmlEntities;
if( isset( $wgHtmlEntities[$name] ) ) {
return "&$name;";
@@ -881,7 +894,7 @@ class Sanitizer {
}
}
- function decCharReference( $codepoint ) {
+ static function decCharReference( $codepoint ) {
$point = intval( $codepoint );
if( Sanitizer::validateCodepoint( $point ) ) {
return sprintf( '&#%d;', $point );
@@ -890,7 +903,7 @@ class Sanitizer {
}
}
- function hexCharReference( $codepoint ) {
+ static function hexCharReference( $codepoint ) {
$point = hexdec( $codepoint );
if( Sanitizer::validateCodepoint( $point ) ) {
return sprintf( '&#x%x;', $point );
@@ -904,7 +917,7 @@ class Sanitizer {
* @param int $codepoint
* @return bool
*/
- function validateCodepoint( $codepoint ) {
+ private static function validateCodepoint( $codepoint ) {
return ($codepoint == 0x09)
|| ($codepoint == 0x0a)
|| ($codepoint == 0x0d)
@@ -920,8 +933,9 @@ class Sanitizer {
* @param string $text
* @return string
* @public
+ * @static
*/
- function decodeCharReferences( $text ) {
+ public static function decodeCharReferences( $text ) {
return preg_replace_callback(
MW_CHAR_REFS_REGEX,
array( 'Sanitizer', 'decodeCharReferencesCallback' ),
@@ -932,7 +946,7 @@ class Sanitizer {
* @param string $matches
* @return string
*/
- function decodeCharReferencesCallback( $matches ) {
+ static function decodeCharReferencesCallback( $matches ) {
if( $matches[1] != '' ) {
return Sanitizer::decodeEntity( $matches[1] );
} elseif( $matches[2] != '' ) {
@@ -953,7 +967,7 @@ class Sanitizer {
* @return string
* @private
*/
- function decodeChar( $codepoint ) {
+ static function decodeChar( $codepoint ) {
if( Sanitizer::validateCodepoint( $codepoint ) ) {
return codepointToUtf8( $codepoint );
} else {
@@ -969,7 +983,7 @@ class Sanitizer {
* @param string $name
* @return string
*/
- function decodeEntity( $name ) {
+ static function decodeEntity( $name ) {
global $wgHtmlEntities;
if( isset( $wgHtmlEntities[$name] ) ) {
return codepointToUtf8( $wgHtmlEntities[$name] );
@@ -985,7 +999,7 @@ class Sanitizer {
* @param string $element
* @return array
*/
- function attributeWhitelist( $element ) {
+ static function attributeWhitelist( $element ) {
static $list;
if( !isset( $list ) ) {
$list = Sanitizer::setupAttributeWhitelist();
@@ -996,9 +1010,10 @@ class Sanitizer {
}
/**
+ * @todo Document it a bit
* @return array
*/
- function setupAttributeWhitelist() {
+ static function setupAttributeWhitelist() {
$common = array( 'id', 'class', 'lang', 'dir', 'title', 'style' );
$block = array_merge( $common, array( 'align' ) );
$tablealign = array( 'align', 'char', 'charoff', 'valign' );
@@ -1082,9 +1097,9 @@ class Sanitizer {
# 11.2.1
'table' => array_merge( $common,
array( 'summary', 'width', 'border', 'frame',
- 'rules', 'cellspacing', 'cellpadding',
- 'align', 'bgcolor', 'frame', 'rules',
- 'border' ) ),
+ 'rules', 'cellspacing', 'cellpadding',
+ 'align', 'bgcolor',
+ ) ),
# 11.2.2
'caption' => array_merge( $common, array( 'align' ) ),
@@ -1142,7 +1157,7 @@ class Sanitizer {
* @param string $text HTML fragment
* @return string
*/
- function stripAllTags( $text ) {
+ static function stripAllTags( $text ) {
# Actual <tags>
$text = preg_replace( '/ < .*? > /x', '', $text );
@@ -1169,7 +1184,7 @@ class Sanitizer {
* @return string
* @static
*/
- function hackDocType() {
+ static function hackDocType() {
global $wgHtmlEntities;
$out = "<!DOCTYPE html [\n";
foreach( $wgHtmlEntities as $entity => $codepoint ) {
@@ -1178,6 +1193,47 @@ class Sanitizer {
$out .= "]>\n";
return $out;
}
+
+ static function cleanUrl( $url, $hostname=true ) {
+ # Normalize any HTML entities in input. They will be
+ # re-escaped by makeExternalLink().
+ $url = Sanitizer::decodeCharReferences( $url );
+
+ # Escape any control characters introduced by the above step
+ $url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F]/e', "urlencode('\\0')", $url );
+
+ # Validate hostname portion
+ if( preg_match( '!^([^:]+:)(//[^/]+)?(.*)$!iD', $url, $matches ) ) {
+ list( $whole, $protocol, $host, $rest ) = $matches;
+
+ // Characters that will be ignored in IDNs.
+ // http://tools.ietf.org/html/3454#section-3.1
+ // Strip them before further processing so blacklists and such work.
+ $strip = "/
+ \\s| # general whitespace
+ \xc2\xad| # 00ad SOFT HYPHEN
+ \xe1\xa0\x86| # 1806 MONGOLIAN TODO SOFT HYPHEN
+ \xe2\x80\x8b| # 200b ZERO WIDTH SPACE
+ \xe2\x81\xa0| # 2060 WORD JOINER
+ \xef\xbb\xbf| # feff ZERO WIDTH NO-BREAK SPACE
+ \xcd\x8f| # 034f COMBINING GRAPHEME JOINER
+ \xe1\xa0\x8b| # 180b MONGOLIAN FREE VARIATION SELECTOR ONE
+ \xe1\xa0\x8c| # 180c MONGOLIAN FREE VARIATION SELECTOR TWO
+ \xe1\xa0\x8d| # 180d MONGOLIAN FREE VARIATION SELECTOR THREE
+ \xe2\x80\x8c| # 200c ZERO WIDTH NON-JOINER
+ \xe2\x80\x8d| # 200d ZERO WIDTH JOINER
+ [\xef\xb8\x80-\xef\xb8\x8f] # fe00-fe00f VARIATION SELECTOR-1-16
+ /xuD";
+
+ $host = preg_replace( $strip, '', $host );
+
+ // @fixme: validate hostnames here
+
+ return $protocol . $host . $rest;
+ } else {
+ return $url;
+ }
+ }
}
diff --git a/includes/SearchEngine.php b/includes/SearchEngine.php
index c3b38519..5e598883 100644
--- a/includes/SearchEngine.php
+++ b/includes/SearchEngine.php
@@ -50,67 +50,72 @@ class SearchEngine {
* @return Title
* @private
*/
- function getNearMatch( $term ) {
- # Exact match? No need to look further.
- $title = Title::newFromText( $term );
- if (is_null($title))
- return NULL;
+ function getNearMatch( $searchterm ) {
+ global $wgContLang;
- if ( $title->getNamespace() == NS_SPECIAL || $title->exists() ) {
- return $title;
- }
+ $allSearchTerms = array($searchterm);
- # Now try all lower case (i.e. first letter capitalized)
- #
- $title = Title::newFromText( strtolower( $term ) );
- if ( $title->exists() ) {
- return $title;
+ if($wgContLang->hasVariants()){
+ $allSearchTerms = array_merge($allSearchTerms,$wgContLang->convertLinkToAllVariants($searchterm));
}
- # Now try capitalized string
- #
- $title = Title::newFromText( ucwords( strtolower( $term ) ) );
- if ( $title->exists() ) {
- return $title;
- }
+ foreach($allSearchTerms as $term){
- # Now try all upper case
- #
- $title = Title::newFromText( strtoupper( $term ) );
- if ( $title->exists() ) {
- return $title;
- }
+ # Exact match? No need to look further.
+ $title = Title::newFromText( $term );
+ if (is_null($title))
+ return NULL;
- # Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc
- $title = Title::newFromText( preg_replace_callback(
- '/\b([\w\x80-\xff]+)\b/',
- create_function( '$matches', '
- global $wgContLang;
- return $wgContLang->ucfirst($matches[1]);
- ' ),
- $term ) );
- if ( $title->exists() ) {
- return $title;
- }
+ if ( $title->getNamespace() == NS_SPECIAL || $title->exists() ) {
+ return $title;
+ }
- global $wgCapitalLinks, $wgContLang;
- if( !$wgCapitalLinks ) {
- // Catch differs-by-first-letter-case-only
- $title = Title::newFromText( $wgContLang->ucfirst( $term ) );
+ # Now try all lower case (i.e. first letter capitalized)
+ #
+ $title = Title::newFromText( $wgContLang->lc( $term ) );
if ( $title->exists() ) {
return $title;
}
- $title = Title::newFromText( $wgContLang->lcfirst( $term ) );
+
+ # Now try capitalized string
+ #
+ $title = Title::newFromText( $wgContLang->ucwords( $term ) );
+ if ( $title->exists() ) {
+ return $title;
+ }
+
+ # Now try all upper case
+ #
+ $title = Title::newFromText( $wgContLang->uc( $term ) );
if ( $title->exists() ) {
return $title;
}
+
+ # Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc
+ $title = Title::newFromText( $wgContLang->ucwordbreaks($term) );
+ if ( $title->exists() ) {
+ return $title;
+ }
+
+ global $wgCapitalLinks, $wgContLang;
+ if( !$wgCapitalLinks ) {
+ // Catch differs-by-first-letter-case-only
+ $title = Title::newFromText( $wgContLang->ucfirst( $term ) );
+ if ( $title->exists() ) {
+ return $title;
+ }
+ $title = Title::newFromText( $wgContLang->lcfirst( $term ) );
+ if ( $title->exists() ) {
+ return $title;
+ }
+ }
}
- $title = Title::newFromText( $term );
+ $title = Title::newFromText( $searchterm );
# Entering an IP address goes to the contributions page
if ( ( $title->getNamespace() == NS_USER && User::isIP($title->getText() ) )
- || User::isIP( trim( $term ) ) ) {
+ || User::isIP( trim( $searchterm ) ) ) {
return Title::makeTitle( NS_SPECIAL, "Contributions/" . $title->getDbkey() );
}
@@ -121,7 +126,7 @@ class SearchEngine {
}
# Quoted term? Try without the quotes...
- if( preg_match( '/^"([^"]+)"$/', $term, $matches ) ) {
+ if( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
return SearchEngine::getNearMatch( $matches[1] );
}
diff --git a/includes/SearchPostgres.php b/includes/SearchPostgres.php
index 8e36b0b5..faf53f02 100644
--- a/includes/SearchPostgres.php
+++ b/includes/SearchPostgres.php
@@ -98,8 +98,8 @@ class SearchPostgres extends SearchEngine {
$match = $this->parseQuery( $filteredTerm, $fulltext );
$query = "SELECT page_id, page_namespace, page_title, old_text AS page_text ".
- "FROM page p, revision r, text t WHERE p.page_latest = r.rev_id " .
- "AND r.rev_text_id = t.old_id AND $fulltext @@ to_tsquery('$match')";
+ "FROM page p, revision r, pagecontent c WHERE p.page_latest = r.rev_id " .
+ "AND r.rev_text_id = c.old_id AND $fulltext @@ to_tsquery('default','$match')";
## Redirects
if (! $this->showRedirects)
@@ -113,7 +113,7 @@ class SearchPostgres extends SearchEngine {
$query .= " AND page_namespace IN ($namespaces)";
}
- $query .= " ORDER BY rank($fulltext, to_tsquery('$fulltext')) DESC";
+ $query .= " ORDER BY rank($fulltext, to_tsquery('default','$fulltext')) DESC";
$query .= $this->db->limitResult( '', $this->limit, $this->offset );
diff --git a/includes/Setup.php b/includes/Setup.php
index 1ef83cc7..8fe9ef71 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -8,7 +8,10 @@
* This file is not a valid entry point, perform no further processing unless
* MEDIAWIKI is defined
*/
-if( defined( 'MEDIAWIKI' ) ) {
+if( !defined( 'MEDIAWIKI' ) ) {
+ echo "This file is part of MediaWiki, it is not a valid entry point.\n";
+ exit( 1 );
+}
# The main wiki script and things like database
# conversion and maintenance scripts all share a
@@ -16,64 +19,37 @@ if( defined( 'MEDIAWIKI' ) ) {
# setting up a few globals.
#
+$fname = 'Setup.php';
+wfProfileIn( $fname );
+
// Check to see if we are at the file scope
if ( !isset( $wgVersion ) ) {
echo "Error, Setup.php must be included from the file scope, after DefaultSettings.php\n";
die( 1 );
}
-if( !isset( $wgProfiling ) )
- $wgProfiling = false;
-
require_once( "$IP/includes/AutoLoader.php" );
-if ( function_exists( 'wfProfileIn' ) ) {
- /* nada, everything should be done already */
-} elseif ( $wgProfiling and (0 == rand() % $wgProfileSampleRate ) ) {
- $wgProfiling = true;
- if ($wgProfilerType == "") {
- $wgProfiler = new Profiler();
- } else {
- $prclass="Profiler{$wgProfilerType}";
- require_once( $prclass.".php" );
- $wgProfiler = new $prclass();
- }
-} else {
- require_once( "$IP/includes/ProfilerStub.php" );
-}
-
-$fname = 'Setup.php';
-wfProfileIn( $fname );
-
wfProfileIn( $fname.'-exception' );
require_once( "$IP/includes/Exception.php" );
wfInstallExceptionHandler();
wfProfileOut( $fname.'-exception' );
wfProfileIn( $fname.'-includes' );
-
require_once( "$IP/includes/GlobalFunctions.php" );
require_once( "$IP/includes/Hooks.php" );
require_once( "$IP/includes/Namespace.php" );
-require_once( "$IP/includes/User.php" );
-require_once( "$IP/includes/OutputPage.php" );
-require_once( "$IP/includes/MagicWord.php" );
-require_once( "$IP/includes/MessageCache.php" );
-require_once( "$IP/includes/Parser.php" );
-require_once( "$IP/includes/LoadBalancer.php" );
require_once( "$IP/includes/ProxyTools.php" );
require_once( "$IP/includes/ObjectCache.php" );
require_once( "$IP/includes/ImageFunctions.php" );
-
-if ( $wgUseDynamicDates ) {
- require_once( "$IP/includes/DateFormatter.php" );
-}
-
+require_once( "$IP/includes/StubObject.php" );
wfProfileOut( $fname.'-includes' );
wfProfileIn( $fname.'-misc1' );
+
$wgIP = false; # Load on demand
-$wgRequest = new WebRequest();
+# Can't stub this one, it sets up $_GET and $_REQUEST in its constructor
+$wgRequest = new WebRequest;
if ( function_exists( 'posix_uname' ) ) {
$wguname = posix_uname();
$wgNodeName = $wguname['nodename'];
@@ -83,7 +59,7 @@ if ( function_exists( 'posix_uname' ) ) {
# Useful debug output
if ( $wgCommandLineMode ) {
- # wfDebug( '"' . implode( '" "', $argv ) . '"' );
+ wfDebug( "\n\nStart command line script $self\n" );
} elseif ( function_exists( 'getallheaders' ) ) {
wfDebug( "\n\nStart request\n" );
wfDebug( $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] . "\n" );
@@ -102,6 +78,14 @@ if ( $wgSkipSkin ) {
$wgUseEnotif = $wgEnotifUserTalk || $wgEnotifWatchlist;
+if($wgMetaNamespace === FALSE) {
+ $wgMetaNamespace = str_replace( ' ', '_', $wgSitename );
+}
+
+# These are now the same, always
+# To determine the user language, use $wgLang->getCode()
+$wgContLanguageCode = $wgLanguageCode;
+
wfProfileOut( $fname.'-misc1' );
wfProfileIn( $fname.'-memcached' );
@@ -131,7 +115,7 @@ if( !ini_get( 'session.auto_start' ) )
if( !$wgCommandLineMode && ( isset( $_COOKIE[session_name()] ) || isset( $_COOKIE[$wgCookiePrefix.'Token'] ) ) ) {
wfIncrStats( 'request_with_session' );
- User::SetupSession();
+ wfSetupSession();
$wgSessionStarted = true;
} else {
wfIncrStats( 'request_without_session' );
@@ -139,7 +123,7 @@ if( !$wgCommandLineMode && ( isset( $_COOKIE[session_name()] ) || isset( $_COOKI
}
wfProfileOut( $fname.'-SetupSession' );
-wfProfileIn( $fname.'-database' );
+wfProfileIn( $fname.'-globals' );
if ( !$wgDBservers ) {
$wgDBservers = array(array(
@@ -152,47 +136,18 @@ if ( !$wgDBservers ) {
'flags' => ($wgDebugDumpSql ? DBO_DEBUG : 0) | DBO_DEFAULT
));
}
-$wgLoadBalancer = LoadBalancer::newFromParams( $wgDBservers, false, $wgMasterWaitTimeout );
-$wgLoadBalancer->loadMasterPos();
-
-wfProfileOut( $fname.'-database' );
-wfProfileIn( $fname.'-language1' );
-
-require_once( "$IP/languages/Language.php" );
-
-function setupLangObj($langclass) {
- global $IP;
-
- if( ! class_exists( $langclass ) ) {
- # Default to English/UTF-8
- $baseclass = 'LanguageUtf8';
- require_once( "$IP/languages/$baseclass.php" );
- $lc = strtolower(substr($langclass, 8));
- $snip = "
- class $langclass extends $baseclass {
- function getVariants() {
- return array(\"$lc\");
- }
-
- }";
- eval($snip);
- }
- $lang = new $langclass();
+$wgLoadBalancer = new StubObject( 'wgLoadBalancer', 'LoadBalancer',
+ array( $wgDBservers, false, $wgMasterWaitTimeout, true ) );
+$wgContLang = new StubContLang;
+$wgUser = new StubUser;
+$wgLang = new StubUserLang;
+$wgOut = new StubObject( 'wgOut', 'OutputPage' );
+$wgParser = new StubObject( 'wgParser', 'Parser' );
+$wgMessageCache = new StubObject( 'wgMessageCache', 'MessageCache',
+ array( $parserMemc, $wgUseDatabaseMessages, $wgMsgCacheExpiry, wfWikiID() ) );
- return $lang;
-}
-
-# $wgLanguageCode may be changed later to fit with user preference.
-# The content language will remain fixed as per the configuration,
-# so let's keep it.
-$wgContLanguageCode = $wgLanguageCode;
-$wgContLangClass = 'Language' . str_replace( '-', '_', ucfirst( $wgContLanguageCode ) );
-
-$wgContLang = setupLangObj( $wgContLangClass );
-$wgContLang->initEncoding();
-
-wfProfileOut( $fname.'-language1' );
+wfProfileOut( $fname.'-globals' );
wfProfileIn( $fname.'-User' );
# Skin setup functions
@@ -204,104 +159,22 @@ foreach ( $wgSkinExtensionFunctions as $func ) {
}
if( !is_object( $wgAuth ) ) {
- require_once( 'AuthPlugin.php' );
- $wgAuth = new AuthPlugin();
-}
-
-if( $wgCommandLineMode ) {
- # Used for some maintenance scripts; user session cookies can screw things up
- # when the database is in an in-between state.
- $wgUser = new User();
- # Prevent loading User settings from the DB.
- $wgUser->setLoaded( true );
-} else {
- $wgUser = null;
- wfRunHooks('AutoAuthenticate',array(&$wgUser));
- if ($wgUser === null) {
- $wgUser = User::loadFromSession();
- }
+ $wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' );
}
-
wfProfileOut( $fname.'-User' );
-wfProfileIn( $fname.'-language2' );
-
-// wgLanguageCode now specifically means the UI language
-$wgLanguageCode = $wgRequest->getText('uselang', '');
-if ($wgLanguageCode == '')
- $wgLanguageCode = $wgUser->getOption('language');
-# Validate $wgLanguageCode, which will soon be sent to an eval()
-if( empty( $wgLanguageCode ) || !preg_match( '/^[a-z]+(-[a-z]+)?$/', $wgLanguageCode ) ) {
- $wgLanguageCode = $wgContLanguageCode;
-}
-
-$wgLangClass = 'Language'. str_replace( '-', '_', ucfirst( $wgLanguageCode ) );
-
-if( $wgLangClass == $wgContLangClass ) {
- $wgLang = &$wgContLang;
-} else {
- wfSuppressWarnings();
- // Preload base classes to work around APC/PHP5 bug
- include_once("$IP/languages/$wgLangClass.deps.php");
- include_once("$IP/languages/$wgLangClass.php");
- wfRestoreWarnings();
-
- $wgLang = setupLangObj( $wgLangClass );
-}
-
-wfProfileOut( $fname.'-language2' );
-wfProfileIn( $fname.'-MessageCache' );
-$wgMessageCache = new MessageCache( $parserMemc, $wgUseDatabaseMessages, $wgMsgCacheExpiry, $wgDBname);
-
-wfProfileOut( $fname.'-MessageCache' );
-
-#
-# I guess the warning about UI switching might still apply...
-#
-# FIXME: THE ABOVE MIGHT BREAK NAMESPACES, VARIABLES,
-# SEARCH INDEX UPDATES, AND MANY MANY THINGS.
-# DO NOT USE THIS MODE EXCEPT FOR TESTING RIGHT NOW.
-#
-# To disable it, the easiest thing could be to uncomment the
-# following; they should effectively disable the UI switch functionality
-#
-# $wgLangClass = $wgContLangClass;
-# $wgLanguageCode = $wgContLanguageCode;
-# $wgLang = $wgContLang;
-#
-# TODO: Need to change reference to $wgLang to $wgContLang at proper
-# places, including namespaces, dates in signatures, magic words,
-# and links
-#
-# TODO: Need to look at the issue of input/output encoding
-#
-
-
-wfProfileIn( $fname.'-OutputPage' );
-
-$wgOut = new OutputPage();
-
-wfProfileOut( $fname.'-OutputPage' );
wfProfileIn( $fname.'-misc2' );
$wgDeferredUpdateList = array();
$wgPostCommitUpdateList = array();
-$wgMagicWords = array();
+if ( $wgAjaxSearch ) $wgAjaxExportList[] = 'wfSajaxSearch';
-if ( $wgUseXMLparser ) {
- require_once( 'ParserXML.php' );
- $wgParser = new ParserXML();
-} else {
- $wgParser = new Parser();
-}
-$wgOut->setParserOptions( ParserOptions::newFromUser( $wgUser ) );
-$wgMsgParserOptions = ParserOptions::newFromUser($wgUser);
wfSeedRandom();
# Placeholders in case of DB error
-$wgTitle = Title::makeTitle( NS_SPECIAL, 'Error' );
-$wgArticle = new Article($wgTitle);
+$wgTitle = null;
+$wgArticle = null;
wfProfileOut( $fname.'-misc2' );
wfProfileIn( $fname.'-extensions' );
@@ -311,7 +184,10 @@ wfProfileIn( $fname.'-extensions' );
# of the extension file. This allows the extension to perform
# any necessary initialisation in the fully initialised environment
foreach ( $wgExtensionFunctions as $func ) {
+ $profName = $fname.'-extensions-'.strval( $func );
+ wfProfileIn( $profName );
call_user_func( $func );
+ wfProfileOut( $profName );
}
// For compatibility
@@ -321,10 +197,9 @@ wfRunHooks( 'LogPageLogHeader', array( &$wgLogHeaders ) );
wfRunHooks( 'LogPageActionText', array( &$wgLogActions ) );
-wfDebug( "\n" );
+wfDebug( "Fully initialised\n" );
$wgFullyInitialised = true;
wfProfileOut( $fname.'-extensions' );
wfProfileOut( $fname );
-}
?>
diff --git a/includes/SiteStatsUpdate.php b/includes/SiteStatsUpdate.php
index 1b6d3804..b91dcfeb 100644
--- a/includes/SiteStatsUpdate.php
+++ b/includes/SiteStatsUpdate.php
@@ -75,8 +75,18 @@ class SiteStatsUpdate {
if ( $updates ) {
$site_stats = $dbw->tableName( 'site_stats' );
$sql = $dbw->limitResultForUpdate("UPDATE $site_stats SET $updates", 1);
+ $dbw->begin();
$dbw->query( $sql, $fname );
+ $dbw->commit();
}
+
+ /*
+ global $wgDBname, $wgTitle;
+ if ( $this->mGood && $wgDBname == 'enwiki' ) {
+ $good = $dbw->selectField( 'site_stats', 'ss_good_articles', '', $fname );
+ error_log( $good . ' ' . $wgTitle->getPrefixedDBkey() . "\n", 3, '/home/wikipedia/logs/million.log' );
+ }
+ */
}
}
?>
diff --git a/includes/Skin.php b/includes/Skin.php
index 8a03f461..ffbe27c7 100644
--- a/includes/Skin.php
+++ b/includes/Skin.php
@@ -33,7 +33,7 @@ class Skin extends Linker {
* @return array of strings
* @static
*/
- function &getSkinNames() {
+ static function &getSkinNames() {
global $wgValidSkinNames;
static $skinsInitialised = false;
if ( !$skinsInitialised ) {
@@ -68,7 +68,7 @@ class Skin extends Linker {
* @return string
* @static
*/
- function normalizeKey( $key ) {
+ static function normalizeKey( $key ) {
global $wgDefaultSkin;
$skinNames = Skin::getSkinNames();
@@ -107,7 +107,7 @@ class Skin extends Linker {
* @return Skin
* @static
*/
- function &newFromKey( $key ) {
+ static function &newFromKey( $key ) {
global $wgStyleDirectory;
$key = Skin::normalizeKey( $key );
@@ -133,7 +133,7 @@ class Skin extends Linker {
$className = 'SkinStandard';
require_once( "{$wgStyleDirectory}/Standard.php" );
}
- $skin =& new $className;
+ $skin = new $className;
return $skin;
}
@@ -157,7 +157,7 @@ class Skin extends Linker {
}
function initPage( &$out ) {
- global $wgFavicon;
+ global $wgFavicon, $wgScriptPath, $wgSitename, $wgLanguageCode, $wgLanguageNames;
$fname = 'Skin::initPage';
wfProfileIn( $fname );
@@ -166,6 +166,14 @@ class Skin extends Linker {
$out->addLink( array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
}
+ # OpenSearch description link
+ $out->addLink( array(
+ 'rel' => 'search',
+ 'type' => 'application/opensearchdescription+xml',
+ 'href' => "$wgScriptPath/opensearch_desc.php",
+ 'title' => "$wgSitename ({$wgLanguageNames[$wgLanguageCode]})",
+ ));
+
$this->addMetadataLinks($out);
$this->mRevisionId = $out->mRevisionId;
@@ -255,17 +263,70 @@ class Skin extends Linker {
$out->out( $this->afterContent() );
+ $out->out( $this->bottomScripts() );
+
$out->out( $out->reportTime() );
$out->out( "\n</body></html>" );
}
+ static function makeGlobalVariablesScript( $data ) {
+ $r = '<script type= "' . $data['jsmimetype'] . '">
+ var skin = "' . Xml::escapeJsString( $data['skinname'] ) . '";
+ var stylepath = "' . Xml::escapeJsString( $data['stylepath'] ) . '";
+
+ var wgArticlePath = "' . Xml::escapeJsString( $data['articlepath'] ) . '";
+ var wgScriptPath = "' . Xml::escapeJsString( $data['scriptpath'] ) . '";
+ var wgServer = "' . Xml::escapeJsString( $data['serverurl'] ) . '";
+
+ var wgCanonicalNamespace = "' . Xml::escapeJsString( $data['nscanonical'] ) . '";
+ var wgNamespaceNumber = ' . (int)$data['nsnumber'] . ';
+ var wgPageName = "' . Xml::escapeJsString( $data['titleprefixeddbkey'] ) . '";
+ var wgTitle = "' . Xml::escapeJsString( $data['titletext'] ) . '";
+ var wgArticleId = ' . (int)$data['articleid'] . ';
+ var wgIsArticle = ' . ( $data['isarticle'] ? 'true' : 'false' ) . ';
+
+ var wgUserName = ' . ( $data['username'] == NULL ? 'null' : ( '"' . Xml::escapeJsString( $data['username'] ) . '"' ) ) . ';
+ var wgUserLanguage = "' . Xml::escapeJsString( $data['userlang'] ) . '";
+ var wgContentLanguage = "' . Xml::escapeJsString( $data['lang'] ) . '";
+ </script>
+ ';
+
+ return $r;
+ }
+
function getHeadScripts() {
global $wgStylePath, $wgUser, $wgAllowUserJs, $wgJsMimeType;
- $r = "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/wikibits.js\"></script>\n";
+ global $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgLang;
+ global $wgTitle, $wgCanonicalNamespaceNames, $wgOut;
+
+ $nsname = @$wgCanonicalNamespaceNames[ $wgTitle->getNamespace() ];
+ if ( $nsname === NULL ) $nsname = $wgTitle->getNsText();
+
+ $vars = array(
+ 'jsmimetype' => $wgJsMimeType,
+ 'skinname' => $this->getSkinName(),
+ 'stylepath' => $wgStylePath,
+ 'articlepath' => $wgArticlePath,
+ 'scriptpath' => $wgScriptPath,
+ 'serverurl' => $wgServer,
+ 'nscanonical' => $nsname,
+ 'nsnumber' => $wgTitle->getNamespace(),
+ 'titleprefixeddbkey' => $wgTitle->getPrefixedDBKey(),
+ 'titletext' => $wgTitle->getText(),
+ 'articleid' => $wgTitle->getArticleId(),
+ 'isarticle' => $wgOut->isArticle(),
+ 'username' => $wgUser->isAnon() ? NULL : $wgUser->getName(),
+ 'userlang' => $wgLang->getCode(),
+ 'lang' => $wgContLang->getCode(),
+ );
+
+ $r = self::makeGlobalVariablesScript( $vars );
+
+ $r .= "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/wikibits.js\"></script>\n";
if( $wgAllowUserJs && $wgUser->isLoggedIn() ) {
$userpage = $wgUser->getUserPage();
- $userjs = htmlspecialchars( $this->makeUrl(
+ $userjs = htmlspecialchars( self::makeUrl(
$userpage->getPrefixedText().'/'.$this->getSkinName().'.js',
'action=raw&ctype='.$wgJsMimeType));
$r .= '<script type="'.$wgJsMimeType.'" src="'.$userjs."\"></script>\n";
@@ -305,9 +366,9 @@ class Skin extends Linker {
$s = "@import \"$wgStylePath/$sheet\";\n";
if($wgContLang->isRTL()) $s .= "@import \"$wgStylePath/common/common_rtl.css\";\n";
- $query = "action=raw&ctype=text/css&smaxage=$wgSquidMaxage";
- $s .= '@import "' . $this->makeNSUrl( 'Common.css', $query, NS_MEDIAWIKI ) . "\";\n" .
- '@import "'.$this->makeNSUrl( ucfirst( $this->getSkinName() . '.css' ), $query, NS_MEDIAWIKI ) . "\";\n";
+ $query = "usemsgcache=yes&action=raw&ctype=text/css&smaxage=$wgSquidMaxage";
+ $s .= '@import "' . self::makeNSUrl( 'Common.css', $query, NS_MEDIAWIKI ) . "\";\n" .
+ '@import "' . self::makeNSUrl( ucfirst( $this->getSkinName() . '.css' ), $query, NS_MEDIAWIKI ) . "\";\n";
$s .= $this->doGetUserStyles();
return $s."\n";
@@ -343,7 +404,7 @@ class Skin extends Linker {
$s .= $wgRequest->getText('wpTextbox1');
} else {
$userpage = $wgUser->getUserPage();
- $s.= '@import "'.$this->makeUrl(
+ $s.= '@import "'.self::makeUrl(
$userpage->getPrefixedText().'/'.$this->getSkinName().'.css',
'action=raw&ctype=text/css').'";'."\n";
}
@@ -393,7 +454,7 @@ END;
}
function getBodyOptions() {
- global $wgUser, $wgTitle, $wgOut, $wgRequest;
+ global $wgUser, $wgTitle, $wgOut, $wgRequest, $wgContLang;
extract( $wgRequest->getValues( 'oldid', 'redirect', 'diff' ) );
@@ -416,6 +477,7 @@ END;
}
$a['onload'] .= 'setupRightClickEdit()';
}
+ $a['class'] = 'ns-'.$wgTitle->getNamespace().' '.($wgContLang->isRTL() ? "rtl" : "ltr");
return $a;
}
@@ -573,14 +635,23 @@ END;
}
/**
- * This gets called immediately before the \</body\> tag.
- * @return String HTML to be put after \</body\> ???
+ * This gets called shortly before the \</body\> tag.
+ * @return String HTML to be put before \</body\>
*/
function afterContent() {
$printfooter = "<div class=\"printfooter\">\n" . $this->printFooter() . "</div>\n";
return $printfooter . $this->doAfterContent();
}
+ /**
+ * This gets called shortly before the \</body\> tag.
+ * @return String HTML-wrapped JS code to be put before \</body\>
+ */
+ function bottomScripts() {
+ global $wgJsMimeType;
+ return "\n\t\t<script type=\"$wgJsMimeType\">if (window.runOnloadHook) runOnloadHook();</script>\n";
+ }
+
/** @return string Retrievied from HTML text */
function printSource() {
global $wgTitle;
@@ -802,8 +873,8 @@ END;
. $this->escapeSearchLink() . "\">\n"
. '<input type="text" name="search" size="19" value="'
. htmlspecialchars(substr($search,0,256)) . "\" />\n"
- . '<input type="submit" name="go" value="' . wfMsg ('go') . '" />&nbsp;'
- . '<input type="submit" name="fulltext" value="' . wfMsg ('search') . "\" />\n</form>";
+ . '<input type="submit" name="go" value="' . wfMsg ('searcharticle') . '" />&nbsp;'
+ . '<input type="submit" name="fulltext" value="' . wfMsg ('searchbutton') . "\" />\n</form>";
return $s;
}
@@ -983,8 +1054,9 @@ END;
$timestamp = $wgArticle->getTimestamp();
if ( $timestamp ) {
- $d = $wgLang->timeanddate( $timestamp, true );
- $s = ' ' . wfMsg( 'lastmodified', $d );
+ $d = $wgLang->date( $timestamp, true );
+ $t = $wgLang->time( $timestamp, true );
+ $s = ' ' . wfMsg( 'lastmodifiedat', $d, $t );
} else {
$s = '';
}
@@ -1013,30 +1085,13 @@ END;
/**
* show a drop-down box of special pages
- * @TODO crash bug913. Need to be rewrote completly.
*/
function specialPagesList() {
- global $wgUser, $wgContLang, $wgServer, $wgRedirectScript, $wgAvailableRights;
- require_once('SpecialPage.php');
+ global $wgUser, $wgContLang, $wgServer, $wgRedirectScript;
$a = array();
- $pages = SpecialPage::getPages();
-
- // special pages without access restriction
- foreach ( $pages[''] as $name => $page ) {
- $a[$name] = $page->getDescription();
- }
-
- // Other special pages that are restricted.
- // Copied from SpecialSpecialpages.php
- foreach($wgAvailableRights as $right) {
- if( $wgUser->isAllowed($right) ) {
- /** Add all pages for this right */
- if(isset($pages[$right])) {
- foreach($pages[$right] as $name => $page) {
- $a[$name] = $page->getDescription();
- }
- }
- }
+ $pages = array_merge( SpecialPage::getRegularPages(), SpecialPage::getRestrictedPages() );
+ foreach ( $pages as $name => $page ) {
+ $pages[$name] = $page->getDescription();
}
$go = wfMsg( 'go' );
@@ -1049,7 +1104,7 @@ END;
$s .= "<option value=\"{$spp}\">{$sp}</option>\n";
- foreach ( $a as $name => $desc ) {
+ foreach ( $pages as $name => $desc ) {
$p = $wgContLang->specialPage( $name );
$s .= "<option value=\"{$p}\">{$desc}</option>\n";
}
@@ -1323,20 +1378,32 @@ END;
if( $wgTitle->isTalkPage() ) {
$link = $wgTitle->getSubjectPage();
switch( $link->getNamespace() ) {
- case NS_MAIN:
- $text = wfMsg('articlepage');
- break;
- case NS_USER:
- $text = wfMsg('userpage');
- break;
- case NS_PROJECT:
- $text = wfMsg('projectpage');
- break;
- case NS_IMAGE:
- $text = wfMsg('imagepage');
- break;
- default:
- $text= wfMsg('articlepage');
+ case NS_MAIN:
+ $text = wfMsg( 'articlepage' );
+ break;
+ case NS_USER:
+ $text = wfMsg( 'userpage' );
+ break;
+ case NS_PROJECT:
+ $text = wfMsg( 'projectpage' );
+ break;
+ case NS_IMAGE:
+ $text = wfMsg( 'imagepage' );
+ break;
+ case NS_MEDIAWIKI:
+ $text = wfMsg( 'mediawikipage' );
+ break;
+ case NS_TEMPLATE:
+ $text = wfMsg( 'templatepage' );
+ break;
+ case NS_HELP:
+ $text = wfMsg( 'viewhelppage' );
+ break;
+ case NS_CATEGORY:
+ $text = wfMsg( 'categorypage' );
+ break;
+ default:
+ $text = wfMsg( 'articlepage' );
}
} else {
$link = $wgTitle->getTalkPage();
@@ -1370,56 +1437,56 @@ END;
}
/* these are used extensively in SkinTemplate, but also some other places */
- /*static*/ function makeSpecialUrl( $name, $urlaction='' ) {
+ static function makeSpecialUrl( $name, $urlaction = '' ) {
$title = Title::makeTitle( NS_SPECIAL, $name );
return $title->getLocalURL( $urlaction );
}
- /*static*/ function makeI18nUrl ( $name, $urlaction='' ) {
- $title = Title::newFromText( wfMsgForContent($name) );
- $this->checkTitle($title, $name);
+ static function makeI18nUrl( $name, $urlaction = '' ) {
+ $title = Title::newFromText( wfMsgForContent( $name ) );
+ self::checkTitle( $title, $name );
return $title->getLocalURL( $urlaction );
}
- /*static*/ function makeUrl ( $name, $urlaction='' ) {
+ static function makeUrl( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
- $this->checkTitle($title, $name);
+ self::checkTitle( $title, $name );
return $title->getLocalURL( $urlaction );
}
# If url string starts with http, consider as external URL, else
# internal
- /*static*/ function makeInternalOrExternalUrl( $name ) {
+ static function makeInternalOrExternalUrl( $name ) {
if ( preg_match( '/^(?:' . wfUrlProtocols() . ')/', $name ) ) {
return $name;
} else {
- return $this->makeUrl( $name );
+ return self::makeUrl( $name );
}
}
# this can be passed the NS number as defined in Language.php
- /*static*/ function makeNSUrl( $name, $urlaction='', $namespace=NS_MAIN ) {
+ static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) {
$title = Title::makeTitleSafe( $namespace, $name );
- $this->checkTitle($title, $name);
+ self::checkTitle( $title, $name );
return $title->getLocalURL( $urlaction );
}
/* these return an array with the 'href' and boolean 'exists' */
- /*static*/ function makeUrlDetails ( $name, $urlaction='' ) {
+ static function makeUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
- $this->checkTitle($title, $name);
+ self::checkTitle( $title, $name );
return array(
'href' => $title->getLocalURL( $urlaction ),
- 'exists' => $title->getArticleID() != 0?true:false
+ 'exists' => $title->getArticleID() != 0 ? true : false
);
}
/**
* Make URL details where the article exists (or at least it's convenient to think so)
*/
- function makeKnownUrlDetails( $name, $urlaction='' ) {
+ static function makeKnownUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
- $this->checkTitle($title, $name);
+ self::checkTitle( $title, $name );
return array(
'href' => $title->getLocalURL( $urlaction ),
'exists' => true
@@ -1427,10 +1494,10 @@ END;
}
# make sure we have some title to operate on
- /*static*/ function checkTitle ( &$title, &$name ) {
- if(!is_object($title)) {
+ static function checkTitle( &$title, &$name ) {
+ if( !is_object( $title ) ) {
$title = Title::newFromText( $name );
- if(!is_object($title)) {
+ if( !is_object( $title ) ) {
$title = Title::newFromText( '--error: link target missing--' );
}
}
@@ -1443,16 +1510,16 @@ END;
* @private
*/
function buildSidebar() {
- global $wgDBname, $parserMemc, $wgEnableSidebarCache;
- global $wgLanguageCode, $wgContLanguageCode;
+ global $parserMemc, $wgEnableSidebarCache;
+ global $wgLang, $wgContLang;
$fname = 'SkinTemplate::buildSidebar';
wfProfileIn( $fname );
- $key = "{$wgDBname}:sidebar";
+ $key = wfMemcKey( 'sidebar' );
$cacheSidebar = $wgEnableSidebarCache &&
- ($wgLanguageCode == $wgContLanguageCode);
+ ($wgLang->getCode() == $wgContLang->getCode());
if ($cacheSidebar) {
$cachedsidebar = $parserMemc->get( $key );
@@ -1480,7 +1547,7 @@ END;
$text = $line[1];
if (wfEmptyMsg($line[0], $link))
$link = $line[0];
- $href = $this->makeInternalOrExternalUrl( $link );
+ $href = self::makeInternalOrExternalUrl( $link );
$bar[$heading][] = array(
'text' => $text,
'href' => $href,
diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php
index 6657d381..482680e6 100644
--- a/includes/SkinTemplate.php
+++ b/includes/SkinTemplate.php
@@ -31,8 +31,6 @@ if ( ! defined( 'MEDIAWIKI' ) )
* @subpackage Skins
*/
-require_once 'GlobalFunctions.php';
-
/**
* Wrapper object for MediaWiki's localization functions,
* to be passed to the template engine.
@@ -140,7 +138,7 @@ class SkinTemplate extends Skin {
global $wgMaxCredits, $wgShowCreditsIfMax;
global $wgPageShowWatchingUsers;
global $wgUseTrackbacks;
- global $wgDBname;
+ global $wgArticlePath, $wgScriptPath, $wgServer, $wgLang, $wgCanonicalNamespaceNames;
$fname = 'SkinTemplate::outputPage';
wfProfileIn( $fname );
@@ -175,11 +173,11 @@ class SkinTemplate extends Skin {
$this->userpage = $userPage->getPrefixedText();
if ( $wgUser->isLoggedIn() || $this->showIPinHeader() ) {
- $this->userpageUrlDetails = $this->makeUrlDetails($this->userpage);
+ $this->userpageUrlDetails = self::makeUrlDetails( $this->userpage );
} else {
# This won't be used in the standard skins, but we define it to preserve the interface
# To save time, we check for existence
- $this->userpageUrlDetails = $this->makeKnownUrlDetails($this->userpage);
+ $this->userpageUrlDetails = self::makeKnownUrlDetails( $this->userpage );
}
$this->usercss = $this->userjs = $this->userjsprev = false;
@@ -193,6 +191,16 @@ class SkinTemplate extends Skin {
$tpl->set( 'pagetitle', $wgOut->getHTMLTitle() );
$tpl->set( 'displaytitle', $wgOut->mPageLinkTitle );
+ $nsname = @$wgCanonicalNamespaceNames[ $this->mTitle->getNamespace() ];
+ if ( $nsname === NULL ) $nsname = $this->mTitle->getNsText();
+
+ $tpl->set( 'nscanonical', $nsname );
+ $tpl->set( 'nsnumber', $this->mTitle->getNamespace() );
+ $tpl->set( 'titleprefixeddbkey', $this->mTitle->getPrefixedDBKey() );
+ $tpl->set( 'titletext', $this->mTitle->getText() );
+ $tpl->set( 'articleid', $this->mTitle->getArticleId() );
+ $tpl->set( 'isarticle', $wgOut->isArticle() );
+
$tpl->setRef( "thispage", $this->thispage );
$subpagestr = $this->subPageSubtitle();
$tpl->set(
@@ -230,6 +238,7 @@ class SkinTemplate extends Skin {
$tpl->set('headscripts', $out->getScript() );
$tpl->setRef( 'wgScript', $wgScript );
$tpl->setRef( 'skinname', $this->skinname );
+ $tpl->set( 'skinclass', get_class( $this ) );
$tpl->setRef( 'stylename', $this->stylename );
$tpl->set( 'printable', $wgRequest->getBool( 'printable' ) );
$tpl->setRef( 'loggedin', $this->loggedin );
@@ -245,15 +254,19 @@ class SkinTemplate extends Skin {
$tpl->set( 'searchaction', $this->escapeSearchLink() );
$tpl->set( 'search', trim( $wgRequest->getVal( 'search' ) ) );
$tpl->setRef( 'stylepath', $wgStylePath );
+ $tpl->setRef( 'articlepath', $wgArticlePath );
+ $tpl->setRef( 'scriptpath', $wgScriptPath );
+ $tpl->setRef( 'serverurl', $wgServer );
$tpl->setRef( 'logopath', $wgLogo );
$tpl->setRef( "lang", $wgContLanguageCode );
$tpl->set( 'dir', $wgContLang->isRTL() ? "rtl" : "ltr" );
$tpl->set( 'rtl', $wgContLang->isRTL() );
$tpl->set( 'langname', $wgContLang->getLanguageName( $wgContLanguageCode ) );
$tpl->set( 'showjumplinks', $wgUser->getOption( 'showjumplinks' ) );
- $tpl->setRef( 'username', $this->username );
+ $tpl->set( 'username', $wgUser->isAnon() ? NULL : $this->username );
$tpl->setRef( 'userpage', $this->userpage);
$tpl->setRef( 'userpageurl', $this->userpageUrlDetails['href']);
+ $tpl->set( 'userlang', $wgLang->getCode() );
$tpl->set( 'pagecss', $this->setupPageCss() );
$tpl->setRef( 'usercss', $this->usercss);
$tpl->setRef( 'userjs', $this->userjs);
@@ -261,16 +274,16 @@ class SkinTemplate extends Skin {
global $wgUseSiteJs;
if ($wgUseSiteJs) {
if($this->loggedin) {
- $tpl->set( 'jsvarurl', $this->makeUrl('-','action=raw&smaxage=0&gen=js') );
+ $tpl->set( 'jsvarurl', self::makeUrl('-','action=raw&smaxage=0&gen=js') );
} else {
- $tpl->set( 'jsvarurl', $this->makeUrl('-','action=raw&gen=js') );
+ $tpl->set( 'jsvarurl', self::makeUrl('-','action=raw&gen=js') );
}
} else {
$tpl->set('jsvarurl', false);
}
$newtalks = $wgUser->getNewMessageLinks();
- if (count($newtalks) == 1 && $newtalks[0]["wiki"] === $wgDBname) {
+ if (count($newtalks) == 1 && $newtalks[0]["wiki"] === wfWikiID() ) {
$usertitle = $this->mUser->getUserPage();
$usertalktitle = $usertitle->getTalkPage();
if( !$usertalktitle->equals( $this->mTitle ) ) {
@@ -308,7 +321,9 @@ class SkinTemplate extends Skin {
$tpl->setRef( 'newtalk', $ntl );
$tpl->setRef( 'skin', $this);
$tpl->set( 'logo', $this->logoText() );
- if ( $wgOut->isArticle() and (!isset( $oldid ) or isset( $diff )) and 0 != $wgArticle->getID() ) {
+ if ( $wgOut->isArticle() and (!isset( $oldid ) or isset( $diff )) and
+ $wgArticle and 0 != $wgArticle->getID() )
+ {
if ( !$wgDisableCounters ) {
$viewcount = $wgLang->formatNum( $wgArticle->getCount() );
if ( $viewcount ) {
@@ -376,6 +391,7 @@ class SkinTemplate extends Skin {
$tpl->setRef( 'debug', $out->mDebugtext );
$tpl->set( 'reporttime', $out->reportTime() );
$tpl->set( 'sitenotice', wfGetSiteNotice() );
+ $tpl->set( 'bottomscripts', $this->bottomScripts() );
$printfooter = "<div class=\"printfooter\">\n" . $this->printSource() . "</div>\n";
$out->mBodytext .= $printfooter ;
@@ -474,27 +490,27 @@ class SkinTemplate extends Skin {
'class' => $usertalkUrlDetails['exists']?false:'new',
'active' => ( $usertalkUrlDetails['href'] == $pageurl )
);
- $href = $this->makeSpecialUrl('Preferences');
+ $href = self::makeSpecialUrl( 'Preferences' );
$personal_urls['preferences'] = array(
- 'text' => wfMsg('preferences'),
- 'href' => $this->makeSpecialUrl('Preferences'),
+ 'text' => wfMsg( 'mypreferences' ),
+ 'href' => self::makeSpecialUrl( 'Preferences' ),
'active' => ( $href == $pageurl )
);
- $href = $this->makeSpecialUrl('Watchlist');
+ $href = self::makeSpecialUrl( 'Watchlist' );
$personal_urls['watchlist'] = array(
- 'text' => wfMsg('watchlist'),
+ 'text' => wfMsg( 'watchlist' ),
'href' => $href,
'active' => ( $href == $pageurl )
);
- $href = $this->makeSpecialUrl("Contributions/$this->username");
+ $href = self::makeSpecialUrl( "Contributions/$this->username" );
$personal_urls['mycontris'] = array(
- 'text' => wfMsg('mycontris'),
+ 'text' => wfMsg( 'mycontris' ),
'href' => $href
# FIXME # 'active' => ( $href == $pageurl . '/' . $this->username )
);
$personal_urls['logout'] = array(
- 'text' => wfMsg('userlogout'),
- 'href' => $this->makeSpecialUrl( 'Userlogout',
+ 'text' => wfMsg( 'userlogout' ),
+ 'href' => self::makeSpecialUrl( 'Userlogout',
$wgTitle->getNamespace() === NS_SPECIAL && $wgTitle->getText() === 'Preferences' ? '' : "returnto={$this->thisurl}"
)
);
@@ -517,14 +533,14 @@ class SkinTemplate extends Skin {
);
$personal_urls['anonlogin'] = array(
'text' => wfMsg('userlogin'),
- 'href' => $this->makeSpecialUrl('Userlogin', 'returnto=' . $this->thisurl ),
+ 'href' => self::makeSpecialUrl( 'Userlogin', 'returnto=' . $this->thisurl ),
'active' => ( NS_SPECIAL == $wgTitle->getNamespace() && 'Userlogin' == $wgTitle->getDBkey() )
);
} else {
$personal_urls['login'] = array(
'text' => wfMsg('userlogin'),
- 'href' => $this->makeSpecialUrl('Userlogin', 'returnto=' . $this->thisurl ),
+ 'href' => self::makeSpecialUrl( 'Userlogin', 'returnto=' . $this->thisurl ),
'active' => ( NS_SPECIAL == $wgTitle->getNamespace() && 'Userlogin' == $wgTitle->getDBkey() )
);
}
@@ -554,9 +570,9 @@ class SkinTemplate extends Skin {
}
$text = wfMsg( $message );
- if ( $text == "&lt;$message&gt;" ) {
+ if ( wfEmptyMsg( $message, $text ) ) {
global $wgContLang;
- $text = $wgContLang->getNsText( Namespace::getSubject( $title->getNamespace() ) );
+ $text = $wgContLang->getFormattedNsText( Namespace::getSubject( $title->getNamespace() ) );
}
return array(
@@ -565,23 +581,23 @@ class SkinTemplate extends Skin {
'href' => $title->getLocalUrl( $query ) );
}
- function makeTalkUrlDetails( $name, $urlaction='' ) {
+ function makeTalkUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
$title = $title->getTalkPage();
- $this->checkTitle($title, $name);
+ self::checkTitle( $title, $name );
return array(
'href' => $title->getLocalURL( $urlaction ),
- 'exists' => $title->getArticleID() != 0?true:false
+ 'exists' => $title->getArticleID() != 0 ? true : false
);
}
- function makeArticleUrlDetails( $name, $urlaction='' ) {
+ function makeArticleUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
$title= $title->getSubjectPage();
- $this->checkTitle($title, $name);
+ self::checkTitle( $title, $name );
return array(
'href' => $title->getLocalURL( $urlaction ),
- 'exists' => $title->getArticleID() != 0?true:false
+ 'exists' => $title->getArticleID() != 0 ? true : false
);
}
@@ -696,7 +712,7 @@ class SkinTemplate extends Skin {
'class' => false,
'text' => wfMsgExt( 'undelete_short', array( 'parsemag' ), $n ),
'href' => $undelTitle->getLocalUrl( 'target=' . urlencode( $this->thispage ) )
- #'href' => $this->makeSpecialUrl("Undelete/$this->thispage")
+ #'href' => self::makeSpecialUrl( "Undelete/$this->thispage" )
);
}
}
@@ -782,26 +798,26 @@ class SkinTemplate extends Skin {
$diff = $wgRequest->getVal( 'diff' );
$nav_urls = array();
- $nav_urls['mainpage'] = array('href' => $this->makeI18nUrl('mainpage'));
+ $nav_urls['mainpage'] = array( 'href' => self::makeI18nUrl( 'mainpage') );
if( $wgEnableUploads ) {
if ($wgUploadNavigationUrl) {
- $nav_urls['upload'] = array('href' => $wgUploadNavigationUrl );
+ $nav_urls['upload'] = array( 'href' => $wgUploadNavigationUrl );
} else {
- $nav_urls['upload'] = array('href' => $this->makeSpecialUrl('Upload'));
+ $nav_urls['upload'] = array( 'href' => self::makeSpecialUrl( 'Upload' ) );
}
} else {
if ($wgUploadNavigationUrl)
- $nav_urls['upload'] = array('href' => $wgUploadNavigationUrl );
+ $nav_urls['upload'] = array( 'href' => $wgUploadNavigationUrl );
else
$nav_urls['upload'] = false;
}
- $nav_urls['specialpages'] = array('href' => $this->makeSpecialUrl('Specialpages'));
+ $nav_urls['specialpages'] = array( 'href' => self::makeSpecialUrl( 'Specialpages' ) );
// A print stylesheet is attached to all pages, but nobody ever
// figures that out. :) Add a link...
if( $this->iscontent && ($action == '' || $action == 'view' || $action == 'purge' ) ) {
- $revid = $wgArticle->getLatest();
+ $revid = $wgArticle ? $wgArticle->getLatest() : 0;
if ( !( $revid == 0 ) )
$nav_urls['print'] = array(
'text' => wfMsg( 'printableversion' ),
@@ -852,11 +868,11 @@ class SkinTemplate extends Skin {
if($id || $ip) { # both anons and non-anons have contri list
$nav_urls['contributions'] = array(
- 'href' => $this->makeSpecialUrl('Contributions/' . $this->mTitle->getText() )
+ 'href' => self::makeSpecialUrl( 'Contributions/' . $this->mTitle->getText() )
);
if ( $wgUser->isAllowed( 'block' ) )
$nav_urls['blockip'] = array(
- 'href' => $this->makeSpecialUrl( 'Blockip/' . $this->mTitle->getText() )
+ 'href' => self::makeSpecialUrl( 'Blockip/' . $this->mTitle->getText() )
);
} else {
$nav_urls['contributions'] = false;
@@ -864,7 +880,7 @@ class SkinTemplate extends Skin {
$nav_urls['emailuser'] = false;
if( $this->showEmailUser( $id ) ) {
$nav_urls['emailuser'] = array(
- 'href' => $this->makeSpecialUrl('Emailuser/' . $this->mTitle->getText() )
+ 'href' => self::makeSpecialUrl( 'Emailuser/' . $this->mTitle->getText() )
);
}
wfProfileOut( $fname );
@@ -892,6 +908,11 @@ class SkinTemplate extends Skin {
$sitecss = '';
$usercss = '';
$siteargs = '&maxage=' . $wgSquidMaxage;
+ if( $this->loggedin ) {
+ // Ensure that logged-in users' generated CSS isn't clobbered
+ // by anons' publicly cacheable generated CSS.
+ $siteargs .= '&smaxage=0';
+ }
# Add user-specific code if this is a user and we allow that kind of thing
@@ -904,7 +925,7 @@ class SkinTemplate extends Skin {
$usercss = $wgRequest->getText('wpTextbox1');
} else {
$usercss = '@import "' .
- $this->makeUrl($this->userpage . '/'.$this->skinname.'.css',
+ self::makeUrl($this->userpage . '/'.$this->skinname.'.css',
'action=raw&ctype=text/css') . '";' ."\n";
}
@@ -915,10 +936,10 @@ class SkinTemplate extends Skin {
# If we use the site's dynamic CSS, throw that in, too
if ( $wgUseSiteCss ) {
- $query = "action=raw&ctype=text/css&smaxage=$wgSquidMaxage";
- $sitecss .= '@import "' . $this->makeNSUrl('Common.css', $query, NS_MEDIAWIKI) . '";' . "\n";
- $sitecss .= '@import "' . $this->makeNSUrl(ucfirst($this->skinname) . '.css', $query, NS_MEDIAWIKI) . '";' . "\n";
- $sitecss .= '@import "' . $this->makeUrl('-','action=raw&gen=css' . $siteargs) . '";' . "\n";
+ $query = "usemsgcache=yes&action=raw&ctype=text/css&smaxage=$wgSquidMaxage";
+ $sitecss .= '@import "' . self::makeNSUrl( 'Common.css', $query, NS_MEDIAWIKI) . '";' . "\n";
+ $sitecss .= '@import "' . self::makeNSUrl( ucfirst( $this->skinname ) . '.css', $query, NS_MEDIAWIKI ) . '";' . "\n";
+ $sitecss .= '@import "' . self::makeUrl( '-', 'action=raw&gen=css' . $siteargs ) . '";' . "\n";
}
# If we use any dynamic CSS, make a little CDATA block out of it.
@@ -944,7 +965,7 @@ class SkinTemplate extends Skin {
# XXX: additional security check/prompt?
$this->userjsprev = '/*<![CDATA[*/ ' . $wgRequest->getText('wpTextbox1') . ' /*]]>*/';
} else {
- $this->userjs = $this->makeUrl($this->userpage.'/'.$this->skinname.'.js', 'action=raw&ctype='.$wgJsMimeType.'&dontcountme=s');
+ $this->userjs = self::makeUrl($this->userpage.'/'.$this->skinname.'.js', 'action=raw&ctype='.$wgJsMimeType.'&dontcountme=s');
}
}
wfProfileOut( $fname );
@@ -996,8 +1017,8 @@ class SkinTemplate extends Skin {
// avoid inclusion of non defined user JavaScript (with custom skins only)
// by checking for default message content
$msgKey = ucfirst($this->skinname).'.js';
- $userJS = wfMsg($msgKey);
- if ('&lt;'.$msgKey.'&gt;' != $userJS) {
+ $userJS = wfMsgForContent($msgKey);
+ if ( !wfEmptyMsg( $msgKey, $userJS ) ) {
$s .= $userJS;
}
@@ -1060,6 +1081,13 @@ class QuickTemplate {
/**
* @private
*/
+ function jstext( $str ) {
+ echo Xml::escapeJsString( $this->data[$str] );
+ }
+
+ /**
+ * @private
+ */
function html( $str ) {
echo $this->data[$str];
}
@@ -1087,7 +1115,7 @@ class QuickTemplate {
$text = $this->translator->translate( $str );
$parserOutput = $wgParser->parse( $text, $wgTitle,
- $wgOut->mParserOptions, true );
+ $wgOut->parserOptions(), true );
echo $parserOutput->getText();
}
diff --git a/includes/SpecialAllmessages.php b/includes/SpecialAllmessages.php
index 60258f9e..6e3f6588 100644
--- a/includes/SpecialAllmessages.php
+++ b/includes/SpecialAllmessages.php
@@ -9,7 +9,7 @@
*
*/
function wfSpecialAllmessages() {
- global $wgOut, $wgAllMessagesEn, $wgRequest, $wgMessageCache, $wgTitle;
+ global $wgOut, $wgRequest, $wgMessageCache, $wgTitle;
global $wgUseDatabaseMessages;
# The page isn't much use if the MediaWiki namespace is not being used
@@ -27,16 +27,16 @@ function wfSpecialAllmessages() {
$navText = wfMsg( 'allmessagestext' );
# Make sure all extension messages are available
- wfLoadAllExtensions();
+ MessageCache::loadAllMessages();
$first = true;
- $sortedArray = array_merge( $wgAllMessagesEn, $wgMessageCache->mExtensionMessages );
+ $sortedArray = array_merge( Language::getMessagesFor( 'en' ), $wgMessageCache->getExtensionMessagesFor( 'en' ) );
ksort( $sortedArray );
$messages = array();
$wgMessageCache->disableTransform();
foreach ( $sortedArray as $key => $value ) {
- $messages[$key]['enmsg'] = is_array( $value ) ? $value['en'] : $value;
+ $messages[$key]['enmsg'] = $value;
$messages[$key]['statmsg'] = wfMsgNoDb( $key );
$messages[$key]['msg'] = wfMsg ( $key );
}
@@ -62,10 +62,10 @@ function wfSpecialAllmessages() {
*
*/
function makePhp($messages) {
- global $wgLanguageCode;
- $txt = "\n\n".'$wgAllMessages'.ucfirst($wgLanguageCode).' = array('."\n";
+ global $wgLang;
+ $txt = "\n\n\$messages = array(\n";
foreach( $messages as $key => $m ) {
- if(strtolower($wgLanguageCode) != 'en' and $m['msg'] == $m['enmsg'] ) {
+ if($wgLang->getCode() != 'en' and $m['msg'] == $m['enmsg'] ) {
//if (strstr($m['msg'],"\n")) {
// $txt.='/* ';
// $comment=' */';
@@ -74,7 +74,7 @@ function makePhp($messages) {
// $comment = '';
//}
continue;
- } elseif ($m['msg'] == '&lt;'.$key.'&gt;'){
+ } elseif ( wfEmptyMsg( $key, $m['msg'] ) ) {
$m['msg'] = '';
$comment = ' #empty';
} else {
@@ -90,7 +90,7 @@ function makePhp($messages) {
*
*/
function makeHTMLText( $messages ) {
- global $wgLang, $wgUser, $wgLanguageCode, $wgContLanguageCode;
+ global $wgLang, $wgContLang, $wgUser;
$fname = "makeHTMLText";
wfProfileIn( $fname );
@@ -148,8 +148,8 @@ function makeHTMLText( $messages ) {
foreach( $messages as $key => $m ) {
$title = $wgLang->ucfirst( $key );
- if($wgLanguageCode != $wgContLanguageCode)
- $title.="/$wgLanguageCode";
+ if($wgLang->getCode() != $wgContLang->getCode())
+ $title.= '/' . $wgLang->getCode();
$titleObj =& Title::makeTitle( NS_MEDIAWIKI, $title );
$talkPage =& Title::makeTitle( NS_MEDIAWIKI_TALK, $title );
diff --git a/includes/SpecialAllpages.php b/includes/SpecialAllpages.php
index 53a5b348..345c48e6 100644
--- a/includes/SpecialAllpages.php
+++ b/includes/SpecialAllpages.php
@@ -91,15 +91,11 @@ function showToplevel ( $namespace = NS_MAIN, $including = false ) {
# in the querycache table.
$dbr =& wfGetDB( DB_SLAVE );
- $page = $dbr->tableName( 'page' );
- $fromwhere = "FROM $page WHERE page_namespace=$namespace";
- $order_arr = array ( 'ORDER BY' => 'page_title' );
- $order_str = 'ORDER BY page_title';
$out = "";
$where = array( 'page_namespace' => $namespace );
- global $wgMemc, $wgDBname;
- $key = "$wgDBname:allpages:ns:$namespace";
+ global $wgMemc;
+ $key = wfMemcKey( 'allpages', 'ns', $namespace );
$lines = $wgMemc->get( $key );
if( !is_array( $lines ) ) {
@@ -280,11 +276,9 @@ function showChunk( $namespace = NS_MAIN, $from, $including = false ) {
$sk->makeKnownLink( $wgContLang->specialPage( "Allpages" ),
wfMsgHtml ( 'allpages' ) );
if ( isset($dbr) && $dbr && ($n == $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) {
- $namespaceparam = $namespace ? "&namespace=$namespace" : "";
- $out2 .= " | " . $sk->makeKnownLink(
- $wgContLang->specialPage( "Allpages" ),
- wfMsgHtml ( 'nextpage', $s->page_title ),
- "from=" . wfUrlEncode ( $s->page_title ) . $namespaceparam );
+ $self = Title::makeTitle( NS_SPECIAL, 'Allpages' );
+ $q = 'from=' . $t->getPartialUrl() . ( $namespace ? '&namespace=' . $namespace : '' );
+ $out2 .= ' | ' . $sk->makeKnownLinkObj( $self, wfMsgHtml( 'nextpage', $t->getText() ), $q );
}
$out2 .= "</td></tr></table><hr />";
}
diff --git a/includes/SpecialBlockip.php b/includes/SpecialBlockip.php
index b3f67ab1..4eb4957a 100644
--- a/includes/SpecialBlockip.php
+++ b/includes/SpecialBlockip.php
@@ -46,6 +46,15 @@ class IPBlockForm {
$this->BlockReason = $wgRequest->getText( 'wpBlockReason' );
$this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg('ipbotheroption') );
$this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' );
+ $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly' );
+
+ # Unchecked checkboxes are not included in the form data at all, so having one
+ # that is true by default is a bit tricky
+ if ( $wgRequest->wasPosted() ) {
+ $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', false );
+ } else {
+ $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', true );
+ }
}
function showForm( $err ) {
@@ -102,7 +111,7 @@ class IPBlockForm {
<tr>
<td align=\"right\">{$mIpaddress}:</td>
<td align=\"left\">
- <input tabindex='1' type='text' size='20' name=\"wpBlockAddress\" value=\"{$scBlockAddress}\" />
+ <input tabindex='1' type='text' size='40' name=\"wpBlockAddress\" value=\"{$scBlockAddress}\" />
</td>
</tr>
<tr>");
@@ -133,13 +142,36 @@ class IPBlockForm {
<tr>
<td>&nbsp;</td>
<td align=\"left\">
- <input tabindex='4' type='submit' name=\"wpBlock\" value=\"{$mIpbsubmit}\" />
+ " . wfCheckLabel( wfMsg( 'ipbanononly' ),
+ 'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly,
+ array( 'tabindex' => 4 ) ) . "
+ </td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td align=\"left\">
+ " . wfCheckLabel( wfMsg( 'ipbcreateaccount' ),
+ 'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount,
+ array( 'tabindex' => 5 ) ) . "
+ </td>
+ </tr>
+ <tr>
+ <td style='padding-top: 1em'>&nbsp;</td>
+ <td style='padding-top: 1em' align=\"left\">
+ <input tabindex='5' type='submit' name=\"wpBlock\" value=\"{$mIpbsubmit}\" />
</td>
</tr>
</table>
<input type='hidden' name='wpEditToken' value=\"{$token}\" />
</form>\n" );
+ $user = User::newFromName( $this->BlockAddress );
+ if( is_object( $user ) ) {
+ $this->showLogFragment( $wgOut, $user->getUserPage() );
+ } elseif( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->BlockAddress ) ) {
+ $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) );
+ }
+
}
function doSubmit() {
@@ -166,8 +198,12 @@ class IPBlockForm {
} else {
# Username block
if ( $wgSysopUserBans ) {
- $userId = User::idFromName( $this->BlockAddress );
- if ( $userId == 0 ) {
+ $user = User::newFromName( $this->BlockAddress );
+ if( !is_null( $user ) && $user->getID() ) {
+ # Use canonical name
+ $this->BlockAddress = $user->getName();
+ $userId = $user->getID();
+ } else {
$this->showForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->BlockAddress ) ) );
return;
}
@@ -188,7 +224,7 @@ class IPBlockForm {
}
if ( $expirestr == 'infinite' || $expirestr == 'indefinite' ) {
- $expiry = '';
+ $expiry = Block::infinity();
} else {
# Convert GNU-style date, on error returns -1 for PHP <5.1 and false for PHP >=5.1
$expiry = strtotime( $expirestr );
@@ -199,20 +235,24 @@ class IPBlockForm {
}
$expiry = wfTimestamp( TS_MW, $expiry );
-
}
# Create block
# Note: for a user block, ipb_address is only for display purposes
- $ban = new Block( $this->BlockAddress, $userId, $wgUser->getID(),
- $this->BlockReason, wfTimestampNow(), 0, $expiry );
+ $block = new Block( $this->BlockAddress, $userId, $wgUser->getID(),
+ $this->BlockReason, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly,
+ $this->BlockCreateAccount );
- if (wfRunHooks('BlockIp', array(&$ban, &$wgUser))) {
+ if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) {
- $ban->insert();
+ if ( !$block->insert() ) {
+ $this->showForm( wfMsg( 'ipb_already_blocked',
+ htmlspecialchars( $this->BlockAddress ) ) );
+ return;
+ }
- wfRunHooks('BlockIpComplete', array($ban, $wgUser));
+ wfRunHooks('BlockIpComplete', array($block, $wgUser));
# Make log entry
$log = new LogPage( 'block' );
@@ -234,6 +274,14 @@ class IPBlockForm {
$text = wfMsg( 'blockipsuccesstext', $this->BlockAddress );
$wgOut->addWikiText( $text );
}
+
+ function showLogFragment( &$out, &$title ) {
+ $out->addHtml( wfElement( 'h2', NULL, LogPage::logName( 'block' ) ) );
+ $request = new FauxRequest( array( 'page' => $title->getPrefixedText(), 'type' => 'block' ) );
+ $viewer = new LogViewer( new LogReader( $request ) );
+ $viewer->showList( $out );
+ }
+
}
?>
diff --git a/includes/SpecialBrokenRedirects.php b/includes/SpecialBrokenRedirects.php
index e5c2dd8e..653e13e2 100644
--- a/includes/SpecialBrokenRedirects.php
+++ b/includes/SpecialBrokenRedirects.php
@@ -68,7 +68,7 @@ class BrokenRedirectsPage extends PageQueryPage {
$from = $skin->makeKnownLinkObj( $fromObj ,'', 'redirect=no' );
$edit = $skin->makeBrokenLinkObj( $fromObj , "(".wfMsg("qbedit").")" , 'redirect=no');
$to = $skin->makeBrokenLinkObj( $toObj );
- $arr = $wgContLang->isRTL() ? '&larr;' : '&rarr;';
+ $arr = $wgContLang->getArrow();
return "$from $edit $arr $to";
}
diff --git a/includes/SpecialCategories.php b/includes/SpecialCategories.php
index 8a6dd5ff..89cff20a 100644
--- a/includes/SpecialCategories.php
+++ b/includes/SpecialCategories.php
@@ -36,7 +36,7 @@ class CategoriesPage extends QueryPage {
1 as value,
COUNT(*) as count
FROM $categorylinks
- GROUP BY cl_to";
+ GROUP BY 1,2,3,4";
return $s;
}
diff --git a/includes/SpecialConfirmemail.php b/includes/SpecialConfirmemail.php
index fd0425a8..72567609 100644
--- a/includes/SpecialConfirmemail.php
+++ b/includes/SpecialConfirmemail.php
@@ -30,7 +30,11 @@ class EmailConfirmation extends SpecialPage {
global $wgUser, $wgOut;
if( empty( $code ) ) {
if( $wgUser->isLoggedIn() ) {
- $this->showRequestForm();
+ if( User::isValidEmailAddr( $wgUser->getEmail() ) ) {
+ $this->showRequestForm();
+ } else {
+ $wgOut->addWikiText( wfMsg( 'confirmemail_noemail' ) );
+ }
} else {
$title = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
$self = Title::makeTitle( NS_SPECIAL, 'Confirmemail' );
diff --git a/includes/SpecialDeadendpages.php b/includes/SpecialDeadendpages.php
index 3f4a0519..b319a170 100644
--- a/includes/SpecialDeadendpages.php
+++ b/includes/SpecialDeadendpages.php
@@ -16,6 +16,10 @@ class DeadendPagesPage extends PageQueryPage {
return "Deadendpages";
}
+ function getPageHeader() {
+ return '<p>' . wfMsg('deadendpagestext') . '</p>';
+ }
+
/**
* LEFT JOIN is expensive
*
diff --git a/includes/SpecialDisambiguations.php b/includes/SpecialDisambiguations.php
index 1a0297af..0355c85b 100644
--- a/includes/SpecialDisambiguations.php
+++ b/includes/SpecialDisambiguations.php
@@ -19,37 +19,69 @@ class DisambiguationsPage extends PageQueryPage {
function isExpensive( ) { return true; }
function isSyndicated() { return false; }
+ function getDisambiguationPageObj() {
+ return Title::makeTitleSafe( NS_MEDIAWIKI, 'disambiguationspage');
+ }
+
function getPageHeader( ) {
global $wgUser;
$sk = $wgUser->getSkin();
- #FIXME : probably need to add a backlink to the maintenance page.
- return '<p>'.wfMsg('disambiguationstext', $sk->makeKnownLink(wfMsgForContent('disambiguationspage')) )."</p><br />\n";
+ return '<p>'.wfMsg('disambiguationstext', $sk->makeKnownLinkObj($this->getDisambiguationPageObj()))."</p><br />\n";
}
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
extract( $dbr->tableNames( 'page', 'pagelinks', 'templatelinks' ) );
- $dp = Title::newFromText(wfMsgForContent('disambiguationspage'));
- $id = $dp->getArticleId();
- $dns = $dp->getNamespace();
- $dtitle = $dbr->addQuotes( $dp->getDBkey() );
-
- if($dns != NS_TEMPLATE) {
- # FIXME we assume the disambiguation message is a template but
- # the page can potentially be from another namespace :/
- wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n");
- }
-
- $sql = "SELECT 'Disambiguations' AS \"type\", pa.page_namespace AS namespace,"
- ." pa.page_title AS title, la.pl_from AS value"
- ." FROM {$templatelinks} AS lb, {$page} AS pa, {$pagelinks} AS la"
- ." WHERE lb.tl_namespace = $dns AND lb.tl_title = $dtitle" # disambiguation template
- .' AND pa.page_id = lb.tl_from'
- .' AND pa.page_namespace = la.pl_namespace'
- .' AND pa.page_title = la.pl_title';
- return $sql;
+ $dMsgText = wfMsgForContent('disambiguationspage');
+
+ $linkBatch = new LinkBatch;
+
+ # If the text can be treated as a title, use it verbatim.
+ # Otherwise, pull the titles from the links table
+ $dp = Title::newFromText($dMsgText);
+ if( $dp ) {
+ if($dp->getNamespace() != NS_TEMPLATE) {
+ # FIXME we assume the disambiguation message is a template but
+ # the page can potentially be from another namespace :/
+ wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n");
+ }
+ $linkBatch->addObj( $dp );
+ } else {
+ # Get all the templates linked from the Mediawiki:Disambiguationspage
+ $disPageObj = $this->getDisambiguationPageObj();
+ $res = $dbr->select(
+ array('pagelinks', 'page'),
+ 'pl_title',
+ array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE,
+ 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()),
+ 'DisambiguationsPage::getSQL' );
+
+ while ( $row = $dbr->fetchObject( $res ) ) {
+ $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title ));
+ }
+ $dbr->freeResult( $res );
+ }
+
+ $set = $linkBatch->constructSet( 'lb.tl', $dbr );
+ if( $set === false ) {
+ $set = 'FALSE'; # We must always return a valid sql query, but this way DB will always quicly return an empty result
+ wfDebug("Mediawiki:disambiguationspage message does not link to any templates!\n");
+ }
+
+ $sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace,"
+ ." pb.page_title AS title, la.pl_from AS value"
+ ." FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa"
+ ." WHERE $set" # disambiguation template(s)
+ .' AND pa.page_id = la.pl_from'
+ .' AND pa.page_namespace = ' . NS_MAIN # Limit to just articles in the main namespace
+ .' AND pb.page_id = lb.tl_from'
+ .' AND pb.page_namespace = la.pl_namespace'
+ .' AND pb.page_title = la.pl_title'
+ .' ORDER BY lb.tl_namespace, lb.tl_title';
+
+ return $sql;
}
function getOrder() {
@@ -57,14 +89,16 @@ class DisambiguationsPage extends PageQueryPage {
}
function formatResult( $skin, $result ) {
+ global $wgContLang;
$title = Title::newFromId( $result->value );
$dp = Title::makeTitle( $result->namespace, $result->title );
$from = $skin->makeKnownLinkObj( $title,'');
$edit = $skin->makeBrokenLinkObj( $title, "(".wfMsg("qbedit").")" , 'redirect=no');
+ $arr = $wgContLang->getArrow();
$to = $skin->makeKnownLinkObj( $dp,'');
- return "$from $edit => $to";
+ return "$from $edit $arr $to";
}
}
diff --git a/includes/SpecialDoubleRedirects.php b/includes/SpecialDoubleRedirects.php
index fe480f60..fe42b00a 100644
--- a/includes/SpecialDoubleRedirects.php
+++ b/includes/SpecialDoubleRedirects.php
@@ -87,7 +87,7 @@ class DoubleRedirectsPage extends PageQueryPage {
$edit = $skin->makeBrokenLinkObj( $titleA, "(".wfMsg("qbedit").")" , 'redirect=no');
$linkB = $skin->makeKnownLinkObj( $titleB, '', 'redirect=no' );
$linkC = $skin->makeKnownLinkObj( $titleC );
- $arr = $wgContLang->isRTL() ? '&larr;' : '&rarr;';
+ $arr = $wgContLang->getArrow() . $wgContLang->getDirMark();
return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
}
diff --git a/includes/SpecialEmailuser.php b/includes/SpecialEmailuser.php
index c66389e1..d711947f 100644
--- a/includes/SpecialEmailuser.php
+++ b/includes/SpecialEmailuser.php
@@ -49,7 +49,7 @@ function wfSpecialEmailuser( $par ) {
$f = new EmailUserForm( $nu );
if ( "success" == $action ) {
- $f->showSuccess();
+ $f->showSuccess( $nu );
} else if ( "submit" == $action && $wgRequest->wasPosted() &&
$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
$f->doSubmit();
@@ -148,13 +148,13 @@ class EmailUserForm {
}
}
- function showSuccess() {
+ function showSuccess( &$user ) {
global $wgOut;
$wgOut->setPagetitle( wfMsg( "emailsent" ) );
$wgOut->addHTML( wfMsg( "emailsenttext" ) );
- $wgOut->returnToMain( false );
+ $wgOut->returnToMain( false, $user->getUserPage() );
}
}
?>
diff --git a/includes/SpecialExport.php b/includes/SpecialExport.php
index 73dcbcd5..dc52e00b 100644
--- a/includes/SpecialExport.php
+++ b/includes/SpecialExport.php
@@ -22,9 +22,6 @@
* @subpackage SpecialPage
*/
-/** */
-require_once( 'Export.php' );
-
/**
*
*/
@@ -33,16 +30,54 @@ function wfSpecialExport( $page = '' ) {
global $wgExportAllowHistory, $wgExportMaxHistory;
$curonly = true;
- if( $wgRequest->getVal( 'action' ) == 'submit') {
+ $fullHistory = array(
+ 'dir' => 'asc',
+ 'offset' => false,
+ 'limit' => $wgExportMaxHistory,
+ );
+ if( $wgRequest->wasPosted() ) {
$page = $wgRequest->getText( 'pages' );
$curonly = $wgRequest->getCheck( 'curonly' );
- }
- if( $wgRequest->getCheck( 'history' ) ) {
- $curonly = false;
+ $rawOffset = $wgRequest->getVal( 'offset' );
+ if( $rawOffset ) {
+ $offset = wfTimestamp( TS_MW, $rawOffset );
+ } else {
+ $offset = null;
+ }
+ $limit = $wgRequest->getInt( 'limit' );
+ $dir = $wgRequest->getVal( 'dir' );
+ $history = array(
+ 'dir' => 'asc',
+ 'offset' => false,
+ 'limit' => $wgExportMaxHistory,
+ );
+ $historyCheck = $wgRequest->getCheck( 'history' );
+ if ( $curonly ) {
+ $history = WikiExporter::CURRENT;
+ } elseif ( !$historyCheck ) {
+ if ( $limit > 0 && $limit < $wgExportMaxHistory ) {
+ $history['limit'] = $limit;
+ }
+ if ( !is_null( $offset ) ) {
+ $history['offset'] = $offset;
+ }
+ if ( strtolower( $dir ) == 'desc' ) {
+ $history['dir'] = 'desc';
+ }
+ }
+ } else {
+ // Default to current-only for GET requests
+ $page = $wgRequest->getText( 'pages', $page );
+ $historyCheck = $wgRequest->getCheck( 'history' );
+ if( $historyCheck ) {
+ $history = WikiExporter::FULL;
+ } else {
+ $history = WikiExporter::CURRENT;
+ }
}
if( !$wgExportAllowHistory ) {
// Override
- $curonly = true;
+ $history = WikiExporter::CURRENT;
}
$list_authors = $wgRequest->getCheck( 'listauthors' );
@@ -63,12 +98,12 @@ function wfSpecialExport( $page = '' ) {
$pages = explode( "\n", $page );
$db =& wfGetDB( DB_SLAVE );
- $history = $curonly ? MW_EXPORT_CURRENT : MW_EXPORT_FULL;
$exporter = new WikiExporter( $db, $history );
$exporter->list_authors = $list_authors ;
$exporter->openStream();
foreach( $pages as $page ) {
+ /*
if( $wgExportMaxHistory && !$curonly ) {
$title = Title::newFromText( $page );
if( $title ) {
@@ -79,7 +114,7 @@ function wfSpecialExport( $page = '' ) {
continue;
}
}
- }
+ }*/
$exporter->pageByName( $page );
}
diff --git a/includes/SpecialImagelist.php b/includes/SpecialImagelist.php
index e456abf5..54ee83e5 100644
--- a/includes/SpecialImagelist.php
+++ b/includes/SpecialImagelist.php
@@ -11,111 +11,159 @@
function wfSpecialImagelist() {
global $wgUser, $wgOut, $wgLang, $wgContLang, $wgRequest, $wgMiserMode;
- $sort = $wgRequest->getVal( 'sort' );
- $wpIlMatch = $wgRequest->getText( 'wpIlMatch' );
- $dbr =& wfGetDB( DB_SLAVE );
- $image = $dbr->tableName( 'image' );
- $sql = "SELECT img_size,img_name,img_user,img_user_text," .
- "img_description,img_timestamp FROM $image";
-
- if ( !$wgMiserMode && !empty( $wpIlMatch ) ) {
- $nt = Title::newFromUrl( $wpIlMatch );
- if($nt ) {
- $m = $dbr->strencode( strtolower( $nt->getDBkey() ) );
- $m = str_replace( "%", "\\%", $m );
- $m = str_replace( "_", "\\_", $m );
- $sql .= " WHERE LCASE(img_name) LIKE '%{$m}%'";
+ $pager = new ImageListPager;
+
+ $limit = $pager->getForm();
+ $body = $pager->getBody();
+ $nav = $pager->getNavigationBar();
+ $wgOut->addHTML( "
+ $limit
+ <br/>
+ $body
+ $nav" );
+}
+
+class ImageListPager extends TablePager {
+ var $mFieldNames = null;
+ var $mMessages = array();
+ var $mQueryConds = array();
+
+ function __construct() {
+ global $wgRequest, $wgMiserMode;
+ if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) {
+ $this->mDefaultDirection = true;
+ } else {
+ $this->mDefaultDirection = false;
+ }
+ $search = $wgRequest->getText( 'ilsearch' );
+ if ( $search != '' && !$wgMiserMode ) {
+ $nt = Title::newFromUrl( $search );
+ if( $nt ) {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $m = $dbr->strencode( strtolower( $nt->getDBkey() ) );
+ $m = str_replace( "%", "\\%", $m );
+ $m = str_replace( "_", "\\_", $m );
+ $this->mQueryConds = array( "LCASE(img_name) LIKE '%{$m}%'" );
+ }
}
- }
- if ( "bysize" == $sort ) {
- $sql .= " ORDER BY img_size DESC";
- } else if ( "byname" == $sort ) {
- $sql .= " ORDER BY img_name";
- } else {
- $sort = "bydate";
- $sql .= " ORDER BY img_timestamp DESC";
+ parent::__construct();
}
- list( $limit, $offset ) = wfCheckLimits( 50 );
- $lt = $wgLang->formatNum( "${limit}" );
- $sql .= " LIMIT {$limit}";
-
- $wgOut->addWikiText( wfMsg( 'imglegend' ) );
- $wgOut->addHTML( wfMsgExt( 'imagelisttext', array('parse'), $lt, wfMsg( $sort ) ) );
-
- $sk = $wgUser->getSkin();
- $titleObj = Title::makeTitle( NS_SPECIAL, "Imagelist" );
- $action = $titleObj->escapeLocalURL( "sort={$sort}&limit={$limit}" );
-
- if ( !$wgMiserMode ) {
- $wgOut->addHTML( "<form id=\"imagesearch\" method=\"post\" action=\"" .
- "{$action}\">" .
- wfElement( 'input',
- array(
- 'type' => 'text',
- 'size' => '20',
- 'name' => 'wpIlMatch',
- 'value' => $wpIlMatch, )) .
- wfElement( 'input',
- array(
- 'type' => 'submit',
- 'name' => 'wpIlSubmit',
- 'value' => wfMsg( 'ilsubmit'), )) .
- '</form>' );
+ function getFieldNames() {
+ if ( !$this->mFieldNames ) {
+ $this->mFieldNames = array(
+ 'links' => '',
+ 'img_timestamp' => wfMsg( 'imagelist_date' ),
+ 'img_name' => wfMsg( 'imagelist_name' ),
+ 'img_user_text' => wfMsg( 'imagelist_user' ),
+ 'img_size' => wfMsg( 'imagelist_size' ),
+ 'img_description' => wfMsg( 'imagelist_description' ),
+ );
+ }
+ return $this->mFieldNames;
}
- $here = Title::makeTitle( NS_SPECIAL, 'Imagelist' );
+ function isFieldSortable( $field ) {
+ static $sortable = array( 'img_timestamp', 'img_name', 'img_size' );
+ return in_array( $field, $sortable );
+ }
- foreach ( array( 'byname', 'bysize', 'bydate') as $sorttype ) {
- $urls = null;
- foreach ( array( 50, 100, 250, 500 ) as $num ) {
- $urls[] = $sk->makeKnownLinkObj( $here, $wgLang->formatNum( $num ),
- "sort={$sorttype}&limit={$num}&wpIlMatch=" . urlencode( $wpIlMatch ) );
- }
- $sortlinks[] = wfMsgExt(
- 'showlast',
- array( 'parseinline', 'replaceafter' ),
- implode($urls, ' | '),
- wfMsgExt( $sorttype, array('escape') )
+ function getQueryInfo() {
+ $fields = $this->getFieldNames();
+ unset( $fields['links'] );
+ $fields = array_keys( $fields );
+ $fields[] = 'img_user';
+ return array(
+ 'tables' => 'image',
+ 'fields' => $fields,
+ 'conds' => $this->mQueryConds
);
}
- $wgOut->addHTML( implode( $sortlinks, "<br />\n") . "\n\n<hr />" );
- // lines
- $wgOut->addHTML( '<p>' );
- $res = $dbr->query( $sql, "wfSpecialImagelist" );
+ function getDefaultSort() {
+ return 'img_timestamp';
+ }
- while ( $s = $dbr->fetchObject( $res ) ) {
- $name = $s->img_name;
- $ut = $s->img_user_text;
- if ( 0 == $s->img_user ) {
- $ul = $ut;
- } else {
- $ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut );
+ function getStartBody() {
+ # Do a link batch query for user pages
+ if ( $this->mResult->numRows() ) {
+ $lb = new LinkBatch;
+ $this->mResult->seek( 0 );
+ while ( $row = $this->mResult->fetchObject() ) {
+ if ( $row->img_user ) {
+ $lb->add( NS_USER, str_replace( ' ', '_', $row->img_user_text ) );
+ }
+ }
+ $lb->execute();
}
- $dirmark = $wgContLang->getDirMark(); // to keep text in correct direction
-
- $ilink = "<a href=\"" . htmlspecialchars( Image::imageUrl( $name ) ) .
- "\">" . strtr(htmlspecialchars( $name ), '_', ' ') . "</a>";
+ # Cache messages used in each row
+ $this->mMessages['imgdesc'] = wfMsgHtml( 'imgdesc' );
+ $this->mMessages['imgfile'] = wfMsgHtml( 'imgfile' );
+
+ return parent::getStartBody();
+ }
- $nb = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
- $wgLang->formatNum( $s->img_size ) );
+ function formatValue( $field, $value ) {
+ global $wgLang;
+ switch ( $field ) {
+ case 'links':
+ $name = $this->mCurrentRow->img_name;
+ $ilink = "<a href=\"" . htmlspecialchars( Image::imageUrl( $name ) ) .
+ "\">" . $this->mMessages['imgfile'] . "</a>";
+ $desc = $this->getSkin()->makeKnownLinkObj( Title::makeTitle( NS_IMAGE, $name ),
+ $this->mMessages['imgdesc'] );
+ return "$desc | $ilink";
+ case 'img_timestamp':
+ return $wgLang->timeanddate( $value, true );
+ case 'img_name':
+ return htmlspecialchars( $value );
+ case 'img_user_text':
+ if ( $this->mCurrentRow->img_user ) {
+ $link = $this->getSkin()->makeLinkObj( Title::makeTitle( NS_USER, $value ),
+ htmlspecialchars( $value ) );
+ } else {
+ $link = htmlspecialchars( $value );
+ }
+ return $link;
+ case 'img_size':
+ return $wgLang->formatNum( $value );
+ case 'img_description':
+ return $this->getSkin()->commentBlock( $value );
+ }
+ }
- $desc = $sk->makeKnownLinkObj( Title::makeTitle( NS_IMAGE, $name ),
- wfMsg( 'imgdesc' ) );
+ function getForm() {
+ global $wgRequest, $wgMiserMode;
+ $url = $this->getTitle()->escapeLocalURL();
+ $msgSubmit = wfMsgHtml( 'table_pager_limit_submit' );
+ $msgSearch = wfMsgHtml( 'imagelist_search_for' );
+ $search = $wgRequest->getText( 'ilsearch' );
+ $encSearch = htmlspecialchars( $search );
+ $s = "<form method=\"get\" action=\"$url\">\n" .
+ wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() );
+ if ( !$wgMiserMode ) {
+ $s .= "<br/>\n" . $msgSearch .
+ " <input type=\"text\" size=\"20\" name=\"ilsearch\" value=\"$encSearch\"/><br/>\n";
+ }
+ $s .= " <input type=\"submit\" value=\"$msgSubmit\"/>\n" .
+ $this->getHiddenFields( array( 'limit', 'ilsearch' ) ) .
+ "</form>\n";
+ return $s;
+ }
- $date = $wgLang->timeanddate( $s->img_timestamp, true );
- $comment = $sk->commentBlock( $s->img_description );
+ function getTableClass() {
+ return 'imagelist ' . parent::getTableClass();
+ }
- $l = "({$desc}) {$dirmark}{$ilink} . . {$dirmark}{$nb} . . {$dirmark}{$ul}".
- " . . {$dirmark}{$date} . . {$dirmark}{$comment}<br />\n";
- $wgOut->addHTML( $l );
+ function getNavClass() {
+ return 'imagelist_nav ' . parent::getNavClass();
}
- $dbr->freeResult( $res );
- $wgOut->addHTML( '</p>' );
+ function getSortHeaderClass() {
+ return 'imagelist_sort ' . parent::getSortHeaderClass();
+ }
}
?>
diff --git a/includes/SpecialImport.php b/includes/SpecialImport.php
index 7976d6c8..aaadb662 100644
--- a/includes/SpecialImport.php
+++ b/includes/SpecialImport.php
@@ -175,39 +175,41 @@ class ImportReporter {
$wgOut->addHtml( "<ul>\n" );
}
- function reportPage( $title, $origTitle, $revisionCount ) {
+ function reportPage( $title, $origTitle, $revisionCount, $successCount ) {
global $wgOut, $wgUser, $wgLang, $wgContLang;
$skin = $wgUser->getSkin();
$this->mPageCount++;
- $localCount = $wgLang->formatNum( $revisionCount );
- $contentCount = $wgContLang->formatNum( $revisionCount );
+ $localCount = $wgLang->formatNum( $successCount );
+ $contentCount = $wgContLang->formatNum( $successCount );
$wgOut->addHtml( "<li>" . $skin->makeKnownLinkObj( $title ) .
" " .
- wfMsgHtml( 'import-revision-count', $localCount ) .
+ wfMsgExt( 'import-revision-count', array( 'parsemag', 'escape' ), $localCount ) .
"</li>\n" );
- $log = new LogPage( 'import' );
- if( $this->mIsUpload ) {
- $detail = wfMsgForContent( 'import-logentry-upload-detail',
- $contentCount );
- $log->addEntry( 'upload', $title, $detail );
- } else {
- $interwiki = '[[:' . $this->mInterwiki . ':' .
- $origTitle->getPrefixedText() . ']]';
- $detail = wfMsgForContent( 'import-logentry-interwiki-detail',
- $contentCount, $interwiki );
- $log->addEntry( 'interwiki', $title, $detail );
+ if( $successCount > 0 ) {
+ $log = new LogPage( 'import' );
+ if( $this->mIsUpload ) {
+ $detail = wfMsgForContent( 'import-logentry-upload-detail',
+ $contentCount );
+ $log->addEntry( 'upload', $title, $detail );
+ } else {
+ $interwiki = '[[:' . $this->mInterwiki . ':' .
+ $origTitle->getPrefixedText() . ']]';
+ $detail = wfMsgForContent( 'import-logentry-interwiki-detail',
+ $contentCount, $interwiki );
+ $log->addEntry( 'interwiki', $title, $detail );
+ }
+
+ $comment = $detail; // quick
+ $dbw = wfGetDB( DB_MASTER );
+ $nullRevision = Revision::newNullRevision(
+ $dbw, $title->getArticleId(), $comment, true );
+ $nullRevId = $nullRevision->insertOn( $dbw );
}
-
- $comment = $detail; // quick
- $dbw = wfGetDB( DB_MASTER );
- $nullRevision = Revision::newNullRevision(
- $dbw, $title->getArticleId(), $comment, true );
- $nullRevId = $nullRevision->insertOn( $dbw );
}
function close() {
@@ -238,7 +240,7 @@ class WikiRevision {
if( is_object( $title ) ) {
$this->title = $title;
} elseif( is_null( $title ) ) {
- throw new MWException( "WikiRevision given a null title in import." );
+ throw new MWException( "WikiRevision given a null title in import. You may need to adjust \$wgLegalTitleChars." );
} else {
throw new MWException( "WikiRevision given non-object title in import." );
}
@@ -327,9 +329,17 @@ class WikiRevision {
$created = true;
} else {
$created = false;
- }
- # FIXME: Check for exact conflicts
+ $prior = Revision::loadFromTimestamp( $dbw, $this->title, $this->timestamp );
+ if( !is_null( $prior ) ) {
+ // FIXME: this could fail slightly for multiple matches :P
+ wfDebug( __METHOD__ . ": skipping existing revision for [[" .
+ $this->title->getPrefixedText() . "]], timestamp " .
+ $this->timestamp . "\n" );
+ return false;
+ }
+ }
+
# FIXME: Use original rev_id optionally
# FIXME: blah blah blah
@@ -353,13 +363,14 @@ class WikiRevision {
if( $created ) {
wfDebug( __METHOD__ . ": running onArticleCreate\n" );
Article::onArticleCreate( $this->title );
- } else {
- if( $changed ) {
- wfDebug( __METHOD__ . ": running onArticleEdit\n" );
- Article::onArticleEdit( $this->title );
- }
- }
- if( $created || $changed ) {
+
+ wfDebug( __METHOD__ . ": running create updates\n" );
+ $article->createUpdates( $revision );
+
+ } elseif( $changed ) {
+ wfDebug( __METHOD__ . ": running onArticleEdit\n" );
+ Article::onArticleEdit( $this->title );
+
wfDebug( __METHOD__ . ": running edit updates\n" );
$article->editUpdates(
$this->getText(),
@@ -499,7 +510,7 @@ class WikiImporter {
*/
function importRevision( &$revision ) {
$dbw =& wfGetDB( DB_MASTER );
- $dbw->deadlockLoop( array( &$revision, 'importOldRevision' ) );
+ return $dbw->deadlockLoop( array( &$revision, 'importOldRevision' ) );
}
/**
@@ -536,12 +547,13 @@ class WikiImporter {
* @param Title $title
* @param Title $origTitle
* @param int $revisionCount
+ * @param int $successCount number of revisions for which callback returned true
* @private
*/
- function pageOutCallback( $title, $origTitle, $revisionCount ) {
+ function pageOutCallback( $title, $origTitle, $revisionCount, $successCount ) {
if( is_callable( $this->mPageOutCallback ) ) {
call_user_func( $this->mPageOutCallback, $title, $origTitle,
- $revisionCount );
+ $revisionCount, $successCount );
}
}
@@ -565,6 +577,7 @@ class WikiImporter {
xml_set_element_handler( $parser, "in_siteinfo", "out_siteinfo" );
} elseif( $name == 'page' ) {
$this->workRevisionCount = 0;
+ $this->workSuccessCount = 0;
xml_set_element_handler( $parser, "in_page", "out_page" );
} else {
return $this->throwXMLerror( "Expected <page>, got <$name>" );
@@ -633,11 +646,12 @@ class WikiImporter {
xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" );
$this->pageOutCallback( $this->pageTitle, $this->origTitle,
- $this->workRevisionCount );
+ $this->workRevisionCount, $this->workSuccessCount );
$this->workTitle = null;
$this->workRevision = null;
$this->workRevisionCount = 0;
+ $this->workSuccessCount = 0;
$this->pageTitle = null;
$this->origTitle = null;
}
@@ -728,11 +742,10 @@ class WikiImporter {
}
xml_set_element_handler( $parser, "in_page", "out_page" );
- $out = call_user_func_array( $this->mRevisionCallback,
+ $ok = call_user_func_array( $this->mRevisionCallback,
array( &$this->workRevision, &$this ) );
- if( !empty( $out ) ) {
- global $wgOut;
- $wgOut->addHTML( "<li>" . $out . "</li>\n" );
+ if( $ok ) {
+ $this->workSuccessCount++;
}
}
diff --git a/includes/SpecialIpblocklist.php b/includes/SpecialIpblocklist.php
index cc5c805c..437fac7f 100644
--- a/includes/SpecialIpblocklist.php
+++ b/includes/SpecialIpblocklist.php
@@ -12,17 +12,19 @@ function wfSpecialIpblocklist() {
global $wgUser, $wgOut, $wgRequest;
$ip = $wgRequest->getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) );
+ $id = $wgRequest->getVal( 'id' );
$reason = $wgRequest->getText( 'wpUnblockReason' );
$action = $wgRequest->getText( 'action' );
+ $successip = $wgRequest->getVal( 'successip' );
- $ipu = new IPUnblockForm( $ip, $reason );
+ $ipu = new IPUnblockForm( $ip, $id, $reason );
if ( "success" == $action ) {
- $ipu->showList( wfMsgWikiHtml( 'unblocked', htmlspecialchars( $ip ) ) );
+ $ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) );
} else if ( "submit" == $action && $wgRequest->wasPosted() &&
$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
if ( ! $wgUser->isAllowed('block') ) {
- $wgOut->sysopRequired();
+ $wgOut->permissionRequired( 'block' );
return;
}
$ipu->doSubmit();
@@ -39,10 +41,11 @@ function wfSpecialIpblocklist() {
* @subpackage SpecialPage
*/
class IPUnblockForm {
- var $ip, $reason;
+ var $ip, $reason, $id;
- function IPUnblockForm( $ip, $reason ) {
+ function IPUnblockForm( $ip, $id, $reason ) {
$this->ip = $ip;
+ $this->id = $id;
$this->reason = $reason;
}
@@ -64,13 +67,27 @@ class IPUnblockForm {
}
$token = htmlspecialchars( $wgUser->editToken() );
+ $addressPart = false;
+ if ( $this->id ) {
+ $block = Block::newFromID( $this->id );
+ if ( $block ) {
+ $encName = htmlspecialchars( $block->getRedactedName() );
+ $encId = htmlspecialchars( $this->id );
+ $addressPart = $encName . "<input type='hidden' name=\"id\" value=\"$encId\" />";
+ }
+ }
+ if ( !$addressPart ) {
+ $addressPart = "<input tabindex='1' type='text' size='20' " .
+ "name=\"wpUnblockAddress\" value=\"" . htmlspecialchars( $this->ip ) . "\" />";
+ }
+
$wgOut->addHTML( "
<form id=\"unblockip\" method=\"post\" action=\"{$action}\">
<table border='0'>
<tr>
<td align='right'>{$ipa}:</td>
<td align='left'>
- <input tabindex='1' type='text' size='20' name=\"wpUnblockAddress\" value=\"" . htmlspecialchars( $this->ip ) . "\" />
+ {$addressPart}
</td>
</tr>
<tr>
@@ -94,27 +111,46 @@ class IPUnblockForm {
function doSubmit() {
global $wgOut;
- $block = new Block();
- $this->ip = trim( $this->ip );
-
- if ( $this->ip{0} == "#" ) {
- $block->mId = substr( $this->ip, 1 );
+ if ( $this->id ) {
+ $block = Block::newFromID( $this->id );
+ if ( $block ) {
+ $this->ip = $block->getRedactedName();
+ }
} else {
- $block->mAddress = $this->ip;
+ $block = new Block();
+ $this->ip = trim( $this->ip );
+ if ( substr( $this->ip, 0, 1 ) == "#" ) {
+ $id = substr( $this->ip, 1 );
+ $block = Block::newFromID( $id );
+ } else {
+ $block = Block::newFromDB( $this->ip );
+ if ( !$block ) {
+ $block = null;
+ }
+ }
+ }
+ $success = false;
+ if ( $block ) {
+ # Delete block
+ if ( $block->delete() ) {
+ # Make log entry
+ $log = new LogPage( 'block' );
+ $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $this->ip ), $this->reason );
+ $success = true;
+ }
}
- # Delete block (if it exists)
- # We should probably check for errors rather than just declaring success
- $block->delete();
-
- # Make log entry
- $log = new LogPage( 'block' );
- $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $this->ip ), $this->reason );
-
- # Report to the user
- $titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
- $success = $titleObj->getFullURL( "action=success&ip=" . urlencode( $this->ip ) );
- $wgOut->redirect( $success );
+ if ( $success ) {
+ # Report to the user
+ $titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
+ $success = $titleObj->getFullURL( "action=success&successip=" . urlencode( $this->ip ) );
+ $wgOut->redirect( $success );
+ } else {
+ if ( !$this->ip && $this->id ) {
+ $this->ip = '#' . $this->id;
+ }
+ $this->showForm( wfMsg( 'ipb_cant_unblock', htmlspecialchars( $this->id ) ) );
+ }
}
function showList( $msg ) {
@@ -124,33 +160,55 @@ class IPUnblockForm {
if ( "" != $msg ) {
$wgOut->setSubtitle( $msg );
}
- global $wgRequest;
- list( $this->limit, $this->offset ) = $wgRequest->getLimitOffset();
- $this->counter = 0;
- $paging = '<p>' . wfViewPrevNext( $this->offset, $this->limit,
- Title::makeTitle( NS_SPECIAL, 'Ipblocklist' ),
- 'ip=' . urlencode( $this->ip ) ) . "</p>\n";
- $wgOut->addHTML( $paging );
+ // Purge expired entries on one in every 10 queries
+ if ( !mt_rand( 0, 10 ) ) {
+ Block::purgeExpired();
+ }
- $search = $this->searchForm();
- $wgOut->addHTML( $search );
-
- $wgOut->addHTML( "<ul>" );
- if( !Block::enumBlocks( array( &$this, "addRow" ), 0 ) ) {
- // FIXME hack to solve #bug 1487
- $wgOut->addHTML( '<li>'.wfMsgHtml( 'ipblocklistempty' ).'</li>' );
+ $conds = array();
+ if ( $this->ip == '' ) {
+ // No extra conditions
+ } elseif ( substr( $this->ip, 0, 1 ) == '#' ) {
+ $conds['ipb_id'] = substr( $this->ip, 1 );
+ } elseif ( IP::toUnsigned( $this->ip ) !== false ) {
+ $conds['ipb_address'] = $this->ip;
+ $conds['ipb_auto'] = 0;
+ } elseif( preg_match( "/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\\/(\\d{1,2})$/", $this->ip, $matches ) ) {
+ $conds['ipb_address'] = Block::normaliseRange( $this->ip );
+ $conds['ipb_auto'] = 0;
+ } else {
+ $user = User::newFromName( $this->ip );
+ if ( $user && ( $id = $user->getID() ) != 0 ) {
+ $conds['ipb_user'] = $id;
+ } else {
+ // Uh...?
+ $conds['ipb_address'] = $this->ip;
+ $conds['ipb_auto'] = 0;
+ }
+ }
+
+ $pager = new IPBlocklistPager( $this, $conds );
+ $s = $pager->getNavigationBar() .
+ $this->searchForm();
+ if ( $pager->getNumRows() ) {
+ $s .= "<ul>" .
+ $pager->getBody() .
+ "</ul>";
+ } else {
+ $s .= '<p>' . wfMsgHTML( 'ipblocklistempty' ) . '</p>';
}
- $wgOut->addHTML( "</ul>\n" );
- $wgOut->addHTML( $paging );
+ $s .= $pager->getNavigationBar();
+ $wgOut->addHTML( $s );
}
function searchForm() {
- global $wgTitle;
+ global $wgTitle, $wgScript, $wgRequest;
return
wfElement( 'form', array(
- 'action' => $wgTitle->getLocalUrl() ),
+ 'action' => $wgScript ),
null ) .
+ wfHidden( 'title', $wgTitle->getPrefixedDbKey() ) .
wfElement( 'input', array(
'type' => 'hidden',
'name' => 'action',
@@ -158,7 +216,7 @@ class IPUnblockForm {
wfElement( 'input', array(
'type' => 'hidden',
'name' => 'limit',
- 'value' => $this->limit ) ).
+ 'value' => $wgRequest->getText( 'limit' ) ) ) .
wfElement( 'input', array(
'name' => 'ip',
'value' => $this->ip ) ) .
@@ -171,33 +229,10 @@ class IPUnblockForm {
/**
* Callback function to output a block
*/
- function addRow( $block, $tag ) {
- global $wgOut, $wgUser, $wgLang;
+ function formatRow( $block ) {
+ global $wgUser, $wgLang;
- if( $this->ip != '' ) {
- if( $block->mAuto ) {
- if( stristr( $block->mId, $this->ip ) == false ) {
- return;
- }
- } else {
- if( stristr( $block->mAddress, $this->ip ) == false ) {
- return;
- }
- }
- }
-
- // Loading blocks is fast; displaying them is slow.
- // Quick hack for paging.
- $this->counter++;
- if( $this->counter <= $this->offset ) {
- return;
- }
- if( $this->counter - $this->offset > $this->limit ) {
- return;
- }
-
- $fname = 'IPUnblockForm-addRow';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
static $sk=null, $msg=null;
@@ -205,14 +240,15 @@ class IPUnblockForm {
$sk = $wgUser->getSkin();
if( is_null( $msg ) ) {
$msg = array();
- foreach( array( 'infiniteblock', 'expiringblock', 'contribslink', 'unblocklink' ) as $key ) {
+ $keys = array( 'infiniteblock', 'expiringblock', 'contribslink', 'unblocklink',
+ 'anononlyblock', 'createaccountblock' );
+ foreach( $keys as $key ) {
$msg[$key] = wfMsgHtml( $key );
}
$msg['blocklistline'] = wfMsg( 'blocklistline' );
$msg['contribslink'] = wfMsg( 'contribslink' );
}
-
# Prepare links to the blocker's user and talk pages
$blocker_name = $block->getByName();
$blocker = $sk->MakeLinkObj( Title::makeTitle( NS_USER, $blocker_name ), $blocker_name );
@@ -220,35 +256,101 @@ class IPUnblockForm {
# Prepare links to the block target's user and contribs. pages (as applicable, don't do it for autoblocks)
if( $block->mAuto ) {
- $target = '#' . $block->mId; # Hide the IP addresses of auto-blocks; privacy
+ $target = $block->getRedactedName(); # Hide the IP addresses of auto-blocks; privacy
} else {
$target = $sk->makeLinkObj( Title::makeTitle( NS_USER, $block->mAddress ), $block->mAddress );
$target .= ' (' . $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), $msg['contribslink'], 'target=' . urlencode( $block->mAddress ) ) . ')';
}
- # Prep the address for the unblock link, masking autoblocks as before
- $addr = $block->mAuto ? '#' . $block->mId : $block->mAddress;
-
$formattedTime = $wgLang->timeanddate( $block->mTimestamp, true );
- if ( $block->mExpiry === "" ) {
- $formattedExpiry = $msg['infiniteblock'];
+ $properties = array();
+ if ( $block->mExpiry === "" || $block->mExpiry === Block::infinity() ) {
+ $properties[] = $msg['infiniteblock'];
} else {
- $formattedExpiry = wfMsgReplaceArgs( $msg['expiringblock'],
+ $properties[] = wfMsgReplaceArgs( $msg['expiringblock'],
array( $wgLang->timeanddate( $block->mExpiry, true ) ) );
}
+ if ( $block->mAnonOnly ) {
+ $properties[] = $msg['anononlyblock'];
+ }
+ if ( $block->mCreateAccount ) {
+ $properties[] = $msg['createaccountblock'];
+ }
+ $properties = implode( ', ', $properties );
- $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $formattedExpiry ) );
+ $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) );
- $wgOut->addHTML( "<li>{$line}" );
+ $s = "<li>{$line}";
if ( $wgUser->isAllowed('block') ) {
$titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
- $wgOut->addHTML( ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&ip=' . urlencode( $addr ) ) . ')' );
+ $s .= ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&id=' . urlencode( $block->mId ) ) . ')';
}
- $wgOut->addHTML( $sk->commentBlock( $block->mReason ) );
- $wgOut->addHTML( "</li>\n" );
- wfProfileOut( $fname );
+ $s .= $sk->commentBlock( $block->mReason );
+ $s .= "</li>\n";
+ wfProfileOut( __METHOD__ );
+ return $s;
+ }
+}
+
+class IPBlocklistPager extends ReverseChronologicalPager {
+ public $mForm, $mConds;
+
+ function __construct( $form, $conds = array() ) {
+ $this->mForm = $form;
+ $this->mConds = $conds;
+ parent::__construct();
+ }
+
+ function getStartBody() {
+ wfProfileIn( __METHOD__ );
+ # Do a link batch query
+ $this->mResult->seek( 0 );
+ $lb = new LinkBatch;
+
+ /*
+ while ( $row = $this->mResult->fetchObject() ) {
+ $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
+ $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
+ $lb->addObj( Title::makeTitleSafe( NS_USER, $row->ipb_address ) );
+ $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ipb_address ) );
+ }*/
+ # Faster way
+ # Usernames and titles are in fact related by a simple substitution of space -> underscore
+ # The last few lines of Title::secureAndSplit() tell the story.
+ while ( $row = $this->mResult->fetchObject() ) {
+ $name = str_replace( ' ', '_', $row->user_name );
+ $lb->add( NS_USER, $name );
+ $lb->add( NS_USER_TALK, $name );
+ $name = str_replace( ' ', '_', $row->ipb_address );
+ $lb->add( NS_USER, $name );
+ $lb->add( NS_USER_TALK, $name );
+ }
+ $lb->execute();
+ wfProfileOut( __METHOD__ );
+ return '';
+ }
+
+ function formatRow( $row ) {
+ $block = new Block;
+ $block->initFromRow( $row );
+ return $this->mForm->formatRow( $block );
+ }
+
+ function getQueryInfo() {
+ $conds = $this->mConds;
+ $conds[] = 'ipb_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() );
+ $conds[] = 'ipb_by=user_id';
+ return array(
+ 'tables' => array( 'ipblocks', 'user' ),
+ 'fields' => $this->mDb->tableName( 'ipblocks' ) . '.*,user_name',
+ 'conds' => $conds,
+ );
+ }
+
+ function getIndexField() {
+ return 'ipb_timestamp';
}
}
diff --git a/includes/SpecialListredirects.php b/includes/SpecialListredirects.php
index 3cbdedab..f717ef72 100644
--- a/includes/SpecialListredirects.php
+++ b/includes/SpecialListredirects.php
@@ -32,6 +32,7 @@ class ListredirectsPage extends QueryPage {
# Make a link to the redirect itself
$rd_title = Title::makeTitle( $result->namespace, $result->title );
+ $arr = $wgContLang->getArrow() . $wgContLang->getDirMark();
$rd_link = $skin->makeKnownLinkObj( $rd_title, '', 'redirect=no' );
# Find out where the redirect leads
@@ -50,11 +51,8 @@ class ListredirectsPage extends QueryPage {
$targetLink = '*';
}
- # Check the language; RTL wikis need a &larr;
- $arr = $wgContLang->isRTL() ? ' &larr; ' : ' &rarr; ';
-
# Format the whole thing and return it
- return( $rd_link . $arr . $targetLink );
+ return "$rd_link $arr $targetLink";
}
diff --git a/includes/SpecialListusers.php b/includes/SpecialListusers.php
index 20b26b63..4668d0c7 100644
--- a/includes/SpecialListusers.php
+++ b/includes/SpecialListusers.php
@@ -93,7 +93,7 @@ class ListUsersPage extends QueryPage {
$out .= wfCloseElement( 'select' ) . ' ';;# . wfElement( 'br' );
# Username field
- $out .= wfElement( 'label', array( 'for' => 'username' ), wfMsg( 'specialloguserlabel' ) ) . ' ';
+ $out .= wfElement( 'label', array( 'for' => 'username' ), wfMsg( 'listusersfrom' ) ) . ' ';
$out .= wfElement( 'input', array( 'type' => 'text', 'id' => 'username', 'name' => 'username',
'value' => $this->requestedUser ) ) . ' ';
@@ -111,6 +111,7 @@ class ListUsersPage extends QueryPage {
}
function getSQL() {
+ global $wgDBtype;
$dbr =& wfGetDB( DB_SLAVE );
$user = $dbr->tableName( 'user' );
$user_groups = $dbr->tableName( 'user_groups' );
@@ -133,24 +134,26 @@ class ListUsersPage extends QueryPage {
"LEFT JOIN $user_groups ON user_id=ug_user " .
$this->userQueryWhere( $dbr ) .
" GROUP BY user_name";
-
+ if ( $wgDBtype != 'mysql' ) {
+ $sql .= ",user_id";
+ }
return $sql;
}
function userQueryWhere( &$dbr ) {
- $conds = $this->userQueryConditions();
+ $conds = $this->userQueryConditions( $dbr );
return empty( $conds )
? ""
: "WHERE " . $dbr->makeList( $conds, LIST_AND );
}
- function userQueryConditions() {
+ function userQueryConditions( $dbr ) {
$conds = array();
if( $this->requestedGroup != '' ) {
$conds['ug_group'] = $this->requestedGroup;
}
if( $this->requestedUser != '' ) {
- $conds['user_name'] = $this->requestedUser;
+ $conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser );
}
return $conds;
}
@@ -189,16 +192,12 @@ class ListUsersPage extends QueryPage {
if( count( $groups ) > 0 ) {
foreach( $groups as $group => $desc ) {
- if( $page = User::getGroupPage( $group ) ) {
- $list[] = $skin->makeLinkObj( $page, htmlspecialchars( $desc ) );
- } else {
- $list[] = htmlspecialchars( $desc );
- }
+ $list[] = User::makeGroupLinkHTML( $group, $desc );
}
$groups = implode( ', ', $list );
} else {
$groups = '';
- }
+ }
}
diff --git a/includes/SpecialLockdb.php b/includes/SpecialLockdb.php
index 38d715be..72172e2c 100644
--- a/includes/SpecialLockdb.php
+++ b/includes/SpecialLockdb.php
@@ -11,10 +11,18 @@
function wfSpecialLockdb() {
global $wgUser, $wgOut, $wgRequest;
- if ( ! $wgUser->isAllowed('siteadmin') ) {
- $wgOut->developerRequired();
+ if( !$wgUser->isAllowed( 'siteadmin' ) ) {
+ $wgOut->permissionRequired( 'siteadmin' );
return;
}
+
+ # If the lock file isn't writable, we can do sweet bugger all
+ global $wgReadOnlyFile;
+ if( !is_writable( dirname( $wgReadOnlyFile ) ) ) {
+ DBLockForm::notWritable();
+ return;
+ }
+
$action = $wgRequest->getVal( 'action' );
$f = new DBLockForm();
@@ -56,12 +64,13 @@ class DBLockForm {
$elr = htmlspecialchars( wfMsg( 'enterlockreason' ) );
$titleObj = Title::makeTitle( NS_SPECIAL, 'Lockdb' );
$action = $titleObj->escapeLocalURL( 'action=submit' );
+ $reason = htmlspecialchars( $this->reason );
$token = htmlspecialchars( $wgUser->editToken() );
$wgOut->addHTML( <<<END
<form id="lockdb" method="post" action="{$action}">
{$elr}:
-<textarea name="wpLockReason" rows="10" cols="60" wrap="virtual"></textarea>
+<textarea name="wpLockReason" rows="10" cols="60" wrap="virtual">{$reason}</textarea>
<table border="0">
<tr>
<td align="right">
@@ -91,10 +100,13 @@ END
$this->showForm( wfMsg( 'locknoconfirm' ) );
return;
}
- $fp = fopen( $wgReadOnlyFile, 'w' );
+ $fp = @fopen( $wgReadOnlyFile, 'w' );
if ( false === $fp ) {
- $wgOut->showFileNotFoundError( $wgReadOnlyFile );
+ # This used to show a file not found error, but the likeliest reason for fopen()
+ # to fail at this point is insufficient permission to write to the file...good old
+ # is_writable() is plain wrong in some cases, it seems...
+ $this->notWritable();
return;
}
fwrite( $fp, $this->reason );
@@ -113,6 +125,12 @@ END
$wgOut->setSubtitle( wfMsg( 'lockdbsuccesssub' ) );
$wgOut->addWikiText( wfMsg( 'lockdbsuccesstext' ) );
}
+
+ function notWritable() {
+ global $wgOut;
+ $wgOut->errorPage( 'lockdb', 'lockfilenotwritable' );
+ }
+
}
?>
diff --git a/includes/SpecialLog.php b/includes/SpecialLog.php
index a9e8573a..e32d2240 100644
--- a/includes/SpecialLog.php
+++ b/includes/SpecialLog.php
@@ -28,11 +28,11 @@
*/
function wfSpecialLog( $par = '' ) {
global $wgRequest;
- $logReader =& new LogReader( $wgRequest );
+ $logReader = new LogReader( $wgRequest );
if( $wgRequest->getVal( 'type' ) == '' && $par != '' ) {
$logReader->limitType( $par );
}
- $logViewer =& new LogViewer( $logReader );
+ $logViewer = new LogViewer( $logReader );
$logViewer->show();
}
diff --git a/includes/SpecialLonelypages.php b/includes/SpecialLonelypages.php
index 326ae54d..15022924 100644
--- a/includes/SpecialLonelypages.php
+++ b/includes/SpecialLonelypages.php
@@ -15,6 +15,9 @@ class LonelyPagesPage extends PageQueryPage {
function getName() {
return "Lonelypages";
}
+ function getPageHeader() {
+ return '<p>' . wfMsg('lonelypagestext') . '</p>';
+ }
function sortDescending() {
return false;
diff --git a/includes/SpecialLongpages.php b/includes/SpecialLongpages.php
index af56c17c..3736d6fc 100644
--- a/includes/SpecialLongpages.php
+++ b/includes/SpecialLongpages.php
@@ -7,11 +7,6 @@
/**
*
- */
-require_once( 'SpecialShortpages.php' );
-
-/**
- *
* @package MediaWiki
* @subpackage SpecialPage
*/
diff --git a/includes/SpecialMostcategories.php b/includes/SpecialMostcategories.php
index 5591bbc4..c0d662cc 100644
--- a/includes/SpecialMostcategories.php
+++ b/includes/SpecialMostcategories.php
@@ -31,7 +31,7 @@ class MostcategoriesPage extends QueryPage {
FROM $categorylinks
LEFT JOIN $page ON cl_from = page_id
WHERE page_namespace = " . NS_MAIN . "
- GROUP BY cl_from
+ GROUP BY 1,2,3
HAVING COUNT(*) > 1
";
}
diff --git a/includes/SpecialMostimages.php b/includes/SpecialMostimages.php
index 30fbdddf..09f71088 100644
--- a/includes/SpecialMostimages.php
+++ b/includes/SpecialMostimages.php
@@ -29,7 +29,7 @@ class MostimagesPage extends QueryPage {
il_to as title,
COUNT(*) as value
FROM $imagelinks
- GROUP BY il_to
+ GROUP BY 1,2,3
HAVING COUNT(*) > 1
";
}
diff --git a/includes/SpecialMostlinked.php b/includes/SpecialMostlinked.php
index ccccc1a4..1791228d 100644
--- a/includes/SpecialMostlinked.php
+++ b/includes/SpecialMostlinked.php
@@ -37,7 +37,7 @@ class MostlinkedPage extends QueryPage {
page_namespace
FROM $pagelinks
LEFT JOIN $page ON pl_namespace=page_namespace AND pl_title=page_title
- GROUP BY pl_namespace,pl_title
+ GROUP BY 1,2,3,5
HAVING COUNT(*) > 1";
}
diff --git a/includes/SpecialMostlinkedcategories.php b/includes/SpecialMostlinkedcategories.php
index 0944d2f8..5942b3f4 100644
--- a/includes/SpecialMostlinkedcategories.php
+++ b/includes/SpecialMostlinkedcategories.php
@@ -32,7 +32,7 @@ class MostlinkedCategoriesPage extends QueryPage {
cl_to as title,
COUNT(*) as value
FROM $categorylinks
- GROUP BY cl_to
+ GROUP BY 1,2,3
";
}
diff --git a/includes/SpecialMostrevisions.php b/includes/SpecialMostrevisions.php
index 81a49c99..676923ae 100644
--- a/includes/SpecialMostrevisions.php
+++ b/includes/SpecialMostrevisions.php
@@ -31,9 +31,9 @@ class MostrevisionsPage extends QueryPage {
page_title as title,
COUNT(*) as value
FROM $revision
- LEFT JOIN $page ON page_id = rev_page
+ JOIN $page ON page_id = rev_page
WHERE page_namespace = " . NS_MAIN . "
- GROUP BY rev_page
+ GROUP BY 1,2,3
HAVING COUNT(*) > 1
";
}
diff --git a/includes/SpecialMovepage.php b/includes/SpecialMovepage.php
index 39397129..e33c1530 100644
--- a/includes/SpecialMovepage.php
+++ b/includes/SpecialMovepage.php
@@ -11,12 +11,19 @@
function wfSpecialMovepage( $par = null ) {
global $wgUser, $wgOut, $wgRequest, $action, $wgOnlySysopMayMove;
- # check rights. We don't want newbies to move pages to prevents possible attack
- if ( !$wgUser->isAllowed( 'move' ) or $wgUser->isBlocked() or ($wgOnlySysopMayMove and $wgUser->isNewbie())) {
- $wgOut->showErrorPage( "movenologin", "movenologintext" );
+ # Check rights
+ if ( !$wgUser->isAllowed( 'move' ) ) {
+ $wgOut->showErrorPage( 'movenologin', 'movenologintext' );
return;
}
- # We don't move protected pages
+
+ # Don't allow blocked users to move pages
+ if ( $wgUser->isBlocked() ) {
+ $wgOut->blockedPage();
+ return;
+ }
+
+ # Check for database lock
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
return;
@@ -249,8 +256,8 @@ class MovePageForm {
$wgOut->setPagetitle( wfMsg( 'movepage' ) );
$wgOut->setSubtitle( wfMsg( 'pagemovedsub' ) );
- $oldText = $wgRequest->getVal('oldtitle');
- $newText = $wgRequest->getVal('newtitle');
+ $oldText = wfEscapeWikiText( $wgRequest->getVal('oldtitle') );
+ $newText = wfEscapeWikiText( $wgRequest->getVal('newtitle') );
$talkmoved = $wgRequest->getVal('talkmoved');
$text = wfMsg( 'pagemovedtext', $oldText, $newText );
@@ -266,7 +273,7 @@ class MovePageForm {
$wgOut->addWikiText( wfMsg( 'talkexists' ) );
} else {
$oldTitle = Title::newFromText( $oldText );
- if ( !$oldTitle->isTalkPage() && $talkmoved != 'notalkpage' ) {
+ if ( isset( $oldTitle ) && !$oldTitle->isTalkPage() && $talkmoved != 'notalkpage' ) {
$wgOut->addWikiText( wfMsg( 'talkpagenotmoved', wfMsg( $talkmoved ) ) );
}
}
diff --git a/includes/SpecialNewimages.php b/includes/SpecialNewimages.php
index 976611a3..95c90e42 100644
--- a/includes/SpecialNewimages.php
+++ b/includes/SpecialNewimages.php
@@ -17,7 +17,8 @@ function wfSpecialNewimages( $par, $specialPage ) {
$shownav = !$specialPage->including();
$hidebots = $wgRequest->getBool('hidebots',1);
- if($hidebots) {
+ $hidebotsql = '';
+ if ($hidebots) {
/** Make a list of group names which have the 'bot' flag
set.
@@ -28,23 +29,26 @@ function wfSpecialNewimages( $par, $specialPage ) {
$botconds[]="ug_group='$groupname'";
}
}
- $isbotmember=$dbr->makeList($botconds, LIST_OR);
- /** This join, in conjunction with WHERE ug_group
- IS NULL, returns only those rows from IMAGE
- where the uploading user is not a member of
- a group which has the 'bot' permission set.
- */
- $ug = $dbr->tableName('user_groups');
- $joinsql=" LEFT OUTER JOIN $ug ON img_user=ug_user AND ("
- . $isbotmember.')';
+ /* If not bot groups, do not set $hidebotsql */
+ if ($botconds) {
+ $isbotmember=$dbr->makeList($botconds, LIST_OR);
+
+ /** This join, in conjunction with WHERE ug_group
+ IS NULL, returns only those rows from IMAGE
+ where the uploading user is not a member of
+ a group which has the 'bot' permission set.
+ */
+ $ug = $dbr->tableName('user_groups');
+ $hidebotsql = " LEFT OUTER JOIN $ug ON img_user=ug_user AND ($isbotmember)";
+ }
}
$image = $dbr->tableName('image');
$sql="SELECT img_timestamp from $image";
- if($hidebots) {
- $sql.=$joinsql.' WHERE ug_group IS NULL';
+ if ($hidebotsql) {
+ $sql .= "$hidebotsql WHERE ug_group IS NULL";
}
$sql.=' ORDER BY img_timestamp DESC LIMIT 1';
$res = $dbr->query($sql, 'wfSpecialNewImages');
@@ -91,8 +95,8 @@ function wfSpecialNewimages( $par, $specialPage ) {
$sql='SELECT img_size, img_name, img_user, img_user_text,'.
"img_description,img_timestamp FROM $image";
- if($hidebots) {
- $sql.=$joinsql;
+ if($hidebotsql) {
+ $sql .= $hidebotsql;
$where[]='ug_group IS NULL';
}
if(count($where)) {
@@ -130,7 +134,7 @@ function wfSpecialNewimages( $par, $specialPage ) {
$ut = $s->img_user_text;
$nt = Title::newFromText( $name, NS_IMAGE );
- $img = Image::newFromTitle( $nt );
+ $img = new Image( $nt );
$ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut );
$gallery->add( $img, "$ul<br />\n<i>".$wgLang->timeanddate( $s->img_timestamp, true )."</i><br />\n" );
diff --git a/includes/SpecialNewpages.php b/includes/SpecialNewpages.php
index c0c6ba96..3fd0eba2 100644
--- a/includes/SpecialNewpages.php
+++ b/includes/SpecialNewpages.php
@@ -11,10 +11,13 @@
* @subpackage SpecialPage
*/
class NewPagesPage extends QueryPage {
+
var $namespace;
+ var $username = '';
- function NewPagesPage( $namespace = NS_MAIN ) {
+ function NewPagesPage( $namespace = NS_MAIN, $username = '' ) {
$this->namespace = $namespace;
+ $this->username = $username;
}
function getName() {
@@ -26,12 +29,23 @@ class NewPagesPage extends QueryPage {
return false;
}
+ function makeUserWhere( &$dbo ) {
+ $title = Title::makeTitleSafe( NS_USER, $this->username );
+ if( $title ) {
+ return ' AND rc_user_text = ' . $dbo->addQuotes( $title->getText() );
+ } else {
+ return '';
+ }
+ }
+
function getSQL() {
global $wgUser, $wgUseRCPatrol;
$usepatrol = ( $wgUseRCPatrol && $wgUser->isAllowed( 'patrol' ) ) ? 1 : 0;
$dbr =& wfGetDB( DB_SLAVE );
extract( $dbr->tableNames( 'recentchanges', 'page', 'text' ) );
+ $uwhere = $this->makeUserWhere( $dbr );
+
# FIXME: text will break with compression
return
"SELECT 'Newpages' as type,
@@ -50,7 +64,8 @@ class NewPagesPage extends QueryPage {
page_latest as rev_id
FROM $recentchanges,$page
WHERE rc_cur_id=page_id AND rc_new=1
- AND rc_namespace=" . $this->namespace . " AND page_is_redirect=0";
+ AND rc_namespace=" . $this->namespace . " AND page_is_redirect=0
+ {$uwhere}";
}
function preprocessResults( &$dbo, &$res ) {
@@ -112,34 +127,20 @@ class NewPagesPage extends QueryPage {
}
/**
- * Show a namespace selection form for filtering
+ * Show a form for filtering namespace and username
*
* @return string
*/
function getPageHeader() {
- $thisTitle = Title::makeTitle( NS_SPECIAL, $this->getName() );
- $form = wfOpenElement( 'form', array(
- 'method' => 'post',
- 'action' => $thisTitle->getLocalUrl() ) );
- $form .= wfElement( 'label', array( 'for' => 'namespace' ),
- wfMsg( 'namespace' ) ) . ' ';
- $form .= HtmlNamespaceSelector( $this->namespace );
- # Preserve the offset and limit
- $form .= wfElement( 'input', array(
- 'type' => 'hidden',
- 'name' => 'offset',
- 'value' => $this->offset ) );
- $form .= wfElement( 'input', array(
- 'type' => 'hidden',
- 'name' => 'limit',
- 'value' => $this->limit ) );
- $form .= wfElement( 'input', array(
- 'type' => 'submit',
- 'name' => 'submit',
- 'id' => 'submit',
- 'value' => wfMsg( 'allpagessubmit' ) ) );
- $form .= wfCloseElement( 'form' );
- return( $form );
+ $self = Title::makeTitle( NS_SPECIAL, $this->getName() );
+ $form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl() ) );
+ $form .= '<table><tr><td align="right">' . wfMsgHtml( 'namespace' ) . '</td>';
+ $form .= '<td>' . HtmlNamespaceSelector( $this->namespace ) . '</td><tr>';
+ $form .= '<tr><td align="right">' . wfMsgHtml( 'newpages-username' ) . '</td>';
+ $form .= '<td>' . wfInput( 'username', 30, $this->username ) . '</td></tr>';
+ $form .= '<tr><td></td><td>' . wfSubmitButton( wfMsg( 'allpagessubmit' ) ) . '</td></tr></table>';
+ $form .= wfHidden( 'offset', $this->offset ) . wfHidden( 'limit', $this->limit ) . '</form>';
+ return $form;
}
/**
@@ -148,7 +149,7 @@ class NewPagesPage extends QueryPage {
* @return array
*/
function linkParameters() {
- return( array( 'namespace' => $this->namespace ) );
+ return( array( 'namespace' => $this->namespace, 'username' => $this->username ) );
}
}
@@ -161,6 +162,7 @@ function wfSpecialNewpages($par, $specialPage) {
list( $limit, $offset ) = wfCheckLimits();
$namespace = NS_MAIN;
+ $username = '';
if ( $par ) {
$bits = preg_split( '/\s*,\s*/', trim( $par ) );
@@ -184,12 +186,14 @@ function wfSpecialNewpages($par, $specialPage) {
} else {
if( $ns = $wgRequest->getInt( 'namespace', 0 ) )
$namespace = $ns;
+ if( $un = $wgRequest->getText( 'username' ) )
+ $username = $un;
}
if ( ! isset( $shownavigation ) )
$shownavigation = ! $specialPage->including();
- $npp = new NewPagesPage( $namespace );
+ $npp = new NewPagesPage( $namespace, $username );
if ( ! $npp->doFeed( $wgRequest->getVal( 'feed' ), $limit ) )
$npp->doQuery( $offset, $limit, $shownavigation );
diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php
index ffcd51fa..294c05ef 100644
--- a/includes/SpecialPage.php
+++ b/includes/SpecialPage.php
@@ -279,24 +279,43 @@ class SpecialPage
}
/**
- * Return categorised listable special pages
- * Returns a 2d array where the first index is the restriction name
+ * Return categorised listable special pages for all users
* @static
*/
- static function getPages() {
+ static function getRegularPages() {
if ( !self::$mListInitialised ) {
self::initList();
}
- $pages = array(
- '' => array(),
- 'sysop' => array(),
- 'developer' => array()
- );
+ $pages = array();
+
+ foreach ( self::$mList as $name => $rec ) {
+ $page = self::getPage( $name );
+ if ( $page->isListed() && $page->getRestriction() == '' ) {
+ $pages[$name] = $page;
+ }
+ }
+ return $pages;
+ }
+
+ /**
+ * Return categorised listable special pages which are available
+ * for the current user, but not for everyone
+ * @static
+ */
+ static function getRestrictedPages() {
+ global $wgUser;
+ if ( !self::$mListInitialised ) {
+ self::initList();
+ }
+ $pages = array();
foreach ( self::$mList as $name => $rec ) {
$page = self::getPage( $name );
if ( $page->isListed() ) {
- $pages[$page->getRestriction()][$page->getName()] = $page;
+ $restriction = $page->getRestriction();
+ if ( $restriction != '' && $wgUser->isAllowed( $restriction ) ) {
+ $pages[$name] = $page;
+ }
}
}
return $pages;
@@ -313,7 +332,7 @@ class SpecialPage
* @param $title a title object
* @param $including output is being captured for use in {{special:whatever}}
*/
- function executePath( &$title, $including = false ) {
+ static function executePath( &$title, $including = false ) {
global $wgOut, $wgTitle;
$fname = 'SpecialPage::executePath';
wfProfileIn( $fname );
@@ -410,7 +429,7 @@ class SpecialPage
* and displayRestrictionError()
*
* @param string $name Name of the special page, as seen in links and URLs
- * @param string $restriction Minimum user level required, e.g. "sysop" or "developer".
+ * @param string $restriction User right required, e.g. "block" or "delete"
* @param boolean $listed Whether the page is listed in Special:Specialpages
* @param string $function Function called by execute(). By default it is constructed from $name
* @param string $file File which is included by execute(). It is also constructed from $name by default
@@ -460,15 +479,7 @@ class SpecialPage
* special page (as defined by $mRestriction)
*/
function userCanExecute( &$user ) {
- if ( $this->mRestriction == "" ) {
- return true;
- } else {
- if ( in_array( $this->mRestriction, $user->getRights() ) ) {
- return true;
- } else {
- return false;
- }
- }
+ return $user->isAllowed( $this->mRestriction );
}
/**
diff --git a/includes/SpecialPreferences.php b/includes/SpecialPreferences.php
index c6003b7c..5eadf3d6 100644
--- a/includes/SpecialPreferences.php
+++ b/includes/SpecialPreferences.php
@@ -74,7 +74,7 @@ class PreferencesForm {
# User toggles (the big ugly unsorted list of checkboxes)
$this->mToggles = array();
if ( $this->mPosted ) {
- $togs = $wgLang->getUserToggles();
+ $togs = User::getToggles();
foreach ( $togs as $tname ) {
$this->mToggles[$tname] = $request->getCheck( "wpOp$tname" ) ? 1 : 0;
}
@@ -156,11 +156,16 @@ class PreferencesForm {
/**
* @access private
*/
- function validateDate( &$val, $min = 0, $max=0x7fffffff ) {
- if ( ( sprintf('%d', $val) === $val && $val >= $min && $val <= $max ) || $val == 'ISO 8601' )
+ function validateDate( $val ) {
+ global $wgLang, $wgContLang;
+ if ( $val !== false && (
+ in_array( $val, (array)$wgLang->getDatePreferences() ) ||
+ in_array( $val, (array)$wgContLang->getDatePreferences() ) ) )
+ {
return $val;
- else
- return 0;
+ } else {
+ return $wgLang->getDefaultDateFormat();
+ }
}
/**
@@ -257,7 +262,7 @@ class PreferencesForm {
if( $wgUseTeX ) {
$wgUser->setOption( 'math', $this->mMath );
}
- $wgUser->setOption( 'date', $this->validateDate( $this->mDate, 0, 20 ) );
+ $wgUser->setOption( 'date', $this->validateDate( $this->mDate ) );
$wgUser->setOption( 'searchlimit', $this->validateIntOrNull( $this->mSearch ) );
$wgUser->setOption( 'contextlines', $this->validateIntOrNull( $this->mSearchLines ) );
$wgUser->setOption( 'contextchars', $this->validateIntOrNull( $this->mSearchChars ) );
@@ -356,7 +361,7 @@ class PreferencesForm {
$this->mQuickbar = $wgUser->getOption( 'quickbar' );
$this->mSkin = Skin::normalizeKey( $wgUser->getOption( 'skin' ) );
$this->mMath = $wgUser->getOption( 'math' );
- $this->mDate = $wgUser->getOption( 'date' );
+ $this->mDate = $wgUser->getDatePreference();
$this->mRows = $wgUser->getOption( 'rows' );
$this->mCols = $wgUser->getOption( 'cols' );
$this->mStubs = $wgUser->getOption( 'stubthreshold' );
@@ -371,7 +376,7 @@ class PreferencesForm {
$this->mUnderline = $wgUser->getOption( 'underline' );
$this->mWatchlistDays = $wgUser->getOption( 'watchlistdays' );
- $togs = $wgLang->getUserToggles();
+ $togs = User::getToggles();
foreach ( $togs as $tname ) {
$ttext = wfMsg('tog-'.$tname);
$this->mToggles[$tname] = $wgUser->getOption( $tname );
@@ -470,8 +475,8 @@ class PreferencesForm {
$qbs = $wgLang->getQuickbarSettings();
$skinNames = $wgLang->getSkinNames();
$mathopts = $wgLang->getMathNames();
- $dateopts = $wgLang->getDateFormats();
- $togs = $wgLang->getUserToggles();
+ $dateopts = $wgLang->getDatePreferences();
+ $togs = User::getToggles();
$titleObj = Title::makeTitle( NS_SPECIAL, 'Preferences' );
$action = $titleObj->escapeLocalURL();
@@ -595,7 +600,7 @@ class PreferencesForm {
* Make sure the site language is in the list; a custom language code
* might not have a defined name...
*/
- $languages = $wgLang->getLanguageNames();
+ $languages = $wgLang->getLanguageNames( true );
if( !array_key_exists( $wgContLanguageCode, $languages ) ) {
$languages[$wgContLanguageCode] = $wgContLanguageCode;
}
@@ -610,12 +615,8 @@ class PreferencesForm {
$selbox = null;
foreach($languages as $code => $name) {
global $IP;
- /* only add languages that have a file */
- $langfile="$IP/languages/Language".str_replace('-', '_', ucfirst($code)).".php";
- if(file_exists($langfile) || $code == $wgContLanguageCode) {
- $sel = ($code == $selectedLang)? ' selected="selected"' : '';
- $selbox .= "<option value=\"$code\"$sel>$code - $name</option>\n";
- }
+ $sel = ($code == $selectedLang)? ' selected="selected"' : '';
+ $selbox .= "<option value=\"$code\"$sel>$code - $name</option>\n";
}
$wgOut->addHTML(
$this->addRow(
@@ -764,20 +765,20 @@ class PreferencesForm {
<legend>" . wfMsg( 'files' ) . "</legend>
<div><label for='wpImageSize'>" . wfMsg('imagemaxsize') . "</label> <select id='wpImageSize' name='wpImageSize'>");
- $imageLimitOptions = null;
- foreach ( $wgImageLimits as $index => $limits ) {
- $selected = ($index == $this->mImageSize) ? 'selected="selected"' : '';
- $imageLimitOptions .= "<option value=\"{$index}\" {$selected}>{$limits[0]}×{$limits[1]}". wfMsgHtml('unit-pixel') ."</option>\n";
- }
+ $imageLimitOptions = null;
+ foreach ( $wgImageLimits as $index => $limits ) {
+ $selected = ($index == $this->mImageSize) ? 'selected="selected"' : '';
+ $imageLimitOptions .= "<option value=\"{$index}\" {$selected}>{$limits[0]}×{$limits[1]}". wfMsgHtml('unit-pixel') ."</option>\n";
+ }
- $imageThumbOptions = null;
- $wgOut->addHTML( "{$imageLimitOptions}</select></div>
- <div><label for='wpThumbSize'>" . wfMsg('thumbsize') . "</label> <select name='wpThumbSize' id='wpThumbSize'>");
- foreach ( $wgThumbLimits as $index => $size ) {
- $selected = ($index == $this->mThumbSize) ? 'selected="selected"' : '';
- $imageThumbOptions .= "<option value=\"{$index}\" {$selected}>{$size}". wfMsgHtml('unit-pixel') ."</option>\n";
- }
- $wgOut->addHTML( "{$imageThumbOptions}</select></div></fieldset>\n\n");
+ $imageThumbOptions = null;
+ $wgOut->addHTML( "{$imageLimitOptions}</select></div>
+ <div><label for='wpThumbSize'>" . wfMsg('thumbsize') . "</label> <select name='wpThumbSize' id='wpThumbSize'>");
+ foreach ( $wgThumbLimits as $index => $size ) {
+ $selected = ($index == $this->mThumbSize) ? 'selected="selected"' : '';
+ $imageThumbOptions .= "<option value=\"{$index}\" {$selected}>{$size}". wfMsgHtml('unit-pixel') ."</option>\n";
+ }
+ $wgOut->addHTML( "{$imageThumbOptions}</select></div></fieldset>\n\n");
# Date format
#
@@ -789,9 +790,9 @@ class PreferencesForm {
if ($dateopts) {
$wgOut->addHTML( "<fieldset>\n<legend>" . wfMsg( 'dateformat' ) . "</legend>\n" );
$idCnt = 0;
- $epoch = '20010408091234';
- foreach($dateopts as $key => $option) {
- if( $key == MW_DATE_DEFAULT ) {
+ $epoch = '20010115161234'; # Wikipedia day
+ foreach( $dateopts as $key ) {
+ if( $key == 'default' ) {
$formatted = wfMsgHtml( 'datedefault' );
} else {
$formatted = htmlspecialchars( $wgLang->timeanddate( $epoch, false, $key ) );
@@ -816,8 +817,7 @@ class PreferencesForm {
) . "<tr><td colspan='2'>
<input type='button' value=\"" . wfMsg( 'guesstimezone' ) ."\"
onclick='javascript:guessTimezone()' id='guesstimezonebutton' style='display:none;' />
- </td></tr></table></fieldset>
- <div class='prefsectiontip'>¹" . wfMsg( 'timezonetext' ) . "</div>
+ </td></tr></table><div class='prefsectiontip'>¹" . wfMsg( 'timezonetext' ) . "</div></fieldset>
</fieldset>\n\n" );
# Editing
diff --git a/includes/SpecialRecentchanges.php b/includes/SpecialRecentchanges.php
index 97f810d9..8dfb68a5 100644
--- a/includes/SpecialRecentchanges.php
+++ b/includes/SpecialRecentchanges.php
@@ -319,7 +319,7 @@ function rcFilterByCategories ( &$rows , $categories , $any ) {
}
function rcOutputFeed( $rows, $feedFormat, $limit, $hideminor, $lastmod ) {
- global $messageMemc, $wgDBname, $wgFeedCacheTimeout;
+ global $messageMemc, $wgFeedCacheTimeout;
global $wgFeedClasses, $wgTitle, $wgSitename, $wgContLanguageCode;
if( !isset( $wgFeedClasses[$feedFormat] ) ) {
@@ -327,8 +327,8 @@ function rcOutputFeed( $rows, $feedFormat, $limit, $hideminor, $lastmod ) {
return false;
}
- $timekey = "$wgDBname:rcfeed:$feedFormat:timestamp";
- $key = "$wgDBname:rcfeed:$feedFormat:limit:$limit:minor:$hideminor";
+ $timekey = wfMemcKey( 'rcfeed', $feedFormat, 'timestamp' );
+ $key = wfMemcKey( 'rcfeed', $feedFormat, 'limit', $limit, 'minor', $hideminor );
$feedTitle = $wgSitename . ' - ' . wfMsgForContent( 'recentchanges' ) .
' [' . $wgContLanguageCode . ']';
@@ -481,7 +481,7 @@ function makeOptionsLink( $title, $override, $options ) {
global $wgUser, $wgContLang;
$sk = $wgUser->getSkin();
return $sk->makeKnownLink( $wgContLang->specialPage( 'Recentchanges' ),
- $title, wfArrayToCGI( $override, $options ) );
+ htmlspecialchars( $title ), wfArrayToCGI( $override, $options ) );
}
/**
@@ -624,7 +624,6 @@ function rcFormatDiffRow( $title, $oldid, $newid, $timestamp, $comment ) {
$fname = 'rcFormatDiff';
wfProfileIn( $fname );
- require_once( 'DifferenceEngine.php' );
$skin = $wgUser->getSkin();
$completeText = '<p>' . $skin->formatComment( $comment ) . "</p>\n";
diff --git a/includes/SpecialRecentchangeslinked.php b/includes/SpecialRecentchangeslinked.php
index 2a611c4d..59a3beb5 100644
--- a/includes/SpecialRecentchangeslinked.php
+++ b/includes/SpecialRecentchangeslinked.php
@@ -71,6 +71,14 @@ function wfSpecialRecentchangeslinked( $par = NULL ) {
$uid = $wgUser->getID();
+ $GROUPBY = "
+ GROUP BY rc_cur_id,rc_namespace,rc_title,
+ rc_user,rc_comment,rc_user_text,rc_timestamp,rc_minor,
+ rc_new, rc_id, rc_this_oldid, rc_last_oldid, rc_bot, rc_patrolled, rc_type
+" . ($uid ? ",wl_user" : "") . "
+ ORDER BY rc_timestamp DESC
+ LIMIT {$limit}";
+
// If target is a Category, use categorylinks and invert from and to
if( $nt->getNamespace() == NS_CATEGORY ) {
$catkey = $dbr->addQuotes( $nt->getDBKey() );
@@ -97,11 +105,7 @@ function wfSpecialRecentchangeslinked( $par = NULL ) {
{$cmq}
AND cl_from=rc_cur_id
AND cl_to=$catkey
- GROUP BY rc_cur_id,rc_namespace,rc_title,
- rc_user,rc_comment,rc_user_text,rc_timestamp,rc_minor,
- rc_new
- ORDER BY rc_timestamp DESC
- LIMIT {$limit};
+$GROUPBY
";
} else {
$sql =
@@ -129,11 +133,8 @@ function wfSpecialRecentchangeslinked( $par = NULL ) {
AND pl_namespace=rc_namespace
AND pl_title=rc_title
AND pl_from=$id
-GROUP BY rc_cur_id,rc_namespace,rc_title,
- rc_user,rc_comment,rc_user_text,rc_timestamp,rc_minor,
- rc_new
-ORDER BY rc_timestamp DESC
- LIMIT {$limit}";
+$GROUPBY
+";
}
$res = $dbr->query( $sql, $fname );
@@ -156,9 +157,6 @@ ORDER BY rc_timestamp DESC
if ( 0 == $count ) { break; }
$obj = $dbr->fetchObject( $res );
--$count;
-# print_r ( $obj ) ;
-# print "<br/>\n" ;
-
$rc = RecentChange::newFromRow( $obj );
$rc->counter = $counter++;
$s .= $list->recentChangesLine( $rc , !empty( $obj->wl_user) );
diff --git a/includes/SpecialRevisiondelete.php b/includes/SpecialRevisiondelete.php
index 7fa8bbb4..afbb589c 100644
--- a/includes/SpecialRevisiondelete.php
+++ b/includes/SpecialRevisiondelete.php
@@ -13,7 +13,7 @@ function wfSpecialRevisiondelete( $par = null ) {
global $wgOut, $wgRequest, $wgUser;
$target = $wgRequest->getVal( 'target' );
- $oldid = $wgRequest->getInt( 'oldid' );
+ $oldid = $wgRequest->getIntArray( 'oldid' );
$sk = $wgUser->getSkin();
$page = Title::newFromUrl( $target );
@@ -23,6 +23,11 @@ function wfSpecialRevisiondelete( $par = null ) {
return;
}
+ if( is_null( $oldid ) ) {
+ $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+ return;
+ }
+
$form = new RevisionDeleteForm( $wgRequest );
if( $wgRequest->wasPosted() ) {
$form->submit( $wgRequest );
@@ -58,13 +63,15 @@ class RevisionDeleteForm {
function show( $request ) {
global $wgOut, $wgUser;
- $first = $this->revisions[0];
-
$wgOut->addWikiText( wfMsg( 'revdelete-selected', $this->page->getPrefixedText() ) );
$wgOut->addHtml( "<ul>" );
foreach( $this->revisions as $revid ) {
$rev = Revision::newFromTitle( $this->page, $revid );
+ if( !isset( $rev ) ) {
+ $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+ return;
+ }
$wgOut->addHtml( $this->historyLine( $rev ) );
$bitfields[] = $rev->mDeleted; // FIXME
}
@@ -85,7 +92,8 @@ class RevisionDeleteForm {
$special = Title::makeTitle( NS_SPECIAL, 'Revisiondelete' );
$wgOut->addHtml( wfElement( 'form', array(
'method' => 'post',
- 'action' => $special->getLocalUrl( 'action=submit' ) ) ) );
+ 'action' => $special->getLocalUrl( 'action=submit' ) ),
+ null ) );
$wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'revdelete-legend' ) . '</legend>' );
foreach( $this->checks as $item ) {
@@ -180,6 +188,9 @@ class RevisionDeleter {
// To work!
foreach( $items as $revid ) {
$rev = Revision::newFromId( $revid );
+ if( !isset( $rev ) ) {
+ return false;
+ }
$this->updateRevision( $rev, $bitfield );
$this->updateRecentChanges( $rev, $bitfield );
diff --git a/includes/SpecialSearch.php b/includes/SpecialSearch.php
index 4db27e87..057b487c 100644
--- a/includes/SpecialSearch.php
+++ b/includes/SpecialSearch.php
@@ -77,6 +77,7 @@ class SpecialSearch {
function goResult( $term ) {
global $wgOut;
global $wgGoToEdit;
+ global $wgContLang;
$this->setupPage( $term );
@@ -110,7 +111,7 @@ class SpecialSearch {
$editurl = $t->escapeLocalURL( 'action=edit' );
}
}
- $wgOut->addWikiText( wfMsg( 'noexactmatch', $term ) );
+ $wgOut->addWikiText( wfMsg( 'noexactmatch', wfEscapeWikiText( $term ) ) );
return $this->showResults( $term );
}
@@ -151,7 +152,7 @@ class SpecialSearch {
wfMsg( 'googlesearch',
htmlspecialchars( $term ),
htmlspecialchars( $wgInputEncoding ),
- htmlspecialchars( wfMsg( 'search' ) )
+ htmlspecialchars( wfMsg( 'searchbutton' ) )
)
);
wfProfileOut( $fname );
diff --git a/includes/SpecialShortpages.php b/includes/SpecialShortpages.php
index d8e13c7b..34b3505b 100644
--- a/includes/SpecialShortpages.php
+++ b/includes/SpecialShortpages.php
@@ -65,6 +65,9 @@ class ShortPagesPage extends QueryPage {
$dm = $wgContLang->getDirMark();
$title = Title::makeTitleSafe( $result->namespace, $result->title );
+ if ( !$title ) {
+ return '<!-- Invalid title ' . htmlspecialchars( "{$result->namespace}:{$result->title}" ). '-->';
+ }
$hlink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' );
$plink = $this->isCached()
? $skin->makeLinkObj( $title )
diff --git a/includes/SpecialSpecialpages.php b/includes/SpecialSpecialpages.php
index 0b53db73..6a01cd08 100644
--- a/includes/SpecialSpecialpages.php
+++ b/includes/SpecialSpecialpages.php
@@ -9,29 +9,16 @@
*
*/
function wfSpecialSpecialpages() {
- global $wgOut, $wgUser, $wgAvailableRights;
+ global $wgOut, $wgUser;
$wgOut->setRobotpolicy( 'index,nofollow' );
$sk = $wgUser->getSkin();
- # Get listable pages, in a 2-d array with the first dimension being user right
- $pages = SpecialPage::getPages();
-
/** Pages available to all */
- wfSpecialSpecialpages_gen($pages[''],'spheading',$sk);
+ wfSpecialSpecialpages_gen( SpecialPage::getRegularPages(), 'spheading', $sk );
/** Restricted special pages */
- $rpages = array();
- foreach($wgAvailableRights as $right) {
- /** only show pages a user can access */
- if( $wgUser->isAllowed($right) ) {
- /** some rights might not have any special page associated */
- if(isset($pages[$right])) {
- $rpages = array_merge( $rpages, $pages[$right] );
- }
- }
- }
- wfSpecialSpecialpages_gen( $rpages, 'restrictedpheading', $sk );
+ wfSpecialSpecialpages_gen( SpecialPage::getRestrictedPages(), 'restrictedpheading', $sk );
}
/**
diff --git a/includes/SpecialStatistics.php b/includes/SpecialStatistics.php
index 5903546a..4a51efd9 100644
--- a/includes/SpecialStatistics.php
+++ b/includes/SpecialStatistics.php
@@ -75,12 +75,32 @@ function wfSpecialStatistics() {
$text .= wfMsg( 'userstatstext',
$wgLang->formatNum( $users ),
$wgLang->formatNum( $admins ),
- '[[' . wfMsgForContent( 'administrators' ) . ']]',
- // should logically be after #admins, damn backwards compatability!
- $wgLang->formatNum( sprintf( '%.2f', $admins / $users * 100 ) )
+ '[[' . wfMsgForContent( 'grouppage-sysop' ) . ']]', # TODO somehow remove, kept for backwards compatibility
+ $wgLang->formatNum( sprintf( '%.2f', $admins / $users * 100 ) ),
+ User::makeGroupLinkWiki( 'sysop' )
);
$wgOut->addWikiText( $text );
+
+ global $wgDisableCounters, $wgMiserMode, $wgUser, $wgLang, $wgContLang;
+ if( !$wgDisableCounters && !$wgMiserMode ) {
+ $sql = "SELECT page_namespace, page_title, page_counter FROM {$page} WHERE page_is_redirect = 0 AND page_counter > 0 ORDER BY page_counter DESC";
+ $sql = $dbr->limitResult($sql, 10, 0);
+ $res = $dbr->query( $sql, $fname );
+ if( $res ) {
+ $wgOut->addHtml( '<h2>' . wfMsgHtml( 'statistics-mostpopular' ) . '</h2>' );
+ $skin =& $wgUser->getSkin();
+ $wgOut->addHtml( '<ol>' );
+ while( $row = $dbr->fetchObject( $res ) ) {
+ $link = $skin->makeKnownLinkObj( Title::makeTitleSafe( $row->page_namespace, $row->page_title ) );
+ $dirmark = $wgContLang->getDirMark();
+ $wgOut->addHtml( '<li>' . $link . $dirmark . ' [' . $wgLang->formatNum( $row->page_counter ) . ']</li>' );
+ }
+ $wgOut->addHtml( '</ol>' );
+ $dbr->freeResult( $res );
+ }
+ }
+
}
}
?>
diff --git a/includes/SpecialUndelete.php b/includes/SpecialUndelete.php
index 695c8c29..8e0291ec 100644
--- a/includes/SpecialUndelete.php
+++ b/includes/SpecialUndelete.php
@@ -77,7 +77,6 @@ class PageArchive {
* @fixme Does this belong in Image for fuller encapsulation?
*/
function listFiles() {
- $fname = __CLASS__ . '::' . __FUNCTION__;
if( $this->title->getNamespace() == NS_IMAGE ) {
$dbr =& wfGetDB( DB_SLAVE );
$res = $dbr->select( 'filearchive',
@@ -93,7 +92,7 @@ class PageArchive {
'fa_user_text',
'fa_timestamp' ),
array( 'fa_name' => $this->title->getDbKey() ),
- $fname,
+ __METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' ) );
$ret = $dbr->resultObject( $res );
return $ret;
@@ -108,14 +107,13 @@ class PageArchive {
* @return string
*/
function getRevisionText( $timestamp ) {
- $fname = 'PageArchive::getRevisionText';
$dbr =& wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'archive',
array( 'ar_text', 'ar_flags', 'ar_text_id' ),
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDbkey(),
'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
- $fname );
+ __METHOD__ );
if( $row ) {
return $this->getTextFromRow( $row );
} else {
@@ -127,8 +125,6 @@ class PageArchive {
* Get the text from an archive row containing ar_text, ar_flags and ar_text_id
*/
function getTextFromRow( $row ) {
- $fname = 'PageArchive::getTextFromRow';
-
if( is_null( $row->ar_text_id ) ) {
// An old row from MediaWiki 1.4 or previous.
// Text is embedded in this row in classic compression format.
@@ -139,7 +135,7 @@ class PageArchive {
$text = $dbr->selectRow( 'text',
array( 'old_text', 'old_flags' ),
array( 'old_id' => $row->ar_text_id ),
- $fname );
+ __METHOD__ );
return Revision::getRevisionText( $text );
}
}
@@ -252,7 +248,6 @@ class PageArchive {
private function undeleteRevisions( $timestamps ) {
global $wgParser, $wgDBtype;
- $fname = __CLASS__ . '::' . __FUNCTION__;
$restoreAll = empty( $timestamps );
$dbw =& wfGetDB( DB_MASTER );
@@ -267,7 +262,7 @@ class PageArchive {
array( 'page_id', 'page_latest' ),
array( 'page_namespace' => $this->title->getNamespace(),
'page_title' => $this->title->getDBkey() ),
- $fname,
+ __METHOD__,
$options );
if( $page ) {
# Page already exists. Import the history, and if necessary
@@ -311,12 +306,12 @@ class PageArchive {
'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
$oldones ),
- $fname,
+ __METHOD__,
/* options */ array(
'ORDER BY' => 'ar_timestamp' )
);
if( $dbw->numRows( $result ) < count( $timestamps ) ) {
- wfDebug( "$fname: couldn't find all requested rows\n" );
+ wfDebug( __METHOD__.": couldn't find all requested rows\n" );
return false;
}
@@ -355,17 +350,11 @@ class PageArchive {
if( $revision ) {
# FIXME: Update latest if newer as well...
if( $newid ) {
- # FIXME: update article count if changed...
+ // Attach the latest revision to the page...
$article->updateRevisionOn( $dbw, $revision, $previousRevId );
-
- # Finally, clean up the link tables
- $options = new ParserOptions;
- $parserOutput = $wgParser->parse( $revision->getText(), $this->title, $options,
- true, true, $newRevId );
- $u = new LinksUpdate( $this->title, $parserOutput );
- $u->doUpdate();
-
- #TODO: SearchUpdate, etc.
+
+ // Update site stats, link tables, etc
+ $article->createUpdates( $revision );
}
if( $newid ) {
@@ -383,7 +372,7 @@ class PageArchive {
'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
$oldones ),
- $fname );
+ __METHOD__ );
return $restored;
}
@@ -401,9 +390,10 @@ class UndeleteForm {
function UndeleteForm( &$request, $par = "" ) {
global $wgUser;
- $this->mAction = $request->getText( 'action' );
- $this->mTarget = $request->getText( 'target' );
- $this->mTimestamp = $request->getText( 'timestamp' );
+ $this->mAction = $request->getVal( 'action' );
+ $this->mTarget = $request->getVal( 'target' );
+ $time = $request->getVal( 'timestamp' );
+ $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
$this->mFile = $request->getVal( 'file' );
$posted = $request->wasPosted() &&
@@ -463,7 +453,6 @@ class UndeleteForm {
/* private */ function showList() {
global $wgLang, $wgContLang, $wgUser, $wgOut;
- $fname = "UndeleteForm::showList";
# List undeletable articles
$result = PageArchive::listAllPages();
@@ -479,14 +468,10 @@ class UndeleteForm {
$undelete =& Title::makeTitle( NS_SPECIAL, 'Undelete' );
$wgOut->addHTML( "<ul>\n" );
while( $row = $result->fetchObject() ) {
- $n = ($row->ar_namespace ?
- ($wgContLang->getNsText( $row->ar_namespace ) . ":") : "").
- $row->ar_title;
- $link = $sk->makeKnownLinkObj( $undelete,
- htmlspecialchars( $n ), "target=" . urlencode( $n ) );
- $revisions = htmlspecialchars( wfMsg( "undeleterevisions",
- $wgLang->formatNum( $row->count ) ) );
- $wgOut->addHTML( "<li>$link ($revisions)</li>\n" );
+ $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
+ $link = $sk->makeKnownLinkObj( $undelete, htmlspecialchars( $title->getPrefixedText() ), 'target=' . $title->getPrefixedUrl() );
+ $revs = wfMsgHtml( 'undeleterevisions', $wgLang->formatNum( $row->count ) );
+ $wgOut->addHtml( "<li>{$link} ({$revs})</li>\n" );
}
$result->free();
$wgOut->addHTML( "</ul>\n" );
@@ -496,11 +481,10 @@ class UndeleteForm {
/* private */ function showRevision( $timestamp ) {
global $wgLang, $wgUser, $wgOut;
- $fname = "UndeleteForm::showRevision";
if(!preg_match("/[0-9]{14}/",$timestamp)) return 0;
- $archive =& new PageArchive( $this->mTargetObj );
+ $archive = new PageArchive( $this->mTargetObj );
$text = $archive->getRevisionText( $timestamp );
$wgOut->setPagetitle( wfMsg( "undeletepage" ) );
@@ -619,8 +603,7 @@ class UndeleteForm {
# Show relevant lines from the deletion log:
$wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
- require_once( 'SpecialLog.php' );
- $logViewer =& new LogViewer(
+ $logViewer = new LogViewer(
new LogReader(
new FauxRequest(
array( 'page' => $this->mTargetObj->getPrefixedText(),
diff --git a/includes/SpecialUnlockdb.php b/includes/SpecialUnlockdb.php
index a10d1ee0..6627f75f 100644
--- a/includes/SpecialUnlockdb.php
+++ b/includes/SpecialUnlockdb.php
@@ -11,10 +11,11 @@
function wfSpecialUnlockdb() {
global $wgUser, $wgOut, $wgRequest;
- if ( ! $wgUser->isAllowed('siteadmin') ) {
- $wgOut->developerRequired();
+ if( !$wgUser->isAllowed( 'siteadmin' ) ) {
+ $wgOut->permissionRequired( 'siteadmin' );
return;
}
+
$action = $wgRequest->getVal( 'action' );
$f = new DBUnlockForm();
@@ -38,6 +39,12 @@ class DBUnlockForm {
{
global $wgOut, $wgUser;
+ global $wgReadOnlyFile;
+ if( !file_exists( $wgReadOnlyFile ) ) {
+ $wgOut->addWikiText( wfMsg( 'databasenotlocked' ) );
+ return;
+ }
+
$wgOut->setPagetitle( wfMsg( "unlockdb" ) );
$wgOut->addWikiText( wfMsg( "unlockdbtext" ) );
diff --git a/includes/SpecialUpload.php b/includes/SpecialUpload.php
index 06336df9..ade58056 100644
--- a/includes/SpecialUpload.php
+++ b/includes/SpecialUpload.php
@@ -5,10 +5,7 @@
* @subpackage SpecialPage
*/
-/**
- *
- */
-require_once 'Image.php';
+
/**
* Entry point
*/
@@ -30,7 +27,7 @@ class UploadForm {
var $mUploadFile, $mUploadDescription, $mLicense ,$mIgnoreWarning, $mUploadError;
var $mUploadSaveName, $mUploadTempName, $mUploadSize, $mUploadOldVersion;
var $mUploadCopyStatus, $mUploadSource, $mReUpload, $mAction, $mUpload;
- var $mOname, $mSessionKey, $mStashed, $mDestFile, $mRemoveTempFile;
+ var $mOname, $mSessionKey, $mStashed, $mDestFile, $mRemoveTempFile, $mSourceType;
/**#@-*/
/**
@@ -39,6 +36,7 @@ class UploadForm {
* @param $request Data posted.
*/
function UploadForm( &$request ) {
+ global $wgAllowCopyUploads;
$this->mDestFile = $request->getText( 'wpDestFile' );
if( !$request->wasPosted() ) {
@@ -55,6 +53,7 @@ class UploadForm {
$this->mUploadCopyStatus = $request->getText( 'wpUploadCopyStatus' );
$this->mUploadSource = $request->getText( 'wpUploadSource' );
$this->mWatchthis = $request->getBool( 'wpWatchthis' );
+ $this->mSourceType = $request->getText( 'wpSourceType' );
wfDebug( "UploadForm: watchthis is: '$this->mWatchthis'\n" );
$this->mAction = $request->getVal( 'action' );
@@ -79,17 +78,113 @@ class UploadForm {
/**
*Check for a newly uploaded file.
*/
- $this->mUploadTempName = $request->getFileTempName( 'wpUploadFile' );
- $this->mUploadSize = $request->getFileSize( 'wpUploadFile' );
- $this->mOname = $request->getFileName( 'wpUploadFile' );
- $this->mUploadError = $request->getUploadError( 'wpUploadFile' );
- $this->mSessionKey = false;
- $this->mStashed = false;
- $this->mRemoveTempFile = false; // PHP will handle this
+ if( $wgAllowCopyUploads && $this->mSourceType == 'web' ) {
+ $this->initializeFromUrl( $request );
+ } else {
+ $this->initializeFromUpload( $request );
+ }
}
}
/**
+ * Initialize the uploaded file from PHP data
+ * @access private
+ */
+ function initializeFromUpload( $request ) {
+ $this->mUploadTempName = $request->getFileTempName( 'wpUploadFile' );
+ $this->mUploadSize = $request->getFileSize( 'wpUploadFile' );
+ $this->mOname = $request->getFileName( 'wpUploadFile' );
+ $this->mUploadError = $request->getUploadError( 'wpUploadFile' );
+ $this->mSessionKey = false;
+ $this->mStashed = false;
+ $this->mRemoveTempFile = false; // PHP will handle this
+ }
+
+ /**
+ * Copy a web file to a temporary file
+ * @access private
+ */
+ function initializeFromUrl( $request ) {
+ global $wgTmpDirectory, $wgMaxUploadSize;
+ $url = $request->getText( 'wpUploadFileURL' );
+ $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' );
+
+ $this->mUploadTempName = $local_file;
+ $this->mUploadError = $this->curlCopy( $url, $local_file );
+ $this->mUploadSize = $this->mUploadTempFileSize;
+ $this->mOname = array_pop( explode( '/', $url ) );
+ $this->mSessionKey = false;
+ $this->mStashed = false;
+
+ // PHP won't auto-cleanup the file
+ $this->mRemoveTempFile = file_exists( $local_file );
+ }
+
+ /**
+ * Safe copy from URL
+ * Returns true if there was an error, false otherwise
+ */
+ private function curlCopy( $url, $dest ) {
+ global $wgMaxUploadSize, $wgUser;
+
+ if( !$wgUser->isAllowed( 'upload_by_url' ) ) {
+ $wgOut->permissionRequired( 'upload_by_url' );
+ return true;
+ }
+
+ # Maybe remove some pasting blanks :-)
+ $url = strtolower( trim( $url ) );
+ if( substr( $url, 0, 7 ) != 'http://' && substr( $url, 0, 6 ) != 'ftp://' ) {
+ # Only HTTP or FTP URLs
+ return true;
+ }
+
+ # Open temporary file
+ $this->mUploadTempFileSize = 0;
+ $this->mUploadTempFile = @fopen( $this->mUploadTempName, "wb" );
+ if( $this->mUploadTempFile === false ) {
+ # Could not open temporary file to write in
+ return true;
+ }
+
+ $ch = curl_init();
+ curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug
+ curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout
+ curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed
+ curl_setopt( $ch, CURLOPT_URL, $url);
+ curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) );
+ curl_exec( $ch );
+ $error = curl_errno( $ch ) ? true : false;
+# if ( $error ) print curl_error ( $ch ) ; # Debugging output
+ curl_close( $ch );
+
+ fclose( $this->mUploadTempFile );
+ unset( $this->mUploadTempFile );
+ if( $error ) {
+ unlink( $dest );
+ }
+
+ return $error;
+ }
+
+ /**
+ * Callback function for CURL-based web transfer
+ * Write data to file unless we've passed the length limit;
+ * if so, abort immediately.
+ * @access private
+ */
+ function uploadCurlCallback( $ch, $data ) {
+ global $wgMaxUploadSize;
+ $length = strlen( $data );
+ $this->mUploadTempFileSize += $length;
+ if( $this->mUploadTempFileSize > $wgMaxUploadSize ) {
+ return 0;
+ }
+ fwrite( $this->mUploadTempFile, $data );
+ return $length;
+ }
+
+ /**
* Start doing stuff
* @access public
*/
@@ -104,13 +199,12 @@ class UploadForm {
}
# Check permissions
- if( $wgUser->isLoggedIn() ) {
- if( !$wgUser->isAllowed( 'upload' ) ) {
+ if( !$wgUser->isAllowed( 'upload' ) ) {
+ if( !$wgUser->isLoggedIn() ) {
+ $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
+ } else {
$wgOut->permissionRequired( 'upload' );
- return;
}
- } else {
- $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
return;
}
@@ -126,17 +220,17 @@ class UploadForm {
}
/** Check if the image directory is writeable, this is a common mistake */
- if ( !is_writeable( $wgUploadDirectory ) ) {
+ if( !is_writeable( $wgUploadDirectory ) ) {
$wgOut->addWikiText( wfMsg( 'upload_directory_read_only', $wgUploadDirectory ) );
return;
}
if( $this->mReUpload ) {
- if ( !$this->unsaveUploadedFile() ) {
+ if( !$this->unsaveUploadedFile() ) {
return;
}
$this->mainUploadForm();
- } else if ( 'submit' == $this->mAction || $this->mUpload ) {
+ } else if( 'submit' == $this->mAction || $this->mUpload ) {
$this->processUpload();
} else {
$this->mainUploadForm();
@@ -156,7 +250,7 @@ class UploadForm {
global $wgUser, $wgOut;
/* Check for PHP error if any, requires php 4.2 or newer */
- if ( $this->mUploadError == 1/*UPLOAD_ERR_INI_SIZE*/ ) {
+ if( $this->mUploadError == 1/*UPLOAD_ERR_INI_SIZE*/ ) {
$this->mainUploadForm( wfMsgHtml( 'largefileserver' ) );
return;
}
@@ -170,7 +264,7 @@ class UploadForm {
}
# Chop off any directories in the given filename
- if ( $this->mDestFile ) {
+ if( $this->mDestFile ) {
$basename = wfBaseName( $this->mDestFile );
} else {
$basename = wfBaseName( $this->mOname );
@@ -196,7 +290,7 @@ class UploadForm {
$partname .= '.' . $ext[$i];
}
- if ( strlen( $partname ) < 3 ) {
+ if( strlen( $partname ) < 3 ) {
$this->mainUploadForm( wfMsgHtml( 'minlength' ) );
return;
}
@@ -363,7 +457,9 @@ class UploadForm {
* is a PHP-managed upload temporary
*/
function saveUploadedFile( $saveName, $tempName, $useRename = false ) {
- global $wgOut;
+ global $wgOut, $wgAllowCopyUploads;
+
+ if ( !$useRename AND $wgAllowCopyUploads AND $this->mSourceType == 'web' ) $useRename = true;
$fname= "SpecialUpload::saveUploadedFile";
@@ -586,6 +682,7 @@ class UploadForm {
function mainUploadForm( $msg='' ) {
global $wgOut, $wgUser;
global $wgUseCopyrightUpload;
+ global $wgRequest, $wgAllowCopyUploads;
$cols = intval($wgUser->getOption( 'cols' ));
$ew = $wgUser->getOption( 'editwidth' );
@@ -624,13 +721,33 @@ class UploadForm {
? 'checked="checked"'
: '';
+ // Prepare form for upload or upload/copy
+ if( $wgAllowCopyUploads && $wgUser->isAllowed( 'upload_by_url' ) ) {
+ $source_comment = wfMsgHtml( 'upload_source_url' );
+ $filename_form =
+ "<input type='radio' id='wpSourceTypeFile' name='wpSourceType' value='file' onchange='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\")' checked />" .
+ "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' onfocus='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\");toggle_element_check(\"wpSourceTypeFile\",\"wpSourceTypeURL\")'" .
+ ($this->mDestFile?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") . "size='40' />" .
+ wfMsgHTML( 'upload_source_file' ) . "<br/>" .
+ "<input type='radio' id='wpSourceTypeURL' name='wpSourceType' value='web' onchange='toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\")' />" .
+ "<input tabindex='1' type='text' name='wpUploadFileURL' id='wpUploadFileURL' onfocus='toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\");toggle_element_check(\"wpSourceTypeURL\",\"wpSourceTypeFile\")'" .
+ ($this->mDestFile?"":"onchange='fillDestFilename(\"wpUploadFileURL\")' ") . "size='40' DISABLED />" .
+ wfMsgHtml( 'upload_source_url' ) ;
+ } else {
+ $filename_form =
+ "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " .
+ ($this->mDestFile?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") .
+ "size='40' />" .
+ "<input type='hidden' name='wpSourceType' value='file' />" ;
+ }
+
$wgOut->addHTML( "
<form id='upload' method='post' enctype='multipart/form-data' action=\"$action\">
<table border='0'>
<tr>
- <td align='right'><label for='wpUploadFile'>{$sourcefilename}:</label></td>
+ <td align='right' valign='top'><label for='wpUploadFile'>{$sourcefilename}:</label></td>
<td align='left'>
- <input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " . ($this->mDestFile?"":"onchange='fillDestFilename()' ") . "size='40' />
+ {$filename_form}
</td>
</tr>
<tr>
@@ -687,7 +804,7 @@ class UploadForm {
<td></td>
<td>
<input tabindex='7' type='checkbox' name='wpWatchthis' id='wpWatchthis' $watchChecked value='true' />
- <label for='wpWatchthis'>" . wfMsgHtml( 'watchthis' ) . "</label>
+ <label for='wpWatchthis'>" . wfMsgHtml( 'watchthisupload' ) . "</label>
<input tabindex='8' type='checkbox' name='wpIgnoreWarning' id='wpIgnoreWarning' value='true' />
<label for='wpIgnoreWarning'>" . wfMsgHtml( 'ignorewarnings' ) . "</label>
</td>
@@ -767,7 +884,7 @@ class UploadForm {
*/
function verify( $tmpfile, $extension ) {
#magically determine mime type
- $magic=& wfGetMimeMagic();
+ $magic=& MimeMagic::singleton();
$mime= $magic->guessMimeType($tmpfile,false);
$fname= "SpecialUpload::verify";
@@ -816,7 +933,7 @@ class UploadForm {
function verifyExtension( $mime, $extension ) {
$fname = 'SpecialUpload::verifyExtension';
- $magic =& wfGetMimeMagic();
+ $magic =& MimeMagic::singleton();
if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' )
if ( ! $magic->isRecognizableExtension( $extension ) ) {
@@ -1106,4 +1223,6 @@ class UploadForm {
}
}
+
+
?>
diff --git a/includes/SpecialUploadMogile.php b/includes/SpecialUploadMogile.php
index 51a6dd28..05bfca08 100644
--- a/includes/SpecialUploadMogile.php
+++ b/includes/SpecialUploadMogile.php
@@ -8,7 +8,6 @@
/**
*
*/
-require_once( 'SpecialUpload.php' );
require_once( 'MogileFS.php' );
/**
diff --git a/includes/SpecialUserlogin.php b/includes/SpecialUserlogin.php
index 4ee35b1b..574579cc 100644
--- a/includes/SpecialUserlogin.php
+++ b/includes/SpecialUserlogin.php
@@ -24,7 +24,17 @@ function wfSpecialUserlogin() {
* @package MediaWiki
* @subpackage SpecialPage
*/
+
class LoginForm {
+
+ const SUCCESS = 0;
+ const NO_NAME = 1;
+ const ILLEGAL = 2;
+ const WRONG_PLUGIN_PASS = 3;
+ const NOT_EXISTS = 4;
+ const WRONG_PASS = 5;
+ const EMPTY_PASS = 6;
+
var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted;
var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword;
var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
@@ -185,8 +195,8 @@ class LoginForm {
function addNewAccountInternal() {
global $wgUser, $wgOut;
global $wgEnableSorbs, $wgProxyWhitelist;
- global $wgMemc, $wgAccountCreationThrottle, $wgDBname;
- global $wgAuth, $wgMinimalPasswordLength, $wgReservedUsernames;
+ global $wgMemc, $wgAccountCreationThrottle;
+ global $wgAuth, $wgMinimalPasswordLength;
// If the user passes an invalid domain, something is fishy
if( !$wgAuth->validDomain( $this->mDomain ) ) {
@@ -227,7 +237,7 @@ class LoginForm {
$name = trim( $this->mName );
$u = User::newFromName( $name );
- if ( is_null( $u ) || in_array( $u->getName(), $wgReservedUsernames ) ) {
+ if ( is_null( $u ) || !User::isCreatableName( $u->getName() ) ) {
$this->mainLoginForm( wfMsg( 'noname' ) );
return false;
}
@@ -248,7 +258,7 @@ class LoginForm {
}
if ( $wgAccountCreationThrottle ) {
- $key = $wgDBname.':acctcreate:ip:'.$ip;
+ $key = wfMemcKey( 'acctcreate', 'ip', $ip );
$value = $wgMemc->incr( $key );
if ( !$value ) {
$wgMemc->set( $key, 1, 86400 );
@@ -305,17 +315,16 @@ class LoginForm {
/**
* @private
*/
- function processLogin() {
- global $wgUser, $wgAuth, $wgReservedUsernames;
-
+
+ function authenticateUserData()
+ {
+ global $wgUser, $wgAuth;
if ( '' == $this->mName ) {
- $this->mainLoginForm( wfMsg( 'noname' ) );
- return;
+ return self::NO_NAME;
}
$u = User::newFromName( $this->mName );
- if( is_null( $u ) || in_array( $u->getName(), $wgReservedUsernames ) ) {
- $this->mainLoginForm( wfMsg( 'noname' ) );
- return;
+ if( is_null( $u ) || !User::isUsableName( $u->getName() ) ) {
+ return self::ILLEGAL;
}
if ( 0 == $u->getID() ) {
global $wgAuth;
@@ -328,42 +337,67 @@ class LoginForm {
if ( $wgAuth->authenticate( $u->getName(), $this->mPassword ) ) {
$u =& $this->initUser( $u );
} else {
- $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
- return;
+ return self::WRONG_PLUGIN_PASS;
}
} else {
- $this->mainLoginForm( wfMsg( 'nosuchuser', $u->getName() ) );
- return;
+ return self::NOT_EXISTS;
}
} else {
$u->loadFromDatabase();
}
if (!$u->checkPassword( $this->mPassword )) {
- $this->mainLoginForm( wfMsg( $this->mPassword == '' ? 'wrongpasswordempty' : 'wrongpassword' ) );
- return;
+ return '' == $this->mPassword ? self::EMPTY_PASS : self::WRONG_PASS;
}
+ else
+ {
+ $wgAuth->updateUser( $u );
+ $wgUser = $u;
- # We've verified now, update the real record
- #
- if ( $this->mRemember ) {
- $r = 1;
- } else {
- $r = 0;
+ return self::SUCCESS;
}
- $u->setOption( 'rememberpassword', $r );
-
- $wgAuth->updateUser( $u );
+ }
+
+ function processLogin() {
+ global $wgUser, $wgAuth;
- $wgUser = $u;
- $wgUser->setCookies();
+ switch ($this->authenticateUserData())
+ {
+ case self::SUCCESS:
+ # We've verified now, update the real record
+ if( (bool)$this->mRemember != (bool)$wgUser->getOption( 'rememberpassword' ) ) {
+ $wgUser->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
+ $wgUser->saveSettings();
+ } else {
+ $wgUser->invalidateCache();
+ }
+ $wgUser->setCookies();
- $wgUser->saveSettings();
+ if( $this->hasSessionCookie() ) {
+ return $this->successfulLogin( wfMsg( 'loginsuccess', $wgUser->getName() ) );
+ } else {
+ return $this->cookieRedirectCheck( 'login' );
+ }
+ break;
- if( $this->hasSessionCookie() ) {
- return $this->successfulLogin( wfMsg( 'loginsuccess', $wgUser->getName() ) );
- } else {
- return $this->cookieRedirectCheck( 'login' );
+ case self::NO_NAME:
+ case self::ILLEGAL:
+ $this->mainLoginForm( wfMsg( 'noname' ) );
+ break;
+ case self::WRONG_PLUGIN_PASS:
+ $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
+ break;
+ case self::NOT_EXISTS:
+ $this->mainLoginForm( wfMsg( 'nosuchuser', htmlspecialchars( $this->mName ) ) );
+ break;
+ case self::WRONG_PASS:
+ $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
+ break;
+ case self::EMPTY_PASS:
+ $this->mainLoginForm( wfMsg( 'wrongpasswordempty' ) );
+ break;
+ default:
+ wfDebugDieBacktrace( "Unhandled case value" );
}
}
@@ -413,7 +447,7 @@ class LoginForm {
global $wgServer, $wgScript;
if ( '' == $u->getEmail() ) {
- return wfMsg( 'noemail', $u->getName() );
+ return new WikiError( wfMsg( 'noemail', $u->getName() ) );
}
$np = $u->randomPassword();
@@ -470,6 +504,27 @@ class LoginForm {
$wgOut->returnToMain( false );
}
+ /** */
+ function userBlockedMessage() {
+ global $wgOut;
+
+ # Let's be nice about this, it's likely that this feature will be used
+ # for blocking large numbers of innocent people, e.g. range blocks on
+ # schools. Don't blame it on the user. There's a small chance that it
+ # really is the user's fault, i.e. the username is blocked and they
+ # haven't bothered to log out before trying to create an account to
+ # evade it, but we'll leave that to their guilty conscience to figure
+ # out.
+
+ $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) );
+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
+ $wgOut->setArticleRelated( false );
+
+ $ip = wfGetIP();
+ $wgOut->addWikiText( wfMsg( 'cantcreateaccounttext', $ip ) );
+ $wgOut->returnToMain( false );
+ }
+
/**
* @private
*/
@@ -477,9 +532,14 @@ class LoginForm {
global $wgUser, $wgOut, $wgAllowRealName, $wgEnableEmail;
global $wgCookiePrefix, $wgAuth, $wgLoginLanguageSelector;
- if ( $this->mType == 'signup' && !$wgUser->isAllowedToCreateAccount() ) {
- $this->userNotPrivilegedMessage();
- return;
+ if ( $this->mType == 'signup' ) {
+ if ( !$wgUser->isAllowed( 'createaccount' ) ) {
+ $this->userNotPrivilegedMessage();
+ return;
+ } elseif ( $wgUser->isBlockedFromCreateAccount() ) {
+ $this->userBlockedMessage();
+ return;
+ }
}
if ( '' == $this->mName ) {
@@ -492,16 +552,13 @@ class LoginForm {
$titleObj = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
- require_once( 'SkinTemplate.php' );
- require_once( 'templates/Userlogin.php' );
-
if ( $this->mType == 'signup' ) {
- $template =& new UsercreateTemplate();
+ $template = new UsercreateTemplate();
$q = 'action=submitlogin&type=signup';
$linkq = 'type=login';
$linkmsg = 'gotaccount';
} else {
- $template =& new UserloginTemplate();
+ $template = new UserloginTemplate();
$q = 'action=submitlogin&type=login';
$linkq = 'type=signup';
$linkmsg = 'nologin';
@@ -570,7 +627,7 @@ class LoginForm {
function showCreateOrLoginLink( &$user ) {
if( $this->mType == 'signup' ) {
return( true );
- } elseif( $user->isAllowedToCreateAccount() ) {
+ } elseif( $user->isAllowed( 'createaccount' ) ) {
return( true );
} else {
return( false );
@@ -634,7 +691,7 @@ class LoginForm {
*/
function makeLanguageSelector() {
$msg = wfMsgForContent( 'loginlanguagelinks' );
- if( $msg != '' && $msg != '&lt;loginlanguagelinks&gt;' ) {
+ if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) {
$langs = explode( "\n", $msg );
$links = array();
foreach( $langs as $lang ) {
diff --git a/includes/SpecialUserrights.php b/includes/SpecialUserrights.php
index 8f43092c..b17cc4aa 100644
--- a/includes/SpecialUserrights.php
+++ b/includes/SpecialUserrights.php
@@ -165,7 +165,7 @@ class UserrightsForm extends HTMLForm {
'name' => 'wpEditToken',
'value' => $wgUser->editToken( $username ) ) ) .
$this->fieldset( 'editusergroup',
- $wgOut->parse( wfMsg('editing', $username ) ) .
+ $wgOut->parse( wfMsg('editinguser', $username ) ) .
'<table border="0" align="center"><tr><td>'.
HTMLSelectGroups('member', $this->mName.'-groupsmember', $groups,true,6).
'</td><td>'.
diff --git a/includes/SpecialVersion.php b/includes/SpecialVersion.php
index 5f7e857f..8744597a 100644
--- a/includes/SpecialVersion.php
+++ b/includes/SpecialVersion.php
@@ -45,7 +45,7 @@ class SpecialVersion {
* @static
*/
function MediaWikiCredits() {
- $version = $this->getVersion();
+ $version = self::getVersion();
$dbr =& wfGetDB( DB_SLAVE );
$ret =
@@ -77,9 +77,9 @@ class SpecialVersion {
return str_replace( "\t\t", '', $ret );
}
- function getVersion() {
+ public static function getVersion() {
global $wgVersion, $IP;
- $svn = $this->getSvnRevision( $IP );
+ $svn = self::getSvnRevision( $IP );
return $svn ? "$wgVersion (r$svn)" : $wgVersion;
}
@@ -129,6 +129,11 @@ class SpecialVersion {
$out .= "** Parser extension tags:\n";
$out .= '***' . $this->listToText( $tags ). "\n";
}
+
+ if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) {
+ $out .= "** Parser function hooks:\n";
+ $out .= '***' . $this->listToText( $fhooks ) . "\n";
+ }
if ( count( $wgSkinExtensionFunction ) ) {
$out .= "** Skin extension functions:\n";
@@ -142,7 +147,7 @@ class SpecialVersion {
if ( $a['name'] === $b['name'] )
return 0;
else
- return LanguageUtf8::lc( $a['name'] ) > LanguageUtf8::lc( $b['name'] ) ? 1 : -1;
+ return Language::lc( $a['name'] ) > Language::lc( $b['name'] ) ? 1 : -1;
}
function formatCredits( $name, $version = null, $author = null, $url = null, $description = null) {
@@ -232,34 +237,51 @@ class SpecialVersion {
/**
* Retrieve the revision number of a Subversion working directory.
+ *
+ * @bug 7335
*
* @param string $dir
* @return mixed revision number as int, or false if not a SVN checkout
*/
- function getSvnRevision( $dir ) {
- if( !function_exists( 'simplexml_load_file' ) ) {
- // We could fall back to expat... YUCK
- return false;
- }
-
+ public static function getSvnRevision( $dir ) {
// http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
$entries = $dir . '/.svn/entries';
-
- // SimpleXml whines about the xmlns...
- wfSuppressWarnings();
- $xml = simplexml_load_file( $entries );
- wfRestoreWarnings();
-
- if( $xml ) {
- foreach( $xml->entry as $entry ) {
- if( $xml->entry[0]['name'] == '' ) {
- // The directory entry should always have a revision marker.
- if( $entry['revision'] ) {
- return intval( $entry['revision'] );
+
+ if( !file_exists( $entries ) ) {
+ return false;
+ }
+
+ $content = file( $entries );
+
+ // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
+ if( preg_match( '/^<\?xml/', $content[0] ) ) {
+ // subversion is release <= 1.3
+ if( !function_exists( 'simplexml_load_file' ) ) {
+ // We could fall back to expat... YUCK
+ return false;
+ }
+
+ // SimpleXml whines about the xmlns...
+ wfSuppressWarnings();
+ $xml = simplexml_load_file( $entries );
+ wfRestoreWarnings();
+
+ if( $xml ) {
+ foreach( $xml->entry as $entry ) {
+ if( $xml->entry[0]['name'] == '' ) {
+ // The directory entry should always have a revision marker.
+ if( $entry['revision'] ) {
+ return intval( $entry['revision'] );
+ }
}
}
}
+ return false;
+ } else {
+ // subversion is release 1.4
+ return intval( $content[3] );
}
+
return false;
}
diff --git a/includes/SpecialWantedcategories.php b/includes/SpecialWantedcategories.php
index 8e75953a..97bb0a26 100644
--- a/includes/SpecialWantedcategories.php
+++ b/includes/SpecialWantedcategories.php
@@ -34,7 +34,7 @@ class WantedCategoriesPage extends QueryPage {
FROM $categorylinks
LEFT JOIN $page ON cl_to = page_title AND page_namespace = ". NS_CATEGORY ."
WHERE page_title IS NULL
- GROUP BY cl_to
+ GROUP BY 1,2,3
";
}
diff --git a/includes/SpecialWantedpages.php b/includes/SpecialWantedpages.php
index 8bbe49cb..7b070604 100644
--- a/includes/SpecialWantedpages.php
+++ b/includes/SpecialWantedpages.php
@@ -46,7 +46,7 @@ class WantedPagesPage extends QueryPage {
WHERE pg1.page_namespace IS NULL
AND pl_namespace NOT IN ( 2, 3 )
AND pg2.page_namespace != 8
- GROUP BY pl_namespace, pl_title
+ GROUP BY 1,2,3
HAVING COUNT(*) > $count";
}
diff --git a/includes/SpecialWatchlist.php b/includes/SpecialWatchlist.php
index 5b1e2890..87c925ac 100644
--- a/includes/SpecialWatchlist.php
+++ b/includes/SpecialWatchlist.php
@@ -17,7 +17,7 @@ require_once( 'SpecialRecentchanges.php' );
*/
function wfSpecialWatchlist( $par ) {
global $wgUser, $wgOut, $wgLang, $wgMemc, $wgRequest, $wgContLang;
- global $wgUseWatchlistCache, $wgWLCacheTimeout, $wgDBname;
+ global $wgUseWatchlistCache, $wgWLCacheTimeout;
global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker;
global $wgEnotifWatchlist;
$fname = 'wfSpecialWatchlist';
@@ -90,20 +90,20 @@ function wfSpecialWatchlist( $par ) {
if( !is_null( $t ) ) {
$wl = WatchedItem::fromUserTitle( $wgUser, $t );
if( $wl->removeWatch() === false ) {
- $wgOut->addHTML( "<br />\n" . wfMsg( 'couldntremove', htmlspecialchars($one) ) );
+ $wgOut->addHTML( wfMsg( 'couldntremove', htmlspecialchars($one) ) . "<br />\n" );
} else {
wfRunHooks('UnwatchArticle', array(&$wgUser, new Article($t)));
- $wgOut->addHTML( ' (' . htmlspecialchars($one) . ')' );
+ $wgOut->addHTML( '(' . htmlspecialchars($one) . ')<br />' );
}
} else {
- $wgOut->addHTML( "<br />\n" . wfMsg( 'iteminvalidname', htmlspecialchars($one) ) );
+ $wgOut->addHTML( wfMsg( 'iteminvalidname', htmlspecialchars($one) ) . "<br />\n" );
}
}
- $wgOut->addHTML( "<br />\n" . wfMsg( 'wldone' ) . "</p>\n" );
+ $wgOut->addHTML( "</p>\n<p>" . wfMsg( 'wldone' ) . "</p>\n" );
}
if ( $wgUseWatchlistCache ) {
- $memckey = "$wgDBname:watchlist:id:" . $wgUser->getId();
+ $memckey = wfMemcKey( 'watchlist', 'id', $wgUser->getId() );
$cache_s = @$wgMemc->get( $memckey );
if( $cache_s ){
$wgOut->addWikiText( wfMsg('wlsaved') );
@@ -235,9 +235,8 @@ function wfSpecialWatchlist( $par ) {
$wgOut->addHTML( '</ul>' );
}
$wgOut->addHTML(
- "<input type='submit' name='remove' value=\"" .
- htmlspecialchars( wfMsg( "removechecked" ) ) . "\" />\n" .
- "</form>\n"
+ wfSubmitButton( wfMsg('removechecked'), array('name' => 'remove') ) .
+ "\n</form>\n"
);
return;
@@ -274,7 +273,7 @@ function wfSpecialWatchlist( $par ) {
}
# TODO: Consider removing the third parameter
- $header .= wfMsg( 'watchdetails', $wgLang->formatNum( $nitems ),
+ $header .= wfMsgExt( 'watchdetails', array( 'parsemag' ), $wgLang->formatNum( $nitems ),
$wgLang->formatNum( $npages ), '',
$specialTitle->getFullUrl( 'edit=yes' ) );
$wgOut->addWikiText( $header );
@@ -313,14 +312,15 @@ function wfSpecialWatchlist( $par ) {
$numRows = $dbr->numRows( $res );
/* Start bottom header */
- $wgOut->addHTML( "<hr />\n<p>" );
+ $wgOut->addHTML( "<hr />\n" );
- if($days >= 1)
+ if($days >= 1) {
$wgOut->addWikiText( wfMsg( 'rcnote', $wgLang->formatNum( $numRows ),
$wgLang->formatNum( $days ), $wgLang->timeAndDate( wfTimestampNow(), true ) ) . '<br />' , false );
- elseif($days > 0)
+ } elseif($days > 0) {
$wgOut->addWikiText( wfMsg( 'wlnote', $wgLang->formatNum( $numRows ),
$wgLang->formatNum( round($days*24) ) ) . '<br />' , false );
+ }
$wgOut->addHTML( "\n" . wlCutoffLinks( $days, 'Watchlist', $nondefaults ) . "<br />\n" );
@@ -343,24 +343,25 @@ function wfSpecialWatchlist( $par ) {
$wgOut->addHTML( implode( ' | ', $links ) );
# Form for namespace filtering
- $thisAction = $thisTitle->escapeLocalUrl();
- $nsForm = "<form method=\"post\" action=\"{$thisAction}\">\n";
- $nsForm .= "<label for=\"namespace\">" . wfMsgExt( 'namespace', array( 'parseinline') ) . "</label> ";
- $nsForm .= HTMLnamespaceselector( $nameSpace, '' ) . "\n";
- $nsForm .= ( $hideOwn ? "<input type=\"hidden\" name=\"hideown\" value=\"1\" />\n" : "" );
- $nsForm .= ( $hideBots ? "<input type=\"hidden\" name=\"hidebots\" value=\"1\" />\n" : "" );
- $nsForm .= "<input type=\"hidden\" name=\"days\" value=\"" . $days . "\" />\n";
- $nsForm .= "<input type=\"submit\" name=\"submit\" value=\"" . wfMsgExt( 'allpagessubmit', array( 'escape') ) . "\" />\n";
- $nsForm .= "</form>\n";
- $wgOut->addHTML( $nsForm );
+ $wgOut->addHTML( "\n" .
+ wfOpenElement( 'form', array(
+ 'method' => 'post',
+ 'action' => $thisTitle->getLocalURL(),
+ ) ) .
+ wfMsgExt( 'namespace', array( 'parseinline') ) .
+ HTMLnamespaceselector( $nameSpace, '' ) . "\n" .
+ ( $hideOwn ? wfHidden('hideown', 1)."\n" : '' ) .
+ ( $hideBots ? wfHidden('hidebots', 1)."\n" : '' ) .
+ wfHidden( 'days', $days ) . "\n" .
+ wfSubmitButton( wfMsgExt( 'allpagessubmit', array( 'escape') ) ) . "\n" .
+ wfCloseElement( 'form' ) . "\n"
+ );
if ( $numRows == 0 ) {
$wgOut->addWikitext( "<br />" . wfMsg( 'watchnochange' ), false );
- $wgOut->addHTML( "</p>\n" );
return;
}
- $wgOut->addHTML( "</p>\n" );
/* End bottom header */
$list = ChangesList::newFromUser( $wgUser );
@@ -475,6 +476,8 @@ function wlCountItems( &$user, $talk = true ) {
* code needs to do something further
*/
function wlHandleClear( &$out, &$request, $par ) {
+ global $wgLang;
+
# Check this function has something to do
if( $request->getText( 'action' ) == 'clear' || $par == 'clear' ) {
global $wgUser;
@@ -486,17 +489,19 @@ function wlHandleClear( &$out, &$request, $par ) {
# Clearing, so do it and report the result
$dbw =& wfGetDB( DB_MASTER );
$dbw->delete( 'watchlist', array( 'wl_user' => $wgUser->mId ), 'wlHandleClear' );
- $out->addWikiText( wfMsg( 'watchlistcleardone', $count ) );
+ $out->addWikiText( wfMsgExt( 'watchlistcleardone', array( 'parsemag', 'escape'), $wgLang->formatNum( $count ) ) );
$out->returnToMain();
} else {
# Confirming, so show a form
$wlTitle = Title::makeTitle( NS_SPECIAL, 'Watchlist' );
$out->addHTML( wfElement( 'form', array( 'method' => 'post', 'action' => $wlTitle->getLocalUrl( 'action=clear' ) ), NULL ) );
- $out->addWikiText( wfMsg( 'watchlistcount', $count ) );
+ $out->addWikiText( wfMsgExt( 'watchlistcount', array( 'parsemag', 'escape'), $wgLang->formatNum( $count ) ) );
$out->addWikiText( wfMsg( 'watchlistcleartext' ) );
- $out->addHTML( wfElement( 'input', array( 'type' => 'hidden', 'name' => 'token', 'value' => $wgUser->editToken( 'clearwatchlist' ) ), '' ) );
- $out->addHTML( wfElement( 'input', array( 'type' => 'submit', 'name' => 'submit', 'value' => wfMsgHtml( 'watchlistclearbutton' ) ), '' ) );
- $out->addHTML( wfCloseElement( 'form' ) );
+ $out->addHTML(
+ wfHidden( 'token', $wgUser->editToken( 'clearwatchlist' ) ) .
+ wfElement( 'input', array( 'type' => 'submit', 'name' => 'submit', 'value' => wfMsgHtml( 'watchlistclearbutton' ) ), '' ) .
+ wfCloseElement( 'form' )
+ );
}
return( true );
} else {
@@ -509,5 +514,4 @@ function wlHandleClear( &$out, &$request, $par ) {
return( false );
}
}
-
?>
diff --git a/includes/SpecialWhatlinkshere.php b/includes/SpecialWhatlinkshere.php
index cedf6049..a95530fe 100644
--- a/includes/SpecialWhatlinkshere.php
+++ b/includes/SpecialWhatlinkshere.php
@@ -59,7 +59,7 @@ class WhatLinksHerePage {
$isredir = ' (' . wfMsg( 'isredirect' ) . ")\n";
- $wgOut->addHTML('&lt; '.$this->skin->makeLinkObj($this->target, '', 'redirect=no' )."<br />\n");
+ $wgOut->addHTML( wfMsg( 'whatlinkshere-barrow' ) . ' ' .$this->skin->makeLinkObj($this->target, '', 'redirect=no' )."<br />\n");
$this->showIndirectLinks( 0, $this->target, $this->limit, $this->from, $this->dir );
}
@@ -128,7 +128,7 @@ class WhatLinksHerePage {
if ( !$dbr->numRows( $plRes ) && !$dbr->numRows( $tlRes ) ) {
if ( 0 == $level ) {
- $wgOut->addWikiText( wfMsg( 'nolinkshere' ) );
+ $wgOut->addWikiText( wfMsg( 'nolinkshere', $this->target->getPrefixedText() ) );
}
return;
}
@@ -187,7 +187,7 @@ class WhatLinksHerePage {
}
if ( 0 == $level ) {
- $wgOut->addWikiText( wfMsg( 'linkshere' ) );
+ $wgOut->addWikiText( wfMsg( 'linkshere', $this->target->getPrefixedText() ) );
}
$isredir = wfMsg( 'isredirect' );
$istemplate = wfMsg( 'istemplate' );
diff --git a/includes/StreamFile.php b/includes/StreamFile.php
index 83417185..81538a84 100644
--- a/includes/StreamFile.php
+++ b/includes/StreamFile.php
@@ -6,10 +6,12 @@ function wfStreamFile( $fname ) {
$stat = @stat( $fname );
if ( !$stat ) {
header( 'HTTP/1.0 404 Not Found' );
+ $encFile = htmlspecialchars( $fname );
+ $encScript = htmlspecialchars( $_SERVER['SCRIPT_NAME'] );
echo "<html><body>
<h1>File not found</h1>
-<p>Although this PHP script ({$_SERVER['SCRIPT_NAME']}) exists, the file requested for output
-does not.</p>
+<p>Although this PHP script ($encScript) exists, the file requested for output
+($encFile) does not.</p>
</body></html>";
return;
}
@@ -64,7 +66,7 @@ function wfGetType( $filename ) {
return 'unknown/unknown';
}
else {
- $magic=& wfGetMimeMagic();
+ $magic=& MimeMagic::singleton();
return $magic->guessMimeType($filename); //full fancy mime detection
}
}
diff --git a/includes/StubObject.php b/includes/StubObject.php
new file mode 100644
index 00000000..ed3b117a
--- /dev/null
+++ b/includes/StubObject.php
@@ -0,0 +1,130 @@
+<?php
+
+/**
+ * Class to implement stub globals, which are globals that delay loading the
+ * their associated module code by deferring initialisation until the first
+ * method call.
+ *
+ * Note on unstub loops:
+ *
+ * Unstub loops (infinite recursion) sometimes occur when a constructor calls
+ * another function, and the other function calls some method of the stub. The
+ * best way to avoid this is to make constructors as lightweight as possible,
+ * deferring any initialisation which depends on other modules. As a last
+ * resort, you can use StubObject::isRealObject() to break the loop, but as a
+ * general rule, the stub object mechanism should be transparent, and code
+ * which refers to it should be kept to a minimum.
+ */
+class StubObject {
+ var $mGlobal, $mClass, $mParams;
+ function __construct( $global = null, $class = null, $params = array() ) {
+ $this->mGlobal = $global;
+ $this->mClass = $class;
+ $this->mParams = $params;
+ }
+
+ static function isRealObject( $obj ) {
+ return is_object( $obj ) && !is_a( $obj, 'StubObject' );
+ }
+
+ function _call( $name, $args ) {
+ $this->_unstub( $name, 5 );
+ return call_user_func_array( array( $GLOBALS[$this->mGlobal], $name ), $args );
+ }
+
+ function _newObject() {
+ return wfCreateObject( $this->mClass, $this->mParams );
+ }
+
+ function __call( $name, $args ) {
+ return $this->_call( $name, $args );
+ }
+
+ /**
+ * This is public, for the convenience of external callers wishing to access
+ * properties, e.g. eval.php
+ */
+ function _unstub( $name = '_unstub', $level = 2 ) {
+ static $recursionLevel = 0;
+ if ( get_class( $GLOBALS[$this->mGlobal] ) != $this->mClass ) {
+ $fname = __METHOD__.'-'.$this->mGlobal;
+ wfProfileIn( $fname );
+ $caller = wfGetCaller( $level );
+ if ( ++$recursionLevel > 2 ) {
+ throw new MWException( "Unstub loop detected on call of \${$this->mGlobal}->$name from $caller\n" );
+ }
+ wfDebug( "Unstubbing \${$this->mGlobal} on call of \${$this->mGlobal}->$name from $caller\n" );
+ $GLOBALS[$this->mGlobal] = $this->_newObject();
+ --$recursionLevel;
+ wfProfileOut( $fname );
+ }
+ }
+}
+
+class StubContLang extends StubObject {
+ function __construct() {
+ parent::__construct( 'wgContLang' );
+ }
+
+ function __call( $name, $args ) {
+ return StubObject::_call( $name, $args );
+ }
+
+ function _newObject() {
+ global $wgContLanguageCode;
+ $obj = Language::factory( $wgContLanguageCode );
+ $obj->initEncoding();
+ $obj->initContLang();
+ return $obj;
+ }
+}
+class StubUserLang extends StubObject {
+ function __construct() {
+ parent::__construct( 'wgLang' );
+ }
+
+ function __call( $name, $args ) {
+ return $this->_call( $name, $args );
+ }
+
+ function _newObject() {
+ global $wgContLanguageCode, $wgRequest, $wgUser, $wgContLang;
+ $code = $wgRequest->getVal('uselang', '');
+ if ($code == '')
+ $code = $wgUser->getOption('language');
+ # Validate $code
+ if( empty( $code ) || !preg_match( '/^[a-z]+(-[a-z]+)?$/', $code ) ) {
+ $code = $wgContLanguageCode;
+ }
+
+ if( $code == $wgContLanguageCode ) {
+ return $wgContLang;
+ } else {
+ $obj = Language::factory( $code );
+ return $obj;
+ }
+ }
+}
+class StubUser extends StubObject {
+ function __construct() {
+ parent::__construct( 'wgUser' );
+ }
+
+ function __call( $name, $args ) {
+ return $this->_call( $name, $args );
+ }
+
+ function _newObject() {
+ global $wgCommandLineMode;
+ if( $wgCommandLineMode ) {
+ $user = new User;
+ $user->setLoaded( true );
+ } else {
+ $user = User::loadFromSession();
+ wfRunHooks('AutoAuthenticate',array($user));
+ }
+ return $user;
+ }
+}
+
+?>
diff --git a/includes/Title.php b/includes/Title.php
index bc8f69a2..0e86063e 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -108,7 +108,7 @@ class Title {
* @static
* @access public
*/
- function newFromText( $text, $defaultNamespace = NS_MAIN ) {
+ public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
$fname = 'Title::newFromText';
if( is_object( $text ) ) {
@@ -132,7 +132,7 @@ class Title {
*/
$filteredText = Sanitizer::decodeCharReferences( $text );
- $t =& new Title();
+ $t = new Title();
$t->mDbkeyform = str_replace( ' ', '_', $filteredText );
$t->mDefaultNamespace = $defaultNamespace;
@@ -233,8 +233,8 @@ class Title {
* @static
* @access public
*/
- function &makeTitle( $ns, $title ) {
- $t =& new Title();
+ public static function &makeTitle( $ns, $title ) {
+ $t = new Title();
$t->mInterwiki = '';
$t->mFragment = '';
$t->mNamespace = intval( $ns );
@@ -246,7 +246,7 @@ class Title {
}
/**
- * Create a new Title frrom a namespace index and a DB key.
+ * Create a new Title from a namespace index and a DB key.
* The parameters will be checked for validity, which is a bit slower
* than makeTitle() but safer for user-provided data.
*
@@ -256,7 +256,7 @@ class Title {
* @static
* @access public
*/
- function makeTitleSafe( $ns, $title ) {
+ public static function makeTitleSafe( $ns, $title ) {
$t = new Title();
$t->mDbkeyform = Title::makeName( $ns, $title );
if( $t->secureAndSplit() ) {
@@ -273,7 +273,7 @@ class Title {
* @return Title the new object
* @access public
*/
- function newMainPage() {
+ public static function newMainPage() {
return Title::newFromText( wfMsgForContent( 'mainpage' ) );
}
@@ -285,8 +285,8 @@ class Title {
* @static
* @access public
*/
- function newFromRedirect( $text ) {
- $mwRedir = MagicWord::get( MAG_REDIRECT );
+ public static function newFromRedirect( $text ) {
+ $mwRedir = MagicWord::get( 'redirect' );
$rt = NULL;
if ( $mwRedir->matchStart( $text ) ) {
if ( preg_match( '/\[{2}(.*?)(?:\||\]{2})/', $text, $m ) ) {
@@ -336,7 +336,7 @@ class Title {
* @static
* @access public
*/
- function legalChars() {
+ public static function legalChars() {
global $wgLegalTitleChars;
return $wgLegalTitleChars;
}
@@ -376,7 +376,7 @@ class Title {
* @param string $title the DB key form the title
* @return string the prefixed form of the title
*/
- /* static */ function makeName( $ns, $title ) {
+ public static function makeName( $ns, $title ) {
global $wgContLang;
$n = $wgContLang->getNsText( $ns );
@@ -392,13 +392,13 @@ class Title {
* @access public
*/
function getInterwikiLink( $key ) {
- global $wgMemc, $wgDBname, $wgInterwikiExpiry;
+ global $wgMemc, $wgInterwikiExpiry;
global $wgInterwikiCache;
$fname = 'Title::getInterwikiLink';
$key = strtolower( $key );
- $k = $wgDBname.':interwiki:'.$key;
+ $k = wfMemcKey( 'interwiki', $key );
if( array_key_exists( $k, Title::$interwikiCache ) ) {
return Title::$interwikiCache[$k]->iw_url;
}
@@ -445,18 +445,18 @@ class Title {
* @access public
*/
function getInterwikiCached( $key ) {
- global $wgDBname, $wgInterwikiCache, $wgInterwikiScopes, $wgInterwikiFallbackSite;
+ global $wgInterwikiCache, $wgInterwikiScopes, $wgInterwikiFallbackSite;
static $db, $site;
if (!$db)
$db=dba_open($wgInterwikiCache,'r','cdb');
/* Resolve site name */
if ($wgInterwikiScopes>=3 and !$site) {
- $site = dba_fetch("__sites:{$wgDBname}", $db);
+ $site = dba_fetch('__sites:' . wfWikiID(), $db);
if ($site=="")
$site = $wgInterwikiFallbackSite;
}
- $value = dba_fetch("{$wgDBname}:{$key}", $db);
+ $value = dba_fetch( wfMemcKey( $key ), $db);
if ($value=='' and $wgInterwikiScopes>=3) {
/* try site-level */
$value = dba_fetch("_{$site}:{$key}", $db);
@@ -476,7 +476,7 @@ class Title {
$s->iw_url=$url;
$s->iw_local=(int)$local;
}
- Title::$interwikiCache[$wgDBname.':interwiki:'.$key] = $s;
+ Title::$interwikiCache[wfMemcKey( 'interwiki', $key )] = $s;
return $s->iw_url;
}
/**
@@ -488,12 +488,10 @@ class Title {
* @access public
*/
function isLocal() {
- global $wgDBname;
-
if ( $this->mInterwiki != '' ) {
# Make sure key is loaded into cache
$this->getInterwikiLink( $this->mInterwiki );
- $k = $wgDBname.':interwiki:' . $this->mInterwiki;
+ $k = wfMemcKey( 'interwiki', $this->mInterwiki );
return (bool)(Title::$interwikiCache[$k]->iw_local);
} else {
return true;
@@ -508,13 +506,11 @@ class Title {
* @access public
*/
function isTrans() {
- global $wgDBname;
-
if ($this->mInterwiki == '')
return false;
# Make sure key is loaded into cache
$this->getInterwikiLink( $this->mInterwiki );
- $k = $wgDBname.':interwiki:' . $this->mInterwiki;
+ $k = wfMemcKey( 'interwiki', $this->mInterwiki );
return (bool)(Title::$interwikiCache[$k]->iw_trans);
}
@@ -1075,6 +1071,7 @@ class Title {
if( $action == 'create' ) {
if( ( $this->isTalkPage() && !$wgUser->isAllowed( 'createtalk' ) ) ||
( !$this->isTalkPage() && !$wgUser->isAllowed( 'createpage' ) ) ) {
+ wfProfileOut( $fname );
return false;
}
}
@@ -1897,7 +1894,7 @@ class Title {
$linkCache->clearLink( $nt->getPrefixedDBkey() );
# Recreate the redirect, this time in the other direction.
- $mwRedir = MagicWord::get( MAG_REDIRECT );
+ $mwRedir = MagicWord::get( 'redirect' );
$redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
$redirectArticle = new Article( $this );
$newid = $redirectArticle->insertOn( $dbw );
@@ -1970,7 +1967,7 @@ class Title {
$linkCache->clearLink( $nt->getPrefixedDBkey() );
# Insert redirect
- $mwRedir = MagicWord::get( MAG_REDIRECT );
+ $mwRedir = MagicWord::get( 'redirect' );
$redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
$redirectArticle = new Article( $this );
$newid = $redirectArticle->insertOn( $dbw );
@@ -2147,7 +2144,9 @@ class Title {
$stack[$parent] = array();
} else {
$nt = Title::newFromText($parent);
- $stack[$parent] = $nt->getParentCategoryTree( $children + array($parent => 1) );
+ if ( $nt ) {
+ $stack[$parent] = $nt->getParentCategoryTree( $children + array($parent => 1) );
+ }
}
}
return $stack;
@@ -2241,6 +2240,46 @@ class Title {
}
}
+ /**
+ * Get the last touched timestamp
+ */
+ function getTouched() {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $touched = $dbr->selectField( 'page', 'page_touched',
+ array(
+ 'page_namespace' => $this->getNamespace(),
+ 'page_title' => $this->getDBkey()
+ ), __METHOD__
+ );
+ return $touched;
+ }
+
+ /**
+ * Get a cached value from a global cache that is invalidated when this page changes
+ * @param string $key the key
+ * @param callback $callback A callback function which generates the value on cache miss
+ */
+ function getRelatedCache( $memc, $key, $expiry, $callback, $params = array() ) {
+ $touched = $this->getTouched();
+ $cacheEntry = $memc->get( $key );
+ if ( $cacheEntry ) {
+ if ( $cacheEntry['touched'] >= $touched ) {
+ return $cacheEntry['value'];
+ } else {
+ wfDebug( __METHOD__.": $key expired\n" );
+ }
+ } else {
+ wfDebug( __METHOD__.": $key not found\n" );
+ }
+ $value = call_user_func_array( $callback, $params );
+ $cacheEntry = array(
+ 'value' => $value,
+ 'touched' => $touched
+ );
+ $memc->set( $key, $cacheEntry, $expiry );
+ return $value;
+ }
+
function trackbackURL() {
global $wgTitle, $wgScriptPath, $wgServer;
diff --git a/includes/User.php b/includes/User.php
index f2426284..aa964d22 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -24,6 +24,7 @@ class User {
*/
var $mBlockedby; //!<
var $mBlockreason; //!<
+ var $mBlock; //!<
var $mDataLoaded; //!<
var $mEmail; //!<
var $mEmailAuthenticated; //!<
@@ -41,9 +42,46 @@ class User {
var $mSkin; //!<
var $mToken; //!<
var $mTouched; //!<
+ var $mDatePreference; // !<
var $mVersion; //!< serialized version
/**@}} */
+ static public $mToggles = array(
+ 'highlightbroken',
+ 'justify',
+ 'hideminor',
+ 'extendwatchlist',
+ 'usenewrc',
+ 'numberheadings',
+ 'showtoolbar',
+ 'editondblclick',
+ 'editsection',
+ 'editsectiononrightclick',
+ 'showtoc',
+ 'rememberpassword',
+ 'editwidth',
+ 'watchcreations',
+ 'watchdefault',
+ 'minordefault',
+ 'previewontop',
+ 'previewonfirst',
+ 'nocache',
+ 'enotifwatchlistpages',
+ 'enotifusertalkpages',
+ 'enotifminoredits',
+ 'enotifrevealaddr',
+ 'shownumberswatching',
+ 'fancysig',
+ 'externaleditor',
+ 'externaldiff',
+ 'showjumplinks',
+ 'uselivepreview',
+ 'autopatrol',
+ 'forceeditsummary',
+ 'watchlisthideown',
+ 'watchlisthidebots',
+ );
+
/** Constructor using User:loadDefaults() */
function User() {
$this->loadDefaults();
@@ -114,8 +152,6 @@ class User {
*/
function __sleep() {
return array(
-'mBlockedby',
-'mBlockreason',
'mDataLoaded',
'mEmail',
'mEmailAuthenticated',
@@ -257,6 +293,48 @@ class User {
return true;
}
+
+ /**
+ * Usernames which fail to pass this function will be blocked
+ * from user login and new account registrations, but may be used
+ * internally by batch processes.
+ *
+ * If an account already exists in this form, login will be blocked
+ * by a failure to pass this function.
+ *
+ * @param string $name
+ * @return bool
+ */
+ static function isUsableName( $name ) {
+ global $wgReservedUsernames;
+ return
+ // Must be a usable username, obviously ;)
+ self::isValidUserName( $name ) &&
+
+ // Certain names may be reserved for batch processes.
+ !in_array( $name, $wgReservedUsernames );
+ }
+
+ /**
+ * Usernames which fail to pass this function will be blocked
+ * from new account registrations, but may be used internally
+ * either by batch processes or by user accounts which have
+ * already been created.
+ *
+ * Additional character blacklisting may be added here
+ * rather than in isValidUserName() to avoid disrupting
+ * existing accounts.
+ *
+ * @param string $name
+ * @return bool
+ */
+ static function isCreatableName( $name ) {
+ return
+ self::isUsableName( $name ) &&
+
+ // Registration-time character blacklisting...
+ strpos( $name, '@' ) === false;
+ }
/**
* Is the input a valid password?
@@ -348,11 +426,9 @@ class User {
$this->mPassword = $this->mNewpassword = '';
$this->mRights = array();
$this->mGroups = array();
- $this->mOptions = User::getDefaultOptions();
+ $this->mOptions = null;
+ $this->mDatePreference = null;
- foreach( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
- $this->mOptions['searchNs'.$nsnum] = $val;
- }
unset( $this->mSkin );
$this->mDataLoaded = false;
$this->mBlockedby = -1; # Unset
@@ -380,19 +456,23 @@ class User {
* @private
*/
function getDefaultOptions() {
+ global $wgNamespacesToBeSearchedDefault;
/**
* Site defaults will override the global/language defaults
*/
- global $wgContLang, $wgDefaultUserOptions;
- $defOpt = $wgDefaultUserOptions + $wgContLang->getDefaultUserOptions();
+ global $wgDefaultUserOptions, $wgContLang;
+ $defOpt = $wgDefaultUserOptions + $wgContLang->getDefaultUserOptionOverrides();
/**
* default language setting
*/
- $variant = $wgContLang->getPreferredVariant();
+ $variant = $wgContLang->getPreferredVariant( false );
$defOpt['variant'] = $variant;
$defOpt['language'] = $variant;
+ foreach( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
+ $defOpt['searchNs'.$nsnum] = $val;
+ }
return $defOpt;
}
@@ -414,6 +494,18 @@ class User {
}
/**
+ * Get a list of user toggle names
+ * @return array
+ */
+ static function getToggles() {
+ global $wgContLang;
+ $extraToggles = array();
+ wfRunHooks( 'UserToggles', array( &$extraToggles ) );
+ return array_merge( self::$mToggles, $extraToggles, $wgContLang->getExtraUserToggles() );
+ }
+
+
+ /**
* Get blocking information
* @private
* @param bool $bFromSlave Specify whether to check slave or master. To improve performance,
@@ -436,21 +528,21 @@ class User {
$ip = wfGetIP();
# User/IP blocking
- $block = new Block();
- $block->fromMaster( !$bFromSlave );
- if ( $block->load( $ip , $this->mId ) ) {
+ $this->mBlock = new Block();
+ $this->mBlock->fromMaster( !$bFromSlave );
+ if ( $this->mBlock->load( $ip , $this->mId ) ) {
wfDebug( "$fname: Found block.\n" );
- $this->mBlockedby = $block->mBy;
- $this->mBlockreason = $block->mReason;
+ $this->mBlockedby = $this->mBlock->mBy;
+ $this->mBlockreason = $this->mBlock->mReason;
if ( $this->isLoggedIn() ) {
$this->spreadBlock();
}
} else {
+ $this->mBlock = null;
wfDebug( "$fname: No block.\n" );
}
# Proxy blocking
- # FIXME ? proxyunbannable is to deprecate the old isSysop()
if ( !$this->isAllowed('proxyunbannable') && !in_array( $ip, $wgProxyWhitelist ) ) {
# Local list
@@ -531,7 +623,7 @@ class User {
return false;
}
- global $wgMemc, $wgDBname, $wgRateLimitLog;
+ global $wgMemc, $wgRateLimitLog;
$fname = 'User::pingLimiter';
wfProfileIn( $fname );
@@ -541,15 +633,15 @@ class User {
$ip = wfGetIP();
if( isset( $limits['anon'] ) && $id == 0 ) {
- $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
+ $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
}
if( isset( $limits['user'] ) && $id != 0 ) {
- $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
+ $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['user'];
}
if( $this->isNewbie() ) {
if( isset( $limits['newbie'] ) && $id != 0 ) {
- $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
+ $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
}
if( isset( $limits['ip'] ) ) {
$keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
@@ -569,7 +661,7 @@ class User {
if( $count > $max ) {
wfDebug( "$fname: tripped! $key at $count $summary\n" );
if( $wgRateLimitLog ) {
- @error_log( wfTimestamp( TS_MW ) . ' ' . $wgDBname . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
+ @error_log( wfTimestamp( TS_MW ) . ' ' . wfWikiID() . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
}
$triggered = true;
} else {
@@ -638,19 +730,10 @@ class User {
/**
* Initialise php session
+ * @deprecated use wfSetupSession()
*/
function SetupSession() {
- global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
- if( $wgSessionsInMemcached ) {
- require_once( 'MemcachedSessions.php' );
- } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
- # If it's left on 'user' or another setting from another
- # application, it will end up failing. Try to recover.
- ini_set ( 'session.save_handler', 'files' );
- }
- session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
- session_cache_limiter( 'private, must-revalidate' );
- @session_start();
+ wfSetupSession();
}
/**
@@ -658,7 +741,7 @@ class User {
* @static
*/
function loadFromSession() {
- global $wgMemc, $wgDBname, $wgCookiePrefix;
+ global $wgMemc, $wgCookiePrefix;
if ( isset( $_SESSION['wsUserID'] ) ) {
if ( 0 != $_SESSION['wsUserID'] ) {
@@ -682,7 +765,7 @@ class User {
}
$passwordCorrect = FALSE;
- $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
+ $user = $wgMemc->get( $key = wfMemcKey( 'user', 'id', $sId ) );
if( !is_object( $user ) || $user->mVersion < MW_USER_VERSION ) {
# Expire old serialized objects; they may be corrupt.
$user = false;
@@ -694,6 +777,8 @@ class User {
$user->loadFromDatabase();
} else {
wfDebug( "User::loadFromSession() got from cache!\n" );
+ # Set block status to unloaded, that should be loaded every time
+ $user->mBlockedby = -1;
}
if ( isset( $_SESSION['wsToken'] ) ) {
@@ -831,8 +916,8 @@ class User {
# Check memcached separately for anons, who have no
# entire User object stored in there.
if( !$this->mId ) {
- global $wgDBname, $wgMemc;
- $key = "$wgDBname:newtalk:ip:" . $this->getName();
+ global $wgMemc;
+ $key = wfMemcKey( 'newtalk', 'ip', $this->getName() );
$newtalk = $wgMemc->get( $key );
if( is_integer( $newtalk ) ) {
$this->mNewtalk = (bool)$newtalk;
@@ -852,7 +937,6 @@ class User {
* Return the talk page(s) this user has new messages on.
*/
function getNewMessageLinks() {
- global $wgDBname;
$talks = array();
if (!wfRunHooks('UserRetrieveNewTalks', array(&$this, &$talks)))
return $talks;
@@ -861,7 +945,7 @@ class User {
return array();
$up = $this->getUserPage();
$utp = $up->getTalkPage();
- return array(array("wiki" => $wgDBname, "link" => $utp->getLocalURL()));
+ return array(array("wiki" => wfWikiID(), "link" => $utp->getLocalURL()));
}
@@ -956,8 +1040,8 @@ class User {
if( $this->isAnon() ) {
// Anons have a separate memcached space, since
// user records aren't kept for them.
- global $wgDBname, $wgMemc;
- $key = "$wgDBname:newtalk:ip:$val";
+ global $wgMemc;
+ $key = wfMemcKey( 'newtalk', 'ip', $val );
$wgMemc->set( $key, $val ? 1 : 0 );
} else {
if( $val ) {
@@ -967,16 +1051,49 @@ class User {
}
}
$this->invalidateCache();
- $this->saveSettings();
+ }
+ }
+
+ /**
+ * Generate a current or new-future timestamp to be stored in the
+ * user_touched field when we update things.
+ */
+ private static function newTouchedTimestamp() {
+ global $wgClockSkewFudge;
+ return wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
+ }
+
+ /**
+ * Clear user data from memcached.
+ * Use after applying fun updates to the database; caller's
+ * responsibility to update user_touched if appropriate.
+ *
+ * Called implicitly from invalidateCache() and saveSettings().
+ */
+ private function clearUserCache() {
+ if( $this->mId ) {
+ global $wgMemc;
+ $wgMemc->delete( wfMemcKey( 'user', 'id', $this->mId ) );
}
}
+ /**
+ * Immediately touch the user data cache for this account.
+ * Updates user_touched field, and removes account data from memcached
+ * for reload on the next hit.
+ */
function invalidateCache() {
- global $wgClockSkewFudge;
- $this->loadFromDatabase();
- $this->mTouched = wfTimestamp(TS_MW, time() + $wgClockSkewFudge );
- # Don't forget to save the options after this or
- # it won't take effect!
+ if( $this->mId ) {
+ $this->mTouched = self::newTouchedTimestamp();
+
+ $dbw =& wfGetDB( DB_MASTER );
+ $dbw->update( 'user',
+ array( 'user_touched' => $dbw->timestamp( $this->mTouched ) ),
+ array( 'user_id' => $this->mId ),
+ __METHOD__ );
+
+ $this->clearUserCache();
+ }
}
function validateCache( $timestamp ) {
@@ -1004,7 +1121,7 @@ class User {
# Set the random token (used for persistent authentication)
function setToken( $token = false ) {
- global $wgSecretKey, $wgProxyKey, $wgDBname;
+ global $wgSecretKey, $wgProxyKey;
if ( !$token ) {
if ( $wgSecretKey ) {
$key = $wgSecretKey;
@@ -1013,7 +1130,7 @@ class User {
} else {
$key = microtime();
}
- $this->mToken = md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId );
+ $this->mToken = md5( $key . mt_rand( 0, 0x7fffffff ) . wfWikiID() . $this->mId );
} else {
$this->mToken = $token;
}
@@ -1061,6 +1178,9 @@ class User {
*/
function getOption( $oname ) {
$this->loadFromDatabase();
+ if ( is_null( $this->mOptions ) ) {
+ $this->mOptions = User::getDefaultOptions();
+ }
if ( array_key_exists( $oname, $this->mOptions ) ) {
return trim( $this->mOptions[$oname] );
} else {
@@ -1069,6 +1189,23 @@ class User {
}
/**
+ * Get the user's date preference, including some important migration for
+ * old user rows.
+ */
+ function getDatePreference() {
+ if ( is_null( $this->mDatePreference ) ) {
+ global $wgLang;
+ $value = $this->getOption( 'date' );
+ $map = $wgLang->getDatePreferenceMigrationMap();
+ if ( isset( $map[$value] ) ) {
+ $value = $map[$value];
+ }
+ $this->mDatePreference = $value;
+ }
+ return $this->mDatePreference;
+ }
+
+ /**
* @param string $oname The option to check
* @return bool False if the option is not selected, true if it is
*/
@@ -1092,6 +1229,9 @@ class User {
function setOption( $oname, $val ) {
$this->loadFromDatabase();
+ if ( is_null( $this->mOptions ) ) {
+ $this->mOptions = User::getDefaultOptions();
+ }
if ( $oname == 'skin' ) {
# Clear cached skin, so the new one displays immediately in Special:Preferences
unset( $this->mSkin );
@@ -1102,7 +1242,6 @@ class User {
$val = str_replace( "\r", "\n", $val );
$val = str_replace( "\n", " ", $val );
$this->mOptions[$oname] = $val;
- $this->invalidateCache();
}
function getRights() {
@@ -1153,7 +1292,6 @@ class User {
$this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );
$this->invalidateCache();
- $this->saveSettings();
}
/**
@@ -1174,7 +1312,6 @@ class User {
$this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );
$this->invalidateCache();
- $this->saveSettings();
}
@@ -1199,44 +1336,16 @@ class User {
}
/**
- * Deprecated in 1.6, die in 1.7, to be removed in 1.8
- * @deprecated
- */
- function isSysop() {
- throw new MWException( "Call to deprecated (v1.7) User::isSysop() method\n" );
- #return $this->isAllowed( 'protect' );
- }
-
- /**
- * Deprecated in 1.6, die in 1.7, to be removed in 1.8
- * @deprecated
- */
- function isDeveloper() {
- throw new MWException( "Call to deprecated (v1.7) User::isDeveloper() method\n" );
- #return $this->isAllowed( 'siteadmin' );
- }
-
- /**
- * Deprecated in 1.6, die in 1.7, to be removed in 1.8
- * @deprecated
- */
- function isBureaucrat() {
- throw new MWException( "Call to deprecated (v1.7) User::isBureaucrat() method\n" );
- #return $this->isAllowed( 'makesysop' );
- }
-
- /**
* Whether the user is a bot
- * @todo need to be migrated to the new user level management sytem
+ * @deprecated
*/
function isBot() {
- $this->loadFromDatabase();
- return in_array( 'bot', $this->mRights );
+ return $this->isAllowed( 'bot' );
}
/**
* Check if user is allowed to access a feature / make an action
- * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
+ * @param string $action Action to be checked
* @return boolean True: action is allowed, False: action should not be allowed
*/
function isAllowed($action='') {
@@ -1375,7 +1484,7 @@ class User {
$dbw =& wfGetDB( DB_MASTER );
$success = $dbw->update( 'watchlist',
array( /* SET */
- 'wl_notificationtimestamp' => 0
+ 'wl_notificationtimestamp' => NULL
), array( /* WHERE */
'wl_user' => $currentUser
), 'UserMailer::clearAll'
@@ -1391,6 +1500,9 @@ class User {
* @return string Encoding options
*/
function encodeOptions() {
+ if ( is_null( $this->mOptions ) ) {
+ $this->mOptions = User::getDefaultOptions();
+ }
$a = array();
foreach ( $this->mOptions as $oname => $oval ) {
array_push( $a, $oname.'='.$oval );
@@ -1403,6 +1515,9 @@ class User {
* @private
*/
function decodeOptions( $str ) {
+ global $wgLang;
+
+ $this->mOptions = array();
$a = explode( "\n", $str );
foreach ( $a as $s ) {
if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
@@ -1451,13 +1566,15 @@ class User {
/**
* Save object settings into database
+ * @fixme Only rarely do all these fields need to be set!
*/
function saveSettings() {
- global $wgMemc, $wgDBname;
$fname = 'User::saveSettings';
if ( wfReadOnly() ) { return; }
if ( 0 == $this->mId ) { return; }
+
+ $this->mTouched = self::newTouchedTimestamp();
$dbw =& wfGetDB( DB_MASTER );
$dbw->update( 'user',
@@ -1475,7 +1592,7 @@ class User {
'user_id' => $this->mId
), $fname
);
- $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
+ $this->clearUserCache();
}
@@ -1532,13 +1649,13 @@ class User {
}
$userblock = Block::newFromDB( '', $this->mId );
- if ( !$userblock->isValid() ) {
+ if ( !$userblock ) {
return;
}
# Check if this IP address is already blocked
$ipblock = Block::newFromDB( wfGetIP() );
- if ( $ipblock->isValid() ) {
+ if ( $ipblock ) {
# If the user is already blocked. Then check if the autoblock would
# excede the user block. If it would excede, then do nothing, else
# prolong block time
@@ -1549,6 +1666,8 @@ class User {
# Just update the timestamp
$ipblock->updateTimestamp();
return;
+ } else {
+ $ipblock = new Block;
}
# Make a new block object with the desired properties
@@ -1586,7 +1705,7 @@ class User {
* @return string
*/
function getPageRenderingHash() {
- global $wgContLang;
+ global $wgContLang, $wgUseDynamicDates;
if( $this->mHash ){
return $this->mHash;
}
@@ -1596,7 +1715,9 @@ class User {
$confstr = $this->getOption( 'math' );
$confstr .= '!' . $this->getOption( 'stubthreshold' );
- $confstr .= '!' . $this->getOption( 'date' );
+ if ( $wgUseDynamicDates ) {
+ $confstr .= '!' . $this->getDatePreference();
+ }
$confstr .= '!' . ($this->getOption( 'numberheadings' ) ? '1' : '');
$confstr .= '!' . $this->getOption( 'language' );
$confstr .= '!' . $this->getOption( 'thumbsize' );
@@ -1612,8 +1733,13 @@ class User {
return $confstr;
}
+ function isBlockedFromCreateAccount() {
+ $this->getBlockedStatus();
+ return $this->mBlock && $this->mBlock->mCreateAccount;
+ }
+
function isAllowedToCreateAccount() {
- return $this->isAllowed( 'createaccount' ) && !$this->isBlocked();
+ return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
}
/**
@@ -1700,7 +1826,7 @@ class User {
} elseif ( function_exists( 'iconv' ) ) {
# Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
# Check for this with iconv
- $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
+ $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password ) );
if ( 0 == strcmp( $cp1252hash, $this->mPassword ) ) {
return true;
}
@@ -1906,7 +2032,7 @@ class User {
* @return array list of permission key names for given groups combined
* @static
*/
- function getGroupPermissions( $groups ) {
+ static function getGroupPermissions( $groups ) {
global $wgGroupPermissions;
$rights = array();
foreach( $groups as $group ) {
@@ -1923,10 +2049,10 @@ class User {
* @return string localized descriptive name for group, if provided
* @static
*/
- function getGroupName( $group ) {
+ static function getGroupName( $group ) {
$key = "group-$group";
$name = wfMsg( $key );
- if( $name == '' || $name == "&lt;$key&gt;" ) {
+ if( $name == '' || wfEmptyMsg( $key, $name ) ) {
return $group;
} else {
return $name;
@@ -1938,17 +2064,16 @@ class User {
* @return string localized descriptive name for member of a group, if provided
* @static
*/
- function getGroupMember( $group ) {
+ static function getGroupMember( $group ) {
$key = "group-$group-member";
$name = wfMsg( $key );
- if( $name == '' || $name == "&lt;$key&gt;" ) {
+ if( $name == '' || wfEmptyMsg( $key, $name ) ) {
return $group;
} else {
return $name;
}
}
-
/**
* Return the set of defined explicit groups.
* The *, 'user', 'autoconfirmed' and 'emailconfirmed'
@@ -1957,20 +2082,20 @@ class User {
* @return array
* @static
*/
- function getAllGroups() {
+ static function getAllGroups() {
global $wgGroupPermissions;
return array_diff(
array_keys( $wgGroupPermissions ),
array( '*', 'user', 'autoconfirmed', 'emailconfirmed' ) );
}
-
+
/**
* Get the title of a page describing a particular group
*
* @param $group Name of the group
* @return mixed
*/
- function getGroupPage( $group ) {
+ static function getGroupPage( $group ) {
$page = wfMsgForContent( 'grouppage-' . $group );
if( !wfEmptyMsg( 'grouppage-' . $group, $page ) ) {
$title = Title::newFromText( $page );
@@ -1979,8 +2104,47 @@ class User {
}
return false;
}
-
-
+
+ /**
+ * Create a link to the group in HTML, if available
+ *
+ * @param $group Name of the group
+ * @param $text The text of the link
+ * @return mixed
+ */
+ static function makeGroupLinkHTML( $group, $text = '' ) {
+ if( $text == '' ) {
+ $text = self::getGroupName( $group );
+ }
+ $title = self::getGroupPage( $group );
+ if( $title ) {
+ global $wgUser;
+ $sk = $wgUser->getSkin();
+ return $sk->makeLinkObj( $title, $text );
+ } else {
+ return $text;
+ }
+ }
+
+ /**
+ * Create a link to the group in Wikitext, if available
+ *
+ * @param $group Name of the group
+ * @param $text The text of the link (by default, the name of the group)
+ * @return mixed
+ */
+ static function makeGroupLinkWiki( $group, $text = '' ) {
+ if( $text == '' ) {
+ $text = self::getGroupName( $group );
+ }
+ $title = self::getGroupPage( $group );
+ if( $title ) {
+ $page = $title->getPrefixedText();
+ return "[[$page|$text]]";
+ } else {
+ return $text;
+ }
+ }
}
?>
diff --git a/includes/UserMailer.php b/includes/UserMailer.php
index 8de39a64..78a8be91 100644
--- a/includes/UserMailer.php
+++ b/includes/UserMailer.php
@@ -53,7 +53,10 @@ class MailAddress {
* @return string
*/
function toString() {
- if( $this->name != '' ) {
+ # PHP's mail() implementation under Windows is somewhat shite, and
+ # can't handle "Joe Bloggs <joe@bloggs.com>" format email addresses,
+ # so don't bother generating them
+ if( $this->name != '' && !wfIsWindows() ) {
$quoted = wfQuotedPrintable( $this->name );
if( strpos( $quoted, '.' ) !== false ) {
$quoted = '"' . $quoted . '"';
@@ -101,6 +104,11 @@ function userMailer( $to, $from, $subject, $body, $replyto=false ) {
// Create the mail object using the Mail::factory method
$mail_object =& Mail::factory('smtp', $wgSMTP);
+ if( PEAR::isError( $mail_object ) ) {
+ wfDebug( "PEAR::Mail factory failed: " . $mail_object->getMessage() . "\n" );
+ return $mail_object->getMessage();
+ }
+
wfDebug( "Sending mail via PEAR::Mail to $dest\n" );
$mailResult =& $mail_object->send($dest, $headers, $body);
@@ -258,8 +266,11 @@ class EmailNotification {
$wuser = $dbr->fetchObject( $res );
$watchingUser->setID($wuser->wl_user);
+
if ( ( $enotifwatchlistpage && $watchingUser->getOption('enotifwatchlistpages') ) ||
- ( $enotifusertalkpage && $watchingUser->getOption('enotifusertalkpages') )
+ ( $enotifusertalkpage
+ && $watchingUser->getOption('enotifusertalkpages')
+ && $title->equals( $watchingUser->getTalkPage() ) )
&& (!$minorEdit || ($wgEnotifMinorEdits && $watchingUser->getOption('enotifminoredits') ) )
&& ($watchingUser->isEmailConfirmed() ) ) {
# ... adjust remaining text and page edit time placeholders
@@ -286,7 +297,7 @@ class EmailNotification {
);
# FIXME what do we do on failure ?
}
-
+ wfProfileOut( $fname );
} # function NotifyOnChange
/**
diff --git a/includes/Utf8Case.php b/includes/Utf8Case.php
index 9a2c7302..8c7fdd0b 100644
--- a/includes/Utf8Case.php
+++ b/includes/Utf8Case.php
@@ -6,7 +6,7 @@
* Hack for bugs in ucfirst() and company
*
* These are pulled from memcached if possible, as this is faster than filling
- * up a big array manually. See also languages/LanguageUtf8.php
+ * up a big array manually.
* @package MediaWiki
* @subpackage Language
*/
diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php
index 3885bb98..788774fb 100644
--- a/includes/WatchedItem.php
+++ b/includes/WatchedItem.php
@@ -35,8 +35,7 @@ class WatchedItem {
* Returns the memcached key for this item
*/
function watchKey() {
- global $wgDBname;
- return "$wgDBname:watchlist:user:$this->id:page:$this->ns:$this->ti";
+ return wfMemcKey( 'watchlist', 'user', $this->id, 'page', $this->ns, $this->ti );
}
/**
diff --git a/includes/WebRequest.php b/includes/WebRequest.php
index 4031e369..32307ed2 100644
--- a/includes/WebRequest.php
+++ b/includes/WebRequest.php
@@ -34,6 +34,15 @@
*
* @package MediaWiki
*/
+
+/**
+ * Some entry points may use this file without first enabling the
+ * autoloader.
+ */
+if ( !function_exists( '__autoload' ) ) {
+ require_once( dirname(__FILE__) . '/normal/UtfNormal.php' );
+}
+
class WebRequest {
function WebRequest() {
$this->checkMagicQuotes();
@@ -44,6 +53,8 @@ class WebRequest {
substr( $_SERVER['PATH_INFO'], 1 );
}
}
+
+ private $_response;
/**
* Recursively strips slashes from the given array;
@@ -117,7 +128,6 @@ class WebRequest {
$data = $wgContLang->checkTitleEncoding( $data );
}
}
- require_once( 'normal/UtfNormal.php' );
$data = $this->normalizeUnicode( $data );
return $data;
} else {
@@ -437,6 +447,19 @@ class WebRequest {
wfDebug( "WebRequest::getFileName() '" . $_FILES[$key]['name'] . "' normalized to '$name'\n" );
return $name;
}
+
+ /**
+ * Return a handle to WebResponse style object, for setting cookies,
+ * headers and other stuff, for Request being worked on.
+ */
+ function response() {
+ /* Lazy initialization of response object for this request */
+ if (!is_object($this->_response)) {
+ $this->_response = new WebResponse;
+ }
+ return $this->_response;
+ }
+
}
/**
diff --git a/includes/WebResponse.php b/includes/WebResponse.php
new file mode 100644
index 00000000..e159152e
--- /dev/null
+++ b/includes/WebResponse.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * Allow programs to request this object from WebRequest::response() and handle all outputting (or lack of outputting) via it.
+ */
+
+class WebResponse {
+ function header($string, $replace=true) {
+ header($string,$replace);
+ }
+
+ function setcookie($name, $value, $expire) {
+ global $wgCookiePath, $wgCookieDomain, $wgCookieSecure;
+ setcookie($name,$value,$expire, $wgCookiePath, $wgCookieDomain, $wgCookieSecure);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/WebStart.php b/includes/WebStart.php
new file mode 100644
index 00000000..0c71ce53
--- /dev/null
+++ b/includes/WebStart.php
@@ -0,0 +1,82 @@
+<?php
+
+# This does the initial setup for a web request. It does some security checks,
+# starts the profiler and loads the configuration, and optionally loads
+# Setup.php depending on whether MW_NO_SETUP is defined.
+
+# Protect against register_globals
+# This must be done before any globals are set by the code
+if ( ini_get( 'register_globals' ) ) {
+ if ( isset( $_REQUEST['GLOBALS'] ) ) {
+ die( '<a href="http://www.hardened-php.net/index.76.html">$GLOBALS overwrite vulnerability</a>');
+ }
+ $verboten = array(
+ 'GLOBALS',
+ '_SERVER',
+ 'HTTP_SERVER_VARS',
+ '_GET',
+ 'HTTP_GET_VARS',
+ '_POST',
+ 'HTTP_POST_VARS',
+ '_COOKIE',
+ 'HTTP_COOKIE_VARS',
+ '_FILES',
+ 'HTTP_POST_FILES',
+ '_ENV',
+ 'HTTP_ENV_VARS',
+ '_REQUEST',
+ '_SESSION',
+ 'HTTP_SESSION_VARS'
+ );
+ foreach ( $_REQUEST as $name => $value ) {
+ if( in_array( $name, $verboten ) ) {
+ header( "HTTP/1.x 500 Internal Server Error" );
+ echo "register_globals security paranoia: trying to overwrite superglobals, aborting.";
+ die( -1 );
+ }
+ unset( $GLOBALS[$name] );
+ }
+}
+
+$wgRequestTime = microtime(true);
+# getrusage() does not exist on the Microsoft Windows platforms, catching this
+if ( function_exists ( 'getrusage' ) ) {
+ $wgRUstart = getrusage();
+} else {
+ $wgRUstart = array();
+}
+unset( $IP );
+@ini_set( 'allow_url_fopen', 0 ); # For security
+
+# Valid web server entry point, enable includes.
+# Please don't move this line to includes/Defines.php. This line essentially
+# defines a valid entry point. If you put it in includes/Defines.php, then
+# any script that includes it becomes an entry point, thereby defeating
+# its purpose.
+define( 'MEDIAWIKI', true );
+
+# Start profiler
+require_once( './StartProfiler.php' );
+wfProfileIn( 'WebStart.php-conf' );
+
+# Load up some global defines.
+require_once( './includes/Defines.php' );
+
+# LocalSettings.php is the per site customization file. If it does not exit
+# the wiki installer need to be launched or the generated file moved from
+# ./config/ to ./
+if( !file_exists( './LocalSettings.php' ) ) {
+ $IP = '.';
+ require_once( './includes/DefaultSettings.php' ); # used for printing the version
+ require_once( './includes/templates/NoLocalSettings.php' );
+ die();
+}
+
+# Include this site setttings
+require_once( './LocalSettings.php' );
+wfProfileOut( 'WebStart.php-conf' );
+
+if ( !defined( 'MW_NO_SETUP' ) ) {
+ require_once( './includes/Setup.php' );
+}
+?>
diff --git a/includes/Wiki.php b/includes/Wiki.php
index 6f010003..401756be 100644
--- a/includes/Wiki.php
+++ b/includes/Wiki.php
@@ -170,6 +170,12 @@ class MediaWiki {
* @return Article
*/
function articleFromTitle( $title ) {
+ $article = null;
+ wfRunHooks('ArticleFromTitle', array( &$title, &$article ) );
+ if ( $article ) {
+ return $article;
+ }
+
if( NS_MEDIA == $title->getNamespace() ) {
// FIXME: where should this go?
$title = Title::makeTitle( NS_IMAGE, $title->getDBkey() );
@@ -258,8 +264,14 @@ class MediaWiki {
*/
function doUpdates ( &$updates ) {
wfProfileIn( 'MediaWiki::doUpdates' );
+ $dbw =& wfGetDB( DB_MASTER );
foreach( $updates as $up ) {
$up->doUpdate();
+
+ # Commit after every update to prevent lock contention
+ if ( $dbw->trxLevel() ) {
+ $dbw->commit();
+ }
}
wfProfileOut( 'MediaWiki::doUpdates' );
}
@@ -270,7 +282,7 @@ class MediaWiki {
function doJobs() {
global $wgJobRunRate;
- if ( $wgJobRunRate <= 0 ) {
+ if ( $wgJobRunRate <= 0 || wfReadOnly() ) {
return;
}
if ( $wgJobRunRate < 1 ) {
@@ -302,8 +314,7 @@ class MediaWiki {
* Ends this task peacefully
*/
function restInPeace ( &$loadBalancer ) {
- wfProfileClose();
- logProfilingData();
+ wfLogProfilingData();
$loadBalancer->closeAll();
wfDebug( "Request ended normally\n" );
}
diff --git a/includes/Xml.php b/includes/Xml.php
index 52993367..34574458 100644
--- a/includes/Xml.php
+++ b/includes/Xml.php
@@ -1,279 +1,301 @@
-<?php
-
-/**
- * Module of static functions for generating XML
- */
-
-class Xml {
- /**
- * Format an XML element with given attributes and, optionally, text content.
- * Element and attribute names are assumed to be ready for literal inclusion.
- * Strings are assumed to not contain XML-illegal characters; special
- * characters (<, >, &) are escaped but illegals are not touched.
- *
- * @param $element String:
- * @param $attribs Array: Name=>value pairs. Values will be escaped.
- * @param $contents String: NULL to make an open tag only; '' for a contentless closed tag (default)
- * @return string
- */
- function element( $element, $attribs = null, $contents = '') {
- $out = '<' . $element;
- if( !is_null( $attribs ) ) {
- foreach( $attribs as $name => $val ) {
- $out .= ' ' . $name . '="' . Sanitizer::encodeAttribute( $val ) . '"';
- }
- }
- if( is_null( $contents ) ) {
- $out .= '>';
- } else {
- if( $contents === '' ) {
- $out .= ' />';
- } else {
- $out .= '>' . htmlspecialchars( $contents ) . "</$element>";
- }
- }
- return $out;
- }
-
- /**
- * Format an XML element as with self::element(), but run text through the
- * UtfNormal::cleanUp() validator first to ensure that no invalid UTF-8
- * is passed.
- *
- * @param $element String:
- * @param $attribs Array: Name=>value pairs. Values will be escaped.
- * @param $contents String: NULL to make an open tag only; '' for a contentless closed tag (default)
- * @return string
- */
- function elementClean( $element, $attribs = array(), $contents = '') {
- if( $attribs ) {
- $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
- }
- if( $contents ) {
- $contents = UtfNormal::cleanUp( $contents );
- }
- return self::element( $element, $attribs, $contents );
- }
-
- // Shortcuts
- function openElement( $element, $attribs = null ) { return self::element( $element, $attribs, null ); }
- function closeElement( $element ) { return "</$element>"; }
-
- /**
- * Create a namespace selector
- *
- * @param $selected Mixed: the namespace which should be selected, default ''
- * @param $allnamespaces String: value of a special item denoting all namespaces. Null to not include (default)
- * @param $includehidden Bool: include hidden namespaces?
- * @return String: Html string containing the namespace selector
- */
- function &namespaceSelector($selected = '', $allnamespaces = null, $includehidden=false) {
- global $wgContLang;
- if( $selected !== '' ) {
- if( is_null( $selected ) ) {
- // No namespace selected; let exact match work without hitting Main
- $selected = '';
- } else {
- // Let input be numeric strings without breaking the empty match.
- $selected = intval( $selected );
- }
- }
- $s = "<select id='namespace' name='namespace' class='namespaceselector'>\n\t";
- $arr = $wgContLang->getFormattedNamespaces();
- if( !is_null($allnamespaces) ) {
- $arr = array($allnamespaces => wfMsg('namespacesall')) + $arr;
- }
- foreach ($arr as $index => $name) {
- if ($index < NS_MAIN) continue;
-
- $name = $index !== 0 ? $name : wfMsg('blanknamespace');
-
- if ($index === $selected) {
- $s .= self::element("option",
- array("value" => $index, "selected" => "selected"),
- $name);
- } else {
- $s .= self::element("option", array("value" => $index), $name);
- }
- }
- $s .= "\n</select>\n";
- return $s;
- }
-
- function span( $text, $class, $attribs=array() ) {
- return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
- }
-
- /**
- * Convenience function to build an HTML text input field
- * @return string HTML
- */
- function input( $name, $size=false, $value=false, $attribs=array() ) {
- return self::element( 'input', array(
- 'name' => $name,
- 'size' => $size,
- 'value' => $value ) + $attribs );
- }
-
- /**
- * Internal function for use in checkboxes and radio buttons and such.
- * @return array
- */
- function attrib( $name, $present = true ) {
- return $present ? array( $name => $name ) : array();
- }
-
- /**
- * Convenience function to build an HTML checkbox
- * @return string HTML
- */
- function check( $name, $checked=false, $attribs=array() ) {
- return self::element( 'input', array(
- 'name' => $name,
- 'type' => 'checkbox',
- 'value' => 1 ) + self::attrib( 'checked', $checked ) + $attribs );
- }
-
- /**
- * Convenience function to build an HTML radio button
- * @return string HTML
- */
- function radio( $name, $value, $checked=false, $attribs=array() ) {
- return self::element( 'input', array(
- 'name' => $name,
- 'type' => 'radio',
- 'value' => $value ) + self::attrib( 'checked', $checked ) + $attribs );
- }
-
- /**
- * Convenience function to build an HTML form label
- * @return string HTML
- */
- function label( $label, $id ) {
- return self::element( 'label', array( 'for' => $id ), $label );
- }
-
- /**
- * Convenience function to build an HTML text input field with a label
- * @return string HTML
- */
- function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
- return Xml::label( $label, $id ) .
- '&nbsp;' .
- self::input( $name, $size, $value, array( 'id' => $id ) + $attribs );
- }
-
- /**
- * Convenience function to build an HTML checkbox with a label
- * @return string HTML
- */
- function checkLabel( $label, $name, $id, $checked=false, $attribs=array() ) {
- return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
- '&nbsp;' .
- self::label( $label, $id );
- }
-
- /**
- * Convenience function to build an HTML radio button with a label
- * @return string HTML
- */
- function radioLabel( $label, $name, $value, $id, $checked=false, $attribs=array() ) {
- return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
- '&nbsp;' .
- self::label( $label, $id );
- }
-
- /**
- * Convenience function to build an HTML submit button
- * @param $value String: label text for the button
- * @param $attribs Array: optional custom attributes
- * @return string HTML
- */
- function submitButton( $value, $attribs=array() ) {
- return self::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
- }
-
- /**
- * Convenience function to build an HTML hidden form field.
- * @todo Document $name parameter.
- * @param $name FIXME
- * @param $value String: label text for the button
- * @param $attribs Array: optional custom attributes
- * @return string HTML
- */
- function hidden( $name, $value, $attribs=array() ) {
- return self::element( 'input', array(
- 'name' => $name,
- 'type' => 'hidden',
- 'value' => $value ) + $attribs );
- }
-
- /**
- * Returns an escaped string suitable for inclusion in a string literal
- * for JavaScript source code.
- * Illegal control characters are assumed not to be present.
- *
- * @param string $string
- * @return string
- */
- function escapeJsString( $string ) {
- // See ECMA 262 section 7.8.4 for string literal format
- $pairs = array(
- "\\" => "\\\\",
- "\"" => "\\\"",
- '\'' => '\\\'',
- "\n" => "\\n",
- "\r" => "\\r",
-
- # To avoid closing the element or CDATA section
- "<" => "\\x3c",
- ">" => "\\x3e",
- );
- return strtr( $string, $pairs );
- }
-
- /**
- * Check if a string is well-formed XML.
- * Must include the surrounding tag.
- *
- * @param $text String: string to test.
- * @return bool
- *
- * @todo Error position reporting return
- */
- function isWellFormed( $text ) {
- $parser = xml_parser_create( "UTF-8" );
-
- # case folding violates XML standard, turn it off
- xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
-
- if( !xml_parse( $parser, $text, true ) ) {
- $err = xml_error_string( xml_get_error_code( $parser ) );
- $position = xml_get_current_byte_index( $parser );
- //$fragment = $this->extractFragment( $html, $position );
- //$this->mXmlError = "$err at byte $position:\n$fragment";
- xml_parser_free( $parser );
- return false;
- }
- xml_parser_free( $parser );
- return true;
- }
-
- /**
- * Check if a string is a well-formed XML fragment.
- * Wraps fragment in an \<html\> bit and doctype, so it can be a fragment
- * and can use HTML named entities.
- *
- * @param $text String:
- * @return bool
- */
- function isWellFormedXmlFragment( $text ) {
- $html =
- Sanitizer::hackDocType() .
- '<html>' .
- $text .
- '</html>';
- return Xml::isWellFormed( $html );
- }
-}
-?>
+<?php
+
+/**
+ * Module of static functions for generating XML
+ */
+
+class Xml {
+ /**
+ * Format an XML element with given attributes and, optionally, text content.
+ * Element and attribute names are assumed to be ready for literal inclusion.
+ * Strings are assumed to not contain XML-illegal characters; special
+ * characters (<, >, &) are escaped but illegals are not touched.
+ *
+ * @param $element String:
+ * @param $attribs Array: Name=>value pairs. Values will be escaped.
+ * @param $contents String: NULL to make an open tag only; '' for a contentless closed tag (default)
+ * @return string
+ */
+ public static function element( $element, $attribs = null, $contents = '') {
+ $out = '<' . $element;
+ if( !is_null( $attribs ) ) {
+ foreach( $attribs as $name => $val ) {
+ $out .= ' ' . $name . '="' . Sanitizer::encodeAttribute( $val ) . '"';
+ }
+ }
+ if( is_null( $contents ) ) {
+ $out .= '>';
+ } else {
+ if( $contents === '' ) {
+ $out .= ' />';
+ } else {
+ $out .= '>' . htmlspecialchars( $contents ) . "</$element>";
+ }
+ }
+ return $out;
+ }
+
+ /**
+ * Format an XML element as with self::element(), but run text through the
+ * UtfNormal::cleanUp() validator first to ensure that no invalid UTF-8
+ * is passed.
+ *
+ * @param $element String:
+ * @param $attribs Array: Name=>value pairs. Values will be escaped.
+ * @param $contents String: NULL to make an open tag only; '' for a contentless closed tag (default)
+ * @return string
+ */
+ public static function elementClean( $element, $attribs = array(), $contents = '') {
+ if( $attribs ) {
+ $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
+ }
+ if( $contents ) {
+ $contents = UtfNormal::cleanUp( $contents );
+ }
+ return self::element( $element, $attribs, $contents );
+ }
+
+ // Shortcuts
+ public static function openElement( $element, $attribs = null ) { return self::element( $element, $attribs, null ); }
+ public static function closeElement( $element ) { return "</$element>"; }
+
+ /**
+ * Create a namespace selector
+ *
+ * @param $selected Mixed: the namespace which should be selected, default ''
+ * @param $allnamespaces String: value of a special item denoting all namespaces. Null to not include (default)
+ * @param $includehidden Bool: include hidden namespaces?
+ * @return String: Html string containing the namespace selector
+ */
+ public static function &namespaceSelector($selected = '', $allnamespaces = null, $includehidden=false) {
+ global $wgContLang;
+ if( $selected !== '' ) {
+ if( is_null( $selected ) ) {
+ // No namespace selected; let exact match work without hitting Main
+ $selected = '';
+ } else {
+ // Let input be numeric strings without breaking the empty match.
+ $selected = intval( $selected );
+ }
+ }
+ $s = "\n<select id='namespace' name='namespace' class='namespaceselector'>\n";
+ $arr = $wgContLang->getFormattedNamespaces();
+ if( !is_null($allnamespaces) ) {
+ $arr = array($allnamespaces => wfMsg('namespacesall')) + $arr;
+ }
+ foreach ($arr as $index => $name) {
+ if ($index < NS_MAIN) continue;
+
+ $name = $index !== 0 ? $name : wfMsg('blanknamespace');
+
+ if ($index === $selected) {
+ $s .= "\t" . self::element("option",
+ array("value" => $index, "selected" => "selected"),
+ $name) . "\n";
+ } else {
+ $s .= "\t" . self::element("option", array("value" => $index), $name) . "\n";
+ }
+ }
+ $s .= "</select>\n";
+ return $s;
+ }
+
+ public static function span( $text, $class, $attribs=array() ) {
+ return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
+ }
+
+ /**
+ * Convenience function to build an HTML text input field
+ * @return string HTML
+ */
+ public static function input( $name, $size=false, $value=false, $attribs=array() ) {
+ return self::element( 'input', array(
+ 'name' => $name,
+ 'size' => $size,
+ 'value' => $value ) + $attribs );
+ }
+
+ /**
+ * Internal function for use in checkboxes and radio buttons and such.
+ * @return array
+ */
+ public static function attrib( $name, $present = true ) {
+ return $present ? array( $name => $name ) : array();
+ }
+
+ /**
+ * Convenience function to build an HTML checkbox
+ * @return string HTML
+ */
+ public static function check( $name, $checked=false, $attribs=array() ) {
+ return self::element( 'input', array(
+ 'name' => $name,
+ 'type' => 'checkbox',
+ 'value' => 1 ) + self::attrib( 'checked', $checked ) + $attribs );
+ }
+
+ /**
+ * Convenience function to build an HTML radio button
+ * @return string HTML
+ */
+ public static function radio( $name, $value, $checked=false, $attribs=array() ) {
+ return self::element( 'input', array(
+ 'name' => $name,
+ 'type' => 'radio',
+ 'value' => $value ) + self::attrib( 'checked', $checked ) + $attribs );
+ }
+
+ /**
+ * Convenience function to build an HTML form label
+ * @return string HTML
+ */
+ public static function label( $label, $id ) {
+ return self::element( 'label', array( 'for' => $id ), $label );
+ }
+
+ /**
+ * Convenience function to build an HTML text input field with a label
+ * @return string HTML
+ */
+ public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
+ return Xml::label( $label, $id ) .
+ '&nbsp;' .
+ self::input( $name, $size, $value, array( 'id' => $id ) + $attribs );
+ }
+
+ /**
+ * Convenience function to build an HTML checkbox with a label
+ * @return string HTML
+ */
+ public static function checkLabel( $label, $name, $id, $checked=false, $attribs=array() ) {
+ return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
+ '&nbsp;' .
+ self::label( $label, $id );
+ }
+
+ /**
+ * Convenience function to build an HTML radio button with a label
+ * @return string HTML
+ */
+ public static function radioLabel( $label, $name, $value, $id, $checked=false, $attribs=array() ) {
+ return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
+ '&nbsp;' .
+ self::label( $label, $id );
+ }
+
+ /**
+ * Convenience function to build an HTML submit button
+ * @param $value String: label text for the button
+ * @param $attribs Array: optional custom attributes
+ * @return string HTML
+ */
+ public static function submitButton( $value, $attribs=array() ) {
+ return self::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
+ }
+
+ /**
+ * Convenience function to build an HTML hidden form field.
+ * @todo Document $name parameter.
+ * @param $name FIXME
+ * @param $value String: label text for the button
+ * @param $attribs Array: optional custom attributes
+ * @return string HTML
+ */
+ public static function hidden( $name, $value, $attribs=array() ) {
+ return self::element( 'input', array(
+ 'name' => $name,
+ 'type' => 'hidden',
+ 'value' => $value ) + $attribs );
+ }
+
+ /**
+ * Convenience function to build an HTML drop-down list item.
+ * @param $text String: text for this item
+ * @param $value String: form submission value; if empty, use text
+ * @param $selected boolean: if true, will be the default selected item
+ * @param $attribs array: optional additional HTML attributes
+ * @return string HTML
+ */
+ public static function option( $text, $value=null, $selected=false,
+ $attribs=array() ) {
+ if( !is_null( $value ) ) {
+ $attribs['value'] = $value;
+ }
+ if( $selected ) {
+ $attribs['selected'] = 'selected';
+ }
+ return self::element( 'option', $attribs, $text );
+ }
+
+ /**
+ * Returns an escaped string suitable for inclusion in a string literal
+ * for JavaScript source code.
+ * Illegal control characters are assumed not to be present.
+ *
+ * @param string $string
+ * @return string
+ */
+ public static function escapeJsString( $string ) {
+ // See ECMA 262 section 7.8.4 for string literal format
+ $pairs = array(
+ "\\" => "\\\\",
+ "\"" => "\\\"",
+ '\'' => '\\\'',
+ "\n" => "\\n",
+ "\r" => "\\r",
+
+ # To avoid closing the element or CDATA section
+ "<" => "\\x3c",
+ ">" => "\\x3e",
+
+ # To avoid any complaints about bad entity refs
+ "&" => "\\x26",
+ );
+ return strtr( $string, $pairs );
+ }
+
+ /**
+ * Check if a string is well-formed XML.
+ * Must include the surrounding tag.
+ *
+ * @param $text String: string to test.
+ * @return bool
+ *
+ * @todo Error position reporting return
+ */
+ public static function isWellFormed( $text ) {
+ $parser = xml_parser_create( "UTF-8" );
+
+ # case folding violates XML standard, turn it off
+ xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
+
+ if( !xml_parse( $parser, $text, true ) ) {
+ $err = xml_error_string( xml_get_error_code( $parser ) );
+ $position = xml_get_current_byte_index( $parser );
+ //$fragment = $this->extractFragment( $html, $position );
+ //$this->mXmlError = "$err at byte $position:\n$fragment";
+ xml_parser_free( $parser );
+ return false;
+ }
+ xml_parser_free( $parser );
+ return true;
+ }
+
+ /**
+ * Check if a string is a well-formed XML fragment.
+ * Wraps fragment in an \<html\> bit and doctype, so it can be a fragment
+ * and can use HTML named entities.
+ *
+ * @param $text String:
+ * @return bool
+ */
+ public static function isWellFormedXmlFragment( $text ) {
+ $html =
+ Sanitizer::hackDocType() .
+ '<html>' .
+ $text .
+ '</html>';
+ return Xml::isWellFormed( $html );
+ }
+}
+?>
diff --git a/includes/XmlFunctions.php b/includes/XmlFunctions.php
index 64e349f2..cbdcf5c4 100644
--- a/includes/XmlFunctions.php
+++ b/includes/XmlFunctions.php
@@ -1,19 +1,19 @@
<?php
-
/**
* Aliases for functions in the Xml module
+ * Look at the Xml class (Xml.php) for the implementations.
*/
-function wfElement( $element, $attribs = null, $contents = '') {
- return Xml::element( $element, $attribs, $contents );
+function wfElement( $element, $attribs = null, $contents = '') {
+ return Xml::element( $element, $attribs, $contents );
}
function wfElementClean( $element, $attribs = array(), $contents = '') {
return Xml::elementClean( $element, $attribs, $contents );
}
-function wfOpenElement( $element, $attribs = null ) {
- return Xml::openElement( $element, $attribs );
+function wfOpenElement( $element, $attribs = null ) {
+ return Xml::openElement( $element, $attribs );
}
-function wfCloseElement( $element ) {
- return "</$element>";
+function wfCloseElement( $element ) {
+ return "</$element>";
}
function &HTMLnamespaceselector($selected = '', $allnamespaces = null, $includehidden=false) {
return Xml::namespaceSelector( $selected, $allnamespaces, $includehidden );
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
new file mode 100644
index 00000000..f578f41b
--- /dev/null
+++ b/includes/api/ApiBase.php
@@ -0,0 +1,441 @@
+<?php
+
+
+/*
+ * Created on Sep 5, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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
+ */
+
+abstract class ApiBase {
+
+ // These constants allow modules to specify exactly how to treat incomming parameters.
+
+ const PARAM_DFLT = 0;
+ const PARAM_ISMULTI = 1;
+ const PARAM_TYPE = 2;
+ const PARAM_MAX1 = 3;
+ const PARAM_MAX2 = 4;
+ const PARAM_MIN = 5;
+
+ private $mMainModule, $mModuleName, $mParamPrefix;
+
+ /**
+ * Constructor
+ */
+ public function __construct($mainModule, $moduleName, $paramPrefix = '') {
+ $this->mMainModule = $mainModule;
+ $this->mModuleName = $moduleName;
+ $this->mParamPrefix = $paramPrefix;
+ }
+
+ /**
+ * Executes this module
+ */
+ public abstract function execute();
+
+ /**
+ * Get the name of the query being executed by this instance
+ */
+ public function getModuleName() {
+ return $this->mModuleName;
+ }
+
+ /**
+ * Get main module
+ */
+ public function getMain() {
+ return $this->mMainModule;
+ }
+
+ /**
+ * If this module's $this is the same as $this->mMainModule, its the root, otherwise no
+ */
+ public function isMain() {
+ return $this === $this->mMainModule;
+ }
+
+ /**
+ * Get result object
+ */
+ public function getResult() {
+ // Main module has getResult() method overriden
+ // Safety - avoid infinite loop:
+ if ($this->isMain())
+ ApiBase :: dieDebug(__METHOD__, 'base method was called on main module. ');
+ return $this->getMain()->getResult();
+ }
+
+ /**
+ * Get the result data array
+ */
+ public function & getResultData() {
+ return $this->getResult()->getData();
+ }
+
+ /**
+ * Generates help message for this module, or false if there is no description
+ */
+ public function makeHelpMsg() {
+
+ static $lnPrfx = "\n ";
+
+ $msg = $this->getDescription();
+
+ if ($msg !== false) {
+
+ if (!is_array($msg))
+ $msg = array (
+ $msg
+ );
+ $msg = $lnPrfx . implode($lnPrfx, $msg) . "\n";
+
+ // Parameters
+ $paramsMsg = $this->makeHelpMsgParameters();
+ if ($paramsMsg !== false) {
+ $msg .= "Parameters:\n$paramsMsg";
+ }
+
+ // Examples
+ $examples = $this->getExamples();
+ if ($examples !== false) {
+ if (!is_array($examples))
+ $examples = array (
+ $examples
+ );
+ $msg .= 'Example' . (count($examples) > 1 ? 's' : '') . ":\n ";
+ $msg .= implode($lnPrfx, $examples) . "\n";
+ }
+
+ if ($this->getMain()->getShowVersions()) {
+ $versions = $this->getVersion();
+ if (is_array($versions))
+ $versions = implode("\n ", $versions);
+ $msg .= "Version:\n $versions\n";
+ }
+ }
+
+ return $msg;
+ }
+
+ public function makeHelpMsgParameters() {
+ $params = $this->getAllowedParams();
+ if ($params !== false) {
+
+ $paramsDescription = $this->getParamDescription();
+ $msg = '';
+ foreach (array_keys($params) as $paramName) {
+ $desc = isset ($paramsDescription[$paramName]) ? $paramsDescription[$paramName] : '';
+ if (is_array($desc))
+ $desc = implode("\n" . str_repeat(' ', 19), $desc);
+ $msg .= sprintf(" %-14s - %s\n", $this->encodeParamName($paramName), $desc);
+ }
+ return $msg;
+
+ } else
+ return false;
+ }
+
+ /**
+ * Returns the description string for this module
+ */
+ protected function getDescription() {
+ return false;
+ }
+
+ /**
+ * Returns usage examples for this module. Return null if no examples are available.
+ */
+ protected function getExamples() {
+ return false;
+ }
+
+ /**
+ * Returns an array of allowed parameters (keys) => default value for that parameter
+ */
+ protected function getAllowedParams() {
+ return false;
+ }
+
+ /**
+ * Returns the description string for the given parameter.
+ */
+ protected function getParamDescription() {
+ return false;
+ }
+
+ /**
+ * This method mangles parameter name based on the prefix supplied to the constructor.
+ * Override this method to change parameter name during runtime
+ */
+ public function encodeParamName($paramName) {
+ return $this->mParamPrefix . $paramName;
+ }
+
+ /**
+ * Using getAllowedParams(), makes an array of the values provided by the user,
+ * with key being the name of the variable, and value - validated value from user or default.
+ * This method can be used to generate local variables using extract().
+ */
+ public function extractRequestParams() {
+ $params = $this->getAllowedParams();
+ $results = array ();
+
+ foreach ($params as $paramName => $paramSettings)
+ $results[$paramName] = $this->getParameterFromSettings($paramName, $paramSettings);
+
+ return $results;
+ }
+
+ /**
+ * Get a value for the given parameter
+ */
+ protected function getParameter($paramName) {
+ $params = $this->getAllowedParams();
+ $paramSettings = $params[$paramName];
+ return $this->getParameterFromSettings($paramName, $paramSettings);
+ }
+
+ /**
+ * Using the settings determine the value for the given parameter
+ * @param $paramName String: parameter name
+ * @param $paramSettings Mixed: default value or an array of settings using PARAM_* constants.
+ */
+ protected function getParameterFromSettings($paramName, $paramSettings) {
+ global $wgRequest;
+
+ // Some classes may decide to change parameter names
+ $paramName = $this->encodeParamName($paramName);
+
+ if (!is_array($paramSettings)) {
+ $default = $paramSettings;
+ $multi = false;
+ $type = gettype($paramSettings);
+ } else {
+ $default = isset ($paramSettings[self :: PARAM_DFLT]) ? $paramSettings[self :: PARAM_DFLT] : null;
+ $multi = isset ($paramSettings[self :: PARAM_ISMULTI]) ? $paramSettings[self :: PARAM_ISMULTI] : false;
+ $type = isset ($paramSettings[self :: PARAM_TYPE]) ? $paramSettings[self :: PARAM_TYPE] : null;
+
+ // When type is not given, and no choices, the type is the same as $default
+ if (!isset ($type)) {
+ if (isset ($default))
+ $type = gettype($default);
+ else
+ $type = 'NULL'; // allow everything
+ }
+ }
+
+ if ($type == 'boolean') {
+ if (isset ($default) && $default !== false) {
+ // Having a default value of anything other than 'false' is pointless
+ ApiBase :: dieDebug(__METHOD__, "Boolean param $paramName's default is set to '$default'");
+ }
+
+ $value = $wgRequest->getCheck($paramName);
+ } else
+ $value = $wgRequest->getVal($paramName, $default);
+
+ if (isset ($value) && ($multi || is_array($type)))
+ $value = $this->parseMultiValue($paramName, $value, $multi, is_array($type) ? $type : null);
+
+ // More validation only when choices were not given
+ // choices were validated in parseMultiValue()
+ if (!is_array($type) && isset ($value)) {
+
+ switch ($type) {
+ case 'NULL' : // nothing to do
+ break;
+ case 'string' : // nothing to do
+ break;
+ case 'integer' : // Force everything using intval()
+ $value = is_array($value) ? array_map('intval', $value) : intval($value);
+ break;
+ case 'limit' :
+ if (!isset ($paramSettings[self :: PARAM_MAX1]) || !isset ($paramSettings[self :: PARAM_MAX2]))
+ ApiBase :: dieDebug(__METHOD__, "MAX1 or MAX2 are not defined for the limit $paramName");
+ if ($multi)
+ ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName");
+ $min = isset ($paramSettings[self :: PARAM_MIN]) ? $paramSettings[self :: PARAM_MIN] : 0;
+ $value = intval($value);
+ $this->validateLimit($paramName, $value, $min, $paramSettings[self :: PARAM_MAX1], $paramSettings[self :: PARAM_MAX2]);
+ break;
+ case 'boolean' :
+ if ($multi)
+ ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName");
+ break;
+ case 'timestamp' :
+ if ($multi)
+ ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName");
+ if (!preg_match('/^[0-9]{14}$/', $value))
+ $this->dieUsage("Invalid value '$value' for timestamp parameter $paramName", "badtimestamp_{$valueName}");
+ break;
+ default :
+ ApiBase :: dieDebug(__METHOD__, "Param $paramName's type is unknown - $type");
+
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 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)
+ */
+ protected function parseMultiValue($valueName, $value, $allowMultiple, $allowedValues) {
+ $valuesList = explode('|', $value);
+ 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 '" : " '") . implode("', '", $unknownValues) . "' for parameter '$valueName'", "unknown_$valueName");
+ }
+ }
+
+ return $allowMultiple ? $valuesList : $valuesList[0];
+ }
+
+ /**
+ * Validate the value against the minimum and user/bot maximum limits. Prints usage info on failure.
+ */
+ function validateLimit($varname, $value, $min, $max, $botMax) {
+ global $wgUser;
+
+ if ($value < $min) {
+ $this->dieUsage("$varname may not be less than $min (set to $value)", $varname);
+ }
+
+ if ($this->getMain()->isBot()) {
+ if ($value > $botMax) {
+ $this->dieUsage("$varname may not be over $botMax (set to $value) for bots", $varname);
+ }
+ }
+ elseif ($value > $max) {
+ $this->dieUsage("$varname may not be over $max (set to $value) for users", $varname);
+ }
+ }
+
+ /**
+ * Call main module's error handler
+ */
+ public function dieUsage($description, $errorCode, $httpRespCode = 0) {
+ $this->getMain()->mainDieUsage($description, $this->encodeParamName($errorCode), $httpRespCode);
+ }
+
+ /**
+ * Internal code errors should be reported with this method
+ */
+ protected static function dieDebug($method, $message) {
+ wfDebugDieBacktrace("Internal error in $method: $message");
+ }
+
+ /**
+ * Profiling: total module execution time
+ */
+ private $mTimeIn = 0, $mModuleTime = 0;
+
+ /**
+ * Start module profiling
+ */
+ public function profileIn() {
+ if ($this->mTimeIn !== 0)
+ ApiBase :: dieDebug(__METHOD__, 'called twice without calling profileOut()');
+ $this->mTimeIn = microtime(true);
+ }
+
+ /**
+ * End module profiling
+ */
+ public function profileOut() {
+ if ($this->mTimeIn === 0)
+ ApiBase :: dieDebug(__METHOD__, 'called without calling profileIn() first');
+ if ($this->mDBTimeIn !== 0)
+ ApiBase :: dieDebug(__METHOD__, 'must be called after database profiling is done with profileDBOut()');
+
+ $this->mModuleTime += microtime(true) - $this->mTimeIn;
+ $this->mTimeIn = 0;
+ }
+
+ /**
+ * Total time the module was executed
+ */
+ public function getProfileTime() {
+ if ($this->mTimeIn !== 0)
+ ApiBase :: dieDebug(__METHOD__, 'called without calling profileOut() first');
+ return $this->mModuleTime;
+ }
+
+ /**
+ * Profiling: database execution time
+ */
+ private $mDBTimeIn = 0, $mDBTime = 0;
+
+ /**
+ * Start module profiling
+ */
+ public function profileDBIn() {
+ if ($this->mTimeIn === 0)
+ ApiBase :: dieDebug(__METHOD__, 'must be called while profiling the entire module with profileIn()');
+ if ($this->mDBTimeIn !== 0)
+ ApiBase :: dieDebug(__METHOD__, 'called twice without calling profileDBOut()');
+ $this->mDBTimeIn = microtime(true);
+ }
+
+ /**
+ * End database profiling
+ */
+ public function profileDBOut() {
+ if ($this->mTimeIn === 0)
+ ApiBase :: dieDebug(__METHOD__, 'must be called while profiling the entire module with profileIn()');
+ if ($this->mDBTimeIn === 0)
+ ApiBase :: dieDebug(__METHOD__, 'called without calling profileDBIn() first');
+
+ $time = microtime(true) - $this->mDBTimeIn;
+ $this->mDBTimeIn = 0;
+
+ $this->mDBTime += $time;
+ $this->getMain()->mDBTime += $time;
+ }
+
+ /**
+ * Total time the module used the database
+ */
+ public function getProfileDBTime() {
+ if ($this->mDBTimeIn !== 0)
+ ApiBase :: dieDebug(__METHOD__, 'called without calling profileDBOut() first');
+ return $this->mDBTime;
+ }
+
+ public abstract function getVersion();
+
+ public static function getBaseVersion() {
+ return __CLASS__ . ': $Id: ApiBase.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
new file mode 100644
index 00000000..6f5b4aca
--- /dev/null
+++ b/includes/api/ApiFormatBase.php
@@ -0,0 +1,161 @@
+<?php
+
+
+/*
+ * Created on Sep 19, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+abstract class ApiFormatBase extends ApiBase {
+
+ private $mIsHtml, $mFormat;
+
+ /**
+ * Constructor
+ */
+ public function __construct($main, $format) {
+ parent :: __construct($main, $format);
+
+ $this->mIsHtml = (substr($format, -2, 2) === 'fm'); // ends with 'fm'
+ if ($this->mIsHtml)
+ $this->mFormat = substr($format, 0, -2); // remove ending 'fm'
+ else
+ $this->mFormat = $format;
+ $this->mFormat = strtoupper($this->mFormat);
+ }
+
+ /**
+ * Overriding class returns the mime type that should be sent to the client.
+ * This method is not called if getIsHtml() returns true.
+ * @return string
+ */
+ public abstract function getMimeType();
+
+ public function getNeedsRawData() {
+ return false;
+ }
+
+ /**
+ * Returns true when an HTML filtering printer should be used.
+ * The default implementation assumes that formats ending with 'fm'
+ * should be formatted in HTML.
+ */
+ public function getIsHtml() {
+ return $this->mIsHtml;
+ }
+
+ /**
+ * Initialize the printer function and prepares the output headers, etc.
+ * This method must be the first outputing method during execution.
+ * A help screen's header is printed for the HTML-based output
+ */
+ function initPrinter($isError) {
+ $isHtml = $this->getIsHtml();
+ $mime = $isHtml ? 'text/html' : $this->getMimeType();
+ header("Content-Type: $mime; charset=utf-8;");
+
+ if ($isHtml) {
+?>
+ <html>
+ <head>
+ <title>MediaWiki API</title>
+ </head>
+ <body>
+<?php
+
+
+ if (!$isError) {
+?>
+ <br/>
+ <small>
+ This result is being shown in <?=$this->mFormat?> format,
+ which might not be suitable for your application.<br/>
+ See <a href='api.php'>API help</a> for more information.<br/>
+ </small>
+<?php
+
+
+ }
+?>
+ <pre>
+<?php
+
+
+ }
+ }
+
+ /**
+ * Finish printing. Closes HTML tags.
+ */
+ public function closePrinter() {
+ if ($this->getIsHtml()) {
+?>
+ </pre>
+ </body>
+<?php
+
+
+ }
+ }
+
+ public function printText($text) {
+ if ($this->getIsHtml())
+ echo $this->formatHTML($text);
+ else
+ echo $text;
+ }
+
+ /**
+ * Prety-print various elements in HTML format, such as xml tags and URLs.
+ * This method also replaces any '<' with &lt;
+ */
+ protected function formatHTML($text) {
+ // encode all tags as safe blue strings
+ $text = ereg_replace('\<([^>]+)\>', '<font color=blue>&lt;\1&gt;</font>', $text);
+ // identify URLs
+ $text = ereg_replace("[a-zA-Z]+://[^ '()<\n]+", '<a href="\\0">\\0</a>', $text);
+ // identify requests to api.php
+ $text = ereg_replace("api\\.php\\?[^ ()<\n\t]+", '<a href="\\0">\\0</a>', $text);
+ // 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);
+
+ return $text;
+ }
+
+ /**
+ * Returns usage examples for this format.
+ */
+ protected function getExamples() {
+ return 'api.php?action=query&meta=siteinfo&si=namespaces&format=' . $this->getModuleName();
+ }
+
+ public static function getBaseVersion() {
+ return __CLASS__ . ': $Id: ApiFormatBase.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
new file mode 100644
index 00000000..fdc29cf2
--- /dev/null
+++ b/includes/api/ApiFormatJson.php
@@ -0,0 +1,56 @@
+<?php
+
+
+/*
+ * Created on Sep 19, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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 ('ApiFormatBase.php');
+}
+
+class ApiFormatJson extends ApiFormatBase {
+
+ public function __construct($main, $format) {
+ parent :: __construct($main, $format);
+ }
+
+ public function getMimeType() {
+ return 'application/json';
+ }
+
+ public function execute() {
+ require ('ApiFormatJson_json.php');
+ $json = new Services_JSON();
+ $this->printText($json->encode($this->getResultData(), true));
+ }
+
+ protected function getDescription() {
+ return 'Output data in JSON format';
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFormatJson.php 16725 2006-10-01 21:20:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatJson_json.php b/includes/api/ApiFormatJson_json.php
new file mode 100644
index 00000000..375de7eb
--- /dev/null
+++ b/includes/api/ApiFormatJson_json.php
@@ -0,0 +1,841 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+* Converts to and from JSON format.
+*
+* JSON (JavaScript Object Notation) is a lightweight data-interchange
+* format. It is easy for humans to read and write. It is easy for machines
+* to parse and generate. It is based on a subset of the JavaScript
+* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+* This feature can also be found in Python. JSON is a text format that is
+* completely language independent but uses conventions that are familiar
+* to programmers of the C-family of languages, including C, C++, C#, Java,
+* JavaScript, Perl, TCL, and many others. These properties make JSON an
+* ideal data-interchange language.
+*
+* This package provides a simple encoder and decoder for JSON notation. It
+* is intended for use with client-side Javascript applications that make
+* use of HTTPRequest to perform server communication functions - data can
+* be encoded into JSON notation for use in a client-side javascript, or
+* decoded from incoming Javascript requests. JSON format is native to
+* Javascript, and can be directly eval()'ed with no further parsing
+* overhead
+*
+* All strings should be in ASCII or UTF-8 format!
+*
+* LICENSE: Redistribution and use in source and binary forms, with or
+* without modification, are permitted provided that the following
+* conditions are met: Redistributions of source code must retain the
+* above copyright notice, this list of conditions and the following
+* disclaimer. Redistributions in binary form must reproduce the above
+* copyright notice, this list of conditions and the following disclaimer
+* in the documentation and/or other materials provided with the
+* distribution.
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+* DAMAGE.
+*
+* @category
+* @package Services_JSON
+* @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 $
+* @license http://www.opensource.org/licenses/bsd-license.php
+* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+*/
+
+/**
+* Marker constant for Services_JSON::decode(), used to flag stack state
+*/
+define('SERVICES_JSON_SLICE', 1);
+
+/**
+* Marker constant for Services_JSON::decode(), used to flag stack state
+*/
+define('SERVICES_JSON_IN_STR', 2);
+
+/**
+* Marker constant for Services_JSON::decode(), used to flag stack state
+*/
+define('SERVICES_JSON_IN_ARR', 3);
+
+/**
+* Marker constant for Services_JSON::decode(), used to flag stack state
+*/
+define('SERVICES_JSON_IN_OBJ', 4);
+
+/**
+* Marker constant for Services_JSON::decode(), used to flag stack state
+*/
+define('SERVICES_JSON_IN_CMT', 5);
+
+/**
+* Behavior switch for Services_JSON::decode()
+*/
+define('SERVICES_JSON_LOOSE_TYPE', 16);
+
+/**
+* Behavior switch for Services_JSON::decode()
+*/
+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
+
+/**
+* Converts to and from JSON format.
+*
+* Brief example of use:
+*
+* <code>
+* // create a new instance of Services_JSON
+* $json = new Services_JSON();
+*
+* // convert a complexe value to JSON notation, and send it to the browser
+* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
+* $output = $json->encode($value);
+*
+* print($output);
+* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
+*
+* // accept incoming POST data, assumed to be in JSON notation
+* $input = file_get_contents('php://input', 1000000);
+* $value = $json->decode($input);
+* </code>
+*/
+class Services_JSON
+{
+ /**
+ * constructs a new JSON instance
+ *
+ * @param int $use object behavior flags; combine with boolean-OR
+ *
+ * possible values:
+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
+ * "{...}" syntax creates associative arrays
+ * instead of objects in decode().
+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
+ * Values which can't be encoded (e.g. resources)
+ * appear as NULL instead of throwing errors.
+ * By default, a deeply-nested resource will
+ * bubble up with an error, so all return values
+ * from encode() should be checked with isError()
+ */
+ function Services_JSON($use = 0)
+ {
+ $this->use = $use;
+ }
+
+ /**
+ * convert a string from one UTF-16 char to one UTF-8 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf16 UTF-16 character
+ * @return string UTF-8 character
+ * @access private
+ */
+ function utf162utf8($utf16)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+ }
+
+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+ switch(true) {
+ case ((0x7F & $bytes) == $bytes):
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x7F & $bytes);
+
+ case (0x07FF & $bytes) == $bytes:
+ // return a 2-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
+ . chr(0x80 | ($bytes & 0x3F));
+
+ case (0xFFFF & $bytes) == $bytes:
+ // return a 3-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
+ . chr(0x80 | ($bytes & 0x3F));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * convert a string from one UTF-8 char to one UTF-16 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf8 UTF-8 character
+ * @return string UTF-16 character
+ * @access private
+ */
+ function utf82utf16($utf8)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+ }
+
+ switch(strlen($utf8)) {
+ case 1:
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return $utf8;
+
+ case 2:
+ // return a UTF-16 character from a 2-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x07 & (ord($utf8{0}) >> 2))
+ . chr((0xC0 & (ord($utf8{0}) << 6))
+ | (0x3F & ord($utf8{1})));
+
+ case 3:
+ // return a UTF-16 character from a 3-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr((0xF0 & (ord($utf8{0}) << 4))
+ | (0x0F & (ord($utf8{1}) >> 2)))
+ . chr((0xC0 & (ord($utf8{1}) << 6))
+ | (0x7F & ord($utf8{2})));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * encodes an arbitrary variable into JSON format
+ *
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
+ * if var is a strng, note that encode() always expects it
+ * to be in ASCII or UTF-8 format!
+ * @param bool $pretty pretty-print output with indents and newlines
+ *
+ * @return mixed JSON string representation of input var or an error if a problem occurs
+ * @access public
+ */
+ function encode($var, $pretty=false)
+ {
+ $this->indent = 0;
+ $this->pretty = $pretty;
+ $this->nameValSeparator = $pretty ? ': ' : ':';
+ return $this->encode2($var);
+ }
+
+ /**
+ * encodes an arbitrary variable into JSON format
+ *
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
+ * if var is a strng, note that encode() always expects it
+ * to be in ASCII or UTF-8 format!
+ *
+ * @return mixed JSON string representation of input var or an error if a problem occurs
+ * @access private
+ */
+ function encode2($var)
+ {
+ if ($this->pretty) {
+ $close = "\n" . str_repeat("\t", $this->indent);
+ $open = $close . "\t";
+ $mid = ',' . $open;
+ }
+ else {
+ $open = $close = '';
+ $mid = ',';
+ }
+
+ switch (gettype($var)) {
+ case 'boolean':
+ return $var ? 'true' : 'false';
+
+ case 'NULL':
+ return 'null';
+
+ case 'integer':
+ return (int) $var;
+
+ case 'double':
+ case 'float':
+ return (float) $var;
+
+ case 'string':
+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+ $ascii = '';
+ $strlen_var = strlen($var);
+
+ /*
+ * Iterate over every character in the string,
+ * escaping with a slash or encoding to UTF-8 where necessary
+ */
+ for ($c = 0; $c < $strlen_var; ++$c) {
+
+ $ord_var_c = ord($var{$c});
+
+ switch (true) {
+ case $ord_var_c == 0x08:
+ $ascii .= '\b';
+ break;
+ case $ord_var_c == 0x09:
+ $ascii .= '\t';
+ break;
+ case $ord_var_c == 0x0A:
+ $ascii .= '\n';
+ break;
+ case $ord_var_c == 0x0C:
+ $ascii .= '\f';
+ break;
+ case $ord_var_c == 0x0D:
+ $ascii .= '\r';
+ break;
+
+ case $ord_var_c == 0x22:
+ case $ord_var_c == 0x2F:
+ case $ord_var_c == 0x5C:
+ // double quote, slash, slosh
+ $ascii .= '\\'.$var{$c};
+ break;
+
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+ // characters U-00000000 - U-0000007F (same as ASCII)
+ $ascii .= $var{$c};
+ break;
+
+ case (($ord_var_c & 0xE0) == 0xC0):
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+ $c += 1;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF0) == 0xE0):
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}));
+ $c += 2;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF8) == 0xF0):
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}));
+ $c += 3;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFC) == 0xF8):
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}));
+ $c += 4;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFE) == 0xFC):
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}),
+ ord($var{$c + 5}));
+ $c += 5;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+ }
+ }
+
+ return '"'.$ascii.'"';
+
+ case 'array':
+ /*
+ * As per JSON spec if any array key is not an integer
+ * we must treat the the whole array as an object. We
+ * also try to catch a sparsely populated associative
+ * array with numeric keys here because some JS engines
+ * will create an array with empty indexes up to
+ * max_index which can cause memory issues and because
+ * the keys, which may be relevant, will be remapped
+ * otherwise.
+ *
+ * As per the ECMA and JSON specification an object may
+ * have any string as a property. Unfortunately due to
+ * a hole in the ECMA specification if the key is a
+ * ECMA reserved word or starts with a digit the
+ * parameter is only accessible using ECMAScript's
+ * bracket notation.
+ */
+
+ // treat as a JSON object
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+ $this->indent++;
+ $properties = array_map(array($this, 'name_value'),
+ array_keys($var),
+ array_values($var));
+ $this->indent--;
+
+ foreach($properties as $property) {
+ if(Services_JSON::isError($property)) {
+ return $property;
+ }
+ }
+
+ return '{' . $open . join($mid, $properties) . $close . '}';
+ }
+
+ // treat it like a regular array
+ $this->indent++;
+ $elements = array_map(array($this, 'encode2'), $var);
+ $this->indent--;
+
+ foreach($elements as $element) {
+ if(Services_JSON::isError($element)) {
+ return $element;
+ }
+ }
+
+ return '[' . $open . join($mid, $elements) . $close . ']';
+
+ case 'object':
+ $vars = get_object_vars($var);
+
+ $this->indent++;
+ $properties = array_map(array($this, 'name_value'),
+ array_keys($vars),
+ array_values($vars));
+ $this->indent--;
+
+ foreach($properties as $property) {
+ if(Services_JSON::isError($property)) {
+ return $property;
+ }
+ }
+
+ return '{' . $open . join($mid, $properties) . $close . '}';
+
+ default:
+ return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+ ? 'null'
+ : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
+ }
+ }
+
+ /**
+ * array-walking function for use in generating JSON-formatted name-value pairs
+ *
+ * @param string $name name of key to use
+ * @param mixed $value reference to an array element to be encoded
+ *
+ * @return string JSON-formatted name-value pair, like '"name":value'
+ * @access private
+ */
+ function name_value($name, $value)
+ {
+ $encoded_value = $this->encode2($value);
+
+ if(Services_JSON::isError($encoded_value)) {
+ return $encoded_value;
+ }
+
+ return $this->encode2(strval($name)) . $this->nameValSeparator . $encoded_value;
+ }
+
+ /**
+ * reduce a string by removing leading and trailing comments and whitespace
+ *
+ * @param $str string string value to strip of comments and whitespace
+ *
+ * @return string string value stripped of comments and whitespace
+ * @access private
+ */
+ function reduce_string($str)
+ {
+ $str = preg_replace(array(
+
+ // eliminate single line comments in '// ...' form
+ '#^\s*//(.+)$#m',
+
+ // eliminate multi-line comments in '/* ... */' form, at start of string
+ '#^\s*/\*(.+)\*/#Us',
+
+ // eliminate multi-line comments in '/* ... */' form, at end of string
+ '#/\*(.+)\*/\s*$#Us'
+
+ ), '', $str);
+
+ // eliminate extraneous space
+ return trim($str);
+ }
+
+ /**
+ * decodes a JSON string into appropriate variable
+ *
+ * @param string $str JSON-formatted string
+ *
+ * @return mixed number, boolean, string, array, or object
+ * corresponding to given JSON input string.
+ * See argument 1 to Services_JSON() above for object-output behavior.
+ * Note that decode() always returns strings
+ * in ASCII or UTF-8 format!
+ * @access public
+ */
+ function decode($str)
+ {
+ $str = $this->reduce_string($str);
+
+ switch (strtolower($str)) {
+ case 'true':
+ return true;
+
+ case 'false':
+ return false;
+
+ case 'null':
+ return null;
+
+ default:
+ $m = array();
+
+ if (is_numeric($str)) {
+ // Lookie-loo, it's a number
+
+ // This would work on its own, but I'm trying to be
+ // good about returning integers where appropriate:
+ // return (float)$str;
+
+ // Return float or int, as appropriate
+ return ((float)$str == (integer)$str)
+ ? (integer)$str
+ : (float)$str;
+
+ } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+ // STRINGS RETURNED IN UTF-8 FORMAT
+ $delim = substr($str, 0, 1);
+ $chrs = substr($str, 1, -1);
+ $utf8 = '';
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+ $ord_chrs_c = ord($chrs{$c});
+
+ switch (true) {
+ case $substr_chrs_c_2 == '\b':
+ $utf8 .= chr(0x08);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\t':
+ $utf8 .= chr(0x09);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\n':
+ $utf8 .= chr(0x0A);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\f':
+ $utf8 .= chr(0x0C);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\r':
+ $utf8 .= chr(0x0D);
+ ++$c;
+ break;
+
+ case $substr_chrs_c_2 == '\\"':
+ case $substr_chrs_c_2 == '\\\'':
+ case $substr_chrs_c_2 == '\\\\':
+ case $substr_chrs_c_2 == '\\/':
+ if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+ ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+ $utf8 .= $chrs{++$c};
+ }
+ break;
+
+ case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
+ // single, escaped unicode character
+ $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+ . chr(hexdec(substr($chrs, ($c + 4), 2)));
+ $utf8 .= $this->utf162utf8($utf16);
+ $c += 5;
+ break;
+
+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+ $utf8 .= $chrs{$c};
+ break;
+
+ case ($ord_chrs_c & 0xE0) == 0xC0:
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 2);
+ ++$c;
+ break;
+
+ case ($ord_chrs_c & 0xF0) == 0xE0:
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 3);
+ $c += 2;
+ break;
+
+ case ($ord_chrs_c & 0xF8) == 0xF0:
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 4);
+ $c += 3;
+ break;
+
+ case ($ord_chrs_c & 0xFC) == 0xF8:
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 5);
+ $c += 4;
+ break;
+
+ case ($ord_chrs_c & 0xFE) == 0xFC:
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 6);
+ $c += 5;
+ break;
+
+ }
+
+ }
+
+ return $utf8;
+
+ } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+ // array, or object notation
+
+ if ($str{0} == '[') {
+ $stk = array(SERVICES_JSON_IN_ARR);
+ $arr = array();
+ } else {
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = array();
+ } else {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = new stdClass();
+ }
+ }
+
+ array_push($stk, array('what' => SERVICES_JSON_SLICE,
+ 'where' => 0,
+ 'delim' => false));
+
+ $chrs = substr($str, 1, -1);
+ $chrs = $this->reduce_string($chrs);
+
+ if ($chrs == '') {
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } else {
+ return $obj;
+
+ }
+ }
+
+ //print("\nparsing {$chrs}\n");
+
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+ $top = end($stk);
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+
+ if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+ // found a comma that is not inside a string, array, etc.,
+ // OR we've reached the end of the character list
+ $slice = substr($chrs, $top['where'], ($c - $top['where']));
+ array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+ //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ // we are in an array, so just push an element onto the stack
+ array_push($arr, $this->decode($slice));
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ // we are in an object, so figure
+ // out the property name and set an
+ // 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]);
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // name:value pair, where name is unquoted
+ $key = $parts[1];
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ }
+
+ }
+
+ } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+ // found a quote, and we are not inside a string
+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+ //print("Found start of string at {$c}\n");
+
+ } elseif (($chrs{$c} == $top['delim']) &&
+ ($top['what'] == SERVICES_JSON_IN_STR) &&
+ (($chrs{$c - 1} != '\\') ||
+ ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) {
+ // found a quote, we're in a string, and it's not escaped
+ array_pop($stk);
+ //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '[') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-bracket, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+ //print("Found start of array at {$c}\n");
+
+ } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+ // found a right-bracket, and we're in an array
+ array_pop($stk);
+ //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '{') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-brace, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+ //print("Found start of object at {$c}\n");
+
+ } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+ // found a right-brace, and we're in an object
+ array_pop($stk);
+ //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($substr_chrs_c_2 == '/*') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a comment start, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+ $c++;
+ //print("Found start of comment at {$c}\n");
+
+ } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+ // found a comment end, and we're in one now
+ array_pop($stk);
+ $c++;
+
+ for ($i = $top['where']; $i <= $c; ++$i)
+ $chrs = substr_replace($chrs, ' ', $i, 1);
+
+ //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ }
+
+ }
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ return $obj;
+
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @todo Ultimately, this should just call PEAR::isError()
+ */
+ function isError($data, $code = null)
+ {
+ if (class_exists('pear')) {
+ return PEAR::isError($data, $code);
+ } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+ is_subclass_of($data, 'services_json_error'))) {
+ return true;
+ }
+
+ return false;
+ }
+}
+
+if (class_exists('PEAR_Error')) {
+
+ class Services_JSON_Error extends PEAR_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+ parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+ }
+ }
+
+} else {
+
+ /**
+ * @todo Ultimately, this class shall be descended from PEAR_Error
+ */
+ class Services_JSON_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+
+ }
+ }
+
+}
+
+?>
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
new file mode 100644
index 00000000..6aa08e00
--- /dev/null
+++ b/includes/api/ApiFormatXml.php
@@ -0,0 +1,161 @@
+<?php
+
+
+/*
+ * Created on Sep 19, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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 ('ApiFormatBase.php');
+}
+
+class ApiFormatXml extends ApiFormatBase {
+
+ public function __construct($main, $format) {
+ parent :: __construct($main, $format);
+ }
+
+ public function getMimeType() {
+ return 'text/xml';
+ }
+
+ public function getNeedsRawData() {
+ return true;
+ }
+
+ public function execute() {
+ $xmlindent = null;
+ extract($this->extractRequestParams());
+
+ if ($xmlindent || $this->getIsHtml())
+ $xmlindent = -2;
+ else
+ $xmlindent = null;
+
+ $this->printText('<?xml version="1.0" encoding="utf-8"?>');
+ $this->recXmlPrint('api', $this->getResultData(), $xmlindent);
+ }
+
+ /**
+ * This method takes an array and converts it into an xml.
+ * There are several noteworthy cases:
+ *
+ * If array contains a key '_element', then the code assumes that ALL other keys are not important and replaces them with the value['_element'].
+ * Example: name='root', value = array( '_element'=>'page', 'x', 'y', 'z') creates <root> <page>x</page> <page>y</page> <page>z</page> </root>
+ *
+ * If any of the array's element key is '*', then the code treats all other key->value pairs as attributes, and the value['*'] as the element's content.
+ * Example: name='root', value = array( '*'=>'text', 'lang'=>'en', 'id'=>10) creates <root lang='en' id='10'>text</root>
+ *
+ * If neither key is found, all keys become element names, and values become element content.
+ * The method is recursive, so the same rules apply to any sub-arrays.
+ */
+ function recXmlPrint($elemName, $elemValue, $indent) {
+ if (!is_null($indent)) {
+ $indent += 2;
+ $indstr = "\n" . str_repeat(" ", $indent);
+ } else {
+ $indstr = '';
+ }
+
+ switch (gettype($elemValue)) {
+ case 'array' :
+
+ if (isset ($elemValue['*'])) {
+ $subElemContent = $elemValue['*'];
+ unset ($elemValue['*']);
+ } else {
+ $subElemContent = null;
+ }
+
+ if (isset ($elemValue['_element'])) {
+ $subElemIndName = $elemValue['_element'];
+ unset ($elemValue['_element']);
+ } else {
+ $subElemIndName = null;
+ }
+
+ $indElements = array ();
+ $subElements = array ();
+ foreach ($elemValue as $subElemId => & $subElemValue) {
+ if (gettype($subElemId) === 'integer') {
+ if (!is_array($subElemValue))
+ ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has a scalar indexed value.");
+ $indElements[] = $subElemValue;
+ unset ($elemValue[$subElemId]);
+ } elseif (is_array($subElemValue)) {
+ $subElements[$subElemId] = $subElemValue;
+ unset ($elemValue[$subElemId]);
+ }
+ }
+
+ if (is_null($subElemIndName) && !empty ($indElements))
+ ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has integer keys without _element value");
+
+ if (!empty ($subElements) && !empty ($indElements) && !is_null($subElemContent))
+ ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has content and subelements");
+
+ if (!is_null($subElemContent)) {
+ $this->printText($indstr . wfElement($elemName, $elemValue, $subElemContent));
+ } elseif (empty ($indElements) && empty ($subElements)) {
+ $this->printText($indstr . wfElement($elemName, $elemValue));
+ } else {
+ $this->printText($indstr . wfElement($elemName, $elemValue, null));
+
+ foreach ($subElements as $subElemId => & $subElemValue)
+ $this->recXmlPrint($subElemId, $subElemValue, $indent);
+
+ foreach ($indElements as $subElemId => & $subElemValue)
+ $this->recXmlPrint($subElemIndName, $subElemValue, $indent);
+
+ $this->printText($indstr . wfCloseElement($elemName));
+ }
+ break;
+ case 'object' :
+ // ignore
+ break;
+ default :
+ $this->printText($indstr . wfElement($elemName, null, $elemValue));
+ break;
+ }
+ }
+ protected function getDescription() {
+ return 'Output data in XML format';
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'xmlindent' => false
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'xmlindent' => 'Enable XML indentation'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFormatXml.php 16725 2006-10-01 21:20:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
new file mode 100644
index 00000000..bd74f01a
--- /dev/null
+++ b/includes/api/ApiFormatYaml.php
@@ -0,0 +1,55 @@
+<?php
+
+
+/*
+ * Created on Sep 19, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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 ('ApiFormatBase.php');
+}
+
+class ApiFormatYaml extends ApiFormatBase {
+
+ public function __construct($main, $format) {
+ parent :: __construct($main, $format);
+ }
+
+ public function getMimeType() {
+ return 'application/yaml';
+ }
+
+ public function execute() {
+ require ('ApiFormatYaml_spyc.php');
+ $this->printText(Spyc :: YAMLDump($this->getResultData()));
+ }
+
+ protected function getDescription() {
+ return 'Output data in YAML format';
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFormatYaml.php 16725 2006-10-01 21:20:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatYaml_spyc.php b/includes/api/ApiFormatYaml_spyc.php
new file mode 100644
index 00000000..05a39e23
--- /dev/null
+++ b/includes/api/ApiFormatYaml_spyc.php
@@ -0,0 +1,854 @@
+<?php
+ /**
+ * Spyc -- A Simple PHP YAML Class
+ * @version 0.2.3 -- 2006-02-04
+ * @author Chris Wanstrath <chris@ozmm.org>
+ * @link http://spyc.sourceforge.net/
+ * @copyright Copyright 2005-2006 Chris Wanstrath
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @package Spyc
+ */
+
+ /**
+ * A node, used by Spyc for parsing YAML.
+ * @package Spyc
+ */
+ 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
+ */
+ var $children = false;
+
+ /**
+ * The constructor assigns the node a unique ID.
+ * @access public
+ * @return void
+ */
+ function YAMLNode() {
+ $this->id = uniqid('');
+ }
+ }
+
+ /**
+ * The Simple PHP YAML Class.
+ *
+ * This class can be used to read a YAML file and convert its contents
+ * into a PHP array. It currently supports a very limited subsection of
+ * the YAML spec.
+ *
+ * Usage:
+ * <code>
+ * $parser = new Spyc;
+ * $array = $parser->load($file);
+ * </code>
+ * @package Spyc
+ */
+ 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
+ * simple.
+ * Usage:
+ * <code>
+ * $array = Spyc::YAMLLoad('lucky.yml');
+ * print_r($array);
+ * </code>
+ * @access public
+ * @return array
+ * @param string $input Path of YAML file or string containing YAML
+ */
+ function YAMLLoad($input) {
+ $spyc = new Spyc;
+ return $spyc->load($input);
+ }
+
+ /**
+ * Dump YAML from PHP array statically
+ *
+ * The dump method, when supplied with an array, will do its best
+ * to convert the array into friendly YAML. Pretty simple. Feel free to
+ * 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
+ * you want to use the default.
+ *
+ * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
+ * you can turn off wordwrap by passing in 0.
+ *
+ * @access public
+ * @static
+ * @return string
+ * @param array $array PHP array
+ * @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),
+ * will do its best to convert the YAML into a PHP array. Pretty simple.
+ * Usage:
+ * <code>
+ * $parser = new Spyc;
+ * $array = $parser->load('lucky.yml');
+ * print_r($array);
+ * </code>
+ * @access public
+ * @return array
+ * @param string $input Path of YAML file or string containing YAML
+ */
+ 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)
+ && file_exists($input)) {
+ $yaml = file($input);
+ } else {
+ $yaml = explode("\n",$input);
+ }
+ // Initiate some objects and values
+ $base = new YAMLNode;
+ $base->indent = 0;
+ $this->_lastIndent = 0;
+ $this->_lastNode = $base->id;
+ $this->_inBlock = false;
+ $this->_isInline = false;
+
+ foreach ($yaml as $linenum => $line) {
+ $ifchk = trim($line);
+
+ // If the line starts with a tab (instead of a space), throw a fit.
+ if (preg_match('/^(\t)+(\w+)/', $line)) {
+ $err = 'ERROR: Line '. ($linenum + 1) .' in your input YAML begins'.
+ ' with a tab. YAML only recognizes spaces. Please reformat.';
+ die($err);
+ }
+
+ if ($this->_inBlock === false && empty($ifchk)) {
+ continue;
+ } elseif ($this->_inBlock == true && empty($ifchk)) {
+ $last =& $this->_allNodes[$this->_lastNode];
+ $last->data[key($last->data)] .= "\n";
+ } elseif ($ifchk{0} != '#' && substr($ifchk,0,3) != '---') {
+ // 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
+ if ($this->_inBlock === true) {
+ $parent =& $this->_allNodes[$this->_lastNode];
+ $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd;
+ } else {
+ // The current node's parent is the same as the previous node's
+ if (isset($this->_allNodes[$this->_lastNode])) {
+ $node->parent = $this->_allNodes[$this->_lastNode]->parent;
+ }
+ }
+ } 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
+ // we drop our indent.
+ $parent =& $this->_allNodes[$node->parent];
+ $this->_allNodes[$node->parent]->children = true;
+ if (is_array($parent->data)) {
+ $chk = $parent->data[key($parent->data)];
+ if ($chk === '>') {
+ $this->_inBlock = true;
+ $this->_blockEnd = ' ';
+ $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;
+ $this->_lastIndent = $node->indent;
+ } elseif ($chk === '|') {
+ $this->_inBlock = true;
+ $this->_blockEnd = "\n";
+ $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;
+ $this->_lastIndent = $node->indent;
+ }
+ }
+ }
+ } elseif ($this->_lastIndent > $node->indent) {
+ // Any block we had going is dead now
+ if ($this->_inBlock === true) {
+ $this->_inBlock = false;
+ if ($this->_blockEnd = "\n") {
+ $last =& $this->_allNodes[$this->_lastNode];
+ $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) {
+ if ($n->indent == $node->indent) {
+ $node->parent = $n->parent;
+ }
+ }
+ }
+
+ if ($this->_inBlock === false) {
+ // Set these properties with information from our current node
+ $this->_lastIndent = $node->indent;
+ // Set the last node
+ $this->_lastNode = $node->id;
+ // Parse the YAML line and return its data
+ $node->data = $this->_parseLine($line);
+ // Add the node to the master list
+ $this->_allNodes[$node->id] = $node;
+ // Add a reference to the node in an indent array
+ $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 (
+ ( (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)])) )
+ ) {
+ $this->_haveRefs[] =& $this->_allNodes[$node->id];
+ } elseif (
+ ( (is_array($node->data)) &&
+ isset($node->data[key($node->data)]) &&
+ (is_array($node->data[key($node->data)])) )
+ ) {
+ // Incomplete reference making code. Ugly, needs cleaned up.
+ foreach ($node->data[key($node->data)] as $d) {
+ if ( !is_array($d) &&
+ ( (preg_match('/^&([^ ]+)/',$d))
+ ||
+ (preg_match('/^\*([^ ]+)/',$d)) )
+ ) {
+ $this->_haveRefs[] =& $this->_allNodes[$node->id];
+ }
+ }
+ }
+ }
+ }
+ }
+ 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
+ *
+ * The dump method, when supplied with an array, will do its best
+ * to convert the array into friendly YAML. Pretty simple. Feel free to
+ * 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
+ * you want to use the default.
+ *
+ * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
+ * you can turn off wordwrap by passing in 0.
+ *
+ * @access public
+ * @return string
+ * @param array $array PHP array
+ * @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) {
+ // Dumps to some very clean YAML. We'll have to add some more features
+ // and options soon. And better support for folding.
+
+ // New features and options.
+ if ($indent === false or !is_numeric($indent)) {
+ $this->_dumpIndent = 2;
+ } 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;
+ var $_lastNode;
+ var $_inBlock;
+ var $_isInline;
+ var $_dumpIndent;
+ var $_dumpWordWrap;
+ /**#@+*/
+
+ /**** Private Methods ****/
+
+ /**
+ * Attempts to convert a key / value array item to YAML
+ * @access private
+ * @return string
+ * @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?
+ // Make it the right kind of item
+ $string = $this->_dumpNode($key,NULL,$indent);
+ // Add the indent
+ $indent += $this->_dumpIndent;
+ // Yamlize the array
+ $string .= $this->_yamlizeArray($value,$indent);
+ } elseif (!is_array($value)) {
+ // It doesn't have children. Yip.
+ $string = $this->_dumpNode($key,$value,$indent);
+ }
+ 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 = '';
+ foreach ($array as $key => $value) {
+ $string .= $this->_yamlize($key,$value,$indent);
+ }
+ return $string;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns YAML from a key and a value
+ * @access private
+ * @return string
+ * @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")) {
+ $value = $this->_doLiteralBlock($value,$indent);
+ } else {
+ $value = $this->_doFolding($value,$indent);
+ }
+
+ $spaces = str_repeat(' ',$indent);
+
+ if (is_int($key)) {
+ // It's a sequence
+ $string = $spaces.'- '.$value."\n";
+ } else {
+ // It's mapped
+ $string = $spaces.$key.': '.$value."\n";
+ }
+ return $string;
+ }
+
+ /**
+ * Creates a literal block for dumping
+ * @access private
+ * @return string
+ * @param $value
+ * @param $indent int The value of the indent
+ */
+ function _doLiteralBlock($value,$indent) {
+ $exploded = explode("\n",$value);
+ $newValue = '|';
+ $indent += $this->_dumpIndent;
+ $spaces = str_repeat(' ',$indent);
+ foreach ($exploded as $line) {
+ $newValue .= "\n" . $spaces . trim($line);
+ }
+ return $newValue;
+ }
+
+ /**
+ * Folds a string of text, if necessary
+ * @access private
+ * @return string
+ * @param $value The string you wish to fold
+ */
+ function _doFolding($value,$indent) {
+ // Don't do anything if wordwrap is set to 0
+ if ($this->_dumpWordWrap === 0) {
+ return $value;
+ }
+
+ if (strlen($value) > $this->_dumpWordWrap) {
+ $indent += $this->_dumpIndent;
+ $indent = str_repeat(' ',$indent);
+ $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent");
+ $value = ">\n".$indent.$wrapped;
+ }
+ return $value;
+ }
+
+ /* Methods used in loading */
+
+ /**
+ * Finds and returns the indentation of a YAML line
+ * @access private
+ * @return int
+ * @param string $line A line from the YAML file
+ */
+ function _getIndent($line) {
+ preg_match('/^\s{1,}/',$line,$match);
+ if (!empty($match[0])) {
+ $indent = substr_count($match[0],' ');
+ } else {
+ $indent = 0;
+ }
+ return $indent;
+ }
+
+ /**
+ * Parses YAML code and returns an array for a node
+ * @access private
+ * @return array
+ * @param string $line A line from the YAML file
+ */
+ function _parseLine($line) {
+ $line = trim($line);
+
+ $array = array();
+
+ if (preg_match('/^-(.*):$/',$line)) {
+ // It's a mapped sequence
+ $key = trim(substr(substr($line,1),0,-1));
+ $array[$key] = '';
+ } elseif ($line[0] == '-' && substr($line,0,3) != '---') {
+ // It's a list item but not a new stream
+ if (strlen($line) > 1) {
+ $value = trim(substr($line,1));
+ // Set the type of the value. Int, string, etc
+ $value = $this->_toType($value);
+ $array[] = $value;
+ } else {
+ $array[] = array();
+ }
+ } elseif (preg_match('/^(.+):/',$line,$key)) {
+ // It's a key/value pair most likely
+ // If the key is in double quotes pull it out
+ if (preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
+ $value = trim(str_replace($matches[1],'',$line));
+ $key = $matches[2];
+ } else {
+ // Do some guesswork as to the key and the value
+ $explode = explode(':',$line);
+ $key = trim($explode[0]);
+ array_shift($explode);
+ $value = trim(implode(':',$explode));
+ }
+
+ // Set the type of the value. Int, string, etc
+ $value = $this->_toType($value);
+ if (empty($key)) {
+ $array[] = $value;
+ } else {
+ $array[$key] = $value;
+ }
+ }
+ return $array;
+ }
+
+ /**
+ * Finds the type of the passed value, returns the value as the new type.
+ * @access private
+ * @param string $value
+ * @return mixed
+ */
+ function _toType($value) {
+ if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) {
+ $value = (string)preg_replace('/(\'\'|\\\\\')/',"'",end($matches));
+ $value = preg_replace('/\\\\"/','"',$value);
+ } elseif (preg_match('/^\\[(.+)\\]$/',$value,$matches)) {
+ // Inline Sequence
+
+ // Take out strings sequences and mappings
+ $explode = $this->_inlineEscape($matches[1]);
+
+ // Propogate value array
+ $value = array();
+ foreach ($explode as $v) {
+ $value[] = $this->_toType($v);
+ }
+ } elseif (strpos($value,': ')!==false && !preg_match('/^{(.+)/',$value)) {
+ // It's a map
+ $array = explode(': ',$value);
+ $key = trim($array[0]);
+ array_shift($array);
+ $value = trim(implode(': ',$array));
+ $value = $this->_toType($value);
+ $value = array($key => $value);
+ } elseif (preg_match("/{(.+)}$/",$value,$matches)) {
+ // Inline Mapping
+
+ // Take out strings sequences and mappings
+ $explode = $this->_inlineEscape($matches[1]);
+
+ // Propogate value array
+ $array = array();
+ foreach ($explode as $v) {
+ $array = $array + $this->_toType($v);
+ }
+ $value = $array;
+ } elseif (strtolower($value) == 'null' or $value == '' or $value == '~') {
+ $value = NULL;
+ } elseif (ctype_digit($value)) {
+ $value = (int)$value;
+ } elseif (in_array(strtolower($value),
+ array('true', 'on', '+', 'yes', 'y'))) {
+ $value = TRUE;
+ } elseif (in_array(strtolower($value),
+ array('false', 'off', '-', 'no', 'n'))) {
+ $value = FALSE;
+ } elseif (is_numeric($value)) {
+ $value = (float)$value;
+ } else {
+ // 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
+ * @return array
+ */
+ function _inlineEscape($inline) {
+ // There's gotta be a cleaner way to do this...
+ // 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
+ $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
+ if (preg_match_all($regex,$inline,$strings)) {
+ $saved_strings[] = $strings[0][0];
+ $inline = preg_replace($regex,'YAMLString',$inline);
+ }
+ unset($regex);
+
+ // Check for sequences
+ if (preg_match_all('/\[(.+)\]/U',$inline,$seqs)) {
+ $inline = preg_replace('/\[(.+)\]/U','YAMLSeq',$inline);
+ $seqs = $seqs[0];
+ }
+
+ // Check for mappings
+ if (preg_match_all('/{(.+)}/U',$inline,$maps)) {
+ $inline = preg_replace('/{(.+)}/U','YAMLMap',$inline);
+ $maps = $maps[0];
+ }
+
+ $explode = explode(', ',$inline);
+
+ // Re-add the strings
+ if (!empty($saved_strings)) {
+ $i = 0;
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLString')) {
+ $explode[$key] = str_replace('YAMLString',$saved_strings[$i],$value);
+ ++$i;
+ }
+ }
+ }
+
+ // Re-add the sequences
+ if (!empty($seqs)) {
+ $i = 0;
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLSeq') !== false) {
+ $explode[$key] = str_replace('YAMLSeq',$seqs[$i],$value);
+ ++$i;
+ }
+ }
+ }
+
+ // Re-add the mappings
+ if (!empty($maps)) {
+ $i = 0;
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLMap') !== false) {
+ $explode[$key] = str_replace('YAMLMap',$maps[$i],$value);
+ ++$i;
+ }
+ }
+ }
+
+ return $explode;
+ }
+
+ /**
+ * Builds the PHP array from all the YAML nodes we've gathered
+ * @access private
+ * @return array
+ */
+ function _buildArray() {
+ $trunk = array();
+
+ if (!isset($this->_indentSort[0])) {
+ return $trunk;
+ }
+
+ foreach ($this->_indentSort[0] as $n) {
+ if (empty($n->parent)) {
+ $this->_nodeArrayizeData($n);
+ // Check for references and copy the needed data to complete them.
+ $this->_makeReferences($n);
+ // Merge our data with the big array we're building
+ $trunk = $this->_array_kmerge($trunk,$n->data);
+ }
+ }
+
+ return $trunk;
+ }
+
+ /**
+ * Traverses node-space and sets references (& and *) accordingly
+ * @access private
+ * @return bool
+ */
+ function _linkReferences() {
+ if (is_array($this->_haveRefs)) {
+ foreach ($this->_haveRefs as $node) {
+ if (!empty($node->data)) {
+ $key = key($node->data);
+ // If it's an array, don't check.
+ if (is_array($node->data[$key])) {
+ foreach ($node->data[$key] as $k => $v) {
+ $this->_linkRef($node,$key,$k,$v);
+ }
+ } else {
+ $this->_linkRef($node,$key);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ function _linkRef(&$n,$key,$k = NULL,$v = NULL) {
+ if (empty($k) && empty($v)) {
+ // Look for &refs
+ 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] =
+ substr($n->data[$key],strlen($matches[0])+1);
+ // Look for *refs
+ } elseif (preg_match('/^\*([^ ]+)/',$n->data[$key],$matches)) {
+ $ref = substr($matches[0],1);
+ // Flag the node as having a reference
+ $this->_allNodes[$n->id]->refKey = $ref;
+ }
+ } elseif (!empty($k) && !empty($v)) {
+ 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] =
+ substr($v,strlen($matches[0])+1);
+ // Look for *refs
+ } elseif (preg_match('/^\*([^ ]+)/',$v,$matches)) {
+ $ref = substr($matches[0],1);
+ // Flag the node as having a reference
+ $this->_allNodes[$n->id]->refKey = $ref;
+ }
+ }
+ }
+
+ /**
+ * Finds the children of a node and aids in the building of the PHP array
+ * @access private
+ * @param int $nid The id of the node whose children we're gathering
+ * @return array
+ */
+ function _gatherChildren($nid) {
+ $return = array();
+ $node =& $this->_allNodes[$nid];
+ foreach ($this->_allNodes as $z) {
+ if ($z->parent == $node->id) {
+ // We found a child
+ $this->_nodeArrayizeData($z);
+ // Check for references
+ $this->_makeReferences($z);
+ // Merge with the big array we're returning
+ // The big array being all the data of the children of our parent node
+ $return = $this->_array_kmerge($return,$z->data);
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Turns a node's data and its children's data into a PHP array
+ *
+ * @access private
+ * @param array $node The node which you want to arrayize
+ * @return boolean
+ */
+ function _nodeArrayizeData(&$node) {
+ if (is_array($node->data) && $node->children == true) {
+ // This node has children, so we need to find them
+ $childs = $this->_gatherChildren($node->id);
+ // We've gathered all our children's data and are ready to use it
+ $key = key($node->data);
+ $key = empty($key) ? 0 : $key;
+ // If it's an array, add to it of course
+ if (is_array($node->data[$key])) {
+ $node->data[$key] = $this->_array_kmerge($node->data[$key],$childs);
+ } else {
+ $node->data[$key] = $childs;
+ }
+ } elseif (!is_array($node->data) && $node->children == true) {
+ // Same as above, find the children of this node
+ $childs = $this->_gatherChildren($node->id);
+ $node->data = array();
+ $node->data[] = $childs;
+ }
+
+ // We edited $node by reference, so just return true
+ return true;
+ }
+
+ /**
+ * Traverses node-space and copies references to / from this object.
+ * @access private
+ * @param object $z A node whose references we wish to make real
+ * @return bool
+ */
+ function _makeReferences(&$z) {
+ // It is a reference
+ if (isset($z->ref)) {
+ $key = key($z->data);
+ // Copy the data to this object for easy retrieval later
+ $this->ref[$z->ref] =& $z->data[$key];
+ // It has a reference
+ } elseif (isset($z->refKey)) {
+ if (isset($this->ref[$z->refKey])) {
+ $key = key($z->data);
+ // Copy the data from this object to make the node a real reference
+ $z->data[$key] =& $this->ref[$z->refKey];
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Merges arrays and maintains numeric keys.
+ *
+ * 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
+ *
+ * @access private
+ * @param array $arr1
+ * @param array $arr2
+ * @return 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();
+
+ 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.
+ if (isset($ret[$key]) and is_int($key)) {
+ while (array_key_exists($key, $ret)) {
+ $key++;
+ }
+ }
+ $ret[$key] = $val;
+ }
+
+ return $ret;
+ }
+ }
+?> \ No newline at end of file
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
new file mode 100644
index 00000000..33fb67fd
--- /dev/null
+++ b/includes/api/ApiHelp.php
@@ -0,0 +1,55 @@
+<?php
+
+
+/*
+ * Created on Sep 6, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+class ApiHelp extends ApiBase {
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action);
+ }
+
+ /**
+ * Stub module for displaying help when no parameters are given
+ */
+ public function execute() {
+ $this->dieUsage('', 'help');
+ }
+
+ protected function getDescription() {
+ return array (
+ 'Display this help screen.'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiHelp.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
new file mode 100644
index 00000000..2aa571c1
--- /dev/null
+++ b/includes/api/ApiLogin.php
@@ -0,0 +1,122 @@
+<?php
+
+
+/*
+ * Created on Sep 19, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+class ApiLogin extends ApiBase {
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action, 'lg');
+ }
+
+ public function execute() {
+ $name = $password = $domain = null;
+ extract($this->extractRequestParams());
+
+ $params = new FauxRequest(array (
+ 'wpName' => $name,
+ 'wpPassword' => $password,
+ 'wpDomain' => $domain,
+ 'wpRemember' => ''
+ ));
+
+ $result = array ();
+
+ $loginForm = new LoginForm($params);
+ switch ($loginForm->authenticateUserData()) {
+ case LoginForm :: SUCCESS :
+ global $wgUser;
+
+ $wgUser->setOption('rememberpassword', 1);
+ $wgUser->setCookies();
+
+ $result['result'] = 'Success';
+ $result['lguserid'] = $_SESSION['wsUserID'];
+ $result['lgusername'] = $_SESSION['wsUserName'];
+ $result['lgtoken'] = $_SESSION['wsToken'];
+ break;
+
+ case LoginForm :: NO_NAME :
+ $result['result'] = 'NoName';
+ break;
+ case LoginForm :: ILLEGAL :
+ $result['result'] = 'Illegal';
+ break;
+ case LoginForm :: WRONG_PLUGIN_PASS :
+ $result['result'] = 'WrongPluginPass';
+ break;
+ case LoginForm :: NOT_EXISTS :
+ $result['result'] = 'NotExists';
+ break;
+ case LoginForm :: WRONG_PASS :
+ $result['result'] = 'WrongPass';
+ break;
+ case LoginForm :: EMPTY_PASS :
+ $result['result'] = 'EmptyPass';
+ break;
+ default :
+ ApiBase :: dieDebug(__METHOD__, 'Unhandled case value');
+ }
+
+ $this->getResult()->addValue(null, 'login', $result);
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'name' => '',
+ 'password' => '',
+ 'domain' => null
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'name' => 'User Name',
+ 'password' => 'Password',
+ 'domain' => 'Domain (optional)'
+ );
+ }
+
+ protected function getDescription() {
+ return array (
+ 'This module is used to login and get the authentication tokens.'
+ );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'api.php?action=login&lgname=user&lgpassword=password'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiLogin.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?>
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
new file mode 100644
index 00000000..046d7d7c
--- /dev/null
+++ b/includes/api/ApiMain.php
@@ -0,0 +1,226 @@
+<?php
+
+
+/*
+ * Created on Sep 4, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+class ApiMain extends ApiBase {
+
+ private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames;
+ private $mApiStartTime, $mResult, $mShowVersions, $mEnableWrite;
+
+ /**
+ * Constructor
+ * $apiStartTime - time of the originating call for profiling purposes
+ * $modules - an array of actions (keys) and classes that handle them (values)
+ */
+ public function __construct($apiStartTime, $modules, $formats, $enableWrite) {
+ // Special handling for the main module: $parent === $this
+ parent :: __construct($this, 'main');
+
+ $this->mModules = $modules;
+ $this->mModuleNames = array_keys($modules);
+ $this->mFormats = $formats;
+ $this->mFormatNames = array_keys($formats);
+ $this->mApiStartTime = $apiStartTime;
+ $this->mResult = new ApiResult($this);
+ $this->mShowVersions = false;
+ $this->mEnableWrite = $enableWrite;
+ }
+
+ public function & getResult() {
+ return $this->mResult;
+ }
+
+ public function getShowVersions() {
+ return $this->mShowVersions;
+ }
+
+ public function requestWriteMode() {
+ 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', 'readonly');
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'format' => array (
+ ApiBase :: PARAM_DFLT => API_DEFAULT_FORMAT,
+ ApiBase :: PARAM_TYPE => $this->mFormatNames
+ ),
+ 'action' => array (
+ ApiBase :: PARAM_DFLT => 'help',
+ ApiBase :: PARAM_TYPE => $this->mModuleNames
+ ),
+ 'version' => false
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'format' => 'The format of the output',
+ 'action' => 'What action you would like to perform',
+ 'version' => 'When showing help, include version for each module'
+ );
+ }
+
+ public function execute() {
+ $this->profileIn();
+ $action = $format = $version = null;
+ try {
+ extract($this->extractRequestParams());
+ $this->mShowVersions = $version;
+
+ // Create an appropriate printer
+ $this->mPrinter = new $this->mFormats[$format] ($this, $format);
+
+ // Instantiate and execute module requested by the user
+ $module = new $this->mModules[$action] ($this, $action);
+ $module->profileIn();
+ $module->execute();
+ $module->profileOut();
+ $this->printResult(false);
+
+ } catch (UsageException $e) {
+
+ // Printer may not be initialized if the extractRequestParams() fails for the main module
+ if (!isset ($this->mPrinter))
+ $this->mPrinter = new $this->mFormats[API_DEFAULT_FORMAT] ($this, API_DEFAULT_FORMAT);
+ $this->printResult(true);
+
+ }
+ $this->profileOut();
+ }
+
+ /**
+ * Internal printer
+ */
+ private function printResult($isError) {
+ $printer = $this->mPrinter;
+ $printer->profileIn();
+ $printer->initPrinter($isError);
+ if (!$printer->getNeedsRawData())
+ $this->getResult()->SanitizeData();
+ $printer->execute();
+ $printer->closePrinter();
+ $printer->profileOut();
+ }
+
+ protected function getDescription() {
+ return array (
+ '',
+ 'This API allows programs to access various functions of MediaWiki software.',
+ 'For more details see API Home Page @ http://meta.wikimedia.org/wiki/API',
+ ''
+ );
+ }
+
+ public function mainDieUsage($description, $errorCode, $httpRespCode = 0) {
+ $this->mResult->Reset();
+ if ($httpRespCode === 0)
+ header($errorCode, true);
+ else
+ header($errorCode, true, $httpRespCode);
+
+ $data = array (
+ 'code' => $errorCode,
+ 'info' => $description
+ );
+ ApiResult :: setContent($data, $this->makeHelpMsg());
+ $this->mResult->addValue(null, 'error', $data);
+
+ throw new UsageException($description, $errorCode);
+ }
+
+ /**
+ * Override the parent to generate help messages for all available modules.
+ */
+ public function makeHelpMsg() {
+
+ // Use parent to make default message for the main module
+ $msg = parent :: makeHelpMsg();
+
+ $astriks = str_repeat('*** ', 10);
+ $msg .= "\n\n$astriks Modules $astriks\n\n";
+ foreach ($this->mModules as $moduleName => $moduleClass) {
+ $msg .= "* action=$moduleName *";
+ $module = new $this->mModules[$moduleName] ($this, $moduleName);
+ $msg2 = $module->makeHelpMsg();
+ if ($msg2 !== false)
+ $msg .= $msg2;
+ $msg .= "\n";
+ }
+
+ $msg .= "\n$astriks Formats $astriks\n\n";
+ foreach ($this->mFormats as $moduleName => $moduleClass) {
+ $msg .= "* format=$moduleName *";
+ $module = new $this->mFormats[$moduleName] ($this, $moduleName);
+ $msg2 = $module->makeHelpMsg();
+ if ($msg2 !== false)
+ $msg .= $msg2;
+ $msg .= "\n";
+ }
+
+ return $msg;
+ }
+
+ private $mIsBot = null;
+ public function isBot() {
+ if (!isset ($this->mIsBot)) {
+ global $wgUser;
+ $this->mIsBot = $wgUser->isAllowed('bot');
+ }
+ return $this->mIsBot;
+ }
+
+ public function getVersion() {
+ $vers = array ();
+ $vers[] = __CLASS__ . ': $Id: ApiMain.php 16820 2006-10-06 01:02:14Z yurik $';
+ $vers[] = ApiBase :: getBaseVersion();
+ $vers[] = ApiFormatBase :: getBaseVersion();
+ $vers[] = ApiQueryBase :: getBaseVersion();
+ return $vers;
+ }
+}
+
+/**
+* @desc This exception will be thrown when dieUsage is called to stop module execution.
+*/
+class UsageException extends Exception {
+
+ private $codestr;
+
+ public function __construct($message, $codestr) {
+ parent :: __construct($message);
+ $this->codestr = $codestr;
+ }
+ public function __toString() {
+ return "{$this->codestr}: {$this->message}";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
new file mode 100644
index 00000000..d2384b39
--- /dev/null
+++ b/includes/api/ApiPageSet.php
@@ -0,0 +1,514 @@
+<?php
+
+
+/*
+ * Created on Sep 24, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+class ApiPageSet extends ApiQueryBase {
+
+ private $mAllPages; // [ns][dbkey] => page_id or 0 when missing
+ private $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles, $mNormalizedTitles;
+ private $mResolveRedirects, $mPendingRedirectIDs;
+
+ private $mRequestedPageFields;
+
+ public function __construct($query, $resolveRedirects = false) {
+ parent :: __construct($query, __CLASS__);
+
+ $this->mAllPages = array ();
+ $this->mGoodTitles = array ();
+ $this->mMissingTitles = array ();
+ $this->mMissingPageIDs = array ();
+ $this->mRedirectTitles = array ();
+ $this->mNormalizedTitles = array ();
+
+ $this->mRequestedPageFields = array ();
+ $this->mResolveRedirects = $resolveRedirects;
+ if($resolveRedirects)
+ $this->mPendingRedirectIDs = array();
+ }
+
+ public function isResolvingRedirects() {
+ return $this->mResolveRedirects;
+ }
+
+ public function requestField($fieldName) {
+ $this->mRequestedPageFields[$fieldName] = null;
+ }
+
+ public function getCustomField($fieldName) {
+ return $this->mRequestedPageFields[$fieldName];
+ }
+
+ /**
+ * Get fields that modules have requested from the page table
+ */
+ public function getPageTableFields() {
+ // Ensure we get minimum required fields
+ $pageFlds = array (
+ 'page_id' => null,
+ 'page_namespace' => null,
+ 'page_title' => null
+ );
+
+ // only store non-default fields
+ $this->mRequestedPageFields = array_diff_key($this->mRequestedPageFields, $pageFlds);
+
+ if ($this->mResolveRedirects)
+ $pageFlds['page_is_redirect'] = null;
+
+ return array_keys(array_merge($pageFlds, $this->mRequestedPageFields));
+ }
+
+ /**
+ * Title objects that were found in the database.
+ * @return array page_id (int) => Title (obj)
+ */
+ public function getGoodTitles() {
+ return $this->mGoodTitles;
+ }
+
+ /**
+ * Returns the number of unique pages (not revisions) in the set.
+ */
+ public function getGoodTitleCount() {
+ return count($this->getGoodTitles());
+ }
+
+ /**
+ * Title objects that were NOT found in the database.
+ * @return array of Title objects
+ */
+ public function getMissingTitles() {
+ return $this->mMissingTitles;
+ }
+
+ /**
+ * Page IDs that were not found in the database
+ * @return array of page IDs
+ */
+ public function getMissingPageIDs() {
+ return $this->mMissingPageIDs;
+ }
+
+ /**
+ * Get a list of redirects when doing redirect resolution
+ * @return array prefixed_title (string) => prefixed_title (string)
+ */
+ public function getRedirectTitles() {
+ return $this->mRedirectTitles;
+ }
+
+ /**
+ * Get a list of title normalizations - maps the title given
+ * with its normalized version.
+ * @return array raw_prefixed_title (string) => prefixed_title (string)
+ */
+ public function getNormalizedTitles() {
+ return $this->mNormalizedTitles;
+ }
+
+ /**
+ * Get the list of revision IDs (requested with revids= parameter)
+ */
+ public function getRevisionIDs() {
+ $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented');
+ }
+
+ /**
+ * Returns the number of revisions (requested with revids= parameter)
+ */
+ public function getRevisionCount() {
+ return 0; // TODO: implement
+ }
+
+ /**
+ * Populate from the request parameters
+ */
+ public function execute() {
+ $this->profileIn();
+ $titles = $pageids = $revids = null;
+ extract($this->extractRequestParams());
+
+ // Only one of the titles/pageids/revids is allowed at the same time
+ $dataSource = null;
+ if (isset ($titles))
+ $dataSource = 'titles';
+ if (isset ($pageids)) {
+ if (isset ($dataSource))
+ $this->dieUsage("Cannot use 'pageids' at the same time as '$dataSource'", 'multisource');
+ $dataSource = 'pageids';
+ }
+ if (isset ($revids)) {
+ if (isset ($dataSource))
+ $this->dieUsage("Cannot use 'revids' at the same time as '$dataSource'", 'multisource');
+ $dataSource = 'revids';
+ }
+
+ switch ($dataSource) {
+ case 'titles' :
+ $this->initFromTitles($titles);
+ break;
+ case 'pageids' :
+ $this->initFromPageIds($pageids);
+ break;
+ case 'revids' :
+ $this->initFromRevIDs($revids);
+ break;
+ default :
+ // Do nothing - some queries do not need any of the data sources.
+ break;
+ }
+ $this->profileOut();
+ }
+
+ /**
+ * Initialize PageSet from a list of Titles
+ */
+ public function populateFromTitles($titles) {
+ $this->profileIn();
+ $this->initFromTitles($titles);
+ $this->profileOut();
+ }
+
+ /**
+ * Initialize PageSet from a list of Page IDs
+ */
+ public function populateFromPageIDs($pageIDs) {
+ $this->profileIn();
+ $pageIDs = array_map('intval', $pageIDs); // paranoia
+ $this->initFromPageIds($pageIDs);
+ $this->profileOut();
+ }
+
+ /**
+ * Initialize PageSet from a rowset returned from the database
+ */
+ public function populateFromQueryResult($db, $queryResult) {
+ $this->profileIn();
+ $this->initFromQueryResult($db, $queryResult);
+ $this->profileOut();
+ }
+
+ /**
+ * Extract all requested fields from the row received from the database
+ */
+ public function processDbRow($row) {
+ $pageId = intval($row->page_id);
+
+ // Store Title object in various data structures
+ $title = Title :: makeTitle($row->page_namespace, $row->page_title);
+ $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId;
+
+ if ($this->mResolveRedirects && $row->page_is_redirect == '1') {
+ $this->mPendingRedirectIDs[$pageId] = $title;
+ } else {
+ $this->mGoodTitles[$pageId] = $title;
+ }
+
+ 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
+ */
+ private function initFromTitles($titles) {
+ $db = $this->getDB();
+
+ // Get validated and normalized title objects
+ $linkBatch = $this->processTitlesStrArray($titles);
+ $set = $linkBatch->constructSet('page', $db);
+
+ // Get pageIDs data from the `page` table
+ $this->profileDBIn();
+ $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__);
+ $this->profileDBOut();
+
+ // Hack: get the ns:titles stored in array(ns => array(titles)) format
+ $this->initFromQueryResult($db, $res, $linkBatch->data, true); // process Titles
+
+ // Resolve any found redirects
+ $this->resolvePendingRedirects();
+ }
+
+ private function initFromPageIds($pageids) {
+ $db = $this->getDB();
+
+ $set = array (
+ 'page_id' => $pageids
+ );
+
+ // Get pageIDs data from the `page` table
+ $this->profileDBIn();
+ $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__);
+ $this->profileDBOut();
+
+ $this->initFromQueryResult($db, $res, array_flip($pageids), 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.
+ * @param $db Database
+ * @param $res DB Query result
+ * @param $remaining Array of either pageID or ns/title elements (optional).
+ * If given, any missing items will go to $mMissingPageIDs and $mMissingTitles
+ * @param $processTitles bool Must be provided together with $remaining.
+ * If true, treat $remaining as an array of [ns][title]
+ * If false, treat it as an array of [pageIDs]
+ * @return Array of redirect IDs (only when resolving redirects)
+ */
+ private function initFromQueryResult($db, $res, &$remaining = null, $processTitles = null) {
+ if (!is_null($remaining) && is_null($processTitles))
+ $this->dieDebug('Missing $processTitles parameter when $remaining is provided');
+
+ while ($row = $db->fetchObject($res)) {
+
+ $pageId = intval($row->page_id);
+
+ // Remove found page from the list of remaining items
+ if (isset($remaining)) {
+ if ($processTitles)
+ unset ($remaining[$row->page_namespace][$row->page_title]);
+ 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) {
+ // The remaining titles in $remaining are non-existant pages
+ foreach ($remaining as $ns => $dbkeys) {
+ foreach ($dbkeys as $dbkey => $nothing) {
+ $this->mMissingTitles[] = Title :: makeTitle($ns, $dbkey);
+ $this->mAllPages[$ns][$dbkey] = 0;
+ }
+ }
+ }
+ else
+ {
+ // The remaining pageids do not exist
+ if(empty($this->mMissingPageIDs))
+ $this->mMissingPageIDs = array_keys($remaining);
+ else
+ $this->mMissingPageIDs = array_merge($this->mMissingPageIDs, array_keys($remaining));
+ }
+ }
+ }
+
+ private function initFromRevIDs($revids) {
+ $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented');
+ }
+
+ private function resolvePendingRedirects() {
+
+ 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)) {
+
+ // 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);
+ }
+ }
+ }
+
+ private function getRedirectTargets() {
+
+ $linkBatch = 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__);
+ $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();
+
+ $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);
+ }
+ }
+
+ $this->mRedirectTitles[$titleStrFrom] = $titleStrTo;
+ }
+ $db->freeResult($res);
+
+ // All IDs must exist in the page table
+ if (!empty($this->mPendingRedirectIDs[$plfrom]))
+ $this->dieDebug('Invalid redirect IDs were found');
+
+ return $linkBatch;
+ }
+
+ /**
+ * Given an array of title strings, convert them into Title objects.
+ * This method validates access rights for the title,
+ * and appends normalization values to the output.
+ *
+ * @return LinkBatch of title objects.
+ */
+ private function processTitlesStrArray($titles) {
+
+ $linkBatch = new LinkBatch();
+
+ foreach ($titles as $titleString) {
+ $titleObj = Title :: newFromText($titleString);
+
+ // Validation
+ if (!$titleObj)
+ $this->dieUsage("bad title $titleString", 'invalidtitle');
+ if ($titleObj->getNamespace() < 0)
+ $this->dieUsage("No support for special page $titleString has been implemented", 'unsupportednamespace');
+ if (!$titleObj->userCanRead())
+ $this->dieUsage("No read permission for $titleString", 'titleaccessdenied');
+
+ $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
+ if ($titleString !== $titleObj->getPrefixedText()) {
+ $this->mNormalizedTitles[$titleString] = $titleObj->getPrefixedText();
+ }
+ }
+
+ return $linkBatch;
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'titles' => array (
+ ApiBase :: PARAM_ISMULTI => true
+ ),
+ 'pageids' => array (
+ ApiBase :: PARAM_TYPE => 'integer',
+ ApiBase :: PARAM_ISMULTI => true
+ ),
+ 'revids' => array (
+ ApiBase :: PARAM_TYPE => 'integer',
+ ApiBase :: PARAM_ISMULTI => true
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'titles' => 'A list of titles to work on',
+ 'pageids' => 'A list of page IDs to work on',
+ 'revids' => 'A list of revision IDs to work on'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiPageSet.php 16820 2006-10-06 01:02:14Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
new file mode 100644
index 00000000..985bde63
--- /dev/null
+++ b/includes/api/ApiQuery.php
@@ -0,0 +1,354 @@
+<?php
+
+
+/*
+ * Created on Sep 7, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+class ApiQuery extends ApiBase {
+
+ private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames;
+ private $mPageSet;
+
+ private $mQueryPropModules = array (
+ 'info' => 'ApiQueryInfo',
+ 'revisions' => 'ApiQueryRevisions'
+ );
+ // 'categories' => 'ApiQueryCategories',
+ // 'imageinfo' => 'ApiQueryImageinfo',
+ // 'langlinks' => 'ApiQueryLanglinks',
+ // 'links' => 'ApiQueryLinks',
+ // 'templates' => 'ApiQueryTemplates',
+
+ private $mQueryListModules = array (
+ 'allpages' => 'ApiQueryAllpages'
+ );
+ // 'backlinks' => 'ApiQueryBacklinks',
+ // 'categorymembers' => 'ApiQueryCategorymembers',
+ // 'embeddedin' => 'ApiQueryEmbeddedin',
+ // 'imagelinks' => 'ApiQueryImagelinks',
+ // 'logevents' => 'ApiQueryLogevents',
+ // 'recentchanges' => 'ApiQueryRecentchanges',
+ // 'usercontribs' => 'ApiQueryUsercontribs',
+ // 'users' => 'ApiQueryUsers',
+ // 'watchlist' => 'ApiQueryWatchlist',
+
+ private $mQueryMetaModules = array (
+ 'siteinfo' => 'ApiQuerySiteinfo'
+ );
+ // 'userinfo' => 'ApiQueryUserinfo',
+
+ private $mSlaveDB = null;
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action);
+ $this->mPropModuleNames = array_keys($this->mQueryPropModules);
+ $this->mListModuleNames = array_keys($this->mQueryListModules);
+ $this->mMetaModuleNames = array_keys($this->mQueryMetaModules);
+
+ // Allow the entire list of modules at first,
+ // but during module instantiation check if it can be used as a generator.
+ $this->mAllowedGenerators = array_merge($this->mListModuleNames, $this->mPropModuleNames);
+ }
+
+ public function getDB() {
+ if (!isset ($this->mSlaveDB))
+ $this->mSlaveDB = & wfGetDB(DB_SLAVE);
+ return $this->mSlaveDB;
+ }
+
+ public function getPageSet() {
+ return $this->mPageSet;
+ }
+
+ /**
+ * 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.
+ * This way the PageSet object will know what shared data is required,
+ * and minimize DB calls.
+ * #4 Output all normalization and redirect resolution information
+ * #5 Execute all requested modules
+ */
+ public function execute() {
+ $prop = $list = $meta = $generator = $redirects = null;
+ extract($this->extractRequestParams());
+
+ //
+ // Create PageSet
+ //
+ $this->mPageSet = new ApiPageSet($this, $redirects);
+
+ // Instantiate required modules
+ $modules = array ();
+ if (isset ($prop))
+ foreach ($prop as $moduleName)
+ $modules[] = new $this->mQueryPropModules[$moduleName] ($this, $moduleName);
+ if (isset ($list))
+ foreach ($list as $moduleName)
+ $modules[] = new $this->mQueryListModules[$moduleName] ($this, $moduleName);
+ if (isset ($meta))
+ foreach ($meta as $moduleName)
+ $modules[] = new $this->mQueryMetaModules[$moduleName] ($this, $moduleName);
+
+ // Modules may optimize data requests through the $this->getPageSet() object
+ // Execute all requested modules.
+ foreach ($modules as $module) {
+ $module->requestExtraData();
+ }
+
+ //
+ // If given, execute generator to substitute user supplied data with generated data.
+ //
+ if (isset ($generator))
+ $this->executeGeneratorModule($generator, $redirects);
+
+ //
+ // Populate page information for the given pageSet
+ //
+ $this->mPageSet->execute();
+
+ //
+ // Record page information (title, namespace, if exists, etc)
+ //
+ $this->outputGeneralPageInfo();
+
+ //
+ // Execute all requested modules.
+ //
+ foreach ($modules as $module) {
+ $module->profileIn();
+ $module->execute();
+ $module->profileOut();
+ }
+ }
+
+ private function outputGeneralPageInfo() {
+
+ $pageSet = $this->getPageSet();
+
+ // Title normalizations
+ $normValues = array ();
+ foreach ($pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr) {
+ $normValues[] = array (
+ 'from' => $rawTitleStr,
+ 'to' => $titleStr
+ );
+ }
+
+ if (!empty ($normValues)) {
+ ApiResult :: setIndexedTagName($normValues, 'n');
+ $this->getResult()->addValue('query', 'normalized', $normValues);
+ }
+
+ // Show redirect information
+ $redirValues = array ();
+ foreach ($pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo) {
+ $redirValues[] = array (
+ 'from' => $titleStrFrom,
+ 'to' => $titleStrTo
+ );
+ }
+
+ if (!empty ($redirValues)) {
+ ApiResult :: setIndexedTagName($redirValues, 'r');
+ $this->getResult()->addValue('query', 'redirects', $redirValues);
+ }
+
+ //
+ // Page elements
+ //
+ $pages = array ();
+
+ // Report any missing titles
+ $fakepageid = -1;
+ foreach ($pageSet->getMissingTitles() as $title) {
+ $pages[$fakepageid--] = array (
+ 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText(), 'missing' => '');
+ }
+
+ // Report any missing page ids
+ foreach ($pageSet->getMissingPageIDs() as $pageid) {
+ $pages[$pageid] = array (
+ 'id' => $pageid,
+ 'missing' => ''
+ );
+ }
+
+ // Output general page information for found titles
+ foreach ($pageSet->getGoodTitles() as $pageid => $title) {
+ $pages[$pageid] = array (
+ 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText(), 'id' => $pageid);
+ }
+
+ if (!empty ($pages)) {
+ ApiResult :: setIndexedTagName($pages, 'page');
+ $this->getResult()->addValue('query', 'pages', $pages);
+ }
+ }
+
+ protected function executeGeneratorModule($generatorName, $redirects) {
+
+ // Find class that implements requested generator
+ if (isset ($this->mQueryListModules[$generatorName])) {
+ $className = $this->mQueryListModules[$generatorName];
+ }
+ elseif (isset ($this->mQueryPropModules[$generatorName])) {
+ $className = $this->mQueryPropModules[$generatorName];
+ } else {
+ ApiBase :: dieDebug(__METHOD__, "Unknown generator=$generatorName");
+ }
+
+ // Use current pageset as the result, and create a new one just for the generator
+ $resultPageSet = $this->mPageSet;
+ $this->mPageSet = new ApiPageSet($this, $redirects);
+
+ // Create and execute the generator
+ $generator = new $className ($this, $generatorName);
+ if (!$generator instanceof ApiQueryGeneratorBase)
+ $this->dieUsage("Module $generatorName cannot be used as a generator", "badgenerator");
+
+ $generator->setGeneratorMode();
+ $generator->requestExtraData();
+
+ // execute current pageSet to get the data for the generator module
+ $this->mPageSet->execute();
+
+ // populate resultPageSet with the generator output
+ $generator->profileIn();
+ $generator->executeGenerator($resultPageSet);
+ $resultPageSet->finishPageSetGeneration();
+ $generator->profileOut();
+
+ // Swap the resulting pageset back in
+ $this->mPageSet = $resultPageSet;
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'prop' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => $this->mPropModuleNames
+ ),
+ 'list' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => $this->mListModuleNames
+ ),
+ 'meta' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => $this->mMetaModuleNames
+ ),
+ 'generator' => array (
+ ApiBase :: PARAM_TYPE => $this->mAllowedGenerators
+ ),
+ 'redirects' => false
+ );
+ }
+
+ /**
+ * Override the parent to generate help messages for all available query modules.
+ */
+ public function makeHelpMsg() {
+
+ // Use parent to make default message for the query module
+ $msg = parent :: makeHelpMsg();
+
+ // Make sure the internal object is empty
+ // (just in case a sub-module decides to optimize during instantiation)
+ $this->mPageSet = null;
+
+ $astriks = str_repeat('--- ', 8);
+ $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');
+
+ return $msg;
+ }
+
+ private function makeHelpMsgHelper($moduleList, $paramName) {
+
+ $moduleDscriptions = array ();
+
+ foreach ($moduleList as $moduleName => $moduleClass) {
+ $msg = "* $paramName=$moduleName *";
+ $module = new $moduleClass ($this, $moduleName, null);
+ $msg2 = $module->makeHelpMsg();
+ if ($msg2 !== false)
+ $msg .= $msg2;
+ if ($module instanceof ApiQueryGeneratorBase)
+ $msg .= "Generator:\n This module may be used as a generator\n";
+ $moduleDscriptions[] = $msg;
+ }
+
+ return implode("\n", $moduleDscriptions);
+ }
+
+ /**
+ * Override to add extra parameters from PageSet
+ */
+ public function makeHelpMsgParameters() {
+ $psModule = new ApiPageSet($this);
+ return $psModule->makeHelpMsgParameters() . parent :: makeHelpMsgParameters();
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'prop' => 'Which properties to get for the titles/revisions/pageids',
+ 'list' => 'Which lists to get',
+ 'meta' => 'Which meta data to get about the site',
+ 'generator' => 'Use the output of a list as the input for other prop/list/meta items',
+ 'redirects' => 'Automatically resolve redirects'
+ );
+ }
+
+ protected function getDescription() {
+ return array (
+ 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',
+ 'and is loosely based on the Query API interface currently available on all MediaWiki servers.',
+ 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.'
+ );
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment'
+ );
+ }
+
+ public function getVersion() {
+ $psModule = new ApiPageSet($this);
+ $vers = array ();
+ $vers[] = __CLASS__ . ': $Id: ApiQuery.php 16820 2006-10-06 01:02:14Z yurik $';
+ $vers[] = $psModule->getVersion();
+ return $vers;
+ }
+}
+?>
diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php
new file mode 100644
index 00000000..51330d62
--- /dev/null
+++ b/includes/api/ApiQueryAllpages.php
@@ -0,0 +1,183 @@
+<?php
+
+
+/*
+ * Created on Sep 25, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+class ApiQueryAllpages extends ApiQueryGeneratorBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'ap');
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator($resultPageSet) {
+ if ($resultPageSet->isResolvingRedirects())
+ $this->dieUsage('Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator', 'params');
+
+ $this->run($resultPageSet);
+ }
+
+ private function run($resultPageSet = null) {
+ $limit = $from = $namespace = $filterredir = null;
+ extract($this->extractRequestParams());
+
+ $db = $this->getDB();
+
+ $where = array (
+ 'page_namespace' => $namespace
+ );
+
+ if (isset ($from)) {
+ $where[] = 'page_title>=' . $db->addQuotes(ApiQueryBase :: titleToKey($from));
+ }
+
+ if ($filterredir === 'redirects') {
+ $where['page_is_redirect'] = 1;
+ }
+ elseif ($filterredir === 'nonredirects') {
+ $where['page_is_redirect'] = 0;
+ }
+
+ if (is_null($resultPageSet)) {
+ $fields = array (
+ 'page_id',
+ 'page_namespace',
+ 'page_title'
+ );
+ } else {
+ $fields = $resultPageSet->getPageTableFields();
+ }
+
+ $this->profileDBIn();
+ $res = $db->select('page', $fields, $where, __CLASS__ . '::' . __METHOD__, array (
+ 'USE INDEX' => 'name_title',
+ 'LIMIT' => $limit +1,
+ 'ORDER BY' => 'page_namespace, page_title'
+ ));
+ $this->profileDBOut();
+
+ $data = array ();
+ $count = 0;
+ 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...
+ $msg = array (
+ 'continue' => $this->encodeParamName('from'
+ ) . '=' . ApiQueryBase :: keyToTitle($row->page_title));
+ $this->getResult()->addValue('query-status', 'allpages', $msg);
+ break;
+ }
+
+ $title = Title :: makeTitle($row->page_namespace, $row->page_title);
+ // skip any pages that user has no rights to read
+ if ($title->userCanRead()) {
+
+ if (is_null($resultPageSet)) {
+ $id = intval($row->page_id);
+ $data[] = $id; // in generator mode, just assemble a list of page IDs.
+ } else {
+ $resultPageSet->processDbRow($row);
+ }
+ }
+ }
+ $db->freeResult($res);
+
+ if (is_null($resultPageSet)) {
+ ApiResult :: setIndexedTagName($data, 'p');
+ $this->getResult()->addValue('query', 'allpages', $data);
+ }
+ }
+
+ protected function getAllowedParams() {
+
+ global $wgContLang;
+ $validNamespaces = array ();
+ foreach (array_keys($wgContLang->getNamespaces()) as $ns) {
+ if ($ns >= 0)
+ $validNamespaces[] = $ns; // strval($ns);
+ }
+
+ return array (
+ 'from' => null,
+ 'namespace' => array (
+ ApiBase :: PARAM_DFLT => 0,
+ ApiBase :: PARAM_TYPE => $validNamespaces
+ ),
+ 'filterredir' => array (
+ ApiBase :: PARAM_DFLT => 'all',
+ ApiBase :: PARAM_TYPE => array (
+ 'all',
+ 'redirects',
+ 'nonredirects'
+ )
+ ),
+ 'limit' => array (
+ ApiBase :: PARAM_DFLT => 10,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 1,
+ ApiBase :: PARAM_MAX1 => 500,
+ ApiBase :: PARAM_MAX2 => 5000
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'from' => 'The page title to start enumerating from.',
+ 'namespace' => 'The namespace to enumerate. Default 0 (Main).',
+ 'filterredir' => 'Which pages to list: "all" (default), "redirects", or "nonredirects"',
+ 'limit' => 'How many total pages to return'
+ );
+ }
+
+ protected function getDescription() {
+ return 'Enumerate all pages sequentially in a given namespace';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'Simple Use',
+ ' api.php?action=query&list=allpages',
+ ' api.php?action=query&list=allpages&apfrom=B&aplimit=5',
+ 'Using as Generator',
+ ' Show info about 4 pages starting at the letter "T"',
+ ' api.php?action=query&generator=allpages&gaplimit=4&gapfrom=T&prop=info',
+ ' Show content of first 2 non-redirect pages begining at "Re"',
+ ' api.php?action=query&generator=allpages&gaplimit=2&gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryAllpages.php 16820 2006-10-06 01:02:14Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
new file mode 100644
index 00000000..574f742e
--- /dev/null
+++ b/includes/api/ApiQueryBase.php
@@ -0,0 +1,112 @@
+<?php
+
+
+/*
+ * Created on Sep 7, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+abstract class ApiQueryBase extends ApiBase {
+
+ private $mQueryModule;
+
+ public function __construct($query, $moduleName, $paramPrefix = '') {
+ parent :: __construct($query->getMain(), $moduleName, $paramPrefix);
+ $this->mQueryModule = $query;
+ }
+
+ /**
+ * Override this method to request extra fields from the pageSet
+ * using $this->getPageSet()->requestField('fieldName')
+ */
+ public function requestExtraData() {
+ }
+
+ /**
+ * Get the main Query module
+ */
+ public function getQuery() {
+ return $this->mQueryModule;
+ }
+
+ /**
+ * Get the Query database connection (readonly)
+ */
+ protected function getDB() {
+ return $this->getQuery()->getDB();
+ }
+
+ /**
+ * Get the PageSet object to work on
+ * @return ApiPageSet data
+ */
+ protected function getPageSet() {
+ return $this->mQueryModule->getPageSet();
+ }
+
+ public static function titleToKey($title) {
+ return str_replace(' ', '_', $title);
+ }
+
+ public static function keyToTitle($key) {
+ return str_replace('_', ' ', $key);
+ }
+
+ public static function getBaseVersion() {
+ return __CLASS__ . ': $Id: ApiQueryBase.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+
+abstract class ApiQueryGeneratorBase extends ApiQueryBase {
+
+ private $mIsGenerator;
+
+ public function __construct($query, $moduleName, $paramPrefix = '') {
+ parent :: __construct($query, $moduleName, $paramPrefix);
+ $mIsGenerator = false;
+ }
+
+ public function setGeneratorMode() {
+ $this->mIsGenerator = true;
+ }
+
+ /**
+ * Overrides base class to prepend 'g' to every generator parameter
+ */
+ public function encodeParamName($paramName) {
+ if ($this->mIsGenerator)
+ return 'g' . parent :: encodeParamName($paramName);
+ else
+ return parent :: encodeParamName($paramName);
+ }
+
+ /**
+ * Execute this module as a generator
+ * @param $resultPageSet PageSet: All output should be appended to this object
+ */
+ public abstract function executeGenerator($resultPageSet);
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
new file mode 100644
index 00000000..de651b00
--- /dev/null
+++ b/includes/api/ApiQueryInfo.php
@@ -0,0 +1,82 @@
+<?php
+
+
+/*
+ * Created on Sep 25, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+class ApiQueryInfo extends ApiQueryBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName);
+ }
+
+ public function requestExtraData() {
+ $pageSet = $this->getPageSet();
+ $pageSet->requestField('page_is_redirect');
+ $pageSet->requestField('page_touched');
+ $pageSet->requestField('page_latest');
+ }
+
+ public function execute() {
+
+ $pageSet = $this->getPageSet();
+ $titles = $pageSet->getGoodTitles();
+ $result = & $this->getResult();
+
+ $pageIsRedir = $pageSet->getCustomField('page_is_redirect');
+ $pageTouched = $pageSet->getCustomField('page_touched');
+ $pageLatest = $pageSet->getCustomField('page_latest');
+
+ foreach ($titles as $pageid => $title) {
+ $pageInfo = array ('touched' => $pageTouched[$pageid], 'lastrevid' => $pageLatest[$pageid]);
+
+ if ($pageIsRedir[$pageid])
+ $pageInfo['redirect'] = '';
+
+ $result->addValue(array (
+ 'query',
+ 'pages'
+ ), $pageid, $pageInfo);
+ }
+ }
+
+ protected function getDescription() {
+ return 'Get basic page information such as namespace, title, last touched date, ...';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=query&prop=info&titles=Main%20Page'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryInfo.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
new file mode 100644
index 00000000..f6097bad
--- /dev/null
+++ b/includes/api/ApiQueryRevisions.php
@@ -0,0 +1,320 @@
+<?php
+
+
+/*
+ * Created on Sep 7, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+class ApiQueryRevisions extends ApiQueryBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'rv');
+ }
+
+ public function execute() {
+ $limit = $startid = $endid = $start = $end = $dir = $prop = null;
+ extract($this->extractRequestParams());
+
+ $db = $this->getDB();
+
+ // true when ordered by timestamp from older to newer, false otherwise
+ $dirNewer = ($dir === 'newer');
+
+ // 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 extremelly
+ // difficult to manage continuations and require additional sql indexes
+ $enumRevMode = ($limit !== 0 || $startid !== 0 || $endid !== 0 || $dirNewer || isset ($start) || isset ($end));
+
+ $pageSet = $this->getPageSet();
+ $pageCount = $pageSet->getGoodTitleCount();
+ $revCount = $pageSet->getRevisionCount();
+
+ // Optimization -- nothing to do
+ if ($revCount === 0 && $pageCount === 0)
+ return;
+
+ if ($revCount > 0 && $pageCount > 0)
+ $this->dieUsage('The revids= parameter may not be used with titles, pageids, or generator options.', 'revids');
+
+ if ($revCount > 0 && $enumRevMode)
+ $this->dieUsage('The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).', 'revids');
+
+ if ($revCount === 0 && $pageCount > 1 && $enumRevMode)
+ $this->dieUsage('titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, start, and end parameters may only be used on a single page.', 'multpages');
+
+ $tables = array (
+ 'revision'
+ );
+ $fields = array (
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_minor_edit'
+ );
+ $conds = array (
+ 'rev_deleted' => 0
+ );
+ $options = array ();
+
+ $showTimestamp = $showUser = $showComment = $showContent = false;
+ if (isset ($prop)) {
+ foreach ($prop as $p) {
+ switch ($p) {
+ case 'timestamp' :
+ $fields[] = 'rev_timestamp';
+ $showTimestamp = true;
+ break;
+ case 'user' :
+ $fields[] = 'rev_user';
+ $fields[] = 'rev_user_text';
+ $showUser = true;
+ break;
+ case 'comment' :
+ $fields[] = 'rev_comment';
+ $showComment = true;
+ break;
+ case 'content' :
+ $tables[] = 'text';
+ $conds[] = 'rev_text_id=old_id';
+ $fields[] = 'old_id';
+ $fields[] = 'old_text';
+ $fields[] = 'old_flags';
+ $showContent = true;
+ break;
+ default :
+ ApiBase :: dieDebug(__METHOD__, "unknown prop $p");
+ }
+ }
+ }
+
+ $userMax = ($showContent ? 50 : 500);
+ $botMax = ($showContent ? 200 : 10000);
+
+ if ($enumRevMode) {
+
+ // This is mostly to prevent parameter errors (and optimize sql?)
+ if ($startid !== 0 && isset ($start))
+ $this->dieUsage('start and startid cannot be used together', 'badparams');
+
+ if ($endid !== 0 && isset ($end))
+ $this->dieUsage('end and endid 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.
+ // The order needs to be the same as start parameter to avoid SQL filesort.
+ $options['ORDER BY'] = ($startid !== 0 ? 'rev_id' : 'rev_timestamp') . ($dirNewer ? '' : ' DESC');
+
+ $before = ($dirNewer ? '<=' : '>=');
+ $after = ($dirNewer ? '>=' : '<=');
+
+ if ($startid !== 0)
+ $conds[] = 'rev_id' . $after . intval($startid);
+ if ($endid !== 0)
+ $conds[] = 'rev_id' . $before . intval($endid);
+ if (isset ($start))
+ $conds[] = 'rev_timestamp' . $after . $db->addQuotes($start);
+ if (isset ($end))
+ $conds[] = 'rev_timestamp' . $before . $db->addQuotes($end);
+
+ // must manually initialize unset limit
+ if (!isset ($limit))
+ $limit = 10;
+
+ $this->validateLimit($this->encodeParamName('limit'), $limit, 1, $userMax, $botMax);
+
+ // There is only one ID, use it
+ $conds['rev_page'] = array_pop(array_keys($pageSet->getGoodTitles()));
+
+ }
+ elseif ($pageCount > 0) {
+ // When working in multi-page non-enumeration mode,
+ // limit to the latest revision only
+ $tables[] = 'page';
+ $conds[] = 'page_id=rev_page';
+ $conds[] = 'page_latest=rev_id';
+ $this->validateLimit('page_count', $pageCount, 1, $userMax, $botMax);
+
+ // Get all page IDs
+ $conds['page_id'] = array_keys($pageSet->getGoodTitles());
+
+ $limit = $pageCount; // assumption testing -- we should never get more then $pageCount rows.
+ }
+ elseif ($revCount > 0) {
+ $this->validateLimit('rev_count', $revCount, 1, $userMax, $botMax);
+
+ // Get all revision IDs
+ $conds['rev_id'] = array_keys($pageSet->getRevisionIDs());
+
+ $limit = $revCount; // assumption testing -- we should never get more then $revCount rows.
+ } else
+ ApiBase :: dieDebug(__METHOD__, 'param validation?');
+
+ $options['LIMIT'] = $limit +1;
+
+ $this->profileDBIn();
+ $res = $db->select($tables, $fields, $conds, __METHOD__, $options);
+ $this->profileDBOut();
+
+ $data = array ();
+ $count = 0;
+ 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...
+ if (!$enumRevMode)
+ ApiBase :: dieDebug(__METHOD__, 'Got more rows then expected'); // bug report
+
+ $startStr = 'startid=' . $row->rev_id;
+ $msg = array (
+ 'continue' => $startStr
+ );
+ $this->getResult()->addValue('query-status', 'revisions', $msg);
+ break;
+ }
+
+ $vals = array (
+ 'revid' => intval($row->rev_id
+ ), 'oldid' => intval($row->rev_text_id));
+
+ if ($row->rev_minor_edit) {
+ $vals['minor'] = '';
+ }
+
+ if ($showTimestamp)
+ $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rev_timestamp);
+
+ if ($showUser) {
+ $vals['user'] = $row->rev_user_text;
+ if (!$row->rev_user)
+ $vals['anon'] = '';
+ }
+
+ if ($showComment)
+ $vals['comment'] = $row->rev_comment;
+
+ if ($showContent) {
+ ApiResult :: setContent($vals, Revision :: getRevisionText($row));
+ }
+
+ $this->getResult()->addValue(array (
+ 'query',
+ 'pages',
+ intval($row->rev_page
+ ), 'revisions'), intval($row->rev_id), $vals);
+ }
+ $db->freeResult($res);
+
+ // Ensure that all revisions are shown as '<r>' elements
+ $data = & $this->getResultData();
+ foreach ($data['query']['pages'] as & $page) {
+ if (is_array($page) && array_key_exists('revisions', $page)) {
+ ApiResult :: setIndexedTagName($page['revisions'], 'rev');
+ }
+ }
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'prop' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => array (
+ 'timestamp',
+ 'user',
+ 'comment',
+ 'content'
+ )
+ ),
+ 'limit' => array (
+ ApiBase :: PARAM_DFLT => 0,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 0,
+ ApiBase :: PARAM_MAX1 => 50,
+ ApiBase :: PARAM_MAX2 => 500
+ ),
+ 'startid' => 0,
+ 'endid' => 0,
+ 'start' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'end' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'dir' => array (
+ ApiBase :: PARAM_DFLT => 'older',
+ ApiBase :: PARAM_TYPE => array (
+ 'newer',
+ 'older'
+ )
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'prop' => 'Which properties to get for each revision: user|timestamp|comment|content',
+ 'limit' => 'limit how many revisions will be returned (enum)',
+ 'startid' => 'from which revision id to start enumeration (enum)',
+ 'endid' => 'stop revision enumeration on this revid (enum)',
+ 'start' => 'from which revision timestamp to start enumeration (enum)',
+ 'end' => 'enumerate up to this timestamp (enum)',
+ 'dir' => 'direction of enumeration - towards "newer" or "older" revisions (enum)'
+ );
+ }
+
+ protected function getDescription() {
+ return array (
+ 'Get revision information.',
+ 'This module may be used in several ways:',
+ ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter.',
+ ' 2) Get revisions for one given page, by using titles/pageids with start/end/limit params.',
+ ' 3) Get data about a set of revisions by setting their IDs with revids parameter.',
+ 'All parameters marked as (enum) may only be used with a single page (#2).'
+ );
+ }
+
+ protected function getExamples() {
+ return array (
+ 'Get data with content for the last revision of titles "API" and "Main Page":',
+ ' api.php?action=query&prop=revisions&titles=API|Main%20Page&rvprop=timestamp|user|comment|content',
+ 'Get last 5 revisions of the "Main Page":',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment',
+ 'Get first 5 revisions of the "Main Page":',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer',
+ 'Get first 5 revisions of the "Main Page" made after 2006-05-01:',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer&rvstart=20060501000000'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryRevisions.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
new file mode 100644
index 00000000..27c3f187
--- /dev/null
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -0,0 +1,113 @@
+<?php
+
+
+/*
+ * Created on Sep 25, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+class ApiQuerySiteinfo extends ApiQueryBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'si');
+ }
+
+ public function execute() {
+ $prop = null;
+ extract($this->extractRequestParams());
+
+ foreach ($prop as $p) {
+ switch ($p) {
+
+ case 'general' :
+
+ global $wgSitename, $wgVersion, $wgCapitalLinks;
+ $data = array ();
+ $mainPage = Title :: newFromText(wfMsgForContent('mainpage'));
+ $data['mainpage'] = $mainPage->getText();
+ $data['base'] = $mainPage->getFullUrl();
+ $data['sitename'] = $wgSitename;
+ $data['generator'] = "MediaWiki $wgVersion";
+ $data['case'] = $wgCapitalLinks ? 'first-letter' : 'case-sensitive'; // 'case-insensitive' option is reserved for future
+ $this->getResult()->addValue('query', $p, $data);
+ break;
+
+ case 'namespaces' :
+
+ global $wgContLang;
+ $data = array ();
+ foreach ($wgContLang->getFormattedNamespaces() as $ns => $title) {
+ $data[$ns] = array (
+ 'id' => $ns
+ );
+ ApiResult :: setContent($data[$ns], $title);
+ }
+ ApiResult :: setIndexedTagName($data, 'ns');
+ $this->getResult()->addValue('query', $p, $data);
+ break;
+
+ default :
+ ApiBase :: dieDebug(__METHOD__, "Unknown prop=$p");
+ }
+ }
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'prop' => array (
+ ApiBase :: PARAM_DFLT => 'general',
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => array (
+ 'general',
+ 'namespaces'
+ )
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'prop' => array (
+ 'Which sysinfo properties to get:',
+ ' "general" - Overall system information',
+ ' "namespaces" - List of registered namespaces (localized)'
+ )
+ );
+ }
+
+ protected function getDescription() {
+ return 'Return general information about the site.';
+ }
+
+ protected function getExamples() {
+ return 'api.php?action=query&meta=siteinfo&siprop=general|namespaces';
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
new file mode 100644
index 00000000..67fbf41e
--- /dev/null
+++ b/includes/api/ApiResult.php
@@ -0,0 +1,153 @@
+<?php
+
+
+/*
+ * Created on Sep 4, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@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');
+}
+
+class ApiResult extends ApiBase {
+
+ private $mData;
+
+ /**
+ * Constructor
+ */
+ public function __construct($main) {
+ parent :: __construct($main, 'result');
+ $this->Reset();
+ }
+
+ public function Reset() {
+ $this->mData = array ();
+ }
+
+ function & getData() {
+ return $this->mData;
+ }
+
+ /**
+ * Add an output value to the array by name.
+ * Verifies that value with the same name has not been added before.
+ */
+ public static function setElement(& $arr, $name, $value) {
+ if ($arr === null || $name === null || $value === null || !is_array($arr) || is_array($name))
+ ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
+
+ if (!isset ($arr[$name])) {
+ $arr[$name] = $value;
+ }
+ elseif (is_array($arr[$name]) && is_array($value)) {
+ $merged = array_intersect_key($arr[$name], $value);
+ if (empty ($merged))
+ $arr[$name] += $value;
+ else
+ ApiBase :: dieDebug(__METHOD__, "Attempting to merge element $name");
+ } else
+ ApiBase :: dieDebug(__METHOD__, "Attempting to add element $name=$value, existing value is {$arr[$name]}");
+ }
+
+ /**
+ * Adds the content element to the array.
+ * Use this function instead of hardcoding the '*' element.
+ */
+ public static function setContent(& $arr, $value) {
+ if (is_array($value))
+ ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
+ ApiResult :: setElement($arr, '*', $value);
+ }
+
+ // public static function makeContentElement($tag, $value) {
+ // $result = array();
+ // ApiResult::setContent($result, )
+ // }
+ //
+ /**
+ * In case the array contains indexed values (in addition to named),
+ * all indexed values will have the given tag name.
+ */
+ public static function setIndexedTagName(& $arr, $tag) {
+ // Do not use setElement() as it is ok to call this more than once
+ if ($arr === null || $tag === null || !is_array($arr) || is_array($tag))
+ ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
+ $arr['_element'] = $tag;
+ }
+
+ /**
+ * 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
+ */
+ public function addValue($path, $name, $value) {
+
+ $data = & $this->getData();
+
+ if (isset ($path)) {
+ if (is_array($path)) {
+ foreach ($path as $p) {
+ if (!isset ($data[$p]))
+ $data[$p] = array ();
+ $data = & $data[$p];
+ }
+ } else {
+ if (!isset ($data[$path]))
+ $data[$path] = array ();
+ $data = & $data[$path];
+ }
+ }
+
+ ApiResult :: setElement($data, $name, $value);
+ }
+
+ /**
+ * Recursivelly removes any elements from the array that begin with an '_'.
+ * The content element '*' is the only special element that is left.
+ * Use this method when the entire data object gets sent to the user.
+ */
+ public function SanitizeData() {
+ ApiResult :: SanitizeDataInt($this->mData);
+ }
+
+ private static function SanitizeDataInt(& $data) {
+ foreach ($data as $key => & $value) {
+ if ($key[0] === '_') {
+ unset ($data[$key]);
+ }
+ elseif (is_array($value)) {
+ ApiResult :: SanitizeDataInt($value);
+ }
+ }
+ }
+
+ public function execute() {
+ ApiBase :: dieDebug(__METHOD__, 'execute() is not supported on Result object');
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiResult.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/memcached-client.php b/includes/memcached-client.php
index 697509e8..b1ba778a 100644
--- a/includes/memcached-client.php
+++ b/includes/memcached-client.php
@@ -221,6 +221,16 @@ class memcached
*/
var $_timeout_microseconds;
+ /**
+ * Connect timeout in seconds
+ */
+ var $_connect_timeout;
+
+ /**
+ * Number of connection attempts for each server
+ */
+ var $_connect_attempts;
+
// }}}
// }}}
// {{{ methods
@@ -250,6 +260,9 @@ class memcached
$this->_timeout_seconds = 1;
$this->_timeout_microseconds = 0;
+
+ $this->_connect_timeout = 0.01;
+ $this->_connect_attempts = 3;
}
// }}}
@@ -675,22 +688,33 @@ class memcached
*
* @param interger $sock Socket to connect
* @param string $host Host:IP to connect to
- * @param float $timeout (optional) Timeout value, defaults to 0.25s
*
* @return boolean
* @access private
*/
- function _connect_sock (&$sock, $host, $timeout = 0.25)
+ function _connect_sock (&$sock, $host)
{
list ($ip, $port) = explode(":", $host);
- if ($this->_persistant == 1)
- {
- $sock = @pfsockopen($ip, $port, $errno, $errstr, $timeout);
- } else
- {
- $sock = @fsockopen($ip, $port, $errno, $errstr, $timeout);
+ $sock = false;
+ $timeout = $this->_connect_timeout;
+ for ($i = 0; !$sock && $i < $this->_connect_attempts; $i++) {
+ if ($i > 0) {
+ # Sleep until the timeout, in case it failed fast
+ $elapsed = microtime(true) - $t;
+ if ( $elapsed < $timeout ) {
+ usleep(($timeout - $elapsed) * 1e6);
+ }
+ $timeout *= 2;
+ }
+ $t = microtime(true);
+ if ($this->_persistant == 1)
+ {
+ $sock = @pfsockopen($ip, $port, $errno, $errstr, $timeout);
+ } else
+ {
+ $sock = @fsockopen($ip, $port, $errno, $errstr, $timeout);
+ }
}
-
if (!$sock) {
if ($this->_debug)
$this->_debugprint( "Error connecting to $host: $errstr\n" );
diff --git a/includes/normal/CleanUpTest.php b/includes/normal/CleanUpTest.php
index 4e147cfd..30ec6a95 100644
--- a/includes/normal/CleanUpTest.php
+++ b/includes/normal/CleanUpTest.php
@@ -412,7 +412,7 @@ class CleanUpTest extends PHPUnit_TestCase {
}
-$suite =& new PHPUnit_TestSuite( 'CleanUpTest' );
+$suite = new PHPUnit_TestSuite( 'CleanUpTest' );
$result = PHPUnit::run( $suite );
echo $result->toString();
diff --git a/includes/normal/RandomTest.php b/includes/normal/RandomTest.php
index 3a5a407b..e2601366 100644
--- a/includes/normal/RandomTest.php
+++ b/includes/normal/RandomTest.php
@@ -65,8 +65,8 @@ function showDiffs( $a, $b ) {
$ota = explode( "\n", str_replace( "\r\n", "\n", $a ) );
$nta = explode( "\n", str_replace( "\r\n", "\n", $b ) );
- $diffs =& new Diff( $ota, $nta );
- $formatter =& new TableDiffFormatter();
+ $diffs = new Diff( $ota, $nta );
+ $formatter = new TableDiffFormatter();
$funky = $formatter->format( $diffs );
preg_match_all( '/<span class="diffchange">(.*?)<\/span>/', $funky, $matches );
foreach( $matches[1] as $bit ) {
@@ -104,4 +104,4 @@ while( true ) {
$norm = '';
}
-?> \ No newline at end of file
+?>
diff --git a/includes/normal/UtfNormal.php b/includes/normal/UtfNormal.php
index d8641993..af3809d5 100644
--- a/includes/normal/UtfNormal.php
+++ b/includes/normal/UtfNormal.php
@@ -33,7 +33,7 @@
*/
/** */
-require_once 'UtfNormalUtil.php';
+require_once dirname(__FILE__).'/UtfNormalUtil.php';
global $utfCombiningClass, $utfCanonicalComp, $utfCanonicalDecomp;
$utfCombiningClass = NULL;
diff --git a/includes/templates/NoLocalSettings.php b/includes/templates/NoLocalSettings.php
new file mode 100644
index 00000000..e71dd396
--- /dev/null
+++ b/includes/templates/NoLocalSettings.php
@@ -0,0 +1,48 @@
+<?php
+# Prevent XSS
+if ( isset( $wgVersion ) ) {
+ $wgVersion = htmlspecialchars( $wgVersion );
+} else {
+ $wgVersion = 'VERSION';
+}
+# Set the path in case we hit a page such as /index.php/Main_Page
+# Could use <base href> but then we have to worry about http[s]/port #/etc.
+$path = '';
+if( isset( $_SERVER['SCRIPT_NAME'] )) {
+ $path = htmlspecialchars( preg_replace('/index.php/', '', $_SERVER['SCRIPT_NAME']) );
+}
+?>
+<!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'>
+ <head>
+ <title>MediaWiki <?php echo $wgVersion ?></title>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
+ <style type='text/css' media='screen, projection'>
+ html, body {
+ color: #000;
+ background-color: #fff;
+ font-family: sans-serif;
+ text-align: center;
+ }
+
+ h1 {
+ font-size: 150%;
+ }
+ </style>
+ </head>
+ <body>
+ <img src="<?php echo $path ?>skins/common/images/mediawiki.png" alt='The MediaWiki logo' />
+
+ <h1>MediaWiki <?php echo $wgVersion ?></h1>
+ <div class='error'>
+ <?php
+ if ( file_exists( 'config/LocalSettings.php' ) ) {
+ echo( 'To complete the installation, move <tt>config/LocalSettings.php</tt> to the parent directory.' );
+ } else {
+ echo( "Please <a href=\"${path}config/index.php\" title='setup'> set up the wiki</a> first." );
+ }
+ ?>
+
+ </div>
+ </body>
+</html>
diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php
index 66368669..83ef4920 100644
--- a/includes/templates/Userlogin.php
+++ b/includes/templates/Userlogin.php
@@ -37,6 +37,7 @@ class UserloginTemplate extends QuickTemplate {
<td align='right'><label for='wpName1'><?php $this->msg('yourname') ?>:</label></td>
<td align='left'>
<input type='text' class='loginText' name="wpName" id="wpName1"
+ tabindex="1"
value="<?php $this->text('name') ?>" size='20' />
</td>
</tr>
@@ -44,6 +45,7 @@ class UserloginTemplate extends QuickTemplate {
<td align='right'><label for='wpPassword1'><?php $this->msg('yourpassword') ?>:</label></td>
<td align='left'>
<input type='password' class='loginPassword' name="wpPassword" id="wpPassword1"
+ tabindex="2"
value="<?php $this->text('password') ?>" size='20' />
</td>
</tr>
@@ -56,7 +58,8 @@ class UserloginTemplate extends QuickTemplate {
<tr>
<td align='right'><?php $this->msg( 'yourdomainname' ) ?>:</td>
<td align='left'>
- <select name="wpDomain" value="<?php $this->text( 'domain' ) ?>">
+ <select name="wpDomain" value="<?php $this->text( 'domain' ) ?>"
+ tabindex="3">
<?php echo $doms ?>
</select>
</td>
@@ -66,6 +69,7 @@ class UserloginTemplate extends QuickTemplate {
<td></td>
<td align='left'>
<input type='checkbox' name="wpRemember"
+ tabindex="4"
value="1" id="wpRemember"
<?php if( $this->data['remember'] ) { ?>checked="checked"<?php } ?>
/> <label for="wpRemember"><?php $this->msg('remembermypassword') ?></label>
@@ -74,7 +78,8 @@ class UserloginTemplate extends QuickTemplate {
<tr>
<td></td>
<td align='left' style="white-space:nowrap">
- <input type='submit' name="wpLoginattempt" id="wpLoginattempt" value="<?php $this->msg('login') ?>" />&nbsp;<?php if( $this->data['useemail'] ) { ?><input type='submit' name="wpMailmypassword" id="wpMailmypassword"
+ <input type='submit' name="wpLoginattempt" id="wpLoginattempt" tabindex="5" value="<?php $this->msg('login') ?>" />&nbsp;<?php if( $this->data['useemail'] ) { ?><input type='submit' name="wpMailmypassword" id="wpMailmypassword"
+ tabindex="6"
value="<?php $this->msg('mailmypassword') ?>" />
<?php } ?>
</td>
@@ -113,6 +118,7 @@ class UsercreateTemplate extends QuickTemplate {
<td align='right'><label for='wpName2'><?php $this->msg('yourname') ?>:</label></td>
<td align='left'>
<input type='text' class='loginText' name="wpName" id="wpName2"
+ tabindex="1"
value="<?php $this->text('name') ?>" size='20' />
</td>
</tr>
@@ -120,6 +126,7 @@ class UsercreateTemplate extends QuickTemplate {
<td align='right'><label for='wpPassword2'><?php $this->msg('yourpassword') ?>:</label></td>
<td align='left'>
<input type='password' class='loginPassword' name="wpPassword" id="wpPassword2"
+ tabindex="2"
value="<?php $this->text('password') ?>" size='20' />
</td>
</tr>
@@ -132,7 +139,8 @@ class UsercreateTemplate extends QuickTemplate {
<tr>
<td align='right'><?php $this->msg( 'yourdomainname' ) ?>:</td>
<td align='left'>
- <select name="wpDomain" value="<?php $this->text( 'domain' ) ?>">
+ <select name="wpDomain" value="<?php $this->text( 'domain' ) ?>"
+ tabindex="3">
<?php echo $doms ?>
</select>
</td>
@@ -142,24 +150,27 @@ class UsercreateTemplate extends QuickTemplate {
<td align='right'><label for='wpRetype'><?php $this->msg('yourpasswordagain') ?>:</label></td>
<td align='left'>
<input type='password' class='loginPassword' name="wpRetype" id="wpRetype"
+ tabindex="4"
value="<?php $this->text('retype') ?>"
size='20' />
</td>
</tr>
<tr>
<?php if( $this->data['useemail'] ) { ?>
- <td align='right'><label for='wpEmail'><?php $this->msg('youremail') ?>:</label></td>
+ <td align='right'><label for='wpEmail'><?php $this->msg('youremail') ?></label></td>
<td align='left'>
<input type='text' class='loginText' name="wpEmail" id="wpEmail"
+ tabindex="5"
value="<?php $this->text('email') ?>" size='20' />
</td>
<?php } ?>
<?php if( $this->data['userealname'] ) { ?>
</tr>
<tr>
- <td align='right'><label for='wpRealName'><?php $this->msg('yourrealname') ?>:</label></td>
+ <td align='right'><label for='wpRealName'><?php $this->msg('yourrealname') ?></label></td>
<td align='left'>
<input type='text' class='loginText' name="wpRealName" id="wpRealName"
+ tabindex="6"
value="<?php $this->text('realname') ?>" size='20' />
</td>
<?php } ?>
@@ -168,6 +179,7 @@ class UsercreateTemplate extends QuickTemplate {
<td></td>
<td align='left'>
<input type='checkbox' name="wpRemember"
+ tabindex="7"
value="1" id="wpRemember"
<?php if( $this->data['remember'] ) { ?>checked="checked"<?php } ?>
/> <label for="wpRemember"><?php $this->msg('remembermypassword') ?></label>
@@ -177,9 +189,11 @@ class UsercreateTemplate extends QuickTemplate {
<td></td>
<td align='left'>
<input type='submit' name="wpCreateaccount" id="wpCreateaccount"
+ tabindex="8"
value="<?php $this->msg('createaccount') ?>" />
<?php if( $this->data['createemail'] ) { ?>
<input type='submit' name="wpCreateaccountMail" id="wpCreateaccountMail"
+ tabindex="9"
value="<?php $this->msg('createaccountmail') ?>" />
<?php } ?>
</td>