summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2007-01-11 19:06:07 +0000
committerPierre Schmitz <pierre@archlinux.de>2007-01-11 19:06:07 +0000
commita58285fd06c8113c45377c655dd43cef6337e815 (patch)
treedfe31d3d12652352fe44890b4811eda0728faefb /includes
parent20194986f6638233732ba1fc3e838f117d3cc9ea (diff)
Aktualisierung auf MediaWiki 1.9.0
Diffstat (limited to 'includes')
-rw-r--r--includes/AjaxDispatcher.php10
-rw-r--r--includes/AjaxFunctions.php39
-rw-r--r--includes/AjaxResponse.php4
-rw-r--r--includes/Article.php329
-rw-r--r--includes/AuthPlugin.php7
-rw-r--r--includes/AutoLoader.php53
-rw-r--r--includes/BagOStuff.php23
-rw-r--r--includes/Block.php168
-rw-r--r--includes/CacheDependency.php328
-rw-r--r--includes/CategoryPage.php11
-rw-r--r--includes/ChangesList.php81
-rw-r--r--includes/CoreParserFunctions.php63
-rw-r--r--includes/Database.php80
-rw-r--r--includes/DatabaseFunctions.php15
-rw-r--r--includes/DatabaseOracle.php24
-rw-r--r--includes/DatabasePostgres.php109
-rw-r--r--includes/DateFormatter.php4
-rw-r--r--includes/DefaultSettings.php223
-rw-r--r--includes/Defines.php1
-rw-r--r--includes/DifferenceEngine.php96
-rw-r--r--includes/DjVuImage.php2
-rw-r--r--includes/EditPage.php230
-rw-r--r--includes/Exception.php2
-rw-r--r--includes/Exif.php16
-rw-r--r--includes/Export.php5
-rw-r--r--includes/Feed.php4
-rw-r--r--includes/FileStore.php8
-rw-r--r--includes/GlobalFunctions.php252
-rw-r--r--includes/HTMLCacheUpdate.php3
-rw-r--r--includes/HTMLFileCache.php159
-rw-r--r--includes/HTMLForm.php2
-rw-r--r--includes/HistoryBlob.php2
-rw-r--r--includes/Hooks.php1
-rw-r--r--includes/IP.php59
-rw-r--r--includes/Image.php16
-rw-r--r--includes/ImageFunctions.php5
-rw-r--r--includes/ImageGallery.php23
-rw-r--r--includes/ImagePage.php34
-rw-r--r--includes/Licenses.php8
-rw-r--r--includes/LinkBatch.php5
-rw-r--r--includes/Linker.php262
-rw-r--r--includes/LoadBalancer.php16
-rw-r--r--includes/LogPage.php6
-rw-r--r--includes/MagicWord.php5
-rw-r--r--includes/Math.php19
-rw-r--r--includes/MessageCache.php432
-rw-r--r--includes/Metadata.php13
-rw-r--r--includes/MimeMagic.php2
-rw-r--r--includes/Namespace.php4
-rw-r--r--includes/OutputPage.php316
-rw-r--r--includes/PageHistory.php24
-rw-r--r--includes/Pager.php10
-rw-r--r--includes/Parser.php1053
-rw-r--r--includes/ParserCache.php2
-rw-r--r--includes/Profiler.php6
-rw-r--r--includes/ProfilerSimple.php19
-rw-r--r--includes/ProfilerSimpleUDP.php2
-rw-r--r--includes/ProtectionForm.php4
-rw-r--r--includes/ProxyTools.php103
-rw-r--r--includes/QueryPage.php36
-rw-r--r--includes/RecentChange.php92
-rw-r--r--includes/Revision.php6
-rw-r--r--includes/Sanitizer.php41
-rw-r--r--includes/SearchEngine.php3
-rw-r--r--includes/SearchMySQL4.php3
-rw-r--r--includes/SearchPostgres.php8
-rw-r--r--includes/SearchTsearch2.php5
-rw-r--r--includes/Setup.php30
-rw-r--r--includes/SiteStats.php168
-rw-r--r--includes/Skin.php288
-rw-r--r--includes/SkinTemplate.php125
-rw-r--r--includes/SpecialAllmessages.php120
-rw-r--r--includes/SpecialAllpages.php86
-rw-r--r--includes/SpecialBlockip.php30
-rw-r--r--includes/SpecialBooksources.php185
-rw-r--r--includes/SpecialBrokenRedirects.php2
-rw-r--r--includes/SpecialCategories.php3
-rw-r--r--includes/SpecialConfirmemail.php18
-rw-r--r--includes/SpecialContributions.php69
-rw-r--r--includes/SpecialDeadendpages.php2
-rw-r--r--includes/SpecialDisambiguations.php2
-rw-r--r--includes/SpecialDoubleRedirects.php2
-rw-r--r--includes/SpecialEmailuser.php27
-rw-r--r--includes/SpecialExport.php14
-rw-r--r--includes/SpecialImagelist.php11
-rw-r--r--includes/SpecialImport.php10
-rw-r--r--includes/SpecialIpblocklist.php24
-rw-r--r--includes/SpecialListusers.php2
-rw-r--r--includes/SpecialLockdb.php4
-rw-r--r--includes/SpecialLog.php8
-rw-r--r--includes/SpecialLonelypages.php2
-rw-r--r--includes/SpecialMIMEsearch.php7
-rw-r--r--includes/SpecialMostcategories.php21
-rw-r--r--includes/SpecialMostimages.php2
-rw-r--r--includes/SpecialMostlinked.php14
-rw-r--r--includes/SpecialMostlinkedcategories.php2
-rw-r--r--includes/SpecialMostrevisions.php2
-rw-r--r--includes/SpecialMovepage.php35
-rw-r--r--includes/SpecialNewimages.php11
-rw-r--r--includes/SpecialNewpages.php9
-rw-r--r--includes/SpecialPage.php518
-rw-r--r--includes/SpecialPreferences.php174
-rw-r--r--includes/SpecialPrefixindex.php3
-rw-r--r--includes/SpecialRandompage.php4
-rw-r--r--includes/SpecialRandomredirect.php2
-rw-r--r--includes/SpecialRecentchanges.php24
-rw-r--r--includes/SpecialRecentchangeslinked.php17
-rw-r--r--includes/SpecialResetpass.php158
-rw-r--r--includes/SpecialRevisiondelete.php9
-rw-r--r--includes/SpecialSearch.php26
-rw-r--r--includes/SpecialShortpages.php10
-rw-r--r--includes/SpecialSpecialpages.php2
-rw-r--r--includes/SpecialStatistics.php39
-rw-r--r--includes/SpecialUncategorizedimages.php2
-rw-r--r--includes/SpecialUncategorizedpages.php2
-rw-r--r--includes/SpecialUndelete.php81
-rw-r--r--includes/SpecialUnlockdb.php4
-rw-r--r--includes/SpecialUnusedcategories.php4
-rw-r--r--includes/SpecialUnusedimages.php4
-rw-r--r--includes/SpecialUnusedtemplates.php4
-rw-r--r--includes/SpecialUnwatchedpages.php2
-rw-r--r--includes/SpecialUpload.php92
-rw-r--r--includes/SpecialUserlogin.php152
-rw-r--r--includes/SpecialUserrights.php35
-rw-r--r--includes/SpecialVersion.php97
-rw-r--r--includes/SpecialWantedcategories.php2
-rw-r--r--includes/SpecialWantedpages.php2
-rw-r--r--includes/SpecialWatchlist.php176
-rw-r--r--includes/SpecialWhatlinkshere.php4
-rw-r--r--includes/SquidUpdate.php15
-rw-r--r--includes/StreamFile.php12
-rw-r--r--includes/StringUtils.php301
-rw-r--r--includes/StubObject.php16
-rw-r--r--includes/Title.php398
-rw-r--r--includes/User.php1263
-rw-r--r--includes/UserMailer.php28
-rw-r--r--includes/WebRequest.php13
-rw-r--r--includes/WebStart.php10
-rw-r--r--includes/Wiki.php43
-rw-r--r--includes/WikiError.php4
-rw-r--r--includes/Xml.php56
-rw-r--r--includes/ZhClient.php2
-rw-r--r--includes/api/ApiBase.php200
-rw-r--r--includes/api/ApiFeedWatchlist.php125
-rw-r--r--includes/api/ApiFormatBase.php109
-rw-r--r--includes/api/ApiFormatJson.php23
-rw-r--r--includes/api/ApiFormatPhp.php54
-rw-r--r--includes/api/ApiFormatWddx.php89
-rw-r--r--includes/api/ApiFormatXml.php36
-rw-r--r--includes/api/ApiFormatYaml.php5
-rw-r--r--includes/api/ApiFormatYaml_spyc.php9
-rw-r--r--includes/api/ApiLogin.php6
-rw-r--r--includes/api/ApiMain.php294
-rw-r--r--includes/api/ApiOpenSearch.php109
-rw-r--r--includes/api/ApiPageSet.php138
-rw-r--r--includes/api/ApiQuery.php58
-rw-r--r--includes/api/ApiQueryAllpages.php114
-rw-r--r--includes/api/ApiQueryBacklinks.php358
-rw-r--r--includes/api/ApiQueryBase.php273
-rw-r--r--includes/api/ApiQueryInfo.php13
-rw-r--r--includes/api/ApiQueryLogEvents.php173
-rw-r--r--includes/api/ApiQueryRecentChanges.php187
-rw-r--r--includes/api/ApiQueryRevisions.php194
-rw-r--r--includes/api/ApiQuerySiteinfo.php11
-rw-r--r--includes/api/ApiQueryUserContributions.php175
-rw-r--r--includes/api/ApiQueryWatchlist.php234
-rw-r--r--includes/api/ApiResult.php64
-rw-r--r--includes/cbt/CBTCompiler.php4
-rw-r--r--includes/memcached-client.php10
-rw-r--r--includes/normal/RandomTest.php1
-rw-r--r--includes/normal/Utf8Test.php2
-rw-r--r--includes/normal/UtfNormal.php54
-rw-r--r--includes/normal/UtfNormalGenerate.php1
-rw-r--r--includes/normal/UtfNormalTest.php2
-rw-r--r--includes/templates/Userlogin.php2
175 files changed, 9602 insertions, 3666 deletions
diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php
index c2744980..89062f87 100644
--- a/includes/AjaxDispatcher.php
+++ b/includes/AjaxDispatcher.php
@@ -15,7 +15,7 @@ class AjaxDispatcher {
var $args;
function AjaxDispatcher() {
- wfProfileIn( 'AjaxDispatcher::AjaxDispatcher' );
+ wfProfileIn( __METHOD__ );
$this->mode = "";
@@ -42,7 +42,7 @@ class AjaxDispatcher {
$this->args = array();
}
}
- wfProfileOut( 'AjaxDispatcher::AjaxDispatcher' );
+ wfProfileOut( __METHOD__ );
}
function performAction() {
@@ -51,7 +51,7 @@ class AjaxDispatcher {
if ( empty( $this->mode ) ) {
return;
}
- wfProfileIn( 'AjaxDispatcher::performAction' );
+ wfProfileIn( __METHOD__ );
if (! in_array( $this->func_name, $wgAjaxExportList ) ) {
header( 'Status: 400 Bad Request', true, 400 );
@@ -72,7 +72,7 @@ class AjaxDispatcher {
$result->sendHeaders();
$result->printText();
}
-
+
} catch (Exception $e) {
if (!headers_sent()) {
header( 'Status: 500 Internal Error', true, 500 );
@@ -83,7 +83,7 @@ class AjaxDispatcher {
}
}
- wfProfileOut( 'AjaxDispatcher::performAction' );
+ wfProfileOut( __METHOD__ );
$wgOut = null;
}
}
diff --git a/includes/AjaxFunctions.php b/includes/AjaxFunctions.php
index 9f7a332f..eee2a1a4 100644
--- a/includes/AjaxFunctions.php
+++ b/includes/AjaxFunctions.php
@@ -129,4 +129,43 @@ function wfSajaxSearch( $term ) {
return $response;
}
+/**
+ * Called for AJAX watch/unwatch requests.
+ * @param $pageID Integer ID of the page to be watched/unwatched
+ * @param $watch String 'w' to watch, 'u' to unwatch
+ * @return String '<w#>' or '<u#>' on successful watch or unwatch, respectively, or '<err#>' on error (invalid XML in case we want to add HTML sometime)
+ */
+function wfAjaxWatch($pageID = "", $watch = "") {
+ if(wfReadOnly())
+ return '<err#>'; // redirect to action=(un)watch, which will display the database lock message
+
+ if(('w' !== $watch && 'u' !== $watch) || !is_numeric($pageID))
+ return '<err#>';
+ $watch = 'w' === $watch;
+ $pageID = intval($pageID);
+
+ $title = Title::newFromID($pageID);
+ if(!$title)
+ return '<err#>';
+ $article = new Article($title);
+ $watching = $title->userIsWatching();
+
+ if($watch) {
+ if(!$watching) {
+ $dbw =& wfGetDB(DB_MASTER);
+ $dbw->begin();
+ $article->doWatch();
+ $dbw->commit();
+ }
+ } else {
+ if($watching) {
+ $dbw =& wfGetDB(DB_MASTER);
+ $dbw->begin();
+ $article->doUnwatch();
+ $dbw->commit();
+ }
+ }
+
+ return $watch ? '<w#>' : '<u#>';
+}
?>
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php
index 40f50876..a59c73bb 100644
--- a/includes/AjaxResponse.php
+++ b/includes/AjaxResponse.php
@@ -61,7 +61,7 @@ class AjaxResponse {
}
function sendHeaders() {
- global $wgUseSquid, $wgUseESI, $wgSquidMaxage;
+ global $wgUseSquid, $wgUseESI;
if ( $this->mResponseCode ) {
$n = preg_replace( '/^ *(\d+)/', '\1', $this->mResponseCode );
@@ -122,7 +122,7 @@ class AjaxResponse {
* returns true iff the response code was set to 304 Not Modified.
*/
function checkLastModified ( $timestamp ) {
- global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest;
+ global $wgCachePages, $wgCacheEpoch, $wgUser;
$fname = 'AjaxResponse::checkLastModified';
if ( !$timestamp || $timestamp == '19700101000000' ) {
diff --git a/includes/Article.php b/includes/Article.php
index 8c07b06c..6b4f5270 100644
--- a/includes/Article.php
+++ b/includes/Article.php
@@ -9,7 +9,7 @@
*
* See design.txt for an overview.
* Note: edit user interface and cache support functions have been
- * moved to separate EditPage and CacheManager classes.
+ * moved to separate EditPage and HTMLFileCache classes.
*
* @package MediaWiki
*/
@@ -48,7 +48,7 @@ class Article {
$this->mOldId = $oldId;
$this->clear();
}
-
+
/**
* Tell the page view functions that this view was redirected
* from another page on the wiki.
@@ -79,13 +79,13 @@ class Article {
}
} else {
if( $rt->getNamespace() == NS_SPECIAL ) {
- // Gotta hand redirects to special pages differently:
+ // Gotta handle redirects to special pages differently:
// Fill the HTTP response "Location" header and ignore
// the rest of the page we're on.
//
// This can be hard to reverse, so they may be disabled.
- if( $rt->getNamespace() == NS_SPECIAL && $rt->getText() == 'Userlogout' ) {
+ if( $rt->isSpecial( 'Userlogout' ) ) {
// rolleyes
} else {
return $rt->getFullURL();
@@ -139,7 +139,7 @@ class Article {
* @return Return the text of this revision
*/
function getContent() {
- global $wgRequest, $wgUser, $wgOut;
+ global $wgUser, $wgOut;
wfProfileIn( __METHOD__ );
@@ -236,9 +236,6 @@ class Article {
# Pre-fill content with error message so that if something
# fails we'll have something telling us what we intended.
-
- $t = $this->mTitle->getPrefixedText();
-
$this->mOldId = $oldid;
$this->fetchContent( $oldid );
}
@@ -575,13 +572,10 @@ class Article {
function getContributors($limit = 0, $offset = 0) {
# XXX: this is expensive; cache this info somewhere.
- $title = $this->mTitle;
$contribs = array();
$dbr =& wfGetDB( DB_SLAVE );
$revTable = $dbr->tableName( 'revision' );
$userTable = $dbr->tableName( 'user' );
- $encDBkey = $dbr->addQuotes( $title->getDBkey() );
- $ns = $title->getNamespace();
$user = $this->getUser();
$pageId = $this->getId();
@@ -638,6 +632,8 @@ class Article {
if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
$policy = $wgNamespaceRobotPolicies[$ns];
} else {
+ # The default policy. Dev note: make sure you change the documentation
+ # in DefaultSettings.php before changing it.
$policy = 'index,follow';
}
$wgOut->setRobotpolicy( $policy );
@@ -697,6 +693,12 @@ class Article {
$redir = $sk->makeKnownLinkObj( $this->mRedirectedFrom, '', 'redirect=no' );
$s = wfMsg( 'redirectedfrom', $redir );
$wgOut->setSubtitle( $s );
+
+ // Set the fragment if one was specified in the redirect
+ if ( strval( $this->mTitle->getFragment() ) != '' ) {
+ $fragment = Xml::escapeJsString( $this->mTitle->getFragmentForURL() );
+ $wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" );
+ }
$wasRedirected = true;
}
} elseif ( !empty( $rdfrom ) ) {
@@ -784,12 +786,9 @@ class Article {
if( !$wasRedirected && $this->isCurrent() ) {
$wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) );
}
- $targetUrl = $rt->escapeLocalURL();
- # fixme unused $titleText :
- $titleText = htmlspecialchars( $rt->getPrefixedText() );
- $link = $sk->makeLinkObj( $rt );
+ $link = $sk->makeLinkObj( $rt, $rt->getFullText() );
- $wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT" />' .
+ $wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT " />' .
'<span class="redirectText">'.$link.'</span>' );
$parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser));
@@ -997,32 +996,87 @@ class Article {
* when different from the currently set value.
* Giving 0 indicates the new page flag should
* be set on.
+ * @param bool $lastRevIsRedirect If given, will optimize adding and
+ * removing rows in redirect table.
* @return bool true on success, false on failure
* @private
*/
- function updateRevisionOn( &$dbw, $revision, $lastRevision = null ) {
+ function updateRevisionOn( &$dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
wfProfileIn( __METHOD__ );
+ $text = $revision->getText();
+ $rt = Title::newFromRedirect( $text );
+
$conditions = array( 'page_id' => $this->getId() );
if( !is_null( $lastRevision ) ) {
# An extra check against threads stepping on each other
$conditions['page_latest'] = $lastRevision;
}
- $text = $revision->getText();
$dbw->update( 'page',
array( /* SET */
'page_latest' => $revision->getId(),
'page_touched' => $dbw->timestamp(),
'page_is_new' => ($lastRevision === 0) ? 1 : 0,
- 'page_is_redirect' => Article::isRedirect( $text ) ? 1 : 0,
+ 'page_is_redirect' => $rt !== NULL ? 1 : 0,
'page_len' => strlen( $text ),
),
$conditions,
__METHOD__ );
+ $result = $dbw->affectedRows() != 0;
+
+ if ($result) {
+ // FIXME: Should the result from updateRedirectOn() be returned instead?
+ $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
+ }
+
wfProfileOut( __METHOD__ );
- return ( $dbw->affectedRows() != 0 );
+ return $result;
+ }
+
+ /**
+ * Add row to the redirect table if this is a redirect, remove otherwise.
+ *
+ * @param Database $dbw
+ * @param $redirectTitle a title object pointing to the redirect target,
+ * or NULL if this is not a redirect
+ * @param bool $lastRevIsRedirect If given, will optimize adding and
+ * removing rows in redirect table.
+ * @return bool true on success, false on failure
+ * @private
+ */
+ function updateRedirectOn( &$dbw, $redirectTitle, $lastRevIsRedirect = null ) {
+
+ // Always update redirects (target link might have changed)
+ // Update/Insert if we don't know if the last revision was a redirect or not
+ // Delete if changing from redirect to non-redirect
+ $isRedirect = !is_null($redirectTitle);
+ if ($isRedirect || is_null($lastRevIsRedirect) || $lastRevIsRedirect !== $isRedirect) {
+
+ wfProfileIn( __METHOD__ );
+
+ if ($isRedirect) {
+
+ // This title is a redirect, Add/Update row in the redirect table
+ $set = array( /* SET */
+ 'rd_namespace' => $redirectTitle->getNamespace(),
+ 'rd_title' => $redirectTitle->getDBkey(),
+ 'rd_from' => $this->getId(),
+ );
+
+ $dbw->replace( 'redirect', array( 'rd_from' ), $set, __METHOD__ );
+ } else {
+ // This is not a redirect, remove row from redirect table
+ $where = array( 'rd_from' => $this->getId() );
+ $dbw->delete( 'redirect', $where, __METHOD__);
+ }
+
+ wfProfileOut( __METHOD__ );
+ return ( $dbw->affectedRows() != 0 );
+ }
+
+ return true;
}
/**
@@ -1037,7 +1091,7 @@ class Article {
$row = $dbw->selectRow(
array( 'revision', 'page' ),
- array( 'rev_id', 'rev_timestamp' ),
+ array( 'rev_id', 'rev_timestamp', 'page_is_redirect' ),
array(
'page_id' => $this->getId(),
'page_latest=rev_id' ),
@@ -1048,12 +1102,14 @@ class Article {
return false;
}
$prev = $row->rev_id;
+ $lastRevIsRedirect = (bool)$row->page_is_redirect;
} else {
# No or missing previous revision; mark the page as new
$prev = 0;
+ $lastRevIsRedirect = null;
}
- $ret = $this->updateRevisionOn( $dbw, $revision, $prev );
+ $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
wfProfileOut( __METHOD__ );
return $ret;
}
@@ -1080,13 +1136,18 @@ class Article {
}
$oldtext = $rev->getText();
- if($section=='new') {
- if($summary) $subject="== {$summary} ==\n\n";
- $text=$oldtext."\n\n".$subject.$text;
+ if( $section == 'new' ) {
+ # Inserting a new section
+ $subject = $summary ? "== {$summary} ==\n\n" : '';
+ $text = strlen( trim( $oldtext ) ) > 0
+ ? "{$oldtext}\n\n{$subject}{$text}"
+ : "{$subject}{$text}";
} else {
+ # Replacing an existing section; roll out the big guns
global $wgParser;
$text = $wgParser->replaceSection( $oldtext, $section, $text );
}
+
}
wfProfileOut( __METHOD__ );
@@ -1097,7 +1158,7 @@ class Article {
* @deprecated use Article::doEdit()
*/
function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false, $comment=false ) {
- $flags = EDIT_NEW | EDIT_DEFER_UPDATES |
+ $flags = EDIT_NEW | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
( $isminor ? EDIT_MINOR : 0 ) |
( $suppressRC ? EDIT_SUPPRESS_RC : 0 );
@@ -1129,7 +1190,7 @@ class Article {
* @deprecated use Article::doEdit()
*/
function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) {
- $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES |
+ $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
( $minor ? EDIT_MINOR : 0 ) |
( $forceBot ? EDIT_FORCE_BOT : 0 );
@@ -1178,6 +1239,8 @@ class Article {
* Mark the edit a "bot" edit regardless of user rights
* EDIT_DEFER_UPDATES
* Defer some of the updates until the end of index.php
+ * EDIT_AUTOSUMMARY
+ * Fill in blank summaries with generated text where possible
*
* If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
* If EDIT_UPDATE is specified and the article doesn't exist, the function will return false. If
@@ -1215,7 +1278,15 @@ class Article {
$isminor = ( $flags & EDIT_MINOR ) && $wgUser->isAllowed('minoredit');
$bot = $wgUser->isAllowed( 'bot' ) || ( $flags & EDIT_FORCE_BOT );
+ $oldtext = $this->getContent();
+ $oldsize = strlen( $oldtext );
+
+ # Provide autosummaries if one is not provided.
+ if ($flags & EDIT_AUTOSUMMARY && $summary == '')
+ $summary = $this->getAutosummary( $oldtext, $text, $flags );
+
$text = $this->preSaveTransform( $text );
+ $newsize = strlen( $text );
$dbw =& wfGetDB( DB_MASTER );
$now = wfTimestampNow();
@@ -1228,9 +1299,6 @@ class Article {
$userAbort = ignore_user_abort( true );
}
- $oldtext = $this->getContent();
- $oldsize = strlen( $oldtext );
- $newsize = strlen( $text );
$lastRevision = 0;
$revisionId = 0;
@@ -1273,11 +1341,12 @@ class Article {
$lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
$revisionId );
- # Mark as patrolled if the user can do so and has it set in their options
- if( $wgUser->isAllowed( 'patrol' ) && $wgUser->getOption( 'autopatrol' ) ) {
+ # Mark as patrolled if the user can do so
+ if( $wgUser->isAllowed( 'autopatrol' ) ) {
RecentChange::markPatrolled( $rcid );
}
}
+ $wgUser->incEditCount();
$dbw->commit();
}
} else {
@@ -1333,11 +1402,12 @@ class Article {
if( !( $flags & EDIT_SUPPRESS_RC ) ) {
$rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot,
'', strlen( $text ), $revisionId );
- # Mark as patrolled if the user can and has the option set
- if( $wgUser->isAllowed( 'patrol' ) && $wgUser->getOption( 'autopatrol' ) ) {
+ # Mark as patrolled if the user can
+ if( $wgUser->isAllowed( 'autopatrol' ) ) {
RecentChange::markPatrolled( $rcid );
}
}
+ $wgUser->incEditCount();
$dbw->commit();
# Update links, etc.
@@ -1393,7 +1463,7 @@ class Article {
*/
function markpatrolled() {
global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUser;
- $wgOut->setRobotpolicy( 'noindex,nofollow' );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
# Check RC patrol config. option
if( !$wgUseRCPatrol ) {
@@ -1407,20 +1477,45 @@ class Article {
return;
}
+ # If we haven't been given an rc_id value, we can't do anything
$rcid = $wgRequest->getVal( 'rcid' );
- if ( !is_null ( $rcid ) ) {
- if( wfRunHooks( 'MarkPatrolled', array( &$rcid, &$wgUser, false ) ) ) {
- RecentChange::markPatrolled( $rcid );
- wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) );
- $wgOut->setPagetitle( wfMsg( 'markedaspatrolled' ) );
- $wgOut->addWikiText( wfMsg( 'markedaspatrolledtext' ) );
- }
- $rcTitle = Title::makeTitle( NS_SPECIAL, 'Recentchanges' );
- $wgOut->returnToMain( false, $rcTitle->getPrefixedText() );
+ if( !$rcid ) {
+ $wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
+ return;
}
- else {
- $wgOut->showErrorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
+
+ # Handle the 'MarkPatrolled' hook
+ if( !wfRunHooks( 'MarkPatrolled', array( $rcid, &$wgUser, false ) ) ) {
+ return;
+ }
+
+ $return = SpecialPage::getTitleFor( 'Recentchanges' );
+ # If it's left up to us, check that the user is allowed to patrol this edit
+ # If the user has the "autopatrol" right, then we'll assume there are no
+ # other conditions stopping them doing so
+ if( !$wgUser->isAllowed( 'autopatrol' ) ) {
+ $rc = RecentChange::newFromId( $rcid );
+ # Graceful error handling, as we've done before here...
+ # (If the recent change doesn't exist, then it doesn't matter whether
+ # the user is allowed to patrol it or not; nothing is going to happen
+ if( is_object( $rc ) && $wgUser->getName() == $rc->getAttribute( 'rc_user_text' ) ) {
+ # The user made this edit, and can't patrol it
+ # Tell them so, and then back off
+ $wgOut->setPageTitle( wfMsg( 'markedaspatrollederror' ) );
+ $wgOut->addWikiText( wfMsgNoTrans( 'markedaspatrollederror-noautopatrol' ) );
+ $wgOut->returnToMain( false, $return );
+ return;
+ }
}
+
+ # Mark the edit as patrolled
+ RecentChange::markPatrolled( $rcid );
+ wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) );
+
+ # Inform the user
+ $wgOut->setPageTitle( wfMsg( 'markedaspatrolled' ) );
+ $wgOut->addWikiText( wfMsgNoTrans( 'markedaspatrolledtext' ) );
+ $wgOut->returnToMain( false, $return );
}
/**
@@ -1662,6 +1757,11 @@ class Article {
if( $confirm ) {
$this->doDelete( $reason );
+ if( $wgRequest->getCheck( 'wpWatch' ) ) {
+ $this->doWatch();
+ } elseif( $this->mTitle->userIsWatching() ) {
+ $this->doUnwatch();
+ }
return;
}
@@ -1801,6 +1901,7 @@ class Article {
$confirm = htmlspecialchars( wfMsg( 'deletepage' ) );
$delcom = htmlspecialchars( wfMsg( 'deletecomment' ) );
$token = htmlspecialchars( $wgUser->editToken() );
+ $watch = Xml::checkLabel( wfMsg( 'watchthis' ), 'wpWatch', 'wpWatch', $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(), array( 'tabindex' => '2' ) );
$wgOut->addHTML( "
<form id='deleteconfirm' method='post' action=\"{$formaction}\">
@@ -1810,13 +1911,17 @@ class Article {
<label for='wpReason'>{$delcom}:</label>
</td>
<td align='left'>
- <input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" />
+ <input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" tabindex=\"1\" />
</td>
</tr>
<tr>
<td>&nbsp;</td>
+ <td>$watch</td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
<td>
- <input type='submit' name='wpConfirmB' value=\"{$confirm}\" />
+ <input type='submit' name='wpConfirmB' id='wpConfirmB' value=\"{$confirm}\" tabindex=\"3\" />
</td>
</tr>
</table>
@@ -1860,7 +1965,7 @@ class Article {
*/
function doDeleteArticle( $reason ) {
global $wgUseSquid, $wgDeferredUpdateList;
- global $wgPostCommitUpdateList, $wgUseTrackbacks;
+ global $wgUseTrackbacks;
wfDebug( __METHOD__."\n" );
@@ -1897,6 +2002,8 @@ class Article {
'ar_minor_edit' => 'rev_minor_edit',
'ar_rev_id' => 'rev_id',
'ar_text_id' => 'rev_text_id',
+ 'ar_text' => '\'\'', // Be explicit to appease
+ 'ar_flags' => '\'\'', // MySQL's "strict mode"...
), array(
'page_id' => $id,
'page_id = rev_page'
@@ -1921,6 +2028,7 @@ class Article {
$dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
$dbw->delete( 'externallinks', array( 'el_from' => $id ) );
$dbw->delete( 'langlinks', array( 'll_from' => $id ) );
+ $dbw->delete( 'redirect', array( 'rd_from' => $id ) );
}
# If using cleanup triggers, we can skip some manual deletes
@@ -1976,8 +2084,6 @@ class Article {
$bot = $wgRequest->getBool( 'bot' );
# Replace all this user's current edits with the next one down
- $tt = $this->mTitle->getDBKey();
- $n = $this->mTitle->getNamespace();
# Get the last editor
$current = Revision::newFromTitle( $this->mTitle );
@@ -2140,8 +2246,10 @@ 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->getTitleKey() && $changed ) {
+ # load of user talk pages and piss people off, nor if it's a minor edit
+ # by a properly-flagged bot.
+ if( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed
+ && !($minoredit && $wgUser->isAllowed('nominornewtalk') ) ) {
if (wfRunHooks('ArticleEditUpdateNewTalk', array(&$this)) ) {
$other = User::newFromName( $shortTitle );
if( is_null( $other ) && User::isIP( $shortTitle ) ) {
@@ -2202,6 +2310,9 @@ class Article {
$lnk = $current
? wfMsg( 'currentrevisionlink' )
: $lnk = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'currentrevisionlink' ) );
+ $curdiff = $current
+ ? wfMsg( 'diff' )
+ : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=cur&oldid='.$oldid );
$prev = $this->mTitle->getPreviousRevisionID( $oldid ) ;
$prevlink = $prev
? $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'previousrevision' ), 'direction=prev&oldid='.$oldid )
@@ -2219,7 +2330,8 @@ class Article {
$userlinks = $sk->userLink( $revision->getUser(), $revision->getUserText() )
. $sk->userToolLinks( $revision->getUser(), $revision->getUserText() );
- $r = wfMsg( 'old-revision-navigation', $td, $lnk, $prevlink, $nextlink, $userlinks, $prevdiff, $nextdiff );
+ $r = "\n\t\t\t\t<div id=\"mw-revision-info\">" . wfMsg( 'revision-info', $td, $userlinks ) . "</div>\n" .
+ "\n\t\t\t\t<div id=\"mw-revision-nav\">" . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
$wgOut->setSubtitle( $r );
}
@@ -2250,7 +2362,7 @@ class Article {
$called = true;
if($this->isFileCacheable()) {
$touched = $this->mTouched;
- $cache = new CacheManager( $this->mTitle );
+ $cache = new HTMLFileCache( $this->mTitle );
if($cache->isFileCacheGood( $touched )) {
wfDebug( "Article::tryFileCache(): about to load file\n" );
$cache->loadFromFileCache();
@@ -2270,7 +2382,11 @@ class Article {
*/
function isFileCacheable() {
global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest;
- extract( $wgRequest->getValues( 'action', 'oldid', 'diff', 'redirect', 'printable' ) );
+ $action = $wgRequest->getVal( 'action' );
+ $oldid = $wgRequest->getVal( 'oldid' );
+ $diff = $wgRequest->getVal( 'diff' );
+ $redirect = $wgRequest->getVal( 'redirect' );
+ $printable = $wgRequest->getVal( 'printable' );
return $wgUseFileCache
and (!$wgShowIPinHeader)
@@ -2338,8 +2454,7 @@ class Article {
'comment' => $comment,
'minor_edit' => $minor ? 1 : 0,
) );
- # fixme : $revisionId never used
- $revisionId = $revision->insertOn( $dbw );
+ $revision->insertOn( $dbw );
$this->updateRevisionOn( $dbw, $revision );
$dbw->commit();
@@ -2361,7 +2476,7 @@ class Article {
$hitcounterTable = $dbw->tableName( 'hitcounter' );
$acchitsTable = $dbw->tableName( 'acchits' );
- if( $wgHitcounterUpdateFreq <= 1 ){ //
+ if( $wgHitcounterUpdateFreq <= 1 ) {
$dbw->query( "UPDATE $pageTable SET page_counter = page_counter + 1 WHERE page_id = $id" );
return;
}
@@ -2388,14 +2503,19 @@ class Article {
if ($wgDBtype == 'mysql')
$dbw->query("LOCK TABLES $hitcounterTable WRITE");
$tabletype = $wgDBtype == 'mysql' ? "ENGINE=HEAP " : '';
- $dbw->query("CREATE TEMPORARY TABLE $acchitsTable $tabletype".
+ $dbw->query("CREATE TEMPORARY TABLE $acchitsTable $tabletype AS ".
"SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable ".
'GROUP BY hc_id');
$dbw->query("DELETE FROM $hitcounterTable");
- if ($wgDBtype == 'mysql')
+ if ($wgDBtype == 'mysql') {
$dbw->query('UNLOCK TABLES');
- $dbw->query("UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n ".
- 'WHERE page_id = hc_id');
+ $dbw->query("UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n ".
+ 'WHERE page_id = hc_id');
+ }
+ else {
+ $dbw->query("UPDATE $pageTable SET page_counter=page_counter + hc_n ".
+ "FROM $acchitsTable WHERE page_id = hc_id");
+ }
$dbw->query("DROP TABLE $acchitsTable");
ignore_user_abort( $old_user_abort );
@@ -2438,7 +2558,7 @@ class Article {
# File cache
if ( $wgUseFileCache ) {
- $cm = new CacheManager( $title );
+ $cm = new HTMLFileCache( $title );
@unlink( $cm->fileCacheName() );
}
@@ -2453,8 +2573,6 @@ class Article {
static function onArticleEdit( $title ) {
global $wgDeferredUpdateList, $wgUseFileCache;
- $urls = array();
-
// Invalidate caches of articles which include this page
$update = new HTMLCacheUpdate( $title, 'templatelinks' );
$wgDeferredUpdateList[] = $update;
@@ -2464,7 +2582,7 @@ class Article {
# Clear file cache
if ( $wgUseFileCache ) {
- $cm = new CacheManager( $title );
+ $cm = new HTMLFileCache( $title );
@unlink( $cm->fileCacheName() );
}
}
@@ -2590,6 +2708,83 @@ class Article {
$dbr->freeResult( $res );
return $result;
}
+
+ /**
+ * Return an auto-generated summary if the text provided is a redirect.
+ *
+ * @param string $text The wikitext to check
+ * @return string '' or an appropriate summary
+ */
+ public static function getRedirectAutosummary( $text ) {
+ $rt = Title::newFromRedirect( $text );
+ if( is_object( $rt ) )
+ return wfMsgForContent( 'autoredircomment', $rt->getFullText() );
+ else
+ return '';
+ }
+
+ /**
+ * Return an auto-generated summary if the new text is much shorter than
+ * the old text.
+ *
+ * @param string $oldtext The previous text of the page
+ * @param string $text The submitted text of the page
+ * @return string An appropriate autosummary, or an empty string.
+ */
+ public static function getBlankingAutosummary( $oldtext, $text ) {
+ if ($oldtext!='' && $text=='') {
+ return wfMsgForContent('autosumm-blank');
+ } elseif (strlen($oldtext) > 10 * strlen($text) && strlen($text) < 500) {
+ #Removing more than 90% of the article
+ global $wgContLang;
+ $truncatedtext = $wgContLang->truncate($text, max(0, 200 - strlen(wfMsgForContent('autosumm-replace'))), '...');
+ return wfMsgForContent('autosumm-replace', $truncatedtext);
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Return an applicable autosummary if one exists for the given edit.
+ * @param string $oldtext The previous text of the page.
+ * @param string $newtext The submitted text of the page.
+ * @param bitmask $flags A bitmask of flags submitted for the edit.
+ * @return string An appropriate autosummary, or an empty string.
+ */
+ public static function getAutosummary( $oldtext, $newtext, $flags ) {
+
+ # This code is UGLY UGLY UGLY.
+ # Somebody PLEASE come up with a more elegant way to do it.
+
+ #Redirect autosummaries
+ $summary = self::getRedirectAutosummary( $newtext );
+
+ if ($summary)
+ return $summary;
+
+ #Blanking autosummaries
+ if (!($flags & EDIT_NEW))
+ $summary = self::getBlankingAutosummary( $oldtext, $newtext );
+
+ if ($summary)
+ return $summary;
+
+ #New page autosummaries
+ if ($flags & EDIT_NEW && strlen($newtext)) {
+ #If they're making a new article, give its text, truncated, in the summary.
+ global $wgContLang;
+ $truncatedtext = $wgContLang->truncate(
+ str_replace("\n", ' ', $newtext),
+ max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new') ) ),
+ '...' );
+ $summary = wfMsgForContent( 'autosumm-new', $truncatedtext );
+ }
+
+ if ($summary)
+ return $summary;
+
+ return $summary;
+ }
}
?>
diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php
index 1d955418..e33ef1bf 100644
--- a/includes/AuthPlugin.php
+++ b/includes/AuthPlugin.php
@@ -146,13 +146,18 @@ class AuthPlugin {
/**
* Set the given password in the authentication database.
+ * As a special case, the password may be set to null to request
+ * locking the password to an unusable value, with the expectation
+ * that it will be set later through a mail reset or other method.
+ *
* Return true if successful.
*
+ * @param $user User object.
* @param $password String: password.
* @return bool
* @public
*/
- function setPassword( $password ) {
+ function setPassword( $user, $password ) {
return true;
}
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 810a448e..8de5608f 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -22,7 +22,11 @@ function __autoload($className) {
'eAccelBagOStuff' => 'includes/BagOStuff.php',
'DBABagOStuff' => 'includes/BagOStuff.php',
'Block' => 'includes/Block.php',
- 'CacheManager' => 'includes/CacheManager.php',
+ 'HTMLFileCache' => 'includes/HTMLFileCache.php',
+ 'DependencyWrapper' => 'includes/CacheDependency.php',
+ 'FileDependency' => 'includes/CacheDependency.php',
+ 'TitleDependency' => 'includes/CacheDependency.php',
+ 'TitleListDependency' => 'includes/CacheDependency.php',
'CategoryPage' => 'includes/CategoryPage.php',
'CategoryViewer' => 'includes/CategoryPage.php',
'Categoryfinder' => 'includes/Categoryfinder.php',
@@ -82,7 +86,6 @@ function __autoload($className) {
'FileStore' => 'includes/FileStore.php',
'FSException' => 'includes/FileStore.php',
'FSTransaction' => 'includes/FileStore.php',
- 'ReplacerCallback' => 'includes/GlobalFunctions.php',
'HTMLForm' => 'includes/HTMLForm.php',
'HistoryBlob' => 'includes/HistoryBlob.php',
'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php',
@@ -146,7 +149,8 @@ function __autoload($className) {
'SearchUpdate' => 'includes/SearchUpdate.php',
'SearchUpdateMyISAM' => 'includes/SearchUpdate.php',
'SiteConfiguration' => 'includes/SiteConfiguration.php',
- 'SiteStatsUpdate' => 'includes/SiteStatsUpdate.php',
+ 'SiteStats' => 'includes/SiteStats.php',
+ 'SiteStatsUpdate' => 'includes/SiteStats.php',
'Skin' => 'includes/Skin.php',
'MediaWiki_I18N' => 'includes/SkinTemplate.php',
'SkinTemplate' => 'includes/SkinTemplate.php',
@@ -154,11 +158,11 @@ function __autoload($className) {
'SpecialAllpages' => 'includes/SpecialAllpages.php',
'AncientPagesPage' => 'includes/SpecialAncientpages.php',
'IPBlockForm' => 'includes/SpecialBlockip.php',
- 'BookSourceList' => 'includes/SpecialBooksources.php',
+ 'SpecialBookSources' => 'includes/SpecialBooksources.php',
'BrokenRedirectsPage' => 'includes/SpecialBrokenRedirects.php',
'CategoriesPage' => 'includes/SpecialCategories.php',
'EmailConfirmation' => 'includes/SpecialConfirmemail.php',
- 'ContribsFinder' => 'includes/SpecialContributions.php',
+ 'ContributionsPage' => 'includes/SpecialContributions.php',
'DeadendPagesPage' => 'includes/SpecialDeadendpages.php',
'DisambiguationsPage' => 'includes/SpecialDisambiguations.php',
'DoubleRedirectsPage' => 'includes/SpecialDoubleRedirects.php',
@@ -182,6 +186,7 @@ function __autoload($className) {
'MostlinkedCategoriesPage' => 'includes/SpecialMostlinkedcategories.php',
'MostrevisionsPage' => 'includes/SpecialMostrevisions.php',
'MovePageForm' => 'includes/SpecialMovepage.php',
+ 'NewbieContributionsPage' => 'includes/SpecialNewbieContributions.php',
'NewPagesPage' => 'includes/SpecialNewpages.php',
'SpecialPage' => 'includes/SpecialPage.php',
'UnlistedSpecialPage' => 'includes/SpecialPage.php',
@@ -211,6 +216,12 @@ function __autoload($className) {
'WantedPagesPage' => 'includes/SpecialWantedpages.php',
'WhatLinksHerePage' => 'includes/SpecialWhatlinkshere.php',
'SquidUpdate' => 'includes/SquidUpdate.php',
+ 'ReplacementArray' => 'includes/StringUtils.php',
+ 'Replacer' => 'includes/StringUtils.php',
+ 'RegexlikeReplacer' => 'includes/StringUtils.php',
+ 'DoubleReplacer' => 'includes/StringUtils.php',
+ 'HashtableReplacer' => 'includes/StringUtils.php',
+ 'StringUtils' => 'includes/StringUtils.php',
'Title' => 'includes/Title.php',
'User' => 'includes/User.php',
'MailAddress' => 'includes/UserMailer.php',
@@ -230,7 +241,39 @@ function __autoload($className) {
'UsercreateTemplate' => 'includes/templates/Userlogin.php',
'UserloginTemplate' => 'includes/templates/Userlogin.php',
'Language' => 'languages/Language.php',
+ 'PasswordResetForm' => 'includes/SpecialResetpass.php',
+
+ // API classes
+ 'ApiBase' => 'includes/api/ApiBase.php',
+ 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
+ 'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php',
+ 'ApiFormatBase' => 'includes/api/ApiFormatBase.php',
+ 'Services_JSON' => 'includes/api/ApiFormatJson_json.php',
+ 'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
+ 'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
+ 'ApiFormatWddx' => 'includes/api/ApiFormatWddx.php',
+ 'ApiFormatXml' => 'includes/api/ApiFormatXml.php',
+ 'Spyc' => 'includes/api/ApiFormatYaml_spyc.php',
+ 'ApiFormatYaml' => 'includes/api/ApiFormatYaml.php',
+ 'ApiHelp' => 'includes/api/ApiHelp.php',
+ 'ApiLogin' => 'includes/api/ApiLogin.php',
+ 'ApiMain' => 'includes/api/ApiMain.php',
+ 'ApiOpenSearch' => 'includes/api/ApiOpenSearch.php',
+ 'ApiPageSet' => 'includes/api/ApiPageSet.php',
+ 'ApiQuery' => 'includes/api/ApiQuery.php',
+ 'ApiQueryAllpages' => 'includes/api/ApiQueryAllpages.php',
+ 'ApiQueryBase' => 'includes/api/ApiQueryBase.php',
+ 'ApiQueryBacklinks' => 'includes/api/ApiQueryBacklinks.php',
+ 'ApiQueryContributions' => 'includes/api/ApiQueryUserContributions.php',
+ 'ApiQueryInfo' => 'includes/api/ApiQueryInfo.php',
+ 'ApiQueryLogEvents' => 'includes/api/ApiQueryLogEvents.php',
+ 'ApiQueryRecentChanges'=> 'includes/api/ApiQueryRecentChanges.php',
+ 'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php',
+ 'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php',
+ 'ApiQueryWatchlist' => 'includes/api/ApiQueryWatchlist.php',
+ 'ApiResult' => 'includes/api/ApiResult.php',
);
+
if ( isset( $localClasses[$className] ) ) {
$filename = $localClasses[$className];
} elseif ( isset( $wgAutoloadClasses[$className] ) ) {
diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php
index 1dc93a2f..c720807d 100644
--- a/includes/BagOStuff.php
+++ b/includes/BagOStuff.php
@@ -240,6 +240,13 @@ abstract class SqlBagOStuff extends BagOStuff {
}
if($row=$this->_fetchobject($res)) {
$this->_debug("get: retrieved data; exp time is " . $row->exptime);
+ if ( $row->exptime != $this->_maxdatetime() &&
+ wfTimestamp( TS_UNIX, $row->exptime ) < time() )
+ {
+ $this->_debug("get: key has expired, deleting");
+ $this->delete($key);
+ return false;
+ }
return $this->_unserialize($this->_blobdecode($row->value));
} else {
$this->_debug('get: no matching rows');
@@ -253,7 +260,7 @@ abstract class SqlBagOStuff extends BagOStuff {
if($exptime == 0) {
$exp = $this->_maxdatetime();
} else {
- if($exptime < 3600*24*30)
+ if($exptime < 3.16e8) # ~10 years
$exptime += time();
$exp = $this->_fromunixtime($exptime);
}
@@ -390,7 +397,8 @@ class MediaWikiBagOStuff extends SqlBagOStuff {
}
function _doinsert($t, $v) {
$dbw =& wfGetDB( DB_MASTER );
- return $dbw->insert($t, $v, 'MediaWikiBagOStuff::_doinsert');
+ return $dbw->insert($t, $v, 'MediaWikiBagOStuff::_doinsert',
+ array( 'IGNORE' ) );
}
function _fetchobject($result) {
$dbw =& wfGetDB( DB_MASTER );
@@ -406,7 +414,11 @@ class MediaWikiBagOStuff extends SqlBagOStuff {
}
function _maxdatetime() {
$dbw =& wfGetDB(DB_MASTER);
- return $dbw->timestamp('9999-12-31 12:59:59');
+ if ( time() > 0x7fffffff ) {
+ return $this->_fromunixtime( 1<<62 );
+ } else {
+ return $this->_fromunixtime( 0x7fffffff );
+ }
}
function _fromunixtime($ts) {
$dbw =& wfGetDB(DB_MASTER);
@@ -492,11 +504,14 @@ class TurckBagOStuff extends BagOStuff {
class APCBagOStuff extends BagOStuff {
function get($key) {
$val = apc_fetch($key);
+ if ( is_string( $val ) ) {
+ $val = unserialize( $val );
+ }
return $val;
}
function set($key, $value, $exptime=0) {
- apc_store($key, $value, $exptime);
+ apc_store($key, serialize($value), $exptime);
return true;
}
diff --git a/includes/Block.php b/includes/Block.php
index b11df22c..ff813ba3 100644
--- a/includes/Block.php
+++ b/includes/Block.php
@@ -17,7 +17,7 @@
class Block
{
/* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
- $mRangeStart, $mRangeEnd, $mAnonOnly;
+ $mRangeStart, $mRangeEnd, $mAnonOnly, $mEnableAutoblock;
/* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName;
const EB_KEEP_EXPIRED = 1;
@@ -25,7 +25,7 @@ class Block
const EB_RANGE_ONLY = 4;
function Block( $address = '', $user = 0, $by = 0, $reason = '',
- $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0 )
+ $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0 )
{
$this->mId = 0;
$this->mAddress = $address;
@@ -37,6 +37,7 @@ class Block
$this->mAnonOnly = $anonOnly;
$this->mCreateAccount = $createAccount;
$this->mExpiry = self::decodeExpiry( $expiry );
+ $this->mEnableAutoblock = $enableAutoblock;
$this->mForUpdate = false;
$this->mFromMaster = false;
@@ -72,7 +73,8 @@ class Block
{
$this->mAddress = $this->mReason = $this->mTimestamp = '';
$this->mId = $this->mAnonOnly = $this->mCreateAccount =
- $this->mAuto = $this->mUser = $this->mBy = 0;
+ $this->mEnableAutoblock = $this->mAuto = $this->mUser =
+ $this->mBy = 0;
$this->mByName = false;
}
@@ -111,9 +113,6 @@ class Block
$options = array();
$db =& $this->getDBOptions( $options );
- $ret = false;
- $killed = false;
-
if ( 0 == $user && $address == '' ) {
# Invalid user specification, not blocked
$this->clear();
@@ -239,14 +238,10 @@ class Block
/**
* Determine if a given integer IPv4 address is in a given CIDR network
+ * @deprecated Use IP::isInRange
*/
function isAddressInRange( $addr, $range ) {
- list( $network, $bits ) = wfParseCIDR( $range );
- if ( $network !== false && $addr >> ( 32 - $bits ) == $network >> ( 32 - $bits ) ) {
- return true;
- } else {
- return false;
- }
+ return IP::isInRange( $addr, $range );
}
function initFromRow( $row )
@@ -259,6 +254,7 @@ class Block
$this->mAuto = $row->ipb_auto;
$this->mAnonOnly = $row->ipb_anon_only;
$this->mCreateAccount = $row->ipb_create_account;
+ $this->mEnableAutoblock = $row->ipb_enable_autoblock;
$this->mId = $row->ipb_id;
$this->mExpiry = self::decodeExpiry( $row->ipb_expiry );
if ( isset( $row->user_name ) ) {
@@ -274,12 +270,9 @@ class Block
{
$this->mRangeStart = '';
$this->mRangeEnd = '';
+
if ( $this->mUser == 0 ) {
- list( $network, $bits ) = wfParseCIDR( $this->mAddress );
- if ( $network !== false ) {
- $this->mRangeStart = sprintf( '%08X', $network );
- $this->mRangeEnd = sprintf( '%08X', $network + (1 << (32 - $bits)) - 1 );
- }
+ list( $this->mRangeStart, $this->mRangeEnd ) = IP::parseRange( $this->mAddress );
}
}
@@ -312,7 +305,7 @@ class Block
$now = wfTimestampNow();
- extract( $db->tableNames( 'ipblocks', 'user' ) );
+ list( $ipblocks, $user ) = $db->tableNamesN( 'ipblocks', 'user' );
$sql = "SELECT $ipblocks.*,user_name FROM $ipblocks,$user " .
"WHERE user_id=ipb_by $cond ORDER BY ipb_timestamp DESC $options";
@@ -335,7 +328,7 @@ class Block
call_user_func( $callback, $block, $tag );
}
}
- wfFreeResult( $res );
+ $db->freeResult( $res );
return $num_rows;
}
@@ -353,6 +346,10 @@ class Block
return $dbw->affectedRows() > 0;
}
+ /**
+ * Insert a block into the block table.
+ *@return Whether or not the insertion was successful.
+ */
function insert()
{
wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
@@ -364,9 +361,14 @@ class Block
$this->mAnonOnly = 0;
}
+ # Unset ipb_enable_autoblock for IP blocks, makes no sense
+ if ( !$this->mUser ) {
+ $this->mEnableAutoblock = 0;
+ }
+
# Don't collide with expired blocks
Block::purgeExpired();
-
+
$ipb_id = $dbw->nextSequenceValue('ipblocks_ipb_id_val');
$dbw->insert( 'ipblocks',
array(
@@ -379,6 +381,7 @@ class Block
'ipb_auto' => $this->mAuto,
'ipb_anon_only' => $this->mAnonOnly,
'ipb_create_account' => $this->mCreateAccount,
+ 'ipb_enable_autoblock' => $this->mEnableAutoblock,
'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
'ipb_range_start' => $this->mRangeStart,
'ipb_range_end' => $this->mRangeEnd,
@@ -386,9 +389,124 @@ class Block
);
$affected = $dbw->affectedRows();
$dbw->commit();
+
+ if ($affected)
+ $this->doRetroactiveAutoblock();
+
return $affected;
}
+ /**
+ * Retroactively autoblocks the last IP used by the user (if it is a user)
+ * blocked by this Block.
+ *@return Whether or not a retroactive autoblock was made.
+ */
+ function doRetroactiveAutoblock() {
+ $dbr = wfGetDB( DB_SLAVE );
+ #If autoblock is enabled, autoblock the LAST IP used
+ # - stolen shamelessly from CheckUser_body.php
+
+ if ($this->mEnableAutoblock && $this->mUser) {
+ wfDebug("Doing retroactive autoblocks for " . $this->mAddress . "\n");
+
+ $row = $dbr->selectRow( 'recentchanges', array( 'rc_ip' ), array( 'rc_user_text' => $this->mAddress ),
+ __METHOD__ , array( 'ORDER BY' => 'rc_timestamp DESC' ) );
+
+ if ( !$row || !$row->rc_ip ) {
+ #No results, don't autoblock anything
+ wfDebug("No IP found to retroactively autoblock\n");
+ } else {
+ #Limit is 1, so no loop needed.
+ $retroblockip = $row->rc_ip;
+ return $this->doAutoblock($retroblockip);
+ }
+ }
+ }
+
+ /**
+ * Autoblocks the given IP, referring to this Block.
+ * @param $autoblockip The IP to autoblock.
+ * @return bool Whether or not an autoblock was inserted.
+ */
+ function doAutoblock( $autoblockip ) {
+ # Check if this IP address is already blocked
+ $dbw =& wfGetDB( DB_MASTER );
+ $dbw->begin();
+
+ # If autoblocks are disabled, go away.
+ if ( !$this->mEnableAutoblock ) {
+ return;
+ }
+
+ # Check for presence on the autoblock whitelist
+ # TODO cache this?
+ $lines = explode( "\n", wfMsgForContentNoTrans( 'autoblock_whitelist' ) );
+
+ $ip = $autoblockip;
+
+ wfDebug("Checking the autoblock whitelist..\n");
+
+ foreach( $lines as $line ) {
+ # List items only
+ if ( substr( $line, 0, 1 ) !== '*' ) {
+ continue;
+ }
+
+ $wlEntry = substr($line, 1);
+ $wlEntry = trim($wlEntry);
+
+ wfDebug("Checking $ip against $wlEntry...");
+
+ # Is the IP in this range?
+ if (IP::isInRange( $ip, $wlEntry )) {
+ wfDebug(" IP $ip matches $wlEntry, not autoblocking\n");
+ #$autoblockip = null; # Don't autoblock a whitelisted IP.
+ return; #This /SHOULD/ introduce a dummy block - but
+ # I don't know a safe way to do so. -werdna
+ } else {
+ wfDebug( " No match\n" );
+ }
+ }
+
+ # It's okay to autoblock. Go ahead and create/insert the block.
+
+ $ipblock = Block::newFromDB( $autoblockip );
+ if ( $ipblock ) {
+ # If the user is already blocked. Then check if the autoblock would
+ # exceed the user block. If it would exceed, then do nothing, else
+ # prolong block time
+ if ($this->mExpiry &&
+ ($this->mExpiry < Block::getAutoblockExpiry($ipblock->mTimestamp))) {
+ return;
+ }
+ # Just update the timestamp
+ $ipblock->updateTimestamp();
+ return;
+ } else {
+ $ipblock = new Block;
+ }
+
+ # Make a new block object with the desired properties
+ wfDebug( "Autoblocking {$this->mAddress}@" . $autoblockip . "\n" );
+ $ipblock->mAddress = $autoblockip;
+ $ipblock->mUser = 0;
+ $ipblock->mBy = $this->mBy;
+ $ipblock->mReason = wfMsgForContent( 'autoblocker', $this->mAddress, $this->mReason );
+ $ipblock->mTimestamp = wfTimestampNow();
+ $ipblock->mAuto = 1;
+ $ipblock->mCreateAccount = $this->mCreateAccount;
+
+ # If the user is already blocked with an expiry date, we don't
+ # want to pile on top of that!
+ if($this->mExpiry) {
+ $ipblock->mExpiry = min ( $this->mExpiry, Block::getAutoblockExpiry( $this->mTimestamp ));
+ } else {
+ $ipblock->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp );
+ }
+ # Insert it
+ return $ipblock->insert();
+ }
+
function deleteIfExpired()
{
$fname = 'Block::deleteIfExpired';
@@ -449,6 +567,16 @@ class Block
return $this->mNetworkBits;
}*/
+ /**
+ * @return The blocker user ID.
+ */
+ public function getBy() {
+ return $this->mBy;
+ }
+
+ /**
+ * @return The blocker user name.
+ */
function getByName()
{
if ( $this->mByName === false ) {
diff --git a/includes/CacheDependency.php b/includes/CacheDependency.php
new file mode 100644
index 00000000..4bb3d328
--- /dev/null
+++ b/includes/CacheDependency.php
@@ -0,0 +1,328 @@
+<?php
+
+/**
+ * This class stores an arbitrary value along with its dependencies.
+ * Users should typically only use DependencyWrapper::getFromCache(), rather
+ * than instantiating one of these objects directly.
+ */
+class DependencyWrapper {
+ var $value;
+ var $deps;
+
+ /**
+ * Create an instance.
+ * @param mixed $value The user-supplied value
+ * @param mixed $deps A dependency or dependency array. All dependencies
+ * must be objects implementing CacheDependency.
+ */
+ function __construct( $value = false, $deps = array() ) {
+ $this->value = $value;
+ if ( !is_array( $deps ) ) {
+ $deps = array( $deps );
+ }
+ $this->deps = $deps;
+ }
+
+ /**
+ * Returns true if any of the dependencies have expired
+ */
+ function isExpired() {
+ foreach ( $this->deps as $dep ) {
+ if ( $dep->isExpired() ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Initialise dependency values in preparation for storing. This must be
+ * called before serialization.
+ */
+ function initialiseDeps() {
+ foreach ( $this->deps as $dep ) {
+ $dep->loadDependencyValues();
+ }
+ }
+
+ /**
+ * Get the user-defined value
+ */
+ function getValue() {
+ return $this->value;
+ }
+
+ /**
+ * Store the wrapper to a cache
+ */
+ function storeToCache( $cache, $key, $expiry = 0 ) {
+ $this->initialiseDeps();
+ $cache->set( $key, $this, $expiry );
+ }
+
+ /**
+ * Attempt to get a value from the cache. If the value is expired or missing,
+ * it will be generated with the callback function (if present), and the newly
+ * calculated value will be stored to the cache in a wrapper.
+ *
+ * @param object $cache A cache object such as $wgMemc
+ * @param string $key The cache key
+ * @param integer $expiry The expiry timestamp or interval in seconds
+ * @param mixed $callback The callback for generating the value, or false
+ * @param array $callbackParams The function parameters for the callback
+ * @param array $deps The dependencies to store on a cache miss. Note: these
+ * are not the dependencies used on a cache hit! Cache hits use the stored
+ * dependency array.
+ *
+ * @return mixed The value, or null if it was not present in the cache and no
+ * callback was defined.
+ */
+ static function getValueFromCache( $cache, $key, $expiry = 0, $callback = false,
+ $callbackParams = array(), $deps = array() )
+ {
+ $obj = $cache->get( $key );
+ if ( is_object( $obj ) && $obj instanceof DependencyWrapper && !$obj->isExpired() ) {
+ $value = $obj->value;
+ } elseif ( $callback ) {
+ $value = call_user_func_array( $callback, $callbackParams );
+ # Cache the newly-generated value
+ $wrapper = new DependencyWrapper( $value, $deps );
+ $wrapper->storeToCache( $cache, $key, $expiry );
+ } else {
+ $value = null;
+ }
+ return $value;
+ }
+}
+
+abstract class CacheDependency {
+ /**
+ * Returns true if the dependency is expired, false otherwise
+ */
+ abstract function isExpired();
+
+ /**
+ * Hook to perform any expensive pre-serialize loading of dependency values.
+ */
+ function loadDependencyValues() {}
+}
+
+class FileDependency extends CacheDependency {
+ var $filename, $timestamp;
+
+ /**
+ * Create a file dependency
+ *
+ * @param string $filename The name of the file, preferably fully qualified
+ * @param mixed $timestamp The unix last modified timestamp, or false if the
+ * file does not exist. If omitted, the timestamp will be loaded from
+ * the file.
+ *
+ * A dependency on a nonexistent file will be triggered when the file is
+ * created. A dependency on an existing file will be triggered when the
+ * file is changed.
+ */
+ function __construct( $filename, $timestamp = null ) {
+ $this->filename = $filename;
+ $this->timestamp = $timestamp;
+ }
+
+ function loadDependencyValues() {
+ if ( is_null( $this->timestamp ) ) {
+ if ( !file_exists( $this->filename ) ) {
+ # Dependency on a non-existent file
+ # This is a valid concept!
+ $this->timestamp = false;
+ } else {
+ $this->timestamp = filemtime( $this->filename );
+ }
+ }
+ }
+
+ function isExpired() {
+ if ( !file_exists( $this->filename ) ) {
+ if ( $this->timestamp === false ) {
+ # Still nonexistent
+ return false;
+ } else {
+ # Deleted
+ wfDebug( "Dependency triggered: {$this->filename} deleted.\n" );
+ return true;
+ }
+ } else {
+ $lastmod = filemtime( $this->filename );
+ if ( $lastmod > $this->timestamp ) {
+ # Modified or created
+ wfDebug( "Dependency triggered: {$this->filename} changed.\n" );
+ return true;
+ } else {
+ # Not modified
+ return false;
+ }
+ }
+ }
+}
+
+class TitleDependency extends CacheDependency {
+ var $titleObj;
+ var $ns, $dbk;
+ var $touched;
+
+ /**
+ * Construct a title dependency
+ * @param Title $title
+ */
+ function __construct( Title $title ) {
+ $this->titleObj = $title;
+ $this->ns = $title->getNamespace();
+ $this->dbk = $title->getDBkey();
+ }
+
+ function loadDependencyValues() {
+ $this->touched = $this->getTitle()->getTouched();
+ }
+
+ /**
+ * Get rid of bulky Title object for sleep
+ */
+ function __sleep() {
+ return array( 'ns', 'dbk', 'touched' );
+ }
+
+ function getTitle() {
+ if ( !isset( $this->titleObj ) ) {
+ $this->titleObj = Title::makeTitle( $this->ns, $this->dbk );
+ }
+ return $this->titleObj;
+ }
+
+ function isExpired() {
+ $touched = $this->getTitle()->getTouched();
+ if ( $this->touched === false ) {
+ if ( $touched === false ) {
+ # Still missing
+ return false;
+ } else {
+ # Created
+ return true;
+ }
+ } elseif ( $touched === false ) {
+ # Deleted
+ return true;
+ } elseif ( $touched > $this->touched ) {
+ # Updated
+ return true;
+ } else {
+ # Unmodified
+ return false;
+ }
+ }
+}
+
+class TitleListDependency extends CacheDependency {
+ var $linkBatch;
+ var $timestamps;
+
+ /**
+ * Construct a dependency on a list of titles
+ */
+ function __construct( LinkBatch $linkBatch ) {
+ $this->linkBatch = $linkBatch;
+ }
+
+ function calculateTimestamps() {
+ # Initialise values to false
+ $timestamps = array();
+ foreach ( $this->getLinkBatch()->data as $ns => $dbks ) {
+ if ( count( $dbks ) > 0 ) {
+ $timestamps[$ns] = array();
+ foreach ( $dbks as $dbk => $value ) {
+ $timestamps[$ns][$dbk] = false;
+ }
+ }
+ }
+
+ # Do the query
+ if ( count( $timestamps ) ) {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $where = $this->getLinkBatch()->constructSet( 'page', $dbr );
+ $res = $dbr->select( 'page',
+ array( 'page_namespace', 'page_title', 'page_touched' ),
+ $where, __METHOD__ );
+ while ( $row = $dbr->fetchObject( $res ) ) {
+ $timestamps[$row->page_namespace][$row->page_title] = $row->page_touched;
+ }
+ }
+ return $timestamps;
+ }
+
+ function loadDependencyValues() {
+ $this->timestamps = $this->calculateTimestamps();
+ }
+
+ function __sleep() {
+ return array( 'timestamps' );
+ }
+
+ function getLinkBatch() {
+ if ( !isset( $this->linkBatch ) ){
+ $this->linkBatch = new LinkBatch;
+ $this->linkBatch->setArray( $this->timestamps );
+ }
+ return $this->linkBatch;
+ }
+
+ function isExpired() {
+ $newTimestamps = $this->calculateTimestamps();
+ foreach ( $this->timestamps as $ns => $dbks ) {
+ foreach ( $dbks as $dbk => $oldTimestamp ) {
+ $newTimestamp = $newTimestamps[$ns][$dbk];
+ if ( $oldTimestamp === false ) {
+ if ( $newTimestamp === false ) {
+ # Still missing
+ } else {
+ # Created
+ return true;
+ }
+ } elseif ( $newTimestamp === false ) {
+ # Deleted
+ return true;
+ } elseif ( $newTimestamp > $oldTimestamp ) {
+ # Updated
+ return true;
+ } else {
+ # Unmodified
+ }
+ }
+ }
+ return false;
+ }
+}
+
+class GlobalDependency extends CacheDependency {
+ var $name, $value;
+
+ function __construct( $name ) {
+ $this->name = $name;
+ $this->value = $GLOBALS[$name];
+ }
+
+ function isExpired() {
+ return $GLOBALS[$this->name] != $this->value;
+ }
+}
+
+class ConstantDependency extends CacheDependency {
+ var $name, $value;
+
+ function __construct( $name ) {
+ $this->name = $name;
+ $this->value = constant( $name );
+ }
+
+ function isExpired() {
+ return constant( $this->name ) != $this->value;
+ }
+}
+
+?>
diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php
index e55d2976..0086a2f9 100644
--- a/includes/CategoryPage.php
+++ b/includes/CategoryPage.php
@@ -236,24 +236,31 @@ class CategoryViewer {
$r = '';
if( count( $this->children ) > 0 ) {
# Showing subcategories
+ $r .= "<div id=\"mw-subcategories\">\n";
$r .= '<h2>' . wfMsg( 'subcategories' ) . "</h2>\n";
$r .= wfMsgExt( 'subcategorycount', array( 'parse' ), count( $this->children) );
$r .= $this->formatList( $this->children, $this->children_start_char );
+ $r .= "\n</div>";
}
return $r;
}
function getPagesSection() {
$ti = htmlspecialchars( $this->title->getText() );
- $r = '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n";
+ $r = "<div id=\"mw-pages\">\n";
+ $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 );
+ $r .= "\n</div>";
return $r;
}
function getImageSection() {
if( $this->showGallery && ! $this->gallery->isEmpty() ) {
- return $this->gallery->toHTML();
+ return "<div id=\"mw-category-media\">\n" .
+ '<h2>' . wfMsg( 'category-media-header', htmlspecialchars($this->title->getText()) ) . "</h2>\n" .
+ wfMsgExt( 'category-media-count', array( 'parse' ), $this->gallery->count() ) .
+ $this->gallery->toHTML() . "\n</div>";
} else {
return '';
}
diff --git a/includes/ChangesList.php b/includes/ChangesList.php
index 6797bb41..a2c1a265 100644
--- a/includes/ChangesList.php
+++ b/includes/ChangesList.php
@@ -46,10 +46,10 @@ class ChangesList {
* @param $user User to fetch the list class for
* @return ChangesList derivative
*/
- function newFromUser( &$user ) {
+ public static function newFromUser( &$user ) {
$sk =& $user->getSkin();
$list = NULL;
- if( wfRunHooks( 'FetchChangesList', array( &$user, &$skin, &$list ) ) ) {
+ if( wfRunHooks( 'FetchChangesList', array( &$user, &$sk, &$list ) ) ) {
return $user->getOption( 'usenewrc' ) ? new EnhancedChangesList( $sk ) : new OldChangesList( $sk );
} else {
return $list;
@@ -184,7 +184,7 @@ class ChangesList {
$s .= ' '.$articlelink;
}
- function insertTimestamp(&$s, &$rc) {
+ function insertTimestamp(&$s, $rc) {
global $wgLang;
# Timestamp
$s .= '; ' . $wgLang->time( $rc->mAttribs['rc_timestamp'], true, true ) . ' . . ';
@@ -212,8 +212,6 @@ class ChangesList {
global $wgUseRCPatrol, $wgUser;
return( $wgUseRCPatrol && $wgUser->isAllowed( 'patrol' ) );
}
-
-
}
@@ -225,15 +223,13 @@ class OldChangesList extends ChangesList {
* Format a line using the old system (aka without any javascript).
*/
function recentChangesLine( &$rc, $watched = false ) {
- global $wgContLang;
+ global $wgContLang, $wgRCShowChangedSize;
$fname = 'ChangesList::recentChangesLineOld';
wfProfileIn( $fname );
-
# Extract DB fields into local scope
extract( $rc->mAttribs );
- $curIdEq = 'curid=' . $rc_cur_id;
# Should patrol-related stuff be shown?
$unpatrolled = $this->usePatrol() && $rc_patrolled == 0;
@@ -246,8 +242,13 @@ class OldChangesList extends ChangesList {
if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
$this->insertMove( $s, $rc );
// log entries
- } elseif( $rc_namespace == NS_SPECIAL && preg_match( '!^Log/(.*)$!', $rc_title, $matches ) ) {
- $this->insertLog($s, $rc->getTitle(), $matches[1]);
+ } elseif ( $rc_namespace == NS_SPECIAL ) {
+ list( $specialName, $specialSubpage ) = SpecialPage::resolveAliasWithSubpage( $rc_title );
+ if ( $specialName == 'Log' ) {
+ $this->insertLog( $s, $rc->getTitle(), $specialSubpage );
+ } else {
+ wfDebug( "Unexpected special page in recentchanges\n" );
+ }
// all other stuff
} else {
wfProfileIn($fname.'-page');
@@ -264,6 +265,11 @@ class OldChangesList extends ChangesList {
wfProfileIn( $fname.'-rest' );
$this->insertTimestamp($s,$rc);
+
+ if( $wgRCShowChangedSize ) {
+ $s .= ( $rc->getCharacterDifference() == '' ? '' : $rc->getCharacterDifference() . ' . . ' );
+ }
+
$this->insertUserRelatedLinks($s,$rc);
$this->insertComment($s, $rc);
@@ -321,11 +327,16 @@ class EnhancedChangesList extends ChangesList {
$msg = ( $rc_type == RC_MOVE ) ? "1movedto2" : "1movedto2_redir";
$clink = wfMsg( $msg, $this->skin->makeKnownLinkObj( $rc->getTitle(), '', 'redirect=no' ),
$this->skin->makeKnownLinkObj( $rc->getMovedToTitle(), '' ) );
- } elseif( $rc_namespace == NS_SPECIAL && preg_match( '!^Log/(.*)$!', $rc_title, $matches ) ) {
- # Log updates, etc
- $logtype = $matches[1];
- $logname = LogPage::logName( $logtype );
- $clink = '(' . $this->skin->makeKnownLinkObj( $rc->getTitle(), $logname ) . ')';
+ } elseif( $rc_namespace == NS_SPECIAL ) {
+ list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $rc_title );
+ if ( $specialName == 'Log' ) {
+ # Log updates, etc
+ $logname = LogPage::logName( $logtype );
+ $clink = '(' . $this->skin->makeKnownLinkObj( $rc->getTitle(), $logname ) . ')';
+ } else {
+ wfDebug( "Unexpected special page in recentchanges\n" );
+ $clink = '';
+ }
} elseif( $rc->unpatrolled && $rc_type == RC_NEW ) {
# Unpatrolled new page, give rc_id in query
$clink = $this->skin->makeKnownLinkObj( $rc->getTitle(), '', "rcid={$rc_id}" );
@@ -394,7 +405,7 @@ class EnhancedChangesList extends ChangesList {
* Enhanced RC group
*/
function recentChangesBlockGroup( $block ) {
- global $wgContLang;
+ global $wgContLang, $wgRCShowChangedSize;
$r = '';
# Collate list of users
@@ -403,7 +414,6 @@ class EnhancedChangesList extends ChangesList {
$userlinks = array();
foreach( $block as $rcObj ) {
$oldid = $rcObj->mAttribs['rc_last_oldid'];
- $newid = $rcObj->mAttribs['rc_this_oldid'];
if( $rcObj->mAttribs['rc_new'] ) {
$isnew = true;
}
@@ -447,8 +457,7 @@ class EnhancedChangesList extends ChangesList {
$r .= $this->recentChangesFlags( $isnew, false, $unpatrolled, '&nbsp;', $bot );
# Timestamp
- $r .= ' '.$block[0]->timestamp.' ';
- $r .= '</tt>';
+ $r .= ' '.$block[0]->timestamp.' </tt>';
# Article link
$r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
@@ -459,13 +468,23 @@ class EnhancedChangesList extends ChangesList {
if( $block[0]->mAttribs['rc_type'] != RC_LOG ) {
# Changes
$r .= ' ('.count($block).' ';
+
if( $isnew ) {
$r .= $this->message['changes'];
} else {
$r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(),
$this->message['changes'], $curIdEq."&diff=$currentRevision&oldid=$oldid" );
}
- $r .= '; ';
+
+ # Character difference
+ $chardiff = $rcObj->getCharacterDifference( $block[ count( $block ) - 1 ]->mAttribs['rc_old_len'],
+ $block[0]->mAttribs['rc_new_len'] );
+ if( $chardiff == '' ) {
+ $r .= '; ';
+ } else {
+ $r .= '; ' . $chardiff . ' ';
+ }
+
# History
$r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(),
@@ -508,7 +527,14 @@ class EnhancedChangesList extends ChangesList {
$r .= $rcObj->curlink;
$r .= '; ';
$r .= $rcObj->lastlink;
- $r .= ') . . '.$rcObj->userlink;
+ $r .= ') . . ';
+
+ # Character diff
+ if( $wgRCShowChangedSize ) {
+ $r .= ( $rcObj->getCharacterDifference() == '' ? '' : $rcObj->getCharacterDifference() . ' . . ' ) ;
+ }
+
+ $r .= $rcObj->userlink;
$r .= $rcObj->usertalklink;
$r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() );
$r .= "<br />\n";
@@ -578,7 +604,7 @@ class EnhancedChangesList extends ChangesList {
* @return string a HTML formated line (generated using $r)
*/
function recentChangesBlockLine( $rcObj ) {
- global $wgContLang;
+ global $wgContLang, $wgRCShowChangedSize;
# Get rc_xxxx variables
extract( $rcObj->mAttribs );
@@ -606,10 +632,15 @@ class EnhancedChangesList extends ChangesList {
$r .= ' ('. $rcObj->difflink .'; ';
# Hist
- $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), wfMsg( 'hist' ), $curIdEq.'&action=history' );
+ $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), wfMsg( 'hist' ), $curIdEq.'&action=history' ) . ') . . ';
+
+ # Character diff
+ if( $wgRCShowChangedSize ) {
+ $r .= ( $rcObj->getCharacterDifference() == '' ? '' : '&nbsp;' . $rcObj->getCharacterDifference() . ' . . ' ) ;
+ }
# User/talk
- $r .= ') . . '.$rcObj->userlink . $rcObj->usertalklink;
+ $r .= $rcObj->userlink . $rcObj->usertalklink;
# Comment
if( $rc_type != RC_MOVE && $rc_type != RC_MOVE_OVER_REDIRECT ) {
@@ -633,7 +664,7 @@ class EnhancedChangesList extends ChangesList {
return '';
}
$blockOut = '';
- foreach( $this->rc_cache as $secureName => $block ) {
+ foreach( $this->rc_cache as $block ) {
if( count( $block ) < 2 ) {
$blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
} else {
diff --git a/includes/CoreParserFunctions.php b/includes/CoreParserFunctions.php
index 2081b3f2..402a3ba9 100644
--- a/includes/CoreParserFunctions.php
+++ b/includes/CoreParserFunctions.php
@@ -65,7 +65,6 @@ class CoreParserFunctions {
static function fullurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeFullURL', $s, $arg ); }
static function urlFunction( $func, $s = '', $arg = null ) {
- $found = false;
$title = Title::newFromText( $s );
# Due to order of execution of a lot of bits, the values might be encoded
# before arriving here; if that's true, then the title can't be created
@@ -79,28 +78,26 @@ class CoreParserFunctions {
} else {
$text = $title->$func();
}
- $found = true;
- }
- if ( $found ) {
return $text;
} else {
return array( 'found' => false );
}
}
- function formatNum( $parser, $num = '' ) {
+ static function formatNum( $parser, $num = '' ) {
return $parser->getFunctionLang()->formatNum( $num );
}
- function grammar( $parser, $case = '', $word = '' ) {
+ static function grammar( $parser, $case = '', $word = '' ) {
return $parser->getFunctionLang()->convertGrammar( $word, $case );
}
- function plural( $parser, $text = '', $arg0 = null, $arg1 = null, $arg2 = null, $arg3 = null, $arg4 = null ) {
+ static function plural( $parser, $text = '', $arg0 = null, $arg1 = null, $arg2 = null, $arg3 = null, $arg4 = null ) {
+ $text = $parser->getFunctionLang()->parseFormattedNumber( $text );
return $parser->getFunctionLang()->convertPlural( $text, $arg0, $arg1, $arg2, $arg3, $arg4 );
}
- function displaytitle( $parser, $param = '' ) {
+ static function displaytitle( $parser, $param = '' ) {
$parserOptions = new ParserOptions;
$local_parser = clone $parser;
$t2 = $local_parser->parse ( $param, $parser->mTitle, $parserOptions, false );
@@ -112,7 +109,7 @@ class CoreParserFunctions {
return '';
}
- function isRaw( $param ) {
+ static function isRaw( $param ) {
static $mwRaw;
if ( !$mwRaw ) {
$mwRaw =& MagicWord::get( 'rawsuffix' );
@@ -124,23 +121,23 @@ class CoreParserFunctions {
}
}
- function statisticsFunction( $func, $raw = null ) {
+ static function statisticsFunction( $func, $raw = null ) {
if ( self::isRaw( $raw ) ) {
- return call_user_func( $func );
+ return call_user_func( array( 'SiteStats', $func ) );
} else {
global $wgContLang;
- return $wgContLang->formatNum( call_user_func( $func ) );
+ return $wgContLang->formatNum( call_user_func( array( 'SiteStats', $func ) ) );
}
}
- function numberofpages( $parser, $raw = null ) { return self::statisticsFunction( 'wfNumberOfPages', $raw ); }
- function numberofusers( $parser, $raw = null ) { return self::statisticsFunction( 'wfNumberOfUsers', $raw ); }
- function numberofarticles( $parser, $raw = null ) { return self::statisticsFunction( 'wfNumberOfArticles', $raw ); }
- function numberoffiles( $parser, $raw = null ) { return self::statisticsFunction( 'wfNumberOfFiles', $raw ); }
- function numberofadmins( $parser, $raw = null ) { return self::statisticsFunction( 'wfNumberOfAdmins', $raw ); }
+ static function numberofpages( $parser, $raw = null ) { return self::statisticsFunction( 'pages', $raw ); }
+ static function numberofusers( $parser, $raw = null ) { return self::statisticsFunction( 'users', $raw ); }
+ static function numberofarticles( $parser, $raw = null ) { return self::statisticsFunction( 'articles', $raw ); }
+ static function numberoffiles( $parser, $raw = null ) { return self::statisticsFunction( 'images', $raw ); }
+ static function numberofadmins( $parser, $raw = null ) { return self::statisticsFunction( 'admins', $raw ); }
- function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
- $count = wfPagesInNs( intval( $namespace ) );
+ static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
+ $count = SiteStats::pagesInNs( intval( $namespace ) );
if ( self::isRaw( $raw ) ) {
global $wgContLang;
return $wgContLang->formatNum( $count );
@@ -149,13 +146,13 @@ class CoreParserFunctions {
}
}
- function language( $parser, $arg = '' ) {
+ static function language( $parser, $arg = '' ) {
global $wgContLang;
$lang = $wgContLang->getLanguageName( strtolower( $arg ) );
return $lang != '' ? $lang : $arg;
}
- function pad( $string = '', $length = 0, $char = 0, $direction = STR_PAD_RIGHT ) {
+ static 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 )
@@ -163,16 +160,32 @@ class CoreParserFunctions {
: $string;
}
- function padleft( $parser, $string = '', $length = 0, $char = 0 ) {
+ static 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 ) {
+ static 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 ) ) );
+ static function anchorencode( $parser, $text ) {
+ return strtr( urlencode( $text ) , array( '%' => '.' , '+' => '_' ) );
+ }
+
+ static function special( $parser, $text ) {
+ $title = SpecialPage::getTitleForAlias( $text );
+ if ( $title ) {
+ return $title->getPrefixedText();
+ } else {
+ return wfMsgForContent( 'nosuchspecialpage' );
+ }
+ }
+
+ public static function defaultsort( $parser, $text ) {
+ $text = trim( $text );
+ if( strlen( $text ) > 0 )
+ $parser->setDefaultSort( $text );
+ return '';
}
}
diff --git a/includes/Database.php b/includes/Database.php
index 53e59968..eb1ee135 100644
--- a/includes/Database.php
+++ b/includes/Database.php
@@ -90,8 +90,8 @@ class DBConnectionError extends DBError {
}
function getHTML() {
- global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding, $wgOutputEncoding;
- global $wgSitename, $wgServer, $wgMessageCache, $wgLogo;
+ global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding;
+ global $wgSitename, $wgServer, $wgMessageCache;
# I give up, Brion is right. Getting the message cache to work when there is no DB is tricky.
# Hard coding strings instead.
@@ -152,7 +152,7 @@ border=\"0\" ALT=\"Google\"></A>
}
}
- $cache = new CacheManager( $t );
+ $cache = new HTMLFileCache( $t );
if( $cache->isFileCached() ) {
$msg = '<p style="color: red"><b>'.$msg."<br />\n" .
$cachederror . "</b></p>\n";
@@ -295,7 +295,7 @@ class Database {
* Turns on (false) or off (true) the automatic generation and sending
* of a "we're sorry, but there has been a database error" page on
* database errors. Default is on (false). When turned off, the
- * code should use wfLastErrno() and wfLastError() to handle the
+ * code should use lastErrno() and lastError() to handle the
* situation as appropriate.
*/
function ignoreErrors( $ignoreErrors = NULL ) {
@@ -362,6 +362,20 @@ class Database {
return $this->mStrictIPs;
}
+ /**
+ * Returns true if this database uses timestamps rather than integers
+ */
+ function realTimestamps() {
+ return false;
+ }
+
+ /**
+ * Returns true if this database does an implicit sort when doing GROUP BY
+ */
+ function implicitGroupby() {
+ return true;
+ }
+
/**#@+
* Get function
*/
@@ -613,7 +627,7 @@ class Database {
# Add a comment for easy SHOW PROCESSLIST interpretation
if ( $fname ) {
- $commentedSql = preg_replace("/\s/", " /* $fname */ ", $sql, 1);
+ $commentedSql = preg_replace('/\s/', " /* $fname */ ", $sql, 1);
} else {
$commentedSql = $sql;
}
@@ -679,7 +693,7 @@ class Database {
* @param bool $tempIgnore
*/
function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- global $wgCommandLineMode, $wgFullyInitialised, $wgColorErrors;
+ global $wgCommandLineMode;
# Ignore errors during error handling to avoid infinite recursion
$ignore = $this->ignoreErrors( true );
++$this->mErrorCount;
@@ -778,7 +792,7 @@ class Database {
case '\\!': return '!';
case '\\&': return '&';
}
- list( $n, $arg ) = each( $this->preparedArgs );
+ list( /* $n */ , $arg ) = each( $this->preparedArgs );
switch( $matches[1] ) {
case '?': return $this->addQuotes( $arg );
case '!': return $arg;
@@ -981,6 +995,7 @@ class Database {
if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
# Various MySQL extensions
+ if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */';
if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY';
if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT';
if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT';
@@ -1000,6 +1015,14 @@ class Database {
/**
* SELECT wrapper
+ *
+ * @param mixed $table Array or string, table name(s) (prefix auto-added)
+ * @param mixed $vars Array or string, field name(s) to be retrieved
+ * @param mixed $conds Array or string, condition(s) for WHERE
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * see Database::makeSelectOptions code for list of supported stuff
+ * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
*/
function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array() )
{
@@ -1010,7 +1033,7 @@ class Database {
$options = array( $options );
}
if( is_array( $table ) ) {
- if ( @is_array( $options['USE INDEX'] ) )
+ if ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
$from = ' FROM ' . $this->tableNamesWithUseIndex( $table, $options['USE INDEX'] );
else
$from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
@@ -1082,7 +1105,7 @@ class Database {
$sql = preg_replace ('/".*"/s', "'X'", $sql);
# All newlines, tabs, etc replaced by single space
- $sql = preg_replace ( "/\s+/", ' ', $sql);
+ $sql = preg_replace ( '/\s+/', ' ', $sql);
# All numbers => N
$sql = preg_replace ('/-?[0-9]+/s', 'N', $sql);
@@ -1143,12 +1166,15 @@ class Database {
return NULL;
}
+ $result = array();
while ( $row = $this->fetchObject( $res ) ) {
if ( $row->Key_name == $index ) {
- return $row;
+ $result[] = $row;
}
}
- return false;
+ $this->freeResult($res);
+
+ return empty($result) ? false : $result;
}
/**
@@ -1202,7 +1228,7 @@ class Database {
if ( !$indexInfo ) {
return NULL;
}
- return !$indexInfo->Non_unique;
+ return !$indexInfo[0]->Non_unique;
}
/**
@@ -1292,7 +1318,7 @@ class Database {
}
/**
- * Makes a wfStrencoded list from an array
+ * Makes an encoded list of strings from an array
* $mode:
* LIST_COMMA - comma separated, no field names
* LIST_AND - ANDed WHERE clause (without the WHERE)
@@ -1321,6 +1347,8 @@ class Database {
}
if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
$list .= "($value)";
+ } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
+ $list .= "$value";
} elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array ($value) ) {
$list .= $field." IN (".$this->makeList($value).") ";
} else {
@@ -1379,7 +1407,7 @@ class Database {
* $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
* WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
*/
- function tableNames() {
+ public function tableNames() {
$inArray = func_get_args();
$retVal = array();
foreach ( $inArray as $name ) {
@@ -1387,6 +1415,24 @@ class Database {
}
return $retVal;
}
+
+ /**
+ * @desc: Fetch a number of table names into an zero-indexed numerical array
+ * This is handy when you need to construct SQL for joins
+ *
+ * Example:
+ * list( $user, $watchlist ) = $dbr->tableNames('user','watchlist');
+ * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
+ * WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
+ */
+ public function tableNamesN() {
+ $inArray = func_get_args();
+ $retVal = array();
+ foreach ( $inArray as $name ) {
+ $retVal[] = $this->tableName( $name );
+ }
+ return $retVal;
+ }
/**
* @private
@@ -1528,7 +1574,8 @@ class Database {
$row = $this->fetchObject( $res );
$this->freeResult( $res );
- if ( preg_match( "/\((.*)\)/", $row->Type, $m ) ) {
+ $m = array();
+ if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
$size = $m[1];
} else {
$size = -1;
@@ -1829,7 +1876,7 @@ class Database {
* @return string Version information from the database
*/
function getServerVersion() {
- return mysql_get_server_info();
+ return mysql_get_server_info( $this->mConn );
}
/**
@@ -1852,7 +1899,6 @@ class Database {
$res = $this->query( 'SHOW PROCESSLIST' );
# Find slave SQL thread. Assumed to be the second one running, which is a bit
# dubious, but unfortunately there's no easy rigorous way
- $slaveThreads = 0;
while ( $row = $this->fetchObject( $res ) ) {
/* This should work for most situations - when default db
* for thread is not specified, it had no events executed,
diff --git a/includes/DatabaseFunctions.php b/includes/DatabaseFunctions.php
index 74b35a31..ca83b9e5 100644
--- a/includes/DatabaseFunctions.php
+++ b/includes/DatabaseFunctions.php
@@ -1,8 +1,7 @@
<?php
/**
- * Backwards compatibility wrapper for Database.php
- *
- * Note: $wgDatabase has ceased to exist. Destroy all references.
+ * Legacy database functions, for compatibility with pre-1.3 code
+ * NOTE: this file is no longer loaded by default.
*
* @package MediaWiki
*/
@@ -15,7 +14,6 @@
* @param $fname String: name of the php function calling
*/
function wfQuery( $sql, $db, $fname = '' ) {
- global $wgOut;
if ( !is_numeric( $db ) ) {
# Someone has tried to call this the old way
throw new FatalError( wfMsgNoDB( 'wrong_wfQuery_params', $db, $sql ) );
@@ -44,15 +42,6 @@ function wfSingleQuery( $sql, $dbi, $fname = '' ) {
return $ret;
}
-/*
- * @todo document function
- */
-function &wfGetDB( $db = DB_LAST, $groups = array() ) {
- global $wgLoadBalancer;
- $ret =& $wgLoadBalancer->getConnection( $db, true, $groups );
- return $ret;
-}
-
/**
* Turns on (false) or off (true) the automatic generation and sending
* of a "we're sorry, but there has been a database error" page on
diff --git a/includes/DatabaseOracle.php b/includes/DatabaseOracle.php
index aa1e329e..1a6f62f2 100644
--- a/includes/DatabaseOracle.php
+++ b/includes/DatabaseOracle.php
@@ -55,9 +55,6 @@ class DatabaseOracle extends Database {
$this->mPassword = $password;
$this->mDBname = $dbName;
- $success = false;
-
- $hstring="";
$this->mConn = oci_new_connect($user, $password, $dbName, "AL32UTF8");
if ( $this->mConn === false ) {
wfDebug( "DB connection error\n" );
@@ -147,7 +144,6 @@ class DatabaseOracle extends Database {
for ($i = 1; $i <= $this->mNcols[$res]; $i++) {
$name = $this->mFieldNames[$res][$i];
- $type = $this->mFieldTypes[$res][$i];
if (isset($this->mFetchCache[$res][$this->mFetchID[$res]][$name]))
$value = $this->mFetchCache[$res][$this->mFetchID[$res]][$name];
else $value = NULL;
@@ -165,7 +161,7 @@ class DatabaseOracle extends Database {
return false;
$i = 0;
$ret = array();
- foreach ($r as $key => $value) {
+ foreach ($r as $value) {
wfdebug("ret[$i]=[$value]\n");
$ret[$i++] = $value;
}
@@ -201,14 +197,19 @@ class DatabaseOracle extends Database {
function lastError() {
if ($this->mErr === false) {
- if ($this->mLastResult !== false) $what = $this->mLastResult;
- else if ($this->mConn !== false) $what = $this->mConn;
- else $what = false;
+ if ($this->mLastResult !== false) {
+ $what = $this->mLastResult;
+ } else if ($this->mConn !== false) {
+ $what = $this->mConn;
+ } else {
+ $what = false;
+ }
$err = ($what !== false) ? oci_error($what) : oci_error();
- if ($err === false)
+ if ($err === false) {
$this->mErr = 'no error';
- else
+ } else {
$this->mErr = $err['message'];
+ }
}
return str_replace("\n", '<br />', $this->mErr);
}
@@ -239,6 +240,9 @@ class DatabaseOracle extends Database {
$this->freeResult($res);
$row->Non_unique = !$row->uniqueness;
return $row;
+
+ // BUG: !!!! This code needs to be synced up with database.php
+
}
function indexUnique ($table, $index, $fname = 'indexUnique') {
diff --git a/includes/DatabasePostgres.php b/includes/DatabasePostgres.php
index a5e02e77..803c0e26 100644
--- a/includes/DatabasePostgres.php
+++ b/includes/DatabasePostgres.php
@@ -1,7 +1,7 @@
<?php
/**
- * This is Postgres database abstraction layer.
+ * This is the Postgres database abstraction layer.
*
* As it includes more generic version for DB functions,
* than MySQL ones, some of them should be moved to parent
@@ -18,7 +18,7 @@ class DatabasePostgres extends Database {
$failFunction = false, $flags = 0 )
{
- global $wgOut, $wgDBprefix, $wgCommandLineMode;
+ global $wgOut;
# Can't get a reference if it hasn't been set yet
if ( !isset( $wgOut ) ) {
$wgOut = NULL;
@@ -33,6 +33,14 @@ class DatabasePostgres extends Database {
}
+ function realTimestamps() {
+ return true;
+ }
+
+ function implicitGroupby() {
+ return false;
+ }
+
static function newFromParams( $server = false, $user = false, $password = false, $dbName = false,
$failFunction = false, $flags = 0)
{
@@ -59,7 +67,6 @@ class DatabasePostgres extends Database {
$this->mPassword = $password;
$this->mDBname = $dbName;
- $success = false;
$hstring="";
if ($server!=false && $server!="") {
$hstring="host=$server ";
@@ -85,13 +92,14 @@ class DatabasePostgres extends Database {
$this->mOpened = true;
## 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;
+ global $wgDBname, $wgDBuser, $wgDBpassword, $wgDBsuperuser, $wgDBmwschema,
+ $wgDBts2schema;
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)) {
+ $thisver = array();
+ if (!preg_match('/PostgreSQL (\d+\.\d+)(\S+)/', $version, $thisver)) {
print "<b>FAILED</b> (could not determine the version)</li>\n";
dieout("</ul>");
}
@@ -131,7 +139,7 @@ class DatabasePostgres extends Database {
dieout('</ul>');
}
print "<li>Creating user <b>$wgDBuser</b>...";
- $safepass = $this->addQuotes($wgDBpass);
+ $safepass = $this->addQuotes($wgDBpassword);
$SQL = "CREATE USER $safeuser NOCREATEDB PASSWORD $safepass";
$this->doQuery($SQL);
print "OK</li>\n";
@@ -460,6 +468,9 @@ class DatabasePostgres extends Database {
while ( $row = $this->fetchObject( $res ) ) {
if ( $row->indexname == $index ) {
return $row;
+
+ // BUG: !!!! This code needs to be synced up with database.php
+
}
}
return false;
@@ -651,9 +662,8 @@ class DatabasePostgres extends Database {
return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
}
- # FIXME: actually detecting deadlocks might be nice
function wasDeadlock() {
- return false;
+ return $this->lastErrno() == '40P01';
}
function timestamp( $ts=0 ) {
@@ -669,11 +679,21 @@ class DatabasePostgres extends Database {
function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- $message = "A database error has occurred\n" .
- "Query: $sql\n" .
- "Function: $fname\n" .
- "Error: $errno $error\n";
- throw new DBUnexpectedError($this, $message);
+ # Ignore errors during error handling to avoid infinite recursion
+ $ignore = $this->ignoreErrors( true );
+ ++$this->mErrorCount;
+
+ if ($ignore || $tempIgnore) {
+ wfDebug("SQL ERROR (ignored): $error\n");
+ $this->ignoreErrors( $ignore );
+ }
+ else {
+ $message = "A database error has occurred\n" .
+ "Query: $sql\n" .
+ "Function: $fname\n" .
+ "Error: $errno $error\n";
+ throw new DBUnexpectedError($this, $message);
+ }
}
/**
@@ -800,10 +820,10 @@ class DatabasePostgres extends Database {
$SQL = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
while ( ! feof( $f ) ) {
$line = fgets($f,1024);
- if (!preg_match("/^\s*(\(.+?),(\d)\)/", $line, $matches)) {
+ $matches = array();
+ if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) {
continue;
}
- $yesno = $matches[2]; ## ? "'true'" : "'false'";
$this->query("$SQL $matches[1],$matches[2])");
}
print " (table interwiki successfully populated)...\n";
@@ -827,13 +847,66 @@ class DatabasePostgres extends Database {
return "E'$s[1]'";
}
return "'" . pg_escape_string($s) . "'";
- return "E'" . pg_escape_string($s) . "'";
+ // Unreachable: return "E'" . pg_escape_string($s) . "'";
}
function quote_ident( $s ) {
return '"' . preg_replace( '/"/', '""', $s) . '"';
}
-}
+ /* For now, does nothing */
+ function selectDB( $db ) {
+ return true;
+ }
+
+ /**
+ * Returns an optional USE INDEX clause to go after the table, and a
+ * string to go at the end of the query
+ *
+ * @private
+ *
+ * @param array $options an associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
+ * @return array
+ */
+ function makeSelectOptions( $options ) {
+ $tailOpts = '';
+ $startOpts = '';
+
+ $noKeyOptions = array();
+ foreach ( $options as $key => $option ) {
+ if ( is_numeric( $key ) ) {
+ $noKeyOptions[$option] = true;
+ }
+ }
+
+ if ( isset( $options['GROUP BY'] ) ) $tailOpts .= " GROUP BY {$options['GROUP BY']}";
+ if ( isset( $options['ORDER BY'] ) ) $tailOpts .= " ORDER BY {$options['ORDER BY']}";
+
+ if (isset($options['LIMIT'])) {
+ $tailOpts .= $this->limitResult('', $options['LIMIT'],
+ isset($options['OFFSET']) ? $options['OFFSET'] : false);
+ }
+
+ if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE';
+ if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE';
+ if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
+
+ if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
+ $useIndex = $this->useIndexClause( $options['USE INDEX'] );
+ } else {
+ $useIndex = '';
+ }
+
+ return array( $startOpts, $useIndex, $tailOpts );
+ }
+
+ function ping() {
+ wfDebug( "Function ping() not written for DatabasePostgres.php yet");
+ return true;
+ }
+
+
+} // end DatabasePostgres class
?>
diff --git a/includes/DateFormatter.php b/includes/DateFormatter.php
index dc077fdc..c795618a 100644
--- a/includes/DateFormatter.php
+++ b/includes/DateFormatter.php
@@ -129,10 +129,10 @@ class DateFormatter
}
for ( $i=1; $i<=self::LAST; $i++ ) {
$this->mSource = $i;
- if ( @$this->rules[$preference][$i] ) {
+ if ( isset ( $this->rules[$preference][$i] ) ) {
# Specific rules
$this->mTarget = $this->rules[$preference][$i];
- } elseif ( @$this->rules[self::ALL][$i] ) {
+ } elseif ( isset ( $this->rules[self::ALL][$i] ) ) {
# General rules
$this->mTarget = $this->rules[self::ALL][$i];
} elseif ( $preference ) {
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index ee1ed3a0..03697b69 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -32,7 +32,7 @@ require_once( 'includes/SiteConfiguration.php' );
$wgConf = new SiteConfiguration;
/** MediaWiki version number */
-$wgVersion = '1.8.3';
+$wgVersion = '1.9.0';
/** Name of the site. It must be changed in LocalSettings.php */
$wgSitename = 'MediaWiki';
@@ -82,56 +82,84 @@ if( isset( $_SERVER['SERVER_PORT'] )
/**
* The path we should point to.
* It might be a virtual path in case with use apache mod_rewrite for example
+ *
+ * This *needs* to be set correctly.
+ *
+ * Other paths will be set to defaults based on it unless they are directly
+ * set in LocalSettings.php
*/
$wgScriptPath = '/wiki';
/**
* Whether to support URLs like index.php/Page_title
- * @global bool $wgUsePathInfo
+ * These often break when PHP is set up in CGI mode.
+ * PATH_INFO *may* be correct if cgi.fix_pathinfo is
+ * set, but then again it may not; lighttpd converts
+ * incoming path data to lowercase on systems with
+ * case-insensitive filesystems, and there have been
+ * reports of problems on Apache as well.
+ *
+ * To be safe we'll continue to keep it off by default.
+ *
+ * Override this to false if $_SERVER['PATH_INFO']
+ * contains unexpectedly incorrect garbage, or to
+ * true if it is really correct.
+ *
+ * The default $wgArticlePath will be set based on
+ * this value at runtime, but if you have customized
+ * it, having this incorrectly set to true can
+ * cause redirect loops when "pretty URLs" are used.
+ *
*/
-$wgUsePathInfo = ( strpos( php_sapi_name(), 'cgi' ) === false );
+$wgUsePathInfo =
+ ( strpos( php_sapi_name(), 'cgi' ) === false ) &&
+ ( strpos( php_sapi_name(), 'apache2filter' ) === false ) &&
+ ( strpos( php_sapi_name(), 'isapi' ) === false );
/**#@+
* Script users will request to get articles
* ATTN: Old installations used wiki.phtml and redirect.phtml -
* make sure that LocalSettings.php is correctly set!
- * @deprecated
- */
-/**
- * @global string $wgScript
- */
-$wgScript = "{$wgScriptPath}/index.php";
-/**
- * @global string $wgRedirectScript
+ *
+ * Will be set based on $wgScriptPath in Setup.php if not overridden
+ * in LocalSettings.php. Generally you should not need to change this
+ * unless you don't like seeing "index.php".
*/
-$wgRedirectScript = "{$wgScriptPath}/redirect.php";
+$wgScript = false; /// defaults to "{$wgScriptPath}/index.php"
+$wgRedirectScript = false; /// defaults to "{$wgScriptPath}/redirect.php"
/**#@-*/
/**#@+
+ * These various web and file path variables are set to their defaults
+ * in Setup.php if they are not explicitly set from LocalSettings.php.
+ * If you do override them, be sure to set them all!
+ *
+ * These will relatively rarely need to be set manually, unless you are
+ * splitting style sheets or images outside the main document root.
+ *
* @global string
*/
/**
* style path as seen by users
- * @global string $wgStylePath
*/
-$wgStylePath = "{$wgScriptPath}/skins";
+$wgStylePath = false; /// defaults to "{$wgScriptPath}/skins"
/**
* filesystem stylesheets directory
- * @global string $wgStyleDirectory
*/
-$wgStyleDirectory = "{$IP}/skins";
+$wgStyleDirectory = false; /// defaults to "{$IP}/skins"
$wgStyleSheetPath = &$wgStylePath;
-$wgArticlePath = "{$wgScript}?title=$1";
-$wgUploadPath = "{$wgScriptPath}/images";
-$wgUploadDirectory = "{$IP}/images";
+$wgArticlePath = false; /// default to "{$wgScript}/$1" or "{$wgScript}?title=$1", depending on $wgUsePathInfo
+$wgVariantArticlePath = false;
+$wgUploadPath = false; /// defaults to "{$wgScriptPath}/images"
+$wgUploadDirectory = false; /// defaults to "{$IP}/images"
$wgHashedUploadDirectory = true;
-$wgLogo = "{$wgUploadPath}/wiki.png";
+$wgLogo = false; /// defaults to "{$wgStylePath}/common/images/wiki.png"
$wgFavicon = '/favicon.ico';
-$wgMathPath = "{$wgUploadPath}/math";
-$wgMathDirectory = "{$wgUploadDirectory}/math";
-$wgTmpDirectory = "{$wgUploadDirectory}/tmp";
+$wgMathPath = false; /// defaults to "{$wgUploadPath}/math"
+$wgMathDirectory = false; /// defaults to "{$wgUploadDirectory}/math"
+$wgTmpDirectory = false; /// defaults to "{$wgUploadDirectory}/tmp"
$wgUploadBaseUrl = "";
/**#@-*/
@@ -290,20 +318,23 @@ $wgMimeInfoFile= "includes/mime.info";
#$wgMimeInfoFile= NULL; #use built-in defaults only.
/** Switch for loading the FileInfo extension by PECL at runtime.
-* This should be used only if fileinfo is installed as a shared object / dynamic libary
-* @global string $wgLoadFileinfoExtension
+ * This should be used only if fileinfo is installed as a shared object
+ * or a dynamic libary
+ * @global string $wgLoadFileinfoExtension
*/
$wgLoadFileinfoExtension= false;
-/** Sets an external mime detector program. The command must print only the mime type to standard output.
-* the name of the file to process will be appended to the command given here.
-* If not set or NULL, mime_content_type will be used if available.
+/** Sets an external mime detector program. The command must print only
+ * the mime type to standard output.
+ * The name of the file to process will be appended to the command given here.
+ * If not set or NULL, mime_content_type will be used if available.
*/
$wgMimeDetectorCommand= NULL; # use internal mime_content_type function, available since php 4.3.0
#$wgMimeDetectorCommand= "file -bi"; #use external mime detector (Linux)
-/** Switch for trivial mime detection. Used by thumb.php to disable all fance things,
-* because only a few types of images are needed and file extensions can be trusted.
+/** Switch for trivial mime detection. Used by thumb.php to disable all fance
+ * things, because only a few types of images are needed and file extensions
+ * can be trusted.
*/
$wgTrivialMimeDetection= false;
@@ -424,6 +455,12 @@ $wgEnableEmail = true;
$wgEnableUserEmail = true;
/**
+ * Minimum time, in hours, which must elapse between password reminder
+ * emails for a given account. This is to prevent abuse by mail flooding.
+ */
+$wgPasswordReminderResendTime = 24;
+
+/**
* SMTP Mode
* For using a direct (authenticated) SMTP server connection.
* Default to false or fill an array :
@@ -650,6 +687,15 @@ $wgMimeType = 'text/html';
$wgJsMimeType = 'text/javascript';
$wgDocType = '-//W3C//DTD XHTML 1.0 Transitional//EN';
$wgDTD = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd';
+$wgXhtmlDefaultNamespace = 'http://www.w3.org/1999/xhtml';
+
+# Permit other namespaces in addition to the w3.org default.
+# Use the prefix for the key and the namespace for the value. For
+# example:
+# $wgXhtmlNamespaces['svg'] = 'http://www.w3.org/2000/svg';
+# Normally we wouldn't have to define this in the root <html>
+# element, but IE needs it there in some circumstances.
+$wgXhtmlNamespaces = array();
/** Enable to allow rewriting dates in page text.
* DOES NOT FORMAT CORRECTLY FOR MOST LANGUAGES */
@@ -664,16 +710,29 @@ $wgAmericanDates = false;
*/
$wgTranslateNumerals = true;
-
-# Translation using MediaWiki: namespace
-# This will increase load times by 25-60% unless memcached is installed
-# Interface messages will be loaded from the database.
+/**
+ * Translation using MediaWiki: namespace.
+ * This will increase load times by 25-60% unless memcached is installed.
+ * Interface messages will be loaded from the database.
+ */
$wgUseDatabaseMessages = true;
+
+/**
+ * Expiry time for the message cache key
+ */
$wgMsgCacheExpiry = 86400;
+/**
+ * Maximum entry size in the message cache, in bytes
+ */
+$wgMaxMsgCacheEntrySize = 10000;
+
# Whether to enable language variant conversion.
$wgDisableLangConversion = false;
+# Default variant code, if false, the default will be the language code
+$wgDefaultLanguageVariant = false;
+
/**
* Show a bar of language selection links in the user login and user
* registration forms; edit the "loginlanguagelinks" message to
@@ -745,7 +804,12 @@ $wgMaxArticleSize = 2048; # Maximum article size in kilobytes
$wgExtraSubtitle = '';
$wgSiteSupportPage = ''; # A page where you users can receive donations
-$wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR";
+/***
+ * If this lock file exists, the wiki will be forced into read-only mode.
+ * Its contents will be shown to users as part of the read-only warning
+ * message.
+ */
+$wgReadOnlyFile = false; /// defaults to "{$wgUploadDirectory}/lock_yBgMBwiR";
/**
* The debug log file should be not be publicly accessible if it is used, as it
@@ -912,6 +976,7 @@ $wgGroupPermissions['emailconfirmed']['emailconfirmed'] = true;
// from various log pages by default
$wgGroupPermissions['bot' ]['bot'] = true;
$wgGroupPermissions['bot' ]['autoconfirmed'] = true;
+$wgGroupPermissions['bot' ]['nominornewtalk'] = true;
// Most extra permission abilities go to this group
$wgGroupPermissions['sysop']['block'] = true;
@@ -923,6 +988,7 @@ $wgGroupPermissions['sysop']['import'] = true;
$wgGroupPermissions['sysop']['importupload'] = true;
$wgGroupPermissions['sysop']['move'] = true;
$wgGroupPermissions['sysop']['patrol'] = true;
+$wgGroupPermissions['sysop']['autopatrol'] = true;
$wgGroupPermissions['sysop']['protect'] = true;
$wgGroupPermissions['sysop']['proxyunbannable'] = true;
$wgGroupPermissions['sysop']['rollback'] = true;
@@ -933,6 +999,7 @@ $wgGroupPermissions['sysop']['reupload-shared'] = true;
$wgGroupPermissions['sysop']['unwatchedpages'] = true;
$wgGroupPermissions['sysop']['autoconfirmed'] = true;
$wgGroupPermissions['sysop']['upload_by_url'] = true;
+$wgGroupPermissions['sysop']['ipblock-exempt'] = true;
// Permission to change users' group assignments
$wgGroupPermissions['bureaucrat']['userrights'] = true;
@@ -950,14 +1017,14 @@ $wgGroupPermissions['bureaucrat']['userrights'] = true;
# $wgGroupPermissions['developer']['siteadmin'] = true;
/**
- * Set of available actions that can be restricted via Special:Protect
+ * Set of available actions that can be restricted via action=protect
* You probably shouldn't change this.
* Translated trough restriction-* messages.
*/
$wgRestrictionTypes = array( 'edit', 'move' );
/**
- * Set of permission keys that can be selected via Special:Protect.
+ * Set of permission keys that can be selected via action=protect.
* 'autoconfirm' allows all registerd users if $wgAutoConfirmAge is 0.
*/
$wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' );
@@ -996,7 +1063,7 @@ $wgBlockOpenProxies = false;
/** Port we want to scan for a proxy */
$wgProxyPorts = array( 80, 81, 1080, 3128, 6588, 8000, 8080, 8888, 65506 );
/** Script used to scan */
-$wgProxyScriptPath = "$IP/proxy_check.php";
+$wgProxyScriptPath = "$IP/includes/proxy_check.php";
/** */
$wgProxyMemcExpiry = 86400;
/** This should always be customised in LocalSettings.php */
@@ -1023,6 +1090,14 @@ $wgCachePages = true;
*/
$wgCacheEpoch = '20030516000000';
+/**
+ * Bump this number when changing the global style sheets and JavaScript.
+ * It should be appended in the query string of static CSS and JS includes,
+ * to ensure that client-side caches don't keep obsolete copies of global
+ * styles.
+ */
+$wgStyleVersion = '42';
+
# Server-side caching:
@@ -1032,8 +1107,9 @@ $wgCacheEpoch = '20030516000000';
* Must set $wgShowIPinHeader = false
*/
$wgUseFileCache = false;
+
/** Directory where the cached page will be saved */
-$wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
+$wgFileCacheDirectory = false; /// defaults to "{$wgUploadDirectory}/cache";
/**
* When using the file cache, we can store the cached HTML gzipped to save disk
@@ -1069,11 +1145,20 @@ $wgEnotifRevealEditorAddress = false; # UPO; reply-to address may be filled with
$wgEnotifMinorEdits = true; # UPO; false: "minor edits" on pages do not trigger notification mails.
# # Attention: _every_ change on a user_talk page trigger a notification mail (if the user is not yet notified)
-
/** Show watching users in recent changes, watchlist and page history views */
$wgRCShowWatchingUsers = false; # UPO
/** Show watching users in Page views */
$wgPageShowWatchingUsers = false;
+/** Show the amount of changed characters in recent changes */
+$wgRCShowChangedSize = true;
+
+/**
+ * If the difference between the character counts of the text
+ * before and after the edit is below that value, the value will be
+ * highlighted on the RC page.
+ */
+$wgRCChangedSizeThreshold = -500;
+
/**
* Show "Updated (since my last visit)" marker in RC view, watchlist and history
* view for watched pages with new changes */
@@ -1124,6 +1209,7 @@ $wgMaxSquidPurgeTitles = 400;
$wgHTCPPort = 4827;
$wgHTCPMulticastTTL = 1;
# $wgHTCPMulticastAddress = "224.0.0.85";
+$wgHTCPMulticastAddress = false;
# Cookie settings:
#
@@ -1155,10 +1241,8 @@ $wgAllowExternalImagesFrom = '';
$wgMiserMode = false;
/** Disable all query pages if miser mode is on, not just some */
$wgDisableQueryPages = false;
-/** Generate a watchlist once every hour or so */
-$wgUseWatchlistCache = false;
-/** The hour or so mentioned above */
-$wgWLCacheTimeout = 3600;
+/** Number of rows to cache in 'querycache' table when miser mode is on */
+$wgQueryCacheLimit = 1000;
/** Number of links to a page required before it is deemed "wanted" */
$wgWantedPagesThreshold = 1;
/** Enable slow parser functions */
@@ -1425,7 +1509,7 @@ if( !isset( $wgCommandLineMode ) ) {
# Recent changes settings
#
-/** Log IP addresses in the recentchanges table */
+/** Log IP addresses in the recentchanges table; can be accessed only by extensions (e.g. CheckUser) or a DB admin */
$wgPutIPinRC = true;
/**
@@ -1999,8 +2083,11 @@ $wgNoFollowLinks = true;
$wgNoFollowNsExceptions = array();
/**
- * Robot policies for namespaces
- * e.g. $wgNamespaceRobotPolicies = array( NS_TALK => 'noindex' );
+ * Robot policies per namespaces.
+ * The default policy is 'index,follow', the array is made of namespace
+ * constants as defined in includes/Defines.php
+ * Example:
+ * $wgNamespaceRobotPolicies = array( NS_TALK => 'noindex' );
*/
$wgNamespaceRobotPolicies = array();
@@ -2043,6 +2130,7 @@ $wgDisableHardRedirects = false;
* Use http.dnsbl.sorbs.net to check for open proxies
*/
$wgEnableSorbs = false;
+$wgSorbsUrl = 'http.dnsbl.sorbs.net.';
/**
* Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other
@@ -2224,6 +2312,13 @@ $wgAjaxSearch = false;
$wgAjaxExportList = array( );
/**
+ * Enable watching/unwatching pages using AJAX.
+ * Requires $wgUseAjax to be true too.
+ * Causes wfAjaxWatch to be added to $wgAjaxExportList
+ */
+$wgAjaxWatch = false;
+
+/**
* Allow DISPLAYTITLE to change title display
*/
$wgAllowDisplayTitle = false ;
@@ -2232,7 +2327,12 @@ $wgAllowDisplayTitle = false ;
* Array of usernames which may not be registered or logged in from
* Maintenance scripts can still use these
*/
-$wgReservedUsernames = array( 'MediaWiki default', 'Conversion script' );
+$wgReservedUsernames = array(
+ 'MediaWiki default', // Default 'Main Page' and MediaWiki: message pages
+ 'Conversion script', // Used for the old Wikipedia software upgrade
+ 'Maintenance script', // ... maintenance/edit.php uses this?
+ 'Template namespace initialisation script', // Used in 1.2->1.3 upgrade
+);
/**
* MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't
@@ -2288,7 +2388,32 @@ $wgDjvuPostProcessor = 'ppmtojpeg';
* Enable direct access to the data API
* through api.php
*/
-$wgEnableAPI = false;
+$wgEnableAPI = true;
$wgEnableWriteAPI = false;
+/**
+ * Parser test suite files to be run by parserTests.php when no specific
+ * filename is passed to it.
+ *
+ * Extensions may add their own tests to this array, or site-local tests
+ * may be added via LocalSettings.php
+ *
+ * Use full paths.
+ */
+$wgParserTestFiles = array(
+ "$IP/maintenance/parserTests.txt",
+);
+
+/**
+ * Break out of framesets. This can be used to prevent external sites from
+ * framing your site with ads.
+ */
+$wgBreakFrames = false;
+
+/**
+ * Set this to an array of special page names to prevent
+ * maintenance/updateSpecialPages.php from updating those pages.
+ */
+$wgDisableQueryPageUpdate = false;
+
?>
diff --git a/includes/Defines.php b/includes/Defines.php
index 40727485..84bc4495 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -193,6 +193,7 @@ define( 'EDIT_MINOR', 4 );
define( 'EDIT_SUPPRESS_RC', 8 );
define( 'EDIT_FORCE_BOT', 16 );
define( 'EDIT_DEFER_UPDATES', 32 );
+define( 'EDIT_AUTOSUMMARY', 64 );
/**#@-*/
/**
diff --git a/includes/DifferenceEngine.php b/includes/DifferenceEngine.php
index 448bcb5d..a72f0153 100644
--- a/includes/DifferenceEngine.php
+++ b/includes/DifferenceEngine.php
@@ -97,12 +97,10 @@ CONTROL;
return;
}
- $t = $this->mTitle->getPrefixedText() . " (Diff: {$this->mOldid}, " .
- "{$this->mNewid})";
- $mtext = wfMsg( 'missingarticle', "<nowiki>$t</nowiki>" );
-
$wgOut->setArticleFlag( false );
if ( ! $this->loadRevisionData() ) {
+ $t = $this->mTitle->getPrefixedText() . " (Diff: {$this->mOldid}, {$this->mNewid})";
+ $mtext = wfMsg( 'missingarticle', "<nowiki>$t</nowiki>" );
$wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
$wgOut->addWikitext( $mtext );
wfProfileOut( $fname );
@@ -144,15 +142,9 @@ CONTROL;
}
$sk = $wgUser->getSkin();
- $talk = $wgContLang->getNsText( NS_TALK );
- $contribs = wfMsg( 'contribslink' );
if ( $this->mNewRev->isCurrent() && $wgUser->isAllowed('rollback') ) {
- $username = $this->mNewRev->getUserText();
- $rollback = '&nbsp;&nbsp;&nbsp;<strong>[' . $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'rollbacklink' ),
- 'action=rollback&from=' . urlencode( $username ) .
- '&token=' . urlencode( $wgUser->editToken( array( $this->mTitle->getPrefixedText(), $username ) ) ) ) .
- ']</strong>';
+ $rollback = '&nbsp;&nbsp;&nbsp;' . $sk->generateRollback( $this->mNewRev );
} else {
$rollback = '';
}
@@ -171,13 +163,26 @@ CONTROL;
'diff=next&oldid='.$this->mNewid, '', '', 'id="differences-nextlink"' );
}
+ $oldminor = '';
+ $newminor = '';
+
+ if ($this->mOldRev->mMinorEdit == 1) {
+ $oldminor = wfElement( 'span', array( 'class' => 'minor' ),
+ wfMsg( 'minoreditletter') ) . ' ';
+ }
+
+ if ($this->mNewRev->mMinorEdit == 1) {
+ $newminor = wfElement( 'span', array( 'class' => 'minor' ),
+ wfMsg( 'minoreditletter') ) . ' ';
+ }
+
$oldHeader = "<strong>{$this->mOldtitle}</strong><br />" .
$sk->revUserTools( $this->mOldRev ) . "<br />" .
- $sk->revComment( $this->mOldRev ) . "<br />" .
+ $oldminor . $sk->revComment( $this->mOldRev, true ) . "<br />" .
$prevlink;
$newHeader = "<strong>{$this->mNewtitle}</strong><br />" .
$sk->revUserTools( $this->mNewRev ) . " $rollback<br />" .
- $sk->revComment( $this->mNewRev ) . "<br />" .
+ $newminor . $sk->revComment( $this->mNewRev, true ) . "<br />" .
$nextlink . $patrol;
$this->showDiff( $oldHeader, $newHeader );
@@ -287,7 +292,8 @@ CONTROL;
if ( $body === false ) {
return false;
} else {
- return $this->addHeader( $body, $otitle, $ntitle );
+ $multi = $this->getMultiNotice();
+ return $this->addHeader( $body, $otitle, $ntitle, $multi );
}
}
@@ -426,20 +432,49 @@ CONTROL;
return wfMsgExt( 'lineno', array('parseinline'), $wgLang->formatNum( $matches[1] ) );
}
+
+ /**
+ * If there are revisions between the ones being compared, return a note saying so.
+ */
+ function getMultiNotice() {
+ if ( !is_object($this->mOldRev) || !is_object($this->mNewRev) )
+ return '';
+
+ if( !$this->mOldPage->equals( $this->mNewPage ) ) {
+ // Comparing two different pages? Count would be meaningless.
+ return '';
+ }
+
+ $oldid = $this->mOldRev->getId();
+ $newid = $this->mNewRev->getId();
+ if ( $oldid > $newid ) {
+ $tmp = $oldid; $oldid = $newid; $newid = $tmp;
+ }
+
+ $n = $this->mTitle->countRevisionsBetween( $oldid, $newid );
+ if ( !$n )
+ return '';
+
+ return wfMsgExt( 'diff-multi', array( 'parseinline' ), $n );
+ }
+
+
/**
* Add the header to a diff body
*/
- function addHeader( $diff, $otitle, $ntitle ) {
- $out = "
+ function addHeader( $diff, $otitle, $ntitle, $multi = '' ) {
+ $header = "
<table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>
<tr>
<td colspan='2' width='50%' align='center' class='diff-otitle'>{$otitle}</td>
<td colspan='2' width='50%' align='center' class='diff-ntitle'>{$ntitle}</td>
</tr>
- $diff
- </table>
";
- return $out;
+
+ if ( $multi != '' )
+ $header .= "<tr><td colspan='4' align='center' class='diff-multi'>{$multi}</td></tr>";
+
+ return $header . $diff . "</table>";
}
/**
@@ -488,17 +523,21 @@ CONTROL;
$newLink = $this->mNewPage->escapeLocalUrl();
$this->mPagetitle = htmlspecialchars( wfMsg( 'currentrev' ) );
$newEdit = $this->mNewPage->escapeLocalUrl( 'action=edit' );
-
- $this->mNewtitle = "<strong><a href='$newLink'>{$this->mPagetitle}</a> ($timestamp)</strong>"
- . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
+ $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undo=' . $this->mNewid );
+
+ $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a> ($timestamp)"
+ . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"
+ . " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
} else {
$newLink = $this->mNewPage->escapeLocalUrl( 'oldid=' . $this->mNewid );
$newEdit = $this->mNewPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mNewid );
+ $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undo=' . $this->mNewid );
$this->mPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $timestamp ) );
-
- $this->mNewtitle = "<strong><a href='$newLink'>{$this->mPagetitle}</a></strong>"
- . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
+
+ $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>"
+ . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"
+ . " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
}
// Load the old revision object
@@ -527,8 +566,8 @@ CONTROL;
$t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true );
$oldLink = $this->mOldPage->escapeLocalUrl( 'oldid=' . $this->mOldid );
$oldEdit = $this->mOldPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mOldid );
- $this->mOldtitle = "<strong><a href='$oldLink'>" . htmlspecialchars( wfMsg( 'revisionasof', $t ) )
- . "</a></strong> (<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
+ $this->mOldtitle = "<a href='$oldLink'>" . htmlspecialchars( wfMsg( 'revisionasof', $t ) )
+ . "</a> (<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
}
return true;
@@ -890,7 +929,7 @@ class _DiffEngine
$ymids[$k] = $ymids[$k-1];
break;
}
- while (list ($junk, $y) = each($matches)) {
+ while (list ( /* $junk */, $y) = each($matches)) {
if ($y > $this->seq[$k-1]) {
USE_ASSERTS && assert($y < $this->seq[$k]);
// Optimization: this is a common case:
@@ -1608,6 +1647,7 @@ class WordLevelDiff extends MappedDiff
$words[] = $line;
$stripped[] = $line;
} else {
+ $m = array();
if (preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
$line, $m))
{
diff --git a/includes/DjVuImage.php b/includes/DjVuImage.php
index 871c563b..f7297dc2 100644
--- a/includes/DjVuImage.php
+++ b/includes/DjVuImage.php
@@ -217,7 +217,7 @@ class DjVuImage {
global $wgDjvuToXML;
if ( isset( $wgDjvuToXML ) ) {
$cmd = $wgDjvuToXML . ' --without-anno --without-text ' . $this->mFilename;
- $xml = wfShellExec( $cmd, $retval );
+ $xml = wfShellExec( $cmd );
} else {
$xml = null;
}
diff --git a/includes/EditPage.php b/includes/EditPage.php
index a1207d10..c53389cc 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -32,6 +32,7 @@ class EditPage {
var $allowBlankSummary = false;
var $autoSumm = '';
var $hookError = '';
+ var $mPreviewTemplates;
# Form values
var $save = false, $preview = false, $diff = false;
@@ -40,6 +41,14 @@ class EditPage {
var $edittime = '', $section = '', $starttime = '';
var $oldid = 0, $editintro = '', $scrolltop = null;
+ # Placeholders for text injection by hooks (must be HTML)
+ # extensions should take care to _append_ to the present value
+ public $editFormPageTop; // Before even the preview
+ public $editFormTextTop;
+ public $editFormTextAfterWarn;
+ public $editFormTextAfterTools;
+ public $editFormTextBottom;
+
/**
* @todo document
* @param $article
@@ -48,17 +57,25 @@ class EditPage {
$this->mArticle =& $article;
global $wgTitle;
$this->mTitle =& $wgTitle;
+
+ # Placeholders for text injection by hooks (empty per default)
+ $this->editFormPageTop =
+ $this->editFormTextTop =
+ $this->editFormTextAfterWarn =
+ $this->editFormTextAfterTools =
+ $this->editFormTextBottom = "";
}
/**
* Fetch initial editing page content.
*/
private function getContent() {
- global $wgRequest, $wgParser;
+ global $wgOut, $wgRequest, $wgParser;
# Get variables from query string :P
$section = $wgRequest->getVal( 'section' );
$preload = $wgRequest->getVal( 'preload' );
+ $undo = $wgRequest->getVal( 'undo' );
wfProfileIn( __METHOD__ );
@@ -79,8 +96,41 @@ class EditPage {
// information.
$text = $this->mArticle->getContent();
-
- if( $section != '' ) {
+
+ if ( $undo > 0 ) {
+ #Undoing a specific edit overrides section editing; section-editing
+ # doesn't work with undoing.
+ $undorev = Revision::newFromId($undo);
+
+ #Sanity check, make sure it's the right page.
+ # Otherwise, $text will be left as-is.
+ if (!is_null($undorev) && $undorev->getPage() == $this->mArticle->getID()) {
+ $oldrev = $undorev->getPrevious();
+ $undorev_text = $undorev->getText();
+ $oldrev_text = $oldrev->getText();
+ $currev_text = $text;
+
+ #No use doing a merge if it's just a straight revert.
+ if ($currev_text != $undorev_text) {
+ $result = wfMerge($undorev_text, $oldrev_text, $currev_text, $text);
+ } else {
+ $text = $oldrev_text;
+ $result = true;
+ }
+
+ if( $result ) {
+ # Inform the user of our success and set an automatic edit summary
+ $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-success' ) );
+ $this->summary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() );
+ $this->formtype = 'diff';
+ } else {
+ # Warn the user that something went wrong
+ $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-failure' ) );
+ }
+
+ }
+ }
+ else if( $section != '' ) {
if( $section == 'new' ) {
$text = $this->getPreloadedText( $preload );
} else {
@@ -439,6 +489,9 @@ class EditPage {
# The unmarked state will be assumed to be a save,
# if the form seems otherwise complete.
wfDebug( "$fname: Passed token check.\n" );
+ } else if ( $this->diff ) {
+ # Failed token check, but only requested "Show Changes".
+ wfDebug( "$fname: Failed token check; Show Changes requested.\n" );
} else {
# Page might be a hack attempt posted from
# an external site. Preview instead of saving.
@@ -507,8 +560,8 @@ class EditPage {
global $wgUser;
if( $wgUser->isAnon() ) {
# Anonymous users may not have a session
- # open. Don't tokenize.
- $this->mTokenOk = true;
+ # open. Check for suffix anyway.
+ $this->mTokenOk = ( EDIT_TOKEN_SUFFIX == $request->getVal( 'wpEditToken' ) );
} else {
$this->mTokenOk = $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) );
}
@@ -549,11 +602,18 @@ class EditPage {
wfProfileIn( $fname );
wfProfileIn( "$fname-checks" );
+ if( !wfRunHooks( 'EditPage::attemptSave', array( &$this ) ) )
+ {
+ wfDebug( "Hook 'EditPage::attemptSave' aborted article saving" );
+ return false;
+ }
+
# Reintegrate metadata
if ( $this->mMetaData != '' ) $this->textbox1 .= "\n" . $this->mMetaData ;
$this->mMetaData = '' ;
# Check for spam
+ $matches = array();
if ( $wgSpamRegex && preg_match( $wgSpamRegex, $this->textbox1, $matches ) ) {
$this->spamPage ( $matches[0] );
wfProfileOut( "$fname-checks" );
@@ -634,6 +694,7 @@ class EditPage {
# If article is new, insert it.
$aid = $this->mTitle->getArticleID( GAID_FOR_UPDATE );
if ( 0 == $aid ) {
+
// Late check for create permission, just in case *PARANOIA*
if ( !$this->mTitle->userCanCreate() ) {
wfDebug( "$fname: no create permission\n" );
@@ -649,14 +710,6 @@ class EditPage {
return false;
}
- # If no edit comment was given when creating a new page, and what's being
- # created is a redirect, be smart and fill in a neat auto-comment
- if( $this->summary == '' ) {
- $rt = Title::newFromRedirect( $this->textbox1 );
- if( is_object( $rt ) )
- $this->summary = wfMsgForContent( 'autoredircomment', $rt->getPrefixedText() );
- }
-
$isComment=($this->section=='new');
$this->mArticle->insertNewArticle( $this->textbox1, $this->summary,
$this->minoredit, $this->watchthis, false, $isComment);
@@ -728,16 +781,11 @@ class EditPage {
return true;
}
- # If no edit comment was given when turning a page into a redirect, be smart
- # and fill in a neat auto-comment
- if( $this->summary == '' ) {
- $rt = Title::newFromRedirect( $this->textbox1 );
- if( is_object( $rt ) )
- $this->summary = wfMsgForContent( 'autoredircomment', $rt->getPrefixedText() );
- }
+ $oldtext = $this->mArticle->getContent();
- # Handle the user preference to force summaries here
- if( $this->section != 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary' ) ) {
+ # Handle the user preference to force summaries here, but not for null edits
+ if( $this->section != 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary')
+ && 0 != strcmp($oldtext, $text) && !Article::getRedirectAutosummary( $text )) {
if( md5( $this->summary ) == $this->autoSumm ) {
$this->missingSummary = true;
wfProfileOut( $fname );
@@ -745,6 +793,15 @@ class EditPage {
}
}
+ #And a similar thing for new sections
+ if( $this->section == 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary' ) ) {
+ if (trim($this->summary) == '') {
+ $this->missingSummary = true;
+ wfProfileOut( $fname );
+ return( true );
+ }
+ }
+
# All's well
wfProfileIn( "$fname-sectionanchor" );
$sectionanchor = '';
@@ -802,8 +859,8 @@ class EditPage {
*/
function initialiseForm() {
$this->edittime = $this->mArticle->getTimestamp();
- $this->textbox1 = $this->getContent();
$this->summary = '';
+ $this->textbox1 = $this->getContent();
if ( !$this->mArticle->exists() && $this->mArticle->mTitle->getNamespace() == NS_MEDIAWIKI )
$this->textbox1 = wfMsgWeirdKey( $this->mArticle->mTitle->getText() ) ;
wfProxyCheck();
@@ -845,6 +902,7 @@ class EditPage {
$s = wfMsg('editingcomment', $this->mTitle->getPrefixedText() );
} else {
$s = wfMsg('editingsection', $this->mTitle->getPrefixedText() );
+ $matches = array();
if( !$this->summary && !$this->preview && !$this->diff ) {
preg_match( "/^(=+)(.+)\\1/mi",
$this->textbox1,
@@ -863,9 +921,13 @@ class EditPage {
$wgOut->addWikiText( wfMsg( 'missingcommenttext' ) );
}
- if( $this->missingSummary ) {
+ if( $this->missingSummary && $this->section != 'new' ) {
$wgOut->addWikiText( wfMsg( 'missingsummary' ) );
}
+
+ if( $this->missingSummary && $this->section == 'new' ) {
+ $wgOut->addWikiText( wfMsg( 'missingcommentheader' ) );
+ }
if( !$this->hookError == '' ) {
$wgOut->addWikiText( $this->hookError );
@@ -924,6 +986,12 @@ class EditPage {
$wgOut->addWikiText( wfMsg( 'longpagewarning', $wgLang->formatNum( $this->kblength ) ) );
}
+ #need to parse the preview early so that we know which templates are used,
+ #otherwise users with "show preview after edit box" will get a blank list
+ if ( $this->formtype == 'preview' ) {
+ $previewOutput = $this->getPreviewText();
+ }
+
$rows = $wgUser->getIntOption( 'rows' );
$cols = $wgUser->getIntOption( 'cols' );
@@ -998,10 +1066,12 @@ class EditPage {
$checkboxhtml = $minoredithtml . $watchhtml;
+ $wgOut->addHTML( $this->editFormPageTop );
+
if ( $wgUser->getOption( 'previewontop' ) ) {
if ( 'preview' == $this->formtype ) {
- $this->showPreview();
+ $this->showPreview( $previewOutput );
} else {
$wgOut->addHTML( '<div id="wikiPreview"></div>' );
}
@@ -1012,22 +1082,29 @@ class EditPage {
}
+ $wgOut->addHTML( $this->editFormTextTop );
+
# if this is a comment, show a subject line at the top, which is also the edit summary.
# Otherwise, show a summary field at the bottom
$summarytext = htmlspecialchars( $wgContLang->recodeForEdit( $this->summary ) ); # FIXME
if( $this->section == 'new' ) {
$commentsubject="<span id='wpSummaryLabel'><label for='wpSummary'>{$subject}:</label></span>\n<div class='editOptions'>\n<input tabindex='1' type='text' value=\"$summarytext\" name='wpSummary' id='wpSummary' maxlength='200' size='60' /><br />";
$editsummary = '';
+ $subjectpreview = $summarytext && $this->preview ? "<div class=\"mw-summary-preview\">".wfMsg('subject-preview').':'.$sk->commentBlock( $this->summary, $this->mTitle )."</div>\n" : '';
+ $summarypreview = '';
} else {
$commentsubject = '';
$editsummary="<span id='wpSummaryLabel'><label for='wpSummary'>{$summary}:</label></span>\n<div class='editOptions'>\n<input tabindex='2' type='text' value=\"$summarytext\" name='wpSummary' id='wpSummary' maxlength='200' size='60' /><br />";
+ $summarypreview = $summarytext && $this->preview ? "<div class=\"mw-summary-preview\">".wfMsg('summary-preview').':'.$sk->commentBlock( $this->summary, $this->mTitle )."</div>\n" : '';
+ $subjectpreview = '';
}
# Set focus to the edit box on load, except on preview or diff, where it would interfere with the display
if( !$this->preview && !$this->diff ) {
$wgOut->setOnloadHandler( 'document.editform.wpTextbox1.focus()' );
}
- $templates = $this->formatTemplates();
+ $templates = ($this->preview || $this->section) ? $this->mPreviewTemplates : $this->mArticle->getUsedTemplates();
+ $formattedtemplates = $sk->formatTemplates( $templates, $this->preview, $this->section != '');
global $wgUseMetadataEdit ;
if ( $wgUseMetadataEdit ) {
@@ -1138,6 +1215,7 @@ END
$wgOut->addHTML( <<<END
$recreate
{$commentsubject}
+{$subjectpreview}
<textarea tabindex='1' accesskey="," name="wpTextbox1" id="wpTextbox1" rows='{$rows}'
cols='{$cols}'{$ew} $hidden>
END
@@ -1147,9 +1225,11 @@ END
" );
$wgOut->addWikiText( $copywarn );
+ $wgOut->addHTML( $this->editFormTextAfterWarn );
$wgOut->addHTML( "
{$metadata}
{$editsummary}
+{$summarypreview}
{$checkboxhtml}
{$safemodehtml}
");
@@ -1164,26 +1244,36 @@ END
</div><!-- editButtons -->
</div><!-- editOptions -->");
+ $wgOut->addHtml( '<div class="mw-editTools">' );
$wgOut->addWikiText( wfMsgForContent( 'edittools' ) );
+ $wgOut->addHtml( '</div>' );
+
+ $wgOut->addHTML( $this->editFormTextAfterTools );
$wgOut->addHTML( "
<div class='templatesUsed'>
-{$templates}
+{$formattedtemplates}
</div>
" );
- if ( $wgUser->isLoggedIn() ) {
- /**
- * To make it harder for someone to slip a user a page
- * which submits an edit form to the wiki without their
- * knowledge, a random token is associated with the login
- * session. If it's not passed back with the submission,
- * we won't save the page, or render user JavaScript and
- * CSS previews.
- */
+ /**
+ * To make it harder for someone to slip a user a page
+ * which submits an edit form to the wiki without their
+ * knowledge, a random token is associated with the login
+ * session. If it's not passed back with the submission,
+ * we won't save the page, or render user JavaScript and
+ * CSS previews.
+ *
+ * For anon editors, who may not have a session, we just
+ * include the constant suffix to prevent editing from
+ * broken text-mangling proxies.
+ */
+ if ( $wgUser->isLoggedIn() )
$token = htmlspecialchars( $wgUser->editToken() );
- $wgOut->addHTML( "\n<input type='hidden' value=\"$token\" name=\"wpEditToken\" />\n" );
- }
+ else
+ $token = EDIT_TOKEN_SUFFIX;
+ $wgOut->addHTML( "\n<input type='hidden' value=\"$token\" name=\"wpEditToken\" />\n" );
+
# If a blank edit summary was previously provided, and the appropriate
# user preference is active, pass a hidden tag here. This will stop the
@@ -1209,11 +1299,12 @@ END
$wgOut->addHTML( "<textarea tabindex=6 id='wpTextbox2' name=\"wpTextbox2\" rows='{$rows}' cols='{$cols}' wrap='virtual'>"
. htmlspecialchars( $this->safeUnicodeOutput( $this->textbox2 ) ) . "\n</textarea>" );
}
+ $wgOut->addHTML( $this->editFormTextBottom );
$wgOut->addHTML( "</form>\n" );
if ( !$wgUser->getOption( 'previewontop' ) ) {
if ( $this->formtype == 'preview') {
- $this->showPreview();
+ $this->showPreview( $previewOutput );
} else {
$wgOut->addHTML( '<div id="wikiPreview"></div>' );
}
@@ -1230,56 +1321,24 @@ END
/**
* Append preview output to $wgOut.
* Includes category rendering if this is a category page.
- * @private
+ *
+ * @param string $text The HTML to be output for the preview.
*/
- function showPreview() {
+ private function showPreview( $text ) {
global $wgOut;
+
$wgOut->addHTML( '<div id="wikiPreview">' );
if($this->mTitle->getNamespace() == NS_CATEGORY) {
$this->mArticle->openShowCategory();
}
- $previewOutput = $this->getPreviewText();
- $wgOut->addHTML( $previewOutput );
+ $wgOut->addHTML( $text );
if($this->mTitle->getNamespace() == NS_CATEGORY) {
$this->mArticle->closeShowCategory();
}
- $wgOut->addHTML( "<br style=\"clear:both;\" />\n" );
$wgOut->addHTML( '</div>' );
}
/**
- * Prepare a list of templates used by this page. Returns HTML.
- */
- function formatTemplates() {
- global $wgUser;
-
- $fname = 'EditPage::formatTemplates';
- wfProfileIn( $fname );
-
- $sk =& $wgUser->getSkin();
-
- $outText = '';
- $templates = $this->mArticle->getUsedTemplates();
- if ( count( $templates ) > 0 ) {
- # Do a batch existence check
- $batch = new LinkBatch;
- foreach( $templates as $title ) {
- $batch->addObj( $title );
- }
- $batch->execute();
-
- # Construct the HTML
- $outText = '<br />'. wfMsgExt( 'templatesused', array( 'parseinline' ) ) . '<ul>';
- foreach ( $templates as $titleObj ) {
- $outText .= '<li>' . $sk->makeLinkObj( $titleObj ) . '</li>';
- }
- $outText .= '</ul>';
- }
- wfProfileOut( $fname );
- return $outText;
- }
-
- /**
* Live Preview lets us fetch rendered preview page content and
* add it to the page without refreshing the whole page.
* If not supported by the browser it will fall through to the normal form
@@ -1290,9 +1349,9 @@ END
* of the preview button
*/
function doLivePreviewScript() {
- global $wgStylePath, $wgJsMimeType, $wgOut, $wgTitle;
+ global $wgStylePath, $wgJsMimeType, $wgStyleVersion, $wgOut, $wgTitle;
$wgOut->addHTML( '<script type="'.$wgJsMimeType.'" src="' .
- htmlspecialchars( $wgStylePath . '/common/preview.js' ) .
+ htmlspecialchars( "$wgStylePath/common/preview.js?$wgStyleVersion" ) .
'"></script>' . "\n" );
$liveAction = $wgTitle->getLocalUrl( 'action=submit&wpPreview=true&live=true' );
return "return !livePreview(" .
@@ -1395,6 +1454,10 @@ END
$previewHTML = $parserOutput->getText();
$wgOut->addParserOutputNoText( $parserOutput );
+ foreach ( $parserOutput->getTemplates() as $ns => $template)
+ foreach ( array_keys( $template ) as $dbk)
+ $this->mPreviewTemplates[] = Title::makeTitle($ns, $dbk);
+
wfProfileOut( $fname );
return $previewhead . $previewHTML;
}
@@ -1434,7 +1497,7 @@ END
global $wgUser, $wgOut;
$skin = $wgUser->getSkin();
- $loginTitle = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
+ $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
$loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $this->mTitle->getPrefixedUrl() );
$wgOut->setPageTitle( wfMsg( 'whitelistedittitle' ) );
@@ -1508,6 +1571,7 @@ END
}
$currentText = $currentRevision->getText();
+ $result = '';
if( wfMerge( $baseText, $editText, $currentText, $result ) ){
$editText = $result;
wfProfileOut( $fname );
@@ -1583,15 +1647,15 @@ END
*/
$toolarray=array(
array( 'image'=>'button_bold.png',
- 'open' => "\'\'\'",
- 'close' => "\'\'\'",
+ 'open' => '\\\'\\\'\\\'',
+ 'close' => '\\\'\\\'\\\'',
'sample'=> wfMsg('bold_sample'),
'tip' => wfMsg('bold_tip'),
'key' => 'B'
),
array( 'image'=>'button_italic.png',
- 'open' => "\'\'",
- 'close' => "\'\'",
+ 'open' => '\\\'\\\'',
+ 'close' => '\\\'\\\'',
'sample'=> wfMsg('italic_sample'),
'tip' => wfMsg('italic_tip'),
'key' => 'I'
diff --git a/includes/Exception.php b/includes/Exception.php
index 56f18d5a..ac9c8a21 100644
--- a/includes/Exception.php
+++ b/includes/Exception.php
@@ -165,7 +165,7 @@ function wfInstallExceptionHandler() {
* Report an exception to the user
*/
function wfReportException( Exception $e ) {
- if ( is_a( $e, 'MWException' ) ) {
+ if ( $e instanceof MWException ) {
try {
$e->report();
} catch ( Exception $e2 ) {
diff --git a/includes/Exif.php b/includes/Exif.php
index 2ab0feb1..0860d5f7 100644
--- a/includes/Exif.php
+++ b/includes/Exif.php
@@ -439,7 +439,7 @@ class Exif {
return false;
}
- if ( preg_match( "/^\s*$/", $in ) ) {
+ if ( preg_match( '/^\s*$/', $in ) ) {
$this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
return false;
}
@@ -468,7 +468,8 @@ class Exif {
}
function isRational( $in ) {
- if ( !is_array( $in ) && @preg_match( "/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/", $in, $m ) ) { # Avoid division by zero
+ $m = array();
+ if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
} else {
$this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
@@ -477,7 +478,7 @@ class Exif {
}
function isUndefined( $in ) {
- if ( !is_array( $in ) && preg_match( "/^\d{4}$/", $in ) ) { // Allow ExifVersion and FlashpixVersion
+ if ( !is_array( $in ) && preg_match( '/^\d{4}$/', $in ) ) { // Allow ExifVersion and FlashpixVersion
$this->debug( $in, __FUNCTION__, true );
return true;
} else {
@@ -497,7 +498,8 @@ class Exif {
}
function isSrational( $in ) {
- if ( !is_array( $in ) && preg_match( "/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/", $in, $m ) ) { # Avoid division by zero
+ $m = array();
+ if ( !is_array( $in ) && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
} else {
$this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
@@ -729,7 +731,9 @@ class FormatExif {
case 'DateTime':
case 'DateTimeOriginal':
case 'DateTimeDigitized':
- if( preg_match( "/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/", $val ) ) {
+ if( $val == '0000:00:00 00:00:00' ) {
+ $tags[$tag] = wfMsg('exif-unknowndate');
+ } elseif( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/', $val ) ) {
$tags[$tag] = $wgLang->timeanddate( wfTimestamp(TS_MW, $val) );
}
break;
@@ -1054,6 +1058,7 @@ class FormatExif {
* @return mixed A floating point number or whatever we were fed
*/
function formatNum( $num ) {
+ $m = array();
if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) )
return $m[2] != 0 ? $m[1] / $m[2] : $num;
else
@@ -1069,6 +1074,7 @@ class FormatExif {
* @return mixed A floating point number or whatever we were fed
*/
function formatFraction( $num ) {
+ $m = array();
if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) ) {
$numerator = intval( $m[1] );
$denominator = intval( $m[2] );
diff --git a/includes/Export.php b/includes/Export.php
index aa70e27b..b7e0f9a1 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -337,8 +337,7 @@ class XmlDumpWriter {
}
function homelink() {
- $page = Title::newFromText( wfMsgForContent( 'mainpage' ) );
- return wfElement( 'base', array(), $page->getFullUrl() );
+ return wfElement( 'base', array(), Title::newMainPage()->getFullUrl() );
}
function caseSetting() {
@@ -597,7 +596,7 @@ class DumpFilter {
* Override for page-based filter types.
* @return bool
*/
- function pass( $page, $string ) {
+ function pass( $page ) {
return true;
}
}
diff --git a/includes/Feed.php b/includes/Feed.php
index 7663e820..5c14865d 100644
--- a/includes/Feed.php
+++ b/includes/Feed.php
@@ -149,12 +149,12 @@ class ChannelFeed extends FeedItem {
* @private
*/
function outXmlHeader() {
- global $wgServer, $wgStylePath;
+ global $wgServer, $wgStylePath, $wgStyleVersion;
$this->httpHeaders();
echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
echo '<?xml-stylesheet type="text/css" href="' .
- htmlspecialchars( "$wgServer$wgStylePath/common/feed.css" ) . '"?' . ">\n";
+ htmlspecialchars( "$wgServer$wgStylePath/common/feed.css?$wgStyleVersion" ) . '"?' . ">\n";
}
}
diff --git a/includes/FileStore.php b/includes/FileStore.php
index 35ebd554..1fd35b01 100644
--- a/includes/FileStore.php
+++ b/includes/FileStore.php
@@ -36,6 +36,9 @@ class FileStore {
* @fixme Probably only works on MySQL. Abstract to the Database class?
*/
static function lock() {
+ global $wgDBtype;
+ if ($wgDBtype != 'mysql')
+ return true;
$dbw = wfGetDB( DB_MASTER );
$lockname = $dbw->addQuotes( FileStore::lockName() );
$result = $dbw->query( "SELECT GET_LOCK($lockname, 5) AS lockstatus", __METHOD__ );
@@ -54,10 +57,13 @@ class FileStore {
* Release the global file store lock.
*/
static function unlock() {
+ global $wgDBtype;
+ if ($wgDBtype != 'mysql')
+ return true;
$dbw = wfGetDB( DB_MASTER );
$lockname = $dbw->addQuotes( FileStore::lockName() );
$result = $dbw->query( "SELECT RELEASE_LOCK($lockname)", __METHOD__ );
- $row = $dbw->fetchObject( $result );
+ $dbw->fetchObject( $result );
$dbw->freeResult( $result );
}
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index 623f9d3b..08094ca1 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -9,24 +9,16 @@
* Some globals and requires needed
*/
-/**
- * Total number of articles
- * @global integer $wgNumberOfArticles
- */
+/** Total number of articles */
$wgNumberOfArticles = -1; # Unset
-/**
- * Total number of views
- * @global integer $wgTotalViews
- */
+
+/** Total number of views */
$wgTotalViews = -1;
-/**
- * Total number of edits
- * @global integer $wgTotalEdits
- */
+
+/** Total number of edits */
$wgTotalEdits = -1;
-require_once( 'DatabaseFunctions.php' );
require_once( 'LogPage.php' );
require_once( 'normal/UtfNormalUtil.php' );
require_once( 'XmlFunctions.php' );
@@ -53,6 +45,7 @@ if( !function_exists('iconv') ) {
# UTF-8 substr function based on a PHP manual comment
if ( !function_exists( 'mb_substr' ) ) {
function mb_substr( $str, $start ) {
+ $ar = array();
preg_match_all( '/./us', $str, $ar );
if( func_num_args() >= 3 ) {
@@ -72,7 +65,7 @@ if ( !function_exists( 'array_diff_key' ) ) {
*/
function array_diff_key( $left, $right ) {
$result = $left;
- foreach ( $left as $key => $value ) {
+ foreach ( $left as $key => $unused ) {
if ( isset( $right[$key] ) ) {
unset( $result[$key] );
}
@@ -114,7 +107,7 @@ function wfSeedRandom() {
function wfRandom() {
# The maximum random value is "only" 2^31-1, so get two random
# values to reduce the chance of dupes
- $max = mt_getrandmax();
+ $max = mt_getrandmax() + 1;
$rand = number_format( (mt_rand() * $max + mt_rand())
/ $max / $max, 12, '.', '' );
return $rand;
@@ -282,6 +275,10 @@ function wfReadOnly() {
*
* @param $key String: lookup key for the message, usually
* defined in languages/Language.php
+ *
+ * This function also takes extra optional parameters (not
+ * shown in the function definition), which can by used to
+ * insert variable text into the predefined message.
*/
function wfMsg( $key ) {
$args = func_get_args();
@@ -295,7 +292,7 @@ function wfMsg( $key ) {
function wfMsgNoTrans( $key ) {
$args = func_get_args();
array_shift( $args );
- return wfMsgReal( $key, $args, true, false );
+ return wfMsgReal( $key, $args, true, false, false );
}
/**
@@ -371,14 +368,14 @@ function wfMsgNoDBForContent( $key ) {
/**
* Really get a message
- * @return $key String: key to get.
- * @return $args
- * @return $useDB Boolean
+ * @param $key String: key to get.
+ * @param $args
+ * @param $useDB Boolean
+ * @param $transform Boolean: Whether or not to transform the message.
+ * @param $forContent Boolean
* @return String: the requested message.
*/
function wfMsgReal( $key, $args, $useDB = true, $forContent=false, $transform = true ) {
- $fname = 'wfMsgReal';
-
$message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
$message = wfMsgReplaceArgs( $message, $args );
return $message;
@@ -522,9 +519,10 @@ function wfMsgWikiHtml( $key ) {
* <i>parseinline<i>: parses wikitext to html and removes the surrounding p's added by parser or tidy
* <i>escape<i>: filters message trough htmlspecialchars
* <i>replaceafter<i>: parameters are substituted after parsing or escaping
+ * <i>parsemag<i>: ??
*/
function wfMsgExt( $key, $options ) {
- global $wgOut, $wgMsgParserOptions, $wgParser;
+ global $wgOut, $wgParser;
$args = func_get_args();
array_shift( $args );
@@ -549,12 +547,10 @@ function wfMsgExt( $key, $options ) {
$string = $m[1];
}
} elseif ( in_array('parsemag', $options) ) {
- global $wgTitle;
- $parser = new Parser();
- $parserOptions = new ParserOptions();
- $parserOptions->setInterfaceMessage( true );
- $parser->startExternalParse( $wgTitle, $parserOptions, OT_MSG );
- $string = $parser->transformMsg( $string, $parserOptions );
+ global $wgMessageCache;
+ if ( isset( $wgMessageCache ) ) {
+ $string = $wgMessageCache->transform( $string );
+ }
}
if ( in_array('escape', $options) ) {
@@ -583,8 +579,8 @@ function wfAbruptExit( $error = false ){
}
$called = true;
- if( function_exists( 'debug_backtrace' ) ){ // PHP >= 4.3
- $bt = debug_backtrace();
+ $bt = wfDebugBacktrace();
+ if( $bt ) {
for($i = 0; $i < count($bt) ; $i++){
$file = isset($bt[$i]['file']) ? $bt[$i]['file'] : "unknown";
$line = isset($bt[$i]['line']) ? $bt[$i]['line'] : "unknown";
@@ -666,18 +662,36 @@ function wfHostname() {
return $com;
}
+/**
+ * Safety wrapper for debug_backtrace().
+ *
+ * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
+ * murky circumstances, which may be triggered in part by stub objects
+ * or other fancy talkin'.
+ *
+ * Will return an empty array if Zend Optimizer is detected, otherwise
+ * the output from debug_backtrace() (trimmed).
+ *
+ * @return array of backtrace information
+ */
+function wfDebugBacktrace() {
+ if( extension_loaded( 'Zend Optimizer' ) ) {
+ wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
+ return array();
+ } else {
+ return array_slice( debug_backtrace(), 1 );
+ }
+}
+
function wfBacktrace() {
global $wgCommandLineMode;
- if ( !function_exists( 'debug_backtrace' ) ) {
- return false;
- }
if ( $wgCommandLineMode ) {
$msg = '';
} else {
$msg = "<ul>\n";
}
- $backtrace = debug_backtrace();
+ $backtrace = wfDebugBacktrace();
foreach( $backtrace as $call ) {
if( isset( $call['file'] ) ) {
$f = explode( DIRECTORY_SEPARATOR, $call['file'] );
@@ -801,6 +815,7 @@ function wfClientAcceptsGzip() {
global $wgUseGzip;
if( $wgUseGzip ) {
# FIXME: we may want to blacklist some broken browsers
+ $m = array();
if( preg_match(
'/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
$_SERVER['HTTP_ACCEPT_ENCODING'],
@@ -966,6 +981,7 @@ function wfEscapeShellArg( ) {
}
// Double the backslashes before the end of the string, because
// we will soon add a quote
+ $m = array();
if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
$arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
}
@@ -1063,16 +1079,75 @@ function wfHttpError( $code, $label, $desc ) {
$wgOut->sendCacheControl();
header( 'Content-type: text/html' );
- print "<html><head><title>" .
+ print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">".
+ "<html><head><title>" .
htmlspecialchars( $label ) .
"</title></head><body><h1>" .
htmlspecialchars( $label ) .
"</h1><p>" .
- htmlspecialchars( $desc ) .
+ nl2br( htmlspecialchars( $desc ) ) .
"</p></body></html>\n";
}
/**
+ * Clear away any user-level output buffers, discarding contents.
+ *
+ * Suitable for 'starting afresh', for instance when streaming
+ * relatively large amounts of data without buffering, or wanting to
+ * output image files without ob_gzhandler's compression.
+ *
+ * The optional $resetGzipEncoding parameter controls suppression of
+ * the Content-Encoding header sent by ob_gzhandler; by default it
+ * is left. See comments for wfClearOutputBuffers() for why it would
+ * be used.
+ *
+ * Note that some PHP configuration options may add output buffer
+ * layers which cannot be removed; these are left in place.
+ *
+ * @parameter bool $resetGzipEncoding
+ */
+function wfResetOutputBuffers( $resetGzipEncoding=true ) {
+ while( $status = ob_get_status() ) {
+ if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
+ // Probably from zlib.output_compression or other
+ // PHP-internal setting which can't be removed.
+ //
+ // Give up, and hope the result doesn't break
+ // output behavior.
+ break;
+ }
+ if( !ob_end_clean() ) {
+ // Could not remove output buffer handler; abort now
+ // to avoid getting in some kind of infinite loop.
+ break;
+ }
+ if( $resetGzipEncoding ) {
+ if( $status['name'] == 'ob_gzhandler' ) {
+ // Reset the 'Content-Encoding' field set by this handler
+ // so we can start fresh.
+ header( 'Content-Encoding:' );
+ }
+ }
+ }
+}
+
+/**
+ * More legible than passing a 'false' parameter to wfResetOutputBuffers():
+ *
+ * Clear away output buffers, but keep the Content-Encoding header
+ * produced by ob_gzhandler, if any.
+ *
+ * This should be used for HTTP 304 responses, where you need to
+ * preserve the Content-Encoding header of the real result, but
+ * also need to suppress the output of ob_gzhandler to keep to spec
+ * and avoid breaking Firefox in rare cases where the headers and
+ * body are broken over two packets.
+ */
+function wfClearOutputBuffers() {
+ wfResetOutputBuffers( false );
+}
+
+/**
* Converts an Accept-* header into an array mapping string values to quality
* factors
*/
@@ -1089,6 +1164,7 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) {
foreach( $parts as $part ) {
# FIXME: doesn't deal with params like 'text/html; level=1'
@list( $value, $qpart ) = explode( ';', $part );
+ $match = array();
if( !isset( $qpart ) ) {
$prefs[$value] = 1;
} elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) {
@@ -1283,19 +1359,19 @@ function wfTimestamp($outputtype=TS_UNIX,$ts=0) {
$da = array();
if ($ts==0) {
$uts=time();
- } elseif (preg_match("/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D",$ts,$da)) {
+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
# TS_DB
$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",$ts,$da)) {
+ } elseif (preg_match('/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
# TS_EXIF
$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",$ts,$da)) {
+ } elseif (preg_match('/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D',$ts,$da)) {
# TS_MW
$uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
(int)$da[2],(int)$da[3],(int)$da[1]);
- } elseif (preg_match("/^(\d{1,13})$/D",$ts,$datearray)) {
+ } elseif (preg_match('/^(\d{1,13})$/D',$ts,$da)) {
# TS_UNIX
$uts = $ts;
} elseif (preg_match('/^(\d{1,2})-(...)-(\d\d(\d\d)?) (\d\d)\.(\d\d)\.(\d\d)/', $ts, $da)) {
@@ -1306,7 +1382,11 @@ 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)) {
+ } 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]);
+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d) GMT$/',$ts,$da)) {
# TS_POSTGRES
$uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
(int)$da[2],(int)$da[3],(int)$da[1]);
@@ -1383,10 +1463,21 @@ function wfGetCachedNotice( $name ) {
wfProfileIn( $fname );
$needParse = false;
- $notice = wfMsgForContent( $name );
- if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) {
- wfProfileOut( $fname );
- return( false );
+
+ if( $name === 'default' ) {
+ // special case
+ global $wgSiteNotice;
+ $notice = $wgSiteNotice;
+ if( empty( $notice ) ) {
+ wfProfileOut( $fname );
+ return false;
+ }
+ } else {
+ $notice = wfMsgForContentNoTrans( $name );
+ if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) {
+ wfProfileOut( $fname );
+ return( false );
+ }
}
$cachedNotice = $parserMemc->get( wfMemcKey( $name ) );
@@ -1446,16 +1537,17 @@ function wfGetSiteNotice() {
if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) {
if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) {
$siteNotice = wfGetCachedNotice( 'sitenotice' );
- $siteNotice = !$siteNotice ? $wgSiteNotice : $siteNotice;
} else {
$anonNotice = wfGetCachedNotice( 'anonnotice' );
if( !$anonNotice ) {
$siteNotice = wfGetCachedNotice( 'sitenotice' );
- $siteNotice = !$siteNotice ? $wgSiteNotice : $siteNotice;
} else {
$siteNotice = $anonNotice;
}
}
+ if( !$siteNotice ) {
+ $siteNotice = wfGetCachedNotice( 'default' );
+ }
}
wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice ) );
@@ -1677,7 +1769,7 @@ function wfShellExec( $cmd, &$retval=null ) {
$output = array();
$retval = 1; // error by default?
- $lastline = exec( $cmd, $output, $retval );
+ exec( $cmd, $output, $retval ); // returns the last line of output.
return implode( "\n", $output );
}
@@ -1725,16 +1817,10 @@ function wfUseMW( $req_ver ) {
}
/**
- * Escape a string to make it suitable for inclusion in a preg_replace()
- * replacement parameter.
- *
- * @param string $string
- * @return string
+ * @deprecated use StringUtils::escapeRegexReplacement
*/
function wfRegexReplacement( $string ) {
- $string = str_replace( '\\', '\\\\', $string );
- $string = str_replace( '$', '\\$', $string );
- return $string;
+ return StringUtils::escapeRegexReplacement( $string );
}
/**
@@ -1749,6 +1835,7 @@ function wfRegexReplacement( $string ) {
* @return string
*/
function wfBaseName( $path ) {
+ $matches = array();
if( preg_match( '#([^/\\\\]*)[/\\\\]*$#', $path, $matches ) ) {
return $matches[1];
} else {
@@ -1804,42 +1891,12 @@ function wfDoUpdates()
}
/**
- * More or less "markup-safe" explode()
- * Ignores any instances of the separator inside <...>
- * @param string $separator
- * @param string $text
- * @return array
+ * @deprecated use StringUtils::explodeMarkup
*/
function wfExplodeMarkup( $separator, $text ) {
- $placeholder = "\x00";
-
- // Just in case...
- $text = str_replace( $placeholder, '', $text );
-
- // Trim stuff
- $replacer = new ReplacerCallback( $separator, $placeholder );
- $cleaned = preg_replace_callback( '/(<.*?>)/', array( $replacer, 'go' ), $text );
-
- $items = explode( $separator, $cleaned );
- foreach( $items as $i => $str ) {
- $items[$i] = str_replace( $placeholder, $separator, $str );
- }
-
- return $items;
+ return StringUtils::explodeMarkup( $separator, $text );
}
-class ReplacerCallback {
- function ReplacerCallback( $from, $to ) {
- $this->from = $from;
- $this->to = $to;
- }
-
- function go( $matches ) {
- return str_replace( $this->from, $this->to, $matches[1] );
- }
-}
-
-
/**
* Convert an arbitrarily-long digit string from one numeric base
* to another, optionally zero-padding to a minimum column width.
@@ -1999,7 +2056,7 @@ function wfGetPrecompiledData( $name ) {
}
function wfGetCaller( $level = 2 ) {
- $backtrace = debug_backtrace();
+ $backtrace = wfDebugBacktrace();
if ( isset( $backtrace[$level] ) ) {
if ( isset( $backtrace[$level]['class'] ) ) {
$caller = $backtrace[$level]['class'] . '::' . $backtrace[$level]['function'];
@@ -2020,7 +2077,7 @@ function wfGetAllCallers() {
$frame["class"]."::".$frame["function"]:
$frame["function"];
'),
- array_reverse(debug_backtrace())));
+ array_reverse(wfDebugBacktrace())));
}
/**
@@ -2063,4 +2120,19 @@ function wfWikiID() {
}
}
+/*
+ * Get a Database object
+ * @param integer $db Index of the connection to get. May be DB_MASTER for the
+ * master (for write queries), DB_SLAVE for potentially lagged
+ * read queries, or an integer >= 0 for a particular server.
+ *
+ * @param mixed $groups Query groups. An array of group names that this query
+ * belongs to. May contain a single string if the query is only
+ * in one group.
+ */
+function &wfGetDB( $db = DB_LAST, $groups = array() ) {
+ global $wgLoadBalancer;
+ $ret = $wgLoadBalancer->getConnection( $db, true, $groups );
+ return $ret;
+}
?>
diff --git a/includes/HTMLCacheUpdate.php b/includes/HTMLCacheUpdate.php
index 47703b20..bda4720d 100644
--- a/includes/HTMLCacheUpdate.php
+++ b/includes/HTMLCacheUpdate.php
@@ -55,7 +55,6 @@ class HTMLCacheUpdate
$numRows = $res->numRows();
$numBatches = ceil( $numRows / $this->mRowsPerJob );
$realBatchSize = $numRows / $numBatches;
- $boundaries = array();
$start = false;
$jobs = array();
do {
@@ -176,7 +175,7 @@ class HTMLCacheUpdate
# Update file cache
if ( $wgUseFileCache ) {
foreach ( $titles as $title ) {
- $cm = new CacheManager($title);
+ $cm = new HTMLFileCache($title);
@unlink($cm->fileCacheName());
}
}
diff --git a/includes/HTMLFileCache.php b/includes/HTMLFileCache.php
new file mode 100644
index 00000000..d85a4411
--- /dev/null
+++ b/includes/HTMLFileCache.php
@@ -0,0 +1,159 @@
+<?php
+/**
+ * Contain the HTMLFileCache class
+ * @package MediaWiki
+ * @subpackage Cache
+ */
+
+/**
+ * Handles talking to the file cache, putting stuff in and taking it back out.
+ * Mostly called from Article.php, also from DatabaseFunctions.php for the
+ * emergency abort/fallback to cache.
+ *
+ * Global options that affect this module:
+ * $wgCachePages
+ * $wgCacheEpoch
+ * $wgUseFileCache
+ * $wgFileCacheDirectory
+ * $wgUseGzip
+ * @package MediaWiki
+ */
+class HTMLFileCache {
+ var $mTitle, $mFileCache;
+
+ function HTMLFileCache( &$title ) {
+ $this->mTitle =& $title;
+ $this->mFileCache = '';
+ }
+
+ function fileCacheName() {
+ global $wgFileCacheDirectory;
+ if( !$this->mFileCache ) {
+ $key = $this->mTitle->getPrefixedDbkey();
+ $hash = md5( $key );
+ $key = str_replace( '.', '%2E', urlencode( $key ) );
+
+ $hash1 = substr( $hash, 0, 1 );
+ $hash2 = substr( $hash, 0, 2 );
+ $this->mFileCache = "{$wgFileCacheDirectory}/{$hash1}/{$hash2}/{$key}.html";
+
+ if($this->useGzip())
+ $this->mFileCache .= '.gz';
+
+ wfDebug( " fileCacheName() - {$this->mFileCache}\n" );
+ }
+ return $this->mFileCache;
+ }
+
+ function isFileCached() {
+ return file_exists( $this->fileCacheName() );
+ }
+
+ function fileCacheTime() {
+ return wfTimestamp( TS_MW, filemtime( $this->fileCacheName() ) );
+ }
+
+ function isFileCacheGood( $timestamp ) {
+ global $wgCacheEpoch;
+
+ if( !$this->isFileCached() ) return false;
+
+ $cachetime = $this->fileCacheTime();
+ $good = (( $timestamp <= $cachetime ) &&
+ ( $wgCacheEpoch <= $cachetime ));
+
+ wfDebug(" isFileCacheGood() - cachetime $cachetime, touched {$timestamp} epoch {$wgCacheEpoch}, good $good\n");
+ return $good;
+ }
+
+ function useGzip() {
+ global $wgUseGzip;
+ return $wgUseGzip;
+ }
+
+ /* In handy string packages */
+ function fetchRawText() {
+ return file_get_contents( $this->fileCacheName() );
+ }
+
+ function fetchPageText() {
+ if( $this->useGzip() ) {
+ /* Why is there no gzfile_get_contents() or gzdecode()? */
+ return implode( '', gzfile( $this->fileCacheName() ) );
+ } else {
+ return $this->fetchRawText();
+ }
+ }
+
+ /* Working directory to/from output */
+ function loadFromFileCache() {
+ global $wgOut, $wgMimeType, $wgOutputEncoding, $wgContLanguageCode;
+ wfDebug(" loadFromFileCache()\n");
+
+ $filename=$this->fileCacheName();
+ $wgOut->sendCacheControl();
+
+ header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" );
+ header( "Content-language: $wgContLanguageCode" );
+
+ if( $this->useGzip() ) {
+ if( wfClientAcceptsGzip() ) {
+ header( 'Content-Encoding: gzip' );
+ } else {
+ /* Send uncompressed */
+ readgzfile( $filename );
+ return;
+ }
+ }
+ readfile( $filename );
+ }
+
+ function checkCacheDirs() {
+ $filename = $this->fileCacheName();
+ $mydir2=substr($filename,0,strrpos($filename,'/')); # subdirectory level 2
+ $mydir1=substr($mydir2,0,strrpos($mydir2,'/')); # subdirectory level 1
+
+ if(!file_exists($mydir1)) { mkdir($mydir1,0775); } # create if necessary
+ if(!file_exists($mydir2)) { mkdir($mydir2,0775); }
+ }
+
+ function saveToFileCache( $origtext ) {
+ $text = $origtext;
+ if(strcmp($text,'') == 0) return '';
+
+ wfDebug(" saveToFileCache()\n", false);
+
+ $this->checkCacheDirs();
+
+ $f = fopen( $this->fileCacheName(), 'w' );
+ if($f) {
+ $now = wfTimestampNow();
+ if( $this->useGzip() ) {
+ $rawtext = str_replace( '</html>',
+ '<!-- Cached/compressed '.$now." -->\n</html>",
+ $text );
+ $text = gzencode( $rawtext );
+ } else {
+ $text = str_replace( '</html>',
+ '<!-- Cached '.$now." -->\n</html>",
+ $text );
+ }
+ fwrite( $f, $text );
+ fclose( $f );
+ if( $this->useGzip() ) {
+ if( wfClientAcceptsGzip() ) {
+ header( 'Content-Encoding: gzip' );
+ return $text;
+ } else {
+ return $rawtext;
+ }
+ } else {
+ return $text;
+ }
+ }
+ return $text;
+ }
+
+}
+
+?>
diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php
index 3ee85859..189e5c79 100644
--- a/includes/HTMLForm.php
+++ b/includes/HTMLForm.php
@@ -99,7 +99,7 @@ class HTMLForm {
if ( $this->mRequest->wasPosted() ) {
$arr = $this->mRequest->getArray( $varname );
if ( is_array( $arr ) ) {
- foreach ( $_POST[$varname] as $index => $element ) {
+ foreach ( $_POST[$varname] as $element ) {
$s .= htmlspecialchars( $element )."\n";
}
}
diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php
index 357c1d48..a06b620d 100644
--- a/includes/HistoryBlob.php
+++ b/includes/HistoryBlob.php
@@ -226,7 +226,7 @@ class HistoryBlobStub {
$flags = explode( ',', $row->old_flags );
if( in_array( 'external', $flags ) ) {
$url=$row->old_text;
- @list($proto,$path)=explode('://',$url,2);
+ @list( /* $proto */ ,$path)=explode('://',$url,2);
if ($path=="") {
wfProfileOut( $fname );
return false;
diff --git a/includes/Hooks.php b/includes/Hooks.php
index 575a28c5..2eecfd72 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -31,7 +31,6 @@
function wfRunHooks($event, $args = null) {
global $wgHooks;
- $fname = 'wfRunHooks';
if (!is_array($wgHooks)) {
throw new MWException("Global hooks array is not an array!\n");
diff --git a/includes/IP.php b/includes/IP.php
index f3ff3427..edf4af7a 100644
--- a/includes/IP.php
+++ b/includes/IP.php
@@ -10,11 +10,15 @@
// 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_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])');
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_PREFIX', '(3[0-2]|[12]?\d)');
define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX);
+// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
+define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
+define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
+define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
class IP {
@@ -23,7 +27,7 @@ class IP {
* @return boolean True if it is valid.
*/
public static function isValid( $ip ) {
- return preg_match( '/^' . RE_IP_ADD . '$/', $ip, $matches) ;
+ return preg_match( '/^' . RE_IP_ADD . '$/', $ip) ;
}
/**
@@ -74,12 +78,13 @@ class IP {
/**
* Split out an IP block as an array of 4 bytes and a mask,
- * return false if it cant be determined
+ * return false if it can't be determined
*
* @parameter $ip string A quad dotted IP address
* @return array
*/
public static function toArray( $ipblock ) {
+ $matches = array();
if(! preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
return false;
} else {
@@ -206,6 +211,50 @@ class IP {
} else {
return array( $start, $end );
}
- }
+ }
+
+ /**
+ * Determine if a given integer IPv4 address is in a given CIDR network
+ * @param $addr The address to check against the given range.
+ * @param $range The range to check the given address against.
+ * @return bool Whether or not the given address is in the given range.
+ */
+ public static function isInRange( $addr, $range ) {
+ $unsignedIP = IP::toUnsigned($addr);
+ list( $start, $end ) = IP::parseRange($range);
+
+ $start = hexdec($start);
+ $end = hexdec($end);
+
+ return (($unsignedIP >= $start) && ($unsignedIP <= $end));
+ }
+
+ /**
+ * Convert some unusual representations of IPv4 addresses to their
+ * canonical dotted quad representation.
+ *
+ * This currently only checks a few IPV4-to-IPv6 related cases. More
+ * unusual representations may be added later.
+ *
+ * @param $addr something that might be an IP address
+ * @return valid dotted quad IPv4 address or null
+ */
+ public static function canonicalize( $addr ) {
+ if ( IP::isValid( $addr ) )
+ return $addr;
+
+ // IPv6 loopback address
+ if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) )
+ return '127.0.0.1';
+
+ // IPv4-mapped and IPv4-compatible IPv6 addresses
+ if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) )
+ return $m[1];
+ if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD . ':' . RE_IPV6_WORD . '$/i', $addr, $m ) )
+ return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
+
+ return null; // give up
+ }
}
+
?>
diff --git a/includes/Image.php b/includes/Image.php
index 55e53e26..1f3895c6 100644
--- a/includes/Image.php
+++ b/includes/Image.php
@@ -58,7 +58,7 @@ class Image
* @param string $name name of the image, used to create a title object using Title::makeTitleSafe
* @public
*/
- function newFromName( $name ) {
+ public static function newFromName( $name ) {
$title = Title::makeTitleSafe( NS_IMAGE, $name );
if ( is_object( $title ) ) {
return new Image( $title );
@@ -235,7 +235,7 @@ class Image
* Load metadata from the file itself
*/
function loadFromFile() {
- global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang, $wgShowEXIF;
+ global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang;
wfProfileIn( __METHOD__ );
$this->imagePath = $this->getFullPath();
$this->fileExists = file_exists( $this->imagePath );
@@ -925,7 +925,7 @@ class Image
if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
$url = $this->getURL();
} else {
- list( $isScriptUrl, $url ) = $this->thumbUrl( $width );
+ list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $width );
}
$thumb = new ThumbnailImage( $url, $width, $height );
} else {
@@ -1360,15 +1360,17 @@ class Image
$dir = wfImageThumbDir( $this->name, $shared );
$urls = array();
foreach ( $files as $file ) {
+ $m = array();
if ( preg_match( '/^(\d+)px/', $file, $m ) ) {
- $urls[] = $this->thumbUrl( $m[1], $this->fromSharedDirectory );
+ list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $m[1] );
+ $urls[] = $url;
@unlink( "$dir/$file" );
}
}
// Purge the squid
if ( $wgUseSquid ) {
- $urls[] = $this->getViewURL();
+ $urls[] = $this->getURL();
foreach ( $archiveFiles as $file ) {
$urls[] = wfImageArchiveUrl( $file );
}
@@ -1461,7 +1463,7 @@ class Image
array( 'img_name' => $this->title->getDBkey() ),
__METHOD__
);
- if ( 0 == wfNumRows( $this->historyRes ) ) {
+ if ( 0 == $dbr->numRows( $this->historyRes ) ) {
return FALSE;
}
} else if ( $this->historyLine == 1 ) {
@@ -1701,7 +1703,7 @@ class Image
}
$linkCache =& LinkCache::singleton();
- extract( $db->tableNames( 'page', 'imagelinks' ) );
+ list( $page, $imagelinks ) = $db->tableNamesN( 'page', 'imagelinks' );
$encName = $db->addQuotes( $this->name );
$sql = "SELECT page_namespace,page_title,page_id FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options";
$res = $db->query( $sql, __METHOD__ );
diff --git a/includes/ImageFunctions.php b/includes/ImageFunctions.php
index d182d527..931fdff1 100644
--- a/includes/ImageFunctions.php
+++ b/includes/ImageFunctions.php
@@ -126,6 +126,7 @@ function wfScaleSVGUnit( $length ) {
'' => 1.0, // "User units" pixels by default
'%' => 2.0, // Fake it!
);
+ $matches = array();
if( preg_match( '/^(\d+(?:\.\d+)?)(em|ex|px|pt|pc|cm|mm|in|%|)$/', $length, $matches ) ) {
$length = floatval( $matches[1] );
$unit = $matches[2];
@@ -156,6 +157,7 @@ function wfGetSVGsize( $filename ) {
fclose( $f );
// Uber-crappy hack! Run through a real XML parser.
+ $matches = array();
if( !preg_match( '/<svg\s*([^>]*)\s*>/s', $chunk, $matches ) ) {
return false;
}
@@ -198,7 +200,7 @@ function wfIsBadImage( $name, $contextTitle = false ) {
if( !$badImages ) {
# Build the list now
$badImages = array();
- $lines = explode( "\n", wfMsgForContent( 'bad_image_list' ) );
+ $lines = explode( "\n", wfMsgForContentNoTrans( 'bad_image_list' ) );
foreach( $lines as $line ) {
# List items only
if ( substr( $line, 0, 1 ) !== '*' ) {
@@ -206,6 +208,7 @@ function wfIsBadImage( $name, $contextTitle = false ) {
}
# Find all links
+ $m = array();
if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) {
continue;
}
diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php
index 7ff456b6..9d58b7f6 100644
--- a/includes/ImageGallery.php
+++ b/includes/ImageGallery.php
@@ -42,11 +42,20 @@ class ImageGallery
}
/**
- * Set the caption
+ * Set the caption (as plain text)
*
* @param $caption Caption
*/
function setCaption( $caption ) {
+ $this->mCaption = htmlspecialchars( $caption );
+ }
+
+ /**
+ * Set the caption (as HTML)
+ *
+ * @param $caption Caption
+ */
+ function setCaptionHtml( $caption ) {
$this->mCaption = $caption;
}
@@ -134,20 +143,19 @@ class ImageGallery
*
*/
function toHTML() {
- global $wgLang, $wgIgnoreImageErrors, $wgGenerateThumbnailOnParse;
+ global $wgLang, $wgGenerateThumbnailOnParse;
$sk = $this->getSkin();
$s = '<table class="gallery" cellspacing="0" cellpadding="0">';
if( $this->mCaption )
- $s .= '<td class="galleryheader" colspan="4"><big>' . htmlspecialchars( $this->mCaption ) . '</big></td>';
+ $s .= '<td class="galleryheader" colspan="4"><big>' . $this->mCaption . '</big></td>';
$i = 0;
foreach ( $this->mImages as $pair ) {
$img =& $pair[0];
$text = $pair[1];
- $name = $img->getName();
$nt = $img->getTitle();
if( $nt->getNamespace() != NS_IMAGE ) {
@@ -206,6 +214,13 @@ class ImageGallery
return $s;
}
+
+ /**
+ * @return int Number of images in the gallery
+ */
+ public function count() {
+ return count( $this->mImages );
+ }
} //class
?>
diff --git a/includes/ImagePage.php b/includes/ImagePage.php
index 908dd5cc..43b99130 100644
--- a/includes/ImagePage.php
+++ b/includes/ImagePage.php
@@ -71,13 +71,13 @@ class ImagePage extends Article {
$this->imageHistory();
$this->imageLinks();
if( $exif ) {
- global $wgStylePath;
+ global $wgStylePath, $wgStyleVersion;
$expand = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-expand' ) ) );
$collapse = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-collapse' ) ) );
$wgOut->addHTML( "<h2 id=\"metadata\">" . wfMsgHtml( 'metadata' ) . "</h2>\n" );
$wgOut->addWikiText( $this->makeMetadataTable( $exif ) );
$wgOut->addHTML(
- "<script type=\"text/javascript\" src=\"$wgStylePath/common/metadata.js\"></script>\n" .
+ "<script type=\"text/javascript\" src=\"$wgStylePath/common/metadata.js?$wgStyleVersion\"></script>\n" .
"<script type=\"text/javascript\">attachMetadataToggle('mw_metadata', '$expand', '$collapse');</script>\n" );
}
} else {
@@ -142,6 +142,7 @@ class ImagePage extends Article {
$fields = array();
$lines = explode( "\n", wfMsgForContent( 'metadata-fields' ) );
foreach( $lines as $line ) {
+ $matches = array();
if( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
$fields[] = $matches[1];
}
@@ -169,12 +170,8 @@ class ImagePage extends Article {
$full_url = $this->img->getURL();
$anchoropen = '';
$anchorclose = '';
+ $sizeSel = intval( $wgUser->getOption( 'imagesize') );
- if( $wgUser->getOption( 'imagesize' ) == '' ) {
- $sizeSel = User::getDefaultOption( 'imagesize' );
- } else {
- $sizeSel = intval( $wgUser->getOption( 'imagesize' ) );
- }
if( !isset( $wgImageLimits[$sizeSel] ) ) {
$sizeSel = User::getDefaultOption( 'imagesize' );
}
@@ -247,7 +244,7 @@ class ImagePage extends Article {
$wgOut->addHTML( '<div class="fullImageLink" id="file">' . $anchoropen .
"<img border=\"0\" src=\"{$url}\" width=\"{$width}\" height=\"{$height}\" alt=\"" .
- htmlspecialchars( $wgRequest->getVal( 'image' ) ).'" />' . $anchorclose . '</div>' );
+ htmlspecialchars( $this->img->getTitle()->getPrefixedText() ).'" />' . $anchorclose . '</div>' );
if ( $this->img->isMultipage() ) {
$count = $this->img->pageCount();
@@ -300,9 +297,15 @@ class ImagePage extends Article {
if ($showLink) {
$filename = wfEscapeWikiText( $this->img->getName() );
+ // Hacky workaround: for some reason we use the incorrect MIME type
+ // image/svg for SVG. This should be fixed internally, but at least
+ // make the displayed type right.
+ $mime = $this->img->getMimeType();
+ if ($mime == 'image/svg') $mime = 'image/svg+xml';
+
$info = wfMsg( 'fileinfo',
ceil($this->img->getSize()/1024.0),
- $this->img->getMimeType() );
+ $mime );
global $wgContLang;
$dirmark = $wgContLang->getDirMark();
@@ -333,7 +336,7 @@ END
} else {
# Image does not exist
- $title = Title::makeTitle( NS_SPECIAL, 'Upload' );
+ $title = SpecialPage::getTitleFor( 'Upload' );
$link = $sk->makeKnownLinkObj($title, wfMsgHtml('noimage-linktext'),
'wpDestFile=' . urlencode( $this->img->getName() ) );
$wgOut->addHTML( wfMsgWikiHtml( 'noimage', $link ) );
@@ -348,7 +351,7 @@ END
if ($wgRepositoryBaseUrl && !$wgFetchCommonsDescriptions) {
$sk = $wgUser->getSkin();
- $title = Title::makeTitle( NS_SPECIAL, 'Upload' );
+ $title = SpecialPage::getTitleFor( 'Upload' );
$link = $sk->makeKnownLinkObj($title, wfMsgHtml('shareduploadwiki-linktext'),
array( 'wpDestFile' => urlencode( $this->img->getName() )));
$sharedtext .= " " . wfMsgWikiHtml('shareduploadwiki', $link);
@@ -365,7 +368,7 @@ END
function getUploadUrl() {
global $wgServer;
- $uploadTitle = Title::makeTitle( NS_SPECIAL, 'Upload' );
+ $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
return $wgServer . $uploadTitle->getLocalUrl( 'wpDestFile=' . urlencode( $this->img->getName() ) );
}
@@ -530,15 +533,10 @@ END
* @param $reason User provided reason for deletion.
*/
function doDelete( $reason ) {
- global $wgOut, $wgRequest, $wgUseSquid;
- global $wgPostCommitUpdateList;
-
- $fname = 'ImagePage::doDelete';
+ global $wgOut, $wgRequest;
$oldimage = $wgRequest->getVal( 'oldimage' );
- $dbw =& wfGetDB( DB_MASTER );
-
if ( !is_null( $oldimage ) ) {
if ( strlen( $oldimage ) < 16 ) {
$wgOut->showUnexpectedValueError( 'oldimage', htmlspecialchars($oldimage) );
diff --git a/includes/Licenses.php b/includes/Licenses.php
index aaa44052..dd1308b4 100644
--- a/includes/Licenses.php
+++ b/includes/Licenses.php
@@ -63,12 +63,14 @@ class Licenses {
$obj = new License( $line );
$this->stackItem( $this->licenses, $levels, $obj );
} else {
- if ( $level < count( $levels ) )
+ if ( $level < count( $levels ) ) {
$levels = array_slice( $levels, 0, $level );
- if ( $level == count( $levels ) )
+ }
+ if ( $level == count( $levels ) ) {
$levels[$level - 1] = $line;
- else if ( $level > count( $levels ) )
+ } else if ( $level > count( $levels ) ) {
$levels[] = $line;
+ }
}
}
}
diff --git a/includes/LinkBatch.php b/includes/LinkBatch.php
index 061f1b19..61e1c040 100644
--- a/includes/LinkBatch.php
+++ b/includes/LinkBatch.php
@@ -97,7 +97,7 @@ class LinkBatch {
// The remaining links in $data are bad links, register them as such
foreach ( $remaining as $ns => $dbkeys ) {
- foreach ( $dbkeys as $dbkey => $nothing ) {
+ foreach ( $dbkeys as $dbkey => $unused ) {
$title = Title::makeTitle( $ns, $dbkey );
$cache->addBadLinkObj( $title );
$ids[$title->getPrefixedDBkey()] = 0;
@@ -112,7 +112,6 @@ class LinkBatch {
*/
function doQuery() {
$fname = 'LinkBatch::doQuery';
- $namespaces = array();
if ( $this->isEmpty() ) {
return false;
@@ -161,7 +160,7 @@ class LinkBatch {
$sql .= "({$prefix}_namespace=$ns AND {$prefix}_title IN (";
$firstTitle = true;
- foreach( $dbkeys as $dbkey => $nothing ) {
+ foreach( $dbkeys as $dbkey => $unused ) {
if ( $firstTitle ) {
$firstTitle = false;
} else {
diff --git a/includes/Linker.php b/includes/Linker.php
index d34971ff..0eabab2f 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -16,7 +16,6 @@
* @package MediaWiki
*/
class Linker {
-
function Linker() {}
/**
@@ -39,7 +38,6 @@ class Linker {
function getInterwikiLinkAttributes( $link, $text, $class='' ) {
global $wgContLang;
- $same = ($link == $text);
$link = urldecode( $link );
$link = $wgContLang->checkTitleEncoding( $link );
$link = preg_replace( '/[\\x00-\\x1f]/', ' ', $link );
@@ -180,12 +178,14 @@ class Linker {
* call this lots of times, pre-fill the link cache with a LinkBatch, otherwise each
* call to this will result in a DB query.
*
- * @param $title String: the text of the title
+ * @param $nt Title: the title object to make the link from, e.g. from
+ * Title::newFromText.
* @param $text String: link text
* @param $query String: optional query part
* @param $trail String: optional trail. Alphabetic characters at the start of this string will
* be included in the link text. Other characters will be appended after
* the end of the link.
+ * @param $prefix String: optional prefix. As trail, only before instead of after.
*/
function makeLinkObj( $nt, $text= '', $query = '', $trail = '', $prefix = '' ) {
global $wgUser;
@@ -199,8 +199,6 @@ class Linker {
return "<!-- ERROR -->{$prefix}{$text}{$trail}";
}
- $ns = $nt->getNamespace();
- $dbkey = $nt->getDBkey();
if ( $nt->isExternal() ) {
$u = $nt->getFullURL();
$link = $nt->getPrefixedURL();
@@ -209,27 +207,12 @@ class Linker {
$inside = '';
if ( '' != $trail ) {
+ $m = array();
if ( preg_match( '/^([a-z]+)(.*)$$/sD', $trail, $m ) ) {
$inside = $m[1];
$trail = $m[2];
}
}
-
- # Check for anchors, normalize the anchor
-
- $parts = explode( '#', $u, 2 );
- if ( count( $parts ) == 2 ) {
- $anchor = urlencode( Sanitizer::decodeCharReferences( str_replace(' ', '_', $parts[1] ) ) );
- $replacearray = array(
- '%3A' => ':',
- '%' => '.'
- );
- $u = $parts[0] . '#' .
- str_replace( array_keys( $replacearray ),
- array_values( $replacearray ),
- $anchor );
- }
-
$t = "<a href=\"{$u}\"{$style}>{$text}{$inside}</a>";
wfProfileOut( $fname );
@@ -308,12 +291,7 @@ class Linker {
$text = htmlspecialchars( $nt->getFragment() );
}
}
- $anchor = urlencode( Sanitizer::decodeCharReferences( str_replace( ' ', '_', $nt->getFragment() ) ) );
- $replacearray = array(
- '%3A' => ':',
- '%' => '.'
- );
- $u .= '#' . str_replace(array_keys($replacearray),array_values($replacearray),$anchor);
+ $u .= $nt->getFragmentForURL();
}
if ( $text == '' ) {
$text = htmlspecialchars( $nt->getPrefixedText() );
@@ -380,8 +358,6 @@ class Linker {
* the end of the link.
*/
function makeStubLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
- $link = $nt->getPrefixedURL();
-
$u = $nt->escapeLocalURL( $query );
if ( '' == $text ) {
@@ -535,6 +511,8 @@ class Linker {
$url = $thumb->getUrl();
} else {
$error = htmlspecialchars( $img->getLastError() );
+ // Do client-side scaling...
+ $height = intval( $img->getHeight() * $width / $img->getWidth() );
}
}
} else {
@@ -627,10 +605,14 @@ class Linker {
$magnifyalign = $wgContLang->isRTL() ? 'left' : 'right';
$textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : '';
- $s = "<div class=\"thumb t{$align}\"><div style=\"width:{$oboxwidth}px;\">";
+ $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$oboxwidth}px;\">";
if( $thumbUrl == '' ) {
// Couldn't generate thumbnail? Scale the image client-side.
$thumbUrl = $img->getViewURL();
+ if( $boxheight == -1 ) {
+ // Approximate...
+ $boxheight = intval( $height * $boxwidth / $width );
+ }
}
if ( $error ) {
$s .= htmlspecialchars( $error );
@@ -642,14 +624,14 @@ class Linker {
$s .= '<a href="'.$u.'" class="internal" title="'.$alt.'">'.
'<img src="'.$thumbUrl.'" alt="'.$alt.'" ' .
'width="'.$boxwidth.'" height="'.$boxheight.'" ' .
- 'longdesc="'.$u.'" /></a>';
+ 'longdesc="'.$u.'" class="thumbimage" /></a>';
if ( $framed ) {
$zoomicon="";
} else {
$zoomicon = '<div class="magnify" style="float:'.$magnifyalign.'">'.
'<a href="'.$u.'" class="internal" title="'.$more.'">'.
'<img src="'.$wgStylePath.'/common/images/magnify-clip.png" ' .
- 'width="15" height="11" alt="'.$more.'" /></a></div>';
+ 'width="15" height="11" alt="" /></a></div>';
}
}
$s .= ' <div class="thumbcaption"'.$textalign.'>'.$zoomicon.$label."</div></div></div>";
@@ -673,7 +655,7 @@ class Linker {
if ( '' != $query ) {
$q .= "&$query";
}
- $uploadTitle = Title::makeTitle( NS_SPECIAL, 'Upload' );
+ $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
$url = $uploadTitle->escapeLocalURL( $q );
if ( '' == $text ) {
@@ -710,13 +692,12 @@ class Linker {
### HOTFIX. Instead of breaking, return empty string.
return $text;
} else {
- $name = $title->getDBKey();
$img = new Image( $title );
if( $img->exists() ) {
$url = $img->getURL();
$class = 'internal';
} else {
- $upload = Title::makeTitle( NS_SPECIAL, 'Upload' );
+ $upload = SpecialPage::getTitleFor( 'Upload' );
$url = $upload->getLocalUrl( 'wpDestFile=' . urlencode( $img->getName() ) );
$class = 'new';
}
@@ -763,9 +744,9 @@ class Linker {
function userLink( $userId, $userText ) {
$encName = htmlspecialchars( $userText );
if( $userId == 0 ) {
- $contribsPage = Title::makeTitle( NS_SPECIAL, 'Contributions' );
+ $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
return $this->makeKnownLinkObj( $contribsPage,
- $encName, 'target=' . urlencode( $userText ) );
+ $encName);
} else {
$userPage = Title::makeTitle( NS_USER, $userText );
return $this->makeLinkObj( $userPage, $encName );
@@ -788,9 +769,9 @@ class Linker {
$items[] = $this->userTalkLink( $userId, $userText );
}
if( $userId ) {
- $contribsPage = Title::makeTitle( NS_SPECIAL, 'Contributions' );
- $items[] = $this->makeKnownLinkObj( $contribsPage,
- wfMsgHtml( 'contribslink' ), 'target=' . urlencode( $userText ) );
+ $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
+ $items[] = $this->makeKnownLinkObj( $contribsPage ,
+ wfMsgHtml( 'contribslink' ) );
}
if( $blockable && $wgUser->isAllowed( 'block' ) ) {
$items[] = $this->blockLink( $userId, $userText );
@@ -825,9 +806,9 @@ class Linker {
* @private
*/
function blockLink( $userId, $userText ) {
- $blockPage = Title::makeTitle( NS_SPECIAL, 'Blockip' );
+ $blockPage = SpecialPage::getTitleFor( 'Blockip', $userText );
$blockLink = $this->makeKnownLinkObj( $blockPage,
- wfMsgHtml( 'blocklink' ), 'ip=' . urlencode( $userText ) );
+ wfMsgHtml( 'blocklink' ) );
return $blockLink;
}
@@ -873,17 +854,18 @@ class Linker {
* comments. It escapes any HTML in the comment, but adds some CSS to format
* auto-generated comments (from section editing) and formats [[wikilinks]].
*
- * The $title parameter must be a title OBJECT. It is used to generate a
- * direct link to the section in the autocomment.
* @author Erik Moeller <moeller@scireview.de>
*
* Note: there's not always a title to pass to this function.
* Since you can't set a default parameter for a reference, I've turned it
* temporarily to a value pass. Should be adjusted further. --brion
+ *
+ * $param string $comment
+ * @param mixed $title Title object (to generate link to the section in autocomment) or null
+ * @param bool $local Whether section links should refer to local page
*/
- function formatComment($comment, $title = NULL) {
- $fname = 'Linker::formatComment';
- wfProfileIn( $fname );
+ function formatComment($comment, $title = NULL, $local = false) {
+ wfProfileIn( __METHOD__ );
global $wgContLang;
$comment = str_replace( "\n", " ", $comment );
@@ -893,6 +875,7 @@ class Linker {
# some nasty regex.
# We look for all comments, match any text before and after the comment,
# add a separator where needed and format the comment itself with CSS
+ $match = array();
while (preg_match('/(.*)\/\*\s*(.*?)\s*\*\/(.*)/', $comment,$match)) {
$pre=$match[1];
$auto=$match[2];
@@ -909,8 +892,12 @@ class Linker {
$section = str_replace( '[[:', '', $section );
$section = str_replace( '[[', '', $section );
$section = str_replace( ']]', '', $section );
- $sectionTitle = wfClone( $title );
- $sectionTitle->mFragment = $section;
+ if ( $local ) {
+ $sectionTitle = Title::newFromText( '#' . $section);
+ } else {
+ $sectionTitle = wfClone( $title );
+ $sectionTitle->mFragment = $section;
+ }
$link = $this->makeKnownLinkObj( $sectionTitle, wfMsg( 'sectionlink' ) );
}
$sep='-';
@@ -923,14 +910,16 @@ class Linker {
# format regular and media links - all other wiki formatting
# is ignored
- $medians = $wgContLang->getNsText( NS_MEDIA ) . ':';
- while(preg_match('/\[\[(.*?)(\|(.*?))*\]\](.*)$/',$comment,$match)) {
+ $medians = '(?:' . preg_quote( Namespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
+ $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):';
+ while(preg_match('/\[\[:?(.*?)(\|(.*?))*\]\](.*)$/',$comment,$match)) {
# Handle link renaming [[foo|text]] will show link as "text"
if( "" != $match[3] ) {
$text = $match[3];
} else {
$text = $match[1];
}
+ $submatch = array();
if( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
# Media link; trail not supported.
$linkRegexp = '/\[\[(.*?)\]\]/';
@@ -943,13 +932,13 @@ class Linker {
$trail = "";
}
$linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
- if ($match[1][0] == ':')
+ if (isset($match[1][0]) && $match[1][0] == ':')
$match[1] = substr($match[1], 1);
$thelink = $this->makeLink( $match[1], $text, "", $trail );
}
- $comment = preg_replace( $linkRegexp, wfRegexReplacement( $thelink ), $comment, 1 );
+ $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 );
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $comment;
}
@@ -957,19 +946,20 @@ class Linker {
* Wrap a comment in standard punctuation and formatting if
* it's non-empty, otherwise return empty string.
*
- * @param $comment String: the comment.
- * @param $title Title object.
+ * @param string $comment
+ * @param mixed $title Title object (to generate link to section in autocomment) or null
+ * @param bool $local Whether section links should refer to local page
*
* @return string
*/
- function commentBlock( $comment, $title = NULL ) {
+ function commentBlock( $comment, $title = NULL, $local = false ) {
// '*' used to be the comment inserted by the software way back
// in antiquity in case none was provided, here for backwards
// compatability, acc. to brion -ævar
if( $comment == '' || $comment == '*' ) {
return '';
} else {
- $formatted = $this->formatComment( $comment, $title );
+ $formatted = $this->formatComment( $comment, $title, $local );
return " <span class=\"comment\">($formatted)</span>";
}
}
@@ -977,12 +967,14 @@ class Linker {
/**
* Wrap and format the given revision's comment block, if the current
* user is allowed to view it.
- * @param $rev Revision object.
+ *
+ * @param Revision $rev
+ * @param bool $local Whether section links should refer to local page
* @return string HTML
*/
- function revComment( $rev ) {
+ function revComment( Revision $rev, $local = false ) {
if( $rev->userCan( Revision::DELETED_COMMENT ) ) {
- $block = $this->commentBlock( $rev->getRawComment(), $rev->getTitle() );
+ $block = $this->commentBlock( $rev->getRawComment(), $rev->getTitle(), $local );
} else {
$block = " <span class=\"comment\">" .
wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
@@ -1039,44 +1031,46 @@ class Linker {
}
/** @todo document */
- function editSectionLinkForOther( $title, $section ) {
+ public function editSectionLinkForOther( $title, $section ) {
global $wgContLang;
$title = Title::newFromText( $title );
$editurl = '&section='.$section;
$url = $this->makeKnownLinkObj( $title, wfMsg('editsection'), 'action=edit'.$editurl );
- if( $wgContLang->isRTL() ) {
- $farside = 'left';
- $nearside = 'right';
- } else {
- $farside = 'right';
- $nearside = 'left';
- }
- return "<div class=\"editsection\" style=\"float:$farside;margin-$nearside:5px;\">[".$url."]</div>";
+ return "<span class=\"editsection\">[".$url."]</span>";
}
- /**
+ /**
* @param $title Title object.
* @param $section Integer: section number.
* @param $hint Link String: title, or default if omitted or empty
*/
- function editSectionLink( $nt, $section, $hint='' ) {
+ public function editSectionLink( $nt, $section, $hint='' ) {
global $wgContLang;
$editurl = '&section='.$section;
$hint = ( $hint=='' ) ? '' : ' title="' . wfMsgHtml( 'editsectionhint', htmlspecialchars( $hint ) ) . '"';
$url = $this->makeKnownLinkObj( $nt, wfMsg('editsection'), 'action=edit'.$editurl, '', '', '', $hint );
- if( $wgContLang->isRTL() ) {
- $farside = 'left';
- $nearside = 'right';
- } else {
- $farside = 'right';
- $nearside = 'left';
- }
- return "<div class=\"editsection\" style=\"float:$farside;margin-$nearside:5px;\">[".$url."]</div>";
+ return "<span class=\"editsection\">[".$url."]</span>";
+ }
+
+ /**
+ * Create a headline for content
+ *
+ * @param int $level The level of the headline (1-6)
+ * @param string $attribs Any attributes for the headline, starting with a space and ending with '>'
+ * This *must* be at least '>' for no attribs
+ * @param string $anchor The anchor to give the headline (the bit after the #)
+ * @param string $text The text of the header
+ * @param string $link HTML to add for the section edit link
+ *
+ * @return string HTML headline
+ */
+ public function makeHeadline( $level, $attribs, $anchor, $text, $link ) {
+ return "<a name=\"$anchor\"></a><h$level$attribs$link <span class=\"mw-headline\">$text</span></h$level>";
}
/**
@@ -1093,6 +1087,7 @@ class Linker {
}
$inside = '';
if ( '' != $trail ) {
+ $m = array();
if ( preg_match( $regex, $trail, $m ) ) {
$inside = $m[1];
$trail = $m[2];
@@ -1101,5 +1096,112 @@ class Linker {
return array( $inside, $trail );
}
+ /**
+ * Generate a rollback link for a given revision. Currently it's the
+ * caller's responsibility to ensure that the revision is the top one. If
+ * it's not, of course, the user will get an error message.
+ *
+ * If the calling page is called with the parameter &bot=1, all rollback
+ * links also get that parameter. It causes the edit itself and the rollback
+ * to be marked as "bot" edits. Bot edits are hidden by default from recent
+ * changes, so this allows sysops to combat a busy vandal without bothering
+ * other users.
+ *
+ * @param Revision $rev
+ */
+ function generateRollback( $rev ) {
+ global $wgUser, $wgRequest;
+ $title = $rev->getTitle();
+
+ $extraRollback = $wgRequest->getBool( 'bot' ) ? '&bot=1' : '';
+ $extraRollback .= '&token=' . urlencode(
+ $wgUser->editToken( array( $title->getPrefixedText(), $rev->getUserText() ) ) );
+ return '<span class="mw-rollback-link">['. $this->makeKnownLinkObj( $title,
+ wfMsg('rollbacklink'),
+ 'action=rollback&from=' . urlencode( $rev->getUserText() ) . $extraRollback ) .']</span>';
+ }
+
+ /**
+ * Returns HTML for the "templates used on this page" list.
+ *
+ * @param array $templates Array of templates from Article::getUsedTemplate
+ * or similar
+ * @param bool $preview Whether this is for a preview
+ * @param bool $section Whether this is for a section edit
+ * @return string HTML output
+ */
+ public function formatTemplates( $templates, $preview = false, $section = false) {
+ global $wgUser;
+ wfProfileIn( __METHOD__ );
+
+ $sk =& $wgUser->getSkin();
+
+ $outText = '';
+ if ( count( $templates ) > 0 ) {
+ # Do a batch existence check
+ $batch = new LinkBatch;
+ foreach( $templates as $title ) {
+ $batch->addObj( $title );
+ }
+ $batch->execute();
+
+ # Construct the HTML
+ $outText = '<div class="mw-templatesUsedExplanation">';
+ if ( $preview ) {
+ $outText .= wfMsgExt( 'templatesusedpreview', array( 'parse' ) );
+ } elseif ( $section ) {
+ $outText .= wfMsgExt( 'templatesusedsection', array( 'parse' ) );
+ } else {
+ $outText .= wfMsgExt( 'templatesused', array( 'parse' ) );
+ }
+ $outText .= '</div><ul>';
+
+ foreach ( $templates as $titleObj ) {
+ $r = $titleObj->getRestrictions( 'edit' );
+ if ( in_array( 'sysop', $r ) ) {
+ $protected = wfMsgExt( 'template-protected', array( 'parseinline' ) );
+ } elseif ( in_array( 'autoconfirmed', $r ) ) {
+ $protected = wfMsgExt( 'template-semiprotected', array( 'parseinline' ) );
+ } else {
+ $protected = '';
+ }
+ $outText .= '<li>' . $sk->makeLinkObj( $titleObj ) . ' ' . $protected . '</li>';
+ }
+ $outText .= '</ul>';
+ }
+ wfProfileOut( __METHOD__ );
+ return $outText;
+ }
+
+ /**
+ * Format a size in bytes for output, using an appropriate
+ * unit (B, KB, MB or GB) according to the magnitude in question
+ *
+ * @param $size Size to format
+ * @return string
+ */
+ public function formatSize( $size ) {
+ global $wgLang;
+ if( $size > 1024 ) {
+ $size = $size / 1024;
+ if( $size > 1024 ) {
+ $size = $size / 1024;
+ if( $size > 1024 ) {
+ $size = $size / 1024;
+ $msg = 'size-gigabytes';
+ } else {
+ $msg = 'size-megabytes';
+ }
+ } else {
+ $msg = 'size-kilobytes';
+ }
+ } else {
+ $msg = 'size-bytes';
+ }
+ $size = round( $size, 0 );
+ return wfMsgHtml( $msg, $wgLang->formatNum( $size ) );
+ }
+
}
+
?>
diff --git a/includes/LoadBalancer.php b/includes/LoadBalancer.php
index 3e81aea9..396ef865 100644
--- a/includes/LoadBalancer.php
+++ b/includes/LoadBalancer.php
@@ -141,6 +141,9 @@ class LoadBalancer {
$i = false;
if ( $this->mForce >= 0 ) {
$i = $this->mForce;
+ } elseif ( count( $this->mServers ) == 1 ) {
+ # Skip the load balancing if there's only one server
+ $i = 0;
} else {
if ( $this->mReadIndex >= 0 ) {
$i = $this->mReadIndex;
@@ -171,10 +174,9 @@ class LoadBalancer {
unset( $loads[$i] );
$sleepTime = 0;
} else {
- $status = $this->mConnections[$i]->getStatus("Thread%");
- if ( isset( $this->mServers[$i]['max threads'] ) &&
- $status['Threads_running'] > $this->mServers[$i]['max threads'] )
- {
+ if ( isset( $this->mServers[$i]['max threads'] ) ) {
+ $status = $this->mConnections[$i]->getStatus("Thread%");
+ if ( $status['Threads_running'] > $this->mServers[$i]['max threads'] ) {
# 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.
@@ -182,9 +184,13 @@ class LoadBalancer {
# If we reach the timeout and exit the loop, don't use it
$i = false;
- } else {
+ } else {
$done = true;
$sleepTime = 0;
+ }
+ } else {
+ $done = true;
+ $sleepTime = 0;
}
}
} else {
diff --git a/includes/LogPage.php b/includes/LogPage.php
index 954b178f..dd395126 100644
--- a/includes/LogPage.php
+++ b/includes/LogPage.php
@@ -74,7 +74,7 @@ class LogPage {
# And update recentchanges
if ( $this->updateRecentChanges ) {
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Log/' . $this->type );
+ $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
$rcComment = $this->actionText;
if( '' != $this->comment ) {
if ($rcComment == '')
@@ -107,7 +107,7 @@ class LogPage {
/**
* @static
*/
- function logName( $type ) {
+ public static function logName( $type ) {
global $wgLogNames;
if( isset( $wgLogNames[$type] ) ) {
@@ -150,7 +150,7 @@ class LogPage {
$titleLink = $title->getText();
} else {
$titleLink = $skin->makeLinkObj( $title, $title->getText() );
- $titleLink .= ' (' . $skin->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions/' . $title->getDBkey() ), wfMsg( 'contribslink' ) ) . ')';
+ $titleLink .= ' (' . $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() ), wfMsg( 'contribslink' ) ) . ')';
}
break;
case 'rights':
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index 68cbe345..60bfd0f4 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -101,6 +101,7 @@ class MagicWord {
'contentlanguage',
'pagesinnamespace',
'numberofadmins',
+ 'defaultsort',
);
static public $mObjects = array();
@@ -289,7 +290,7 @@ class MagicWord {
* Used in matchAndRemove()
* @private
**/
- function pregRemoveAndRecord( $match ) {
+ function pregRemoveAndRecord( ) {
$this->mFound = true;
return '';
}
@@ -298,7 +299,7 @@ class MagicWord {
* Replaces the word with something else
*/
function replace( $replacement, $subject, $limit=-1 ) {
- $res = preg_replace( $this->getRegex(), wfRegexReplacement( $replacement ), $subject, $limit );
+ $res = preg_replace( $this->getRegex(), StringUtils::escapeRegexReplacement( $replacement ), $subject, $limit );
$this->mModified = !($res === $subject);
return $res;
}
diff --git a/includes/Math.php b/includes/Math.php
index a8b33984..9fa631f7 100644
--- a/includes/Math.php
+++ b/includes/Math.php
@@ -39,6 +39,9 @@ class MathRenderer {
# No need to render or parse anything more!
return ('$ '.htmlspecialchars( $this->tex ).' $');
}
+ if( $this->tex == '' ) {
+ return; # bug 8372
+ }
if( !$this->_recall() ) {
# Ensure that the temp and output directories are available before continuing...
@@ -75,12 +78,13 @@ class MathRenderer {
$retval = substr ($contents, 0, 1);
$errmsg = '';
if (($retval == 'C') || ($retval == 'M') || ($retval == 'L')) {
- if ($retval == 'C')
+ if ($retval == 'C') {
$this->conservativeness = 2;
- else if ($retval == 'M')
+ } else if ($retval == 'M') {
$this->conservativeness = 1;
- else
+ } else {
$this->conservativeness = 0;
+ }
$outdata = substr ($contents, 33);
$i = strpos($outdata, "\000");
@@ -89,12 +93,13 @@ class MathRenderer {
$this->mathml = substr($outdata, $i+1);
} else if (($retval == 'c') || ($retval == 'm') || ($retval == 'l')) {
$this->html = substr ($contents, 33);
- if ($retval == 'c')
+ if ($retval == 'c') {
$this->conservativeness = 2;
- else if ($retval == 'm')
+ } else if ($retval == 'm') {
$this->conservativeness = 1;
- else
+ } else {
$this->conservativeness = 0;
+ }
$this->mathml = NULL;
} else if ($retval == 'X') {
$this->html = NULL;
@@ -118,7 +123,7 @@ class MathRenderer {
$this->hash = substr ($contents, 1, 32);
}
- $res = wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) );
+ wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) );
if ( $errmsg ) {
return $errmsg;
diff --git a/includes/MessageCache.php b/includes/MessageCache.php
index 9cab222b..a269c620 100644
--- a/includes/MessageCache.php
+++ b/includes/MessageCache.php
@@ -11,10 +11,11 @@
define( 'MSG_LOAD_TIMEOUT', 60);
define( 'MSG_LOCK_TIMEOUT', 10);
define( 'MSG_WAIT_TIMEOUT', 10);
+define( 'MSG_CACHE_VERSION', 1 );
/**
* Message cache
- * Performs various useful MediaWiki namespace-related functions
+ * Performs various MediaWiki namespace-related functions
*
* @package MediaWiki
*/
@@ -79,7 +80,7 @@ class MessageCache {
if ( $hash == $localHash ) {
// All good, get the rest of it
$serialized = fread( $file, 10000000 );
- $this->mCache = unserialize( $serialized );
+ $this->setCache( unserialize( $serialized ) );
}
fclose( $file );
}
@@ -130,6 +131,7 @@ class MessageCache {
return;
}
require("$wgLocalMessageCache/messages-" . wfWikiID());
+ $this->setCache( $this->mCache);
}
function saveToScript($array, $hash) {
@@ -162,6 +164,17 @@ class MessageCache {
}
/**
+ * Set the cache to $cache, if it is valid. Otherwise set the cache to false.
+ */
+ function setCache( $cache ) {
+ if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
+ $this->mCache = $cache;
+ } else {
+ $this->mCache = false;
+ }
+ }
+
+ /**
* Loads messages either from memcached or the database, if not disabled
* On error, quietly switches to a fallback mode
* Returns false for a reportable error, true otherwise
@@ -177,110 +190,104 @@ class MessageCache {
}
return true;
}
+ if ( !$this->mUseCache ) {
+ $this->mDeferred = false;
+ return true;
+ }
+
$fname = 'MessageCache::load';
wfProfileIn( $fname );
$success = true;
- if ( $this->mUseCache ) {
- $this->mCache = false;
+ $this->mCache = false;
- # Try local cache
- wfProfileIn( $fname.'-fromlocal' );
- $hash = $this->mMemc->get( "{$this->mMemcKey}-hash" );
- if ( $hash ) {
- if ($wgLocalMessageCacheSerialized) {
- $this->loadFromLocal( $hash );
- } else {
- $this->loadFromScript( $hash );
- }
- if ( $this->mCache ) {
- wfDebug( "MessageCache::load(): got from local cache\n" );
- }
+ # Try local cache
+ wfProfileIn( $fname.'-fromlocal' );
+ $hash = $this->mMemc->get( "{$this->mMemcKey}-hash" );
+ if ( $hash ) {
+ if ($wgLocalMessageCacheSerialized) {
+ $this->loadFromLocal( $hash );
+ } else {
+ $this->loadFromScript( $hash );
}
- wfProfileOut( $fname.'-fromlocal' );
-
- # Try memcached
- if ( !$this->mCache ) {
- wfProfileIn( $fname.'-fromcache' );
- $this->mCache = $this->mMemc->get( $this->mMemcKey );
- 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' );
+ if ( $this->mCache ) {
+ wfDebug( "MessageCache::load(): got from local cache\n" );
}
-
-
- # If there's nothing in memcached, load all the messages from the database
- if ( !$this->mCache ) {
- wfDebug( "MessageCache::load(): loading all messages\n" );
- $this->lock();
- # Other threads don't need to load the messages if another thread is doing it.
- $success = $this->mMemc->add( $this->mMemcKey.'-status', "loading", MSG_LOAD_TIMEOUT );
- if ( $success ) {
- wfProfileIn( $fname.'-load' );
- $this->loadFromDB();
- wfProfileOut( $fname.'-load' );
-
- # Save in memcached
- # Keep trying if it fails, this is kind of important
- wfProfileIn( $fname.'-save' );
- for ($i=0; $i<20 &&
- !$this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry );
- $i++ ) {
- usleep(mt_rand(500000,1500000));
- }
-
- # Save to local cache
- if ( $wgLocalMessageCache !== false ) {
- $serialized = serialize( $this->mCache );
+ }
+ wfProfileOut( $fname.'-fromlocal' );
+
+ # Try memcached
+ if ( !$this->mCache ) {
+ wfProfileIn( $fname.'-fromcache' );
+ $this->setCache( $this->mMemc->get( $this->mMemcKey ) );
+ 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.'-save' );
- if ( $i == 20 ) {
- $this->mMemc->set( $this->mMemcKey.'-status', 'error', 60*5 );
- wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
+ if ($wgLocalMessageCacheSerialized) {
+ $this->saveToLocal( $serialized,$hash );
+ } else {
+ $this->saveToScript( $this->mCache, $hash );
}
}
- $this->unlock();
}
+ wfProfileOut( $fname.'-fromcache' );
+ }
+
+
+ # If there's nothing in memcached, load all the messages from the database
+ if ( !$this->mCache ) {
+ wfDebug( "MessageCache::load(): cache is empty\n" );
+ $this->lock();
+ # Other threads don't need to load the messages if another thread is doing it.
+ $success = $this->mMemc->add( $this->mMemcKey.'-status', "loading", MSG_LOAD_TIMEOUT );
+ if ( $success ) {
+ wfProfileIn( $fname.'-load' );
+ wfDebug( "MessageCache::load(): loading all messages from DB\n" );
+ $this->loadFromDB();
+ wfProfileOut( $fname.'-load' );
+
+ # Save in memcached
+ # Keep trying if it fails, this is kind of important
+ wfProfileIn( $fname.'-save' );
+ for ($i=0; $i<20 &&
+ !$this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry );
+ $i++ ) {
+ usleep(mt_rand(500000,1500000));
+ }
- if ( !is_array( $this->mCache ) ) {
- wfDebug( "MessageCache::load(): individual message mode\n" );
- # If it is 'loading' or 'error', switch to individual message mode, otherwise disable
- # Causing too much DB load, disabling -- TS
- $this->mDisable = true;
- /*
- if ( $this->mCache == "loading" ) {
- $this->mUseCache = false;
- } elseif ( $this->mCache == "error" ) {
- $this->mUseCache = false;
- $success = false;
+ # Save to local cache
+ if ( $wgLocalMessageCache !== false ) {
+ $serialized = serialize( $this->mCache );
+ $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.'-save' );
+ if ( $i == 20 ) {
+ $this->mMemc->set( $this->mMemcKey.'-status', 'error', 60*5 );
+ wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
} else {
- $this->mDisable = true;
- $success = false;
- }*/
- $this->mCache = false;
+ $this->mMemc->delete( $this->mMemcKey.'-status' );
+ }
}
+ $this->unlock();
+ }
+
+ if ( !is_array( $this->mCache ) ) {
+ wfDebug( "MessageCache::load(): unable to load cache, disabled\n" );
+ $this->mDisable = true;
+ $this->mCache = false;
}
wfProfileOut( $fname );
$this->mDeferred = false;
@@ -291,50 +298,42 @@ class MessageCache {
* Loads all or main part of cacheable messages from the database
*/
function loadFromDB() {
- global $wgLang;
+ global $wgLang, $wgMaxMsgCacheEntrySize;
- $fname = 'MessageCache::loadFromDB';
+ wfProfileIn( __METHOD__ );
$dbr =& wfGetDB( DB_SLAVE );
- if ( !$dbr ) {
- throw new MWException( 'Invalid database object' );
- }
- $conditions = array( 'page_is_redirect' => 0,
- 'page_namespace' => NS_MEDIAWIKI);
- $res = $dbr->select( array( 'page', 'revision', 'text' ),
- array( 'page_title', 'old_text', 'old_flags' ),
- 'page_is_redirect=0 AND page_namespace='.NS_MEDIAWIKI.' AND page_latest=rev_id AND rev_text_id=old_id',
- $fname
- );
-
$this->mCache = array();
- for ( $row = $dbr->fetchObject( $res ); $row; $row = $dbr->fetchObject( $res ) ) {
- $this->mCache[$row->page_title] = Revision::getRevisionText( $row );
- }
- # Negative caching
- # Go through the language array and the extension array and make a note of
- # any keys missing from the cache
- $allMessages = Language::getMessagesFor( 'en' );
- foreach ( $allMessages as $key => $value ) {
- $uckey = $wgLang->ucfirst( $key );
- if ( !array_key_exists( $uckey, $this->mCache ) ) {
- $this->mCache[$uckey] = false;
- }
+ # Load titles for all oversized pages in the MediaWiki namespace
+ $res = $dbr->select( 'page', 'page_title',
+ array(
+ 'page_len > ' . intval( $wgMaxMsgCacheEntrySize ),
+ 'page_is_redirect' => 0,
+ 'page_namespace' => NS_MEDIAWIKI,
+ ),
+ __METHOD__ );
+ while ( $row = $dbr->fetchObject( $res ) ) {
+ $this->mCache[$row->page_title] = '!TOO BIG';
}
+ $dbr->freeResult( $res );
- # Make sure all extension messages are available
- MessageCache::loadAllMessages();
+ # Load text for the remaining pages
+ $res = $dbr->select( array( 'page', 'revision', 'text' ),
+ array( 'page_title', 'old_text', 'old_flags' ),
+ array(
+ 'page_is_redirect' => 0,
+ 'page_namespace' => NS_MEDIAWIKI,
+ 'page_latest=rev_id',
+ 'rev_text_id=old_id',
+ 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ) ),
+ __METHOD__ );
- # Add them to the cache
- foreach ( $this->mExtensionMessages as $key => $value ) {
- $uckey = $wgLang->ucfirst( $key );
- if ( !array_key_exists( $uckey, $this->mCache ) &&
- ( isset( $this->mExtensionMessages[$key][$wgLang->getCode()] ) || isset( $this->mExtensionMessages[$key]['en'] ) ) ) {
- $this->mCache[$uckey] = false;
- }
+ for ( $row = $dbr->fetchObject( $res ); $row; $row = $dbr->fetchObject( $res ) ) {
+ $this->mCache[$row->page_title] = ' ' . Revision::getRevisionText( $row );
}
-
+ $this->mCache['VERSION'] = MSG_CACHE_VERSION;
$dbr->freeResult( $res );
+ wfProfileOut( __METHOD__ );
}
/**
@@ -345,7 +344,7 @@ class MessageCache {
if ( !$this->mKeys ) {
$this->mKeys = array();
$allMessages = Language::getMessagesFor( 'en' );
- foreach ( $allMessages as $key => $value ) {
+ foreach ( $allMessages as $key => $unused ) {
$title = $wgContLang->ucfirst( $key );
array_push( $this->mKeys, $title );
}
@@ -353,21 +352,26 @@ class MessageCache {
return $this->mKeys;
}
- /**
- * @deprecated
- */
- function isCacheable( $key ) {
- return true;
- }
-
function replace( $title, $text ) {
global $wgLocalMessageCache, $wgLocalMessageCacheSerialized, $parserMemc;
+ global $wgMaxMsgCacheEntrySize;
+ wfProfileIn( __METHOD__ );
$this->lock();
$this->load();
$parserMemc->delete(wfMemcKey('sidebar'));
if ( is_array( $this->mCache ) ) {
- $this->mCache[$title] = $text;
+ if ( $text === false ) {
+ # Article was deleted
+ unset( $this->mCache[$title] );
+ $this->mMemc->delete( "$this->mMemcKey:{$title}" );
+ } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
+ $this->mCache[$title] = '!TOO BIG';
+ $this->mMemc->set( "$this->mMemcKey:{$title}", ' '.$text, $this->mExpiry );
+ } else {
+ $this->mCache[$title] = ' ' . $text;
+ $this->mMemc->delete( "$this->mMemcKey:{$title}" );
+ }
$this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry );
# Save to local cache
@@ -381,10 +385,9 @@ class MessageCache {
$this->saveToScript( $this->mCache, $hash );
}
}
-
-
}
$this->unlock();
+ wfProfileOut( __METHOD__ );
}
/**
@@ -413,9 +416,18 @@ class MessageCache {
$this->mMemc->delete( $lockKey );
}
- function get( $key, $useDB = true, $forcontent = true, $isfullkey = false ) {
+ /**
+ * Get a message from either the content language or the user language.
+ *
+ * @param string $key The message cache key
+ * @param bool $useDB Get the message from the DB, false to use only the localisation
+ * @param bool $forContent Get the message from the content language rather than the
+ * user language
+ * @param bool $isFullKey Specifies whether $key is a two part key "lang/msg".
+ */
+ function get( $key, $useDB = true, $forContent = true, $isFullKey = false ) {
global $wgContLanguageCode, $wgContLang, $wgLang;
- if( $forcontent ) {
+ if( $forContent ) {
$lang =& $wgContLang;
} else {
$lang =& $wgLang;
@@ -431,37 +443,32 @@ class MessageCache {
}
$message = false;
+
+ # Normalise title-case input
+ $lckey = $wgContLang->lcfirst( $key );
+ $lckey = str_replace( ' ', '_', $lckey );
+
+ # Try the MediaWiki namespace
if( !$this->mDisable && $useDB ) {
- $title = $wgContLang->ucfirst( $key );
- if(!$isfullkey && ($langcode != $wgContLanguageCode) ) {
+ $title = $wgContLang->ucfirst( $lckey );
+ if(!$isFullKey && ($langcode != $wgContLanguageCode) ) {
$title .= '/' . $langcode;
}
- $message = $this->getFromCache( $title );
+ $message = $this->getMsgFromNamespace( $title );
}
# Try the extension array
- if( $message === false && array_key_exists( $key, $this->mExtensionMessages ) ) {
- if ( isset( $this->mExtensionMessages[$key][$langcode] ) ) {
- $message = $this->mExtensionMessages[$key][$langcode];
- } elseif ( isset( $this->mExtensionMessages[$key]['en'] ) ) {
- $message = $this->mExtensionMessages[$key]['en'];
- }
+ if( $message === false && isset( $this->mExtensionMessages[$langcode][$lckey] ) ) {
+ $message = $this->mExtensionMessages[$langcode][$lckey];
+ }
+ if ( $message === false && isset( $this->mExtensionMessages['en'][$lckey] ) ) {
+ $message = $this->mExtensionMessages['en'][$lckey];
}
# Try the array in the language object
if( $message === false ) {
#wfDebug( "Trying language object for message $key\n" );
wfSuppressWarnings();
- $message = $lang->getMessage( $key );
- wfRestoreWarnings();
- if ( is_null( $message ) ) {
- $message = false;
- }
- }
-
- # Try the English array
- if( $message === false && $langcode != 'en' ) {
- wfSuppressWarnings();
- $message = Language::getMessage( $key );
+ $message = $lang->getMessage( $lckey );
wfRestoreWarnings();
if ( is_null( $message ) ) {
$message = false;
@@ -469,8 +476,8 @@ class MessageCache {
}
# Try the array of another language
- if( $message === false && strpos( $key, '/' ) ) {
- $message = explode( '/', $key );
+ if( $message === false && strpos( $lckey, '/' ) ) {
+ $message = explode( '/', $lckey );
if ( $message[1] ) {
wfSuppressWarnings();
$message = Language::getMessageFor( $message[0], $message[1] );
@@ -486,8 +493,8 @@ class MessageCache {
# 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( $wgContLang->ucfirst( $key ) );
+ !$isFullKey && ($langcode != $wgContLanguageCode) ) {
+ $message = $this->getMsgFromNamespace( $wgContLang->ucfirst( $lckey ) );
}
# Final fallback
@@ -500,46 +507,70 @@ class MessageCache {
return $message;
}
- function getFromCache( $title ) {
+ /**
+ * Get a message from the MediaWiki namespace, with caching. The key must
+ * first be converted to two-part lang/msg form if necessary.
+ *
+ * @param string $title Message cache key with initial uppercase letter
+ */
+ function getMsgFromNamespace( $title ) {
$message = false;
+ $type = false;
# Try the cache
- if( $this->mUseCache && is_array( $this->mCache ) && array_key_exists( $title, $this->mCache ) ) {
- return $this->mCache[$title];
+ if( $this->mUseCache && isset( $this->mCache[$title] ) ) {
+ $entry = $this->mCache[$title];
+ $type = substr( $entry, 0, 1 );
+ if ( $type == ' ' ) {
+ return substr( $entry, 1 );
+ }
+ }
+
+ # Call message hooks, in case they are defined
+ wfRunHooks('MessagesPreLoad', array( $title, &$message ) );
+ if ( $message !== false ) {
+ return $message;
+ }
+
+ # If there is no cache entry and no placeholder, it doesn't exist
+ if ( $type != '!' && $message === false ) {
+ return false;
}
- # Try individual message cache
+ $memcKey = $this->mMemcKey . ':' . $title;
+
+ # Try the individual message cache
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;
- return $message;
- } else {
- $message = false;
+ $entry = $this->mMemc->get( $memcKey );
+ if ( $entry ) {
+ $type = substr( $entry, 0, 1 );
+
+ if ( $type == ' ' ) {
+ $message = substr( $entry, 1 );
+ $this->mCache[$title] = $message;
+ return $message;
+ } elseif ( $entry == '!NONEXISTENT' ) {
+ return false;
+ } else {
+ # Corrupt/obsolete entry, delete it
+ $this->mMemc->delete( $memcKey );
+ }
+
}
}
- # Call message Hooks, in case they are defined
- wfRunHooks('MessagesPreLoad',array($title,&$message));
-
- # If it wasn't in the cache, load each message from the DB individually
+ # Try loading it from the DB
$revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) );
if( $revision ) {
$message = $revision->getText();
if ($this->mUseCache) {
- $this->mCache[$title]=$message;
- /* individual messages may be often
- recached until proper purge code exists
- */
- $this->mMemc->set( $this->mMemcKey . ':' . $title, $message, 300 );
+ $this->mCache[$title] = ' ' . $message;
+ $this->mMemc->set( $memcKey, $message, $this->mExpiry );
}
} else {
# 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->mMemc->set( $memcKey, '!NONEXISTENT', $this->mExpiry );
$this->mCache[$title] = false;
}
@@ -577,7 +608,7 @@ class MessageCache {
* @param string $lang The messages language, English by default
*/
function addMessage( $key, $value, $lang = 'en' ) {
- $this->mExtensionMessages[$key][$lang] = $value;
+ $this->mExtensionMessages[$lang][$key] = $value;
}
/**
@@ -588,8 +619,24 @@ class MessageCache {
*/
function addMessages( $messages, $lang = 'en' ) {
wfProfileIn( __METHOD__ );
+ if ( isset( $this->mExtensionMessages[$lang] ) ) {
+ $this->mExtensionMessages[$lang] = $messages + $this->mExtensionMessages[$lang];
+ } else {
+ $this->mExtensionMessages[$lang] = $messages;
+ }
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Add a 2-D array of messages by lang. Useful for extensions.
+ * Introduced in 1.9. Please do not use it for now, for backwards compatibility.
+ *
+ * @param array $messages The array to be added
+ */
+ function addMessagesByLang( $messages ) {
+ wfProfileIn( __METHOD__ );
foreach ( $messages as $key => $value ) {
- $this->addMessage( $key, $value, $lang );
+ $this->addMessages( $value, $key );
}
wfProfileOut( __METHOD__ );
}
@@ -602,12 +649,11 @@ class MessageCache {
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'];
- }
+ if ( isset( $this->mExtensionMessages[$lang] ) ) {
+ $messages = $this->mExtensionMessages[$lang];
+ }
+ if ( $lang != 'en' ) {
+ $messages = $messages + $this->mExtensionMessages['en'];
}
wfProfileOut( __METHOD__ );
return $messages;
diff --git a/includes/Metadata.php b/includes/Metadata.php
index af40ab21..b48ced0d 100644
--- a/includes/Metadata.php
+++ b/includes/Metadata.php
@@ -22,7 +22,8 @@
*/
/**
- *
+ * TODO: Perhaps make this file into a Metadata class, with static methods (declared
+ * as private where indicated), to move these functions out of the global namespace?
*/
define('RDF_TYPE_PREFS', "application/rdf+xml,text/xml;q=0.7,application/xml;q=0.5,text/rdf;q=0.1");
@@ -142,7 +143,7 @@ function dcBasics($article) {
dcPerson('contributor', $user_parts[0], $user_parts[1], $user_parts[2]);
}
- dcRights($article);
+ dcRights();
}
/**
@@ -291,7 +292,7 @@ function dcPerson($name, $id, $user_name='', $user_real_name='') {
* different pages.
* @private
*/
-function dcRights($article) {
+function dcRights() {
global $wgRightsPage, $wgRightsUrl, $wgRightsText;
@@ -316,7 +317,11 @@ function ccGetTerms($url) {
return $wgLicenseTerms;
} else {
$known = getKnownLicenses();
- return $known[$url];
+ if( isset( $known[$url] ) ) {
+ return $known[$url];
+ } else {
+ return array();
+ }
}
}
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index dd197c31..ca05dbb3 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -403,8 +403,6 @@ class MimeMagic {
elseif ($tag==="svg") $mime= "image/svg";
elseif (strpos($doctype,"-//W3C//DTD XHTML")===0) $mime= "text/html";
elseif ($tag==="html") $mime= "text/html";
-
- $test_more= false;
}
}
diff --git a/includes/Namespace.php b/includes/Namespace.php
index 73dc2969..78493902 100644
--- a/includes/Namespace.php
+++ b/includes/Namespace.php
@@ -11,7 +11,7 @@
$wgCanonicalNamespaceNames = array(
NS_MEDIA => 'Media',
NS_SPECIAL => 'Special',
- NS_TALK => 'Talk',
+ NS_TALK => 'Talk',
NS_USER => 'User',
NS_USER_TALK => 'User_talk',
NS_PROJECT => 'Project',
@@ -24,7 +24,7 @@ $wgCanonicalNamespaceNames = array(
NS_TEMPLATE_TALK => 'Template_talk',
NS_HELP => 'Help',
NS_HELP_TALK => 'Help_talk',
- NS_CATEGORY => 'Category',
+ NS_CATEGORY => 'Category',
NS_CATEGORY_TALK => 'Category_talk',
);
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 0d55c2e0..4ca9e88a 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -54,18 +54,34 @@ class OutputPage {
$this->mNewSectionLink = false;
}
- function redirect( $url, $responsecode = '302' ) {
+ public function redirect( $url, $responsecode = '302' ) {
# Strip newlines as a paranoia check for header injection in PHP<5.1.2
$this->mRedirect = str_replace( "\n", '', $url );
$this->mRedirectCode = $responsecode;
}
+ /**
+ * Set the HTTP status code to send with the output.
+ *
+ * @param int $statusCode
+ * @return nothing
+ */
function setStatusCode( $statusCode ) { $this->mStatusCode = $statusCode; }
# To add an http-equiv meta tag, precede the name with "http:"
function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); }
function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
function addScript( $script ) { $this->mScripts .= $script; }
+
+ /**
+ * Add a self-contained script tag with the given contents
+ * @param string $script JavaScript text, no <script> tags
+ */
+ function addInlineScript( $script ) {
+ global $wgJsMimeType;
+ $this->mScripts .= "<script type=\"$wgJsMimeType\"><!--\n$script\n--></script>";
+ }
+
function getScript() { return $this->mScripts; }
function setETag($tag) { $this->mETag = $tag; }
@@ -88,8 +104,9 @@ class OutputPage {
/**
* checkLastModified tells the client to use the client-cached page if
* possible. If sucessful, the OutputPage is disabled so that
- * any future call to OutputPage->output() have no effect. The method
- * returns true iff cache-ok headers was sent.
+ * any future call to OutputPage->output() have no effect.
+ *
+ * @return bool True iff cache-ok headers was sent.
*/
function checkLastModified ( $timestamp ) {
global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest;
@@ -127,7 +144,12 @@ class OutputPage {
$this->sendCacheControl();
wfDebug( "$fname: CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
$this->disable();
- @ob_end_clean(); // Don't output compressed blob
+
+ // Don't output a compressed blob when using ob_gzhandler;
+ // it's technically against HTTP spec and seems to confuse
+ // Firefox when the response gets split over two packets.
+ wfClearOutputBuffers();
+
return true;
} else {
wfDebug( "$fname: READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
@@ -162,9 +184,9 @@ class OutputPage {
}
}
- function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; }
- function setHTMLTitle( $name ) {$this->mHTMLtitle = $name; }
- function setPageTitle( $name ) {
+ public function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; }
+ public function setHTMLTitle( $name ) {$this->mHTMLtitle = $name; }
+ public function setPageTitle( $name ) {
global $action, $wgContLang;
$name = $wgContLang->convert($name, true);
$this->mPagetitle = $name;
@@ -177,50 +199,50 @@ class OutputPage {
$this->setHTMLTitle( wfMsg( 'pagetitle', $name ) );
}
- function getHTMLTitle() { return $this->mHTMLtitle; }
- function getPageTitle() { return $this->mPagetitle; }
- function setSubtitle( $str ) { $this->mSubtitle = /*$this->parse(*/$str/*)*/; } // @bug 2514
- function getSubtitle() { return $this->mSubtitle; }
- function isArticle() { return $this->mIsarticle; }
- function setPrintable() { $this->mPrintable = true; }
- function isPrintable() { return $this->mPrintable; }
- function setSyndicated( $show = true ) { $this->mShowFeedLinks = $show; }
- function isSyndicated() { return $this->mShowFeedLinks; }
- function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; }
- function getOnloadHandler() { return $this->mOnloadHandler; }
- function disable() { $this->mDoNothing = true; }
-
- function setArticleRelated( $v ) {
+ public function getHTMLTitle() { return $this->mHTMLtitle; }
+ public function getPageTitle() { return $this->mPagetitle; }
+ public function setSubtitle( $str ) { $this->mSubtitle = /*$this->parse(*/$str/*)*/; } // @bug 2514
+ public function getSubtitle() { return $this->mSubtitle; }
+ public function isArticle() { return $this->mIsarticle; }
+ public function setPrintable() { $this->mPrintable = true; }
+ public function isPrintable() { return $this->mPrintable; }
+ public function setSyndicated( $show = true ) { $this->mShowFeedLinks = $show; }
+ public function isSyndicated() { return $this->mShowFeedLinks; }
+ public function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; }
+ public function getOnloadHandler() { return $this->mOnloadHandler; }
+ public function disable() { $this->mDoNothing = true; }
+
+ public function setArticleRelated( $v ) {
$this->mIsArticleRelated = $v;
if ( !$v ) {
$this->mIsarticle = false;
}
}
- function setArticleFlag( $v ) {
+ public function setArticleFlag( $v ) {
$this->mIsarticle = $v;
if ( $v ) {
$this->mIsArticleRelated = $v;
}
}
- function isArticleRelated() { return $this->mIsArticleRelated; }
+ public function isArticleRelated() { return $this->mIsArticleRelated; }
- function getLanguageLinks() { return $this->mLanguageLinks; }
- function addLanguageLinks($newLinkArray) {
+ public function getLanguageLinks() { return $this->mLanguageLinks; }
+ public function addLanguageLinks($newLinkArray) {
$this->mLanguageLinks += $newLinkArray;
}
- function setLanguageLinks($newLinkArray) {
+ public function setLanguageLinks($newLinkArray) {
$this->mLanguageLinks = $newLinkArray;
}
- function getCategoryLinks() {
+ public function getCategoryLinks() {
return $this->mCategoryLinks;
}
/**
* Add an array of categories, with names in the keys
*/
- function addCategoryLinks($categories) {
+ public function addCategoryLinks($categories) {
global $wgUser, $wgContLang;
if ( !is_array( $categories ) ) {
@@ -233,32 +255,32 @@ class OutputPage {
$lb->execute();
$sk =& $wgUser->getSkin();
- foreach ( $categories as $category => $arbitrary ) {
+ foreach ( $categories as $category => $unused ) {
$title = Title::makeTitleSafe( NS_CATEGORY, $category );
$text = $wgContLang->convertHtml( $title->getText() );
$this->mCategoryLinks[] = $sk->makeLinkObj( $title, $text );
}
}
- function setCategoryLinks($categories) {
+ public function setCategoryLinks($categories) {
$this->mCategoryLinks = array();
$this->addCategoryLinks($categories);
}
- function suppressQuickbar() { $this->mSuppressQuickbar = true; }
- function isQuickbarSuppressed() { return $this->mSuppressQuickbar; }
+ public function suppressQuickbar() { $this->mSuppressQuickbar = true; }
+ public function isQuickbarSuppressed() { return $this->mSuppressQuickbar; }
- function addHTML( $text ) { $this->mBodytext .= $text; }
- function clearHTML() { $this->mBodytext = ''; }
- function getHTML() { return $this->mBodytext; }
- function debug( $text ) { $this->mDebugtext .= $text; }
+ public function addHTML( $text ) { $this->mBodytext .= $text; }
+ public function clearHTML() { $this->mBodytext = ''; }
+ public function getHTML() { return $this->mBodytext; }
+ public function debug( $text ) { $this->mDebugtext .= $text; }
/* @deprecated */
- function setParserOptions( $options ) {
+ public function setParserOptions( $options ) {
return $this->parserOptions( $options );
}
- function parserOptions( $options = null ) {
+ public function parserOptions( $options = null ) {
if ( !$this->mParserOptions ) {
$this->mParserOptions = new ParserOptions;
}
@@ -271,7 +293,7 @@ class OutputPage {
* @param mixed $revid an integer, or NULL
* @return mixed previous value
*/
- function setRevisionId( $revid ) {
+ public function setRevisionId( $revid ) {
$val = is_null( $revid ) ? null : intval( $revid );
return wfSetVar( $this->mRevisionId, $val );
}
@@ -280,17 +302,20 @@ class OutputPage {
* Convert wikitext to HTML and add it to the buffer
* Default assumes that the current page title will
* be used.
+ *
+ * @param string $text
+ * @param bool $linestart
*/
- function addWikiText( $text, $linestart = true ) {
+ public function addWikiText( $text, $linestart = true ) {
global $wgTitle;
$this->addWikiTextTitle($text, $wgTitle, $linestart);
}
- function addWikiTextWithTitle($text, &$title, $linestart = true) {
+ public function addWikiTextWithTitle($text, &$title, $linestart = true) {
$this->addWikiTextTitle($text, $title, $linestart);
}
- function addWikiTextTitle($text, &$title, $linestart) {
+ private function addWikiTextTitle($text, &$title, $linestart) {
global $wgParser;
$fname = 'OutputPage:addWikiTextTitle';
wfProfileIn($fname);
@@ -301,7 +326,11 @@ class OutputPage {
wfProfileOut($fname);
}
- function addParserOutputNoText( &$parserOutput ) {
+ /**
+ * @todo document
+ * @param ParserOutput object &$parserOutput
+ */
+ public function addParserOutputNoText( &$parserOutput ) {
$this->mLanguageLinks += $parserOutput->getLanguageLinks();
$this->addCategoryLinks( $parserOutput->getCategories() );
$this->mNewSectionLink = $parserOutput->getNewSection();
@@ -319,6 +348,10 @@ class OutputPage {
wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
}
+ /**
+ * @todo document
+ * @param ParserOutput &$parserOutput
+ */
function addParserOutput( &$parserOutput ) {
$this->addParserOutputNoText( $parserOutput );
$text = $parserOutput->getText();
@@ -328,9 +361,13 @@ class OutputPage {
/**
* Add wikitext to the buffer, assuming that this is the primary text for a page view
- * Saves the text into the parser cache if possible
+ * Saves the text into the parser cache if possible.
+ *
+ * @param string $text
+ * @param Article $article
+ * @param bool $cache
*/
- function addPrimaryWikiText( $text, $article, $cache = true ) {
+ public function addPrimaryWikiText( $text, $article, $cache = true ) {
global $wgParser, $wgUser;
$popts = $this->parserOptions();
@@ -348,8 +385,11 @@ class OutputPage {
/**
* For anything that isn't primary text or interface message
+ *
+ * @param string $text
+ * @param bool $linestart Is this the start of a line?
*/
- function addSecondaryWikiText( $text, $linestart = true ) {
+ public function addSecondaryWikiText( $text, $linestart = true ) {
global $wgTitle;
$popts = $this->parserOptions();
$popts->setTidy(true);
@@ -360,9 +400,10 @@ class OutputPage {
/**
* Add the output of a QuickTemplate to the output buffer
+ *
* @param QuickTemplate $template
*/
- function addTemplate( &$template ) {
+ public function addTemplate( &$template ) {
ob_start();
$template->execute();
$this->addHTML( ob_get_contents() );
@@ -371,8 +412,12 @@ class OutputPage {
/**
* Parse wikitext and return the HTML.
+ *
+ * @param string $text
+ * @param bool $linestart Is this the start of a line?
+ * @param bool $interface ??
*/
- function parse( $text, $linestart = true, $interface = false ) {
+ public function parse( $text, $linestart = true, $interface = false ) {
global $wgParser, $wgTitle;
$popts = $this->parserOptions();
if ( $interface) { $popts->setInterfaceMessage(true); }
@@ -383,12 +428,12 @@ class OutputPage {
}
/**
- * @param $article
- * @param $user
+ * @param Article $article
+ * @param User $user
*
- * @return bool
+ * @return bool True if successful, else false.
*/
- function tryParserCache( &$article, $user ) {
+ public function tryParserCache( &$article, $user ) {
$parserCache =& ParserCache::singleton();
$parserOutput = $parserCache->get( $article, $user );
if ( $parserOutput !== false ) {
@@ -400,18 +445,17 @@ class OutputPage {
}
/**
- * Set the maximum cache time on the Squid in seconds
- * @param $maxage
+ * @param int $maxage Maximum cache time on the Squid, in seconds.
*/
- function setSquidMaxage( $maxage ) {
+ public function setSquidMaxage( $maxage ) {
$this->mSquidMaxage = $maxage;
}
/**
* Use enableClientCache(false) to force it to send nocache headers
- * @param $state
+ * @param $state ??
*/
- function enableClientCache( $state ) {
+ public function enableClientCache( $state ) {
return wfSetVar( $this->mEnableClientCache, $state );
}
@@ -421,7 +465,7 @@ class OutputPage {
&& $wgRequest->getText('uselang', false) === false;
}
- function sendCacheControl() {
+ public function sendCacheControl() {
global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest;
$fname = 'OutputPage::sendCacheControl';
@@ -477,10 +521,11 @@ class OutputPage {
* Finally, all the text has been munged and accumulated into
* the object, let's actually output it:
*/
- function output() {
+ public function output() {
global $wgUser, $wgOutputEncoding, $wgRequest;
global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType;
- global $wgJsMimeType, $wgStylePath, $wgUseAjax, $wgAjaxSearch, $wgScriptPath, $wgServer;
+ global $wgJsMimeType, $wgStylePath, $wgUseAjax, $wgAjaxSearch, $wgAjaxWatch;
+ global $wgServer, $wgStyleVersion;
if( $this->mDoNothing ){
return;
@@ -490,12 +535,15 @@ class OutputPage {
$sk = $wgUser->getSkin();
if ( $wgUseAjax ) {
- $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajax.js\"></script>\n" );
- }
+ $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajax.js?$wgStyleVersion\"></script>\n" );
+ if( $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 ( $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( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
+ $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxwatch.js\"></script>\n" );
+ }
}
if ( '' != $this->mRedirect ) {
@@ -601,7 +649,11 @@ class OutputPage {
wfProfileOut( $fname );
}
- function out( $ins ) {
+ /**
+ * @todo document
+ * @param string $ins
+ */
+ public function out( $ins ) {
global $wgInputEncoding, $wgOutputEncoding, $wgContLang;
if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
$outs = $ins;
@@ -612,7 +664,10 @@ class OutputPage {
print $outs;
}
- function setEncodings() {
+ /**
+ * @todo document
+ */
+ public static function setEncodings() {
global $wgInputEncoding, $wgOutputEncoding;
global $wgUser, $wgContLang;
@@ -626,19 +681,20 @@ class OutputPage {
}
/**
- * Returns a HTML comment with the elapsed time since request.
- * This method has no side effects.
- * Use wfReportTime() instead.
+ * Deprecated, use wfReportTime() instead.
* @return string
* @deprecated
*/
- function reportTime() {
+ public function reportTime() {
$time = wfReportTime();
return $time;
}
/**
- * Produce a "user is blocked" page
+ * Produce a "user is blocked" page.
+ *
+ * @param bool $return Whether to have a "return to $wgTitle" message or not.
+ * @return nothing
*/
function blockedPage( $return = true ) {
global $wgUser, $wgContLang, $wgTitle;
@@ -658,7 +714,9 @@ class OutputPage {
}
$link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
- $this->addWikiText( wfMsg( 'blockedtext', $link, $reason, $ip, $name ) );
+ $blockid = $wgUser->mBlock->mId;
+
+ $this->addWikiText( wfMsg( 'blockedtext', $link, $reason, $ip, $name, $blockid ) );
# Don't auto-return to special pages
if( $return ) {
@@ -668,9 +726,13 @@ class OutputPage {
}
/**
- * Note: these arguments are keys into wfMsg(), not text!
+ * Outputs a pretty page to explain why the request exploded.
+ *
+ * @param string $title Message key for page title.
+ * @param string $msg Message key for page text.
+ * @return nothing
*/
- function showErrorPage( $title, $msg ) {
+ public function showErrorPage( $title, $msg ) {
global $wgTitle;
$this->mDebugtext .= 'Original title: ' .
@@ -688,7 +750,7 @@ class OutputPage {
}
/** @obsolete */
- function errorpage( $title, $msg ) {
+ public function errorpage( $title, $msg ) {
throw new ErrorPageError( $title, $msg );
}
@@ -698,7 +760,7 @@ class OutputPage {
*
* @param mixed $version The version of MediaWiki needed to use the page
*/
- function versionRequired( $version ) {
+ public function versionRequired( $version ) {
$this->setPageTitle( wfMsg( 'versionrequired', $version ) );
$this->setHTMLTitle( wfMsg( 'versionrequired', $version ) );
$this->setRobotpolicy( 'noindex,nofollow' );
@@ -711,9 +773,10 @@ class OutputPage {
/**
* Display an error page noting that a given permission bit is required.
+ *
* @param string $permission key required
*/
- function permissionRequired( $permission ) {
+ public function permissionRequired( $permission ) {
global $wgGroupPermissions, $wgUser;
$this->setPageTitle( wfMsg( 'badaccess' ) );
@@ -751,23 +814,25 @@ class OutputPage {
}
/**
+ * Use permissionRequired.
* @deprecated
*/
- function sysopRequired() {
+ public function sysopRequired() {
throw new MWException( "Call to deprecated OutputPage::sysopRequired() method\n" );
}
/**
+ * Use permissionRequired.
* @deprecated
*/
- function developerRequired() {
+ public function developerRequired() {
throw new MWException( "Call to deprecated OutputPage::developerRequired() method\n" );
}
/**
* Produce the stock "please login to use the wiki" page
*/
- function loginToUse() {
+ public function loginToUse() {
global $wgUser, $wgTitle, $wgContLang;
if( $wgUser->isLoggedIn() ) {
@@ -782,40 +847,45 @@ class OutputPage {
$this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleFlag( false );
- $loginTitle = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
+ $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
$loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $wgTitle->getPrefixedUrl() );
$this->addHtml( wfMsgWikiHtml( 'loginreqpagetext', $loginLink ) );
$this->addHtml( "\n<!--" . $wgTitle->getPrefixedUrl() . "-->" );
# 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' ) );
+ $mainPage = Title::newMainPage();
if( $mainPage->userCanRead() )
$this->returnToMain( true, $mainPage );
}
/** @obsolete */
- function databaseError( $fname, $sql, $error, $errno ) {
+ public function databaseError( $fname, $sql, $error, $errno ) {
throw new MWException( "OutputPage::databaseError is obsolete\n" );
}
- function readOnlyPage( $source = null, $protected = false ) {
+ /**
+ * @todo document
+ * @param bool $protected Is the reason the page can't be reached because it's protected?
+ * @param mixed $source
+ */
+ public function readOnlyPage( $source = null, $protected = false ) {
global $wgUser, $wgReadOnlyFile, $wgReadOnly, $wgTitle;
+ $skin = $wgUser->getSkin();
$this->setRobotpolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
if( $protected ) {
- $skin = $wgUser->getSkin();
$this->setPageTitle( wfMsg( 'viewsource' ) );
$this->setSubtitle( wfMsg( 'viewsourcefor', $skin->makeKnownLinkObj( $wgTitle ) ) );
-
+
# Determine if protection is due to the page being a system message
# and show an appropriate explanation
if( $wgTitle->getNamespace() == NS_MEDIAWIKI && !$wgUser->isAllowed( 'editinterface' ) ) {
$this->addWikiText( wfMsg( 'protectedinterface' ) );
} else {
- $this->addWikiText( wfMsg( 'protectedtext' ) );
+ $this->addWikiText( wfMsg( 'protectedpagetext' ) );
}
} else {
$this->setPageTitle( wfMsg( 'readonly' ) );
@@ -828,7 +898,8 @@ class OutputPage {
}
if( is_string( $source ) ) {
- if( strcmp( $source, '' ) == 0 ) {
+ $this->addWikiText( wfMsg( 'viewsourcetext' ) );
+ if( $source === '' ) {
global $wgTitle;
if ( $wgTitle->getNamespace() == NS_MEDIAWIKI ) {
$source = wfMsgWeirdKey ( $wgTitle->getText() );
@@ -838,46 +909,48 @@ class OutputPage {
}
$rows = $wgUser->getIntOption( 'rows' );
$cols = $wgUser->getIntOption( 'cols' );
-
+
$text = "\n<textarea name='wpTextbox1' id='wpTextbox1' cols='$cols' rows='$rows' readonly='readonly'>" .
htmlspecialchars( $source ) . "\n</textarea>";
$this->addHTML( $text );
}
+ $article = new Article($wgTitle);
+ $this->addHTML( $skin->formatTemplates($article->getUsedTemplates()) );
$this->returnToMain( false );
}
/** @obsolete */
- function fatalError( $message ) {
+ public function fatalError( $message ) {
throw new FatalError( $message );
}
/** @obsolete */
- function unexpectedValueError( $name, $val ) {
+ public function unexpectedValueError( $name, $val ) {
throw new FatalError( wfMsg( 'unexpected', $name, $val ) );
}
/** @obsolete */
- function fileCopyError( $old, $new ) {
+ public function fileCopyError( $old, $new ) {
throw new FatalError( wfMsg( 'filecopyerror', $old, $new ) );
}
/** @obsolete */
- function fileRenameError( $old, $new ) {
+ public function fileRenameError( $old, $new ) {
throw new FatalError( wfMsg( 'filerenameerror', $old, $new ) );
}
/** @obsolete */
- function fileDeleteError( $name ) {
+ public function fileDeleteError( $name ) {
throw new FatalError( wfMsg( 'filedeleteerror', $name ) );
}
/** @obsolete */
- function fileNotFoundError( $name ) {
+ public function fileNotFoundError( $name ) {
throw new FatalError( wfMsg( 'filenotfound', $name ) );
}
- function showFatalError( $message ) {
+ public function showFatalError( $message ) {
$this->setPageTitle( wfMsg( "internalerror" ) );
$this->setRobotpolicy( "noindex,nofollow" );
$this->setArticleRelated( false );
@@ -886,23 +959,23 @@ class OutputPage {
$this->mBodytext = $message;
}
- function showUnexpectedValueError( $name, $val ) {
+ public function showUnexpectedValueError( $name, $val ) {
$this->showFatalError( wfMsg( 'unexpected', $name, $val ) );
}
- function showFileCopyError( $old, $new ) {
+ public function showFileCopyError( $old, $new ) {
$this->showFatalError( wfMsg( 'filecopyerror', $old, $new ) );
}
- function showFileRenameError( $old, $new ) {
+ public function showFileRenameError( $old, $new ) {
$this->showFatalError( wfMsg( 'filerenameerror', $old, $new ) );
}
- function showFileDeleteError( $name ) {
+ public function showFileDeleteError( $name ) {
$this->showFatalError( wfMsg( 'filedeleteerror', $name ) );
}
- function showFileNotFoundError( $name ) {
+ public function showFileNotFoundError( $name ) {
$this->showFatalError( wfMsg( 'filenotfound', $name ) );
}
@@ -911,7 +984,7 @@ class OutputPage {
* @param $auto automatically redirect the user after 10 seconds
* @param $returnto page title to return to. Default is Main Page.
*/
- function returnToMain( $auto = true, $returnto = NULL ) {
+ public function returnToMain( $auto = true, $returnto = NULL ) {
global $wgUser, $wgOut, $wgRequest;
if ( $returnto == NULL ) {
@@ -919,7 +992,7 @@ class OutputPage {
}
if ( '' === $returnto ) {
- $returnto = wfMsgForContent( 'mainpage' );
+ $returnto = Title::newMainPage();
}
if ( is_object( $returnto ) ) {
@@ -944,8 +1017,10 @@ class OutputPage {
/**
* This function takes the title (first item of mGoodLinks), categories, existing and broken links for the page
* and uses the first 10 of them for META keywords
+ *
+ * @param ParserOutput &$parserOutput
*/
- function addKeywords( &$parserOutput ) {
+ private function addKeywords( &$parserOutput ) {
global $wgTitle;
$this->addKeyword( $wgTitle->getPrefixedText() );
$count = 1;
@@ -953,8 +1028,8 @@ class OutputPage {
if ( !is_array( $links2d ) ) {
return;
}
- foreach ( $links2d as $ns => $dbkeys ) {
- foreach( $dbkeys as $dbkey => $id ) {
+ foreach ( $links2d as $dbkeys ) {
+ foreach( $dbkeys as $dbkey => $unused ) {
$this->addKeyword( $dbkey );
if ( ++$count > 10 ) {
break 2;
@@ -964,12 +1039,12 @@ class OutputPage {
}
/**
- * @access private
- * @return string
+ * @return string The doctype, opening <html>, and head element.
*/
- function headElement() {
+ public function headElement() {
global $wgDocType, $wgDTD, $wgContLanguageCode, $wgOutputEncoding, $wgMimeType;
- global $wgUser, $wgContLang, $wgUseTrackbacks, $wgTitle;
+ global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces;
+ global $wgUser, $wgContLang, $wgUseTrackbacks, $wgTitle, $wgStyleVersion;
if( $wgMimeType == 'text/xml' || $wgMimeType == 'application/xhtml+xml' || $wgMimeType == 'application/xml' ) {
$ret = "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?>\n";
@@ -984,7 +1059,11 @@ class OutputPage {
}
$rtl = $wgContLang->isRTL() ? " dir='RTL'" : '';
- $ret .= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"$wgContLanguageCode\" lang=\"$wgContLanguageCode\" $rtl>\n";
+ $ret .= "<html xmlns=\"{$wgXhtmlDefaultNamespace}\" ";
+ foreach($wgXhtmlNamespaces as $tag => $ns) {
+ $ret .= "xmlns:{$tag}=\"{$ns}\" ";
+ }
+ $ret .= "xml:lang=\"$wgContLanguageCode\" lang=\"$wgContLanguageCode\" $rtl>\n";
$ret .= "<head>\n<title>" . htmlspecialchars( $this->getHTMLTitle() ) . "</title>\n";
array_push( $this->mMetatags, array( "http:Content-type", "$wgMimeType; charset={$wgOutputEncoding}" ) );
@@ -995,7 +1074,7 @@ class OutputPage {
} else {
$media = "media='print'";
}
- $printsheet = htmlspecialchars( "$wgStylePath/common/wikiprintable.css" );
+ $printsheet = htmlspecialchars( "$wgStylePath/common/wikiprintable.css?$wgStyleVersion" );
$ret .= "<link rel='stylesheet' type='text/css' $media href='$printsheet' />\n";
$sk = $wgUser->getSkin();
@@ -1010,7 +1089,10 @@ class OutputPage {
return $ret;
}
- function getHeadLinks() {
+ /**
+ * @return string HTML tag links to be put in the header.
+ */
+ public function getHeadLinks() {
global $wgRequest;
$ret = '';
foreach ( $this->mMetatags as $tag ) {
@@ -1060,9 +1142,8 @@ class OutputPage {
* Turn off regular page output and return an error reponse
* for when rate limiting has triggered.
* @todo i18n
- * @access public
*/
- function rateLimited() {
+ public function rateLimited() {
global $wgOut;
$wgOut->disable();
wfHttpError( 500, 'Internal Server Error',
@@ -1075,9 +1156,8 @@ class OutputPage {
*
* @return bool True if the parser output instructs us to add one
*/
- function showNewSectionLink() {
+ public function showNewSectionLink() {
return $this->mNewSectionLink;
}
-
}
?>
diff --git a/includes/PageHistory.php b/includes/PageHistory.php
index d7f426fc..aea0f0ed 100644
--- a/includes/PageHistory.php
+++ b/includes/PageHistory.php
@@ -70,7 +70,7 @@ class PageHistory {
$wgOut->setRobotpolicy( 'noindex,nofollow' );
$wgOut->setSyndicated( true );
- $logPage = Title::makeTitle( NS_SPECIAL, 'Log' );
+ $logPage = SpecialPage::getTitleFor( 'Log' );
$logLink = $this->mSkin->makeKnownLinkObj( $logPage, wfMsgHtml( 'viewpagelogs' ), 'page=' . $this->mTitle->getPrefixedUrl() );
$subtitle = wfMsgHtml( 'revhistory' ) . '<br />' . $logLink;
@@ -106,12 +106,11 @@ class PageHistory {
* Do the list
*/
$pager = new PageHistoryPager( $this );
- $navbar = $pager->getNavigationBar();
$this->linesonpage = $pager->getNumRows();
$wgOut->addHTML(
$pager->getNavigationBar() .
$this->beginHistoryList() .
- $pager->getBody() .
+ $pager->getBody() .
$this->endHistoryList() .
$pager->getNavigationBar()
);
@@ -166,7 +165,19 @@ class PageHistory {
: '';
}
- /** @todo document */
+ /**
+ * Returns a row from the history printout.
+ *
+ * @todo document some more, and maybe clean up the code (some params redundant?)
+ *
+ * @param object $row The database row corresponding to the line (or is it the previous line?).
+ * @param object $next The database row corresponding to the next line (or is it this one?).
+ * @param int $counter Apparently a counter of what row number we're at, counted from the top row = 1.
+ * @param $notificationtimestamp
+ * @param bool $latest Whether this row corresponds to the page's latest revision.
+ * @param bool $firstInList Whether this row corresponds to the first displayed on this history page.
+ * @return string HTML output for the row
+ */
function historyLine( $row, $next, $counter = '', $notificationtimestamp = false, $latest = false, $firstInList = false ) {
global $wgUser;
$rev = new Revision( $row );
@@ -184,7 +195,7 @@ class PageHistory {
$s .= "($curlink) ($lastlink) $arbitrary";
if( $wgUser->isAllowed( 'deleterevision' ) ) {
- $revdel = Title::makeTitle( NS_SPECIAL, 'Revisiondelete' );
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
if( $firstInList ) {
// We don't currently handle well changing the top revision's settings
$del = wfMsgHtml( 'rev-delundel' );
@@ -210,6 +221,9 @@ class PageHistory {
if( $row->rev_deleted & Revision::DELETED_TEXT ) {
$s .= ' ' . wfMsgHtml( 'deletedrev' );
}
+ if( $wgUser->isAllowed( 'rollback' ) && $latest ) {
+ $s .= ' '.$this->mSkin->generateRollback( $rev );
+ }
$s .= "</li>\n";
return $s;
diff --git a/includes/Pager.php b/includes/Pager.php
index b14aa8ca..0987cc06 100644
--- a/includes/Pager.php
+++ b/includes/Pager.php
@@ -323,7 +323,7 @@ abstract class IndexPager implements Pager {
$next = array( 'offset' => $this->mLastShown, 'limit' => $urlLimit );
$last = array( 'dir' => 'prev', 'limit' => $urlLimit );
}
- return compact( 'prev', 'next', 'first', 'last' );
+ return array( 'prev' => $prev, 'next' => $next, 'first' => $first, 'last' => $last );
}
/**
@@ -487,7 +487,7 @@ abstract class TablePager extends IndexPager {
}
function getEndBody() {
- return '</tbody></table>';
+ return "</tbody></table>\n";
}
function getEmptyBody() {
@@ -553,7 +553,7 @@ abstract class TablePager extends IndexPager {
'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 ) {
@@ -564,12 +564,12 @@ abstract class TablePager extends IndexPager {
$links = $this->getPagingLinks( $linkTexts, $disabledTexts );
$navClass = htmlspecialchars( $this->getNavClass() );
- $s = "<table class=\"$navClass\" align=\"center\" cellpadding=\"3\"><tr>";
+ $s = "<table class=\"$navClass\" align=\"center\" cellpadding=\"3\"><tr>\n";
$cellAttrs = 'valign="top" align="center" width="' . 100 / count( $links ) . '%"';
foreach ( $labels as $type => $label ) {
$s .= "<td $cellAttrs>{$links[$type]}</td>\n";
}
- $s .= '</tr></table>';
+ $s .= "</tr></table>\n";
return $s;
}
diff --git a/includes/Parser.php b/includes/Parser.php
index 76783448..8d67279d 100644
--- a/includes/Parser.php
+++ b/includes/Parser.php
@@ -62,13 +62,15 @@ define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 );
* Processes wiki markup
*
* <pre>
- * There are three main entry points into the Parser class:
+ * There are four main entry points into the Parser class:
* parse()
* produces HTML output
* preSaveTransform().
* produces altered wiki markup.
* transformMsg()
* performs brace substitution on MediaWiki messages
+ * preprocess()
+ * removes HTML comments and expands templates
*
* Globals used:
* objects: $wgLang, $wgContLang
@@ -78,7 +80,7 @@ define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 );
* settings:
* $wgUseTex*, $wgUseDynamicDates*, $wgInterwikiMagic*,
* $wgNamespacesWithSubpages, $wgAllowExternalImages*,
- * $wgLocaltimezone, $wgAllowSpecialInclusion*,
+ * $wgLocaltimezone, $wgAllowSpecialInclusion*,
* $wgMaxArticleSize*
*
* * only within ParserOptions
@@ -95,10 +97,10 @@ class Parser
var $mTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables;
# Cleared with clearState():
- var $mOutput, $mAutonumber, $mDTopen, $mStripState = array();
+ var $mOutput, $mAutonumber, $mDTopen, $mStripState;
var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
var $mInterwikiLinkHolders, $mLinkHolders, $mUniqPrefix;
- var $mIncludeSizes;
+ var $mIncludeSizes, $mDefaultSort;
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
@@ -110,7 +112,9 @@ class Parser
$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
+ $mRevisionId, // ID to display in {{REVISIONID}} tags
+ $mRevisionTimestamp, // The timestamp of the specified revision ID
+ $mRevIdForTs; // The revision ID which was used to fetch the timestamp
/**#@-*/
@@ -162,6 +166,8 @@ class Parser
$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 );
+ $this->setFunctionHook( 'special', array( 'CoreParserFunctions', 'special' ) );
+ $this->setFunctionHook( 'defaultsort', array( 'CoreParserFunctions', 'defaultsort' ), SFH_NO_HASH );
if ( $wgAllowDisplayTitle ) {
$this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH );
@@ -169,12 +175,11 @@ class Parser
if ( $wgAllowSlowParserFunctions ) {
$this->setFunctionHook( 'pagesinnamespace', array( 'CoreParserFunctions', 'pagesinnamespace' ), SFH_NO_HASH );
}
-
- $this->initialiseVariables();
+ $this->initialiseVariables();
$this->mFirstCall = false;
wfProfileOut( __METHOD__ );
- }
+ }
/**
* Clear Parser state
@@ -191,7 +196,7 @@ class Parser
$this->mLastSection = '';
$this->mDTopen = false;
$this->mIncludeCount = array();
- $this->mStripState = array();
+ $this->mStripState = new StripState;
$this->mArgStack = array();
$this->mInPre = false;
$this->mInterwikiLinkHolders = array(
@@ -205,7 +210,7 @@ class Parser
'texts' => array(),
'titles' => array()
);
- $this->mRevisionId = null;
+ $this->mRevisionTimestamp = $this->mRevisionId = null;
/**
* Prefix for temporary replacement strings for the multipass parser.
@@ -227,6 +232,7 @@ class Parser
'post-expand' => 0,
'arg' => 0
);
+ $this->mDefaultSort = false;
wfRunHooks( 'ParserClearState', array( &$this ) );
wfProfileOut( __METHOD__ );
@@ -273,6 +279,7 @@ class Parser
global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang;
$fname = 'Parser::parse-' . wfGetCaller();
+ wfProfileIn( __METHOD__ );
wfProfileIn( $fname );
if ( $clearState ) {
@@ -282,22 +289,17 @@ class Parser
$this->mOptions = $options;
$this->mTitle =& $title;
$oldRevisionId = $this->mRevisionId;
+ $oldRevisionTimestamp = $this->mRevisionTimestamp;
if( $revid !== null ) {
$this->mRevisionId = $revid;
+ $this->mRevisionTimestamp = null;
}
$this->setOutputType( OT_HTML );
-
- //$text = $this->strip( $text, $this->mStripState );
- // VOODOO MAGIC FIX! Sometimes the above segfaults in PHP5.
- $x =& $this->mStripState;
-
- wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$x ) );
- $text = $this->strip( $text, $x );
- wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$x ) );
-
+ wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+ $text = $this->strip( $text, $this->mStripState );
+ wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
$text = $this->internalParse( $text );
-
- $text = $this->unstrip( $text, $this->mStripState );
+ $text = $this->mStripState->unstripGeneral( $text );
# Clean up special characters, only run once, next-to-last before doBlockLevels
$fixtags = array(
@@ -320,7 +322,7 @@ class Parser
# Side-effects: this calls $this->mOutput->setTitleText()
$text = $wgContLang->parserConvert( $text, $this );
- $text = $this->unstripNoWiki( $text, $this->mStripState );
+ $text = $this->mStripState->unstripNoWiki( $text );
wfRunHooks( 'ParserBeforeTidy', array( &$this, &$text ) );
@@ -370,7 +372,9 @@ class Parser
}
$this->mOutput->setText( $text );
$this->mRevisionId = $oldRevisionId;
+ $this->mRevisionTimestamp = $oldRevisionTimestamp;
wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $this->mOutput;
}
@@ -381,10 +385,9 @@ class Parser
*/
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 ) );
+ wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+ $text = $this->strip( $text, $this->mStripState );
+ wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
$text = $this->internalParse( $text );
wfProfileOut( __METHOD__ );
return $text;
@@ -400,16 +403,14 @@ class Parser
$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 ) );
+ wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+ $text = $this->strip( $text, $this->mStripState );
+ wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
if ( $this->mOptions->getRemoveComments() ) {
$text = Sanitizer::removeHTMLcomments( $text );
}
$text = $this->replaceVariables( $text );
- $text = $this->unstrip( $text, $x );
- $text = $this->unstripNowiki( $text, $x );
+ $text = $this->mStripState->unstripBoth( $text );
wfProfileOut( __METHOD__ );
return $text;
}
@@ -503,7 +504,7 @@ class Parser
$text = $q[2];
}
}
-
+
$matches[$marker] = array( $element,
$content,
Sanitizer::decodeTagAttributes( $attributes ),
@@ -516,25 +517,29 @@ class Parser
* Strips and renders nowiki, pre, math, hiero
* If $render is set, performs necessary rendering operations on plugins
* Returns the text, and fills an array with data needed in unstrip()
- * If the $state is already a valid strip state, it adds to the state
+ *
+ * @param StripState $state
*
* @param bool $stripcomments when set, HTML comments <!-- like this -->
* will be stripped in addition to other tags. This is important
* for section editing, where these comments cause confusion when
* counting the sections in the wikisource
- *
+ *
* @param array dontstrip contains tags which should not be stripped;
* used to prevent stipping of <gallery> when saving (fixes bug 2700)
*
* @private
*/
- function strip( $text, &$state, $stripcomments = false , $dontstrip = array () ) {
+ function strip( $text, $state, $stripcomments = false , $dontstrip = array () ) {
+ global $wgContLang;
wfProfileIn( __METHOD__ );
$render = ($this->mOutputType == OT_HTML);
$uniq_prefix = $this->mUniqPrefix;
- $commentState = array();
-
+ $commentState = new ReplacementArray;
+ $nowikiItems = array();
+ $generalItems = array();
+
$elements = array_merge(
array( 'nowiki', 'gallery' ),
array_keys( $this->mTagHooks ) );
@@ -545,13 +550,13 @@ class Parser
if( $this->mOptions->getUseTeX() ) {
$elements[] = 'math';
}
-
+
# Removing $dontstrip tags from $elements list (currently only 'gallery', fixing bug 2700)
foreach ( $elements AS $k => $v ) {
if ( !in_array ( $v , $dontstrip ) ) continue;
unset ( $elements[$k] );
}
-
+
$matches = array();
$text = Parser::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix );
@@ -578,10 +583,10 @@ class Parser
}
// Shouldn't happen otherwise. :)
case 'nowiki':
- $output = wfEscapeHTMLTagsOnly( $content );
+ $output = Xml::escapeTagsOnly( $content );
break;
case 'math':
- $output = MathRenderer::renderMath( $content );
+ $output = $wgContLang->armourMath( MathRenderer::renderMath( $content ) );
break;
case 'gallery':
$output = $this->renderImageGallery( $content, $params );
@@ -600,18 +605,22 @@ class Parser
$output = $tag;
}
- // Unstrip the output, because unstrip() is no longer recursive so
- // it won't do it itself
- $output = $this->unstrip( $output, $state );
+ // Unstrip the output, to support recursive strip() calls
+ $output = $state->unstripBoth( $output );
if( !$stripcomments && $element == '!--' ) {
- $commentState[$marker] = $output;
+ $commentState->setPair( $marker, $output );
} elseif ( $element == 'html' || $element == 'nowiki' ) {
- $state['nowiki'][$marker] = $output;
+ $nowikiItems[$marker] = $output;
} else {
- $state['general'][$marker] = $output;
+ $generalItems[$marker] = $output;
}
}
+ # Add the new items to the state
+ # We do this after the loop instead of during it to avoid slowing
+ # down the recursive unstrip
+ $state->nowiki->mergeArray( $nowikiItems );
+ $state->general->mergeArray( $generalItems );
# Unstrip comments unless explicitly told otherwise.
# (The comments are always stripped prior to this point, so as to
@@ -619,7 +628,7 @@ class Parser
# a comment.)
if ( !$stripcomments ) {
// Put them all back and forget them
- $text = strtr( $text, $commentState );
+ $text = $commentState->replace( $text );
}
wfProfileOut( __METHOD__ );
@@ -631,35 +640,27 @@ class Parser
*
* always call unstripNoWiki() after this one
* @private
+ * @deprecated use $this->mStripState->unstrip()
*/
- function unstrip( $text, &$state ) {
- if ( !isset( $state['general'] ) ) {
- return $text;
- }
-
- wfProfileIn( __METHOD__ );
- # TODO: good candidate for FSS
- $text = strtr( $text, $state['general'] );
- wfProfileOut( __METHOD__ );
- return $text;
+ function unstrip( $text, $state ) {
+ return $state->unstripGeneral( $text );
}
/**
* Always call this after unstrip() to preserve the order
*
* @private
+ * @deprecated use $this->mStripState->unstrip()
*/
- function unstripNoWiki( $text, &$state ) {
- if ( !isset( $state['nowiki'] ) ) {
- return $text;
- }
+ function unstripNoWiki( $text, $state ) {
+ return $state->unstripNoWiki( $text );
+ }
- wfProfileIn( __METHOD__ );
- # TODO: good candidate for FSS
- $text = strtr( $text, $state['nowiki'] );
- wfProfileOut( __METHOD__ );
-
- return $text;
+ /**
+ * @deprecated use $this->mStripState->unstripBoth()
+ */
+ function unstripForHTML( $text ) {
+ return $this->mStripState->unstripBoth( $text );
}
/**
@@ -671,10 +672,7 @@ class Parser
*/
function insertStripItem( $text, &$state ) {
$rnd = $this->mUniqPrefix . '-item' . Parser::getRandomString();
- if ( !$state ) {
- $state = array();
- }
- $state['general'][$rnd] = $text;
+ $state->general->setPair( $rnd, $text );
return $rnd;
}
@@ -791,135 +789,191 @@ class Parser
*
* @private
*/
- function doTableStuff ( $t ) {
+ function doTableStuff ( $text ) {
$fname = 'Parser::doTableStuff';
wfProfileIn( $fname );
- $t = explode ( "\n" , $t ) ;
- $td = array () ; # Is currently a td tag open?
- $ltd = array () ; # Was it TD or TH?
- $tr = array () ; # Is currently a tr tag open?
- $ltr = array () ; # tr attributes
- $has_opened_tr = array(); # Did this table open a <tr> element?
- $indent_level = 0; # indent level of the table
- foreach ( $t AS $k => $x )
+ $lines = explode ( "\n" , $text );
+ $td_history = array (); // Is currently a td tag open?
+ $last_tag_history = array (); // Save history of last lag activated (td, th or caption)
+ $tr_history = array (); // Is currently a tr tag open?
+ $tr_attributes = array (); // history of tr attributes
+ $has_opened_tr = array(); // Did this table open a <tr> element?
+ $indent_level = 0; // indent level of the table
+ foreach ( $lines as $key => $line )
{
- $x = trim ( $x ) ;
- $fc = substr ( $x , 0 , 1 ) ;
- if ( preg_match( '/^(:*)\{\|(.*)$/', $x, $matches ) ) {
+ $line = trim ( $line );
+
+ if( $line == '' ) { // empty line, go to next line
+ continue;
+ }
+ $first_character = $line{0};
+ $matches = array();
+
+ if ( preg_match( '/^(:*)\{\|(.*)$/' , $line , $matches ) ) {
+ // First check if we are starting a new table
$indent_level = strlen( $matches[1] );
- $attributes = $this->unstripForHTML( $matches[2] );
+ $attributes = $this->mStripState->unstripBoth( $matches[2] );
+ $attributes = Sanitizer::fixTagAttributes ( $attributes , 'table' );
+
+ $lines[$key] = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>";
+ array_push ( $td_history , false );
+ array_push ( $last_tag_history , '' );
+ array_push ( $tr_history , false );
+ array_push ( $tr_attributes , '' );
+ array_push ( $has_opened_tr , false );
+ } else if ( count ( $td_history ) == 0 ) {
+ // Don't do any of the following
+ continue;
+ } else if ( substr ( $line , 0 , 2 ) == '|}' ) {
+ // We are ending a table
+ $line = '</table>' . substr ( $line , 2 );
+ $last_tag = array_pop ( $last_tag_history );
- $t[$k] = str_repeat( '<dl><dd>', $indent_level ) .
- '<table' . Sanitizer::fixTagAttributes ( $attributes, 'table' ) . '>' ;
- array_push ( $td , false ) ;
- array_push ( $ltd , '' ) ;
- array_push ( $tr , false ) ;
- array_push ( $ltr , '' ) ;
- array_push ( $has_opened_tr, false );
- }
- else if ( count ( $td ) == 0 ) { } # Don't do any of the following
- else if ( '|}' == substr ( $x , 0 , 2 ) ) {
- $z = "</table>" . substr ( $x , 2);
- $l = array_pop ( $ltd ) ;
- if ( !array_pop ( $has_opened_tr ) ) $z = "<tr><td></td></tr>" . $z ;
- if ( array_pop ( $tr ) ) $z = '</tr>' . $z ;
- if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ;
- array_pop ( $ltr ) ;
- $t[$k] = $z . str_repeat( '</dd></dl>', $indent_level );
- }
- else if ( '|-' == substr ( $x , 0 , 2 ) ) { # Allows for |---------------
- $x = substr ( $x , 1 ) ;
- while ( $x != '' && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ;
- $z = '' ;
- $l = array_pop ( $ltd ) ;
+ if ( !array_pop ( $has_opened_tr ) ) {
+ $line = "<tr><td></td></tr>{$line}";
+ }
+
+ if ( array_pop ( $tr_history ) ) {
+ $line = "</tr>{$line}";
+ }
+
+ if ( array_pop ( $td_history ) ) {
+ $line = "</{$last_tag}>{$line}";
+ }
+ array_pop ( $tr_attributes );
+ $lines[$key] = $line . str_repeat( '</dd></dl>' , $indent_level );
+ } else if ( substr ( $line , 0 , 2 ) == '|-' ) {
+ // Now we have a table row
+ $line = preg_replace( '#^\|-+#', '', $line );
+
+ // Whats after the tag is now only attributes
+ $attributes = $this->mStripState->unstripBoth( $line );
+ $attributes = Sanitizer::fixTagAttributes ( $attributes , 'tr' );
+ array_pop ( $tr_attributes );
+ array_push ( $tr_attributes , $attributes );
+
+ $line = '';
+ $last_tag = array_pop ( $last_tag_history );
array_pop ( $has_opened_tr );
- array_push ( $has_opened_tr , true ) ;
- if ( array_pop ( $tr ) ) $z = '</tr>' . $z ;
- if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ;
- array_pop ( $ltr ) ;
- $t[$k] = $z ;
- array_push ( $tr , false ) ;
- array_push ( $td , false ) ;
- array_push ( $ltd , '' ) ;
- $attributes = $this->unstripForHTML( $x );
- array_push ( $ltr , Sanitizer::fixTagAttributes ( $attributes, 'tr' ) ) ;
+ array_push ( $has_opened_tr , true );
+
+ if ( array_pop ( $tr_history ) ) {
+ $line = '</tr>';
+ }
+
+ if ( array_pop ( $td_history ) ) {
+ $line = "</{$last_tag}>{$line}";
+ }
+
+ $lines[$key] = $line;
+ array_push ( $tr_history , false );
+ array_push ( $td_history , false );
+ array_push ( $last_tag_history , '' );
}
- else if ( '|' == $fc || '!' == $fc || '|+' == substr ( $x , 0 , 2 ) ) { # Caption
- # $x is a table row
- if ( '|+' == substr ( $x , 0 , 2 ) ) {
- $fc = '+' ;
- $x = substr ( $x , 1 ) ;
+ else if ( $first_character == '|' || $first_character == '!' || substr ( $line , 0 , 2 ) == '|+' ) {
+ // This might be cell elements, td, th or captions
+ if ( substr ( $line , 0 , 2 ) == '|+' ) {
+ $first_character = '+';
+ $line = substr ( $line , 1 );
+ }
+
+ $line = substr ( $line , 1 );
+
+ if ( $first_character == '!' ) {
+ $line = str_replace ( '!!' , '||' , $line );
}
- $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
+ // 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 );
+ $cells = StringUtils::explodeMarkup( '||' , $line );
- $t[$k] = '' ;
+ $lines[$key] = '';
- # Loop through each table cell
- foreach ( $after AS $theline )
+ // Loop through each table cell
+ foreach ( $cells as $cell )
{
- $z = '' ;
- if ( $fc != '+' )
+ $previous = '';
+ if ( $first_character != '+' )
{
- $tra = array_pop ( $ltr ) ;
- if ( !array_pop ( $tr ) ) $z = '<tr'.$tra.">\n" ;
- array_push ( $tr , true ) ;
- array_push ( $ltr , '' ) ;
+ $tr_after = array_pop ( $tr_attributes );
+ if ( !array_pop ( $tr_history ) ) {
+ $previous = "<tr{$tr_after}>\n";
+ }
+ array_push ( $tr_history , true );
+ array_push ( $tr_attributes , '' );
array_pop ( $has_opened_tr );
- array_push ( $has_opened_tr , true ) ;
+ array_push ( $has_opened_tr , true );
+ }
+
+ $last_tag = array_pop ( $last_tag_history );
+
+ if ( array_pop ( $td_history ) ) {
+ $previous = "</{$last_tag}>{$previous}";
}
- $l = array_pop ( $ltd ) ;
- if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ;
- if ( $fc == '|' ) $l = 'td' ;
- else if ( $fc == '!' ) $l = 'th' ;
- else if ( $fc == '+' ) $l = 'caption' ;
- else $l = '' ;
- array_push ( $ltd , $l ) ;
-
- # Cell parameters
- $y = explode ( '|' , $theline , 2 ) ;
- # Note that a '|' inside an invalid link should not
- # be mistaken as delimiting cell parameters
- if ( strpos( $y[0], '[[' ) !== false ) {
- $y = array ($theline);
+ if ( $first_character == '|' ) {
+ $last_tag = 'td';
+ } else if ( $first_character == '!' ) {
+ $last_tag = 'th';
+ } else if ( $first_character == '+' ) {
+ $last_tag = 'caption';
+ } else {
+ $last_tag = '';
}
- if ( count ( $y ) == 1 )
- $y = "{$z}<{$l}>{$y[0]}" ;
+
+ array_push ( $last_tag_history , $last_tag );
+
+ // A cell could contain both parameters and data
+ $cell_data = explode ( '|' , $cell , 2 );
+
+ // Bug 553: Note that a '|' inside an invalid link should not
+ // be mistaken as delimiting cell parameters
+ if ( strpos( $cell_data[0], '[[' ) !== false ) {
+ $cell = "{$previous}<{$last_tag}>{$cell}";
+ } else if ( count ( $cell_data ) == 1 )
+ $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
else {
- $attributes = $this->unstripForHTML( $y[0] );
- $y = "{$z}<{$l}".Sanitizer::fixTagAttributes($attributes, $l).">{$y[1]}" ;
+ $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
+ $attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag );
+ $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
}
- $t[$k] .= $y ;
- array_push ( $td , true ) ;
+
+ $lines[$key] .= $cell;
+ array_push ( $td_history , true );
}
}
}
- # Closing open td, tr && table
- while ( count ( $td ) > 0 )
+ // Closing open td, tr && table
+ while ( count ( $td_history ) > 0 )
{
- $l = array_pop ( $ltd ) ;
- if ( array_pop ( $td ) ) $t[] = '</td>' ;
- if ( array_pop ( $tr ) ) $t[] = '</tr>' ;
- if ( !array_pop ( $has_opened_tr ) ) $t[] = "<tr><td></td></tr>" ;
- $t[] = '</table>' ;
+ if ( array_pop ( $td_history ) ) {
+ $lines[] = '</td>' ;
+ }
+ if ( array_pop ( $tr_history ) ) {
+ $lines[] = '</tr>' ;
+ }
+ if ( !array_pop ( $has_opened_tr ) ) {
+ $lines[] = "<tr><td></td></tr>" ;
+ }
+
+ $lines[] = '</table>' ;
+ }
+
+ $output = implode ( "\n" , $lines ) ;
+
+ // special case: don't return empty table
+ if( $output == "<table>\n<tr><td></td></tr>\n</table>" ) {
+ $output = '';
}
- $t = implode ( "\n" , $t ) ;
- # special case: don't return empty table
- if($t == "<table>\n<tr><td></td></tr>\n</table>")
- $t = '';
wfProfileOut( $fname );
- return $t ;
+
+ return $output;
}
/**
@@ -935,7 +989,7 @@ class Parser
wfProfileIn( $fname );
# Hook to suspend the parser in this state
- if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$x ) ) ) {
+ if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) {
wfProfileOut( $fname );
return $text ;
}
@@ -943,7 +997,7 @@ class Parser
# 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 = StringUtils::delimiterReplace( '<includeonly>', '</includeonly>', '', $text );
$text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ) );
@@ -987,15 +1041,19 @@ class Parser
*/
function &doMagicLinks( &$text ) {
wfProfileIn( __METHOD__ );
- $text = preg_replace_callback(
+ $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]
+ ISBN\s+(\b # ISBN, capture number as m[2]
+ (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix
+ (?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters
+ [0-9Xx] # check digit
+ \b)
)!x', array( &$this, 'magicLinkCallback' ), $text );
wfProfileOut( __METHOD__ );
- return $text;
+ return $text;
}
function magicLinkCallback( $m ) {
@@ -1004,12 +1062,12 @@ class Parser
return $m[0];
} elseif ( substr( $m[0], 0, 4 ) == 'ISBN' ) {
$isbn = $m[2];
- $num = strtr( $isbn, array(
+ $num = strtr( $isbn, array(
'-' => '',
' ' => '',
'x' => 'X',
));
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Booksources' );
+ $titleObj = SpecialPage::getTitleFor( 'Booksources' );
$text = '<a href="' .
$titleObj->escapeLocalUrl( "isbn=$num" ) .
"\" class=\"internal\">ISBN $isbn</a>";
@@ -1023,10 +1081,10 @@ class Parser
$urlmsg = 'pubmedurl';
$id = $m[1];
} else {
- throw new MWException( __METHOD__.': unrecognised match type "' .
+ 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 );
@@ -1106,9 +1164,9 @@ class Parser
}
# Count the number of occurrences of bold and italics mark-ups.
# We are not counting sequences of five apostrophes.
- if ( strlen( $arr[$i] ) == 2 ) $numitalics++; else
- if ( strlen( $arr[$i] ) == 3 ) $numbold++; else
- if ( strlen( $arr[$i] ) == 5 ) { $numitalics++; $numbold++; }
+ if ( strlen( $arr[$i] ) == 2 ) { $numitalics++; }
+ else if ( strlen( $arr[$i] ) == 3 ) { $numbold++; }
+ else if ( strlen( $arr[$i] ) == 5 ) { $numitalics++; $numbold++; }
}
$i++;
}
@@ -1264,6 +1322,7 @@ class Parser
# The characters '<' and '>' (which were escaped by
# removeHTMLtags()) should not be included in
# URLs, per RFC 2396.
+ $m2 = array();
if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) {
$text = substr($url, $m2[0][1]) . ' ' . $text;
$url = substr($url, 0, $m2[0][1]);
@@ -1299,7 +1358,7 @@ class Parser
}
$text = $wgContLang->markNoConversion($text);
-
+
$url = Sanitizer::cleanUrl( $url );
# Process the trail (i.e. everything after this link up until start of the next link),
@@ -1342,6 +1401,7 @@ class Parser
$protocol = $bits[$i++];
$remainder = $bits[$i++];
+ $m = array();
if ( preg_match( '/^('.EXT_LINK_URL_CLASS.'+)(.*)$/s', $remainder, $m ) ) {
# Found some characters after the protocol that look promising
$url = $protocol . $m[1];
@@ -1349,10 +1409,10 @@ class Parser
# special case: handle urls as url args:
# http://www.example.com/foo?=http://www.example.com/bar
- if(strlen($trail) == 0 &&
+ if(strlen($trail) == 0 &&
isset($bits[$i]) &&
preg_match('/^'. wfUrlProtocols() . '$/S', $bits[$i]) &&
- preg_match( '/^('.EXT_LINK_URL_CLASS.'+)(.*)$/s', $bits[$i + 1], $m ))
+ preg_match( '/^('.EXT_LINK_URL_CLASS.'+)(.*)$/s', $bits[$i + 1], $m ))
{
# add protocol, arg
$url .= $bits[$i] . $m[1]; # protocol, url as arg to previous link
@@ -1363,6 +1423,7 @@ class Parser
# The characters '<' and '>' (which were escaped by
# removeHTMLtags()) should not be included in
# URLs, per RFC 2396.
+ $m2 = array();
if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) {
$trail = substr($url, $m2[0][1]) . $trail;
$url = substr($url, 0, $m2[0][1]);
@@ -1498,6 +1559,7 @@ class Parser
$nottalk = !$this->mTitle->isTalkPage();
if ( $useLinkPrefixExtension ) {
+ $m = array();
if ( preg_match( $e2, $s, $m ) ) {
$first_prefix = $m[2];
} else {
@@ -1507,7 +1569,10 @@ class Parser
$prefix = '';
}
- $selflink = $this->mTitle->getPrefixedText();
+ if($wgContLang->hasVariants())
+ $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText());
+ else
+ $selflink = array($this->mTitle->getPrefixedText());
$useSubpages = $this->areSubpagesAllowed();
wfProfileOut( $fname.'-setup' );
@@ -1543,10 +1608,10 @@ class Parser
# Still some problems for cases where the ] is meant to be outside punctuation,
# and no image is in sight. See bug 2095.
#
- if( $text !== '' &&
- substr( $m[3], 0, 1 ) === ']' &&
- strpos($text, '[') !== false
- )
+ if( $text !== '' &&
+ substr( $m[3], 0, 1 ) === ']' &&
+ strpos($text, '[') !== false
+ )
{
$text .= ']'; # so that replaceExternalLinks($text) works later
$m[3] = substr( $m[3], 1 );
@@ -1595,7 +1660,7 @@ class Parser
wfProfileOut( "$fname-misc" );
wfProfileIn( "$fname-title" );
- $nt = Title::newFromText( $this->unstripNoWiki($link, $this->mStripState) );
+ $nt = Title::newFromText( $this->mStripState->unstripNoWiki($link) );
if( !$nt ) {
$s .= $prefix . '[[' . $line;
wfProfileOut( "$fname-title" );
@@ -1605,7 +1670,7 @@ class Parser
$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
@@ -1693,11 +1758,7 @@ class Parser
$s = rtrim($s . "\n"); # bug 87
if ( $wasblank ) {
- if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
- $sortkey = $this->mTitle->getText();
- } else {
- $sortkey = $this->mTitle->getPrefixedText();
- }
+ $sortkey = $this->getDefaultSort();
} else {
$sortkey = $text;
}
@@ -1717,7 +1778,7 @@ class Parser
}
}
- if( ( $nt->getPrefixedText() === $selflink ) &&
+ if( ( in_array( $nt->getPrefixedText(), $selflink ) ) &&
( $nt->getFragment() === '' ) ) {
# Self-links are handled specially; generally de-link and change to bold.
$s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail );
@@ -1819,7 +1880,7 @@ class Parser
* @return string less-or-more HTML with NOPARSE bits
*/
function armorLinks( $text ) {
- return preg_replace( "/\b(" . wfUrlProtocols() . ')/',
+ return preg_replace( '/\b(' . wfUrlProtocols() . ')/',
"{$this->mUniqPrefix}NOPARSE$1", $text );
}
@@ -2073,10 +2134,10 @@ class Parser
wfProfileIn( "$fname-paragraph" );
# No prefix (not in list)--go to paragraph mode
// XXX: use a stack for nestable elements like span, table and div
- $openmatch = preg_match('/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/center|<\\/tr|<\\/td|<\\/th)/iS', $t );
+ $openmatch = preg_match('/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
$closematch = preg_match(
'/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'.
- '<td|<th|<div|<\\/div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<center)/iS', $t );
+ '<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS', $t );
if ( $openmatch or $closematch ) {
$paragraphStack = false;
# TODO bug 5718: paragraph closed
@@ -2326,9 +2387,11 @@ class Parser
* expensive to check many times.
*/
static $varCache = array();
- if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$varCache ) ) )
- if ( isset( $varCache[$index] ) )
+ if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$varCache ) ) ) {
+ if ( isset( $varCache[$index] ) ) {
return $varCache[$index];
+ }
+ }
$ts = time();
wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
@@ -2416,15 +2479,15 @@ class Parser
case 'revisionid':
return $this->mRevisionId;
case 'revisionday':
- return intval( substr( wfRevisionTimestamp( $this->mRevisionId ), 6, 2 ) );
+ return intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
case 'revisionday2':
- return substr( wfRevisionTimestamp( $this->mRevisionId ), 6, 2 );
+ return substr( $this->getRevisionTimestamp(), 6, 2 );
case 'revisionmonth':
- return intval( substr( wfRevisionTimestamp( $this->mRevisionId ), 4, 2 ) );
+ return intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
case 'revisionyear':
- return substr( wfRevisionTimestamp( $this->mRevisionId ), 0, 4 );
+ return substr( $this->getRevisionTimestamp(), 0, 4 );
case 'revisiontimestamp':
- return wfRevisionTimestamp( $this->mRevisionId );
+ return $this->getRevisionTimestamp();
case 'namespace':
return str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
case 'namespacee':
@@ -2466,15 +2529,15 @@ class Parser
case 'localdow':
return $varCache[$index] = $wgContLang->formatNum( $localDayOfWeek );
case 'numberofarticles':
- return $varCache[$index] = $wgContLang->formatNum( wfNumberOfArticles() );
+ return $varCache[$index] = $wgContLang->formatNum( SiteStats::articles() );
case 'numberoffiles':
- return $varCache[$index] = $wgContLang->formatNum( wfNumberOfFiles() );
+ return $varCache[$index] = $wgContLang->formatNum( SiteStats::images() );
case 'numberofusers':
- return $varCache[$index] = $wgContLang->formatNum( wfNumberOfUsers() );
+ return $varCache[$index] = $wgContLang->formatNum( SiteStats::users() );
case 'numberofpages':
- return $varCache[$index] = $wgContLang->formatNum( wfNumberOfPages() );
+ return $varCache[$index] = $wgContLang->formatNum( SiteStats::pages() );
case 'numberofadmins':
- return $varCache[$index] = $wgContLang->formatNum( wfNumberOfAdmins() );
+ return $varCache[$index] = $wgContLang->formatNum( SiteStats::admins() );
case 'currenttimestamp':
return $varCache[$index] = wfTimestampNow();
case 'localtimestamp':
@@ -2543,7 +2606,7 @@ class Parser
$lastOpeningBrace = -1; # last not closed parentheses
$validOpeningBraces = implode( '', array_keys( $callbacks ) );
-
+
$i = 0;
while ( $i < strlen( $text ) ) {
# Find next opening brace, closing brace or pipe
@@ -2597,13 +2660,13 @@ class Parser
$maxCount = $openingBraceStack[$lastOpeningBrace]['count'];
$count = strspn( $text, $text[$i], $i, $maxCount );
- # check for maximum matching characters (if there are 5 closing
+ # 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
+ # The specified maximum exists in the callback array, unless the caller
# has made an error
$matchingCount = $cbType['max'];
} else {
@@ -2624,12 +2687,12 @@ class Parser
# let's set a title or last part (if '|' was found)
if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
- $openingBraceStack[$lastOpeningBrace]['title'] =
- substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+ $openingBraceStack[$lastOpeningBrace]['title'] =
+ substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
$i - $openingBraceStack[$lastOpeningBrace]['partStart']);
} else {
- $openingBraceStack[$lastOpeningBrace]['parts'][] =
- substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+ $openingBraceStack[$lastOpeningBrace]['parts'][] =
+ substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
$i - $openingBraceStack[$lastOpeningBrace]['partStart']);
}
@@ -2679,13 +2742,13 @@ class Parser
} elseif ( $found == 'pipe' ) {
# lets set a title if it is a first separator, or next part otherwise
if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
- $openingBraceStack[$lastOpeningBrace]['title'] =
- substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
+ $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'],
+ $openingBraceStack[$lastOpeningBrace]['parts'][] =
+ substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
$i - $openingBraceStack[$lastOpeningBrace]['partStart']);
}
$openingBraceStack[$lastOpeningBrace]['partStart'] = ++$i;
@@ -2731,15 +2794,15 @@ class Parser
$braceCallbacks[3] = array( &$this, 'argSubstitution' );
}
if ( $braceCallbacks ) {
- $callbacks = array(
+ $callbacks = array(
'{' => array(
'end' => '}',
'cb' => $braceCallbacks,
'min' => $argsOnly ? 3 : 2,
'max' => isset( $braceCallbacks[3] ) ? 3 : 2,
),
- '[' => array(
- 'end' => ']',
+ '[' => array(
+ 'end' => ']',
'cb' => array(2=>null),
'min' => 2,
'max' => 2,
@@ -2789,31 +2852,30 @@ class Parser
return $text;
}
- # Split template arguments
- function getTemplateArgs( $argsString ) {
- if ( $argsString === '' ) {
- return array();
- }
-
- $args = explode( '|', substr( $argsString, 1 ) );
-
- # If any of the arguments contains a '[[' but no ']]', it needs to be
- # merged with the next arg because the '|' character between belongs
- # to the link syntax and not the template parameter syntax.
- $argc = count($args);
-
- for ( $i = 0; $i < $argc-1; $i++ ) {
- if ( substr_count ( $args[$i], '[[' ) != substr_count ( $args[$i], ']]' ) ) {
- $args[$i] .= '|'.$args[$i+1];
- array_splice($args, $i+1, 1);
- $i--;
- $argc--;
+
+ /// Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
+ static function createAssocArgs( $args ) {
+ $assocArgs = array();
+ $index = 1;
+ foreach( $args as $arg ) {
+ $eqpos = strpos( $arg, '=' );
+ if ( $eqpos === false ) {
+ $assocArgs[$index++] = $arg;
+ } else {
+ $name = trim( substr( $arg, 0, $eqpos ) );
+ $value = trim( substr( $arg, $eqpos+1 ) );
+ if ( $value === false ) {
+ $value = '';
+ }
+ if ( $name !== false ) {
+ $assocArgs[$name] = $value;
+ }
}
}
-
- return $args;
+
+ return $assocArgs;
}
-
+
/**
* Return the text of a template, after recursively
* replacing any variables or templates within the template.
@@ -2826,7 +2888,7 @@ class Parser
* @private
*/
function braceSubstitution( $piece ) {
- global $wgContLang, $wgLang, $wgAllowDisplayTitle, $action;
+ global $wgContLang, $wgLang, $wgAllowDisplayTitle;
$fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/;
wfProfileIn( $fname );
wfProfileIn( __METHOD__.'-setup' );
@@ -2837,6 +2899,7 @@ class Parser
$noparse = false; # Unsafe HTML tags should not be stripped, etc.
$noargs = false; # Don't replace triple-brace arguments in $text
$replaceHeadings = false; # Make the edit section links go to the template not the article
+ $headingOffset = 0; # Skip headings when number, to account for those that weren't transcluded.
$isHTML = false; # $text is HTML, armour it against wikitext transformation
$forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered
@@ -2845,7 +2908,7 @@ 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
@@ -2863,7 +2926,6 @@ class Parser
}
$args = (null == $piece['parts']) ? array() : $piece['parts'];
- $argc = count( $args );
wfProfileOut( __METHOD__.'-setup' );
# SUBST
@@ -2893,7 +2955,7 @@ class Parser
$mwMsg =& MagicWord::get( 'msg' );
$mwMsg->matchStartAndRemove( $part1 );
}
-
+
# Check for RAW:
$mwRaw =& MagicWord::get( 'raw' );
if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
@@ -2902,10 +2964,13 @@ class Parser
}
wfProfileOut( __METHOD__.'-modifiers' );
+ //save path level before recursing into functions & templates.
+ $lastPathLevel = $this->mTemplatePath;
+
# Parser functions
if ( !$found ) {
wfProfileIn( __METHOD__ . '-pfunc' );
-
+
$colonPos = strpos( $part1, ':' );
if ( $colonPos !== false ) {
# Case sensitive functions
@@ -2970,7 +3035,6 @@ class Parser
}
# Load from database
- $lastPathLevel = $this->mTemplatePath;
if ( !$found ) {
wfProfileIn( __METHOD__ . '-loadtpl' );
$ns = NS_TEMPLATE;
@@ -2987,9 +3051,8 @@ 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){
+ if($wgContLang->hasVariants() && $title->getArticleID() == 0){
$wgContLang->findVariantLink($part1, $title);
}
@@ -3062,24 +3125,7 @@ class Parser
$assocArgs = array();
} else {
# Clean up argument array
- $assocArgs = array();
- $index = 1;
- foreach( $args as $arg ) {
- $eqpos = strpos( $arg, '=' );
- if ( $eqpos === false ) {
- $assocArgs[$index++] = $arg;
- } else {
- $name = trim( substr( $arg, 0, $eqpos ) );
- $value = trim( substr( $arg, $eqpos+1 ) );
- if ( $value === false ) {
- $value = '';
- }
- if ( $name !== false ) {
- $assocArgs[$name] = $value;
- }
- }
- }
-
+ $assocArgs = self::createAssocArgs($args);
# Add a new element to the templace recursion path
$this->mTemplatePath[$part1] = 1;
}
@@ -3087,13 +3133,13 @@ class Parser
if ( !$noparse ) {
# If there are any <onlyinclude> tags, only include them
if ( in_string( '<onlyinclude>', $text ) && in_string( '</onlyinclude>', $text ) ) {
- preg_match_all( '/<onlyinclude>(.*?)\n?<\/onlyinclude>/s', $text, $m );
- $text = '';
- foreach ($m[1] as $piece)
- $text .= $piece;
+ $replacer = new OnlyIncludeReplacer;
+ StringUtils::delimiterReplaceCallback( '<onlyinclude>', '</onlyinclude>',
+ array( &$replacer, 'replace' ), $text );
+ $text = $replacer->output;
}
# Remove <noinclude> sections and <includeonly> tags
- $text = preg_replace( '/<noinclude>.*?<\/noinclude>/s', '', $text );
+ $text = StringUtils::delimiterReplace( '<noinclude>', '</noinclude>', '', $text );
$text = strtr( $text, array( '<includeonly>' => '' , '</includeonly>' => '' ) );
if( $this->ot['html'] || $this->ot['pre'] ) {
@@ -3109,7 +3155,7 @@ class Parser
# 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 ) {
@@ -3151,7 +3197,7 @@ class Parser
$m = preg_split('/(^={1,6}.*?={1,6}\s*?$)/m', $text, -1,
PREG_SPLIT_DELIM_CAPTURE);
$text = '';
- $nsec = 0;
+ $nsec = $headingOffset;
for( $i = 0; $i < count($m); $i += 2 ) {
$text .= $m[$i];
if (!isset($m[$i + 1]) || $m[$i + 1] == "") continue;
@@ -3160,6 +3206,7 @@ class Parser
$text .= $hl;
continue;
}
+ $m2 = array();
preg_match('/^(={1,6})(.*?)(={1,6})\s*?$/m', $hl, $m2);
$text .= $m2[1] . $m2[2] . "<!--MWTEMPLATESECTION="
. $encodedname . "&" . base64_encode("$nsec") . "-->" . $m2[3];
@@ -3192,10 +3239,19 @@ class Parser
for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
$rev = Revision::newFromTitle( $title );
$this->mOutput->addTemplate( $title, $title->getArticleID() );
- if ( !$rev ) {
+ if ( $rev ) {
+ $text = $rev->getText();
+ } elseif( $title->getNamespace() == NS_MEDIAWIKI ) {
+ global $wgLang;
+ $message = $wgLang->lcfirst( $title->getText() );
+ $text = wfMsgForContentNoTrans( $message );
+ if( wfEmptyMsg( $message, $text ) ) {
+ $text = false;
+ break;
+ }
+ } else {
break;
}
- $text = $rev->getText();
if ( $text === false ) {
break;
}
@@ -3209,22 +3265,13 @@ class Parser
* Transclude an interwiki link.
*/
function interwikiTransclude( $title, $action ) {
- global $wgEnableScaryTranscluding, $wgCanonicalNamespaceNames;
+ global $wgEnableScaryTranscluding;
if (!$wgEnableScaryTranscluding)
return wfMsg('scarytranscludedisabled');
- // The namespace will actually only be 0 or 10, depending on whether there was a leading :
- // But we'll handle it generally anyway
- if ( $title->getNamespace() ) {
- // Use the canonical namespace, which should work anywhere
- $articleName = $wgCanonicalNamespaceNames[$title->getNamespace()] . ':' . $title->getDBkey();
- } else {
- $articleName = $title->getDBkey();
- }
-
- $url = str_replace('$1', urlencode($articleName), Title::getInterwikiLink($title->getInterwiki()));
- $url .= "?action=$action";
+ $url = $title->getFullUrl( "action=$action" );
+
if (strlen($url) > 255)
return wfMsg('scarytranscludetoolong');
return $this->fetchScaryTemplateMaybeFromCache($url);
@@ -3267,7 +3314,7 @@ class Parser
if ( array_key_exists( $arg, $inputArgs ) ) {
$text = $inputArgs[$arg];
- } else if (($this->mOutputType == OT_HTML || $this->mOutputType == OT_PREPROCESS ) &&
+ } else if (($this->mOutputType == OT_HTML || $this->mOutputType == OT_PREPROCESS ) &&
null != $matches['parts'] && count($matches['parts']) > 0) {
$text = $matches['parts'][0];
}
@@ -3362,7 +3409,8 @@ class Parser
# Get all headlines for numbering them and adding funky stuff like [edit]
# links - this is for later, but we need the number of headlines right now
- $numMatches = preg_match_all( '/<H([1-6])(.*?'.'>)(.*?)<\/H[1-6] *>/i', $text, $matches );
+ $matches = array();
+ $numMatches = preg_match_all( '/<H(?P<level>[1-6])(?P<attrib>.*?'.'>)(?P<header>.*?)<\/H[1-6] *>/i', $text, $matches );
# if there are fewer than 4 headlines in the article, do not show TOC
# unless it's been explicitly enabled.
@@ -3413,7 +3461,7 @@ class Parser
$templatetitle = '';
$templatesection = 0;
$numbering = '';
-
+ $mat = array();
if (preg_match("/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/", $headline, $mat)) {
$istemplate = 1;
$templatetitle = base64_decode($mat[1]);
@@ -3486,8 +3534,7 @@ class Parser
# The canonized header is a version of the header text safe to use for links
# Avoid insertion of weird stuff like <math> by expanding the relevant sections
- $canonized_headline = $this->unstrip( $headline, $this->mStripState );
- $canonized_headline = $this->unstripNoWiki( $canonized_headline, $this->mStripState );
+ $canonized_headline = $this->mStripState->unstripBoth( $headline );
# Remove link placeholders by the link text.
# <!--LINK number-->
@@ -3509,7 +3556,7 @@ class Parser
$refers[$headlineCount] = $canonized_headline;
# count how many in assoc. array so we can track dupes in anchors
- @$refers[$canonized_headline]++;
+ isset( $refers[$canonized_headline] ) ? $refers[$canonized_headline]++ : $refers[$canonized_headline] = 1;
$refcount[$headlineCount]=$refers[$canonized_headline];
# Don't number the heading if it is the only one (looks silly)
@@ -3526,18 +3573,16 @@ class Parser
if( $enoughToc && ( !isset($wgMaxTocLevel) || $toclevel<$wgMaxTocLevel ) ) {
$toc .= $sk->tocLine($anchor, $tocline, $numbering, $toclevel);
}
+ # give headline the correct <h#> tag
if( $showEditLink && ( !$istemplate || $templatetitle !== "" ) ) {
- if ( empty( $head[$headlineCount] ) ) {
- $head[$headlineCount] = '';
- }
if( $istemplate )
- $head[$headlineCount] .= $sk->editSectionLinkForOther($templatetitle, $templatesection);
+ $editlink = $sk->editSectionLinkForOther($templatetitle, $templatesection);
else
- $head[$headlineCount] .= $sk->editSectionLink($this->mTitle, $sectionCount+1, $headline_hint);
+ $editlink = $sk->editSectionLink($this->mTitle, $sectionCount+1, $headline_hint);
+ } else {
+ $editlink = '';
}
-
- # give headline the correct <h#> tag
- @$head[$headlineCount] .= "<a name=\"$anchor\"></a><h".$level.$matches[2][$headlineCount] .$headline.'</h'.$level.'>';
+ $head[$headlineCount] = $sk->makeHeadline( $level, $matches['attrib'][$headlineCount], $anchor, $headline, $editlink );
$headlineCount++;
if( !$istemplate )
@@ -3595,7 +3640,7 @@ class Parser
* @return string the altered wiki markup
* @public
*/
- function preSaveTransform( $text, &$title, &$user, $options, $clearState = true ) {
+ function preSaveTransform( $text, &$title, $user, $options, $clearState = true ) {
$this->mOptions = $options;
$this->mTitle =& $title;
$this->setOutputType( OT_WIKI );
@@ -3604,15 +3649,14 @@ class Parser
$this->clearState();
}
- $stripState = false;
+ $stripState = new StripState;
$pairs = array(
"\r\n" => "\n",
);
$text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
$text = $this->strip( $text, $stripState, true, array( 'gallery' ) );
$text = $this->pstPass2( $text, $stripState, $user );
- $text = $this->unstrip( $text, $stripState );
- $text = $this->unstripNoWiki( $text, $stripState );
+ $text = $stripState->unstripBoth( $text );
return $text;
}
@@ -3620,7 +3664,7 @@ class Parser
* Pre-save transform helper function
* @private
*/
- function pstPass2( $text, &$stripState, &$user ) {
+ function pstPass2( $text, &$stripState, $user ) {
global $wgContLang, $wgLocaltimezone;
/* Note: This is the timestamp saved as hardcoded wikitext to
@@ -3668,6 +3712,7 @@ class Parser
$text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
$t = $this->mTitle->getText();
+ $m = array();
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]" ) {
@@ -3834,7 +3879,7 @@ class Parser
*/
function setHook( $tag, $callback ) {
$tag = strtolower( $tag );
- $oldVal = @$this->mTagHooks[$tag];
+ $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
$this->mTagHooks[$tag] = $callback;
return $oldVal;
@@ -3845,10 +3890,10 @@ class Parser
* The callback function should have the form:
* function myParserFunction( &$parser, $arg1, $arg2, $arg3 ) { ... }
*
- * The callback may either return the text result of the function, or an array with the text
- * in element 0, and a number of flags in the other elements. The names of the flags are
+ * The callback may either return the text result of the function, or an array with the text
+ * in element 0, and a number of flags in the other elements. The names of the flags are
* specified in the keys. Valid flags are:
- * found The text returned is valid, stop processing the template. This
+ * found The text returned is valid, stop processing the template. This
* is on by default.
* nowiki Wiki markup in the return value should be escaped
* noparse Unsafe HTML tags should not be stripped, etc.
@@ -3859,13 +3904,13 @@ class Parser
*
* @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:
+ * @param integer $flags a combination of the following flags:
* SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}}
*
* @return The old callback function for this name, if any
*/
function setFunctionHook( $id, $callback, $flags = 0 ) {
- $oldVal = @$this->mFunctionHooks[$id];
+ $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id] : null;
$this->mFunctionHooks[$id] = $callback;
# Add to function cache
@@ -3914,8 +3959,7 @@ class Parser
*/
function replaceLinkHolders( &$text, $options = 0 ) {
global $wgUser;
- global $wgOutputReplace;
- global $wgContLang, $wgLanguageCode;
+ global $wgContLang;
$fname = 'Parser::replaceLinkHolders';
wfProfileIn( $fname );
@@ -3936,6 +3980,7 @@ class Parser
# Generate query
$query = false;
+ $current = null;
foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) {
# Make title object
$title = $this->mLinkHolders['titles'][$key];
@@ -4006,10 +4051,14 @@ class Parser
}
wfProfileOut( $fname.'-check' );
- # Do a second query for different language variants of links (if needed)
+ # Do a second query for different language variants of links and categories
if($wgContLang->hasVariants()){
- $linkBatch = new LinkBatch();
- $variantMap = array(); // maps $pdbkey_Variant => $pdbkey_original
+ $linkBatch = new LinkBatch();
+ $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders)
+ $categoryMap = array(); // maps $category_variant => $category (dbkeys)
+ $varCategories = array(); // category replacements oldDBkey => newDBkey
+
+ $categories = $this->mOutput->getCategoryLinks();
// Add variants of links to link batch
foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) {
@@ -4018,21 +4067,37 @@ class Parser
continue;
$pdbk = $title->getPrefixedDBkey();
+ $titleText = $title->getText();
// generate all variants of the link title text
- $allTextVariants = $wgContLang->convertLinkToAllVariants($title->getText());
+ $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText);
// 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($textVariant != $titleText){
+ $variantTitle = Title::makeTitle( $ns, $textVariant );
+ if(is_null($variantTitle)) continue;
+ $linkBatch->addObj( $variantTitle );
+ $variantMap[$variantTitle->getPrefixedDBkey()][] = $key;
+ }
+ }
+ }
+ }
+
+ // process categories, check if a category exists in some variant
+ foreach( $categories as $category){
+ $variants = $wgContLang->convertLinkToAllVariants($category);
+ foreach($variants as $variant){
+ if($variant != $category){
+ $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) );
if(is_null($variantTitle)) continue;
$linkBatch->addObj( $variantTitle );
- $variantMap[$variantTitle->getPrefixedDBkey()][] = $key;
+ $categoryMap[$variant] = $category;
}
}
}
-
+
if(!$linkBatch->isEmpty()){
// construct query
@@ -4055,13 +4120,17 @@ class Parser
$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 );
+ $vardbk = $variantTitle->getDBkey();
- $holderKeys = $variantMap[$varPdbk];
+ $holderKeys = array();
+ if(isset($variantMap[$varPdbk])){
+ $holderKeys = $variantMap[$varPdbk];
+ $linkCache->addGoodLinkObj( $s->page_id, $variantTitle );
+ $this->mOutput->addLink( $variantTitle, $s->page_id );
+ }
// loop over link holders
- foreach($holderKeys as $key){
+ foreach($holderKeys as $key){
$title = $this->mLinkHolders['titles'][$key];
if ( is_null( $title ) ) continue;
@@ -4071,7 +4140,7 @@ class Parser
// 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 ) {
@@ -4081,19 +4150,39 @@ class Parser
} else {
$colours[$varPdbk] = 2;
}
- }
+ }
else {
$colours[$varPdbk] = 1;
- }
+ }
}
}
+
+ // check if the object is a variant of a category
+ if(isset($categoryMap[$vardbk])){
+ $oldkey = $categoryMap[$vardbk];
+ if($oldkey != $vardbk)
+ $varCategories[$oldkey]=$vardbk;
+ }
+ }
+
+ // rebuild the categories in original order (if there are replacements)
+ if(count($varCategories)>0){
+ $newCats = array();
+ $originalCats = $this->mOutput->getCategories();
+ foreach($originalCats as $cat => $sortkey){
+ // make the replacement
+ if( array_key_exists($cat,$varCategories) )
+ $newCats[$varCategories[$cat]] = $sortkey;
+ else $newCats[$cat] = $sortkey;
+ }
+ $this->mOutput->setCategoryLinks($newCats);
}
}
}
# Construct search and replace arrays
wfProfileIn( $fname.'-construct' );
- $wgOutputReplace = array();
+ $replacePairs = array();
foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) {
$pdbk = $pdbks[$key];
$searchkey = "<!--LINK $key-->";
@@ -4102,27 +4191,27 @@ class Parser
$linkCache->addBadLinkObj( $title );
$colours[$pdbk] = 0;
$this->mOutput->addLink( $title, 0 );
- $wgOutputReplace[$searchkey] = $sk->makeBrokenLinkObj( $title,
+ $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title,
$this->mLinkHolders['texts'][$key],
$this->mLinkHolders['queries'][$key] );
} elseif ( $colours[$pdbk] == 1 ) {
- $wgOutputReplace[$searchkey] = $sk->makeKnownLinkObj( $title,
+ $replacePairs[$searchkey] = $sk->makeKnownLinkObj( $title,
$this->mLinkHolders['texts'][$key],
$this->mLinkHolders['queries'][$key] );
} elseif ( $colours[$pdbk] == 2 ) {
- $wgOutputReplace[$searchkey] = $sk->makeStubLinkObj( $title,
+ $replacePairs[$searchkey] = $sk->makeStubLinkObj( $title,
$this->mLinkHolders['texts'][$key],
$this->mLinkHolders['queries'][$key] );
}
}
+ $replacer = new HashtableReplacer( $replacePairs, 1 );
wfProfileOut( $fname.'-construct' );
# Do the thing
wfProfileIn( $fname.'-replace' );
-
$text = preg_replace_callback(
'/(<!--LINK .*?-->)/',
- "wfOutputReplaceMatches",
+ $replacer->cb(),
$text);
wfProfileOut( $fname.'-replace' );
@@ -4133,15 +4222,16 @@ class Parser
if ( !empty( $this->mInterwikiLinkHolders['texts'] ) ) {
wfProfileIn( $fname.'-interwiki' );
# Make interwiki link HTML
- $wgOutputReplace = array();
+ $replacePairs = array();
foreach( $this->mInterwikiLinkHolders['texts'] as $key => $link ) {
$title = $this->mInterwikiLinkHolders['titles'][$key];
- $wgOutputReplace[$key] = $sk->makeLinkObj( $title, $link );
+ $replacePairs[$key] = $sk->makeLinkObj( $title, $link );
}
+ $replacer = new HashtableReplacer( $replacePairs, 1 );
$text = preg_replace_callback(
'/<!--IWLINK (.*?)-->/',
- "wfOutputReplaceMatches",
+ $replacer->cb(),
$text );
wfProfileOut( $fname.'-interwiki' );
}
@@ -4192,13 +4282,13 @@ class Parser
/**
* Tag hook handler for 'pre'.
*/
- function renderPreTag( $text, $attribs, $parser ) {
+ function renderPreTag( $text, $attribs ) {
// Backwards-compatibility hack
- $content = preg_replace( '!<nowiki>(.*?)</nowiki>!is', '\\1', $text );
+ $content = StringUtils::delimiterReplace( '<nowiki>', '</nowiki>', '$1', $text, 'i' );
$attribs = Sanitizer::validateTagAttributes( $attribs, 'pre' );
return wfOpenElement( 'pre', $attribs ) .
- wfEscapeHTMLTagsOnly( $content ) .
+ Xml::escapeTagsOnly( $content ) .
'</pre>';
}
@@ -4218,13 +4308,18 @@ class Parser
$ig->setParsing();
$ig->useSkin( $this->mOptions->getSkin() );
- if( isset( $params['caption'] ) )
- $ig->setCaption( $params['caption'] );
+ if( isset( $params['caption'] ) ) {
+ $caption = $params['caption'];
+ $caption = htmlspecialchars( $caption );
+ $caption = $this->replaceInternalLinks( $caption );
+ $ig->setCaptionHtml( $caption );
+ }
$lines = explode( "\n", $text );
foreach ( $lines as $line ) {
# match lines like these:
# Image:someimage.jpg|This is some image
+ $matches = array();
preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
# Skip empty lines
if ( count( $matches ) == 0 ) {
@@ -4263,7 +4358,7 @@ class Parser
/**
* Parse image options text and use it to make an image
*/
- function makeImage( &$nt, $options ) {
+ function makeImage( $nt, $options ) {
global $wgUseImageResize, $wgDjvuRenderer;
$align = '';
@@ -4295,7 +4390,7 @@ class Parser
$page = null;
$manual_thumb = '' ;
- foreach( $part as $key => $val ) {
+ foreach( $part as $val ) {
if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
$thumb=true;
} elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) {
@@ -4318,9 +4413,10 @@ class Parser
&& ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
# Select a page in a multipage document
$page = $match;
- } elseif ( $wgUseImageResize && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
+ } elseif ( $wgUseImageResize && !$width && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
wfDebug( "img_width match: $match\n" );
# $match is the image width in pixels
+ $m = array();
if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
$width = intval( $m[1] );
$height = intval( $m[2] );
@@ -4339,7 +4435,7 @@ class Parser
# make sure there are no placeholders in thumbnail attributes
# that are later expanded to html- so expand them now and
# remove the tags
- $alt = $this->unstrip($alt, $this->mStripState);
+ $alt = $this->mStripState->unstripBoth( $alt );
$alt = Sanitizer::stripAllTags( $alt );
# Linker does the rest
@@ -4366,15 +4462,10 @@ class Parser
*/
function attributeStripCallback( &$text, $args ) {
$text = $this->replaceVariables( $text, $args );
- $text = $this->unstripForHTML( $text );
+ $text = $this->mStripState->unstripBoth( $text );
return $text;
}
- function unstripForHTML( $text ) {
- $text = $this->unstrip( $text, $this->mStripState );
- $text = $this->unstripNoWiki( $text, $this->mStripState );
- return $text;
- }
/**#@-*/
/**#@+
@@ -4410,14 +4501,14 @@ class Parser
private function extractSections( $text, $section, $mode, $newtext='' ) {
# strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
# comments to be stripped as well)
- $striparray = array();
+ $stripState = new StripState;
$oldOutputType = $this->mOutputType;
$oldOptions = $this->mOptions;
$this->mOptions = new ParserOptions();
$this->setOutputType( OT_WIKI );
- $striptext = $this->strip( $text, $striparray, true );
+ $striptext = $this->strip( $text, $stripState, true );
$this->setOutputType( $oldOutputType );
$this->mOptions = $oldOptions;
@@ -4524,9 +4615,7 @@ class Parser
}
}
# reinsert stripped tags
- $rv = $this->unstrip( $rv, $striparray );
- $rv = $this->unstripNoWiki( $rv, $striparray );
- $rv = trim( $rv );
+ $rv = trim( $stripState->unstripBoth( $rv ) );
return $rv;
}
@@ -4549,6 +4638,62 @@ class Parser
return $this->extractSections( $oldtext, $section, "replace", $text );
}
+ /**
+ * Get the timestamp associated with the current revision, adjusted for
+ * the default server-local timestamp
+ */
+ function getRevisionTimestamp() {
+ if ( is_null( $this->mRevisionTimestamp ) ) {
+ wfProfileIn( __METHOD__ );
+ global $wgContLang;
+ $dbr =& wfGetDB( DB_SLAVE );
+ $timestamp = $dbr->selectField( 'revision', 'rev_timestamp',
+ array( 'rev_id' => $this->mRevisionId ), __METHOD__ );
+
+ // Normalize timestamp to internal MW format for timezone processing.
+ // This has the added side-effect of replacing a null value with
+ // the current time, which gives us more sensible behavior for
+ // previews.
+ $timestamp = wfTimestamp( TS_MW, $timestamp );
+
+ // The cryptic '' timezone parameter tells to use the site-default
+ // timezone offset instead of the user settings.
+ //
+ // Since this value will be saved into the parser cache, served
+ // to other users, and potentially even used inside links and such,
+ // it needs to be consistent for all visitors.
+ $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
+
+ wfProfileOut( __METHOD__ );
+ }
+ return $this->mRevisionTimestamp;
+ }
+
+ /**
+ * Mutator for $mDefaultSort
+ *
+ * @param $sort New value
+ */
+ public function setDefaultSort( $sort ) {
+ $this->mDefaultSort = $sort;
+ }
+
+ /**
+ * Accessor for $mDefaultSort
+ * Will use the title/prefixed title if none is set
+ *
+ * @return string
+ */
+ public function getDefaultSort() {
+ if( $this->mDefaultSort !== false ) {
+ return $this->mDefaultSort;
+ } else {
+ return $this->mTitle->getNamespace() == NS_CATEGORY
+ ? $this->mTitle->getText()
+ : $this->mTitle->getPrefixedText();
+ }
+ }
+
}
/**
@@ -4619,7 +4764,7 @@ class ParserOutput
function addImage( $name ) { $this->mImages[$name] = 1; }
function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; }
function addExternalLink( $url ) { $this->mExternalLinks[$url] = 1; }
-
+
function setNewSection( $value ) {
$this->mNewSection = (bool)$value;
}
@@ -4674,7 +4819,7 @@ class ParserOutput
*/
class ParserOptions
{
- # All variables are private
+ # All variables are supposed to be private in theory, although in practise this is not the case.
var $mUseTeX; # Use texvc to expand <math> tags
var $mUseDynamicDates; # Use DateFormatter to format dates
var $mInterwikiMagic; # Interlanguage links are removed and returned in an array
@@ -4712,7 +4857,7 @@ class ParserOptions
return $this->mSkin;
}
- function getDateFormat() {
+ function getDateFormat() {
if ( !isset( $this->mDateFormat ) ) {
$this->mDateFormat = $this->mUser->getDatePreference();
}
@@ -4729,7 +4874,7 @@ class ParserOptions
function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); }
function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); }
function setTidy( $x ) { return wfSetVar( $this->mTidy, $x); }
- function setSkin( &$x ) { $this->mSkin =& $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 ); }
@@ -4758,7 +4903,6 @@ class ParserOptions
$user = $wgUser;
} else {
$user = new User;
- $user->setLoaded( true );
}
} else {
$user =& $userInput;
@@ -4784,152 +4928,47 @@ class ParserOptions
}
}
-/**
- * Callback function used by Parser::replaceLinkHolders()
- * to substitute link placeholders.
- */
-function &wfOutputReplaceMatches( $matches ) {
- global $wgOutputReplace;
- return $wgOutputReplace[$matches[1]];
-}
-
-/**
- * Return the total number of articles
- */
-function wfNumberOfArticles() {
- global $wgNumberOfArticles;
+class OnlyIncludeReplacer {
+ var $output = '';
- wfLoadSiteStats();
- return $wgNumberOfArticles;
-}
-
-/**
- * Return the number of files
- */
-function wfNumberOfFiles() {
- $fname = 'wfNumberOfFiles';
-
- wfProfileIn( $fname );
- $dbr =& wfGetDB( DB_SLAVE );
- $numImages = $dbr->selectField('site_stats', 'ss_images', array(), $fname );
- wfProfileOut( $fname );
-
- return $numImages;
-}
-
-/**
- * Return the number of user accounts
- * @return integer
- */
-function wfNumberOfUsers() {
- wfProfileIn( 'wfNumberOfUsers' );
- $dbr =& wfGetDB( DB_SLAVE );
- $count = $dbr->selectField( 'site_stats', 'ss_users', array(), 'wfNumberOfUsers' );
- wfProfileOut( 'wfNumberOfUsers' );
- return (int)$count;
+ function replace( $matches ) {
+ if ( substr( $matches[1], -1 ) == "\n" ) {
+ $this->output .= substr( $matches[1], 0, -1 );
+ } else {
+ $this->output .= $matches[1];
+ }
+ }
}
-/**
- * Return the total number of pages
- * @return integer
- */
-function wfNumberOfPages() {
- wfProfileIn( 'wfNumberOfPages' );
- $dbr =& wfGetDB( DB_SLAVE );
- $count = $dbr->selectField( 'site_stats', 'ss_total_pages', array(), 'wfNumberOfPages' );
- wfProfileOut( 'wfNumberOfPages' );
- return (int)$count;
-}
+class StripState {
+ var $general, $nowiki;
-/**
- * Return the total number of admins
- *
- * @return integer
- */
-function wfNumberOfAdmins() {
- static $admins = -1;
- wfProfileIn( 'wfNumberOfAdmins' );
- if( $admins == -1 ) {
- $dbr =& wfGetDB( DB_SLAVE );
- $admins = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), 'wfNumberOfAdmins' );
+ function __construct() {
+ $this->general = new ReplacementArray;
+ $this->nowiki = new ReplacementArray;
}
- wfProfileOut( 'wfNumberOfAdmins' );
- return (int)$admins;
-}
-/**
- * Count the number of pages in a particular namespace
- *
- * @param $ns Namespace
- * @return integer
- */
-function wfPagesInNs( $ns ) {
- static $pageCount = array();
- wfProfileIn( 'wfPagesInNs' );
- if( !isset( $pageCount[$ns] ) ) {
- $dbr =& wfGetDB( DB_SLAVE );
- $pageCount[$ns] = $dbr->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $ns ), 'wfPagesInNs' );
+ function unstripGeneral( $text ) {
+ wfProfileIn( __METHOD__ );
+ $text = $this->general->replace( $text );
+ wfProfileOut( __METHOD__ );
+ return $text;
}
- wfProfileOut( 'wfPagesInNs' );
- return (int)$pageCount[$ns];
-}
-/**
- * Get various statistics from the database
- * @private
- */
-function wfLoadSiteStats() {
- global $wgNumberOfArticles, $wgTotalViews, $wgTotalEdits;
- $fname = 'wfLoadSiteStats';
-
- if ( -1 != $wgNumberOfArticles ) return;
- $dbr =& wfGetDB( DB_SLAVE );
- $s = $dbr->selectRow( 'site_stats',
- array( 'ss_total_views', 'ss_total_edits', 'ss_good_articles' ),
- array( 'ss_row_id' => 1 ), $fname
- );
-
- if ( $s === false ) {
- return;
- } else {
- $wgTotalViews = $s->ss_total_views;
- $wgTotalEdits = $s->ss_total_edits;
- $wgNumberOfArticles = $s->ss_good_articles;
+ function unstripNoWiki( $text ) {
+ wfProfileIn( __METHOD__ );
+ $text = $this->nowiki->replace( $text );
+ wfProfileOut( __METHOD__ );
+ return $text;
}
-}
-
-/**
- * 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;)
- *
- * @param $in String: text that might contain HTML tags.
- * @return string Escaped string
- */
-function wfEscapeHTMLTagsOnly( $in ) {
- return str_replace(
- array( '"', '>', '<' ),
- array( '&quot;', '&gt;', '&lt;' ),
- $in );
+ function unstripBoth( $text ) {
+ wfProfileIn( __METHOD__ );
+ $text = $this->general->replace( $text );
+ $text = $this->nowiki->replace( $text );
+ wfProfileOut( __METHOD__ );
+ return $text;
+ }
}
?>
diff --git a/includes/ParserCache.php b/includes/ParserCache.php
index 1f2e2aaf..37a42b7f 100644
--- a/includes/ParserCache.php
+++ b/includes/ParserCache.php
@@ -56,8 +56,6 @@ class ParserCache {
$fname = 'ParserCache::get';
wfProfileIn( $fname );
- $hash = $user->getPageRenderingHash();
- $pageid = intval( $article->getID() );
$key = $this->getKey( $article, $user );
wfDebug( "Trying parser cache $key\n" );
diff --git a/includes/Profiler.php b/includes/Profiler.php
index 78003e02..30cda63f 100644
--- a/includes/Profiler.php
+++ b/includes/Profiler.php
@@ -164,7 +164,7 @@ class Profiler {
}
function getCallTreeLine($entry) {
- list ($fname, $level, $start, $x, $end) = $entry;
+ list ($fname, $level, $start, /* $x */, $end) = $entry;
$delta = $end - $start;
$space = str_repeat(' ', $level);
@@ -208,7 +208,6 @@ class Profiler {
# First, subtract the overhead!
foreach ($this->mStack as $entry) {
$fname = $entry[0];
- $thislevel = $entry[1];
$start = $entry[2];
$end = $entry[4];
$elapsed = $end - $start;
@@ -229,7 +228,6 @@ class Profiler {
# Collate
foreach ($this->mStack as $index => $entry) {
$fname = $entry[0];
- $thislevel = $entry[1];
$start = $entry[2];
$end = $entry[4];
$elapsed = $end - $start;
@@ -351,7 +349,7 @@ class Profiler {
}
static function getCaller( $level ) {
- $backtrace = debug_backtrace();
+ $backtrace = wfDebugBacktrace();
if ( isset( $backtrace[$level] ) ) {
if ( isset( $backtrace[$level]['class'] ) ) {
$caller = $backtrace[$level]['class'] . '::' . $backtrace[$level]['function'];
diff --git a/includes/ProfilerSimple.php b/includes/ProfilerSimple.php
index d5bdaf94..e69bfc47 100644
--- a/includes/ProfilerSimple.php
+++ b/includes/ProfilerSimple.php
@@ -12,6 +12,7 @@ require_once(dirname(__FILE__).'/Profiler.php');
class ProfilerSimple extends Profiler {
var $mMinimumTime = 0;
+ var $mProfileID = false;
function ProfilerSimple() {
global $wgRequestTime,$wgRUstart;
@@ -24,7 +25,7 @@ class ProfilerSimple extends Profiler {
$entry =& $this->mCollated["-setup"];
if (!is_array($entry)) {
$entry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0);
- $this->mCollated[$functionname] =& $entry;
+ $this->mCollated["-setup"] =& $entry;
}
$entry['cpu'] += $elapsedcpu;
@@ -39,6 +40,18 @@ class ProfilerSimple extends Profiler {
$this->mMinimumTime = $min;
}
+ function setProfileID( $id ) {
+ $this->mProfileID = $id;
+ }
+
+ function getProfileID() {
+ if ( $this->mProfileID === false ) {
+ return wfWikiID();
+ } else {
+ return $this->mProfileID;
+ }
+ }
+
function profileIn($functionname) {
global $wgDebugFunctionEntry;
if ($wgDebugFunctionEntry) {
@@ -48,15 +61,13 @@ class ProfilerSimple extends Profiler {
}
function profileOut($functionname) {
- $memory = memory_get_usage();
-
global $wgDebugFunctionEntry;
if ($wgDebugFunctionEntry) {
$this->debug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n");
}
- list($ofname,$ocount,$ortime,$octime) = array_pop($this->mWorkStack);
+ list($ofname, /* $ocount */ ,$ortime,$octime) = array_pop($this->mWorkStack);
if (!$ofname) {
$this->debug("Profiling error: $functionname\n");
diff --git a/includes/ProfilerSimpleUDP.php b/includes/ProfilerSimpleUDP.php
index e0490512..a8527c38 100644
--- a/includes/ProfilerSimpleUDP.php
+++ b/includes/ProfilerSimpleUDP.php
@@ -21,7 +21,7 @@ class ProfilerSimpleUDP extends ProfilerSimple {
$plength=0;
$packet="";
foreach ($this->mCollated as $entry=>$pfdata) {
- $pfline=sprintf ("%s %s %d %f %f %f %f %s\n", wfWikiID(),"-",$pfdata['count'],
+ $pfline=sprintf ("%s %s %d %f %f %f %f %s\n", $this->getProfileID(),"-",$pfdata['count'],
$pfdata['cpu'],$pfdata['cpu_sq'],$pfdata['real'],$pfdata['real_sq'],$entry);
$length=strlen($pfline);
/* printf("<!-- $pfline -->"); */
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index fd1bc81e..f96262fe 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -212,9 +212,9 @@ class ProtectionForm {
}
function buildScript() {
- global $wgStylePath;
+ global $wgStylePath, $wgStyleVersion;
return '<script type="text/javascript" src="' .
- htmlspecialchars( $wgStylePath . "/common/protect.js" ) .
+ htmlspecialchars( $wgStylePath . "/common/protect.js?$wgStyleVersion" ) .
'"></script>';
}
diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php
index 7974c882..22ea4947 100644
--- a/includes/ProxyTools.php
+++ b/includes/ProxyTools.php
@@ -23,7 +23,7 @@ function wfGetForwardedFor() {
/** Work out the IP address based on various globals */
function wfGetIP() {
- global $wgSquidServers, $wgSquidServersNoPurge, $wgIP;
+ global $wgIP;
# Return cached result
if ( !empty( $wgIP ) ) {
@@ -33,34 +33,31 @@ function wfGetIP() {
/* collect the originating ips */
# Client connecting to this webserver
if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
- $ipchain = array( $_SERVER['REMOTE_ADDR'] );
+ $ipchain = array( IP::canonicalize( $_SERVER['REMOTE_ADDR'] ) );
} else {
# Running on CLI?
$ipchain = array( '127.0.0.1' );
}
$ip = $ipchain[0];
- # Get list of trusted proxies
- # Flipped for quicker access
- $trustedProxies = array_flip( array_merge( $wgSquidServers, $wgSquidServersNoPurge ) );
- if ( count( $trustedProxies ) ) {
- # Append XFF on to $ipchain
- $forwardedFor = wfGetForwardedFor();
- if ( isset( $forwardedFor ) ) {
- $xff = array_map( 'trim', explode( ',', $forwardedFor ) );
- $xff = array_reverse( $xff );
- $ipchain = array_merge( $ipchain, $xff );
- }
- # Step through XFF list and find the last address in the list which is a trusted server
- # 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] ) && IP::isPublic( $ipchain[$i + 1] ) ) {
- $ip = $ipchain[$i + 1];
- }
- } else {
- break;
+ # Append XFF on to $ipchain
+ $forwardedFor = wfGetForwardedFor();
+ if ( isset( $forwardedFor ) ) {
+ $xff = array_map( 'trim', explode( ',', $forwardedFor ) );
+ $xff = array_reverse( $xff );
+ $ipchain = array_merge( $ipchain, $xff );
+ }
+
+ # Step through XFF list and find the last address in the list which is a trusted server
+ # 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 ) {
+ $curIP = IP::canonicalize( $curIP );
+ if ( wfIsTrustedProxy( $curIP ) ) {
+ if ( isset( $ipchain[$i + 1] ) && IP::isPublic( $ipchain[$i + 1] ) ) {
+ $ip = $ipchain[$i + 1];
}
+ } else {
+ break;
}
}
@@ -69,6 +66,21 @@ function wfGetIP() {
return $ip;
}
+function wfIsTrustedProxy( $ip ) {
+ global $wgSquidServers, $wgSquidServersNoPurge;
+
+ if ( in_array( $ip, $wgSquidServers ) ||
+ in_array( $ip, $wgSquidServersNoPurge ) ||
+ wfIsAOLProxy( $ip )
+ ) {
+ $trusted = true;
+ } else {
+ $trusted = false;
+ }
+ wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) );
+ return $trusted;
+}
+
/**
* Forks processes to scan the originating IP for an open proxy server
* MemCached can be used to skip IPs that have already been scanned
@@ -96,7 +108,7 @@ function wfProxyCheck() {
# Fork the processes
if ( !$skip ) {
- $title = Title::makeTitle( NS_SPECIAL, 'Blockme' );
+ $title = SpecialPage::getTitleFor( 'Blockme' );
$iphash = md5( $ip . $wgProxyKey );
$url = $title->getFullURL( 'ip='.$iphash );
@@ -154,6 +166,51 @@ function wfIsLocallyBlockedProxy( $ip ) {
return $ret;
}
+/**
+ * TODO: move this list to the database in a global IP info table incorporating
+ * trusted ISP proxies, blocked IP addresses and open proxies.
+ */
+function wfIsAOLProxy( $ip ) {
+ $ranges = array(
+ '64.12.96.0/19',
+ '149.174.160.0/20',
+ '152.163.240.0/21',
+ '152.163.248.0/22',
+ '152.163.252.0/23',
+ '152.163.96.0/22',
+ '152.163.100.0/23',
+ '195.93.32.0/22',
+ '195.93.48.0/22',
+ '195.93.64.0/19',
+ '195.93.96.0/19',
+ '195.93.16.0/20',
+ '198.81.0.0/22',
+ '198.81.16.0/20',
+ '198.81.8.0/23',
+ '202.67.64.128/25',
+ '205.188.192.0/20',
+ '205.188.208.0/23',
+ '205.188.112.0/20',
+ '205.188.146.144/30',
+ '207.200.112.0/21',
+ );
+
+ static $parsedRanges;
+ if ( is_null( $parsedRanges ) ) {
+ $parsedRanges = array();
+ foreach ( $ranges as $range ) {
+ $parsedRanges[] = IP::parseRange( $range );
+ }
+ }
+
+ $hex = IP::toHex( $ip );
+ foreach ( $parsedRanges as $range ) {
+ if ( $hex >= $range[0] && $hex <= $range[1] ) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/includes/QueryPage.php b/includes/QueryPage.php
index 7d6dc900..ff6355e7 100644
--- a/includes/QueryPage.php
+++ b/includes/QueryPage.php
@@ -92,7 +92,7 @@ class QueryPage {
* @return Title
*/
function getTitle() {
- return Title::makeTitle( NS_SPECIAL, $this->getName() );
+ return SpecialPage::getTitleFor( $this->getName() );
}
/**
@@ -282,13 +282,15 @@ class QueryPage {
$sname = $this->getName();
$fname = get_class($this) . '::doQuery';
- $sql = $this->getSQL();
$dbr =& wfGetDB( DB_SLAVE );
- $querycache = $dbr->tableName( 'querycache' );
$wgOut->setSyndicated( $this->isSyndicated() );
- if ( $this->isCached() ) {
+ if ( !$this->isCached() ) {
+ $sql = $this->getSQL();
+ } else {
+ # Get the cached result
+ $querycache = $dbr->tableName( 'querycache' );
$type = $dbr->strencode( $sname );
$sql =
"SELECT qc_type as type, qc_namespace as namespace,qc_title as title, qc_value as value
@@ -310,6 +312,14 @@ class QueryPage {
}
$wgOut->addWikiText( $cacheNotice );
+
+ # If updates on this page have been disabled, let the user know
+ # that the data set won't be refreshed for now
+ global $wgDisableQueryPageUpdate;
+ if( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) {
+ $wgOut->addWikiText( wfMsg( 'querypage-no-updates' ) );
+ }
+
}
}
@@ -339,7 +349,7 @@ class QueryPage {
if ( $num > 0 ) {
$s = array();
if ( ! $this->listoutput )
- $s[] = "<ol start='" . ( $offset + 1 ) . "' class='special'>";
+ $s[] = $this->openList( $offset );
# Only read at most $num rows, because $res may contain the whole 1000
for ( $i = 0; $i < $num && $obj = $dbr->fetchObject( $res ); $i++ ) {
@@ -364,7 +374,7 @@ class QueryPage {
$dbr->freeResult( $res );
if ( ! $this->listoutput )
- $s[] = '</ol>';
+ $s[] = $this->closeList();
$str = $this->listoutput ? $wgContLang->listToText( $s ) : implode( '', $s );
$wgOut->addHTML( $str );
}
@@ -373,12 +383,20 @@ class QueryPage {
}
return $num;
}
+
+ function openList( $offset ) {
+ return "<ol start='" . ( $offset + 1 ) . "' class='special'>";
+ }
+
+ function closeList() {
+ return '</ol>';
+ }
/**
* Do any necessary preprocessing of the result object.
- * You should pass this by reference: &$db , &$res
+ * You should pass this by reference: &$db , &$res [although probably no longer necessary in PHP5]
*/
- function preprocessResults( $db, $res ) {}
+ function preprocessResults( &$db, &$res ) {}
/**
* Similar to above, but packaging in a syndicated feed instead of a web page
@@ -459,7 +477,7 @@ class QueryPage {
}
function feedUrl() {
- $title = Title::MakeTitle( NS_SPECIAL, $this->getName() );
+ $title = SpecialPage::getTitleFor( $this->getName() );
return $title->getFullURL();
}
}
diff --git a/includes/RecentChange.php b/includes/RecentChange.php
index ebd4b335..1c7791c2 100644
--- a/includes/RecentChange.php
+++ b/includes/RecentChange.php
@@ -24,6 +24,8 @@
* rc_ip IP address of the user in dotted quad notation
* rc_new obsolete, use rc_type==RC_NEW
* rc_patrolled boolean whether or not someone has marked this edit as patrolled
+ * rc_old_len integer byte length of the text before the edit
+ * rc_new_len the same after the edit
*
* mExtra:
* prefixedDBkey prefixed db key, used by external app via msg queue
@@ -54,7 +56,7 @@ class RecentChange
return $rc;
}
- /* static */ function newFromCurRow( $row, $rc_this_oldid = 0 )
+ public static function newFromCurRow( $row, $rc_this_oldid = 0 )
{
$rc = new RecentChange;
$rc->loadFromCurRow( $row, $rc_this_oldid );
@@ -62,6 +64,24 @@ class RecentChange
$rc->numberofWatchingusers = false;
return $rc;
}
+
+ /**
+ * Obtain the recent change with a given rc_id value
+ *
+ * @param $rcid rc_id value to retrieve
+ * @return RecentChange
+ */
+ public static function newFromId( $rcid ) {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'recentchanges', '*', array( 'rc_id' => $rcid ), __METHOD__ );
+ if( $res && $dbr->numRows( $res ) > 0 ) {
+ $row = $dbr->fetchObject( $res );
+ $dbr->freeResult( $res );
+ return self::newFromRow( $row );
+ } else {
+ return NULL;
+ }
+ }
# Accessors
@@ -95,7 +115,7 @@ class RecentChange
# Writes the data in this object to the database
function save()
{
- global $wgLocalInterwiki, $wgPutIPinRC, $wgRC2UDPAddress, $wgRC2UDPPort, $wgRC2UDPPrefix, $wgUseRCPatrol;
+ global $wgLocalInterwiki, $wgPutIPinRC, $wgRC2UDPAddress, $wgRC2UDPPort, $wgRC2UDPPrefix;
$fname = 'RecentChange::save';
$dbw =& wfGetDB( DB_MASTER );
@@ -212,6 +232,7 @@ class RecentChange
$oldId, $lastTimestamp, $bot = "default", $ip = '', $oldSize = 0, $newSize = 0,
$newId = 0)
{
+
if ( $bot === 'default' ) {
$bot = $user->isAllowed( 'bot' );
}
@@ -240,9 +261,11 @@ class RecentChange
'rc_bot' => $bot ? 1 : 0,
'rc_moved_to_ns' => 0,
'rc_moved_to_title' => '',
- 'rc_ip' => $ip,
- 'rc_patrolled' => 0,
- 'rc_new' => 0 # obsolete
+ 'rc_ip' => $ip,
+ 'rc_patrolled' => 0,
+ 'rc_new' => 0, # obsolete
+ 'rc_old_len' => $oldSize,
+ 'rc_new_len' => $newSize
);
$rc->mExtra = array(
@@ -294,7 +317,9 @@ class RecentChange
'rc_moved_to_title' => '',
'rc_ip' => $ip,
'rc_patrolled' => 0,
- 'rc_new' => 1 # obsolete
+ 'rc_new' => 1, # obsolete
+ 'rc_old_len' => 0,
+ 'rc_new_len' => $size
);
$rc->mExtra = array(
@@ -336,7 +361,9 @@ class RecentChange
'rc_moved_to_title' => $newTitle->getDBkey(),
'rc_ip' => $ip,
'rc_new' => 0, # obsolete
- 'rc_patrolled' => 1
+ 'rc_patrolled' => 1,
+ 'rc_old_len' => NULL,
+ 'rc_new_len' => NULL,
);
$rc->mExtra = array(
@@ -386,7 +413,9 @@ class RecentChange
'rc_moved_to_title' => '',
'rc_ip' => $ip,
'rc_patrolled' => 1,
- 'rc_new' => 0 # obsolete
+ 'rc_new' => 0, # obsolete
+ 'rc_old_len' => NULL,
+ 'rc_new_len' => NULL,
);
$rc->mExtra = array(
'prefixedDBkey' => $title->getPrefixedDBkey(),
@@ -408,7 +437,7 @@ class RecentChange
$this->mExtra = array();
}
- # Makes a pseudo-RC entry from a cur row, for watchlists and things
+ # Makes a pseudo-RC entry from a cur row
function loadFromCurRow( $row )
{
$this->mAttribs = array(
@@ -430,12 +459,23 @@ class RecentChange
'rc_ip' => '',
'rc_id' => $row->rc_id,
'rc_patrolled' => $row->rc_patrolled,
- 'rc_new' => $row->page_is_new # obsolete
+ 'rc_new' => $row->page_is_new, # obsolete
+ 'rc_old_len' => $row->rc_old_len,
+ 'rc_new_len' => $row->rc_new_len,
);
$this->mExtra = array();
}
+ /**
+ * Get an attribute value
+ *
+ * @param $name Attribute name
+ * @return mixed
+ */
+ public function getAttribute( $name ) {
+ return isset( $this->mAttribs[$name] ) ? $this->mAttribs[$name] : NULL;
+ }
/**
* Gets the end part of the diff URL associated with this object
@@ -522,5 +562,37 @@ class RecentChange
return $fullString;
}
+ /**
+ * Returns the change size (HTML).
+ * The lengths can be given optionally.
+ */
+ function getCharacterDifference( $old = 0, $new = 0 ) {
+ global $wgRCChangedSizeThreshold, $wgLang;
+
+ if( $old === 0 ) {
+ $old = $this->mAttribs['rc_old_len'];
+ }
+ if( $new === 0 ) {
+ $new = $this->mAttribs['rc_new_len'];
+ }
+
+ if( $old === NULL || $new === NULL ) {
+ return '';
+ }
+
+ $szdiff = $new - $old;
+ $formatedSize = wfMsgExt( 'rc-change-size', array( 'parsemag', 'escape'),
+ $wgLang->formatNum($szdiff) );
+
+ if( $szdiff < $wgRCChangedSizeThreshold ) {
+ return '<strong class=\'mw-plusminus-neg\'>(' . $formatedSize . ')</strong>';
+ } elseif( $szdiff === 0 ) {
+ return '<span class=\'mw-plusminus-null\'>(' . $formatedSize . ')</span>';
+ } elseif( $szdiff > 0 ) {
+ return '<span class=\'mw-plusminus-pos\'>(+' . $formatedSize . ')</span>';
+ } else {
+ return '<span class=\'mw-plusminus-neg\'>(' . $formatedSize . ')</span>';
+ }
+ }
}
?>
diff --git a/includes/Revision.php b/includes/Revision.php
index bd68e05a..c5235e22 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -297,6 +297,7 @@ class Revision {
// Enforce spacing trimming on supplied text
$this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
$this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
+ $this->mTextRow = null;
$this->mTitle = null; # Load on demand if needed
$this->mCurrent = false;
@@ -507,7 +508,7 @@ class Revision {
* @param string $prefix table prefix (default 'old_')
* @return string $text|false the text requested
*/
- function getRevisionText( $row, $prefix = 'old_' ) {
+ public static function getRevisionText( $row, $prefix = 'old_' ) {
$fname = 'Revision::getRevisionText';
wfProfileIn( $fname );
@@ -531,7 +532,7 @@ class Revision {
# Use external methods for external objects, text in table is URL-only then
if ( in_array( 'external', $flags ) ) {
$url=$text;
- @list($proto,$path)=explode('://',$url,2);
+ @list(/* $proto */,$path)=explode('://',$url,2);
if ($path=="") {
wfProfileOut( $fname );
return false;
@@ -801,6 +802,7 @@ class Revision {
* @param integer $id
*/
static function getTimestampFromID( $id ) {
+ $dbr =& wfGetDB( DB_SLAVE );
$timestamp = $dbr->selectField( 'revision', 'rev_timestamp',
array( 'rev_id' => $id ), __METHOD__ );
if ( $timestamp === false ) {
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index 185679f6..0c0f7244 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -390,11 +390,13 @@ class Sanitizer {
if(!$wgUseTidy) {
$tagstack = $tablestack = array();
foreach ( $bits as $x ) {
- $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
- preg_match( '!^(/?)(\\w+)([^>]*?)(/{0,1}>)([^<]*)$!', $x, $regs );
- list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
- error_reporting( $prev );
-
+ $regs = array();
+ if( preg_match( '!^(/?)(\\w+)([^>]*?)(/{0,1}>)([^<]*)$!', $x, $regs ) ) {
+ list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
+ } else {
+ $slash = $t = $params = $brace = $rest = null;
+ }
+
$badtag = 0 ;
if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
# Check our stack
@@ -487,7 +489,7 @@ class Sanitizer {
foreach ( $bits as $x ) {
preg_match( '/^(\\/?)(\\w+)([^>]*?)(\\/{0,1}>)([^<]*)$/',
$x, $regs );
- @list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
+ @list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
if( is_callable( $processCallback ) ) {
call_user_func_array( $processCallback, array( &$params, $args ) );
@@ -603,7 +605,8 @@ class Sanitizer {
$stripped = Sanitizer::decodeCharReferences( $value );
// Remove any comments; IE gets token splitting wrong
- $stripped = preg_replace( '!/\\*.*?\\*/!S', ' ', $stripped );
+ $stripped = StringUtils::delimiterReplace( '/*', '*/', ' ', $stripped );
+
$value = $stripped;
// ... and continue checks
@@ -737,6 +740,25 @@ class Sanitizer {
}
/**
+ * Given a value, escape it so that it can be used as a CSS class and
+ * return it.
+ *
+ * TODO: For extra validity, input should be validated UTF-8.
+ *
+ * @link http://www.w3.org/TR/CSS21/syndata.html Valid characters/format
+ *
+ * @param string $class
+ * @return string
+ */
+ static function escapeClass( $class ) {
+ // Convert ugly stuff to underscores and kill underscores in ugly places
+ return rtrim(preg_replace(
+ array('/(^[0-9\\-])|[\\x00-\\x20!"#$%&\'()*+,.\\/:;<=>?@[\\]^`{|}~]|\\xC2\\xA0/','/_+/'),
+ '_',
+ $class ), '_');
+ }
+
+ /**
* Regex replace callback for armoring links against further processing.
* @param array $matches
* @return string
@@ -1159,7 +1181,7 @@ class Sanitizer {
*/
static function stripAllTags( $text ) {
# Actual <tags>
- $text = preg_replace( '/ < .*? > /x', '', $text );
+ $text = StringUtils::delimiterReplace( '<', '>', '', $text );
# Normalize &entities and whitespace
$text = Sanitizer::normalizeAttributeValue( $text );
@@ -1203,8 +1225,9 @@ class Sanitizer {
$url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F]/e', "urlencode('\\0')", $url );
# Validate hostname portion
+ $matches = array();
if( preg_match( '!^([^:]+:)(//[^/]+)?(.*)$!iD', $url, $matches ) ) {
- list( $whole, $protocol, $host, $rest ) = $matches;
+ list( /* $whole */, $protocol, $host, $rest ) = $matches;
// Characters that will be ignored in IDNs.
// http://tools.ietf.org/html/3454#section-3.1
diff --git a/includes/SearchEngine.php b/includes/SearchEngine.php
index 5e598883..cec40c91 100644
--- a/includes/SearchEngine.php
+++ b/includes/SearchEngine.php
@@ -116,7 +116,7 @@ class SearchEngine {
# Entering an IP address goes to the contributions page
if ( ( $title->getNamespace() == NS_USER && User::isIP($title->getText() ) )
|| User::isIP( trim( $searchterm ) ) ) {
- return Title::makeTitle( NS_SPECIAL, "Contributions/" . $title->getDbkey() );
+ return SpecialPage::getTitleFor( 'Contributions', $title->getDbkey() );
}
@@ -126,6 +126,7 @@ class SearchEngine {
}
# Quoted term? Try without the quotes...
+ $matches = array();
if( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
return SearchEngine::getNearMatch( $matches[1] );
}
diff --git a/includes/SearchMySQL4.php b/includes/SearchMySQL4.php
index dcc1f685..c20e3f8e 100644
--- a/includes/SearchMySQL4.php
+++ b/includes/SearchMySQL4.php
@@ -43,6 +43,7 @@ class SearchMySQL4 extends SearchMySQL {
$this->searchTerms = array();
# FIXME: This doesn't handle parenthetical expressions.
+ $m = array();
if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
$filteredText, $m, PREG_SET_ORDER ) ) {
foreach( $m as $terms ) {
@@ -60,7 +61,7 @@ class SearchMySQL4 extends SearchMySQL {
$this->searchTerms[] = $regexp;
}
wfDebug( "Would search with '$searchon'\n" );
- wfDebug( "Match with /\b" . implode( '\b|\b', $this->searchTerms ) . "\b/\n" );
+ wfDebug( 'Match with /\b' . implode( '\b|\b', $this->searchTerms ) . "\b/\n" );
} else {
wfDebug( "Can't understand search query '{$filteredText}'\n" );
}
diff --git a/includes/SearchPostgres.php b/includes/SearchPostgres.php
index faf53f02..457636b4 100644
--- a/includes/SearchPostgres.php
+++ b/includes/SearchPostgres.php
@@ -60,6 +60,7 @@ class SearchPostgres extends SearchEngine {
$this->searchTerms = array();
# FIXME: This doesn't handle parenthetical expressions.
+ $m = array();
if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
$filteredText, $m, PREG_SET_ORDER ) ) {
foreach( $m as $terms ) {
@@ -77,7 +78,7 @@ class SearchPostgres extends SearchEngine {
$this->searchTerms[] = $regexp;
}
wfDebug( "Would search with '$searchon'\n" );
- wfDebug( "Match with /\b" . implode( '\b|\b', $this->searchTerms ) . "\b/\n" );
+ wfDebug( 'Match with /\b' . implode( '\b|\b', $this->searchTerms ) . "\b/\n" );
} else {
wfDebug( "Can't understand search query '{$this->filteredText}'\n" );
}
@@ -97,7 +98,8 @@ class SearchPostgres extends SearchEngine {
$match = $this->parseQuery( $filteredTerm, $fulltext );
- $query = "SELECT page_id, page_namespace, page_title, old_text AS page_text ".
+ $query = "SELECT page_id, page_namespace, page_title, old_text AS page_text, ".
+ "rank(titlevector, to_tsquery('default','$match')) AS rnk ".
"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')";
@@ -113,7 +115,7 @@ class SearchPostgres extends SearchEngine {
$query .= " AND page_namespace IN ($namespaces)";
}
- $query .= " ORDER BY rank($fulltext, to_tsquery('default','$fulltext')) DESC";
+ $query .= " ORDER BY rnk DESC, page_id DESC";
$query .= $this->db->limitResult( '', $this->limit, $this->offset );
diff --git a/includes/SearchTsearch2.php b/includes/SearchTsearch2.php
index a8f354b3..1fca9899 100644
--- a/includes/SearchTsearch2.php
+++ b/includes/SearchTsearch2.php
@@ -47,6 +47,7 @@ class SearchTsearch2 extends SearchEngine {
$this->searchTerms = array();
# FIXME: This doesn't handle parenthetical expressions.
+ $m = array();
if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
$filteredText, $m, PREG_SET_ORDER ) ) {
foreach( $m as $terms ) {
@@ -64,7 +65,7 @@ class SearchTsearch2 extends SearchEngine {
$this->searchTerms[] = $regexp;
}
wfDebug( "Would search with '$searchon'\n" );
- wfDebug( "Match with /\b" . implode( '\b|\b', $this->searchTerms ) . "\b/\n" );
+ wfDebug( 'Match with /\b' . implode( '\b|\b', $this->searchTerms ) . "\b/\n" );
} else {
wfDebug( "Can't understand search query '{$this->filteredText}'\n" );
}
@@ -112,7 +113,7 @@ class SearchTsearch2 extends SearchEngine {
$dbw=& wfGetDB(DB_MASTER);
$searchindex = $dbw->tableName( 'searchindex' );
$sql = "UPDATE $searchindex SET si_title=to_tsvector('" .
- $db->strencode( $title ) .
+ $dbw->strencode( $title ) .
"') WHERE si_page={$id}";
$dbw->query( $sql, "SearchMySQL4::updateTitle" );
diff --git a/includes/Setup.php b/includes/Setup.php
index 8fe9ef71..80a5b48a 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -28,6 +28,33 @@ if ( !isset( $wgVersion ) ) {
die( 1 );
}
+// Set various default paths sensibly...
+if( $wgScript === false ) $wgScript = "$wgScriptPath/index.php";
+if( $wgRedirectScript === false ) $wgRedirectScript = "$wgScriptPath/redirect.php";
+
+if( $wgArticlePath === false ) {
+ if( $wgUsePathInfo ) {
+ $wgArticlePath = "$wgScript/$1";
+ } else {
+ $wgArticlePath = "$wgScript?title=$1";
+ }
+}
+
+if( $wgStylePath === false ) $wgStylePath = "$wgScriptPath/skins";
+if( $wgStyleDirectory === false) $wgStyleDirectory = "$IP/skins";
+
+if( $wgLogo === false ) $wgLogo = "$wgStylePath/common/images/wiki.png";
+
+if( $wgUploadPath === false ) $wgUploadPath = "$wgScriptPath/images";
+if( $wgUploadDirectory === false ) $wgUploadDirectory = "$IP/images";
+
+if( $wgMathPath === false ) $wgMathPath = "{$wgUploadPath}/math";
+if( $wgMathDirectory === false ) $wgMathDirectory = "{$wgUploadDirectory}/math";
+if( $wgTmpDirectory === false ) $wgTmpDirectory = "{$wgUploadDirectory}/tmp";
+
+if( $wgReadOnlyFile === false ) $wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR";
+if( $wgFileCacheDirectory === false ) $wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
+
require_once( "$IP/includes/AutoLoader.php" );
wfProfileIn( $fname.'-exception' );
@@ -160,7 +187,9 @@ foreach ( $wgSkinExtensionFunctions as $func ) {
if( !is_object( $wgAuth ) ) {
$wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' );
+ wfRunHooks( 'AuthPluginSetup', array( &$wgAuth ) );
}
+
wfProfileOut( $fname.'-User' );
wfProfileIn( $fname.'-misc2' );
@@ -169,6 +198,7 @@ $wgDeferredUpdateList = array();
$wgPostCommitUpdateList = array();
if ( $wgAjaxSearch ) $wgAjaxExportList[] = 'wfSajaxSearch';
+if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch';
wfSeedRandom();
diff --git a/includes/SiteStats.php b/includes/SiteStats.php
new file mode 100644
index 00000000..e2774a14
--- /dev/null
+++ b/includes/SiteStats.php
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * Static accessor class for site_stats and related things
+ * @package MediaWiki
+ */
+class SiteStats {
+ static $row, $loaded = false;
+ static $admins;
+ static $pageCount = array();
+
+ static function recache() {
+ self::load( true );
+ }
+
+ static function load( $recache = false ) {
+ if ( self::$loaded && !$recache ) {
+ return;
+ }
+
+ $dbr =& wfGetDB( DB_SLAVE );
+ self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ );
+
+ # This code is somewhat schema-agnostic, because I'm changing it in a minor release -- TS
+ if ( !isset( self::$row->ss_total_pages ) && self::$row->ss_total_pages == -1 ) {
+ # Update schema
+ $u = new SiteStatsUpdate( 0, 0, 0 );
+ $u->doUpdate();
+ self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ );
+ }
+ }
+
+ static function views() {
+ self::load();
+ return self::$row->ss_total_views;
+ }
+
+ static function edits() {
+ self::load();
+ return self::$row->ss_total_edits;
+ }
+
+ static function articles() {
+ self::load();
+ return self::$row->ss_good_articles;
+ }
+
+ static function pages() {
+ self::load();
+ return self::$row->ss_total_pages;
+ }
+
+ static function users() {
+ self::load();
+ return self::$row->ss_users;
+ }
+
+ static function images() {
+ self::load();
+ return self::$row->ss_images;
+ }
+
+ static function admins() {
+ if ( !isset( self::$admins ) ) {
+ $dbr =& wfGetDB( DB_SLAVE );
+ self::$admins = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), __METHOD__ );
+ }
+ return self::$admins;
+ }
+
+ static function pagesInNs( $ns ) {
+ wfProfileIn( __METHOD__ );
+ if( !isset( self::$pageCount[$ns] ) ) {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $pageCount[$ns] = (int)$dbr->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $ns ), __METHOD__ );
+ }
+ wfProfileOut( __METHOD__ );
+ return $pageCount[$ns];
+ }
+
+}
+
+
+/**
+ *
+ * @package MediaWiki
+ */
+class SiteStatsUpdate {
+
+ var $mViews, $mEdits, $mGood, $mPages, $mUsers;
+
+ function SiteStatsUpdate( $views, $edits, $good, $pages = 0, $users = 0 ) {
+ $this->mViews = $views;
+ $this->mEdits = $edits;
+ $this->mGood = $good;
+ $this->mPages = $pages;
+ $this->mUsers = $users;
+ }
+
+ function appendUpdate( &$sql, $field, $delta ) {
+ if ( $delta ) {
+ if ( $sql ) {
+ $sql .= ',';
+ }
+ if ( $delta < 0 ) {
+ $sql .= "$field=$field-1";
+ } else {
+ $sql .= "$field=$field+1";
+ }
+ }
+ }
+
+ function doUpdate() {
+ $fname = 'SiteStatsUpdate::doUpdate';
+ $dbw =& wfGetDB( DB_MASTER );
+
+ # First retrieve the row just to find out which schema we're in
+ $row = $dbw->selectRow( 'site_stats', '*', false, $fname );
+
+ $updates = '';
+
+ $this->appendUpdate( $updates, 'ss_total_views', $this->mViews );
+ $this->appendUpdate( $updates, 'ss_total_edits', $this->mEdits );
+ $this->appendUpdate( $updates, 'ss_good_articles', $this->mGood );
+
+ if ( isset( $row->ss_total_pages ) ) {
+ # Update schema if required
+ if ( $row->ss_total_pages == -1 && !$this->mViews ) {
+ $dbr =& wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow') );
+ list( $page, $user ) = $dbr->tableNamesN( 'page', 'user' );
+
+ $sql = "SELECT COUNT(page_namespace) AS total FROM $page";
+ $res = $dbr->query( $sql, $fname );
+ $pageRow = $dbr->fetchObject( $res );
+ $pages = $pageRow->total + $this->mPages;
+
+ $sql = "SELECT COUNT(user_id) AS total FROM $user";
+ $res = $dbr->query( $sql, $fname );
+ $userRow = $dbr->fetchObject( $res );
+ $users = $userRow->total + $this->mUsers;
+
+ if ( $updates ) {
+ $updates .= ',';
+ }
+ $updates .= "ss_total_pages=$pages, ss_users=$users";
+ } else {
+ $this->appendUpdate( $updates, 'ss_total_pages', $this->mPages );
+ $this->appendUpdate( $updates, 'ss_users', $this->mUsers );
+ }
+ }
+ 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 ffbe27c7..3e4f5d3c 100644
--- a/includes/Skin.php
+++ b/includes/Skin.php
@@ -23,6 +23,7 @@ class Skin extends Linker {
var $rc_cache ; # Cache for Enhanced Recent Changes
var $rcCacheIndex ; # Recent Changes Cache Counter for visibility toggle
var $rcMoveIndex;
+ var $mWatchLinkNum = 0; // Appended to end of watch link id's
/**#@-*/
/** Constructor, call parent constructor */
@@ -48,6 +49,7 @@ class Skin extends Linker {
# while code from www.php.net
while (false !== ($file = $skinDir->read())) {
// Skip non-PHP files, hidden files, and '.dep' includes
+ $matches = array();
if(preg_match('/^([^.]*)\.php$/',$file, $matches)) {
$aSkin = $matches[1];
$wgValidSkinNames[strtolower($aSkin)] = $aSkin;
@@ -116,10 +118,9 @@ class Skin extends Linker {
$skinName = $skinNames[$key];
# Grab the skin class and initialise it.
- wfSuppressWarnings();
// Preload base classes to work around APC/PHP5 bug
- include_once( "{$wgStyleDirectory}/{$skinName}.deps.php" );
- wfRestoreWarnings();
+ $deps = "{$wgStyleDirectory}/{$skinName}.deps.php";
+ if( file_exists( $deps ) ) include_once( $deps );
require_once( "{$wgStyleDirectory}/{$skinName}.php" );
# Check if we got if not failback to default skin
@@ -139,7 +140,7 @@ class Skin extends Linker {
/** @return string path to the skin stylesheet */
function getStylesheet() {
- return 'common/wikistandard.css?1';
+ return 'common/wikistandard.css';
}
/** @return string skin name */
@@ -151,8 +152,7 @@ class Skin extends Linker {
global $wgOut, $wgUser;
if ( $wgOut->isQuickbarSuppressed() ) { return 0; }
- $q = $wgUser->getOption( 'quickbar' );
- if ( '' == $q ) { $q = 0; }
+ $q = $wgUser->getOption( 'quickbar', 0 );
return $q;
}
@@ -270,60 +270,71 @@ class Skin extends Linker {
$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>
- ';
-
+ static function makeVariablesScript( $data ) {
+ global $wgJsMimeType;
+
+ $r = "<script type= \"$wgJsMimeType\">/*<![CDATA[*/\n";
+ foreach ( $data as $name => $value ) {
+ $encValue = Xml::encodeJsVar( $value );
+ $r .= "var $name = $encValue;\n";
+ }
+ $r .= "/*]]>*/</script>\n";
+
return $r;
}
- function getHeadScripts() {
- global $wgStylePath, $wgUser, $wgAllowUserJs, $wgJsMimeType;
+ /**
+ * Make a <script> tag containing global variables
+ * @param array $data Associative array containing one element:
+ * skinname => the skin name
+ * The odd calling convention is for backwards compatibility
+ */
+ static function makeGlobalVariablesScript( $data ) {
+ global $wgStylePath, $wgUser;
global $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgLang;
- global $wgTitle, $wgCanonicalNamespaceNames, $wgOut;
-
- $nsname = @$wgCanonicalNamespaceNames[ $wgTitle->getNamespace() ];
- if ( $nsname === NULL ) $nsname = $wgTitle->getNsText();
+ global $wgTitle, $wgCanonicalNamespaceNames, $wgOut, $wgArticle;
+ global $wgBreakFrames;
+ $ns = $wgTitle->getNamespace();
+ $nsname = isset( $wgCanonicalNamespaceNames[ $ns ] ) ? $wgCanonicalNamespaceNames[ $ns ] : $wgTitle->getNsText();
+
$vars = array(
- 'jsmimetype' => $wgJsMimeType,
- 'skinname' => $this->getSkinName(),
+ 'skin' => $data['skinname'],
'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(),
+ 'wgArticlePath' => $wgArticlePath,
+ 'wgScriptPath' => $wgScriptPath,
+ 'wgServer' => $wgServer,
+ 'wgCanonicalNamespace' => $nsname,
+ 'wgCanonicalSpecialPageName' => SpecialPage::resolveAlias( $wgTitle->getDBKey() ),
+ 'wgNamespaceNumber' => $wgTitle->getNamespace(),
+ 'wgPageName' => $wgTitle->getPrefixedDBKey(),
+ 'wgTitle' => $wgTitle->getText(),
+ 'wgArticleId' => $wgTitle->getArticleId(),
+ 'wgIsArticle' => $wgOut->isArticle(),
+ 'wgUserName' => $wgUser->isAnon() ? NULL : $wgUser->getName(),
+ 'wgUserLanguage' => $wgLang->getCode(),
+ 'wgContentLanguage' => $wgContLang->getCode(),
+ 'wgBreakFrames' => $wgBreakFrames,
+ 'wgCurRevisionId' => isset( $wgArticle ) ? $wgArticle->getLatest() : 0,
);
- $r = self::makeGlobalVariablesScript( $vars );
+ return self::makeVariablesScript( $vars );
+ }
+
+ function getHeadScripts() {
+ global $wgStylePath, $wgUser, $wgAllowUserJs, $wgJsMimeType, $wgStyleVersion;
+
+ $r = self::makeGlobalVariablesScript( array( 'skinname' => $this->getSkinName() ) );
- $r .= "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/wikibits.js\"></script>\n";
+ $r .= "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/wikibits.js?$wgStyleVersion\"></script>\n";
+ global $wgUseSiteJs;
+ if ($wgUseSiteJs) {
+ if ($wgUser->isLoggedIn()) {
+ $r .= "<script type=\"$wgJsMimeType\" src=\"".htmlspecialchars(self::makeUrl('-','action=raw&smaxage=0&gen=js'))."\"><!-- site js --></script>\n";
+ } else {
+ $r .= "<script type=\"$wgJsMimeType\" src=\"".htmlspecialchars(self::makeUrl('-','action=raw&gen=js'))."\"><!-- site js --></script>\n";
+ }
+ }
if( $wgAllowUserJs && $wgUser->isLoggedIn() ) {
$userpage = $wgUser->getUserPage();
$userjs = htmlspecialchars( self::makeUrl(
@@ -360,11 +371,11 @@ class Skin extends Linker {
# get the user/site-specific stylesheet, SkinTemplate loads via RawPage.php (settings are cached that way)
function getUserStylesheet() {
- global $wgStylePath, $wgRequest, $wgContLang, $wgSquidMaxage;
+ global $wgStylePath, $wgRequest, $wgContLang, $wgSquidMaxage, $wgStyleVersion;
$sheet = $this->getStylesheet();
- $action = $wgRequest->getText('action');
- $s = "@import \"$wgStylePath/$sheet\";\n";
- if($wgContLang->isRTL()) $s .= "@import \"$wgStylePath/common/common_rtl.css\";\n";
+ $s = "@import \"$wgStylePath/common/common.css?$wgStyleVersion\";\n";
+ $s .= "@import \"$wgStylePath/$sheet?$wgStyleVersion\";\n";
+ if($wgContLang->isRTL()) $s .= "@import \"$wgStylePath/common/common_rtl.css?$wgStyleVersion\";\n";
$query = "usemsgcache=yes&action=raw&ctype=text/css&smaxage=$wgSquidMaxage";
$s .= '@import "' . self::makeNSUrl( 'Common.css', $query, NS_MEDIAWIKI ) . "\";\n" .
@@ -375,9 +386,40 @@ class Skin extends Linker {
}
/**
- * placeholder, returns generated js in monobook
+ * This returns MediaWiki:Common.js. For some bizarre reason, it does
+ * *not* return any custom user JS from user subpages. Huh?
+ *
+ * @return string
*/
- function getUserJs() { return; }
+ function getUserJs() {
+ $fname = 'Skin::getUserJs';
+ wfProfileIn( __METHOD__ );
+
+ global $wgStylePath;
+ $s = "/* generated javascript */\n";
+ $s .= "var skin = '{$this->skinname}';\nvar stylepath = '{$wgStylePath}';";
+ $s .= "\n\n/* MediaWiki:Common.js */\n";
+ $commonJs = wfMsgForContent('common.js');
+ if ( !wfEmptyMsg ( 'common.js', $commonJs ) ) {
+ $s .= $commonJs;
+ }
+
+ global $wgUseAjax, $wgAjaxWatch;
+ if($wgUseAjax && $wgAjaxWatch) {
+ $s .= "
+
+/* AJAX (un)watch (see /skins/common/ajaxwatch.js) */
+var wgAjaxWatch = {
+ watchMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'watch', array() ) )."',
+ unwatchMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'unwatch', array() ) )."',
+ watchingMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'watching', array() ) )."',
+ unwatchingMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'unwatching', array() ) )."'
+};";
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $s;
+ }
/**
* Return html code that include User stylesheets
@@ -464,7 +506,6 @@ END;
else $a = array( 'bgcolor' => '#FFFFFF' );
if($wgOut->isArticle() && $wgUser->getOption('editondblclick') &&
$wgTitle->userCanEdit() ) {
- $t = wfMsg( 'editthispage' );
$s = $wgTitle->getFullURL( $this->editUrlOptions() );
$s = 'document.location = "' .wfEscapeJSString( $s ) .'";';
$a += array ('ondblclick' => $s);
@@ -477,7 +518,8 @@ END;
}
$a['onload'] .= 'setupRightClickEdit()';
}
- $a['class'] = 'ns-'.$wgTitle->getNamespace().' '.($wgContLang->isRTL() ? "rtl" : "ltr");
+ $a['class'] = 'ns-'.$wgTitle->getNamespace().' '.($wgContLang->isRTL() ? "rtl" : "ltr").
+ ' '.Sanitizer::escapeId( 'page-'.$wgTitle->getPrefixedText() );
return $a;
}
@@ -576,9 +618,8 @@ END;
$pop = '</span>';
$t = $embed . implode ( "{$pop} {$sep} {$embed}" , $wgOut->mCategoryLinks ) . $pop;
- $msg = wfMsgExt('categories', array('parsemag', 'escape'), count( $wgOut->mCategoryLinks ));
- $s = $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Categories' ),
- $msg, 'article=' . urlencode( $wgTitle->getPrefixedDBkey() ) )
+ $msg = wfMsgExt( 'pagecategories', array( 'parsemag', 'escape' ), count( $wgOut->mCategoryLinks ) );
+ $s = $this->makeLinkObj( Title::newFromText( wfMsgForContent('pagecategorieslink') ), $msg )
. ': ' . $t;
# optional 'dmoz-like' category browser. Will be shown under the list
@@ -670,7 +711,8 @@ END;
function pageTitleLinks() {
global $wgOut, $wgTitle, $wgUser, $wgRequest;
- extract( $wgRequest->getValues( 'oldid', 'diff' ) );
+ $oldid = $wgRequest->getVal( 'oldid' );
+ $diff = $wgRequest->getVal( 'diff' );
$action = $wgRequest->getText( 'action' );
$s = $this->printableLink();
@@ -731,8 +773,8 @@ END;
$msg = 'viewdeleted';
}
return wfMsg( $msg,
- $this->makeKnownLink(
- $wgContLang->SpecialPage( 'Undelete/' . $wgTitle->getPrefixedDBkey() ),
+ $this->makeKnownLinkObj(
+ SpecialPage::getTitleFor( 'Undelete', $wgTitle->getPrefixedDBkey() ),
wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $n ) ) );
}
return '';
@@ -812,7 +854,6 @@ END;
function nameAndLogin() {
global $wgUser, $wgTitle, $wgLang, $wgContLang, $wgShowIPinHeader;
- $li = $wgContLang->specialPage( 'Userlogin' );
$lo = $wgContLang->specialPage( 'Userlogout' );
$s = '';
@@ -834,7 +875,7 @@ END;
} else { $q = "returnto={$rt}"; }
$s .= "\n<br />" . $this->makeKnownLinkObj(
- Title::makeTitle( NS_SPECIAL, 'Userlogin' ),
+ SpecialPage::getTitleFor( 'Userlogin' ),
wfMsg( 'login' ), $q );
} else {
$n = $wgUser->getName();
@@ -846,7 +887,7 @@ END;
$s .= $this->makeKnownLinkObj( $wgUser->getUserPage(),
$n ) . "{$tl}<br />" .
- $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Userlogout' ), wfMsg( 'logout' ),
+ $this->makeKnownLinkObj( SpecialPage::getTitleFor( 'Userlogout' ), wfMsg( 'logout' ),
"returnto={$rt}" ) . ' | ' .
$this->specialLink( 'preferences' );
}
@@ -857,7 +898,7 @@ END;
}
function getSearchLink() {
- $searchPage =& Title::makeTitle( NS_SPECIAL, 'Search' );
+ $searchPage = SpecialPage::getTitleFor( 'Search' );
return $searchPage->getLocalURL();
}
@@ -892,7 +933,38 @@ END;
}
# Many people don't like this dropdown box
#$s .= $sep . $this->specialPagesList();
+
+ $s .= $this->variantLinks();
+
+ $s .= $this->extensionTabLinks();
+ return $s;
+ }
+
+ /**
+ * Compatibility for extensions adding functionality through tabs.
+ * Eventually these old skins should be replaced with SkinTemplate-based
+ * versions, sigh...
+ * @return string
+ */
+ function extensionTabLinks() {
+ $tabs = array();
+ $s = '';
+ wfRunHooks( 'SkinTemplateTabs', array( $this, &$tabs ) );
+ foreach( $tabs as $tab ) {
+ $s .= ' | ' . Xml::element( 'a',
+ array( 'href' => $tab['href'] ),
+ $tab['text'] );
+ }
+ return $s;
+ }
+
+ /**
+ * Language/charset variant links for classic-style skins
+ * @return string
+ */
+ function variantLinks() {
+ $s = '';
/* show links to different language variants */
global $wgDisableLangConversion, $wgContLang, $wgTitle;
$variants = $wgContLang->getVariants();
@@ -901,10 +973,9 @@ END;
$varname = $wgContLang->getVariantname( $code );
if( $varname == 'disable' )
continue;
- $s .= ' | <a href="' . $wgTitle->getLocalUrl( 'variant=' . $code ) . '">' . $varname . '</a>';
+ $s .= ' | <a href="' . $wgTitle->escapeLocalUrl( 'variant=' . $code ) . '">' . htmlspecialchars( $varname ) . '</a>';
}
}
-
return $s;
}
@@ -955,7 +1026,8 @@ END;
global $wgOut, $wgLang, $wgArticle, $wgRequest, $wgUser;
global $wgDisableCounters, $wgMaxCredits, $wgShowCreditsIfMax, $wgTitle, $wgPageShowWatchingUsers;
- extract( $wgRequest->getValues( 'oldid', 'diff' ) );
+ $oldid = $wgRequest->getVal( 'oldid' );
+ $diff = $wgRequest->getVal( 'diff' );
if ( ! $wgOut->isArticle() ) { return ''; }
if ( isset( $oldid ) || isset( $diff ) ) { return ''; }
if ( 0 == $wgArticle->getID() ) { return ''; }
@@ -977,7 +1049,7 @@ END;
if ($wgPageShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'watchlist' ) );
+ $watchlist = $dbr->tableName( 'watchlist' );
$sql = "SELECT COUNT(*) AS n FROM $watchlist
WHERE wl_title='" . $dbr->strencode($wgTitle->getDBKey()) .
"' AND wl_namespace=" . $wgTitle->getNamespace() ;
@@ -1045,7 +1117,7 @@ END;
function getPoweredBy() {
global $wgStylePath;
$url = htmlspecialchars( "$wgStylePath/common/images/poweredby_mediawiki_88x31.png" );
- $img = '<a href="http://www.mediawiki.org/"><img src="'.$url.'" alt="MediaWiki" /></a>';
+ $img = '<a href="http://www.mediawiki.org/"><img src="'.$url.'" alt="Powered by MediaWiki" /></a>';
return $img;
}
@@ -1071,12 +1143,8 @@ END;
else { $a = ''; }
$mp = wfMsg( 'mainpage' );
- $titleObj = Title::newFromText( $mp );
- if ( is_object( $titleObj ) ) {
- $url = $titleObj->escapeLocalURL();
- } else {
- $url = '';
- }
+ $mptitle = Title::newMainPage();
+ $url = ( is_object($mptitle) ? $mptitle->escapeLocalURL() : '' );
$logourl = $this->getLogo();
$s = "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
@@ -1088,7 +1156,6 @@ END;
*/
function specialPagesList() {
global $wgUser, $wgContLang, $wgServer, $wgRedirectScript;
- $a = array();
$pages = array_merge( SpecialPage::getRegularPages(), SpecialPage::getRestrictedPages() );
foreach ( $pages as $name => $page ) {
$pages[$name] = $page->getDescription();
@@ -1115,9 +1182,7 @@ END;
}
function mainPageLink() {
- $mp = wfMsgForContent( 'mainpage' );
- $mptxt = wfMsg( 'mainpage');
- $s = $this->makeKnownLink( $mp, $mptxt );
+ $s = $this->makeKnownLinkObj( Title::newMainPage(), wfMsg( 'mainpage' ) );
return $s;
}
@@ -1221,16 +1286,19 @@ END;
function watchThisPage() {
global $wgOut, $wgTitle;
+ ++$this->mWatchLinkNum;
if ( $wgOut->isArticleRelated() ) {
if ( $wgTitle->userIsWatching() ) {
$t = wfMsg( 'unwatchthispage' );
$q = 'action=unwatch';
+ $id = "mw-unwatch-link".$this->mWatchLinkNum;
} else {
$t = wfMsg( 'watchthispage' );
$q = 'action=watch';
+ $id = 'mw-watch-link'.$this->mWatchLinkNum;
}
- $s = $this->makeKnownLinkObj( $wgTitle, $t, $q );
+ $s = $this->makeKnownLinkObj( $wgTitle, $t, $q, '', '', " id=\"$id\"" );
} else {
$s = wfMsg( 'notanarticle' );
}
@@ -1241,7 +1309,7 @@ END;
global $wgTitle;
if ( $wgTitle->userCanMove() ) {
- return $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Movepage' ),
+ return $this->makeKnownLinkObj( SpecialPage::getTitleFor( 'Movepage' ),
wfMsg( 'movethispage' ), 'target=' . $wgTitle->getPrefixedURL() );
} else {
// no message if page is protected - would be redundant
@@ -1259,15 +1327,17 @@ END;
function whatLinksHere() {
global $wgTitle;
- return $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Whatlinkshere' ),
- wfMsg( 'whatlinkshere' ), 'target=' . $wgTitle->getPrefixedURL() );
+ return $this->makeKnownLinkObj(
+ SpecialPage::getTitleFor( 'Whatlinkshere', $wgTitle->getPrefixedDBkey() ),
+ wfMsg( 'whatlinkshere' ) );
}
function userContribsLink() {
global $wgTitle;
- return $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ),
- wfMsg( 'contributions' ), 'target=' . $wgTitle->getPartialURL() );
+ return $this->makeKnownLinkObj(
+ SpecialPage::getTitleFor( 'Contributions', $wgTitle->getDBkey() ),
+ wfMsg( 'contributions' ) );
}
function showEmailUser( $id ) {
@@ -1284,8 +1354,9 @@ END;
function emailUserLink() {
global $wgTitle;
- return $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Emailuser' ),
- wfMsg( 'emailuser' ), 'target=' . $wgTitle->getPartialURL() );
+ return $this->makeKnownLinkObj(
+ SpecialPage::getTitleFor( 'Emailuser', $wgTitle->getDBkey() ),
+ wfMsg( 'emailuser' ) );
}
function watchPageLinksLink() {
@@ -1294,9 +1365,9 @@ END;
if ( ! $wgOut->isArticleRelated() ) {
return '(' . wfMsg( 'notanarticle' ) . ')';
} else {
- return $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL,
- 'Recentchangeslinked' ), wfMsg( 'recentchangeslinked' ),
- 'target=' . $wgTitle->getPrefixedURL() );
+ return $this->makeKnownLinkObj(
+ SpecialPage::getTitleFor( 'Recentchangeslinked', $wgTitle->getPrefixedDBkey() ),
+ wfMsg( 'recentchangeslinked' ) );
}
}
@@ -1437,8 +1508,19 @@ END;
}
/* these are used extensively in SkinTemplate, but also some other places */
+ static function makeMainPageUrl( $urlaction = '' ) {
+ $title = Title::newMainPage();
+ self::checkTitle( $title, $name );
+ return $title->getLocalURL( $urlaction );
+ }
+
static function makeSpecialUrl( $name, $urlaction = '' ) {
- $title = Title::makeTitle( NS_SPECIAL, $name );
+ $title = SpecialPage::getTitleFor( $name );
+ return $title->getLocalURL( $urlaction );
+ }
+
+ static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) {
+ $title = SpecialPage::getSafeTitleFor( $name, $subpage );
return $title->getLocalURL( $urlaction );
}
@@ -1547,7 +1629,19 @@ END;
$text = $line[1];
if (wfEmptyMsg($line[0], $link))
$link = $line[0];
- $href = self::makeInternalOrExternalUrl( $link );
+
+ if ( preg_match( '/^(?:' . wfUrlProtocols() . ')/', $link ) ) {
+ $href = $link;
+ } else {
+ $title = Title::newFromText( $link );
+ if ( $title ) {
+ $title = $title->fixSpecialName();
+ $href = $title->getLocalURL();
+ } else {
+ $href = 'INVALID-TITLE';
+ }
+ }
+
$bar[$heading][] = array(
'text' => $text,
'href' => $href,
@@ -1558,7 +1652,7 @@ END;
}
}
if ($cacheSidebar)
- $cachednotice = $parserMemc->set( $key, $bar, 86400 );
+ $parserMemc->set( $key, $bar, 86400 );
wfProfileOut( $fname );
return $bar;
}
diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php
index 482680e6..ff095477 100644
--- a/includes/SkinTemplate.php
+++ b/includes/SkinTemplate.php
@@ -54,6 +54,7 @@ class MediaWiki_I18N {
$value = wfMsg( $value );
// interpolate variables
+ $m = array();
while (preg_match('/\$([0-9]*?)/sm', $value, $m)) {
list($src, $var) = $m;
wfSuppressWarnings();
@@ -134,6 +135,7 @@ class SkinTemplate extends Skin {
global $wgTitle, $wgArticle, $wgUser, $wgLang, $wgContLang, $wgOut;
global $wgScript, $wgStylePath, $wgContLanguageCode;
global $wgMimeType, $wgJsMimeType, $wgOutputEncoding, $wgRequest;
+ global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces;
global $wgDisableCounters, $wgLogo, $action, $wgFeedClasses, $wgHideInterlanguageLinks;
global $wgMaxCredits, $wgShowCreditsIfMax;
global $wgPageShowWatchingUsers;
@@ -147,7 +149,8 @@ class SkinTemplate extends Skin {
// adding of CSS or Javascript by extensions.
wfRunHooks( 'BeforePageDisplay', array( &$out ) );
- extract( $wgRequest->getValues( 'oldid', 'diff' ) );
+ $oldid = $wgRequest->getVal( 'oldid' );
+ $diff = $wgRequest->getVal( 'diff' );
wfProfileIn( "$fname-init" );
$this->initPage( $out );
@@ -190,17 +193,21 @@ class SkinTemplate extends Skin {
$tpl->set( 'title', $wgOut->getPageTitle() );
$tpl->set( 'pagetitle', $wgOut->getHTMLTitle() );
$tpl->set( 'displaytitle', $wgOut->mPageLinkTitle );
+ $tpl->set( 'pageclass', Sanitizer::escapeClass( 'page-'.$wgTitle->getPrefixedText() ) );
+
+ $nsname = isset( $wgCanonicalNamespaceNames[ $this->mTitle->getNamespace() ] ) ?
+ $wgCanonicalNamespaceNames[ $this->mTitle->getNamespace() ] :
+ $this->mTitle->getNsText();
- $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( 'currevisionid', isset( $wgArticle ) ? $wgArticle->getLatest() : 0 );
+
$tpl->set( 'isarticle', $wgOut->isArticle() );
-
+
$tpl->setRef( "thispage", $this->thispage );
$subpagestr = $this->subPageSubtitle();
$tpl->set(
@@ -219,8 +226,14 @@ class SkinTemplate extends Skin {
if( $wgOut->isSyndicated() ) {
$feeds = array();
foreach( $wgFeedClasses as $format => $class ) {
+ $linktext = $format;
+ if ( $format == "atom" ) {
+ $linktext = wfMsg( 'feed-atom' );
+ } else if ( $format == "rss" ) {
+ $linktext = wfMsg( 'feed-rss' );
+ }
$feeds[$format] = array(
- 'text' => $format,
+ 'text' => $linktext,
'href' => $wgRequest->appendQuery( "feed=$format" )
);
}
@@ -228,9 +241,14 @@ class SkinTemplate extends Skin {
} else {
$tpl->set( 'feeds', false );
}
- if ($wgUseTrackbacks && $out->isArticleRelated())
- $tpl->set( 'trackbackhtml', $wgTitle->trackbackRDF());
+ if ($wgUseTrackbacks && $out->isArticleRelated()) {
+ $tpl->set( 'trackbackhtml', $wgTitle->trackbackRDF() );
+ } else {
+ $tpl->set( 'trackbackhtml', null );
+ }
+ $tpl->setRef( 'xhtmldefaultnamespace', $wgXhtmlDefaultNamespace );
+ $tpl->set( 'xhtmlnamespaces', $wgXhtmlNamespaces );
$tpl->setRef( 'mimetype', $wgMimeType );
$tpl->setRef( 'jsmimetype', $wgJsMimeType );
$tpl->setRef( 'charset', $wgOutputEncoding );
@@ -337,7 +355,7 @@ class SkinTemplate extends Skin {
if ($wgPageShowWatchingUsers) {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'watchlist' ) );
+ $watchlist = $dbr->tableName( 'watchlist' );
$sql = "SELECT COUNT(*) AS n FROM $watchlist
WHERE wl_title='" . $dbr->strencode($this->mTitle->getDBKey()) .
"' AND wl_namespace=" . $this->mTitle->getNamespace() ;
@@ -502,17 +520,20 @@ class SkinTemplate extends Skin {
'href' => $href,
'active' => ( $href == $pageurl )
);
- $href = self::makeSpecialUrl( "Contributions/$this->username" );
+ $href = self::makeSpecialUrlSubpage( 'Contributions', $this->username );
$personal_urls['mycontris'] = array(
'text' => wfMsg( 'mycontris' ),
- 'href' => $href
- # FIXME # 'active' => ( $href == $pageurl . '/' . $this->username )
+ 'href' => $href,
+ // FIXME # 'active' was disabed in r11346 with message: "disable bold link to my contributions; link was bold on all
+ // Special:Contributions, not just current user's (fix me please!)". Until resolved, explicitly setting active to false.
+ 'active' => false # ( ( $href == $pageurl . '/' . $this->username )
);
$personal_urls['logout'] = array(
'text' => wfMsg( 'userlogout' ),
'href' => self::makeSpecialUrl( 'Userlogout',
- $wgTitle->getNamespace() === NS_SPECIAL && $wgTitle->getText() === 'Preferences' ? '' : "returnto={$this->thisurl}"
- )
+ $wgTitle->isSpecial( 'Preferences' ) ? '' : "returnto={$this->thisurl}"
+ ),
+ 'active' => false
);
} else {
if( $wgShowIPinHeader && isset( $_COOKIE[ini_get("session.name")] ) ) {
@@ -534,14 +555,14 @@ class SkinTemplate extends Skin {
$personal_urls['anonlogin'] = array(
'text' => wfMsg('userlogin'),
'href' => self::makeSpecialUrl( 'Userlogin', 'returnto=' . $this->thisurl ),
- 'active' => ( NS_SPECIAL == $wgTitle->getNamespace() && 'Userlogin' == $wgTitle->getDBkey() )
+ 'active' => $wgTitle->isSpecial( 'Userlogin' )
);
} else {
$personal_urls['login'] = array(
'text' => wfMsg('userlogin'),
'href' => self::makeSpecialUrl( 'Userlogin', 'returnto=' . $this->thisurl ),
- 'active' => ( NS_SPECIAL == $wgTitle->getNamespace() && 'Userlogin' == $wgTitle->getDBkey() )
+ 'active' => $wgTitle->isSpecial( 'Userlogin' )
);
}
}
@@ -696,18 +717,18 @@ class SkinTemplate extends Skin {
);
}
if ( $this->mTitle->userCanMove()) {
- $moveTitle = Title::makeTitle( NS_SPECIAL, 'Movepage' );
+ $moveTitle = SpecialPage::getTitleFor( 'Movepage', $this->thispage );
$content_actions['move'] = array(
- 'class' => ($this->mTitle->getDbKey() == 'Movepage' and $this->mTitle->getNamespace == NS_SPECIAL) ? 'selected' : false,
+ 'class' => $this->mTitle->isSpecial( 'Movepage' ) ? 'selected' : false,
'text' => wfMsg('move'),
- 'href' => $moveTitle->getLocalUrl( 'target=' . urlencode( $this->thispage ) )
+ 'href' => $moveTitle->getLocalUrl()
);
}
} else {
//article doesn't exist or is deleted
if( $wgUser->isAllowed( 'delete' ) ) {
if( $n = $this->mTitle->isDeleted() ) {
- $undelTitle = Title::makeTitle( NS_SPECIAL, 'Undelete' );
+ $undelTitle = SpecialPage::getTitleFor( 'Undelete' );
$content_actions['undelete'] = array(
'class' => false,
'text' => wfMsgExt( 'undelete_short', array( 'parsemag' ), $n ),
@@ -739,7 +760,7 @@ class SkinTemplate extends Skin {
} else {
/* show special page tab */
- $content_actions['article'] = array(
+ $content_actions[$this->mTitle->getNamespaceKey()] = array(
'class' => 'selected',
'text' => wfMsg('specialpage'),
'href' => $wgRequest->getRequestURL(), // @bug 2457, 2510
@@ -753,9 +774,6 @@ class SkinTemplate extends Skin {
$variants = $wgContLang->getVariants();
if( !$wgDisableLangConversion && sizeof( $variants ) > 1 ) {
$preferred = $wgContLang->getPreferredVariant();
- $actstr = '';
- if( $action )
- $actstr = 'action=' . $action . '&';
$vcount=0;
foreach( $variants as $code ) {
$varname = $wgContLang->getVariantname( $code );
@@ -765,7 +783,7 @@ class SkinTemplate extends Skin {
$content_actions['varlang-' . $vcount] = array(
'class' => $selected,
'text' => $varname,
- 'href' => $this->mTitle->getLocalUrl( $actstr . 'variant=' . urlencode( $code ) )
+ 'href' => $this->mTitle->getLocalURL('',$code)
);
$vcount ++;
}
@@ -795,10 +813,9 @@ class SkinTemplate extends Skin {
$action = $wgRequest->getText( 'action' );
$oldid = $wgRequest->getVal( 'oldid' );
- $diff = $wgRequest->getVal( 'diff' );
$nav_urls = array();
- $nav_urls['mainpage'] = array( 'href' => self::makeI18nUrl( 'mainpage') );
+ $nav_urls['mainpage'] = array( 'href' => self::makeMainPageUrl() );
if( $wgEnableUploads ) {
if ($wgUploadNavigationUrl) {
$nav_urls['upload'] = array( 'href' => $wgUploadNavigationUrl );
@@ -813,7 +830,9 @@ class SkinTemplate extends Skin {
}
$nav_urls['specialpages'] = array( 'href' => self::makeSpecialUrl( 'Specialpages' ) );
-
+ // default permalink to being off, will override it as required below.
+ $nav_urls['permalink'] = false;
+
// 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' ) ) {
@@ -842,15 +861,17 @@ class SkinTemplate extends Skin {
}
if( $this->mTitle->getNamespace() != NS_SPECIAL ) {
- $wlhTitle = Title::makeTitle( NS_SPECIAL, 'Whatlinkshere' );
+ $wlhTitle = SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage );
$nav_urls['whatlinkshere'] = array(
- 'href' => $wlhTitle->getLocalUrl( 'target=' . urlencode( $this->thispage ) )
+ 'href' => $wlhTitle->getLocalUrl()
);
if( $this->mTitle->getArticleId() ) {
- $rclTitle = Title::makeTitle( NS_SPECIAL, 'Recentchangeslinked' );
+ $rclTitle = SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage );
$nav_urls['recentchangeslinked'] = array(
- 'href' => $rclTitle->getLocalUrl( 'target=' . urlencode( $this->thispage ) )
+ 'href' => $rclTitle->getLocalUrl()
);
+ } else {
+ $nav_urls['recentchangeslinked'] = false;
}
if ($wgUseTrackbacks)
$nav_urls['trackbacklink'] = array(
@@ -868,19 +889,23 @@ class SkinTemplate extends Skin {
if($id || $ip) { # both anons and non-anons have contri list
$nav_urls['contributions'] = array(
- 'href' => self::makeSpecialUrl( 'Contributions/' . $this->mTitle->getText() )
+ 'href' => self::makeSpecialUrlSubpage( 'Contributions', $this->mTitle->getText() )
);
- if ( $wgUser->isAllowed( 'block' ) )
+ if ( $wgUser->isAllowed( 'block' ) ) {
$nav_urls['blockip'] = array(
- 'href' => self::makeSpecialUrl( 'Blockip/' . $this->mTitle->getText() )
- );
+ 'href' => self::makeSpecialUrlSubpage( 'Blockip', $this->mTitle->getText() )
+ );
+ } else {
+ $nav_urls['blockip'] = false;
+ }
} else {
$nav_urls['contributions'] = false;
+ $nav_urls['blockip'] = false;
}
$nav_urls['emailuser'] = false;
if( $this->showEmailUser( $id ) ) {
$nav_urls['emailuser'] = array(
- 'href' => self::makeSpecialUrl( 'Emailuser/' . $this->mTitle->getText() )
+ 'href' => self::makeSpecialUrlSubpage( 'Emailuser', $this->mTitle->getText() )
);
}
wfProfileOut( $fname );
@@ -932,7 +957,10 @@ class SkinTemplate extends Skin {
$siteargs .= '&ts=' . $wgUser->mTouched;
}
- if ($wgContLang->isRTL()) $sitecss .= '@import "' . $wgStylePath . '/' . $this->stylename . '/rtl.css";' . "\n";
+ if( $wgContLang->isRTL() ) {
+ global $wgStyleVersion;
+ $sitecss .= "@import \"$wgStylePath/$this->stylename/rtl.css?$wgStyleVersion\";\n";
+ }
# If we use the site's dynamic CSS, throw that in, too
if ( $wgUseSiteCss ) {
@@ -1003,16 +1031,23 @@ class SkinTemplate extends Skin {
}
/**
- * @public
+ * This returns MediaWiki:Common.js and MediaWiki:[Skinname].js concate-
+ * nated together. For some bizarre reason, it does *not* return any
+ * custom user JS from subpages. Huh?
+ *
+ * There's absolutely no reason to have separate Monobook/Common JSes.
+ * Any JS that cares can just check the skin variable generated at the
+ * top. For now Monobook.js will be maintained, but it should be consi-
+ * dered deprecated.
+ *
+ * @return string
*/
- function getUserJs() {
+ public function getUserJs() {
$fname = 'SkinTemplate::getUserJs';
wfProfileIn( $fname );
- global $wgStylePath;
- $s = '/* generated javascript */';
- $s .= "var skin = '{$this->skinname}';\nvar stylepath = '{$wgStylePath}';";
- $s .= '/* MediaWiki:'.ucfirst($this->skinname)." */\n";
+ $s = parent::getUserJs();
+ $s .= "\n\n/* MediaWiki:".ucfirst($this->skinname).".js (deprecated; migrate to Common.js!) */\n";
// avoid inclusion of non defined user JavaScript (with custom skins only)
// by checking for default message content
@@ -1123,7 +1158,7 @@ class QuickTemplate {
* @private
*/
function haveData( $str ) {
- return $this->data[$str];
+ return isset( $this->data[$str] );
}
/**
diff --git a/includes/SpecialAllmessages.php b/includes/SpecialAllmessages.php
index 6e3f6588..a28ab3c2 100644
--- a/includes/SpecialAllmessages.php
+++ b/includes/SpecialAllmessages.php
@@ -1,12 +1,12 @@
<?php
/**
- * Provide functions to generate a special page
+ * Use this special page to get a list of the MediaWiki system messages.
* @package MediaWiki
* @subpackage SpecialPage
*/
/**
- *
+ * Constructor.
*/
function wfSpecialAllmessages() {
global $wgOut, $wgRequest, $wgMessageCache, $wgTitle;
@@ -18,10 +18,9 @@ function wfSpecialAllmessages() {
return;
}
- $fname = "wfSpecialAllMessages";
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
- wfProfileIn( "$fname-setup");
+ wfProfileIn( __METHOD__ . '-setup' );
$ot = $wgRequest->getText( 'ot' );
$navText = wfMsg( 'allmessagestext' );
@@ -29,7 +28,6 @@ function wfSpecialAllmessages() {
# Make sure all extension messages are available
MessageCache::loadAllMessages();
- $first = true;
$sortedArray = array_merge( Language::getMessagesFor( 'en' ), $wgMessageCache->getExtensionMessagesFor( 'en' ) );
ksort( $sortedArray );
$messages = array();
@@ -42,89 +40,84 @@ function wfSpecialAllmessages() {
}
$wgMessageCache->enableTransform();
- wfProfileOut( "$fname-setup" );
+ wfProfileOut( __METHOD__ . '-setup' );
- wfProfileIn( "$fname-output" );
- if ($ot == 'php') {
- $navText .= makePhp($messages);
- $wgOut->addHTML('PHP | <a href="'.$wgTitle->escapeLocalUrl('ot=html').'">HTML</a><pre>'.htmlspecialchars($navText).'</pre>');
+ wfProfileIn( __METHOD__ . '-output' );
+ if ( $ot == 'php' ) {
+ $navText .= makePhp( $messages );
+ $wgOut->addHTML( 'PHP | <a href="' . $wgTitle->escapeLocalUrl( 'ot=html' ) . '">HTML</a><pre>' . htmlspecialchars( $navText ) . '</pre>' );
} else {
- $wgOut->addHTML( '<a href="'.$wgTitle->escapeLocalUrl('ot=php').'">PHP</a> | HTML' );
+ $wgOut->addHTML( '<a href="' . $wgTitle->escapeLocalUrl( 'ot=php' ) . '">PHP</a> | HTML' );
$wgOut->addWikiText( $navText );
$wgOut->addHTML( makeHTMLText( $messages ) );
}
- wfProfileOut( "$fname-output" );
+ wfProfileOut( __METHOD__ . '-output' );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
}
/**
- *
+ * Create the messages array, formatted in PHP to copy to language files.
+ * @param $messages Messages array.
+ * @return The PHP messages array.
+ * @todo Make suitable for language files.
*/
-function makePhp($messages) {
+function makePhp( $messages ) {
global $wgLang;
$txt = "\n\n\$messages = array(\n";
foreach( $messages as $key => $m ) {
- if($wgLang->getCode() != 'en' and $m['msg'] == $m['enmsg'] ) {
- //if (strstr($m['msg'],"\n")) {
- // $txt.='/* ';
- // $comment=' */';
- //} else {
- // $txt .= '#';
- // $comment = '';
- //}
+ if( $wgLang->getCode() != 'en' && $m['msg'] == $m['enmsg'] ) {
continue;
- } elseif ( wfEmptyMsg( $key, $m['msg'] ) ) {
+ } else if ( wfEmptyMsg( $key, $m['msg'] ) ) {
$m['msg'] = '';
$comment = ' #empty';
} else {
$comment = '';
}
- $txt .= "'$key' => '" . preg_replace( "/(?<!\\\\)'/", "\'", $m['msg']) . "',$comment\n";
+ $txt .= "'$key' => '" . preg_replace( '/(?<!\\\\)\'/', "\'", $m['msg']) . "',$comment\n";
}
$txt .= ');';
return $txt;
}
/**
- *
+ * Create a list of messages, formatted in HTML as a list of messages and values and showing differences between the default language file message and the message in MediaWiki: namespace.
+ * @param $messages Messages array.
+ * @return The HTML list of messages.
*/
function makeHTMLText( $messages ) {
global $wgLang, $wgContLang, $wgUser;
- $fname = "makeHTMLText";
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
$sk =& $wgUser->getSkin();
$talk = $wgLang->getNsText( NS_TALK );
- $mwnspace = $wgLang->getNsText( NS_MEDIAWIKI );
- $mwtalk = $wgLang->getNsText( NS_MEDIAWIKI_TALK );
$input = wfElement( 'input', array(
'type' => 'text',
'id' => 'allmessagesinput',
- 'onkeyup' => 'allmessagesfilter()',),
- '');
+ 'onkeyup' => 'allmessagesfilter()'
+ ), '' );
$checkbox = wfElement( 'input', array(
'type' => 'button',
'value' => wfMsgHtml( 'allmessagesmodified' ),
'id' => 'allmessagescheckbox',
- 'onclick' => 'allmessagesmodified()',),
- '');
+ 'onclick' => 'allmessagesmodified()'
+ ), '' );
- $txt = '<span id="allmessagesfilter" style="display:none;">' .
- wfMsgHtml('allmessagesfilter') . " {$input}{$checkbox} " . '</span>';
+ $txt = '<span id="allmessagesfilter" style="display: none;">' . wfMsgHtml( 'allmessagesfilter' ) . " {$input}{$checkbox} " . '</span>';
- $txt .= "
-<table border='1' cellspacing='0' width='100%' id='allmessagestable'>
+ $txt .= '
+<table border="1" cellspacing="0" width="100%" id="allmessagestable">
<tr>
- <th rowspan='2'>" . wfMsgHtml('allmessagesname') . "</th>
- <th>" . wfMsgHtml('allmessagesdefault') . "</th>
+ <th rowspan="2">' . wfMsgHtml( 'allmessagesname' ) . '</th>
+ <th>' . wfMsgHtml( 'allmessagesdefault' ) . '</th>
</tr>
<tr>
- <th>" . wfMsgHtml('allmessagescurrent') . "</th>
- </tr>";
+ <th>' . wfMsgHtml( 'allmessagescurrent' ) . '</th>
+ </tr>';
+
+ wfProfileIn( __METHOD__ . "-check" );
- wfProfileIn( "$fname-check" );
# This is a nasty hack to avoid doing independent existence checks
# without sending the links and table through the slow wiki parser.
$pageExists = array(
@@ -139,31 +132,29 @@ function makeHTMLText( $messages ) {
$pageExists[$s->page_namespace][$s->page_title] = true;
}
$dbr->freeResult( $res );
- wfProfileOut( "$fname-check" );
+ wfProfileOut( __METHOD__ . "-check" );
- wfProfileIn( "$fname-output" );
+ wfProfileIn( __METHOD__ . "-output" );
$i = 0;
foreach( $messages as $key => $m ) {
-
$title = $wgLang->ucfirst( $key );
- if($wgLang->getCode() != $wgContLang->getCode())
- $title.= '/' . $wgLang->getCode();
+ if( $wgLang->getCode() != $wgContLang->getCode() ) {
+ $title .= '/' . $wgLang->getCode();
+ }
$titleObj =& Title::makeTitle( NS_MEDIAWIKI, $title );
$talkPage =& Title::makeTitle( NS_MEDIAWIKI_TALK, $title );
- $changed = ($m['statmsg'] != $m['msg']);
+ $changed = ( $m['statmsg'] != $m['msg'] );
$message = htmlspecialchars( $m['statmsg'] );
$mw = htmlspecialchars( $m['msg'] );
- #$pageLink = $sk->makeLinkObj( $titleObj, htmlspecialchars( $key ) );
- #$talkLink = $sk->makeLinkObj( $talkPage, htmlspecialchars( $talk ) );
if( isset( $pageExists[NS_MEDIAWIKI][$title] ) ) {
- $pageLink = $sk->makeKnownLinkObj( $titleObj, "<span id='sp-allmessages-i-$i'>" . htmlspecialchars( $key ) . "</span>" );
+ $pageLink = $sk->makeKnownLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" . htmlspecialchars( $key ) . '</span>' );
} else {
- $pageLink = $sk->makeBrokenLinkObj( $titleObj, "<span id='sp-allmessages-i-$i'>" . htmlspecialchars( $key ) . "</span>" );
+ $pageLink = $sk->makeBrokenLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" . htmlspecialchars( $key ) . '</span>' );
}
if( isset( $pageExists[NS_MEDIAWIKI_TALK][$title] ) ) {
$talkLink = $sk->makeKnownLinkObj( $talkPage, htmlspecialchars( $talk ) );
@@ -174,38 +165,35 @@ function makeHTMLText( $messages ) {
$anchor = 'msg_' . htmlspecialchars( strtolower( $title ) );
$anchor = "<a id=\"$anchor\" name=\"$anchor\"></a>";
- if($changed) {
-
+ if( $changed ) {
$txt .= "
- <tr class='orig' id='sp-allmessages-r1-$i'>
- <td rowspan='2'>
+ <tr class=\"orig\" id=\"sp-allmessages-r1-$i\">
+ <td rowspan=\"2\">
$anchor$pageLink<br />$talkLink
</td><td>
$message
</td>
- </tr><tr class='new' id='sp-allmessages-r2-$i'>
+ </tr><tr class=\"new\" id=\"sp-allmessages-r2-$i\">
<td>
$mw
</td>
</tr>";
} else {
-
$txt .= "
- <tr class='def' id='sp-allmessages-r1-$i'>
+ <tr class=\"def\" id=\"sp-allmessages-r1-$i\">
<td>
$anchor$pageLink<br />$talkLink
</td><td>
$mw
</td>
</tr>";
-
}
- $i++;
+ $i++;
}
- $txt .= "</table>";
- wfProfileOut( "$fname-output" );
+ $txt .= '</table>';
+ wfProfileOut( __METHOD__ . '-output' );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $txt;
}
diff --git a/includes/SpecialAllpages.php b/includes/SpecialAllpages.php
index 345c48e6..737e6834 100644
--- a/includes/SpecialAllpages.php
+++ b/includes/SpecialAllpages.php
@@ -24,7 +24,7 @@ function wfSpecialAllpages( $par=NULL, $specialPage ) {
$namespace = 0;
$wgOut->setPagetitle( $namespace > 0 ?
- wfMsg( 'allinnamespace', $namespaces[$namespace] ) :
+ wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
wfMsg( 'allarticles' )
);
@@ -51,7 +51,7 @@ class SpecialAllpages {
*/
function namespaceForm ( $namespace = NS_MAIN, $from = '' ) {
global $wgScript;
- $t = Title::makeTitle( NS_SPECIAL, $this->name );
+ $t = SpecialPage::getTitleFor( $this->name );
$namespaceselect = HTMLnamespaceselector($namespace, null);
@@ -83,8 +83,7 @@ function namespaceForm ( $namespace = NS_MAIN, $from = '' ) {
* @param integer $namespace (default NS_MAIN)
*/
function showToplevel ( $namespace = NS_MAIN, $including = false ) {
- global $wgOut, $wgUser;
- $sk = $wgUser->getSkin();
+ global $wgOut;
$fname = "indexShowToplevel";
# TODO: Either make this *much* faster or cache the title index points
@@ -185,14 +184,10 @@ function showToplevel ( $namespace = NS_MAIN, $including = false ) {
* @param integer $namespace (Default NS_MAIN)
*/
function showline( $inpoint, $outpoint, $namespace = NS_MAIN ) {
- global $wgUser;
- $sk = $wgUser->getSkin();
- $dbr =& wfGetDB( DB_SLAVE );
-
$inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) );
$outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) );
$queryparams = ($namespace ? "namespace=$namespace" : '');
- $special = Title::makeTitle( NS_SPECIAL, $this->name . '/' . $inpoint );
+ $special = SpecialPage::getTitleFor( $this->name, $inpoint );
$link = $special->escapeLocalUrl( $queryparams );
$out = wfMsgHtml(
@@ -215,7 +210,8 @@ function showChunk( $namespace = NS_MAIN, $from, $including = false ) {
$sk = $wgUser->getSkin();
$fromList = $this->getNamespaceKeyAndText($namespace, $from);
-
+ $n = 0;
+
if ( !$fromList ) {
$out = wfMsgWikiHtml( 'allpagesbadtitle' );
} else {
@@ -236,12 +232,8 @@ function showChunk( $namespace = NS_MAIN, $from, $including = false ) {
)
);
- ### FIXME: side link to previous
-
- $n = 0;
$out = '<table style="background: inherit;" border="0" width="100%">';
- $namespaces = $wgContLang->getFormattedNamespaces();
while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) {
$t = Title::makeTitle( $s->page_namespace, $s->page_title );
if( $t ) {
@@ -269,21 +261,71 @@ function showChunk( $namespace = NS_MAIN, $from, $including = false ) {
if ( $including ) {
$out2 = '';
} else {
+
+ # Get the last title from previous chunk
+ $dbr =& wfGetDB( DB_SLAVE );
+ $res_prev = $dbr->select(
+ 'page',
+ 'page_title',
+ array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ),
+ $fname,
+ array( 'ORDER BY' => 'page_title DESC', 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 ) )
+ );
+
+ # Get first title of previous complete chunk
+ if( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) {
+ $pt = $dbr->fetchObject( $res_prev );
+ $prevTitle = Title::makeTitle( $namespace, $pt->page_title );
+ } else {
+ # The previous chunk is not complete, need to link to the very first title
+ # available in the database
+ $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title', array( 'page_namespace' => $namespace ), $fname, array( 'LIMIT' => 1) );
+
+ # Show the previous link if it s not the current requested chunk
+ if( $from != $reallyFirstPage_title ) {
+ $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title );
+ } else {
+ $prevTitle = null;
+ }
+ }
+
$nsForm = $this->namespaceForm ( $namespace, $from );
$out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">';
$out2 .= '<tr valign="top"><td align="left">' . $nsForm;
$out2 .= '</td><td align="right" style="font-size: smaller; margin-bottom: 1em;">' .
$sk->makeKnownLink( $wgContLang->specialPage( "Allpages" ),
wfMsgHtml ( 'allpages' ) );
- if ( isset($dbr) && $dbr && ($n == $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) {
- $self = Title::makeTitle( NS_SPECIAL, 'Allpages' );
+
+ $self = SpecialPage::getTitleFor( 'Allpages' );
+
+ # Do we put a previous link ?
+ if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) {
+ $q = 'from=' . $prevTitle->getPartialUrl() . ( $namespace ? '&namespace=' . $namespace : '' );
+ $prevLink = $sk->makeKnownLinkObj( $self, wfMsgHTML( 'prevpage', $pt ), $q );
+ $out2 .= ' | ' . $prevLink;
+ }
+
+ if( $n == $this->maxPerPage && $s = $dbr->fetchObject($res) ) {
+ # $s is the first link of the next chunk
+ $t = Title::MakeTitle($namespace, $s->page_title);
$q = 'from=' . $t->getPartialUrl() . ( $namespace ? '&namespace=' . $namespace : '' );
- $out2 .= ' | ' . $sk->makeKnownLinkObj( $self, wfMsgHtml( 'nextpage', $t->getText() ), $q );
+ $nextLink = $sk->makeKnownLinkObj( $self, wfMsgHtml( 'nextpage', $t->getText() ), $q );
+ $out2 .= ' | ' . $nextLink;
}
$out2 .= "</td></tr></table><hr />";
}
$wgOut->addHtml( $out2 . $out );
+ if( isset($prevLink) or isset($nextLink) ) {
+ $wgOut->addHtml( '<hr/><p style="font-size: smaller; float: right;">' );
+ if( isset( $prevLink ) )
+ $wgOut->addHTML( $prevLink . ' | ');
+ if( isset( $nextLink ) )
+ $wgOut->addHTML( $nextLink );
+ $wgOut->addHTML( '</p>' );
+
+ }
+
}
/**
@@ -298,18 +340,20 @@ function getNamespaceKeyAndText ($ns, $text) {
return array( $ns, '', '' ); # shortcut for common case
$t = Title::makeTitleSafe($ns, $text);
- if ( $t && $t->isLocal() )
+ if ( $t && $t->isLocal() ) {
return array( $t->getNamespace(), $t->getDBkey(), $t->getText() );
- else if ( $t )
+ } else if ( $t ) {
return NULL;
+ }
# try again, in case the problem was an empty pagename
$text = preg_replace('/(#|$)/', 'X$1', $text);
$t = Title::makeTitleSafe($ns, $text);
- if ( $t && $t->isLocal() )
+ if ( $t && $t->isLocal() ) {
return array( $t->getNamespace(), '', '' );
- else
+ } else {
return NULL;
+ }
}
}
diff --git a/includes/SpecialBlockip.php b/includes/SpecialBlockip.php
index 4eb4957a..626922bb 100644
--- a/includes/SpecialBlockip.php
+++ b/includes/SpecialBlockip.php
@@ -46,15 +46,13 @@ 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 );
- }
+ $byDefault = !$wgRequest->wasPosted();
+ $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly', $byDefault );
+ $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', $byDefault );
+ $this->BlockEnableAutoblock = $wgRequest->getBool( 'wpEnableAutoblock', $byDefault );
}
function showForm( $err ) {
@@ -73,7 +71,7 @@ class IPBlockForm {
$mIpbothertime = wfMsgHtml( 'ipbotheroption' );
$mIpbreason = wfMsgHtml( 'ipbreason' );
$mIpbsubmit = wfMsgHtml( 'ipbsubmit' );
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Blockip' );
+ $titleObj = SpecialPage::getTitleFor( 'Blockip' );
$action = $titleObj->escapeLocalURL( "action=submit" );
if ( "" != $err ) {
@@ -82,7 +80,6 @@ class IPBlockForm {
}
$scBlockAddress = htmlspecialchars( $this->BlockAddress );
- $scBlockExpiry = htmlspecialchars( $this->BlockExpiry );
$scBlockReason = htmlspecialchars( $this->BlockReason );
$scBlockOtherTime = htmlspecialchars( $this->BlockOther );
$scBlockExpiryOptions = htmlspecialchars( wfMsgForContent( 'ipboptions' ) );
@@ -155,10 +152,18 @@ class IPBlockForm {
array( 'tabindex' => 5 ) ) . "
</td>
</tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td align=\"left\">
+ " . wfCheckLabel( wfMsg( 'ipbenableautoblock' ),
+ 'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock,
+ array( 'tabindex' => 6 ) ) . "
+ </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}\" />
+ <input tabindex='7' type='submit' name=\"wpBlock\" value=\"{$mIpbsubmit}\" />
</td>
</tr>
</table>
@@ -183,6 +188,7 @@ class IPBlockForm {
# Check for invalid specifications
if ( ! preg_match( "/^$rxIP$/", $this->BlockAddress ) ) {
+ $matches = array();
if ( preg_match( "/^($rxIP)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) {
if ( $wgSysopRangeBans ) {
if ( $matches[2] > 31 || $matches[2] < 16 ) {
@@ -242,7 +248,7 @@ class IPBlockForm {
$block = new Block( $this->BlockAddress, $userId, $wgUser->getID(),
$this->BlockReason, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly,
- $this->BlockCreateAccount );
+ $this->BlockCreateAccount, $this->BlockEnableAutoblock );
if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) {
@@ -260,7 +266,7 @@ class IPBlockForm {
$this->BlockReason, $expirestr );
# Report to the user
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Blockip' );
+ $titleObj = SpecialPage::getTitleFor( 'Blockip' );
$wgOut->redirect( $titleObj->getFullURL( 'action=success&ip=' .
urlencode( $this->BlockAddress ) ) );
}
@@ -275,7 +281,7 @@ class IPBlockForm {
$wgOut->addWikiText( $text );
}
- function showLogFragment( &$out, &$title ) {
+ 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 ) );
diff --git a/includes/SpecialBooksources.php b/includes/SpecialBooksources.php
index 960f6224..5c047fbe 100644
--- a/includes/SpecialBooksources.php
+++ b/includes/SpecialBooksources.php
@@ -1,109 +1,110 @@
<?php
-/**
- * ISBNs in wiki pages will create links to this page, with the ISBN passed
- * in via the query string.
- *
- * @package MediaWiki
- * @subpackage SpecialPage
- */
-
-/**
- * Constructor
- */
-function wfSpecialBooksources( $par ) {
- global $wgRequest;
-
- $isbn = $par;
- if( empty( $par ) ) {
- $isbn = $wgRequest->getVal( 'isbn' );
- }
- $isbn = preg_replace( '/[^0-9X]/', '', $isbn );
-
- $bsl = new BookSourceList( $isbn );
- $bsl->show();
-}
/**
+ * Special page outputs information on sourcing a book with a particular ISBN
+ * The parser creates links to this page when dealing with ISBNs in wikitext
*
* @package MediaWiki
- * @subpackage SpecialPage
+ * @subpackage Special pages
+ * @author Rob Church <robchur@gmail.com>
+ * @todo Validate ISBNs using the standard check-digit method
*/
-class BookSourceList {
- var $mIsbn;
+class SpecialBookSources extends SpecialPage {
- function BookSourceList( $isbn ) {
- $this->mIsbn = $isbn;
+ /**
+ * ISBN passed to the page, if any
+ */
+ private $isbn = '';
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct( 'Booksources' );
}
-
- function show() {
- global $wgOut;
-
- $wgOut->setPagetitle( wfMsg( "booksources" ) );
- if( $this->mIsbn == '' ) {
- $this->askForm();
- } else {
+
+ /**
+ * Show the special page
+ *
+ * @param $isbn ISBN passed as a subpage parameter
+ */
+ public function execute( $isbn = false ) {
+ global $wgOut, $wgRequest;
+ $this->setHeaders();
+ $this->isbn = $this->cleanIsbn( $isbn ? $isbn : $wgRequest->getText( 'isbn' ) );
+ $wgOut->addWikiText( wfMsgNoTrans( 'booksources-summary' ) );
+ $wgOut->addHtml( $this->makeForm() );
+ if( strlen( $this->isbn) > 0 )
$this->showList();
- }
}
-
- function showList() {
+
+ /**
+ * Trim ISBN and remove characters which aren't required
+ *
+ * @param $isbn Unclean ISBN
+ * @return string
+ */
+ private function cleanIsbn( $isbn ) {
+ return trim( preg_replace( '![^0-9X]!', '', $isbn ) );
+ }
+
+ /**
+ * Generate a form to allow users to enter an ISBN
+ *
+ * @return string
+ */
+ private function makeForm() {
+ global $wgScript;
+ $title = self::getTitleFor( 'Booksources' );
+ $form = '<fieldset><legend>' . wfMsgHtml( 'booksources-search-legend' ) . '</legend>';
+ $form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
+ $form .= Xml::hidden( 'title', $title->getPrefixedText() );
+ $form .= '<p>' . Xml::inputLabel( wfMsg( 'booksources-isbn' ), 'isbn', 'isbn', 20, $this->isbn );
+ $form .= '&nbsp;' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '</p>';
+ $form .= Xml::closeElement( 'form' );
+ $form .= '</fieldset>';
+ return $form;
+ }
+
+ /**
+ * Determine where to get the list of book sources from,
+ * format and output them
+ *
+ * @return string
+ */
+ private function showList() {
global $wgOut, $wgContLang;
- $fname = "BookSourceList::showList()";
-
- # First, see if we have a custom list setup in
- # [[Wikipedia:Book sources]] or equivalent.
- $bstitle = Title::makeTitleSafe( NS_PROJECT, wfMsg( "booksources" ) );
- if( $bstitle ) {
- $revision = Revision::newFromTitle( $bstitle );
- if( $revision ) {
- $bstext = $revision->getText();
- if( $bstext ) {
- $bstext = str_replace( "MAGICNUMBER", $this->mIsbn, $bstext );
- $wgOut->addWikiText( $bstext );
- return;
- }
- }
+
+ # Check for a local page such as Project:Book_sources and use that if available
+ $title = Title::makeTitleSafe( NS_PROJECT, wfMsg( 'booksources' ) ); # Should this be wfMsgForContent()? -- RC
+ if( is_object( $title ) && $title->exists() ) {
+ $rev = Revision::newFromTitle( $title );
+ $wgOut->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) );
+ return true;
}
-
- # Otherwise, use the list of links in the default Language.php file.
- $s = wfMsgWikiHtml( 'booksourcetext' ) . "<ul>\n";
- $bs = $wgContLang->getBookstoreList() ;
- $bsn = array_keys ( $bs ) ;
- foreach ( $bsn as $name ) {
- $adr = $bs[$name] ;
- if ( ! $this->mIsbn ) {
- $adr = explode( ":" , $adr , 2 );
- $adr = explode( "/" , $adr[1] );
- $a = "";
- while ( $a == "" ) {
- $a = array_shift( $adr );
- }
- $adr = "http://".$a ;
- } else {
- $adr = str_replace ( "$1" , $this->mIsbn , $adr ) ;
- }
- $name = htmlspecialchars( $name );
- $adr = htmlspecialchars( $adr );
- $s .= "<li><a href=\"{$adr}\" class=\"external\">{$name}</a></li>\n" ;
- }
- $s .= "</ul>\n";
-
- $wgOut->addHTML( $s );
+
+ # Fall back to the defaults given in the language file
+ $wgOut->addWikiText( wfMsgNoTrans( 'booksources-text' ) );
+ $wgOut->addHtml( '<ul>' );
+ $items = $wgContLang->getBookstoreList();
+ foreach( $items as $label => $url )
+ $wgOut->addHtml( $this->makeListItem( $label, $url ) );
+ $wgOut->addHtml( '</ul>' );
+ return true;
}
-
- function askForm() {
- global $wgOut, $wgTitle;
- $fname = "BookSourceList::askForm()";
-
- $action = $wgTitle->escapeLocalUrl();
- $isbn = htmlspecialchars( wfMsg( "isbn" ) );
- $go = htmlspecialchars( wfMsg( "go" ) );
- $out = "<form action=\"$action\" method='post'>
- $isbn: <input name='isbn' id='isbn' />
- <input type='submit' value=\"$go\" />
- </form>";
- $wgOut->addHTML( $out );
+
+ /**
+ * Format a book source list item
+ *
+ * @param $label Book source label
+ * @param $url Book source URL
+ * @return string
+ */
+ private function makeListItem( $label, $url ) {
+ $url = str_replace( '$1', $this->isbn, $url );
+ return '<li><a href="' . htmlspecialchars( $url ) . '">' . htmlspecialchars( $label ) . '</a></li>';
}
+
}
?>
diff --git a/includes/SpecialBrokenRedirects.php b/includes/SpecialBrokenRedirects.php
index 653e13e2..50935654 100644
--- a/includes/SpecialBrokenRedirects.php
+++ b/includes/SpecialBrokenRedirects.php
@@ -27,7 +27,7 @@ class BrokenRedirectsPage extends PageQueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'page', 'pagelinks' ) );
+ list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' );
$sql = "SELECT 'BrokenRedirects' AS type,
p1.page_namespace AS namespace,
diff --git a/includes/SpecialCategories.php b/includes/SpecialCategories.php
index 89cff20a..346eac63 100644
--- a/includes/SpecialCategories.php
+++ b/includes/SpecialCategories.php
@@ -30,10 +30,11 @@ class CategoriesPage extends QueryPage {
$NScat = NS_CATEGORY;
$dbr =& wfGetDB( DB_SLAVE );
$categorylinks = $dbr->tableName( 'categorylinks' );
+ $implicit_groupby = $dbr->implicitGroupby() ? '1' : 'cl_to';
$s= "SELECT 'Categories' as type,
{$NScat} as namespace,
cl_to as title,
- 1 as value,
+ $implicit_groupby as value,
COUNT(*) as count
FROM $categorylinks
GROUP BY 1,2,3,4";
diff --git a/includes/SpecialConfirmemail.php b/includes/SpecialConfirmemail.php
index 72567609..e64232aa 100644
--- a/includes/SpecialConfirmemail.php
+++ b/includes/SpecialConfirmemail.php
@@ -36,8 +36,8 @@ class EmailConfirmation extends SpecialPage {
$wgOut->addWikiText( wfMsg( 'confirmemail_noemail' ) );
}
} else {
- $title = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
- $self = Title::makeTitle( NS_SPECIAL, 'Confirmemail' );
+ $title = SpecialPage::getTitleFor( 'Userlogin' );
+ $self = SpecialPage::getTitleFor( 'Confirmemail' );
$skin = $wgUser->getSkin();
$llink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $self->getPrefixedUrl() );
$wgOut->addHtml( wfMsgWikiHtml( 'confirmemail_needlogin', $llink ) );
@@ -54,15 +54,21 @@ class EmailConfirmation extends SpecialPage {
global $wgOut, $wgUser, $wgLang, $wgRequest;
if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getText( 'token' ) ) ) {
$ok = $wgUser->sendConfirmationMail();
- $message = WikiError::isError( $ok ) ? 'confirmemail_sendfailed' : 'confirmemail_sent';
- $wgOut->addWikiText( wfMsg( $message ) );
+ if ( WikiError::isError( $ok ) ) {
+ $wgOut->addWikiText( wfMsg( 'confirmemail_sendfailed', $ok->toString() ) );
+ } else {
+ $wgOut->addWikiText( wfMsg( 'confirmemail_sent' ) );
+ }
} else {
if( $wgUser->isEmailConfirmed() ) {
$time = $wgLang->timeAndDate( $wgUser->mEmailAuthenticated, true );
$wgOut->addWikiText( wfMsg( 'emailauthenticated', $time ) );
}
+ if( $wgUser->isEmailConfirmationPending() ) {
+ $wgOut->addWikiText( wfMsg( 'confirmemail_pending' ) );
+ }
$wgOut->addWikiText( wfMsg( 'confirmemail_text' ) );
- $self = Title::makeTitle( NS_SPECIAL, 'Confirmemail' );
+ $self = SpecialPage::getTitleFor( 'Confirmemail' );
$form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl() ) );
$form .= wfHidden( 'token', $wgUser->editToken() );
$form .= wfSubmitButton( wfMsgHtml( 'confirmemail_send' ) );
@@ -85,7 +91,7 @@ class EmailConfirmation extends SpecialPage {
$message = $wgUser->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
$wgOut->addWikiText( wfMsg( $message ) );
if( !$wgUser->isLoggedIn() ) {
- $title = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
+ $title = SpecialPage::getTitleFor( 'Userlogin' );
$wgOut->returnToMain( true, $title->getPrefixedText() );
}
} else {
diff --git a/includes/SpecialContributions.php b/includes/SpecialContributions.php
index 8477b6bc..0a1ef6ee 100644
--- a/includes/SpecialContributions.php
+++ b/includes/SpecialContributions.php
@@ -9,6 +9,10 @@ class ContribsFinder {
var $username, $offset, $limit, $namespace;
var $dbr;
+ /**
+ * Constructor
+ * @param $username Username as a string
+ */
function ContribsFinder( $username ) {
$this->username = $username;
$this->namespace = false;
@@ -27,11 +31,17 @@ class ContribsFinder {
$this->offset = $offset;
}
+ /**
+ * Get timestamp of either first or last contribution made by the user.
+ * @todo Maybe it should be private ?
+ * @param $dir string 'ASC' or 'DESC'.
+ * @return Revision timestamp (rev_timestamp).
+ */
function getEditLimit( $dir ) {
list( $index, $usercond ) = $this->getUserCond();
$nscond = $this->getNamespaceCond();
$use_index = $this->dbr->useIndexClause( $index );
- extract( $this->dbr->tableNames( 'revision', 'page' ) );
+ list( $revision, $page) = $this->dbr->tableNamesN( 'revision', 'page' );
$sql = "SELECT rev_timestamp " .
" FROM $page,$revision $use_index " .
" WHERE rev_page=page_id AND $usercond $nscond" .
@@ -46,6 +56,10 @@ class ContribsFinder {
}
}
+ /**
+ * Get timestamps of first and last contributions made by the user.
+ * @return Array containing first rev_timestamp and last rev_timestamp.
+ */
function getEditLimits() {
return array(
$this->getEditLimit( "ASC" ),
@@ -77,12 +91,15 @@ class ContribsFinder {
return '';
}
+ /**
+ * @return Timestamp of first entry in previous page.
+ */
function getPreviousOffsetForPaging() {
list( $index, $usercond ) = $this->getUserCond();
$nscond = $this->getNamespaceCond();
$use_index = $this->dbr->useIndexClause( $index );
- extract( $this->dbr->tableNames( 'page', 'revision' ) );
+ list( $page, $revision ) = $this->dbr->tableNamesN( 'page', 'revision' );
$sql = "SELECT rev_timestamp FROM $page, $revision $use_index " .
"WHERE page_id = rev_page AND rev_timestamp > '" . $this->offset . "' AND " .
@@ -90,7 +107,7 @@ class ContribsFinder {
$sql .= " ORDER BY rev_timestamp ASC";
$sql = $this->dbr->limitResult( $sql, $this->limit, 0 );
$res = $this->dbr->query( $sql );
-
+
$numRows = $this->dbr->numRows( $res );
if ( $numRows ) {
$this->dbr->dataSeek( $res, $numRows - 1 );
@@ -103,10 +120,13 @@ class ContribsFinder {
return $offset;
}
+ /**
+ * @return Timestamp of first entry in next page.
+ */
function getFirstOffsetForPaging() {
list( $index, $usercond ) = $this->getUserCond();
$use_index = $this->dbr->useIndexClause( $index );
- extract( $this->dbr->tableNames( 'page', 'revision' ) );
+ list( $page, $revision ) = $this->dbr->tableNamesN( 'page', 'revision' );
$nscond = $this->getNamespaceCond();
$sql = "SELECT rev_timestamp FROM $page, $revision $use_index " .
"WHERE page_id = rev_page AND " .
@@ -114,7 +134,7 @@ class ContribsFinder {
$sql .= " ORDER BY rev_timestamp ASC";
$sql = $this->dbr->limitResult( $sql, $this->limit, 0 );
$res = $this->dbr->query( $sql );
-
+
$numRows = $this->dbr->numRows( $res );
if ( $numRows ) {
$this->dbr->dataSeek( $res, $numRows - 1 );
@@ -128,13 +148,13 @@ class ContribsFinder {
}
/* private */ function makeSql() {
- $userCond = $condition = $index = $offsetQuery = '';
+ $offsetQuery = '';
- extract( $this->dbr->tableNames( 'page', 'revision' ) );
+ list( $page, $revision ) = $this->dbr->tableNamesN( 'page', 'revision' );
list( $index, $userCond ) = $this->getUserCond();
if ( $this->offset )
- $offsetQuery = "AND rev_timestamp <= '{$this->offset}'";
+ $offsetQuery = "AND rev_timestamp < '{$this->offset}'";
$nscond = $this->getNamespaceCond();
$use_index = $this->dbr->useIndexClause( $index );
@@ -149,6 +169,11 @@ class ContribsFinder {
return $sql;
}
+ /**
+ * This do the search for the user given when creating the object.
+ * It should probably be the only public function in this class.
+ * @return Array of contributions.
+ */
function find() {
$contribs = array();
$res = $this->dbr->query( $this->makeSql(), __METHOD__ );
@@ -168,7 +193,6 @@ class ContribsFinder {
*/
function wfSpecialContributions( $par = null ) {
global $wgUser, $wgOut, $wgLang, $wgRequest;
- $fname = 'wfSpecialContributions';
$target = isset( $par ) ? $par : $wgRequest->getVal( 'target' );
if ( !strlen( $target ) ) {
@@ -190,7 +214,7 @@ function wfSpecialContributions( $par = null ) {
if ( !strlen( $options['offset'] ) || !preg_match( '/^[0-9]+$/', $options['offset'] ) )
$options['offset'] = '';
- $title = Title::makeTitle( NS_SPECIAL, 'Contributions' );
+ $title = SpecialPage::getTitleFor( 'Contributions' );
$options['target'] = $target;
$nt =& Title::makeTitle( NS_USER, $nt->getDBkey() );
@@ -316,17 +340,17 @@ function contributionsSub( $nt ) {
}
$talk = $nt->getTalkPage();
if( $talk ) {
- # Talk page link
+ # Talk page link
$tools[] = $sk->makeLinkObj( $talk, $wgLang->getNsText( NS_TALK ) );
if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && User::isIP( $nt->getText() ) ) ) {
# Block link
if( $wgUser->isAllowed( 'block' ) )
- $tools[] = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Blockip/' . $nt->getDBkey() ), wfMsgHtml( 'blocklink' ) );
+ $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ), wfMsgHtml( 'blocklink' ) );
# Block log link
- $tools[] = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Log' ), htmlspecialchars( LogPage::logName( 'block' ) ), 'type=block&page=' . $nt->getPrefixedUrl() );
+ $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'sp-contributions-blocklog' ), 'type=block&page=' . $nt->getPrefixedUrl() );
}
# Other logs link
- $tools[] = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Log' ), wfMsgHtml( 'log' ), 'user=' . $nt->getPartialUrl() );
+ $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'log' ), 'user=' . $nt->getPartialUrl() );
$ul .= ' (' . implode( ' | ', $tools ) . ')';
}
return $ul;
@@ -369,12 +393,6 @@ function contributionsForm( $options ) {
* privileges. The rollback link restores the most recent version that was not
* written by the target user.
*
- * If the contributions page is called with the parameter &bot=1, all rollback
- * links also get that parameter. It causes the edit itself and the rollback
- * to be marked as "bot" edits. Bot edits are hidden by default from recent
- * changes, so this allows sysops to combat a busy vandal without bothering
- * other users.
- *
* @todo This would probably look a lot nicer in a table.
*/
function ucListEdit( $sk, $row ) {
@@ -390,7 +408,7 @@ function ucListEdit( $sk, $row ) {
}
$rev = new Revision( $row );
-
+
$page = Title::makeTitle( $row->page_namespace, $row->page_title );
$link = $sk->makeKnownLinkObj( $page );
$difftext = $topmarktext = '';
@@ -403,12 +421,7 @@ function ucListEdit( $sk, $row ) {
}
if( $wgUser->isAllowed( 'rollback' ) ) {
- $extraRollback = $wgRequest->getBool( 'bot' ) ? '&bot=1' : '';
- $extraRollback .= '&token=' . urlencode(
- $wgUser->editToken( array( $page->getPrefixedText(), $row->rev_user_text ) ) );
- $topmarktext .= ' ['. $sk->makeKnownLinkObj( $page,
- $messages['rollbacklink'],
- 'action=rollback&from=' . urlencode( $row->rev_user_text ) . $extraRollback ) .']';
+ $topmarktext .= ' '.$sk->generateRollback( $rev );
}
}
@@ -421,7 +434,7 @@ function ucListEdit( $sk, $row ) {
$comment = $sk->revComment( $rev );
$d = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true );
-
+
if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
$d = '<span class="history-deleted">' . $d . '</span>';
}
diff --git a/includes/SpecialDeadendpages.php b/includes/SpecialDeadendpages.php
index b319a170..4ffe5e03 100644
--- a/includes/SpecialDeadendpages.php
+++ b/includes/SpecialDeadendpages.php
@@ -43,7 +43,7 @@ class DeadendPagesPage extends PageQueryPage {
*/
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'page', 'pagelinks' ) );
+ list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' );
return "SELECT 'Deadendpages' as type, page_namespace AS namespace, page_title as title, page_title AS value " .
"FROM $page LEFT JOIN $pagelinks ON page_id = pl_from " .
"WHERE pl_from IS NULL " .
diff --git a/includes/SpecialDisambiguations.php b/includes/SpecialDisambiguations.php
index 0355c85b..626b967c 100644
--- a/includes/SpecialDisambiguations.php
+++ b/includes/SpecialDisambiguations.php
@@ -32,7 +32,7 @@ class DisambiguationsPage extends PageQueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'page', 'pagelinks', 'templatelinks' ) );
+ list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' );
$dMsgText = wfMsgForContent('disambiguationspage');
diff --git a/includes/SpecialDoubleRedirects.php b/includes/SpecialDoubleRedirects.php
index fe42b00a..cf1153ea 100644
--- a/includes/SpecialDoubleRedirects.php
+++ b/includes/SpecialDoubleRedirects.php
@@ -26,7 +26,7 @@ class DoubleRedirectsPage extends PageQueryPage {
function getSQLText( &$dbr, $namespace = null, $title = null ) {
- extract( $dbr->tableNames( 'page', 'pagelinks' ) );
+ list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' );
$limitToTitle = !( $namespace === null && $title === null );
$sql = $limitToTitle ? "SELECT" : "SELECT 'DoubleRedirects' as type," ;
diff --git a/includes/SpecialEmailuser.php b/includes/SpecialEmailuser.php
index d711947f..38745a37 100644
--- a/includes/SpecialEmailuser.php
+++ b/includes/SpecialEmailuser.php
@@ -67,6 +67,7 @@ class EmailUserForm {
var $target;
var $text, $subject;
+ var $cc_me; // Whether user requested to be sent a separate copy of their email.
/**
* @param User $target
@@ -76,6 +77,7 @@ class EmailUserForm {
$this->target = $target;
$this->text = $wgRequest->getText( 'wpText' );
$this->subject = $wgRequest->getText( 'wpSubject' );
+ $this->cc_me = $wgRequest->getBool( 'wpCCMe' );
}
function showForm() {
@@ -95,9 +97,10 @@ class EmailUserForm {
$emr = wfMsg( "emailsubject" );
$emm = wfMsg( "emailmessage" );
$ems = wfMsg( "emailsend" );
+ $emc = wfMsg( "emailccme" );
$encSubject = htmlspecialchars( $this->subject );
- $titleObj = Title::makeTitle( NS_SPECIAL, "Emailuser" );
+ $titleObj = SpecialPage::getTitleFor( "Emailuser" );
$action = $titleObj->escapeLocalURL( "target=" .
urlencode( $this->target->getName() ) . "&action=submit" );
$token = $wgUser->editToken();
@@ -120,6 +123,7 @@ class EmailUserForm {
<span id='wpTextLabel'><label for=\"wpText\">{$emm}:</label><br /></span>
<textarea name=\"wpText\" rows='20' cols='80' wrap='virtual' style=\"width: 100%;\">" . htmlspecialchars( $this->text ) .
"</textarea>
+" . wfCheckLabel( $emc, 'wpCCMe', 'wpCCMe', $wgUser->getBoolOption( 'ccmeonemails' ) ) . "<br />
<input type='submit' name=\"wpSend\" value=\"{$ems}\" />
<input type='hidden' name='wpEditToken' value=\"$token\" />
</form>\n" );
@@ -140,7 +144,26 @@ class EmailUserForm {
if( WikiError::isError( $mailResult ) ) {
$wgOut->addHTML( wfMsg( "usermailererror" ) . $mailResult);
} else {
- $titleObj = Title::makeTitle( NS_SPECIAL, "Emailuser" );
+
+ // if the user requested a copy of this mail, do this now,
+ // unless they are emailing themselves, in which case one copy of the message is sufficient.
+ if ($this->cc_me && $to != $from) {
+ $cc_subject = wfMsg('emailccsubject', $this->target->getName(), $subject);
+ if( wfRunHooks( 'EmailUser', array( &$from, &$from, &$cc_subject, &$this->text ) ) ) {
+ $ccResult = userMailer( $from, $from, $cc_subject, $this->text );
+ if( WikiError::isError( $ccResult ) ) {
+ // At this stage, the user's CC mail has failed, but their
+ // original mail has succeeded. It's unlikely, but still, what to do?
+ // We can either show them an error, or we can say everything was fine,
+ // or we can say we sort of failed AND sort of succeeded. Of these options,
+ // simply saying there was an error is probably best.
+ $wgOut->addHTML( wfMsg( "usermailererror" ) . $ccResult);
+ return;
+ }
+ }
+ }
+
+ $titleObj = SpecialPage::getTitleFor( "Emailuser" );
$encTarget = wfUrlencode( $this->target->getName() );
$wgOut->redirect( $titleObj->getFullURL( "target={$encTarget}&action=success" ) );
wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $this->text ) );
diff --git a/includes/SpecialExport.php b/includes/SpecialExport.php
index dc52e00b..5e6d6d8d 100644
--- a/includes/SpecialExport.php
+++ b/includes/SpecialExport.php
@@ -30,11 +30,6 @@ function wfSpecialExport( $page = '' ) {
global $wgExportAllowHistory, $wgExportMaxHistory;
$curonly = true;
- $fullHistory = array(
- 'dir' => 'asc',
- 'offset' => false,
- 'limit' => $wgExportMaxHistory,
- );
if( $wgRequest->wasPosted() ) {
$page = $wgRequest->getText( 'pages' );
$curonly = $wgRequest->getCheck( 'curonly' );
@@ -88,12 +83,7 @@ function wfSpecialExport( $page = '' ) {
// Cancel output buffering and gzipping if set
// This should provide safer streaming for pages with history
- while( $status = ob_get_status() ) {
- ob_end_clean();
- if( $status['name'] == 'ob_gzhandler' ) {
- header( 'Content-Encoding:' );
- }
- }
+ wfResetOutputBuffers();
header( "Content-type: application/xml; charset=utf-8" );
$pages = explode( "\n", $page );
@@ -123,7 +113,7 @@ function wfSpecialExport( $page = '' ) {
}
$wgOut->addWikiText( wfMsg( "exporttext" ) );
- $titleObj = Title::makeTitle( NS_SPECIAL, "Export" );
+ $titleObj = SpecialPage::getTitleFor( "Export" );
$form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalUrl() ) );
$form .= wfOpenElement( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ) ) . '</textarea><br />';
diff --git a/includes/SpecialImagelist.php b/includes/SpecialImagelist.php
index 54ee83e5..5ecbe8a6 100644
--- a/includes/SpecialImagelist.php
+++ b/includes/SpecialImagelist.php
@@ -9,18 +9,19 @@
*
*/
function wfSpecialImagelist() {
- global $wgUser, $wgOut, $wgLang, $wgContLang, $wgRequest, $wgMiserMode;
+ global $wgOut;
$pager = new ImageListPager;
$limit = $pager->getForm();
$body = $pager->getBody();
$nav = $pager->getNavigationBar();
- $wgOut->addHTML( "
+ $wgOut->addHTML(
$limit
- <br/>
- $body
- $nav" );
+ . '<br/>'
+ . $body
+ . '<br/>'
+ . $nav );
}
class ImageListPager extends TablePager {
diff --git a/includes/SpecialImport.php b/includes/SpecialImport.php
index aaadb662..1c8ee2e0 100644
--- a/includes/SpecialImport.php
+++ b/includes/SpecialImport.php
@@ -208,7 +208,7 @@ class ImportReporter {
$dbw = wfGetDB( DB_MASTER );
$nullRevision = Revision::newNullRevision(
$dbw, $title->getArticleId(), $comment, true );
- $nullRevId = $nullRevision->insertOn( $dbw );
+ $nullRevision->insertOn( $dbw );
}
}
@@ -304,7 +304,6 @@ class WikiRevision {
}
function importOldRevision() {
- $fname = "WikiImporter::importOldRevision";
$dbw =& wfGetDB( DB_MASTER );
# Sneak a single revision into place
@@ -818,7 +817,7 @@ class ImportStreamSource {
return new ImportStreamSource( $file );
}
- function newFromUpload( $fieldname = "xmlimport" ) {
+ static function newFromUpload( $fieldname = "xmlimport" ) {
$upload =& $_FILES[$fieldname];
if( !isset( $upload ) || !$upload['name'] ) {
@@ -844,10 +843,9 @@ class ImportStreamSource {
return $ret;
}
- function newFromInterwiki( $interwiki, $page, $history=false ) {
- $base = Title::getInterwikiLink( $interwiki );
+ public static function newFromInterwiki( $interwiki, $page, $history=false ) {
$link = Title::newFromText( "$interwiki:Special:Export/$page" );
- if( empty( $base ) || empty( $link ) ) {
+ if( is_null( $link ) || $link->getInterwiki() == '' ) {
return new WikiErrorMsg( 'importbadinterwiki' );
} else {
$params = $history ? 'history=1' : '';
diff --git a/includes/SpecialIpblocklist.php b/includes/SpecialIpblocklist.php
index 437fac7f..293059f2 100644
--- a/includes/SpecialIpblocklist.php
+++ b/includes/SpecialIpblocklist.php
@@ -58,7 +58,7 @@ class IPUnblockForm {
$ipa = wfMsgHtml( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' );
$ipr = wfMsgHtml( 'ipbreason' );
$ipus = wfMsgHtml( 'ipusubmit' );
- $titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
+ $titleObj = SpecialPage::getTitleFor( "Ipblocklist" );
$action = $titleObj->escapeLocalURL( "action=submit" );
if ( "" != $err ) {
@@ -142,7 +142,7 @@ class IPUnblockForm {
if ( $success ) {
# Report to the user
- $titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
+ $titleObj = SpecialPage::getTitleFor( "Ipblocklist" );
$success = $titleObj->getFullURL( "action=success&successip=" . urlencode( $this->ip ) );
$wgOut->redirect( $success );
} else {
@@ -167,6 +167,7 @@ class IPUnblockForm {
}
$conds = array();
+ $matches = array();
if ( $this->ip == '' ) {
// No extra conditions
} elseif ( substr( $this->ip, 0, 1 ) == '#' ) {
@@ -174,7 +175,7 @@ class IPUnblockForm {
} 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 ) ) {
+ } 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 {
@@ -222,7 +223,7 @@ class IPUnblockForm {
'value' => $this->ip ) ) .
wfElement( 'input', array(
'type' => 'submit',
- 'value' => wfMsg( 'search' ) ) ) .
+ 'value' => wfMsg( 'searchbutton' ) ) ) .
'</form>';
}
@@ -241,7 +242,7 @@ class IPUnblockForm {
if( is_null( $msg ) ) {
$msg = array();
$keys = array( 'infiniteblock', 'expiringblock', 'contribslink', 'unblocklink',
- 'anononlyblock', 'createaccountblock' );
+ 'anononlyblock', 'createaccountblock', 'noautoblockblock' );
foreach( $keys as $key ) {
$msg[$key] = wfMsgHtml( $key );
}
@@ -250,16 +251,17 @@ class IPUnblockForm {
}
# Prepare links to the blocker's user and talk pages
+ $blocker_id = $block->getBy();
$blocker_name = $block->getByName();
- $blocker = $sk->MakeLinkObj( Title::makeTitle( NS_USER, $blocker_name ), $blocker_name );
- $blocker .= ' (' . $sk->makeLinkObj( Title::makeTitle( NS_USER_TALK, $blocker_name ), $wgLang->getNsText( NS_TALK ) ) . ')';
+ $blocker = $sk->userLink( $blocker_id, $blocker_name );
+ $blocker .= $sk->userToolLinks( $blocker_id, $blocker_name );
# Prepare links to the block target's user and contribs. pages (as applicable, don't do it for autoblocks)
if( $block->mAuto ) {
$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 ) ) . ')';
+ $target .= ' (' . $sk->makeKnownLinkObj( SpecialPage::getSafeTitleFor( 'Contributions', $block->mAddress ), $msg['contribslink'] ) . ')';
}
$formattedTime = $wgLang->timeanddate( $block->mTimestamp, true );
@@ -277,6 +279,10 @@ class IPUnblockForm {
if ( $block->mCreateAccount ) {
$properties[] = $msg['createaccountblock'];
}
+ if (!$block->mEnableAutoblock && $block->mUser ) {
+ $properties[] = $msg['noautoblockblock'];
+ }
+
$properties = implode( ', ', $properties );
$line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) );
@@ -284,7 +290,7 @@ class IPUnblockForm {
$s = "<li>{$line}";
if ( $wgUser->isAllowed('block') ) {
- $titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
+ $titleObj = SpecialPage::getTitleFor( "Ipblocklist" );
$s .= ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&id=' . urlencode( $block->mId ) ) . ')';
}
$s .= $sk->commentBlock( $block->mReason );
diff --git a/includes/SpecialListusers.php b/includes/SpecialListusers.php
index 4668d0c7..b0794344 100644
--- a/includes/SpecialListusers.php
+++ b/includes/SpecialListusers.php
@@ -210,7 +210,7 @@ class ListUsersPage extends QueryPage {
* $par string (optional) A group to list users from
*/
function wfSpecialListusers( $par = null ) {
- global $wgRequest, $wgContLang;
+ global $wgRequest;
list( $limit, $offset ) = wfCheckLimits();
diff --git a/includes/SpecialLockdb.php b/includes/SpecialLockdb.php
index 72172e2c..f0142e5c 100644
--- a/includes/SpecialLockdb.php
+++ b/includes/SpecialLockdb.php
@@ -62,7 +62,7 @@ class DBLockForm {
$lc = htmlspecialchars( wfMsg( 'lockconfirm' ) );
$lb = htmlspecialchars( wfMsg( 'lockbtn' ) );
$elr = htmlspecialchars( wfMsg( 'enterlockreason' ) );
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Lockdb' );
+ $titleObj = SpecialPage::getTitleFor( 'Lockdb' );
$action = $titleObj->escapeLocalURL( 'action=submit' );
$reason = htmlspecialchars( $this->reason );
$token = htmlspecialchars( $wgUser->editToken() );
@@ -114,7 +114,7 @@ END
$wgLang->timeanddate( wfTimestampNow() ) . ")\n" );
fclose( $fp );
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Lockdb' );
+ $titleObj = SpecialPage::getTitleFor( 'Lockdb' );
$wgOut->redirect( $titleObj->getFullURL( 'action=success' ) );
}
diff --git a/includes/SpecialLog.php b/includes/SpecialLog.php
index e32d2240..7076d819 100644
--- a/includes/SpecialLog.php
+++ b/includes/SpecialLog.php
@@ -97,7 +97,7 @@ class LogReader {
function limitUser( $name ) {
if ( $name == '' )
return false;
- $usertitle = Title::makeTitle( NS_USER, $name );
+ $usertitle = Title::makeTitleSafe( NS_USER, $name );
if ( is_null( $usertitle ) )
return false;
$this->user = $usertitle->getText();
@@ -151,7 +151,6 @@ class LogReader {
*/
function getQuery() {
$logging = $this->db->tableName( "logging" );
- $user = $this->db->tableName( 'user' );
$sql = "SELECT /*! STRAIGHT_JOIN */ log_type, log_action, log_timestamp,
log_user, user_name,
log_namespace, log_title, page_id,
@@ -304,7 +303,6 @@ class LogViewer {
function logLine( $s ) {
global $wgLang;
$title = Title::makeTitle( $s->log_namespace, $s->log_title );
- $user = Title::makeTitleSafe( NS_USER, $s->user_name );
$time = $wgLang->timeanddate( wfTimestamp(TS_MW, $s->log_timestamp), true );
// Enter the existence or non-existence of this page into the link cache,
@@ -321,7 +319,7 @@ class LogViewer {
$paramArray = LogPage::extractParams( $s->log_params );
$revert = '';
if ( $s->log_type == 'move' && isset( $paramArray[0] ) ) {
- $specialTitle = Title::makeTitle( NS_SPECIAL, 'Movepage' );
+ $specialTitle = SpecialPage::getTitleFor( 'Movepage' );
$destTitle = Title::newFromText( $paramArray[0] );
if ( $destTitle ) {
$revert = '(' . $this->skin->makeKnownLinkObj( $specialTitle, wfMsg( 'revertmove' ),
@@ -356,7 +354,7 @@ class LogViewer {
function showOptions( &$out ) {
global $wgScript;
$action = htmlspecialchars( $wgScript );
- $title = Title::makeTitle( NS_SPECIAL, 'Log' );
+ $title = SpecialPage::getTitleFor( 'Log' );
$special = htmlspecialchars( $title->getPrefixedDBkey() );
$out->addHTML( "<form action=\"$action\" method=\"get\">\n" .
"<input type='hidden' name='title' value=\"$special\" />\n" .
diff --git a/includes/SpecialLonelypages.php b/includes/SpecialLonelypages.php
index 15022924..8770a9e7 100644
--- a/includes/SpecialLonelypages.php
+++ b/includes/SpecialLonelypages.php
@@ -30,7 +30,7 @@ class LonelyPagesPage extends PageQueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'page', 'pagelinks' ) );
+ list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' );
return
"SELECT 'Lonelypages' AS type,
diff --git a/includes/SpecialMIMEsearch.php b/includes/SpecialMIMEsearch.php
index cbbe6f93..8678118f 100644
--- a/includes/SpecialMIMEsearch.php
+++ b/includes/SpecialMIMEsearch.php
@@ -126,9 +126,12 @@ function wfSpecialMIMEsearch( $par = null ) {
}
function wfSpecialMIMEsearchParse( $str ) {
- wfSuppressWarnings();
+ // searched for an invalid MIME type.
+ if( strpos( $str, '/' ) === false) {
+ return array ('', '');
+ }
+
list( $major, $minor ) = explode( '/', $str, 2 );
- wfRestoreWarnings();
return array(
ltrim( $major, ' ' ),
diff --git a/includes/SpecialMostcategories.php b/includes/SpecialMostcategories.php
index c0d662cc..41bfb0cd 100644
--- a/includes/SpecialMostcategories.php
+++ b/includes/SpecialMostcategories.php
@@ -20,7 +20,7 @@ class MostcategoriesPage extends QueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'categorylinks', 'page' ) );
+ list( $categorylinks, $page) = $dbr->tableNamesN( 'categorylinks', 'page' );
return
"
SELECT
@@ -37,20 +37,11 @@ class MostcategoriesPage extends QueryPage {
}
function formatResult( $skin, $result ) {
- global $wgContLang, $wgLang;
-
- $nt = Title::makeTitle( $result->namespace, $result->title );
- $text = $wgContLang->convert( $nt->getPrefixedText() );
-
- $plink = $skin->makeKnownLink( $nt->getPrefixedText(), $text );
-
- $nl = wfMsgExt( 'ncategories', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->value ) );
-
- $nlink = $skin->makeKnownLink( $wgContLang->specialPage( 'Categories' ),
- $nl, 'article=' . $nt->getPrefixedURL() );
-
- return wfSpecialList($plink, $nlink);
+ global $wgLang;
+ $title = Title::makeTitleSafe( $result->namespace, $result->title );
+ $count = wfMsgExt( 'ncategories', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->value ) );
+ $link = $skin->makeKnownLinkObj( $title, $title->getText() );
+ return wfSpecialList( $link, $count );
}
}
diff --git a/includes/SpecialMostimages.php b/includes/SpecialMostimages.php
index 09f71088..17c07c70 100644
--- a/includes/SpecialMostimages.php
+++ b/includes/SpecialMostimages.php
@@ -20,7 +20,7 @@ class MostimagesPage extends QueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'imagelinks' ) );
+ $imagelinks = $dbr->tableName( 'imagelinks' );
return
"
SELECT
diff --git a/includes/SpecialMostlinked.php b/includes/SpecialMostlinked.php
index 1791228d..2794ecbb 100644
--- a/includes/SpecialMostlinked.php
+++ b/includes/SpecialMostlinked.php
@@ -28,7 +28,7 @@ class MostlinkedPage extends QueryPage {
*/
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'pagelinks', 'page' ) );
+ list( $pagelinks, $page ) = $dbr->tableNamesN( 'pagelinks', 'page' );
return
"SELECT 'Mostlinked' AS type,
pl_namespace AS namespace,
@@ -44,12 +44,12 @@ class MostlinkedPage extends QueryPage {
/**
* Pre-fill the link cache
*/
- function preprocessResults( &$dbr, $res ) {
- if( $dbr->numRows( $res ) > 0 ) {
+ function preprocessResults( &$db, &$res ) {
+ if( $db->numRows( $res ) > 0 ) {
$linkBatch = new LinkBatch();
- while( $row = $dbr->fetchObject( $res ) )
+ while( $row = $db->fetchObject( $res ) )
$linkBatch->addObj( Title::makeTitleSafe( $row->namespace, $row->title ) );
- $dbr->dataSeek( $res, 0 );
+ $db->dataSeek( $res, 0 );
$linkBatch->execute();
}
}
@@ -62,8 +62,8 @@ class MostlinkedPage extends QueryPage {
* @return string
*/
function makeWlhLink( &$title, $caption, &$skin ) {
- $wlh = Title::makeTitle( NS_SPECIAL, 'Whatlinkshere' );
- return $skin->makeKnownLinkObj( $wlh, $caption, 'target=' . $title->getPrefixedUrl() );
+ $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
+ return $skin->makeKnownLinkObj( $wlh, $caption );
}
/**
diff --git a/includes/SpecialMostlinkedcategories.php b/includes/SpecialMostlinkedcategories.php
index 5942b3f4..e1f84847 100644
--- a/includes/SpecialMostlinkedcategories.php
+++ b/includes/SpecialMostlinkedcategories.php
@@ -22,7 +22,7 @@ class MostlinkedCategoriesPage extends QueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'categorylinks', 'page' ) );
+ $categorylinks = $dbr->tableName( 'categorylinks' );
$name = $dbr->addQuotes( $this->getName() );
return
"
diff --git a/includes/SpecialMostrevisions.php b/includes/SpecialMostrevisions.php
index 676923ae..1e3334e9 100644
--- a/includes/SpecialMostrevisions.php
+++ b/includes/SpecialMostrevisions.php
@@ -22,7 +22,7 @@ class MostrevisionsPage extends QueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'revision', 'page' ) );
+ list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' );
return
"
SELECT
diff --git a/includes/SpecialMovepage.php b/includes/SpecialMovepage.php
index e33c1530..e3112c4c 100644
--- a/includes/SpecialMovepage.php
+++ b/includes/SpecialMovepage.php
@@ -9,7 +9,7 @@
* Constructor
*/
function wfSpecialMovepage( $par = null ) {
- global $wgUser, $wgOut, $wgRequest, $action, $wgOnlySysopMayMove;
+ global $wgUser, $wgOut, $wgRequest, $action;
# Check rights
if ( !$wgUser->isAllowed( 'move' ) ) {
@@ -49,6 +49,8 @@ function wfSpecialMovepage( $par = null ) {
class MovePageForm {
var $oldTitle, $newTitle, $reason; # Text input
var $moveTalk, $deleteAndMove;
+
+ private $watch = false;
function MovePageForm( $par ) {
global $wgRequest;
@@ -56,8 +58,13 @@ class MovePageForm {
$this->oldTitle = $wgRequest->getText( 'wpOldTitle', $target );
$this->newTitle = $wgRequest->getText( 'wpNewTitle' );
$this->reason = $wgRequest->getText( 'wpReason' );
- $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', true );
+ if ( $wgRequest->wasPosted() ) {
+ $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', false );
+ } else {
+ $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', true );
+ }
$this->deleteAndMove = $wgRequest->getBool( 'wpDeleteAndMove' ) && $wgRequest->getBool( 'wpConfirm' );
+ $this->watch = $wgRequest->getCheck( 'wpWatch' );
}
function showForm( $err ) {
@@ -126,7 +133,7 @@ class MovePageForm {
$movetalk = wfMsgHtml( 'movetalk' );
$movereason = wfMsgHtml( 'movereason' );
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Movepage' );
+ $titleObj = SpecialPage::getTitleFor( 'Movepage' );
$action = $titleObj->escapeLocalURL( 'action=submit' );
$token = htmlspecialchars( $wgUser->editToken() );
@@ -167,6 +174,14 @@ class MovePageForm {
<td><label for=\"wpMovetalk\">{$movetalk}</label></td>
</tr>" );
}
+
+ $watchChecked = $this->watch || $wgUser->getBoolOption( 'watchmoves' ) || $ot->userIsWatching();
+ $watch = '<tr>';
+ $watch .= '<td align="right">' . Xml::check( 'wpWatch', $watchChecked, array( 'id' => 'watch' ) ) . '</td>';
+ $watch .= '<td>' . Xml::label( wfMsg( 'move-watch' ), 'watch' ) . '</td>';
+ $watch .= '</tr>';
+ $wgOut->addHtml( $watch );
+
$wgOut->addHTML( "
{$confirm}
<tr>
@@ -185,7 +200,6 @@ class MovePageForm {
function doSubmit() {
global $wgOut, $wgUser, $wgRequest;
- $fname = "MovePageForm::doSubmit";
if ( $wgUser->pingLimiter( 'move' ) ) {
$wgOut->rateLimited();
@@ -221,7 +235,7 @@ class MovePageForm {
# Move the talk page if relevant, if it exists, and if we've been told to
$ott = $ot->getTalkPage();
if( $ott->exists() ) {
- if( $wgRequest->getVal( 'wpMovetalk' ) == 1 && !$ot->isTalkPage() && !$nt->isTalkPage() ) {
+ if( $this->moveTalk && !$ot->isTalkPage() && !$nt->isTalkPage() ) {
$ntt = $nt->getTalkPage();
# Attempt the move
@@ -239,9 +253,18 @@ class MovePageForm {
} else {
$talkmoved = 'notalkpage';
}
+
+ # Deal with watches
+ if( $this->watch ) {
+ $wgUser->addWatch( $ot );
+ $wgUser->addWatch( $nt );
+ } else {
+ $wgUser->removeWatch( $ot );
+ $wgUser->removeWatch( $nt );
+ }
# Give back result to user.
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Movepage' );
+ $titleObj = SpecialPage::getTitleFor( 'Movepage' );
$success = $titleObj->getFullURL(
'action=success&oldtitle=' . wfUrlencode( $ot->getPrefixedText() ) .
'&newtitle=' . wfUrlencode( $nt->getPrefixedText() ) .
diff --git a/includes/SpecialNewimages.php b/includes/SpecialNewimages.php
index 95c90e42..062e7e12 100644
--- a/includes/SpecialNewimages.php
+++ b/includes/SpecialNewimages.php
@@ -9,7 +9,7 @@
*
*/
function wfSpecialNewimages( $par, $specialPage ) {
- global $wgUser, $wgOut, $wgLang, $wgContLang, $wgRequest, $wgGroupPermissions;
+ global $wgUser, $wgOut, $wgLang, $wgRequest, $wgGroupPermissions;
$wpIlMatch = $wgRequest->getText( 'wpIlMatch' );
$dbr =& wfGetDB( DB_SLAVE );
@@ -67,9 +67,11 @@ function wfSpecialNewimages( $par, $specialPage ) {
/** Hardcode this for now. */
$limit = 48;
- if ( $parval = intval( $par ) )
- if ( $parval <= $limit && $parval > 0 )
+ if ( $parval = intval( $par ) ) {
+ if ( $parval <= $limit && $parval > 0 ) {
$limit = $parval;
+ }
+ }
$where = array();
$searchpar = '';
@@ -154,7 +156,7 @@ function wfSpecialNewimages( $par, $specialPage ) {
}
$sub = wfMsg( 'ilsubmit' );
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Newimages' );
+ $titleObj = SpecialPage::getTitleFor( 'Newimages' );
$action = $titleObj->escapeLocalURL( $hidebots ? '' : 'hidebots=0' );
if ($shownav) {
$wgOut->addHTML( "<form id=\"imagesearch\" method=\"post\" action=\"" .
@@ -163,7 +165,6 @@ function wfSpecialNewimages( $par, $specialPage ) {
htmlspecialchars( $wpIlMatch ) . "\" /> " .
"<input type='submit' name=\"wpIlSubmit\" value=\"{$sub}\" /></form>" );
}
- $here = $wgContLang->specialPage( 'Newimages' );
/**
* Paging controls...
diff --git a/includes/SpecialNewpages.php b/includes/SpecialNewpages.php
index 3fd0eba2..62007383 100644
--- a/includes/SpecialNewpages.php
+++ b/includes/SpecialNewpages.php
@@ -42,7 +42,7 @@ class NewPagesPage extends QueryPage {
global $wgUser, $wgUseRCPatrol;
$usepatrol = ( $wgUseRCPatrol && $wgUser->isAllowed( 'patrol' ) ) ? 1 : 0;
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'recentchanges', 'page', 'text' ) );
+ list( $recentchanges, $page ) = $dbr->tableNamesN( 'recentchanges', 'page' );
$uwhere = $this->makeUserWhere( $dbr );
@@ -96,8 +96,8 @@ class NewPagesPage extends QueryPage {
$time = $wgLang->timeAndDate( $result->timestamp, true );
$plink = $skin->makeKnownLinkObj( $title, '', $this->patrollable( $result ) ? 'rcid=' . $result->rcid : '' );
$hist = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' );
- $length = wfMsgHtml( 'nbytes', $wgLang->formatNum( htmlspecialchars( $result->length ) ) );
- $ulink = $skin->userLink( $result->user, $result->user_text ) . $skin->userToolLinks( $result->user, $result->user_text );
+ $length = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( htmlspecialchars( $result->length ) ) );
+ $ulink = $skin->userLink( $result->user, $result->user_text ) . ' ' . $skin->userToolLinks( $result->user, $result->user_text );
$comment = $skin->commentBlock( $result->comment );
return "{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment}";
@@ -132,7 +132,7 @@ class NewPagesPage extends QueryPage {
* @return string
*/
function getPageHeader() {
- $self = Title::makeTitle( NS_SPECIAL, $this->getName() );
+ $self = SpecialPage::getTitleFor( $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>';
@@ -172,6 +172,7 @@ function wfSpecialNewpages($par, $specialPage) {
if ( is_numeric( $bit ) )
$limit = $bit;
+ $m = array();
if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) )
$limit = intval($m[1]);
if ( preg_match( '/^offset=(\d+)$/', $bit, $m ) )
diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php
index 294c05ef..86438756 100644
--- a/includes/SpecialPage.php
+++ b/includes/SpecialPage.php
@@ -36,11 +36,15 @@ class SpecialPage
* @access private
*/
/**
- * The name of the class, used in the URL.
+ * The canonical name of this special page
* Also used for the default <h1> heading, @see getDescription()
*/
var $mName;
/**
+ * The local name of this special page
+ */
+ var $mLocalName;
+ /**
* Minimum user level required to access this page, or "" for anyone.
* Also used to categorise the pages in Special:Specialpages
*/
@@ -65,72 +69,83 @@ class SpecialPage
* Whether the special page can be included in an article
*/
var $mIncludable;
+ /**
+ * Query parameters that can be passed through redirects
+ */
+ var $mAllowedRedirectParams = array();
static public $mList = array(
- 'DoubleRedirects' => array( 'SpecialPage', 'DoubleRedirects' ),
- 'BrokenRedirects' => array( 'SpecialPage', 'BrokenRedirects' ),
- 'Disambiguations' => array( 'SpecialPage', 'Disambiguations' ),
-
- 'Userlogin' => array( 'SpecialPage', 'Userlogin' ),
- 'Userlogout' => array( 'UnlistedSpecialPage', 'Userlogout' ),
- 'Preferences' => array( 'SpecialPage', 'Preferences' ),
- 'Watchlist' => array( 'SpecialPage', 'Watchlist' ),
-
- 'Recentchanges' => array( 'IncludableSpecialPage', 'Recentchanges' ),
- 'Upload' => array( 'SpecialPage', 'Upload' ),
- 'Imagelist' => array( 'SpecialPage', 'Imagelist' ),
- 'Newimages' => array( 'IncludableSpecialPage', 'Newimages' ),
- 'Listusers' => array( 'SpecialPage', 'Listusers' ),
- 'Statistics' => array( 'SpecialPage', 'Statistics' ),
- 'Random' => array( 'SpecialPage', 'Randompage' ),
- 'Lonelypages' => array( 'SpecialPage', 'Lonelypages' ),
- 'Uncategorizedpages'=> array( 'SpecialPage', 'Uncategorizedpages' ),
- 'Uncategorizedcategories'=> array( 'SpecialPage', 'Uncategorizedcategories' ),
- 'Uncategorizedimages' => array( 'SpecialPage', 'Uncategorizedimages' ),
- 'Unusedcategories' => array( 'SpecialPage', 'Unusedcategories' ),
- 'Unusedimages' => array( 'SpecialPage', 'Unusedimages' ),
- 'Wantedpages' => array( 'IncludableSpecialPage', 'Wantedpages' ),
- 'Wantedcategories' => array( 'SpecialPage', 'Wantedcategories' ),
- 'Mostlinked' => array( 'SpecialPage', 'Mostlinked' ),
- 'Mostlinkedcategories' => array( 'SpecialPage', 'Mostlinkedcategories' ),
- 'Mostcategories' => array( 'SpecialPage', 'Mostcategories' ),
- 'Mostimages' => array( 'SpecialPage', 'Mostimages' ),
- 'Mostrevisions' => array( 'SpecialPage', 'Mostrevisions' ),
- 'Shortpages' => array( 'SpecialPage', 'Shortpages' ),
- 'Longpages' => array( 'SpecialPage', 'Longpages' ),
- 'Newpages' => array( 'IncludableSpecialPage', 'Newpages' ),
- 'Ancientpages' => array( 'SpecialPage', 'Ancientpages' ),
- 'Deadendpages' => array( 'SpecialPage', 'Deadendpages' ),
- 'Allpages' => array( 'IncludableSpecialPage', 'Allpages' ),
- 'Prefixindex' => array( 'IncludableSpecialPage', 'Prefixindex' ) ,
- 'Ipblocklist' => array( 'SpecialPage', 'Ipblocklist' ),
- 'Specialpages' => array( 'UnlistedSpecialPage', 'Specialpages' ),
- 'Contributions' => array( 'UnlistedSpecialPage', 'Contributions' ),
- 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ),
- 'Whatlinkshere' => array( 'UnlistedSpecialPage', 'Whatlinkshere' ),
- 'Recentchangeslinked' => array( 'UnlistedSpecialPage', 'Recentchangeslinked' ),
- 'Movepage' => array( 'UnlistedSpecialPage', 'Movepage' ),
- 'Blockme' => array( 'UnlistedSpecialPage', 'Blockme' ),
- 'Booksources' => array( 'SpecialPage', 'Booksources' ),
- 'Categories' => array( 'SpecialPage', 'Categories' ),
- 'Export' => array( 'SpecialPage', 'Export' ),
- 'Version' => array( 'SpecialPage', 'Version' ),
- 'Allmessages' => array( 'SpecialPage', 'Allmessages' ),
- 'Log' => array( 'SpecialPage', 'Log' ),
- 'Blockip' => array( 'SpecialPage', 'Blockip', 'block' ),
- 'Undelete' => array( 'SpecialPage', 'Undelete', 'deletedhistory' ),
- "Import" => array( 'SpecialPage', "Import", 'import' ),
- 'Lockdb' => array( 'SpecialPage', 'Lockdb', 'siteadmin' ),
- 'Unlockdb' => array( 'SpecialPage', 'Unlockdb', 'siteadmin' ),
- 'Userrights' => array( 'SpecialPage', 'Userrights', 'userrights' ),
- 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ),
- 'Unwatchedpages' => array( 'SpecialPage', 'Unwatchedpages', 'unwatchedpages' ),
- 'Listredirects' => array( 'SpecialPage', 'Listredirects' ),
- 'Revisiondelete' => array( 'SpecialPage', 'Revisiondelete', 'deleterevision' ),
- 'Unusedtemplates' => array( 'SpecialPage', 'Unusedtemplates' ),
- 'Randomredirect' => array( 'SpecialPage', 'Randomredirect' ),
+ 'DoubleRedirects' => array( 'SpecialPage', 'DoubleRedirects' ),
+ 'BrokenRedirects' => array( 'SpecialPage', 'BrokenRedirects' ),
+ 'Disambiguations' => array( 'SpecialPage', 'Disambiguations' ),
+
+ 'Userlogin' => array( 'SpecialPage', 'Userlogin' ),
+ 'Userlogout' => array( 'UnlistedSpecialPage', 'Userlogout' ),
+ 'Preferences' => array( 'SpecialPage', 'Preferences' ),
+ 'Watchlist' => array( 'SpecialPage', 'Watchlist' ),
+
+ 'Recentchanges' => array( 'IncludableSpecialPage', 'Recentchanges' ),
+ 'Upload' => array( 'SpecialPage', 'Upload' ),
+ 'Imagelist' => array( 'SpecialPage', 'Imagelist' ),
+ 'Newimages' => array( 'IncludableSpecialPage', 'Newimages' ),
+ 'Listusers' => array( 'SpecialPage', 'Listusers' ),
+ 'Statistics' => array( 'SpecialPage', 'Statistics' ),
+ 'Randompage' => array( 'SpecialPage', 'Randompage' ),
+ 'Lonelypages' => array( 'SpecialPage', 'Lonelypages' ),
+ 'Uncategorizedpages' => array( 'SpecialPage', 'Uncategorizedpages' ),
+ 'Uncategorizedcategories' => array( 'SpecialPage', 'Uncategorizedcategories' ),
+ 'Uncategorizedimages' => array( 'SpecialPage', 'Uncategorizedimages' ),
+ 'Unusedcategories' => array( 'SpecialPage', 'Unusedcategories' ),
+ 'Unusedimages' => array( 'SpecialPage', 'Unusedimages' ),
+ 'Wantedpages' => array( 'IncludableSpecialPage', 'Wantedpages' ),
+ 'Wantedcategories' => array( 'SpecialPage', 'Wantedcategories' ),
+ 'Mostlinked' => array( 'SpecialPage', 'Mostlinked' ),
+ 'Mostlinkedcategories' => array( 'SpecialPage', 'Mostlinkedcategories' ),
+ 'Mostcategories' => array( 'SpecialPage', 'Mostcategories' ),
+ 'Mostimages' => array( 'SpecialPage', 'Mostimages' ),
+ 'Mostrevisions' => array( 'SpecialPage', 'Mostrevisions' ),
+ 'Shortpages' => array( 'SpecialPage', 'Shortpages' ),
+ 'Longpages' => array( 'SpecialPage', 'Longpages' ),
+ 'Newpages' => array( 'IncludableSpecialPage', 'Newpages' ),
+ 'Ancientpages' => array( 'SpecialPage', 'Ancientpages' ),
+ 'Deadendpages' => array( 'SpecialPage', 'Deadendpages' ),
+ 'Allpages' => array( 'IncludableSpecialPage', 'Allpages' ),
+ 'Prefixindex' => array( 'IncludableSpecialPage', 'Prefixindex' ) ,
+ 'Ipblocklist' => array( 'SpecialPage', 'Ipblocklist' ),
+ 'Specialpages' => array( 'UnlistedSpecialPage', 'Specialpages' ),
+ 'Contributions' => array( 'UnlistedSpecialPage', 'Contributions' ),
+ 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ),
+ 'Whatlinkshere' => array( 'UnlistedSpecialPage', 'Whatlinkshere' ),
+ 'Recentchangeslinked' => array( 'UnlistedSpecialPage', 'Recentchangeslinked' ),
+ 'Movepage' => array( 'UnlistedSpecialPage', 'Movepage' ),
+ 'Blockme' => array( 'UnlistedSpecialPage', 'Blockme' ),
+ 'Resetpass' => array( 'UnlistedSpecialPage', 'Resetpass' ),
+ 'Booksources' => 'SpecialBookSources',
+ 'Categories' => array( 'SpecialPage', 'Categories' ),
+ 'Export' => array( 'SpecialPage', 'Export' ),
+ 'Version' => array( 'SpecialPage', 'Version' ),
+ 'Allmessages' => array( 'SpecialPage', 'Allmessages' ),
+ 'Log' => array( 'SpecialPage', 'Log' ),
+ 'Blockip' => array( 'SpecialPage', 'Blockip', 'block' ),
+ 'Undelete' => array( 'SpecialPage', 'Undelete', 'deletedhistory' ),
+ 'Import' => array( 'SpecialPage', "Import", 'import' ),
+ 'Lockdb' => array( 'SpecialPage', 'Lockdb', 'siteadmin' ),
+ 'Unlockdb' => array( 'SpecialPage', 'Unlockdb', 'siteadmin' ),
+ 'Userrights' => array( 'SpecialPage', 'Userrights', 'userrights' ),
+ 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ),
+ 'Unwatchedpages' => array( 'SpecialPage', 'Unwatchedpages', 'unwatchedpages' ),
+ 'Listredirects' => array( 'SpecialPage', 'Listredirects' ),
+ 'Revisiondelete' => array( 'SpecialPage', 'Revisiondelete', 'deleterevision' ),
+ 'Unusedtemplates' => array( 'SpecialPage', 'Unusedtemplates' ),
+ 'Randomredirect' => array( 'SpecialPage', 'Randomredirect' ),
+
+ 'Mypage' => array( 'SpecialMypage' ),
+ 'Mytalk' => array( 'SpecialMytalk' ),
+ 'Mycontributions' => array( 'SpecialMycontributions' ),
+ 'Listadmins' => array( 'SpecialRedirectToSpecial', 'Listadmins', 'Listusers', 'sysop' ),
);
+ static public $mAliases;
static public $mListInitialised = false;
/**#@-*/
@@ -148,6 +163,9 @@ class SpecialPage
}
wfProfileIn( __METHOD__ );
+ # Better to set this now, to avoid infinite recursion in carelessly written hooks
+ self::$mListInitialised = true;
+
if( !$wgDisableCounters ) {
self::$mList['Popularpages'] = array( 'SpecialPage', 'Popularpages' );
}
@@ -163,15 +181,65 @@ class SpecialPage
# Add extension special pages
self::$mList = array_merge( self::$mList, $wgSpecialPages );
- # Better to set this now, to avoid infinite recursion in carelessly written hooks
- self::$mListInitialised = true;
-
# Run hooks
# This hook can be used to remove undesired built-in special pages
wfRunHooks( 'SpecialPage_initList', array( &self::$mList ) );
wfProfileOut( __METHOD__ );
}
+ static function initAliasList() {
+ if ( !is_null( self::$mAliases ) ) {
+ return;
+ }
+
+ global $wgContLang;
+ $aliases = $wgContLang->getSpecialPageAliases();
+ $missingPages = self::$mList;
+ self::$mAliases = array();
+ foreach ( $aliases as $realName => $aliasList ) {
+ foreach ( $aliasList as $alias ) {
+ self::$mAliases[$wgContLang->caseFold( $alias )] = $realName;
+ }
+ unset( $missingPages[$realName] );
+ }
+ foreach ( $missingPages as $name => $stuff ) {
+ self::$mAliases[$wgContLang->caseFold( $name )] = $name;
+ }
+ }
+
+ /**
+ * Given a special page alias, return the special page name.
+ * Returns false if there is no such alias.
+ */
+ static function resolveAlias( $alias ) {
+ global $wgContLang;
+
+ if ( !self::$mListInitialised ) self::initList();
+ if ( is_null( self::$mAliases ) ) self::initAliasList();
+ $caseFoldedAlias = $wgContLang->caseFold( $alias );
+ if ( isset( self::$mAliases[$caseFoldedAlias] ) ) {
+ return self::$mAliases[$caseFoldedAlias];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Given a special page name with a possible subpage, return an array
+ * where the first element is the special page name and the second is the
+ * subpage.
+ */
+ static function resolveAliasWithSubpage( $alias ) {
+ $bits = explode( '/', $alias, 2 );
+ $name = self::resolveAlias( $bits[0] );
+ if( !isset( $bits[1] ) ) { // bug 2087
+ $par = NULL;
+ } else {
+ $par = $bits[1];
+ }
+ return array( $name, $par );
+ }
+
/**
* Add a page to the list of valid special pages. This used to be the preferred
* method for adding special pages in extensions. It's now suggested that you add
@@ -228,55 +296,18 @@ class SpecialPage
}
}
-
- /**
- * @static
- * @param string $name
- * @return mixed Title object if the redirect exists, otherwise NULL
- */
- static function getRedirect( $name ) {
- global $wgUser;
-
- $redirects = array(
- 'Mypage' => Title::makeTitle( NS_USER, $wgUser->getName() ),
- 'Mytalk' => Title::makeTitle( NS_USER_TALK, $wgUser->getName() ),
- 'Mycontributions' => Title::makeTitle( NS_SPECIAL, 'Contributions/' . $wgUser->getName() ),
- 'Listadmins' => Title::makeTitle( NS_SPECIAL, 'Listusers/sysop' ), # @bug 2832
- 'Logs' => Title::makeTitle( NS_SPECIAL, 'Log' ),
- 'Randompage' => Title::makeTitle( NS_SPECIAL, 'Random' ),
- 'Userlist' => Title::makeTitle( NS_SPECIAL, 'Listusers' )
- );
- wfRunHooks( 'SpecialPageGetRedirect', array( &$redirects ) );
-
- return isset( $redirects[$name] ) ? $redirects[$name] : null;
- }
-
/**
- * Return part of the request string for a special redirect page
- * This allows passing, e.g. action=history to Special:Mypage, etc.
- *
- * @param $name Name of the redirect page
- * @return string
+ * Get a special page with a given localised name, or NULL if there
+ * is no such special page.
*/
- function getRedirectParams( $name ) {
- global $wgRequest;
-
- $args = array();
- switch( $name ) {
- case 'Mypage':
- case 'Mytalk':
- case 'Randompage':
- $args = array( 'action' );
- }
-
- $params = array();
- foreach( $args as $arg ) {
- if( $val = $wgRequest->getVal( $arg, false ) )
- $params[] = $arg . '=' . $val;
+ static function getPageByAlias( $alias ) {
+ $realName = self::resolveAlias( $alias );
+ if ( $realName ) {
+ return self::getPage( $realName );
+ } else {
+ return NULL;
}
-
- return count( $params ) ? implode( '&', $params ) : false;
- }
+ }
/**
* Return categorised listable special pages for all users
@@ -333,67 +364,74 @@ class SpecialPage
* @param $including output is being captured for use in {{special:whatever}}
*/
static function executePath( &$title, $including = false ) {
- global $wgOut, $wgTitle;
- $fname = 'SpecialPage::executePath';
- wfProfileIn( $fname );
+ global $wgOut, $wgTitle, $wgRequest;
+ wfProfileIn( __METHOD__ );
- $bits = split( "/", $title->getDBkey(), 2 );
+ # FIXME: redirects broken due to this call
+ $bits = explode( '/', $title->getDBkey(), 2 );
$name = $bits[0];
if( !isset( $bits[1] ) ) { // bug 2087
$par = NULL;
} else {
$par = $bits[1];
}
-
- $page = SpecialPage::getPage( $name );
- if ( is_null( $page ) ) {
- if ( $including ) {
- wfProfileOut( $fname );
- return false;
- } else {
- $redir = SpecialPage::getRedirect( $name );
- if ( isset( $redir ) ) {
- if( $par )
- $redir = Title::makeTitle( $redir->getNamespace(), $redir->getText() . '/' . $par );
- $params = SpecialPage::getRedirectParams( $name );
- if( $params ) {
- $url = $redir->getFullUrl( $params );
- } else {
- $url = $redir->getFullUrl();
- }
- $wgOut->redirect( $url );
- $retVal = $redir;
- $wgOut->redirect( $url );
- $retVal = $redir;
- } else {
- $wgOut->setArticleRelated( false );
- $wgOut->setRobotpolicy( 'noindex,nofollow' );
- $wgOut->setStatusCode( 404 );
- $wgOut->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
- $retVal = false;
- }
+ $page = SpecialPage::getPageByAlias( $name );
+
+ # Nonexistent?
+ if ( !$page ) {
+ if ( !$including ) {
+ $wgOut->setArticleRelated( false );
+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
+ $wgOut->setStatusCode( 404 );
+ $wgOut->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
}
- } else {
- if ( $including && !$page->includable() ) {
- wfProfileOut( $fname );
- return false;
- } elseif ( !$including ) {
- if($par !== NULL) {
- $wgTitle = Title::makeTitle( NS_SPECIAL, $name );
- } else {
- $wgTitle = $title;
- }
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ # Check for redirect
+ if ( !$including ) {
+ $redirect = $page->getRedirect( $par );
+ if ( $redirect ) {
+ $query = $page->getRedirectQuery();
+ $url = $redirect->getFullUrl( $query );
+ $wgOut->redirect( $url );
+ wfProfileOut( __METHOD__ );
+ return $redirect;
}
- $page->including( $including );
+ }
+
+ # Redirect to canonical alias for GET commands
+ # Not for POST, we'd lose the post data, so it's best to just distribute
+ # the request. Such POST requests are possible for old extensions that
+ # generate self-links without being aware that their default name has
+ # changed.
+ if ( !$including && $name != $page->getLocalName() && !$wgRequest->wasPosted() ) {
+ $query = $_GET;
+ unset( $query['title'] );
+ $query = wfArrayToCGI( $query );
+ $title = $page->getTitle( $par );
+ $url = $title->getFullUrl( $query );
+ $wgOut->redirect( $url );
+ wfProfileOut( __METHOD__ );
+ return $redirect;
+ }
- $profName = 'Special:' . $page->getName();
- wfProfileIn( $profName );
- $page->execute( $par );
- wfProfileOut( $profName );
- $retVal = true;
+ if ( $including && !$page->includable() ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ } elseif ( !$including ) {
+ $wgTitle = $page->getTitle();
}
- wfProfileOut( $fname );
- return $retVal;
+ $page->including( $including );
+
+ // Execute special page
+ $profName = 'Special:' . $page->getName();
+ wfProfileIn( $profName );
+ $page->execute( $par );
+ wfProfileOut( $profName );
+ wfProfileOut( __METHOD__ );
+ return true;
}
/**
@@ -419,6 +457,58 @@ class SpecialPage
}
/**
+ * Get the local name for a specified canonical name
+ */
+ static function getLocalNameFor( $name, $subpage = false ) {
+ global $wgContLang;
+ $aliases = $wgContLang->getSpecialPageAliases();
+ if ( isset( $aliases[$name][0] ) ) {
+ $name = $aliases[$name][0];
+ }
+ if ( $subpage !== false && !is_null( $subpage ) ) {
+ $name = "$name/$subpage";
+ }
+ return $name;
+ }
+
+ /**
+ * Get a localised Title object for a specified special page name
+ */
+ static function getTitleFor( $name, $subpage = false ) {
+ $name = self::getLocalNameFor( $name, $subpage );
+ if ( $name ) {
+ return Title::makeTitle( NS_SPECIAL, $name );
+ } else {
+ throw new MWException( "Invalid special page name \"$name\"" );
+ }
+ }
+
+ /**
+ * Get a localised Title object for a page name with a possibly unvalidated subpage
+ */
+ static function getSafeTitleFor( $name, $subpage = false ) {
+ $name = self::getLocalNameFor( $name, $subpage );
+ if ( $name ) {
+ return Title::makeTitleSafe( NS_SPECIAL, $name );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get a title for a given alias
+ * @return Title or null if there is no such alias
+ */
+ static function getTitleForAlias( $alias ) {
+ $name = self::resolveAlias( $alias );
+ if ( $name ) {
+ return self::getTitleFor( $name );
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Default constructor for special pages
* Derivative classes should call this from their constructor
* Note that if the user does not have the required level, an error message will
@@ -475,6 +565,16 @@ class SpecialPage
/**#@-*/
/**
+ * Get the localised name of the special page
+ */
+ function getLocalName() {
+ if ( !isset( $this->mLocalName ) ) {
+ $this->mLocalName = self::getLocalNameFor( $this->mName );
+ }
+ return $this->mLocalName;
+ }
+
+ /**
* Checks if the given user (identified by an object) can execute this
* special page (as defined by $mRestriction)
*/
@@ -503,6 +603,8 @@ class SpecialPage
/**
* Default execute method
* Checks user permissions, calls the function given in mFunction
+ *
+ * This may be overridden by subclasses.
*/
function execute( $par ) {
global $wgUser;
@@ -515,6 +617,7 @@ class SpecialPage
if(!function_exists($func) and $this->mFile) {
require_once( $this->mFile );
}
+ # FIXME: these hooks are broken for extensions and anything else that subclasses SpecialPage.
if ( wfRunHooks( 'SpecialPageExecuteBeforeHeader', array( &$this, &$par, &$func ) ) )
$this->outputHeader();
if ( ! wfRunHooks( 'SpecialPageExecuteBeforePage', array( &$this, &$par, &$func ) ) )
@@ -549,8 +652,8 @@ class SpecialPage
/**
* Get a self-referential title object
*/
- function getTitle() {
- return Title::makeTitle( NS_SPECIAL, $this->mName );
+ function getTitle( $subpage = false) {
+ return self::getTitleFor( $this->mName, $subpage );
}
/**
@@ -560,6 +663,30 @@ class SpecialPage
return wfSetVar( $this->mListed, $listed );
}
+ /**
+ * If the special page is a redirect, then get the Title object it redirects to.
+ * False otherwise.
+ */
+ function getRedirect( $subpage ) {
+ return false;
+ }
+
+ /**
+ * Return part of the request string for a special redirect page
+ * This allows passing, e.g. action=history to Special:Mypage, etc.
+ *
+ * @return string
+ */
+ function getRedirectQuery() {
+ global $wgRequest;
+ $params = array();
+ foreach( $this->mAllowedRedirectParams as $arg ) {
+ if( $val = $wgRequest->getVal( $arg, false ) )
+ $params[] = $arg . '=' . $val;
+ }
+
+ return count( $params ) ? implode( '&', $params ) : false;
+ }
}
/**
@@ -583,4 +710,67 @@ class IncludableSpecialPage extends SpecialPage
SpecialPage::SpecialPage( $name, $restriction, $listed, $function, $file, true );
}
}
+
+class SpecialRedirectToSpecial extends UnlistedSpecialPage {
+ var $redirName, $redirSubpage;
+
+ function __construct( $name, $redirName, $redirSubpage = false, $redirectParams = array() ) {
+ parent::__construct( $name );
+ $this->redirName = $redirName;
+ $this->redirSubpage = $redirSubpage;
+ $this->mAllowedRedirectParams = $redirectParams;
+ }
+
+ function getRedirect( $subpage ) {
+ if ( $this->redirSubpage === false ) {
+ return SpecialPage::getTitleFor( $this->redirName, $subpage );
+ } else {
+ return SpecialPage::getTitleFor( $this->redirName, $this->redirSubpage );
+ }
+ }
+}
+
+class SpecialMypage extends UnlistedSpecialPage {
+ function __construct() {
+ parent::__construct( 'Mypage' );
+ $this->mAllowedRedirectParams = array( 'action' );
+ }
+
+ function getRedirect( $subpage ) {
+ global $wgUser;
+ if ( strval( $subpage ) !== '' ) {
+ return Title::makeTitle( NS_USER, $wgUser->getName() . '/' . $subpage );
+ } else {
+ return Title::makeTitle( NS_USER, $wgUser->getName() );
+ }
+ }
+}
+
+class SpecialMytalk extends UnlistedSpecialPage {
+ function __construct() {
+ parent::__construct( 'Mytalk' );
+ $this->mAllowedRedirectParams = array( 'action' );
+ }
+
+ function getRedirect( $subpage ) {
+ global $wgUser;
+ if ( strval( $subpage ) !== '' ) {
+ return Title::makeTitle( NS_USER_TALK, $wgUser->getName() . '/' . $subpage );
+ } else {
+ return Title::makeTitle( NS_USER_TALK, $wgUser->getName() );
+ }
+ }
+}
+
+class SpecialMycontributions extends UnlistedSpecialPage {
+ function __construct() {
+ parent::__construct( 'Mycontributions' );
+ }
+
+ function getRedirect( $subpage ) {
+ global $wgUser;
+ return SpecialPage::getTitleFor( 'Contributions', $wgUser->getName() );
+ }
+}
+
?>
diff --git a/includes/SpecialPreferences.php b/includes/SpecialPreferences.php
index 5eadf3d6..643932c4 100644
--- a/includes/SpecialPreferences.php
+++ b/includes/SpecialPreferences.php
@@ -34,7 +34,7 @@ class PreferencesForm {
* Load some values
*/
function PreferencesForm( &$request ) {
- global $wgLang, $wgContLang, $wgUser, $wgAllowRealName;
+ global $wgContLang, $wgUser, $wgAllowRealName;
$this->mQuickbar = $request->getVal( 'wpQuickbar' );
$this->mOldpass = $request->getVal( 'wpOldpass' );
@@ -206,7 +206,7 @@ class PreferencesForm {
function savePreferences() {
global $wgUser, $wgOut, $wgParser;
global $wgEnableUserEmail, $wgEnableEmail;
- global $wgEmailAuthentication, $wgMinimalPasswordLength;
+ global $wgEmailAuthentication;
global $wgAuth;
@@ -216,22 +216,18 @@ class PreferencesForm {
return;
}
- if ( strlen( $this->mNewpass ) < $wgMinimalPasswordLength ) {
- $this->mainPrefsForm( 'error', wfMsg( 'passwordtooshort', $wgMinimalPasswordLength ) );
- return;
- }
-
if (!$wgUser->checkPassword( $this->mOldpass )) {
$this->mainPrefsForm( 'error', wfMsg( 'wrongpassword' ) );
return;
}
- if (!$wgAuth->setPassword( $wgUser, $this->mNewpass )) {
- $this->mainPrefsForm( 'error', wfMsg( 'externaldberror' ) );
+
+ try {
+ $wgUser->setPassword( $this->mNewpass );
+ $this->mNewpass = $this->mOldpass = $this->mRetypePass = '';
+ } catch( PasswordError $e ) {
+ $this->mainPrefsForm( 'error', $e->getMessage() );
return;
}
- $wgUser->setPassword( $this->mNewpass );
- $this->mNewpass = $this->mOldpass = $this->mRetypePass = '';
-
}
$wgUser->setRealName( $this->mRealName );
@@ -328,13 +324,12 @@ class PreferencesForm {
}
if( $needRedirect && $error === false ) {
- $title =& Title::makeTitle( NS_SPECIAL, "Preferences" );
+ $title =& SpecialPage::getTitleFor( "Preferences" );
$wgOut->redirect($title->getFullURL('success'));
return;
}
$wgOut->setParserOptions( ParserOptions::newFromUser( $wgUser ) );
- $po = ParserOptions::newFromUser( $wgUser );
$this->mainPrefsForm( $error === false ? 'success' : 'error', $error);
}
@@ -342,18 +337,16 @@ class PreferencesForm {
* @access private
*/
function resetPrefs() {
- global $wgUser, $wgLang, $wgContLang, $wgAllowRealName;
+ global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName;
$this->mOldpass = $this->mNewpass = $this->mRetypePass = '';
$this->mUserEmail = $wgUser->getEmail();
$this->mUserEmailAuthenticationtimestamp = $wgUser->getEmailAuthenticationtimestamp();
$this->mRealName = ($wgAllowRealName) ? $wgUser->getRealName() : '';
- $this->mUserLanguage = $wgUser->getOption( 'language' );
- if( empty( $this->mUserLanguage ) ) {
- # Quick hack for conversions, where this value is blank
- global $wgContLanguageCode;
- $this->mUserLanguage = $wgContLanguageCode;
- }
+
+ # language value might be blank, default to content language
+ $this->mUserLanguage = $wgUser->getOption( 'language', $wgContLanguageCode );
+
$this->mUserVariant = $wgUser->getOption( 'variant');
$this->mEmailFlag = $wgUser->getOption( 'disablemail' ) == 1 ? 1 : 0;
$this->mNick = $wgUser->getOption( 'nickname' );
@@ -378,7 +371,6 @@ class PreferencesForm {
$togs = User::getToggles();
foreach ( $togs as $tname ) {
- $ttext = wfMsg('tog-'.$tname);
$this->mToggles[$tname] = $wgUser->getOption( $tname );
}
@@ -478,7 +470,7 @@ class PreferencesForm {
$dateopts = $wgLang->getDatePreferences();
$togs = User::getToggles();
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Preferences' );
+ $titleObj = SpecialPage::getTitleFor( 'Preferences' );
$action = $titleObj->escapeLocalURL();
# Pre-expire some toggles so they won't show if disabled
@@ -488,6 +480,7 @@ class PreferencesForm {
$this->mUsedToggles[ 'enotifusertalkpages' ] = true;
$this->mUsedToggles[ 'enotifminoredits' ] = true;
$this->mUsedToggles[ 'enotifrevealaddr' ] = true;
+ $this->mUsedToggles[ 'ccmeonemails' ] = true;
$this->mUsedToggles[ 'uselivepreview' ] = true;
# Enotif
@@ -508,7 +501,7 @@ class PreferencesForm {
$disableEmailPrefs = true;
$skin = $wgUser->getSkin();
$emailauthenticated = wfMsg('emailnotauthenticated').'<br />' .
- $skin->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Confirmemail' ),
+ $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Confirmemail' ),
wfMsg( 'emailconfirmlink' ) );
}
} else {
@@ -526,8 +519,6 @@ class PreferencesForm {
$enotifusertalkpages = ($wgEnotifUserTalk) ? $this->getToggle( 'enotifusertalkpages', false, $disableEmailPrefs ) : '';
$enotifminoredits = ($wgEnotifWatchlist && $wgEnotifMinorEdits) ? $this->getToggle( 'enotifminoredits', false, $disableEmailPrefs ) : '';
$enotifrevealaddr = (($wgEnotifWatchlist || $wgEnotifUserTalk) && $wgEnotifRevealEditorAddress) ? $this->getToggle( 'enotifrevealaddr', false, $disableEmailPrefs ) : '';
- $prefs_help_email_enotif = ( $wgEnotifWatchlist || $wgEnotifUserTalk) ? ' ' . wfMsg('prefs-help-email-enotif') : '';
- $prefs_help_realname = '';
# </FIXME>
@@ -539,19 +530,19 @@ class PreferencesForm {
$wgOut->addHTML( "<fieldset>\n<legend>" . wfMsg('prefs-personal') . "</legend>\n<table>\n");
- $wgOut->addHTML(
+ $userInformationHtml =
$this->addRow(
wfMsg( 'username'),
$wgUser->getName()
- )
- );
-
- $wgOut->addHTML(
+ ) .
$this->addRow(
wfMsg( 'uid' ),
$wgUser->getID()
- )
- );
+ );
+
+ if( wfRunHooks( 'PreferencesUserInformationPanel', array( $this, &$userInformationHtml ) ) ) {
+ $wgOut->addHtml( $userInformationHtml );
+ }
if ($wgAllowRealName) {
@@ -600,7 +591,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( true );
+ $languages = Language::getLanguageNames( true );
if( !array_key_exists( $wgContLanguageCode, $languages ) ) {
$languages[$wgContLanguageCode] = $wgContLanguageCode;
}
@@ -612,16 +603,15 @@ class PreferencesForm {
* with an Afrikaans interface since it's first in the list.
*/
$selectedLang = isset( $languages[$this->mUserLanguage] ) ? $this->mUserLanguage : $wgContLanguageCode;
- $selbox = null;
- foreach($languages as $code => $name) {
- global $IP;
- $sel = ($code == $selectedLang)? ' selected="selected"' : '';
- $selbox .= "<option value=\"$code\"$sel>$code - $name</option>\n";
+ $options = "\n";
+ foreach( $languages as $code => $name ) {
+ $selected = ($code == $selectedLang);
+ $options .= Xml::option( "$code - $name", $code, $selected ) . "\n";
}
$wgOut->addHTML(
$this->addRow(
'<label for="wpUserLanguage">' . wfMsg('yourlanguage') . '</label>',
- "<select name='wpUserLanguage' id='wpUserLanguage'>$selbox</select>"
+ "<select name='wpUserLanguage' id='wpUserLanguage'>$options</select>"
)
);
@@ -638,15 +628,16 @@ class PreferencesForm {
}
}
- $selbox = null;
- foreach($variantArray as $code => $name) {
- $sel = $code == $this->mUserVariant ? 'selected="selected"' : '';
- $selbox .= "<option value=\"$code\" $sel>$code - $name</option>";
+ $options = "\n";
+ foreach( $variantArray as $code => $name ) {
+ $selected = ($code == $this->mUserVariant);
+ $options .= Xml::option( "$code - $name", $code, $selected ) . "\n";
}
if(count($variantArray) > 1) {
$wgOut->addHtml(
- $this->addRow( wfMsg( 'yourvariant' ), "<select name='wpUserVariant'>$selbox</select>" )
+ $this->addRow( wfMsg( 'yourvariant' ),
+ "<select name='wpUserVariant'>$options</select>" )
);
}
}
@@ -692,6 +683,7 @@ class PreferencesForm {
$wgOut->addHTML(
"<div><input type='checkbox' $emfc $disabled value='1' name='wpEmailFlag' id='wpEmailFlag' /> <label for='wpEmailFlag'>$emf</label></div>" );
}
+ $wgOut->addHtml( $this->getToggle( 'ccmeonemails' ) );
$wgOut->addHTML( '</fieldset>' );
}
@@ -732,12 +724,19 @@ class PreferencesForm {
# Only show members of Skin::getSkinNames() rather than
# $skinNames (skins is all skin names from Language.php)
$validSkinNames = Skin::getSkinNames();
- foreach ($validSkinNames as $skinkey => $skinname ) {
+ # Sort by UI skin name. First though need to update validSkinNames as sometimes
+ # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI).
+ foreach ($validSkinNames as $skinkey => & $skinname ) {
+ if ( isset( $skinNames[$skinkey] ) ) {
+ $skinname = $skinNames[$skinkey];
+ }
+ }
+ asort($validSkinNames);
+ foreach ($validSkinNames as $skinkey => $sn ) {
if ( in_array( $skinkey, $wgSkipSkins ) ) {
continue;
}
$checked = $skinkey == $this->mSkin ? ' checked="checked"' : '';
- $sn = isset( $skinNames[$skinkey] ) ? $skinNames[$skinkey] : $skinname;
$mplink = htmlspecialchars($mptitle->getLocalURL("useskin=$skinkey"));
$previewlink = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
@@ -761,24 +760,41 @@ class PreferencesForm {
# Files
#
- $wgOut->addHTML("<fieldset>
- <legend>" . wfMsg( 'files' ) . "</legend>
- <div><label for='wpImageSize'>" . wfMsg('imagemaxsize') . "</label> <select id='wpImageSize' name='wpImageSize'>");
+ $wgOut->addHTML(
+ "<fieldset>\n" . Xml::element( 'legend', null, wfMsg( 'files' ) ) . "\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";
+ $selected = ($index == $this->mImageSize);
+ $imageLimitOptions .= Xml::option( "{$limits[0]}×{$limits[1]}" .
+ wfMsg('unit-pixel'), $index, $selected );
}
+ $imageSizeId = 'wpImageSize';
+ $wgOut->addHTML(
+ "<div>" . Xml::label( wfMsg('imagemaxsize'), $imageSizeId ) . " " .
+ Xml::openElement( 'select', array( 'name' => $imageSizeId, 'id' => $imageSizeId ) ) .
+ $imageLimitOptions .
+ Xml::closeElement( 'select' ) . "</div>\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";
+ $selected = ($index == $this->mThumbSize);
+ $imageThumbOptions .= Xml::option($size . wfMsg('unit-pixel'), $index,
+ $selected);
}
- $wgOut->addHTML( "{$imageThumbOptions}</select></div></fieldset>\n\n");
+
+ $thumbSizeId = 'wpThumbSize';
+ $wgOut->addHTML(
+ "<div>" . Xml::label( wfMsg('thumbsize'), $thumbSizeId ) . " " .
+ Xml::openElement( 'select', array( 'name' => $thumbSizeId, 'id' => $thumbSizeId ) ) .
+ $imageThumbOptions .
+ Xml::closeElement( 'select' ) . "</div>\n"
+ );
+
+ $wgOut->addHTML( "</fieldset>\n\n" );
# Date format
#
@@ -837,17 +853,13 @@ class PreferencesForm {
'showtoolbar',
'previewonfirst',
'previewontop',
- 'watchcreations',
- 'watchdefault',
'minordefault',
'externaleditor',
'externaldiff',
$wgLivePreview ? 'uselivepreview' : false,
- $wgUser->isAllowed( 'patrol' ) && $wgUseRCPatrol ? 'autopatrol' : false,
'forceeditsummary',
) ) . '</fieldset>'
);
- $this->mUsedToggles['autopatrol'] = true; # Don't show this up for users who can't; the handler below is dumb and doesn't know it
$wgOut->addHTML( '<fieldset><legend>' . htmlspecialchars(wfMsg('prefs-rc')) . '</legend>' .
wfInputLabel( wfMsg( 'recentchangescount' ),
@@ -860,16 +872,29 @@ class PreferencesForm {
);
# Watchlist
- $wgOut->addHTML( '<fieldset><legend>' . wfMsgHtml( 'prefs-watchlist' ) . '</legend>' );
-
- $wgOut->addHTML( wfInputLabel( wfMsg( 'prefs-watchlist-days' ),
- 'wpWatchlistDays', 'wpWatchlistDays', 3, $this->mWatchlistDays ) );
- $wgOut->addHTML( '<br /><br />' ); # Spacing
- $wgOut->addHTML( $this->getToggles( array( 'watchlisthideown', 'watchlisthidebots', 'extendwatchlist' ) ) );
- $wgOut->addHTML( wfInputLabel( wfMsg( 'prefs-watchlist-edits' ),
- 'wpWatchlistEdits', 'wpWatchlistEdits', 3, $this->mWatchlistEdits ) );
-
- $wgOut->addHTML( '</fieldset>' );
+ $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'prefs-watchlist' ) . '</legend>' );
+
+ $wgOut->addHtml( wfInputLabel( wfMsg( 'prefs-watchlist-days' ), 'wpWatchlistDays', 'wpWatchlistDays', 3, $this->mWatchlistDays ) );
+ $wgOut->addHtml( '<br /><br />' );
+
+ $wgOut->addHtml( $this->getToggle( 'extendwatchlist' ) );
+ $wgOut->addHtml( wfInputLabel( wfMsg( 'prefs-watchlist-edits' ), 'wpWatchlistEdits', 'wpWatchlistEdits', 3, $this->mWatchlistEdits ) );
+ $wgOut->addHtml( '<br /><br />' );
+
+ $wgOut->addHtml( $this->getToggles( array( 'watchlisthideown', 'watchlisthidebots', 'watchlisthideminor' ) ) );
+
+ if( $wgUser->isAllowed( 'createpage' ) || $wgUser->isAllowed( 'createtalk' ) )
+ $wgOut->addHtml( $this->getToggle( 'watchcreations' ) );
+ foreach( array( 'edit' => 'watchdefault', 'move' => 'watchmoves', 'delete' => 'watchdeletion' ) as $action => $toggle ) {
+ if( $wgUser->isAllowed( $action ) )
+ $wgOut->addHtml( $this->getToggle( $toggle ) );
+ }
+ $this->mUsedToggles['watchcreations'] = true;
+ $this->mUsedToggles['watchdefault'] = true;
+ $this->mUsedToggles['watchmoves'] = true;
+ $this->mUsedToggles['watchdeletion'] = true;
+
+ $wgOut->addHtml( '</fieldset>' );
# Search
$wgOut->addHTML( '<fieldset><legend>' . wfMsg( 'searchresultshead' ) . '</legend><table>' .
@@ -901,14 +926,13 @@ class PreferencesForm {
$s1 = $uopt == 1 ? ' selected="selected"' : '';
$s2 = $uopt == 2 ? ' selected="selected"' : '';
$wgOut->addHTML("
-<div class='toggle'><label for='wpOpunderline'>$msgUnderline</label>
+<div class='toggle'><p><label for='wpOpunderline'>$msgUnderline</label>
<select name='wpOpunderline' id='wpOpunderline'>
<option value=\"0\"$s0>$msgUnderlinenever</option>
<option value=\"1\"$s1>$msgUnderlinealways</option>
<option value=\"2\"$s2>$msgUnderlinedefault</option>
-</select>
-</div>
-");
+</select></p></div>");
+
foreach ( $togs as $tname ) {
if( !array_key_exists( $tname, $this->mUsedToggles ) ) {
$wgOut->addHTML( $this->getToggle( $tname ) );
diff --git a/includes/SpecialPrefixindex.php b/includes/SpecialPrefixindex.php
index bbfc2782..ce296b4b 100644
--- a/includes/SpecialPrefixindex.php
+++ b/includes/SpecialPrefixindex.php
@@ -27,7 +27,7 @@ function wfSpecialPrefixIndex( $par=NULL, $specialPage ) {
$namespace = 0;
$wgOut->setPagetitle( $namespace > 0 ?
- wfMsg( 'allinnamespace', $namespaces[$namespace] ) :
+ wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
wfMsg( 'allarticles' )
);
@@ -97,7 +97,6 @@ function showChunk( $namespace = NS_MAIN, $prefix, $including = false, $from = n
$n = 0;
$out = '<table style="background: inherit;" border="0" width="100%">';
- $namespaces = $wgContLang->getFormattedNamespaces();
while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) {
$t = Title::makeTitle( $s->page_namespace, $s->page_title );
if( $t ) {
diff --git a/includes/SpecialRandompage.php b/includes/SpecialRandompage.php
index 9d38abcb..2cd31eb5 100644
--- a/includes/SpecialRandompage.php
+++ b/includes/SpecialRandompage.php
@@ -11,7 +11,7 @@
* used as e.g. Special:Randompage/Category
*/
function wfSpecialRandompage( $par = NS_MAIN ) {
- global $wgOut, $wgExtraRandompageSQL, $wgContLang, $wgLang;
+ global $wgOut, $wgExtraRandompageSQL;
$fname = 'wfSpecialRandompage';
# Determine namespace
@@ -49,7 +49,7 @@ function wfSpecialRandompage( $par = NS_MAIN ) {
}
if( is_null( $title ) ) {
# That's not supposed to happen :)
- $title = Title::newFromText( wfMsg( 'mainpage' ) );
+ $title = Title::newMainPage();
}
$wgOut->reportTime(); # for logfile
$wgOut->redirect( $title->getFullUrl() );
diff --git a/includes/SpecialRandomredirect.php b/includes/SpecialRandomredirect.php
index 512553c0..2cb2498b 100644
--- a/includes/SpecialRandomredirect.php
+++ b/includes/SpecialRandomredirect.php
@@ -45,7 +45,7 @@ function wfSpecialRandomredirect( $par = NULL ) {
# Catch dud titles and return to the main page
if( is_null( $title ) )
- $title = Title::newFromText( wfMsg( 'mainpage' ) );
+ $title = Title::newMainPage();
$wgOut->reportTime();
$wgOut->redirect( $title->getFullUrl( 'redirect=no' ) );
diff --git a/includes/SpecialRecentchanges.php b/includes/SpecialRecentchanges.php
index 8dfb68a5..3b8d69f2 100644
--- a/includes/SpecialRecentchanges.php
+++ b/includes/SpecialRecentchanges.php
@@ -14,7 +14,7 @@ require_once( 'ChangesList.php' );
* Constructor
*/
function wfSpecialRecentchanges( $par, $specialPage ) {
- global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol, $wgDBtype;
+ global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol;
global $wgRCShowWatchingUsers, $wgShowUpdatedMarker;
global $wgAllowCategorizedRecentChanges ;
$fname = 'wfSpecialRecentchanges';
@@ -43,12 +43,10 @@ function wfSpecialRecentchanges( $par, $specialPage ) {
extract($defaults);
- $days = $wgUser->getOption( 'rcdays' );
- if ( !$days ) { $days = $defaults['days']; }
+ $days = $wgUser->getOption( 'rcdays', $defaults['days']);
$days = $wgRequest->getInt( 'days', $days );
- $limit = $wgUser->getOption( 'rclimit' );
- if ( !$limit ) { $limit = $defaults['limit']; }
+ $limit = $wgUser->getOption( 'rclimit', $defaults['limit'] );
# list( $limit, $offset ) = wfCheckLimits( 100, 'rclimit' );
$limit = $wgRequest->getInt( 'limit', $limit );
@@ -90,7 +88,8 @@ function wfSpecialRecentchanges( $par, $specialPage ) {
if ( is_numeric( $bit ) ) {
$limit = $bit;
}
-
+
+ $m = array();
if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
$limit = $m[1];
}
@@ -107,7 +106,7 @@ function wfSpecialRecentchanges( $par, $specialPage ) {
# Database connection and caching
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'recentchanges', 'watchlist' ) );
+ list( $recentchanges, $watchlist ) = $dbr->tableNamesN( 'recentchanges', 'watchlist' );
$cutoff_unixtime = time() - ( $days * 86400 );
@@ -198,7 +197,7 @@ function wfSpecialRecentchanges( $par, $specialPage ) {
// Output header
if ( !$specialPage->including() ) {
- $wgOut->addWikiText( wfMsgForContent( "recentchangestext" ) );
+ $wgOut->addWikiText( wfMsgForContentNoTrans( "recentchangestext" ) );
// Dump everything here
$nondefaults = array();
@@ -222,7 +221,6 @@ function wfSpecialRecentchanges( $par, $specialPage ) {
}
// And now for the content
- $sk = $wgUser->getSkin();
$wgOut->setSyndicated( true );
$list = ChangesList::newFromUser( $wgUser );
@@ -334,7 +332,7 @@ function rcOutputFeed( $rows, $feedFormat, $limit, $hideminor, $lastmod ) {
' [' . $wgContLanguageCode . ']';
$feed = new $wgFeedClasses[$feedFormat](
$feedTitle,
- htmlspecialchars( wfMsgForContent( 'recentchangestext' ) ),
+ htmlspecialchars( wfMsgForContent( 'recentchanges-feed-description' ) ),
$wgTitle->getFullUrl() );
/**
@@ -397,7 +395,6 @@ function rcDoOutputFeed( $rows, &$feed ) {
$sorted[$n] = $obj;
$n++;
}
- $first = false;
}
foreach( $sorted as $obj ) {
@@ -571,7 +568,7 @@ function rcOptionsPanel( $defaults, $nondefaults ) {
*/
function rcNamespaceForm( $namespace, $invert, $nondefaults, $categories_any ) {
global $wgScript, $wgAllowCategorizedRecentChanges, $wgRequest;
- $t = Title::makeTitle( NS_SPECIAL, 'Recentchanges' );
+ $t = SpecialPage::getTitleFor( 'Recentchanges' );
$namespaceselect = HTMLnamespaceselector($namespace, '');
$submitbutton = '<input type="submit" value="' . wfMsgHtml( 'allpagessubmit' ) . "\" />\n";
@@ -613,9 +610,10 @@ function rcNamespaceForm( $namespace, $invert, $nondefaults, $categories_any ) {
*/
function rcFormatDiff( $row ) {
$titleObj = Title::makeTitle( $row->rc_namespace, $row->rc_title );
+ $timestamp = wfTimestamp( TS_MW, $row->rc_timestamp );
return rcFormatDiffRow( $titleObj,
$row->rc_last_oldid, $row->rc_this_oldid,
- $row->rc_timestamp,
+ $timestamp,
$row->rc_comment );
}
diff --git a/includes/SpecialRecentchangeslinked.php b/includes/SpecialRecentchangeslinked.php
index 59a3beb5..2214576c 100644
--- a/includes/SpecialRecentchangeslinked.php
+++ b/includes/SpecialRecentchangeslinked.php
@@ -44,11 +44,9 @@ function wfSpecialRecentchangeslinked( $par = NULL ) {
$wgOut->setSubtitle( htmlspecialchars( wfMsg( 'rclsub', $nt->getPrefixedText() ) ) );
if ( ! $days ) {
- $days = $wgUser->getOption( 'rcdays' );
- if ( ! $days ) { $days = 7; }
+ $days = (int)$wgUser->getOption( 'rcdays', 7 );
}
- $days = (int)$days;
- list( $limit, $offset ) = wfCheckLimits( 100, 'rclimit' );
+ list( $limit, /* offset */ ) = wfCheckLimits( 100, 'rclimit' );
$dbr =& wfGetDB( DB_SLAVE );
$cutoff = $dbr->timestamp( time() - ( $days * 86400 ) );
@@ -67,7 +65,8 @@ function wfSpecialRecentchangeslinked( $par = NULL ) {
$cmq = 'AND rc_minor=0';
} else { $cmq = ''; }
- extract( $dbr->tableNames( 'recentchanges', 'categorylinks', 'pagelinks', 'revision', 'page' , "watchlist" ) );
+ list($recentchanges, $categorylinks, $pagelinks, $watchlist) =
+ $dbr->tableNamesN( 'recentchanges', 'categorylinks', 'pagelinks', "watchlist" );
$uid = $wgUser->getID();
@@ -97,7 +96,9 @@ function wfSpecialRecentchangeslinked( $par = NULL ) {
rc_bot,
rc_new,
rc_patrolled,
- rc_type
+ rc_type,
+ rc_old_len,
+ rc_new_len
" . ($uid ? ",wl_user" : "") . "
FROM $categorylinks, $recentchanges
" . ($uid ? "LEFT OUTER JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") . "
@@ -124,7 +125,9 @@ $GROUPBY
rc_bot,
rc_new,
rc_patrolled,
- rc_type
+ rc_type,
+ rc_old_len,
+ rc_new_len
" . ($uid ? ",wl_user" : "") . "
FROM $pagelinks, $recentchanges
" . ($uid ? " LEFT OUTER JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") . "
diff --git a/includes/SpecialResetpass.php b/includes/SpecialResetpass.php
new file mode 100644
index 00000000..cde582b1
--- /dev/null
+++ b/includes/SpecialResetpass.php
@@ -0,0 +1,158 @@
+<?php
+
+function wfSpecialResetpass( $par ) {
+ $form = new PasswordResetForm();
+ $form->execute( $par );
+}
+
+class PasswordResetForm extends SpecialPage {
+ function __construct( $name=null, $reset=null ) {
+ if( $name !== null ) {
+ $this->mName = $name;
+ $this->mTemporaryPassword = $reset;
+ } else {
+ global $wgRequest;
+ $this->mName = $wgRequest->getVal( 'wpName' );
+ $this->mTemporaryPassword = $wgRequest->getVal( 'wpPassword' );
+ }
+ }
+
+ /**
+ * Main execution point
+ */
+ function execute( $par='' ) {
+ global $wgUser, $wgAuth, $wgOut, $wgRequest;
+
+ if( !$wgAuth->allowPasswordChange() ) {
+ $this->error( wfMsg( 'resetpass_forbidden' ) );
+ return;
+ }
+
+ if( $this->mName === null && !$wgRequest->wasPosted() ) {
+ $this->error( wfMsg( 'resetpass_missing' ) );
+ return;
+ }
+
+ if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) {
+ $newpass = $wgRequest->getVal( 'wpNewPassword' );
+ $retype = $wgRequest->getVal( 'wpRetype' );
+ try {
+ $this->attemptReset( $newpass, $retype );
+ $wgOut->addWikiText( wfMsg( 'resetpass_success' ) );
+
+ $data = array(
+ 'action' => 'submitlogin',
+ 'wpName' => $this->mName,
+ 'wpPassword' => $newpass,
+ 'returnto' => $wgRequest->getVal( 'returnto' ),
+ );
+ if( $wgRequest->getCheck( 'wpRemember' ) ) {
+ $data['wpRemember'] = 1;
+ }
+ $login = new LoginForm( new FauxRequest( $data, true ) );
+ $login->execute();
+
+ return;
+ } catch( PasswordError $e ) {
+ $this->error( $e->getMessage() );
+ }
+ }
+ $this->showForm();
+ }
+
+ function error( $msg ) {
+ global $wgOut;
+ $wgOut->addHtml( '<div class="errorbox">' .
+ htmlspecialchars( $msg ) .
+ '</div>' );
+ }
+
+ function showForm() {
+ global $wgOut, $wgUser, $wgLang, $wgRequest;
+
+ $self = SpecialPage::getTitleFor( 'Resetpass' );
+ $form =
+ '<div id="userloginForm">' .
+ wfOpenElement( 'form',
+ array(
+ 'method' => 'post',
+ 'action' => $self->getLocalUrl() ) ) .
+ '<h2>' . wfMsgHtml( 'resetpass_header' ) . '</h2>' .
+ '<div id="userloginprompt">' .
+ wfMsgExt( 'resetpass_text', array( 'parse' ) ) .
+ '</div>' .
+ '<table>' .
+ wfHidden( 'token', $wgUser->editToken() ) .
+ wfHidden( 'wpName', $this->mName ) .
+ wfHidden( 'wpPassword', $this->mTemporaryPassword ) .
+ wfHidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) .
+ $this->pretty( array(
+ array( 'wpName', 'username', 'text', $this->mName ),
+ array( 'wpNewPassword', 'newpassword', 'password', '' ),
+ array( 'wpRetype', 'yourpasswordagain', 'password', '' ),
+ ) ) .
+ '<tr>' .
+ '<td></td>' .
+ '<td>' .
+ Xml::checkLabel( wfMsg( 'remembermypassword' ),
+ 'wpRemember', 'wpRemember',
+ $wgRequest->getCheck( 'wpRemember' ) ) .
+ '</td>' .
+ '</tr>' .
+ '<tr>' .
+ '<td></td>' .
+ '<td>' .
+ wfSubmitButton( wfMsgHtml( 'resetpass_submit' ) ) .
+ '</td>' .
+ '</tr>' .
+ '</table>' .
+ wfCloseElement( 'form' ) .
+ '</div>';
+ $wgOut->addHtml( $form );
+ }
+
+ function pretty( $fields ) {
+ $out = '';
+ foreach( $fields as $list ) {
+ list( $name, $label, $type, $value ) = $list;
+ if( $type == 'text' ) {
+ $field = '<tt>' . htmlspecialchars( $value ) . '</tt>';
+ } else {
+ $field = Xml::input( $name, 20, $value,
+ array( 'id' => $name, 'type' => $type ) );
+ }
+ $out .= '<tr>';
+ $out .= '<td align="right">';
+ $out .= Xml::label( wfMsg( $label ), $name );
+ $out .= '</td>';
+ $out .= '<td>';
+ $out .= $field;
+ $out .= '</td>';
+ $out .= '</tr>';
+ }
+ return $out;
+ }
+
+ /**
+ * @throws PasswordError
+ */
+ function attemptReset( $newpass, $retype ) {
+ $user = User::newFromName( $this->mName );
+ if( $user->isAnon() ) {
+ throw new PasswordError( 'no such user' );
+ }
+
+ if( !$user->checkTemporaryPassword( $this->mTemporaryPassword ) ) {
+ throw new PasswordError( wfMsg( 'resetpass_bad_temporary' ) );
+ }
+
+ if( $newpass !== $retype ) {
+ throw new PasswordError( wfMsg( 'badretype' ) );
+ }
+
+ $user->setPassword( $newpass );
+ $user->saveSettings();
+ }
+}
+
+?>
diff --git a/includes/SpecialRevisiondelete.php b/includes/SpecialRevisiondelete.php
index afbb589c..fb5e9ec8 100644
--- a/includes/SpecialRevisiondelete.php
+++ b/includes/SpecialRevisiondelete.php
@@ -10,12 +10,11 @@
*/
function wfSpecialRevisiondelete( $par = null ) {
- global $wgOut, $wgRequest, $wgUser;
+ global $wgOut, $wgRequest;
$target = $wgRequest->getVal( 'target' );
$oldid = $wgRequest->getIntArray( 'oldid' );
- $sk = $wgUser->getSkin();
$page = Title::newFromUrl( $target );
if( is_null( $page ) ) {
@@ -89,7 +88,7 @@ class RevisionDeleteForm {
$hidden[] = wfHidden( 'oldid[]', $revid );
}
- $special = Title::makeTitle( NS_SPECIAL, 'Revisiondelete' );
+ $special = SpecialPage::getTitleFor( 'Revisiondelete' );
$wgOut->addHtml( wfElement( 'form', array(
'method' => 'post',
'action' => $special->getLocalUrl( 'action=submit' ) ),
@@ -156,7 +155,7 @@ class RevisionDeleteForm {
function extractBitfield( $request ) {
$bitfield = 0;
foreach( $this->checks as $item ) {
- list( $message, $name, $field ) = $item;
+ list( /* message */ , $name, $field ) = $item;
if( $request->getCheck( $name ) ) {
$bitfield |= $field;
}
@@ -167,7 +166,7 @@ class RevisionDeleteForm {
function save( $bitfield, $reason ) {
$dbw = wfGetDB( DB_MASTER );
$deleter = new RevisionDeleter( $dbw );
- $ok = $deleter->setVisibility( $this->revisions, $bitfield, $reason );
+ $deleter->setVisibility( $this->revisions, $bitfield, $reason );
}
}
diff --git a/includes/SpecialSearch.php b/includes/SpecialSearch.php
index 057b487c..9ecd39ef 100644
--- a/includes/SpecialSearch.php
+++ b/includes/SpecialSearch.php
@@ -70,19 +70,17 @@ class SpecialSearch {
}
/**
- * If an exact title match can be found, jump straight ahead to
+ * If an exact title match can be found, jump straight ahead to it.
* @param string $term
* @public
*/
function goResult( $term ) {
global $wgOut;
global $wgGoToEdit;
- global $wgContLang;
$this->setupPage( $term );
# Try to go to page as entered.
- #
$t = Title::newFromText( $term );
# If the string cannot be used to create a title
@@ -99,17 +97,13 @@ class SpecialSearch {
# No match, generate an edit URL
$t = Title::newFromText( $term );
- if( is_null( $t ) ) {
- $editurl = ''; # hrm...
- } else {
+ if( ! is_null( $t ) ) {
wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) );
# If the feature is enabled, go straight to the edit page
if ( $wgGoToEdit ) {
$wgOut->redirect( $t->getFullURL( 'action=edit' ) );
return;
- } else {
- $editurl = $t->escapeLocalURL( 'action=edit' );
- }
+ }
}
$wgOut->addWikiText( wfMsg( 'noexactmatch', wfEscapeWikiText( $term ) ) );
@@ -126,8 +120,7 @@ class SpecialSearch {
$this->setupPage( $term );
- global $wgUser, $wgOut;
- $sk = $wgUser->getSkin();
+ global $wgOut;
$wgOut->addWikiText( wfMsg( 'searchresulttext' ) );
#if ( !$this->parseQuery() ) {
@@ -177,7 +170,7 @@ class SpecialSearch {
if( $num || $this->offset ) {
$prevnext = wfViewPrevNext( $this->offset, $this->limit,
- 'Special:Search',
+ SpecialPage::getTitleFor( 'Search' ),
wfArrayToCGI(
$this->powerSearchOptions(),
array( 'search' => $term ) ) );
@@ -323,10 +316,8 @@ class SpecialSearch {
}
$sk =& $wgUser->getSkin();
- $contextlines = $wgUser->getOption( 'contextlines' );
- if ( '' == $contextlines ) { $contextlines = 5; }
- $contextchars = $wgUser->getOption( 'contextchars' );
- if ( '' == $contextchars ) { $contextchars = 50; }
+ $contextlines = $wgUser->getOption( 'contextlines', 5 );
+ $contextchars = $wgUser->getOption( 'contextchars', 50 );
$link = $sk->makeKnownLinkObj( $t );
$revision = Revision::newFromTitle( $t );
@@ -348,6 +339,7 @@ class SpecialSearch {
break;
}
++$lineno;
+ $m = array();
if ( ! preg_match( $pat1, $line, $m ) ) {
continue;
}
@@ -404,7 +396,7 @@ class SpecialSearch {
'', '', '', '', '', # Dummy placeholders
$searchButton );
- $title = Title::makeTitle( NS_SPECIAL, 'Search' );
+ $title = SpecialPage::getTitleFor( 'Search' );
$action = $title->escapeLocalURL();
return "<br /><br />\n<form id=\"powersearch\" method=\"get\" " .
"action=\"$action\">\n{$ret}\n</form>\n";
diff --git a/includes/SpecialShortpages.php b/includes/SpecialShortpages.php
index 34b3505b..03164deb 100644
--- a/includes/SpecialShortpages.php
+++ b/includes/SpecialShortpages.php
@@ -43,16 +43,16 @@ class ShortPagesPage extends QueryPage {
WHERE page_namespace=".NS_MAIN." AND page_is_redirect=0";
}
- function preprocessResults( &$dbo, $res ) {
+ function preprocessResults( &$db, &$res ) {
# There's no point doing a batch check if we aren't caching results;
# the page must exist for it to have been pulled out of the table
if( $this->isCached() ) {
$batch = new LinkBatch();
- while( $row = $dbo->fetchObject( $res ) )
+ while( $row = $db->fetchObject( $res ) )
$batch->addObj( Title::makeTitleSafe( $row->namespace, $row->title ) );
$batch->execute();
- if( $dbo->numRows( $res ) > 0 )
- $dbo->dataSeek( $res, 0 );
+ if( $db->numRows( $res ) > 0 )
+ $db->dataSeek( $res, 0 );
}
}
@@ -72,7 +72,7 @@ class ShortPagesPage extends QueryPage {
$plink = $this->isCached()
? $skin->makeLinkObj( $title )
: $skin->makeKnownLinkObj( $title );
- $size = wfMsgHtml( 'nbytes', $wgLang->formatNum( htmlspecialchars( $result->value ) ) );
+ $size = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( htmlspecialchars( $result->value ) ) );
return $title->exists()
? "({$hlink}) {$dm}{$plink} {$dm}[{$size}]"
diff --git a/includes/SpecialSpecialpages.php b/includes/SpecialSpecialpages.php
index 6a01cd08..78f9dee5 100644
--- a/includes/SpecialSpecialpages.php
+++ b/includes/SpecialSpecialpages.php
@@ -37,7 +37,7 @@ function wfSpecialSpecialpages_gen($pages,$heading,$sk) {
/** Put them into a sortable array */
$sortedPages = array();
- foreach ( $pages as $name => $page ) {
+ foreach ( $pages as $page ) {
if ( $page->isListed() ) {
$sortedPages[$page->getDescription()] = $page->getTitle();
}
diff --git a/includes/SpecialStatistics.php b/includes/SpecialStatistics.php
index 4a51efd9..a5a0fc3a 100644
--- a/includes/SpecialStatistics.php
+++ b/includes/SpecialStatistics.php
@@ -15,39 +15,13 @@ function wfSpecialStatistics() {
$action = $wgRequest->getVal( 'action' );
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'page', 'site_stats', 'user', 'user_groups' ) );
- $row = $dbr->selectRow( 'site_stats', '*', false, $fname );
- $views = $row->ss_total_views;
- $edits = $row->ss_total_edits;
- $good = $row->ss_good_articles;
- $images = $row->ss_images;
-
- # This code is somewhat schema-agnostic, because I'm changing it in a minor release -- TS
- if ( isset( $row->ss_total_pages ) && $row->ss_total_pages == -1 ) {
- # Update schema
- $u = new SiteStatsUpdate( 0, 0, 0 );
- $u->doUpdate();
- $row = $dbr->selectRow( 'site_stats', '*', false, $fname );
- }
-
- if ( isset( $row->ss_total_pages ) ) {
- $total = $row->ss_total_pages;
- } else {
- $sql = "SELECT COUNT(page_namespace) AS total FROM $page";
- $res = $dbr->query( $sql, $fname );
- $pageRow = $dbr->fetchObject( $res );
- $total = $pageRow->total;
- }
-
- if ( isset( $row->ss_users ) ) {
- $users = $row->ss_users;
- } else {
- $sql = "SELECT MAX(user_id) AS total FROM $user";
- $res = $dbr->query( $sql, $fname );
- $userRow = $dbr->fetchObject( $res );
- $users = $userRow->total;
- }
+ $views = SiteStats::views();
+ $edits = SiteStats::edits();
+ $good = SiteStats::articles();
+ $images = SiteStats::images();
+ $total = SiteStats::pages();
+ $users = SiteStats::users();
$admins = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), $fname );
$numJobs = $dbr->selectField( 'job', 'COUNT(*)', '', $fname );
@@ -84,6 +58,7 @@ function wfSpecialStatistics() {
global $wgDisableCounters, $wgMiserMode, $wgUser, $wgLang, $wgContLang;
if( !$wgDisableCounters && !$wgMiserMode ) {
+ $page = $dbr->tableName( 'page' );
$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 );
diff --git a/includes/SpecialUncategorizedimages.php b/includes/SpecialUncategorizedimages.php
index 38156976..1daba8ed 100644
--- a/includes/SpecialUncategorizedimages.php
+++ b/includes/SpecialUncategorizedimages.php
@@ -28,7 +28,7 @@ class UncategorizedImagesPage extends QueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'page', 'categorylinks' ) );
+ list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' );
$ns = NS_IMAGE;
return "SELECT 'Uncategorizedimages' AS type, page_namespace AS namespace,
diff --git a/includes/SpecialUncategorizedpages.php b/includes/SpecialUncategorizedpages.php
index 0ecc5d07..dbf23a60 100644
--- a/includes/SpecialUncategorizedpages.php
+++ b/includes/SpecialUncategorizedpages.php
@@ -28,7 +28,7 @@ class UncategorizedPagesPage extends PageQueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'page', 'categorylinks' ) );
+ list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' );
$name = $dbr->addQuotes( $this->getName() );
return
diff --git a/includes/SpecialUndelete.php b/includes/SpecialUndelete.php
index 8e0291ec..7c9b1191 100644
--- a/includes/SpecialUndelete.php
+++ b/includes/SpecialUndelete.php
@@ -105,17 +105,49 @@ class PageArchive {
* revision of the page with the given timestamp.
*
* @return string
+ * @deprecated Use getRevision() for more flexible information
*/
function getRevisionText( $timestamp ) {
+ $rev = $this->getRevision( $timestamp );
+ return $rev ? $rev->getText() : null;
+ }
+
+ /**
+ * Return a Revision object containing data for the deleted revision.
+ * Note that the result *may* or *may not* have a null page ID.
+ * @param string $timestamp
+ * @return Revision
+ */
+ function getRevision( $timestamp ) {
$dbr =& wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'archive',
- array( 'ar_text', 'ar_flags', 'ar_text_id' ),
+ array(
+ 'ar_rev_id',
+ 'ar_text',
+ 'ar_comment',
+ 'ar_user',
+ 'ar_user_text',
+ 'ar_timestamp',
+ 'ar_minor_edit',
+ 'ar_flags',
+ 'ar_text_id' ),
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDbkey(),
'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
__METHOD__ );
if( $row ) {
- return $this->getTextFromRow( $row );
+ return new Revision( array(
+ 'page' => $this->title->getArticleId(),
+ 'id' => $row->ar_rev_id,
+ 'text' => ($row->ar_text_id
+ ? null
+ : Revision::getRevisionText( $row, 'ar_' ) ),
+ 'comment' => $row->ar_comment,
+ 'user' => $row->ar_user,
+ 'user_text' => $row->ar_user_text,
+ 'timestamp' => $row->ar_timestamp,
+ 'minor_edit' => $row->ar_minor_edit,
+ 'text_id' => $row->ar_text_id ) );
} else {
return null;
}
@@ -246,12 +278,12 @@ class PageArchive {
* @return int number of revisions restored
*/
private function undeleteRevisions( $timestamps ) {
- global $wgParser, $wgDBtype;
+ global $wgDBtype;
$restoreAll = empty( $timestamps );
$dbw =& wfGetDB( DB_MASTER );
- extract( $dbw->tableNames( 'page', 'archive' ) );
+ $page = $dbw->tableName( 'archive' );
# Does this page already exist? We'll have to update it...
$article = new Article( $this->title );
@@ -316,7 +348,6 @@ class PageArchive {
}
$revision = null;
- $newRevId = $previousRevId;
$restored = 0;
while( $row = $dbw->fetchObject( $result ) ) {
@@ -343,7 +374,7 @@ class PageArchive {
'minor_edit' => $row->ar_minor_edit,
'text_id' => $row->ar_text_id,
) );
- $newRevId = $revision->insertOn( $dbw );
+ $revision->insertOn( $dbw );
$restored++;
}
@@ -421,6 +452,7 @@ class UndeleteForm {
$timestamps = array();
$this->mFileVersions = array();
foreach( $_REQUEST as $key => $val ) {
+ $matches = array();
if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
array_push( $timestamps, $matches[1] );
}
@@ -465,7 +497,7 @@ class UndeleteForm {
$wgOut->addWikiText( wfMsg( "undeletepagetext" ) );
$sk = $wgUser->getSkin();
- $undelete =& Title::makeTitle( NS_SPECIAL, 'Undelete' );
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
$wgOut->addHTML( "<ul>\n" );
while( $row = $result->fetchObject() ) {
$title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
@@ -485,25 +517,33 @@ class UndeleteForm {
if(!preg_match("/[0-9]{14}/",$timestamp)) return 0;
$archive = new PageArchive( $this->mTargetObj );
- $text = $archive->getRevisionText( $timestamp );
-
+ $rev = $archive->getRevision( $timestamp );
+
$wgOut->setPagetitle( wfMsg( "undeletepage" ) );
$wgOut->addWikiText( "(" . wfMsg( "undeleterevision",
- $wgLang->date( $timestamp ) ) . ")\n" );
+ $wgLang->timeAndDate( $timestamp ) ) . ")\n" );
+
+ if( !$rev ) {
+ $wgOut->addWikiText( wfMsg( 'undeleterevision-missing' ) );
+ return;
+ }
+
+ wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) );
if( $this->mPreview ) {
$wgOut->addHtml( "<hr />\n" );
- $wgOut->addWikiText( $text );
+ $article = new Article ( $archive->title ); # OutputPage wants an Article obj
+ $wgOut->addPrimaryWikiText( $rev->getText(), $article, false );
}
- $self = Title::makeTitle( NS_SPECIAL, "Undelete" );
+ $self = SpecialPage::getTitleFor( "Undelete" );
$wgOut->addHtml(
wfElement( 'textarea', array(
'readonly' => true,
'cols' => intval( $wgUser->getOption( 'cols' ) ),
'rows' => intval( $wgUser->getOption( 'rows' ) ) ),
- $text . "\n" ) .
+ $rev->getText() . "\n" ) .
wfOpenElement( 'div' ) .
wfOpenElement( 'form', array(
'method' => 'post',
@@ -535,9 +575,17 @@ class UndeleteForm {
* Show a deleted file version requested by the visitor.
*/
function showFile( $key ) {
- global $wgOut;
+ global $wgOut, $wgRequest;
$wgOut->disable();
+ # We mustn't allow the output to be Squid cached, otherwise
+ # if an admin previews a deleted image, and it's cached, then
+ # a user without appropriate permissions can toddle off and
+ # nab the image, and Squid will serve it
+ $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' );
+
$store = FileStore::get( 'deleted' );
$store->stream( $key );
}
@@ -553,8 +601,8 @@ class UndeleteForm {
}
$archive = new PageArchive( $this->mTargetObj );
- $text = $archive->getLastRevisionText();
/*
+ $text = $archive->getLastRevisionText();
if( is_null( $text ) ) {
$wgOut->addWikiText( wfMsg( "nohistory" ) );
return;
@@ -594,7 +642,7 @@ class UndeleteForm {
}
if ( $this->mAllowed ) {
- $titleObj = Title::makeTitle( NS_SPECIAL, "Undelete" );
+ $titleObj = SpecialPage::getTitleFor( "Undelete" );
$action = $titleObj->getLocalURL( "action=submit" );
# Start the form here
$top = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) );
@@ -698,7 +746,6 @@ class UndeleteForm {
global $wgOut, $wgUser;
if( !is_null( $this->mTargetObj ) ) {
$archive = new PageArchive( $this->mTargetObj );
- $ok = true;
$ok = $archive->undelete(
$this->mTargetTimestamp,
diff --git a/includes/SpecialUnlockdb.php b/includes/SpecialUnlockdb.php
index 6627f75f..1f24d131 100644
--- a/includes/SpecialUnlockdb.php
+++ b/includes/SpecialUnlockdb.php
@@ -54,7 +54,7 @@ class DBUnlockForm {
}
$lc = htmlspecialchars( wfMsg( "unlockconfirm" ) );
$lb = htmlspecialchars( wfMsg( "unlockbtn" ) );
- $titleObj = Title::makeTitle( NS_SPECIAL, "Unlockdb" );
+ $titleObj = SpecialPage::getTitleFor( "Unlockdb" );
$action = $titleObj->escapeLocalURL( "action=submit" );
$token = htmlspecialchars( $wgUser->editToken() );
@@ -94,7 +94,7 @@ END
$wgOut->showFileDeleteError( $wgReadOnlyFile );
return;
}
- $titleObj = Title::makeTitle( NS_SPECIAL, "Unlockdb" );
+ $titleObj = SpecialPage::getTitleFor( "Unlockdb" );
$success = $titleObj->getFullURL( "action=success" );
$wgOut->redirect( $success );
}
diff --git a/includes/SpecialUnusedcategories.php b/includes/SpecialUnusedcategories.php
index 270180ef..80f46a87 100644
--- a/includes/SpecialUnusedcategories.php
+++ b/includes/SpecialUnusedcategories.php
@@ -23,9 +23,9 @@ class UnusedCategoriesPage extends QueryPage {
function getSQL() {
$NScat = NS_CATEGORY;
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'categorylinks','page' ));
+ list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' );
return "SELECT 'Unusedcategories' as type,
- {$NScat} as namespace, page_title as title, 1 as value
+ {$NScat} as namespace, page_title as title, page_title as value
FROM $page
LEFT JOIN $categorylinks ON page_title=cl_to
WHERE cl_from IS NULL
diff --git a/includes/SpecialUnusedimages.php b/includes/SpecialUnusedimages.php
index 32a6f95a..75d702c8 100644
--- a/includes/SpecialUnusedimages.php
+++ b/includes/SpecialUnusedimages.php
@@ -25,7 +25,7 @@ class UnusedimagesPage extends QueryPage {
$dbr =& wfGetDB( DB_SLAVE );
if ( $wgCountCategorizedImagesAsUsed ) {
- extract( $dbr->tableNames( 'page', 'image', 'imagelinks', 'categorylinks' ) );
+ list( $page, $image, $imagelinks, $categorylinks ) = $dbr->tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' );
return 'SELECT img_name as title, img_user, img_user_text, img_timestamp as value, img_description
FROM ((('.$page.' AS I LEFT JOIN '.$categorylinks.' AS L ON I.page_id = L.cl_from)
@@ -33,7 +33,7 @@ class UnusedimagesPage extends QueryPage {
INNER JOIN '.$image.' AS G ON I.page_title = G.img_name)
WHERE I.page_namespace = '.NS_IMAGE.' AND L.cl_from IS NULL AND P.il_to IS NULL';
} else {
- extract( $dbr->tableNames( 'image','imagelinks' ) );
+ list( $image, $imagelinks ) = $dbr->tableNamesN( 'image','imagelinks' );
return 'SELECT img_name as title, img_user, img_user_text, img_timestamp as value, img_description' .
' FROM '.$image.' LEFT JOIN '.$imagelinks.' ON img_name=il_to WHERE il_to IS NULL ';
diff --git a/includes/SpecialUnusedtemplates.php b/includes/SpecialUnusedtemplates.php
index b33a24da..2af9abc6 100644
--- a/includes/SpecialUnusedtemplates.php
+++ b/includes/SpecialUnusedtemplates.php
@@ -23,7 +23,7 @@ class UnusedtemplatesPage extends QueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'page', 'templatelinks' ) );
+ list( $page, $templatelinks) = $dbr->tableNamesN( 'page', 'templatelinks' );
$sql = "SELECT 'Unusedtemplates' AS type, page_title AS title,
page_namespace AS namespace, 0 AS value
FROM $page
@@ -37,7 +37,7 @@ class UnusedtemplatesPage extends QueryPage {
$title = Title::makeTitle( NS_TEMPLATE, $result->title );
$pageLink = $skin->makeKnownLinkObj( $title, '', 'redirect=no' );
$wlhLink = $skin->makeKnownLinkObj(
- Title::makeTitle( NS_SPECIAL, 'Whatlinkshere' ),
+ SpecialPage::getTitleFor( 'Whatlinkshere' ),
wfMsgHtml( 'unusedtemplateswlh' ),
'target=' . $title->getPrefixedUrl() );
return wfSpecialList( $pageLink, $wlhLink );
diff --git a/includes/SpecialUnwatchedpages.php b/includes/SpecialUnwatchedpages.php
index 66e5c091..f9dff724 100644
--- a/includes/SpecialUnwatchedpages.php
+++ b/includes/SpecialUnwatchedpages.php
@@ -22,7 +22,7 @@ class UnwatchedpagesPage extends QueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'page', 'watchlist' ) );
+ list( $page, $watchlist ) = $dbr->tableNamesN( 'page', 'watchlist' );
$mwns = NS_MEDIAWIKI;
return
"
diff --git a/includes/SpecialUpload.php b/includes/SpecialUpload.php
index ade58056..d2fd839c 100644
--- a/includes/SpecialUpload.php
+++ b/includes/SpecialUpload.php
@@ -28,6 +28,13 @@ class UploadForm {
var $mUploadSaveName, $mUploadTempName, $mUploadSize, $mUploadOldVersion;
var $mUploadCopyStatus, $mUploadSource, $mReUpload, $mAction, $mUpload;
var $mOname, $mSessionKey, $mStashed, $mDestFile, $mRemoveTempFile, $mSourceType;
+ var $mUploadTempFileSize = 0;
+
+ # Placeholders for text injection by hooks (must be HTML)
+ # extensions should take care to _append_ to the present value
+ var $uploadFormTextTop;
+ var $uploadFormTextAfterSummary;
+
/**#@-*/
/**
@@ -44,6 +51,10 @@ class UploadForm {
return;
}
+ # Placeholders for text injection by hooks (empty per default)
+ $this->uploadFormTextTop = "";
+ $this->uploadFormTextAfterSummary = "";
+
$this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' );
$this->mReUpload = $request->getCheck( 'wpReUpload' );
$this->mUpload = $request->getCheck( 'wpUpload' );
@@ -105,7 +116,7 @@ class UploadForm {
* @access private
*/
function initializeFromUrl( $request ) {
- global $wgTmpDirectory, $wgMaxUploadSize;
+ global $wgTmpDirectory;
$url = $request->getText( 'wpUploadFileURL' );
$local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' );
@@ -125,7 +136,7 @@ class UploadForm {
* Returns true if there was an error, false otherwise
*/
private function curlCopy( $url, $dest ) {
- global $wgMaxUploadSize, $wgUser;
+ global $wgUser, $wgOut;
if( !$wgUser->isAllowed( 'upload_by_url' ) ) {
$wgOut->permissionRequired( 'upload_by_url' );
@@ -133,17 +144,18 @@ class UploadForm {
}
# Maybe remove some pasting blanks :-)
- $url = strtolower( trim( $url ) );
- if( substr( $url, 0, 7 ) != 'http://' && substr( $url, 0, 6 ) != 'ftp://' ) {
+ $url = trim( $url );
+ if( stripos($url, 'http://') !== 0 && stripos($url, 'ftp://') !== 0 ) {
# Only HTTP or FTP URLs
+ $wgOut->errorPage( 'upload-proto-error', 'upload-proto-error-text' );
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
+ $wgOut->errorPage( 'upload-file-error', 'upload-file-error-text');
return true;
}
@@ -155,13 +167,18 @@ class UploadForm {
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
+ $errornum = curl_errno( $ch );
+ // if ( $error ) print curl_error ( $ch ) ; # Debugging output
curl_close( $ch );
fclose( $this->mUploadTempFile );
unset( $this->mUploadTempFile );
if( $error ) {
unlink( $dest );
+ if( wfEmptyMsg( "upload-curl-error$errornum", wfMsg("upload-curl-error$errornum") ) )
+ $wgOut->errorPage( 'upload-misc-error', 'upload-misc-error-text' );
+ else
+ $wgOut->errorPage( "upload-curl-error$errornum", "upload-curl-error$errornum-text" );
}
return $error;
@@ -249,6 +266,12 @@ class UploadForm {
function processUpload() {
global $wgUser, $wgOut;
+ if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) )
+ {
+ wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file." );
+ return false;
+ }
+
/* Check for PHP error if any, requires php 4.2 or newer */
if( $this->mUploadError == 1/*UPLOAD_ERR_INI_SIZE*/ ) {
$this->mainUploadForm( wfMsgHtml( 'largefileserver' ) );
@@ -330,7 +353,7 @@ class UploadForm {
if( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) ||
($wgStrictFileExtensions &&
!$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) ) {
- return $this->uploadError( wfMsgHtml( 'badfiletype', htmlspecialchars( $fullExt ) ) );
+ return $this->uploadError( wfMsgHtml( 'badfiletype', htmlspecialchars( $finalExt ) ) );
}
/**
@@ -373,15 +396,16 @@ class UploadForm {
global $wgCheckFileExtensions;
if ( $wgCheckFileExtensions ) {
if ( ! $this->checkFileExtension( $finalExt, $wgFileExtensions ) ) {
- $warning .= '<li>'.wfMsgHtml( 'badfiletype', htmlspecialchars( $fullExt ) ).'</li>';
+ $warning .= '<li>'.wfMsgHtml( 'badfiletype', htmlspecialchars( $finalExt ) ).'</li>';
}
}
global $wgUploadSizeWarning;
if ( $wgUploadSizeWarning && ( $this->mUploadSize > $wgUploadSizeWarning ) ) {
- # TODO: Format $wgUploadSizeWarning to something that looks better than the raw byte
- # value, perhaps add GB,MB and KB suffixes?
- $warning .= '<li>'.wfMsgHtml( 'largefile', $wgUploadSizeWarning, $this->mUploadSize ).'</li>';
+ $skin =& $wgUser->getSkin();
+ $wsize = $skin->formatSize( $wgUploadSizeWarning );
+ $asize = $skin->formatSize( $this->mUploadSize );
+ $warning .= '<li>' . wfMsgHtml( 'large-file', $wsize, $asize ) . '</li>';
}
if ( $this->mUploadSize == 0 ) {
$warning .= '<li>'.wfMsgHtml( 'emptyfile' ).'</li>';
@@ -398,7 +422,7 @@ class UploadForm {
$image = new Image( $nt );
if( $image->wasDeleted() ) {
$skin = $wgUser->getSkin();
- $ltitle = Title::makeTitle( NS_SPECIAL, 'Log' );
+ $ltitle = SpecialPage::getTitleFor( 'Log' );
$llink = $skin->makeKnownLinkObj( $ltitle, wfMsgHtml( 'deletionlog' ), 'type=delete&page=' . $nt->getPrefixedUrl() );
$warning .= wfOpenElement( 'li' ) . wfMsgWikiHtml( 'filewasdeleted', $llink ) . wfCloseElement( 'li' );
}
@@ -632,7 +656,7 @@ class UploadForm {
$reupload = wfMsgHtml( 'reupload' );
$iw = wfMsgWikiHtml( 'ignorewarning' );
$reup = wfMsgWikiHtml( 'reuploaddesc' );
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Upload' );
+ $titleObj = SpecialPage::getTitleFor( 'Upload' );
$action = $titleObj->escapeLocalURL( 'action=submit' );
if ( $wgUseCopyrightUpload )
@@ -684,6 +708,12 @@ class UploadForm {
global $wgUseCopyrightUpload;
global $wgRequest, $wgAllowCopyUploads;
+ if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) )
+ {
+ wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
+ return false;
+ }
+
$cols = intval($wgUser->getOption( 'cols' ));
$ew = $wgUser->getOption( 'editwidth' );
if ( $ew ) $ew = " style=\"width:100%\"";
@@ -697,8 +727,6 @@ class UploadForm {
$wgOut->addHTML( '<div id="uploadtext">' );
$wgOut->addWikiText( wfMsg( 'uploadtext' ) );
$wgOut->addHTML( '</div>' );
- $sk = $wgUser->getSkin();
-
$sourcefilename = wfMsgHtml( 'sourcefilename' );
$destfilename = wfMsgHtml( 'destfilename' );
@@ -712,18 +740,19 @@ class UploadForm {
$ulb = wfMsgHtml( 'uploadbtn' );
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Upload' );
+ $titleObj = SpecialPage::getTitleFor( 'Upload' );
$action = $titleObj->escapeLocalURL();
$encDestFile = htmlspecialchars( $this->mDestFile );
- $watchChecked = $wgUser->getOption( 'watchdefault' )
+ $watchChecked =
+ ( $wgUser->getOption( 'watchdefault' ) ||
+ ( $wgUser->getOption( 'watchcreations' ) && $this->mDestFile == '' ) )
? '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\")'" .
@@ -745,6 +774,7 @@ class UploadForm {
<form id='upload' method='post' enctype='multipart/form-data' action=\"$action\">
<table border='0'>
<tr>
+ {$this->uploadFormTextTop}
<td align='right' valign='top'><label for='wpUploadFile'>{$sourcefilename}:</label></td>
<td align='left'>
{$filename_form}
@@ -760,6 +790,7 @@ class UploadForm {
<td align='right'><label for='wpUploadDescription'>{$summary}</label></td>
<td align='left'>
<textarea tabindex='3' name='wpUploadDescription' id='wpUploadDescription' rows='6' cols='{$cols}'{$ew}>" . htmlspecialchars( $this->mUploadDescription ) . "</textarea>
+ {$this->uploadFormTextAfterSummary}
</td>
</tr>
<tr>" );
@@ -810,9 +841,6 @@ class UploadForm {
</td>
</tr>
<tr>
-
- </tr>
- <tr>
<td></td>
<td align='left'><input tabindex='9' type='submit' name='wpUpload' value=\"{$ulb}\" /></td>
</tr>
@@ -1046,13 +1074,13 @@ class UploadForm {
$chunk = Sanitizer::decodeCharReferences( $chunk );
#look for script-types
- if (preg_match("!type\s*=\s*['\"]?\s*(\w*/)?(ecma|java)!sim",$chunk)) return true;
+ if (preg_match('!type\s*=\s*[\'"]?\s*(\w*/)?(ecma|java)!sim',$chunk)) return true;
#look for html-style script-urls
- if (preg_match("!(href|src|data)\s*=\s*['\"]?\s*(ecma|java)script:!sim",$chunk)) return true;
+ if (preg_match('!(href|src|data)\s*=\s*[\'"]?\s*(ecma|java)script:!sim',$chunk)) return true;
#look for css-style script-urls
- if (preg_match("!url\s*\(\s*['\"]?\s*(ecma|java)script:!sim",$chunk)) return true;
+ if (preg_match('!url\s*\(\s*[\'"]?\s*(ecma|java)script:!sim',$chunk)) return true;
wfDebug("SpecialUpload::detectScript: no scripts found\n");
return false;
@@ -1104,21 +1132,25 @@ class UploadForm {
#NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
# that does not seem to be worth the pain.
# Ask me (Duesentrieb) about it if it's ever needed.
+ $output = array();
if (wfIsWindows()) exec("$scanner",$output,$code);
else exec("$scanner 2>&1",$output,$code);
- $exit_code= $code; #remeber for user feedback
+ $exit_code= $code; #remember for user feedback
if ($virus_scanner_codes) { #map exit code to AV_xxx constants.
- if (isset($virus_scanner_codes[$code])) $code= $virus_scanner_codes[$code]; #explicite mapping
- else if (isset($virus_scanner_codes["*"])) $code= $virus_scanner_codes["*"]; #fallback mapping
+ if (isset($virus_scanner_codes[$code])) {
+ $code= $virus_scanner_codes[$code]; # explicit mapping
+ } else if (isset($virus_scanner_codes["*"])) {
+ $code= $virus_scanner_codes["*"]; # fallback mapping
+ }
}
if ($code===AV_SCAN_FAILED) { #scan failed (code was mapped to false by $virus_scanner_codes)
wfDebug("$fname: failed to scan $file (code $exit_code).\n");
- if ($wgAntivirusRequired) return "scan failed (code $exit_code)";
- else return NULL;
+ if ($wgAntivirusRequired) { return "scan failed (code $exit_code)"; }
+ else { return NULL; }
}
else if ($code===AV_SCAN_ABORTED) { #scan failed because filetype is unknown (probably imune)
wfDebug("$fname: unsupported file type $file (code $exit_code).\n");
@@ -1132,7 +1164,7 @@ class UploadForm {
$output= join("\n",$output);
$output= trim($output);
- if (!$output) $output= true; #if ther's no output, return true
+ if (!$output) $output= true; #if there's no output, return true
else if ($msg_pattern) {
$groups= array();
if (preg_match($msg_pattern,$output,$groups)) {
diff --git a/includes/SpecialUserlogin.php b/includes/SpecialUserlogin.php
index 574579cc..e60e3d54 100644
--- a/includes/SpecialUserlogin.php
+++ b/includes/SpecialUserlogin.php
@@ -12,7 +12,7 @@ function wfSpecialUserlogin() {
global $wgCommandLineMode;
global $wgRequest;
if( !$wgCommandLineMode && !isset( $_COOKIE[session_name()] ) ) {
- User::SetupSession();
+ wfSetupSession();
}
$form = new LoginForm( $wgRequest );
@@ -34,6 +34,7 @@ class LoginForm {
const NOT_EXISTS = 4;
const WRONG_PASS = 5;
const EMPTY_PASS = 6;
+ const RESET_PASS = 7;
var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted;
var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword;
@@ -122,8 +123,10 @@ class LoginForm {
return;
}
+ // Wipe the initial password and mail a temporary one
+ $u->setPassword( null );
$u->saveSettings();
- $result = $this->mailPasswordInternal($u);
+ $result = $this->mailPasswordInternal( $u, false );
wfRunHooks( 'AddNewAccount', array( $u ) );
@@ -157,12 +160,19 @@ class LoginForm {
global $wgLoginLanguageSelector;
if( $wgLoginLanguageSelector && $this->mLanguage )
$u->setOption( 'language', $this->mLanguage );
-
+
# Save user settings and send out an email authentication message if needed
$u->saveSettings();
- if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) )
- $u->sendConfirmationMail();
-
+ if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) ) {
+ global $wgOut;
+ $error = $u->sendConfirmationMail();
+ if( WikiError::isError( $error ) ) {
+ $wgOut->addWikiText( wfMsg( 'confirmemail_sendfailed', $error->getMessage() ) );
+ } else {
+ $wgOut->addWikiText( wfMsg( 'confirmemail_oncreate' ) );
+ }
+ }
+
# If not logged in, assume the new account as the current one and set session cookies
# then show a "welcome" message or a "need cookies" message as needed
if( $wgUser->isAnon() ) {
@@ -177,8 +187,7 @@ class LoginForm {
} else {
# Confirm that the account was created
global $wgOut;
- $skin = $wgUser->getSkin();
- $self = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
+ $self = SpecialPage::getTitleFor( 'Userlogin' );
$wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) );
$wgOut->setArticleRelated( false );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
@@ -236,8 +245,8 @@ class LoginForm {
}
$name = trim( $this->mName );
- $u = User::newFromName( $name );
- if ( is_null( $u ) || !User::isCreatableName( $u->getName() ) ) {
+ $u = User::newFromName( $name, 'creatable' );
+ if ( is_null( $u ) ) {
$this->mainLoginForm( wfMsg( 'noname' ) );
return false;
}
@@ -257,6 +266,14 @@ class LoginForm {
return false;
}
+ $abortError = '';
+ if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) {
+ // Hook point to add extra creation throttles and blocks
+ wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
+ $this->mainLoginForm( $abortError );
+ return false;
+ }
+
if ( $wgAccountCreationThrottle ) {
$key = wfMemcKey( 'acctcreate', 'ip', $ip );
$value = $wgMemc->incr( $key );
@@ -269,14 +286,6 @@ class LoginForm {
}
}
- $abortError = '';
- if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) {
- // Hook point to add extra creation throttles and blocks
- wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
- $this->mainLoginForm( $abortError );
- return false;
- }
-
if( !$wgAuth->addUser( $u, $this->mPassword ) ) {
$this->mainLoginForm( wfMsg( 'externaldberror' ) );
return false;
@@ -297,7 +306,7 @@ class LoginForm {
* @return User object.
* @private
*/
- function &initUser( &$u ) {
+ function initUser( $u ) {
$u->addToDatabase();
$u->setPassword( $this->mPassword );
$u->setEmail( $this->mEmail );
@@ -308,16 +317,21 @@ class LoginForm {
$wgAuth->initUser( $u );
$u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
+ $u->saveSettings();
return $u;
}
/**
- * @private
+ * Internally authenticate the login request.
+ *
+ * This may create a local account as a side effect if the
+ * authentication plugin allows transparent local account
+ * creation.
+ *
+ * @public
*/
-
- function authenticateUserData()
- {
+ function authenticateUserData() {
global $wgUser, $wgAuth;
if ( '' == $this->mName ) {
return self::NO_NAME;
@@ -335,7 +349,7 @@ class LoginForm {
*/
if ( $wgAuth->autoCreate() && $wgAuth->userExists( $u->getName() ) ) {
if ( $wgAuth->authenticate( $u->getName(), $this->mPassword ) ) {
- $u =& $this->initUser( $u );
+ $u = $this->initUser( $u );
} else {
return self::WRONG_PLUGIN_PASS;
}
@@ -343,14 +357,43 @@ class LoginForm {
return self::NOT_EXISTS;
}
} else {
- $u->loadFromDatabase();
+ $u->load();
}
if (!$u->checkPassword( $this->mPassword )) {
- return '' == $this->mPassword ? self::EMPTY_PASS : self::WRONG_PASS;
- }
- else
- {
+ if( $u->checkTemporaryPassword( $this->mPassword ) ) {
+ // The e-mailed temporary password should not be used
+ // for actual logins; that's a very sloppy habit,
+ // and insecure if an attacker has a few seconds to
+ // click "search" on someone's open mail reader.
+ //
+ // Allow it to be used only to reset the password
+ // a single time to a new value, which won't be in
+ // the user's e-mail archives.
+ //
+ // For backwards compatibility, we'll still recognize
+ // it at the login form to minimize surprises for
+ // people who have been logging in with a temporary
+ // password for some time.
+ //
+ // As a side-effect, we can authenticate the user's
+ // e-mail address if it's not already done, since
+ // the temporary password was sent via e-mail.
+ //
+ if( !$u->isEmailConfirmed() ) {
+ $u->confirmEmail();
+ }
+
+ // At this point we just return an appropriate code
+ // indicating that the UI should show a password
+ // reset form; bot interfaces etc will probably just
+ // fail cleanly here.
+ //
+ return self::RESET_PASS;
+ } else {
+ return '' == $this->mPassword ? self::EMPTY_PASS : self::WRONG_PASS;
+ }
+ } else {
$wgAuth->updateUser( $u );
$wgUser = $u;
@@ -396,23 +439,45 @@ class LoginForm {
case self::EMPTY_PASS:
$this->mainLoginForm( wfMsg( 'wrongpasswordempty' ) );
break;
+ case self::RESET_PASS:
+ $this->resetLoginForm( wfMsg( 'resetpass_announce' ) );
+ break;
default:
wfDebugDieBacktrace( "Unhandled case value" );
}
}
+
+ function resetLoginForm( $error ) {
+ global $wgOut;
+ $wgOut->addWikiText( "<div class=\"errorbox\">$error</div>" );
+ $reset = new PasswordResetForm( $this->mName, $this->mPassword );
+ $reset->execute();
+ }
/**
* @private
*/
function mailPassword() {
- global $wgUser, $wgOut;
+ global $wgUser, $wgOut, $wgAuth;
+
+ if( !$wgAuth->allowPasswordChange() ) {
+ $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) );
+ return;
+ }
+
+ # Check against blocked IPs
+ # fixme -- should we not?
+ if( $wgUser->isBlocked() ) {
+ $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) );
+ return;
+ }
# Check against the rate limiter
if( $wgUser->pingLimiter( 'mailpassword' ) ) {
$wgOut->rateLimited();
return;
}
-
+
if ( '' == $this->mName ) {
$this->mainLoginForm( wfMsg( 'noname' ) );
return;
@@ -427,9 +492,16 @@ class LoginForm {
return;
}
- $u->loadFromDatabase();
+ # Check against password throttle
+ if ( $u->isPasswordReminderThrottled() ) {
+ global $wgPasswordReminderResendTime;
+ # Round the time in hours to 3 d.p., in case someone is specifying minutes or seconds.
+ $this->mainLoginForm( wfMsg( 'throttled-mailpassword',
+ round( $wgPasswordReminderResendTime, 3 ) ) );
+ return;
+ }
- $result = $this->mailPasswordInternal( $u );
+ $result = $this->mailPasswordInternal( $u, true );
if( WikiError::isError( $result ) ) {
$this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) );
} else {
@@ -442,7 +514,7 @@ class LoginForm {
* @return mixed true on success, WikiError on failure
* @private
*/
- function mailPasswordInternal( $u ) {
+ function mailPasswordInternal( $u, $throttle = true ) {
global $wgCookiePath, $wgCookieDomain, $wgCookiePrefix, $wgCookieSecure;
global $wgServer, $wgScript;
@@ -451,7 +523,7 @@ class LoginForm {
}
$np = $u->randomPassword();
- $u->setNewpassword( $np );
+ $u->setNewpassword( $np, $throttle );
setcookie( "{$wgCookiePrefix}Token", '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
@@ -531,6 +603,7 @@ class LoginForm {
function mainLoginForm( $msg, $msgtype = 'error' ) {
global $wgUser, $wgOut, $wgAllowRealName, $wgEnableEmail;
global $wgCookiePrefix, $wgAuth, $wgLoginLanguageSelector;
+ global $wgAuth;
if ( $this->mType == 'signup' ) {
if ( !$wgUser->isAllowed( 'createaccount' ) ) {
@@ -546,11 +619,11 @@ class LoginForm {
if ( $wgUser->isLoggedIn() ) {
$this->mName = $wgUser->getName();
} else {
- $this->mName = @$_COOKIE[$wgCookiePrefix.'UserName'];
+ $this->mName = isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ? $_COOKIE[$wgCookiePrefix.'UserName'] : null;
}
}
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
+ $titleObj = SpecialPage::getTitleFor( 'Userlogin' );
if ( $this->mType == 'signup' ) {
$template = new UsercreateTemplate();
@@ -598,6 +671,7 @@ class LoginForm {
$template->set( 'createemail', $wgEnableEmail && $wgUser->isLoggedIn() );
$template->set( 'userealname', $wgAllowRealName );
$template->set( 'useemail', $wgEnableEmail );
+ $template->set( 'canreset', $wgAuth->allowPasswordChange() );
$template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember );
# Prepare language selection links as needed
@@ -648,7 +722,7 @@ class LoginForm {
function cookieRedirectCheck( $type ) {
global $wgOut;
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
+ $titleObj = SpecialPage::getTitleFor( 'Userlogin' );
$check = $titleObj->getFullURL( 'wpCookieCheck='.$type );
return $wgOut->redirect( $check );
@@ -714,7 +788,7 @@ class LoginForm {
*/
function makeLanguageSelectorLink( $text, $lang ) {
global $wgUser;
- $self = Title::makeTitle( NS_SPECIAL, 'Userlogin' );
+ $self = SpecialPage::getTitleFor( 'Userlogin' );
$attr[] = 'uselang=' . $lang;
if( $this->mType == 'signup' )
$attr[] = 'type=signup';
diff --git a/includes/SpecialUserrights.php b/includes/SpecialUserrights.php
index b17cc4aa..99abd7a7 100644
--- a/includes/SpecialUserrights.php
+++ b/includes/SpecialUserrights.php
@@ -1,11 +1,11 @@
<?php
+
/**
- * Provide an administration interface
- * DO NOT USE: INSECURE.
+ * Special page to allow managing user group membership
*
- * TODO : remove everything related to group editing (SpecialGrouplevels.php)
* @package MediaWiki
- * @subpackage SpecialPage
+ * @subpackage Special pages
+ * @todo This code is disgusting and needs a total rewrite
*/
/** */
@@ -34,7 +34,7 @@ class UserrightsForm extends HTMLForm {
$this->mRequest =& $request;
$this->mName = 'userrights';
- $titleObj = Title::makeTitle( NS_SPECIAL, 'Userrights' );
+ $titleObj = SpecialPage::getTitleFor( 'Userrights' );
$this->action = $titleObj->escapeLocalURL();
}
@@ -89,7 +89,6 @@ class UserrightsForm extends HTMLForm {
$oldGroups = $u->getGroups();
$newGroups = $oldGroups;
- $logcomment = ' ';
// remove then add groups
if(isset($removegroup)) {
$newGroups = array_diff($newGroups, $removegroup);
@@ -119,22 +118,18 @@ class UserrightsForm extends HTMLForm {
}
/**
- * The entry form
- * It allows a user to look for a username and edit its groups membership
+ * Output a form to allow searching for a user
*/
function switchForm() {
- global $wgOut;
-
- // user selection
- $wgOut->addHTML( "<form name=\"uluser\" action=\"$this->action\" method=\"post\">\n" );
- $wgOut->addHTML( $this->fieldset( 'lookup-user',
- $this->textbox( 'user-editname' ) .
- wfElement( 'input', array(
- 'type' => 'submit',
- 'name' => 'ssearchuser',
- 'value' => wfMsg( 'editusergroup' ) ) )
- ));
- $wgOut->addHTML( "</form>\n" );
+ global $wgOut, $wgRequest;
+ $username = $wgRequest->getText( 'user-editname' );
+ $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->action, 'name' => 'uluser' ) );
+ $form .= '<fieldset><legend>' . wfMsgHtml( 'userrights-lookup-user' ) . '</legend>';
+ $form .= '<p>' . Xml::inputLabel( wfMsg( 'userrights-user-editname' ), 'user-editname', 'username', 30, $username ) . '</p>';
+ $form .= '<p>' . Xml::submitButton( wfMsg( 'editusergroup' ), array( 'name' => 'ssearchuser' ) ) . '</p>';
+ $form .= '</fieldset>';
+ $form .= '</form>';
+ $wgOut->addHTML( $form );
}
/**
diff --git a/includes/SpecialVersion.php b/includes/SpecialVersion.php
index 8744597a..dba694c0 100644
--- a/includes/SpecialVersion.php
+++ b/includes/SpecialVersion.php
@@ -21,6 +21,8 @@ function wfSpecialVersion() {
}
class SpecialVersion {
+ private $firstExtOpened = true;
+
/**
* main()
*/
@@ -42,16 +44,21 @@ class SpecialVersion {
*/
/**
+ * Return wiki text showing the licence information and third party
+ * software versions (apache, php, mysql).
* @static
*/
function MediaWikiCredits() {
$version = self::getVersion();
$dbr =& wfGetDB( DB_SLAVE );
+ global $wgLanguageNames, $wgLanguageCode;
+ $mwlang = $wgLanguageNames[$wgLanguageCode];
+
$ret =
"__NOTOC__
This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''',
- copyright (C) 2001-2006 Magnus Manske, Brion Vibber, Lee Daniel Crocker,
+ copyright (C) 2001-2007 Magnus Manske, Brion Vibber, Lee Daniel Crocker,
Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason,
Niklas Laxström, Domas Mituzas, Rob Church and others.
@@ -74,15 +81,17 @@ class SpecialVersion {
* [http://www.php.net/ PHP]: " . phpversion() . " (" . php_sapi_name() . ")
* " . $dbr->getSoftwareLink() . ": " . $dbr->getServerVersion();
- return str_replace( "\t\t", '', $ret );
+ return str_replace( "\t\t", '', $ret ) . "\n";
}
-
+
+ /** Return a string of the MediaWiki version with SVN revision if available */
public static function getVersion() {
global $wgVersion, $IP;
$svn = self::getSvnRevision( $IP );
return $svn ? "$wgVersion (r$svn)" : $wgVersion;
}
+ /** Generate wikitext showing extensions name, URL, author and description */
function extensionCredits() {
global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunction;
@@ -97,10 +106,12 @@ class SpecialVersion {
);
wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
- $out = "\n* Extensions:\n";
+ $out = "<h2>Extensions</h2>\n";
+ $out .= wfOpenElement('table', array('id' => 'sv-ext') );
+
foreach ( $extensionTypes as $type => $text ) {
if ( count( @$wgExtensionCredits[$type] ) ) {
- $out .= "** $text:\n";
+ $out .= $this->openExtType( $text );
usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
@@ -119,30 +130,31 @@ class SpecialVersion {
}
if ( count( $wgExtensionFunctions ) ) {
- $out .= "** Extension functions:\n";
- $out .= '***' . $this->listToText( $wgExtensionFunctions ) . "\n";
+ $out .= $this->openExtType('Extension functions');
+ $out .= '<tr><td colspan="3">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
}
if ( $cnt = count( $tags = $wgParser->getTags() ) ) {
for ( $i = 0; $i < $cnt; ++$i )
$tags[$i] = "&lt;{$tags[$i]}&gt;";
- $out .= "** Parser extension tags:\n";
- $out .= '***' . $this->listToText( $tags ). "\n";
+ $out .= $this->openExtType('Parser extension tags');
+ $out .= '<tr><td colspan="3">' . $this->listToText( $tags ). "</td></tr>\n";
}
-
+
if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) {
- $out .= "** Parser function hooks:\n";
- $out .= '***' . $this->listToText( $fhooks ) . "\n";
+ $out .= $this->openExtType('Parser function hooks');
+ $out .= '<tr><td colspan="3">' . $this->listToText( $fhooks ) . "</td></tr>\n";
}
if ( count( $wgSkinExtensionFunction ) ) {
- $out .= "** Skin extension functions:\n";
- $out .= '***' . $this->listToText( $wgSkinExtensionFunction ) . "\n";
+ $out .= $this->openExtType('Skin extension functions');
+ $out .= '<tr><td colspan="3">' . $this->listToText( $wgSkinExtensionFunction ) . "</td></tr>\n";
}
-
+ $out .= wfCloseElement( 'table' );
return $out;
}
+ /** Callback to sort extensions by type */
function compare( $a, $b ) {
if ( $a['name'] === $b['name'] )
return 0;
@@ -151,7 +163,7 @@ class SpecialVersion {
}
function formatCredits( $name, $version = null, $author = null, $url = null, $description = null) {
- $ret = '*** ';
+ $ret = '<tr><td>';
if ( isset( $url ) )
$ret .= "[$url ";
$ret .= "''$name";
@@ -160,13 +172,10 @@ class SpecialVersion {
$ret .= "''";
if ( isset( $url ) )
$ret .= ']';
- if ( isset( $description ) )
- $ret .= ', ' . $description;
- if ( isset( $description ) && isset( $author ) )
- $ret .= ', ';
- if ( isset( $author ) )
- $ret .= ' by ' . $this->listToText( (array)$author );
-
+ $ret .= '</td>';
+ $ret .= "<td>$description</td>";
+ $ret .= "<td>" . $this->listToText( (array)$author ) . "</td>";
+ $ret .= '</tr>';
return "$ret\n";
}
@@ -179,16 +188,36 @@ class SpecialVersion {
if ( count( $wgHooks ) ) {
$myWgHooks = $wgHooks;
ksort( $myWgHooks );
-
- $ret = "* Hooks:\n";
+
+ $ret = "<h2>Hooks</h2>\n"
+ . wfOpenElement('table', array('id' => 'sv-hooks') )
+ . "<tr><th>Hook name</th><th>Subscribed by</th></tr>\n";
+
foreach ($myWgHooks as $hook => $hooks)
- $ret .= "** $hook: " . $this->listToText( $hooks ) . "\n";
+ $ret .= "<tr><td>$hook</td><td>" . $this->listToText( $hooks ) . "</td></tr>\n";
+ $ret .= '</table>';
return $ret;
} else
return '';
}
+ private function openExtType($text, $name = null) {
+ $opt = array( 'colspan' => 3 );
+ $out = '';
+
+ if(!$this->firstExtOpened) {
+ // Insert a spacing line
+ $out .= '<tr class="sv-space">' . wfElement( 'td', $opt ) . "</tr>\n";
+ }
+ $this->firstExtOpened = false;
+
+ if($name) { $opt['id'] = "sv-$name"; }
+
+ $out .= "<tr>" . wfElement( 'th', $opt, $text) . "</tr>\n";
+ return $out;
+ }
+
/**
* @static
*
@@ -207,14 +236,16 @@ class SpecialVersion {
function listToText( $list ) {
$cnt = count( $list );
- if ( $cnt == 1 )
+ if ( $cnt == 1 ) {
// Enforce always returning a string
return (string)$this->arrayToString( $list[0] );
- else {
+ } elseif ( $cnt == 0 ) {
+ return '';
+ } else {
$t = array_slice( $list, 0, $cnt - 1 );
$one = array_map( array( &$this, 'arrayToString' ), $t );
$two = $this->arrayToString( $list[$cnt - 1] );
-
+
return implode( ', ', $one ) . " and $two";
}
}
@@ -227,9 +258,9 @@ class SpecialVersion {
* @return mixed
*/
function arrayToString( $list ) {
- if ( ! is_array( $list ) )
+ if ( ! is_array( $list ) ) {
return $list;
- else {
+ } else {
$class = get_class( $list[0] );
return "($class, {$list[1]})";
}
@@ -237,7 +268,7 @@ class SpecialVersion {
/**
* Retrieve the revision number of a Subversion working directory.
- *
+ *
* @bug 7335
*
* @param string $dir
@@ -281,8 +312,6 @@ class SpecialVersion {
// subversion is release 1.4
return intval( $content[3] );
}
-
- return false;
}
/**#@-*/
diff --git a/includes/SpecialWantedcategories.php b/includes/SpecialWantedcategories.php
index 97bb0a26..05ee7ec0 100644
--- a/includes/SpecialWantedcategories.php
+++ b/includes/SpecialWantedcategories.php
@@ -22,7 +22,7 @@ class WantedCategoriesPage extends QueryPage {
function getSQL() {
$dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'categorylinks', 'page' ) );
+ list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' );
$name = $dbr->addQuotes( $this->getName() );
return
"
diff --git a/includes/SpecialWantedpages.php b/includes/SpecialWantedpages.php
index 7b070604..8e5cee3e 100644
--- a/includes/SpecialWantedpages.php
+++ b/includes/SpecialWantedpages.php
@@ -103,7 +103,7 @@ class WantedPagesPage extends QueryPage {
* @return string
*/
function makeWlhLink( &$title, &$skin, $text ) {
- $wlhTitle = Title::makeTitle( NS_SPECIAL, 'Whatlinkshere' );
+ $wlhTitle = SpecialPage::getTitleFor( 'Whatlinkshere' );
return $skin->makeKnownLinkObj( $wlhTitle, $text, 'target=' . $title->getPrefixedUrl() );
}
diff --git a/includes/SpecialWatchlist.php b/includes/SpecialWatchlist.php
index 87c925ac..33e19a2b 100644
--- a/includes/SpecialWatchlist.php
+++ b/includes/SpecialWatchlist.php
@@ -12,24 +12,23 @@ require_once( 'SpecialRecentchanges.php' );
/**
* Constructor
- * @todo Document $par parameter.
- * @param $par String: FIXME
+ *
+ * @param $par Parameter passed to the page
*/
function wfSpecialWatchlist( $par ) {
global $wgUser, $wgOut, $wgLang, $wgMemc, $wgRequest, $wgContLang;
- global $wgUseWatchlistCache, $wgWLCacheTimeout;
global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker;
global $wgEnotifWatchlist;
$fname = 'wfSpecialWatchlist';
$skin =& $wgUser->getSkin();
- $specialTitle = Title::makeTitle( NS_SPECIAL, 'Watchlist' );
+ $specialTitle = SpecialPage::getTitleFor( 'Watchlist' );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
# Anons don't get a watchlist
if( $wgUser->isAnon() ) {
$wgOut->setPageTitle( wfMsg( 'watchnologin' ) );
- $llink = $skin->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Userlogin' ), wfMsgHtml( 'loginreqlink' ), 'returnto=' . $specialTitle->getPrefixedUrl() );
+ $llink = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Userlogin' ), wfMsgHtml( 'loginreqlink' ), 'returnto=' . $specialTitle->getPrefixedUrl() );
$wgOut->addHtml( wfMsgWikiHtml( 'watchlistanontext', $llink ) );
return;
} else {
@@ -45,6 +44,7 @@ function wfSpecialWatchlist( $par ) {
/* float */ 'days' => floatval( $wgUser->getOption( 'watchlistdays' ) ), /* 3.0 or 0.5, watch further below */
/* bool */ 'hideOwn' => (int)$wgUser->getBoolOption( 'watchlisthideown' ),
/* bool */ 'hideBots' => (int)$wgUser->getBoolOption( 'watchlisthidebots' ),
+ /* bool */ 'hideMinor' => (int)$wgUser->getBoolOption( 'watchlisthideminor' ),
/* ? */ 'namespace' => 'all',
);
@@ -55,12 +55,14 @@ function wfSpecialWatchlist( $par ) {
$prefs['days' ] = floatval( $wgUser->getOption( 'watchlistdays' ) );
$prefs['hideown' ] = $wgUser->getBoolOption( 'watchlisthideown' );
$prefs['hidebots'] = $wgUser->getBoolOption( 'watchlisthidebots' );
+ $prefs['hideminor'] = $wgUser->getBoolOption( 'watchlisthideminor' );
# Get query variables
- $days = $wgRequest->getVal( 'days', $prefs['days'] );
- $hideOwn = $wgRequest->getBool( 'hideOwn', $prefs['hideown'] );
+ $days = $wgRequest->getVal( 'days', $prefs['days'] );
+ $hideOwn = $wgRequest->getBool( 'hideOwn', $prefs['hideown'] );
$hideBots = $wgRequest->getBool( 'hideBots', $prefs['hidebots'] );
-
+ $hideMinor = $wgRequest->getBool( 'hideMinor', $prefs['hideminor'] );
+
# Get namespace value, if supplied, and prepare a WHERE fragment
$nameSpace = $wgRequest->getIntOrNull( 'namespace' );
if( !is_null( $nameSpace ) ) {
@@ -74,14 +76,14 @@ function wfSpecialWatchlist( $par ) {
# Watchlist editing
$action = $wgRequest->getVal( 'action' );
$remove = $wgRequest->getVal( 'remove' );
- $id = $wgRequest->getArray( 'id' );
+ $id = $wgRequest->getArray( 'id' );
$uid = $wgUser->getID();
if( $wgEnotifWatchlist && $wgRequest->getVal( 'reset' ) && $wgRequest->wasPosted() ) {
$wgUser->clearAllNotifications( $uid );
}
- # Deleting items from watchlist
+ # Deleting items from watchlist
if(($action == 'submit') && isset($remove) && is_array($id)) {
$wgOut->addWikiText( wfMsg( 'removingchecked' ) );
$wgOut->addHTML( '<p>' );
@@ -102,23 +104,13 @@ function wfSpecialWatchlist( $par ) {
$wgOut->addHTML( "</p>\n<p>" . wfMsg( 'wldone' ) . "</p>\n" );
}
- if ( $wgUseWatchlistCache ) {
- $memckey = wfMemcKey( 'watchlist', 'id', $wgUser->getId() );
- $cache_s = @$wgMemc->get( $memckey );
- if( $cache_s ){
- $wgOut->addWikiText( wfMsg('wlsaved') );
- $wgOut->addHTML( $cache_s );
- return;
- }
- }
-
- $dbr =& wfGetDB( DB_SLAVE );
- extract( $dbr->tableNames( 'page', 'revision', 'watchlist', 'recentchanges' ) );
+ $dbr =& wfGetDB( DB_SLAVE, 'watchlist' );
+ list( $page, $watchlist, $recentchanges ) = $dbr->tableNamesN( 'page', 'watchlist', 'recentchanges' );
$sql = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_user=$uid";
$res = $dbr->query( $sql, $fname );
$s = $dbr->fetchObject( $res );
-
+
# Patch *** A1 *** (see A2 below)
# adjust for page X, talk:page X, which are both stored separately, but treated together
$nitems = floor($s->n / 2);
@@ -144,27 +136,24 @@ function wfSpecialWatchlist( $par ) {
// Dump everything here
$nondefaults = array();
- wfAppendToArrayIfNotDefault( 'days', $days, $defaults, $nondefaults);
- wfAppendToArrayIfNotDefault( 'hideOwn', (int)$hideOwn, $defaults, $nondefaults);
- wfAppendToArrayIfNotDefault( 'hideBots', (int)$hideBots, $defaults, $nondefaults);
- wfAppendToArrayIfNotDefault( 'namespace', $nameSpace, $defaults, $nondefaults );
+ wfAppendToArrayIfNotDefault('days' , $days , $defaults, $nondefaults);
+ wfAppendToArrayIfNotDefault('hideOwn' , (int)$hideOwn , $defaults, $nondefaults);
+ wfAppendToArrayIfNotDefault('hideBots' , (int)$hideBots, $defaults, $nondefaults);
+ wfAppendToArrayIfNotDefault( 'hideMinor', (int)$hideMinor, $defaults, $nondefaults );
+ wfAppendToArrayIfNotDefault('namespace', $nameSpace , $defaults, $nondefaults);
if ( $days <= 0 ) {
- $docutoff = '';
- $cutoff = false;
+ $andcutoff = '';
$npages = wfMsg( 'watchlistall1' );
} else {
- $docutoff = "AND rev_timestamp > '" .
- ( $cutoff = $dbr->timestamp( time() - intval( $days * 86400 ) ) )
- . "'";
- /*
- $sql = "SELECT COUNT(*) AS n FROM $page, $revision WHERE rev_timestamp>'$cutoff' AND page_id=rev_page";
- $res = $dbr->query( $sql, $fname );
- $s = $dbr->fetchObject( $res );
- $npages = $s->n;
- */
- $npages = 40000 * $days;
-
+ $andcutoff = "AND rc_timestamp > '".$dbr->timestamp( time() - intval( $days * 86400 ) )."'";
+ /*
+ $sql = "SELECT COUNT(*) AS n FROM $page, $revision WHERE rev_timestamp>'$cutoff' AND page_id=rev_page";
+ $res = $dbr->query( $sql, $fname );
+ $s = $dbr->fetchObject( $res );
+ $npages = $s->n;
+ */
+ $npages = 40000 * $days;
}
/* Edit watchlist form */
@@ -182,15 +171,16 @@ function wfSpecialWatchlist( $par ) {
$sql = "SELECT wl_namespace, wl_title, page_is_redirect FROM $watchlist LEFT JOIN $page ON wl_namespace = page_namespace AND wl_title = page_title WHERE wl_user=$uid";
$res = $dbr->query( $sql, $fname );
-
+
# Batch existence check
$linkBatch = new LinkBatch();
while( $row = $dbr->fetchObject( $res ) )
$linkBatch->addObj( Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ) );
$linkBatch->execute();
+
if( $dbr->numRows( $res ) > 0 )
$dbr->dataSeek( $res, 0 ); # Let's do the time warp again!
-
+
$sk = $wgUser->getSkin();
$list = array();
@@ -215,7 +205,6 @@ function wfSpecialWatchlist( $par ) {
} else {
global $wgContLang;
$toolLinks = array();
- $titleText = $titleObj->getPrefixedText();
$pageLink = $sk->makeLinkObj( $titleObj );
$toolLinks[] = $sk->makeLinkObj( $titleObj->getTalkPage(), $wgLang->getNsText( NS_TALK ) );
if( $titleObj->exists() )
@@ -228,7 +217,7 @@ function wfSpecialWatchlist( $par ) {
} else {
$spanopen = $spanclosed = '';
}
-
+
$wgOut->addHTML( "<li>{$checkbox}{$spanopen}{$pageLink}{$spanclosed} {$toolLinks}</li>\n" );
}
}
@@ -253,6 +242,7 @@ function wfSpecialWatchlist( $par ) {
# Toggles
$andHideOwn = $hideOwn ? "AND (rc_user <> $uid)" : '';
$andHideBots = $hideBots ? "AND (rc_bot = 0)" : '';
+ $andHideMinor = $hideMinor ? 'AND rc_minor = 0' : '';
# Show watchlist header
$header = '';
@@ -265,7 +255,7 @@ function wfSpecialWatchlist( $par ) {
# Toggle watchlist content (all recent edits or just the latest)
if( $wgUser->getOption( 'extendwatchlist' )) {
- $andLatest='';
+ $andLatest='';
$limitWatchlist = 'LIMIT ' . intval( $wgUser->getOption( 'wllimit' ) );
} else {
$andLatest= 'AND rc_this_oldid=page_latest';
@@ -287,23 +277,17 @@ function wfSpecialWatchlist( $par ) {
"\n\n" );
}
- $sql = "SELECT
- rc_namespace AS page_namespace, rc_title AS page_title,
- rc_comment AS rev_comment, rc_cur_id AS page_id,
- rc_user AS rev_user, rc_user_text AS rev_user_text,
- rc_timestamp AS rev_timestamp, rc_minor AS rev_minor_edit,
- rc_this_oldid AS rev_id,
- rc_last_oldid, rc_id, rc_patrolled,
- rc_new AS page_is_new,wl_notificationtimestamp
+ $sql = "SELECT *
FROM $watchlist,$recentchanges,$page
WHERE wl_user=$uid
AND wl_namespace=rc_namespace
AND wl_title=rc_title
- AND rc_timestamp > '$cutoff'
AND rc_cur_id=page_id
+ $andcutoff
$andLatest
$andHideOwn
$andHideBots
+ $andHideMinor
$nameSpaceClause
ORDER BY rc_timestamp DESC
$limitWatchlist";
@@ -325,40 +309,45 @@ function wfSpecialWatchlist( $par ) {
$wgOut->addHTML( "\n" . wlCutoffLinks( $days, 'Watchlist', $nondefaults ) . "<br />\n" );
# Spit out some control panel links
- $thisTitle = Title::makeTitle( NS_SPECIAL, 'Watchlist' );
+ $thisTitle = SpecialPage::getTitleFor( 'Watchlist' );
$skin = $wgUser->getSkin();
- $linkElements = array( 'hideOwn' => 'wlhideshowown', 'hideBots' => 'wlhideshowbots' );
-
- # Problems encountered using the fancier method
- $label = $hideBots ? wfMsgHtml( 'show' ) : wfMsgHtml( 'hide' );
- $linkBits = wfArrayToCGI( array( 'hideBots' => 1 - (int)$hideBots ), $nondefaults );
- $link = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits );
- $links[] = wfMsgHtml( 'wlhideshowbots', $link );
- $label = $hideOwn ? wfMsgHtml( 'show' ) : wfMsgHtml( 'hide' );
+ # Hide/show bot edits
+ $label = $hideBots ? wfMsgHtml( 'watchlist-show-bots' ) : wfMsgHtml( 'watchlist-hide-bots' );
+ $linkBits = wfArrayToCGI( array( 'hideBots' => 1 - (int)$hideBots ), $nondefaults );
+ $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits );
+
+ # Hide/show own edits
+ $label = $hideOwn ? wfMsgHtml( 'watchlist-show-own' ) : wfMsgHtml( 'watchlist-hide-own' );
$linkBits = wfArrayToCGI( array( 'hideOwn' => 1 - (int)$hideOwn ), $nondefaults );
- $link = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits );
- $links[] = wfMsgHtml( 'wlhideshowown', $link );
+ $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits );
+
+ # Hide/show minor edits
+ $label = $hideMinor ? wfMsgHtml( 'watchlist-show-minor' ) : wfMsgHtml( 'watchlist-hide-minor' );
+ $linkBits = wfArrayToCGI( array( 'hideMinor' => 1 - (int)$hideMinor ), $nondefaults );
+ $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits );
$wgOut->addHTML( implode( ' | ', $links ) );
# Form for namespace filtering
- $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 );
+ $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) );
+ $form .= '<p>';
+ $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . '&nbsp;';
+ $form .= Xml::namespaceSelector( $nameSpace, '' ) . '&nbsp;';
+ $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '</p>';
+ $form .= Xml::hidden( 'days', $days );
+ if( $hideOwn )
+ $form .= Xml::hidden( 'hideOwn', 1 );
+ if( $hideBots )
+ $form .= Xml::hidden( 'hideBots', 1 );
+ if( $hideMinor )
+ $form .= Xml::hidden( 'hideMinor', 1 );
+ $form .= Xml::closeElement( 'form' );
+ $wgOut->addHtml( $form );
+
+ # If there's nothing to show, stop here
+ if( $numRows == 0 ) {
+ $wgOut->addWikiText( wfMsgNoTrans( 'watchnochange' ) );
return;
}
@@ -369,8 +358,8 @@ function wfSpecialWatchlist( $par ) {
$s = $list->beginRecentChangesList();
$counter = 1;
while ( $obj = $dbr->fetchObject( $res ) ) {
- # Make fake RC entry
- $rc = RecentChange::newFromCurRow( $obj, $obj->rc_last_oldid );
+ # Make RC entry
+ $rc = RecentChange::newFromRow( $obj );
$rc->counter = $counter++;
if ( $wgShowUpdatedMarker ) {
@@ -381,8 +370,8 @@ function wfSpecialWatchlist( $par ) {
}
if ($wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) {
- $sql3 = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_title='" .wfStrencode($obj->page_title). "' AND wl_namespace='{$obj->page_namespace}'" ;
- $res3 = $dbr->query( $sql3, DB_READ, $fname );
+ $sql3 = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_title='" .$dbr->strencode($obj->page_title). "' AND wl_namespace='{$obj->page_namespace}'" ;
+ $res3 = $dbr->query( $sql3, $fname );
$x = $dbr->fetchObject( $res3 );
$rc->numberofWatchingusers = $x->n;
} else {
@@ -396,10 +385,6 @@ function wfSpecialWatchlist( $par ) {
$dbr->freeResult( $res );
$wgOut->addHTML( $s );
- if ( $wgUseWatchlistCache ) {
- $wgMemc->set( $memckey, $s, $wgWLCacheTimeout);
- }
-
}
function wlHoursLink( $h, $page, $options = array() ) {
@@ -428,7 +413,6 @@ function wlDaysLink( $d, $page, $options = array() ) {
function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) {
$hours = array( 1, 2, 6, 12 );
$days = array( 1, 3, 7 );
- $cl = '';
$i = 0;
foreach( $hours as $h ) {
$hours[$i++] = wlHoursLink( $h, $page, $options );
@@ -451,19 +435,19 @@ function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) {
* @return integer
*/
function wlCountItems( &$user, $talk = true ) {
- $dbr =& wfGetDB( DB_SLAVE );
-
+ $dbr =& wfGetDB( DB_SLAVE, 'watchlist' );
+
# Fetch the raw count
$res = $dbr->select( 'watchlist', 'COUNT(*) AS count', array( 'wl_user' => $user->mId ), 'wlCountItems' );
$row = $dbr->fetchObject( $res );
$count = $row->count;
$dbr->freeResult( $res );
-
+
# Halve to remove talk pages if needed
if( !$talk )
$count = floor( $count / 2 );
-
- return( $count );
+
+ return( $count );
}
/**
@@ -493,7 +477,7 @@ function wlHandleClear( &$out, &$request, $par ) {
$out->returnToMain();
} else {
# Confirming, so show a form
- $wlTitle = Title::makeTitle( NS_SPECIAL, 'Watchlist' );
+ $wlTitle = SpecialPage::getTitleFor( 'Watchlist' );
$out->addHTML( wfElement( 'form', array( 'method' => 'post', 'action' => $wlTitle->getLocalUrl( 'action=clear' ) ), NULL ) );
$out->addWikiText( wfMsgExt( 'watchlistcount', array( 'parsemag', 'escape'), $wgLang->formatNum( $count ) ) );
$out->addWikiText( wfMsg( 'watchlistcleartext' ) );
diff --git a/includes/SpecialWhatlinkshere.php b/includes/SpecialWhatlinkshere.php
index a95530fe..bed783f8 100644
--- a/includes/SpecialWhatlinkshere.php
+++ b/includes/SpecialWhatlinkshere.php
@@ -57,8 +57,6 @@ class WhatLinksHerePage {
$wgOut->setPagetitle( $this->target->getPrefixedText() );
$wgOut->setSubtitle( wfMsg( 'linklistsub' ) );
- $isredir = ' (' . wfMsg( 'isredirect' ) . ")\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 );
@@ -78,8 +76,6 @@ class WhatLinksHerePage {
$dbr =& wfGetDB( DB_READ );
- extract( $dbr->tableNames( 'pagelinks', 'templatelinks', 'page' ) );
-
// Some extra validation
$from = intval( $from );
if ( !$from && $dir == 'prev' ) {
diff --git a/includes/SquidUpdate.php b/includes/SquidUpdate.php
index 37d97e01..2e2a4a5d 100644
--- a/includes/SquidUpdate.php
+++ b/includes/SquidUpdate.php
@@ -29,8 +29,6 @@ class SquidUpdate {
wfProfileIn( $fname );
# Get a list of URLs linking to this page
- $id = $title->getArticleID();
-
$dbr =& wfGetDB( DB_SLAVE );
$res = $dbr->select( array( 'links', 'page' ),
array( 'page_namespace', 'page_title' ),
@@ -201,9 +199,11 @@ class SquidUpdate {
$htcpOpCLR = 4; // HTCP CLR
// FIXME PHP doesn't support these socket constants (include/linux/in.h)
- define( "IPPROTO_IP", 0 );
- define( "IP_MULTICAST_LOOP", 34 );
- define( "IP_MULTICAST_TTL", 33 );
+ if( !defined( "IPPROTO_IP" ) ) {
+ define( "IPPROTO_IP", 0 );
+ define( "IP_MULTICAST_LOOP", 34 );
+ define( "IP_MULTICAST_TTL", 33 );
+ }
// pfsockopen doesn't work because we need set_sock_opt
$conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
@@ -215,6 +215,9 @@ class SquidUpdate {
$wgHTCPMulticastTTL );
foreach ( $urlArr as $url ) {
+ if( !is_string( $url ) ) {
+ wfDebugDieBacktrace( 'Bad purge URL' );
+ }
$url = SquidUpdate::expand( $url );
// Construct a minimal HTCP request diagram
@@ -223,7 +226,7 @@ class SquidUpdate {
$htcpTransID = rand();
$htcpSpecifier = pack( 'na4na*na8n',
- 4, 'NONE', strlen( $url ), $url,
+ 4, 'HEAD', strlen( $url ), $url,
8, 'HTTP/1.0', 0 );
$htcpDataLen = 8 + 2 + strlen( $htcpSpecifier );
diff --git a/includes/StreamFile.php b/includes/StreamFile.php
index 81538a84..949422d6 100644
--- a/includes/StreamFile.php
+++ b/includes/StreamFile.php
@@ -6,25 +6,23 @@ function wfStreamFile( $fname ) {
$stat = @stat( $fname );
if ( !$stat ) {
header( 'HTTP/1.0 404 Not Found' );
+ header( 'Cache-Control: no-cache' );
+ header( 'Content-Type: text/html' );
$encFile = htmlspecialchars( $fname );
$encScript = htmlspecialchars( $_SERVER['SCRIPT_NAME'] );
echo "<html><body>
<h1>File not found</h1>
<p>Although this PHP script ($encScript) exists, the file requested for output
($encFile) does not.</p>
-</body></html>";
+</body></html>
+";
return;
}
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $stat['mtime'] ) . ' GMT' );
// Cancel output buffering and gzipping if set
- while( $status = ob_get_status() ) {
- ob_end_clean();
- if( $status['name'] == 'ob_gzhandler' ) {
- header( 'Content-Encoding:' );
- }
- }
+ wfResetOutputBuffers();
$type = wfGetType( $fname );
if ( $type and $type!="unknown/unknown") {
diff --git a/includes/StringUtils.php b/includes/StringUtils.php
new file mode 100644
index 00000000..0090604d
--- /dev/null
+++ b/includes/StringUtils.php
@@ -0,0 +1,301 @@
+<?php
+
+class StringUtils {
+ /**
+ * Perform an operation equivalent to
+ *
+ * preg_replace( "!$startDelim(.*?)$endDelim!", $replace, $subject );
+ *
+ * except that it's worst-case O(N) instead of O(N^2)
+ *
+ * Compared to delimiterReplace(), this implementation is fast but memory-
+ * hungry and inflexible. The memory requirements are such that I don't
+ * recommend using it on anything but guaranteed small chunks of text.
+ */
+ static function hungryDelimiterReplace( $startDelim, $endDelim, $replace, $subject ) {
+ $segments = explode( $startDelim, $subject );
+ $output = array_shift( $segments );
+ foreach ( $segments as $s ) {
+ $endDelimPos = strpos( $s, $endDelim );
+ if ( $endDelimPos === false ) {
+ $output .= $startDelim . $s;
+ } else {
+ $output .= $replace . substr( $s, $endDelimPos + strlen( $endDelim ) );
+ }
+ }
+ return $output;
+ }
+
+ /**
+ * Perform an operation equivalent to
+ *
+ * preg_replace_callback( "!$startDelim(.*)$endDelim!s$flags", $callback, $subject )
+ *
+ * This implementation is slower than hungryDelimiterReplace but uses far less
+ * memory. The delimiters are literal strings, not regular expressions.
+ *
+ * @param string $flags Regular expression flags
+ */
+ # If the start delimiter ends with an initial substring of the end delimiter,
+ # e.g. in the case of C-style comments, the behaviour differs from the model
+ # regex. In this implementation, the end must share no characters with the
+ # start, so e.g. /*/ is not considered to be both the start and end of a
+ # comment. /*/xy/*/ is considered to be a single comment with contents /xy/.
+ static function delimiterReplaceCallback( $startDelim, $endDelim, $callback, $subject, $flags = '' ) {
+ $inputPos = 0;
+ $outputPos = 0;
+ $output = '';
+ $foundStart = false;
+ $encStart = preg_quote( $startDelim, '!' );
+ $encEnd = preg_quote( $endDelim, '!' );
+ $strcmp = strpos( $flags, 'i' ) === false ? 'strcmp' : 'strcasecmp';
+ $endLength = strlen( $endDelim );
+ $m = array();
+
+ while ( $inputPos < strlen( $subject ) &&
+ preg_match( "!($encStart)|($encEnd)!S$flags", $subject, $m, PREG_OFFSET_CAPTURE, $inputPos ) )
+ {
+ $tokenOffset = $m[0][1];
+ if ( $m[1][0] != '' ) {
+ if ( $foundStart &&
+ $strcmp( $endDelim, substr( $subject, $tokenOffset, $endLength ) ) == 0 )
+ {
+ # An end match is present at the same location
+ $tokenType = 'end';
+ $tokenLength = $endLength;
+ } else {
+ $tokenType = 'start';
+ $tokenLength = strlen( $m[0][0] );
+ }
+ } elseif ( $m[2][0] != '' ) {
+ $tokenType = 'end';
+ $tokenLength = strlen( $m[0][0] );
+ } else {
+ throw new MWException( 'Invalid delimiter given to ' . __METHOD__ );
+ }
+
+ if ( $tokenType == 'start' ) {
+ $inputPos = $tokenOffset + $tokenLength;
+ # Only move the start position if we haven't already found a start
+ # This means that START START END matches outer pair
+ if ( !$foundStart ) {
+ # Found start
+ # Write out the non-matching section
+ $output .= substr( $subject, $outputPos, $tokenOffset - $outputPos );
+ $outputPos = $tokenOffset;
+ $contentPos = $inputPos;
+ $foundStart = true;
+ }
+ } elseif ( $tokenType == 'end' ) {
+ if ( $foundStart ) {
+ # Found match
+ $output .= call_user_func( $callback, array(
+ substr( $subject, $outputPos, $tokenOffset + $tokenLength - $outputPos ),
+ substr( $subject, $contentPos, $tokenOffset - $contentPos )
+ ));
+ $foundStart = false;
+ } else {
+ # Non-matching end, write it out
+ $output .= substr( $subject, $inputPos, $tokenOffset + $tokenLength - $outputPos );
+ }
+ $inputPos = $outputPos = $tokenOffset + $tokenLength;
+ } else {
+ throw new MWException( 'Invalid delimiter given to ' . __METHOD__ );
+ }
+ }
+ if ( $outputPos < strlen( $subject ) ) {
+ $output .= substr( $subject, $outputPos );
+ }
+ return $output;
+ }
+
+ /*
+ * Perform an operation equivalent to
+ *
+ * preg_replace( "!$startDelim(.*)$endDelim!$flags", $replace, $subject )
+ *
+ * @param string $startDelim Start delimiter regular expression
+ * @param string $endDelim End delimiter regular expression
+ * @param string $replace Replacement string. May contain $1, which will be
+ * replaced by the text between the delimiters
+ * @param string $subject String to search
+ * @return string The string with the matches replaced
+ */
+ static function delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags = '' ) {
+ $replacer = new RegexlikeReplacer( $replace );
+ return self::delimiterReplaceCallback( $startDelim, $endDelim,
+ $replacer->cb(), $subject, $flags );
+ }
+
+ /**
+ * More or less "markup-safe" explode()
+ * Ignores any instances of the separator inside <...>
+ * @param string $separator
+ * @param string $text
+ * @return array
+ */
+ static function explodeMarkup( $separator, $text ) {
+ $placeholder = "\x00";
+
+ // Remove placeholder instances
+ $text = str_replace( $placeholder, '', $text );
+
+ // Replace instances of the separator inside HTML-like tags with the placeholder
+ $replacer = new DoubleReplacer( $separator, $placeholder );
+ $cleaned = StringUtils::delimiterReplaceCallback( '<', '>', $replacer->cb(), $text );
+
+ // Explode, then put the replaced separators back in
+ $items = explode( $separator, $cleaned );
+ foreach( $items as $i => $str ) {
+ $items[$i] = str_replace( $placeholder, $separator, $str );
+ }
+
+ return $items;
+ }
+
+ /**
+ * Escape a string to make it suitable for inclusion in a preg_replace()
+ * replacement parameter.
+ *
+ * @param string $string
+ * @return string
+ */
+ static function escapeRegexReplacement( $string ) {
+ $string = str_replace( '\\', '\\\\', $string );
+ $string = str_replace( '$', '\\$', $string );
+ return $string;
+ }
+}
+
+/**
+ * Base class for "replacers", objects used in preg_replace_callback() and
+ * StringUtils::delimiterReplaceCallback()
+ */
+class Replacer {
+ function cb() {
+ return array( &$this, 'replace' );
+ }
+}
+
+/**
+ * Class to replace regex matches with a string similar to that used in preg_replace()
+ */
+class RegexlikeReplacer extends Replacer {
+ var $r;
+ function __construct( $r ) {
+ $this->r = $r;
+ }
+
+ function replace( $matches ) {
+ $pairs = array();
+ foreach ( $matches as $i => $match ) {
+ $pairs["\$$i"] = $match;
+ }
+ return strtr( $this->r, $pairs );
+ }
+
+}
+
+/**
+ * Class to perform secondary replacement within each replacement string
+ */
+class DoubleReplacer extends Replacer {
+ function __construct( $from, $to, $index = 0 ) {
+ $this->from = $from;
+ $this->to = $to;
+ $this->index = $index;
+ }
+
+ function replace( $matches ) {
+ return str_replace( $this->from, $this->to, $matches[$this->index] );
+ }
+}
+
+/**
+ * Class to perform replacement based on a simple hashtable lookup
+ */
+class HashtableReplacer extends Replacer {
+ var $table, $index;
+
+ function __construct( $table, $index = 0 ) {
+ $this->table = $table;
+ $this->index = $index;
+ }
+
+ function replace( $matches ) {
+ return $this->table[$matches[$this->index]];
+ }
+}
+
+/**
+ * Replacement array for FSS with fallback to strtr()
+ * Supports lazy initialisation of FSS resource
+ */
+class ReplacementArray {
+ /*mostly private*/ var $data = false;
+ /*mostly private*/ var $fss = false;
+
+ /**
+ * Create an object with the specified replacement array
+ * The array should have the same form as the replacement array for strtr()
+ */
+ function __construct( $data = array() ) {
+ $this->data = $data;
+ }
+
+ function __sleep() {
+ return array( 'data' );
+ }
+
+ function __wakeup() {
+ $this->fss = false;
+ }
+
+ /**
+ * Set the whole replacement array at once
+ */
+ function setArray( $data ) {
+ $this->data = $data;
+ $this->fss = false;
+ }
+
+ function getArray() {
+ return $this->data;
+ }
+
+ /**
+ * Set an element of the replacement array
+ */
+ function setPair( $from, $to ) {
+ $this->data[$from] = $to;
+ $this->fss = false;
+ }
+
+ function mergeArray( $data ) {
+ $this->data = array_merge( $this->data, $data );
+ $this->fss = false;
+ }
+
+ function merge( $other ) {
+ $this->data = array_merge( $this->data, $other->data );
+ $this->fss = false;
+ }
+
+ function replace( $subject ) {
+ if ( function_exists( 'fss_prep_replace' ) ) {
+ wfProfileIn( __METHOD__.'-fss' );
+ if ( $this->fss === false ) {
+ $this->fss = fss_prep_replace( $this->data );
+ }
+ $result = fss_exec_replace( $this->fss, $subject );
+ wfProfileOut( __METHOD__.'-fss' );
+ } else {
+ wfProfileIn( __METHOD__.'-strtr' );
+ $result = strtr( $subject, $this->data );
+ wfProfileOut( __METHOD__.'-strtr' );
+ }
+ return $result;
+ }
+}
+
+?>
diff --git a/includes/StubObject.php b/includes/StubObject.php
index 63945f27..1501d963 100644
--- a/includes/StubObject.php
+++ b/includes/StubObject.php
@@ -89,9 +89,16 @@ class StubUserLang extends StubObject {
function _newObject() {
global $wgContLanguageCode, $wgRequest, $wgUser, $wgContLang;
- $code = $wgRequest->getVal('uselang', '');
- if ($code == '')
- $code = $wgUser->getOption('language');
+ $code = $wgRequest->getVal('uselang', $wgUser->getOption('language') );
+
+ // if variant is explicitely selected, use it instead the one from wgUser
+ // see bug #7605
+ if($wgContLang->hasVariants()){
+ $variant = $wgContLang->getPreferredVariant();
+ if($variant != $wgContLanguageCode)
+ $code = $variant;
+ }
+
# Validate $code
if( empty( $code ) || !preg_match( '/^[a-z]+(-[a-z]+)?$/', $code ) ) {
$code = $wgContLanguageCode;
@@ -118,9 +125,8 @@ class StubUser extends StubObject {
global $wgCommandLineMode;
if( $wgCommandLineMode ) {
$user = new User;
- $user->setLoaded( true );
} else {
- $user = User::loadFromSession();
+ $user = User::newFromSession();
wfRunHooks('AutoAuthenticate',array(&$user));
}
return $user;
diff --git a/includes/Title.php b/includes/Title.php
index 4a5e7156..56414c8a 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -109,8 +109,6 @@ class Title {
* @access public
*/
public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
- $fname = 'Title::newFromText';
-
if( is_object( $text ) ) {
throw new MWException( 'Title::newFromText given an object' );
}
@@ -162,7 +160,7 @@ class Title {
* @static
* @access public
*/
- function newFromURL( $url ) {
+ public static function newFromURL( $url ) {
global $wgLegalTitleChars;
$t = new Title();
@@ -192,7 +190,7 @@ class Title {
* @access public
* @static
*/
- function newFromID( $id ) {
+ public static function newFromID( $id ) {
$fname = 'Title::newFromID';
$dbr =& wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'page', array( 'page_namespace', 'page_title' ),
@@ -289,6 +287,7 @@ class Title {
$mwRedir = MagicWord::get( 'redirect' );
$rt = NULL;
if ( $mwRedir->matchStart( $text ) ) {
+ $m = array();
if ( preg_match( '/\[{2}(.*?)(?:\||\]{2})/', $text, $m ) ) {
# categories are escaped using : for example one can enter:
# #REDIRECT [[:Category:Music]]. Need to remove it.
@@ -299,7 +298,7 @@ class Title {
$rt = Title::newFromText( $m[1] );
# Disallow redirects to Special:Userlogout
- if ( !is_null($rt) && $rt->getNamespace() == NS_SPECIAL && preg_match( '/^Userlogout/i', $rt->getText() ) ) {
+ if ( !is_null($rt) && $rt->isSpecial( 'Userlogout' ) ) {
$rt = NULL;
}
}
@@ -540,7 +539,7 @@ class Title {
foreach ( $titles as $title ) {
if ( $wgUseFileCache ) {
- $cm = new CacheManager($title);
+ $cm = new HTMLFileCache($title);
@unlink($cm->fileCacheName());
}
@@ -568,6 +567,19 @@ class Title {
}
}
+ /**
+ * Escape a text fragment, say from a link, for a URL
+ */
+ static function escapeFragmentForURL( $fragment ) {
+ $fragment = str_replace( ' ', '_', $fragment );
+ $fragment = urlencode( Sanitizer::decodeCharReferences( $fragment ) );
+ $replaceArray = array(
+ '%3A' => ':',
+ '%' => '.'
+ );
+ return strtr( $fragment, $replaceArray );
+ }
+
#----------------------------------------------------------------------------
# Other stuff
#----------------------------------------------------------------------------
@@ -603,7 +615,19 @@ class Title {
* @access public
*/
function getNsText() {
- global $wgContLang;
+ global $wgContLang, $wgCanonicalNamespaceNames;
+
+ if ( '' != $this->mInterwiki ) {
+ // This probably shouldn't even happen. ohh man, oh yuck.
+ // But for interwiki transclusion it sometimes does.
+ // Shit. Shit shit shit.
+ //
+ // Use the canonical namespaces if possible to try to
+ // resolve a foreign namespace.
+ if( isset( $wgCanonicalNamespaceNames[$this->mNamespace] ) ) {
+ return $wgCanonicalNamespaceNames[$this->mNamespace];
+ }
+ }
return $wgContLang->getNsText( $this->mNamespace );
}
/**
@@ -640,12 +664,25 @@ class Title {
*/
function getInterwiki() { return $this->mInterwiki; }
/**
- * Get the Title fragment (i.e. the bit after the #)
+ * Get the Title fragment (i.e. the bit after the #) in text form
* @return string
* @access public
*/
function getFragment() { return $this->mFragment; }
/**
+ * Get the fragment in URL form, including the "#" character if there is one
+ *
+ * @return string
+ * @access public
+ */
+ function getFragmentForURL() {
+ if ( $this->mFragment == '' ) {
+ return '';
+ } else {
+ return '#' . Title::escapeFragmentForURL( $this->mFragment );
+ }
+ }
+ /**
* Get the default namespace index, for when there is no namespace
* @return int
* @access public
@@ -769,14 +806,15 @@ class Title {
*
* @param string $query an optional query string, not used
* for interwiki links
+ * @param string $variant language variant of url (for sr, zh..)
* @return string the URL
* @access public
*/
- function getFullURL( $query = '' ) {
+ function getFullURL( $query = '', $variant = false ) {
global $wgContLang, $wgServer, $wgRequest;
if ( '' == $this->mInterwiki ) {
- $url = $this->getLocalUrl( $query );
+ $url = $this->getLocalUrl( $query, $variant );
// Ugly quick hack to avoid duplicate prefixes (bug 4571 etc)
// Correct fix would be to move the prepending elsewhere.
@@ -786,9 +824,10 @@ class Title {
} else {
$baseUrl = $this->getInterwikiLink( $this->mInterwiki );
- $namespace = $wgContLang->getNsText( $this->mNamespace );
+ $namespace = wfUrlencode( $this->getNsText() );
if ( '' != $namespace ) {
# Can this actually happen? Interwikis shouldn't be parsed.
+ # Yes! It can in interwiki transclusion. But... it probably shouldn't.
$namespace .= ':';
}
$url = str_replace( '$1', $namespace . $this->mUrlform, $baseUrl );
@@ -803,9 +842,7 @@ class Title {
}
# Finally, add the fragment.
- if ( '' != $this->mFragment ) {
- $url .= '#' . $this->mFragment;
- }
+ $url .= $this->getFragmentForURL();
wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
return $url;
@@ -816,11 +853,20 @@ class Title {
* with action=render, $wgServer is prepended.
* @param string $query an optional query string; if not specified,
* $wgArticlePath will be used.
+ * @param string $variant language variant of url (for sr, zh..)
* @return string the URL
* @access public
*/
- function getLocalURL( $query = '' ) {
+ function getLocalURL( $query = '', $variant = false ) {
global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
+ global $wgVariantArticlePath, $wgContLang, $wgUser;
+
+ // internal links should point to same variant as current page (only anonymous users)
+ if($variant == false && $wgContLang->hasVariants() && !$wgUser->isLoggedIn()){
+ $pref = $wgContLang->getPreferredVariant(false);
+ if($pref != $wgContLang->getCode())
+ $variant = $pref;
+ }
if ( $this->isExternal() ) {
$url = $this->getFullURL();
@@ -834,10 +880,22 @@ class Title {
} else {
$dbkey = wfUrlencode( $this->getPrefixedDBkey() );
if ( $query == '' ) {
- $url = str_replace( '$1', $dbkey, $wgArticlePath );
+ if($variant!=false && $wgContLang->hasVariants()){
+ if($wgVariantArticlePath==false)
+ $variantArticlePath = "$wgScript?title=$1&variant=$2"; // default
+ else
+ $variantArticlePath = $wgVariantArticlePath;
+
+ $url = str_replace( '$2', urlencode( $variant ), $variantArticlePath );
+ $url = str_replace( '$1', $dbkey, $url );
+
+ }
+ else
+ $url = str_replace( '$1', $dbkey, $wgArticlePath );
} else {
global $wgActionPaths;
$url = false;
+ $matches = array();
if( !empty( $wgActionPaths ) &&
preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
{
@@ -896,12 +954,13 @@ class Title {
* internal hostname for the server from the exposed one.
*
* @param string $query an optional query string
+ * @param string $variant language variant of url (for sr, zh..)
* @return string the URL
* @access public
*/
- function getInternalURL( $query = '' ) {
+ function getInternalURL( $query = '', $variant = false ) {
global $wgInternalServer;
- $url = $wgInternalServer . $this->getLocalURL( $query );
+ $url = $wgInternalServer . $this->getLocalURL( $query, $variant );
wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
return $url;
}
@@ -943,14 +1002,22 @@ class Title {
* @return bool
*/
function isSemiProtected( $action = 'edit' ) {
- $restrictions = $this->getRestrictions( $action );
- # We do a full compare because this could be an array
- foreach( $restrictions as $restriction ) {
- if( strtolower( $restriction ) != 'autoconfirmed' ) {
- return( false );
+ if( $this->exists() ) {
+ $restrictions = $this->getRestrictions( $action );
+ if( count( $restrictions ) > 0 ) {
+ foreach( $restrictions as $restriction ) {
+ if( strtolower( $restriction ) != 'autoconfirmed' )
+ return false;
+ }
+ } else {
+ # Not protected
+ return false;
}
+ return true;
+ } else {
+ # If it doesn't exist, it can't be protected
+ return false;
}
- return( true );
}
/**
@@ -962,7 +1029,7 @@ class Title {
*/
function isProtected( $action = '' ) {
global $wgRestrictionLevels;
- if ( -1 == $this->mNamespace ) { return true; }
+ if ( NS_SPECIAL == $this->mNamespace ) { return true; }
if( $action == 'edit' || $action == '' ) {
$r = $this->getRestrictions( 'edit' );
@@ -994,7 +1061,7 @@ class Title {
global $wgUser;
if ( is_null( $this->mWatched ) ) {
- if ( -1 == $this->mNamespace || 0 == $wgUser->getID()) {
+ if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn()) {
$this->mWatched = false;
} else {
$this->mWatched = $wgUser->isWatched( $this );
@@ -1043,8 +1110,7 @@ class Title {
# protect css/js subpages of user pages
# XXX: this might be better using restrictions
# XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working
- if( NS_USER == $this->mNamespace
- && preg_match("/\\.(css|js)$/", $this->mTextform )
+ if( $this->isCssJsSubpage()
&& !$wgUser->isAllowed('editinterface')
&& !preg_match('/^'.preg_quote($wgUser->getName(), '/').'\//', $this->mTextform) ) {
wfProfileOut( $fname );
@@ -1138,11 +1204,11 @@ class Title {
} else {
global $wgWhitelistRead;
- /** If anon users can create an account,
- they need to reach the login page first! */
- if( $wgUser->isAllowed( 'createaccount' )
- && $this->getNamespace() == NS_SPECIAL
- && $this->getText() == 'Userlogin' ) {
+ /**
+ * Always grant access to the login page.
+ * Even anons need to be able to log in.
+ */
+ if( $this->isSpecial( 'Userlogin' ) || $this->isSpecial( 'Resetpass' ) ) {
return true;
}
@@ -1172,12 +1238,27 @@ class Title {
}
/**
+ * Is this a subpage?
+ * @return bool
+ * @access public
+ */
+ function isSubpage() {
+ global $wgNamespacesWithSubpages;
+
+ if( isset( $wgNamespacesWithSubpages[ $this->mNamespace ] ) ) {
+ return ( strpos( $this->getText(), '/' ) !== false && $wgNamespacesWithSubpages[ $this->mNamespace ] == true );
+ } else {
+ return false;
+ }
+ }
+
+ /**
* Is this a .css or .js subpage of a user page?
* @return bool
* @access public
*/
function isCssJsSubpage() {
- return ( NS_USER == $this->mNamespace and preg_match("/\\.(css|js)$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace and preg_match("/\\/.*\\.(css|js)$/", $this->mTextform ) );
}
/**
* Is this a *valid* .css or .js subpage of a user page?
@@ -1205,7 +1286,7 @@ class Title {
* @access public
*/
function isCssSubpage() {
- return ( NS_USER == $this->mNamespace and preg_match("/\\.css$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace and preg_match("/\\/.*\\.css$/", $this->mTextform ) );
}
/**
* Is this a .js subpage of a user page?
@@ -1213,7 +1294,7 @@ class Title {
* @access public
*/
function isJsSubpage() {
- return ( NS_USER == $this->mNamespace and preg_match("/\\.js$/", $this->mTextform ) );
+ return ( NS_USER == $this->mNamespace and preg_match("/\\/.*\\.js$/", $this->mTextform ) );
}
/**
* Protect css/js subpages of user pages: can $wgUser edit
@@ -1234,6 +1315,15 @@ class Title {
* @access public
*/
function loadRestrictions( $res ) {
+ $this->mRestrictions['edit'] = array();
+ $this->mRestrictions['move'] = array();
+
+ if( !$res ) {
+ # No restrictions (page_restrictions blank)
+ $this->mRestrictionsLoaded = true;
+ return;
+ }
+
foreach( explode( ':', trim( $res ) ) as $restrict ) {
$temp = explode( '=', trim( $restrict ) );
if(count($temp) == 1) {
@@ -1249,23 +1339,24 @@ class Title {
/**
* Accessor/initialisation for mRestrictions
+ *
+ * @access public
* @param string $action action that permission needs to be checked for
* @return array the array of groups allowed to edit this article
- * @access public
*/
- function getRestrictions($action) {
- $id = $this->getArticleID();
- if ( 0 == $id ) { return array(); }
-
- if ( ! $this->mRestrictionsLoaded ) {
- $dbr =& wfGetDB( DB_SLAVE );
- $res = $dbr->selectField( 'page', 'page_restrictions', 'page_id='.$id );
- $this->loadRestrictions( $res );
- }
- if( isset( $this->mRestrictions[$action] ) ) {
- return $this->mRestrictions[$action];
+ function getRestrictions( $action ) {
+ if( $this->exists() ) {
+ if( !$this->mRestrictionsLoaded ) {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $res = $dbr->selectField( 'page', 'page_restrictions', array( 'page_id' => $this->getArticleId() ) );
+ $this->loadRestrictions( $res );
+ }
+ return isset( $this->mRestrictions[$action] )
+ ? $this->mRestrictions[$action]
+ : array();
+ } else {
+ return array();
}
- return array();
}
/**
@@ -1366,7 +1457,7 @@ class Title {
);
if ($wgUseFileCache) {
- $cache = new CacheManager($this);
+ $cache = new HTMLFileCache($this);
@unlink($cache->fileCacheName());
}
@@ -1382,14 +1473,12 @@ class Title {
* @private
*/
/* private */ function prefix( $name ) {
- global $wgContLang;
-
$p = '';
if ( '' != $this->mInterwiki ) {
$p = $this->mInterwiki . ':';
}
if ( 0 != $this->mNamespace ) {
- $p .= $wgContLang->getNsText( $this->mNamespace ) . ':';
+ $p .= $this->getNsText() . ':';
}
return $p . $name;
}
@@ -1407,7 +1496,6 @@ class Title {
*/
/* private */ function secureAndSplit() {
global $wgContLang, $wgLocalInterwiki, $wgCapitalLinks;
- $fname = 'Title::secureAndSplit';
# Initialisation
static $rxTc = false;
@@ -1418,43 +1506,52 @@ class Title {
$this->mInterwiki = $this->mFragment = '';
$this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
+
+ $dbkey = $this->mDbkeyform;
+ # Strip Unicode bidi override characters.
+ # Sometimes they slip into cut-n-pasted page titles, where the
+ # override chars get included in list displays.
+ $dbkey = str_replace( "\xE2\x80\x8E", '', $dbkey ); // 200E LEFT-TO-RIGHT MARK
+ $dbkey = str_replace( "\xE2\x80\x8F", '', $dbkey ); // 200F RIGHT-TO-LEFT MARK
+
# Clean up whitespace
#
- $t = preg_replace( '/[ _]+/', '_', $this->mDbkeyform );
- $t = trim( $t, '_' );
+ $dbkey = preg_replace( '/[ _]+/', '_', $dbkey );
+ $dbkey = trim( $dbkey, '_' );
- if ( '' == $t ) {
+ if ( '' == $dbkey ) {
return false;
}
- if( false !== strpos( $t, UTF8_REPLACEMENT ) ) {
+ if( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
# Contained illegal UTF-8 sequences or forbidden Unicode chars.
return false;
}
- $this->mDbkeyform = $t;
+ $this->mDbkeyform = $dbkey;
# Initial colon indicates main namespace rather than specified default
# but should not create invalid {ns,title} pairs such as {0,Project:Foo}
- if ( ':' == $t{0} ) {
+ if ( ':' == $dbkey{0} ) {
$this->mNamespace = NS_MAIN;
- $t = substr( $t, 1 ); # remove the colon but continue processing
+ $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
}
# Namespace or interwiki prefix
$firstPass = true;
do {
- if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $t, $m ) ) {
+ $m = array();
+ if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $dbkey, $m ) ) {
$p = $m[1];
$lowerNs = $wgContLang->lc( $p );
if ( $ns = Namespace::getCanonicalIndex( $lowerNs ) ) {
# Canonical namespace
- $t = $m[2];
+ $dbkey = $m[2];
$this->mNamespace = $ns;
} elseif ( $ns = $wgContLang->getNsIndex( $lowerNs )) {
# Ordinary namespace
- $t = $m[2];
+ $dbkey = $m[2];
$this->mNamespace = $ns;
} elseif( $this->getInterwikiLink( $p ) ) {
if( !$firstPass ) {
@@ -1464,12 +1561,12 @@ class Title {
}
# Interwiki link
- $t = $m[2];
+ $dbkey = $m[2];
$this->mInterwiki = $wgContLang->lc( $p );
# Redundant interwiki prefix to the local wiki
if ( 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) ) {
- if( $t == '' ) {
+ if( $dbkey == '' ) {
# Can't have an empty self-link
return false;
}
@@ -1481,9 +1578,9 @@ class Title {
# If there's an initial colon after the interwiki, that also
# resets the default namespace
- if ( $t !== '' && $t[0] == ':' ) {
+ if ( $dbkey !== '' && $dbkey[0] == ':' ) {
$this->mNamespace = NS_MAIN;
- $t = substr( $t, 1 );
+ $dbkey = substr( $dbkey, 1 );
}
}
# If there's no recognized interwiki or namespace,
@@ -1491,25 +1588,24 @@ class Title {
}
break;
} while( true );
- $r = $t;
# We already know that some pages won't be in the database!
#
- if ( '' != $this->mInterwiki || -1 == $this->mNamespace ) {
+ if ( '' != $this->mInterwiki || NS_SPECIAL == $this->mNamespace ) {
$this->mArticleID = 0;
}
- $f = strstr( $r, '#' );
- if ( false !== $f ) {
- $this->mFragment = substr( $f, 1 );
- $r = substr( $r, 0, strlen( $r ) - strlen( $f ) );
+ $fragment = strstr( $dbkey, '#' );
+ if ( false !== $fragment ) {
+ $this->setFragment( $fragment );
+ $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
# remove whitespace again: prevents "Foo_bar_#"
# becoming "Foo_bar_"
- $r = preg_replace( '/_*$/', '', $r );
+ $dbkey = preg_replace( '/_*$/', '', $dbkey );
}
# Reject illegal characters.
#
- if( preg_match( $rxTc, $r ) ) {
+ if( preg_match( $rxTc, $dbkey ) ) {
return false;
}
@@ -1518,19 +1614,26 @@ class Title {
* often be unreachable due to the way web browsers deal
* with 'relative' URLs. Forbid them explicitly.
*/
- if ( strpos( $r, '.' ) !== false &&
- ( $r === '.' || $r === '..' ||
- strpos( $r, './' ) === 0 ||
- strpos( $r, '../' ) === 0 ||
- strpos( $r, '/./' ) !== false ||
- strpos( $r, '/../' ) !== false ) )
+ if ( strpos( $dbkey, '.' ) !== false &&
+ ( $dbkey === '.' || $dbkey === '..' ||
+ strpos( $dbkey, './' ) === 0 ||
+ strpos( $dbkey, '../' ) === 0 ||
+ strpos( $dbkey, '/./' ) !== false ||
+ strpos( $dbkey, '/../' ) !== false ) )
{
return false;
}
- # We shouldn't need to query the DB for the size.
- #$maxSize = $dbr->textFieldSize( 'page', 'page_title' );
- if ( strlen( $r ) > 255 ) {
+ /**
+ * Limit the size of titles to 255 bytes.
+ * This is typically the size of the underlying database field.
+ * We make an exception for special pages, which don't need to be stored
+ * in the database, and may edge over 255 bytes due to subpage syntax
+ * for long titles, e.g. [[Special:Block/Long name]]
+ */
+ if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) ||
+ strlen( $dbkey ) > 512 )
+ {
return false;
}
@@ -1543,9 +1646,7 @@ class Title {
* site might be case-sensitive.
*/
if( $wgCapitalLinks && $this->mInterwiki == '') {
- $t = $wgContLang->ucfirst( $r );
- } else {
- $t = $r;
+ $dbkey = $wgContLang->ucfirst( $dbkey );
}
/**
@@ -1553,27 +1654,40 @@ class Title {
* "empty" local links can only be self-links
* with a fragment identifier.
*/
- if( $t == '' &&
+ if( $dbkey == '' &&
$this->mInterwiki == '' &&
$this->mNamespace != NS_MAIN ) {
return false;
}
// Any remaining initial :s are illegal.
- if ( $t !== '' && ':' == $t{0} ) {
+ if ( $dbkey !== '' && ':' == $dbkey{0} ) {
return false;
}
# Fill fields
- $this->mDbkeyform = $t;
- $this->mUrlform = wfUrlencode( $t );
+ $this->mDbkeyform = $dbkey;
+ $this->mUrlform = wfUrlencode( $dbkey );
- $this->mTextform = str_replace( '_', ' ', $t );
+ $this->mTextform = str_replace( '_', ' ', $dbkey );
return true;
}
/**
+ * Set the fragment for this title
+ * This is kind of bad, since except for this rarely-used function, Title objects
+ * are immutable. The reason this is here is because it's better than setting the
+ * members directly, which is what Linker::formatComment was doing previously.
+ *
+ * @param string $fragment text
+ * @access kind of public
+ */
+ function setFragment( $fragment ) {
+ $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
+ }
+
+ /**
* Get a Title object associated with the talk page of this article
* @return Title the object for the talk page
* @access public
@@ -1606,7 +1720,6 @@ class Title {
*/
function getLinksTo( $options = '', $table = 'pagelinks', $prefix = 'pl' ) {
$linkCache =& LinkCache::singleton();
- $id = $this->getArticleID();
if ( $options ) {
$db =& wfGetDB( DB_MASTER );
@@ -1698,10 +1811,23 @@ class Title {
* @access public
*/
function getSquidURLs() {
- return array(
+ global $wgContLang;
+
+ $urls = array(
$this->getInternalURL(),
$this->getInternalURL( 'action=history' )
);
+
+ // purge variant urls as well
+ if($wgContLang->hasVariants()){
+ $variants = $wgContLang->getVariants();
+ foreach($variants as $vCode){
+ if($vCode==$wgContLang->getCode()) continue; // we don't want default variant
+ $urls[] = $this->getInternalURL('',$vCode);
+ }
+ }
+
+ return $urls;
}
function purgeSquid() {
@@ -1864,7 +1990,6 @@ class Title {
}
$now = wfTimestampNow();
- $rand = wfRandom();
$newid = $nt->getArticleID();
$oldid = $this->getArticleID();
$dbw =& wfGetDB( DB_MASTER );
@@ -1902,7 +2027,7 @@ class Title {
'page' => $newid,
'comment' => $comment,
'text' => $redirectText ) );
- $revid = $redirectRevision->insertOn( $dbw );
+ $redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
$linkCache->clearLink( $this->getPrefixedDBkey() );
@@ -1945,7 +2070,6 @@ class Title {
$oldid = $this->getArticleID();
$dbw =& wfGetDB( DB_MASTER );
$now = $dbw->timestamp();
- $rand = wfRandom();
$linkCache =& LinkCache::singleton();
# Save a null revision in the page's history notifying of the move
@@ -1975,7 +2099,7 @@ class Title {
'page' => $newid,
'comment' => $comment,
'text' => $redirectText ) );
- $revid = $redirectRevision->insertOn( $dbw );
+ $redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
$linkCache->clearLink( $this->getPrefixedDBkey() );
@@ -2027,6 +2151,7 @@ class Title {
# Does the redirect point to the source?
# Or is it a broken self-redirect, usually caused by namespace collisions?
+ $m = array();
if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
$redirTitle = Title::newFromText( $m[1] );
if( !is_object( $redirTitle ) ||
@@ -2078,7 +2203,7 @@ class Title {
'comment' => $comment,
'text' => "#REDIRECT [[" . $dest->getPrefixedText() . "]]\n",
) );
- $revisionId = $revision->insertOn( $dbw );
+ $revision->insertOn( $dbw );
$article->updateRevisionOn( $dbw, $revision, 0 );
# Link table
@@ -2171,7 +2296,7 @@ class Title {
* Get the revision ID of the previous revision
*
* @param integer $revision Revision ID. Get the revision that was before this one.
- * @return interger $oldrevision|false
+ * @return integer $oldrevision|false
*/
function getPreviousRevisionID( $revision ) {
$dbr =& wfGetDB( DB_SLAVE );
@@ -2184,7 +2309,7 @@ class Title {
* Get the revision ID of the next revision
*
* @param integer $revision Revision ID. Get the revision that was after this one.
- * @return interger $oldrevision|false
+ * @return integer $oldrevision|false
*/
function getNextRevisionID( $revision ) {
$dbr =& wfGetDB( DB_SLAVE );
@@ -2194,6 +2319,21 @@ class Title {
}
/**
+ * Get the number of revisions between the given revision IDs.
+ *
+ * @param integer $old Revision ID.
+ * @param integer $new Revision ID.
+ * @return integer Number of revisions between these IDs.
+ */
+ function countRevisionsBetween( $old, $new ) {
+ $dbr =& wfGetDB( DB_SLAVE );
+ return $dbr->selectField( 'revision', 'count(*)',
+ 'rev_page = ' . intval( $this->getArticleId() ) .
+ ' AND rev_id > ' . intval( $old ) .
+ ' AND rev_id < ' . intval( $new ) );
+ }
+
+ /**
* Compare with another title.
*
* @param Title $title
@@ -2258,26 +2398,12 @@ class Title {
* 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
+ *
+ * @deprecated use DependencyWrapper
*/
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;
+ return DependencyWrapper::getValueFromCache( $memc, $key, $expiry, $callback,
+ $params, new TitleDependency( $this ) );
}
function trackbackURL() {
@@ -2343,5 +2469,37 @@ class Title {
return 'nstab-' . $wgContLang->lc( $this->getSubjectNsText() );
}
}
+
+ /**
+ * Returns true if this title resolves to the named special page
+ * @param string $name The special page name
+ * @access public
+ */
+ function isSpecial( $name ) {
+ if ( $this->getNamespace() == NS_SPECIAL ) {
+ list( $thisName, /* $subpage */ ) = SpecialPage::resolveAliasWithSubpage( $this->getDBkey() );
+ if ( $name == $thisName ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * If the Title refers to a special page alias which is not the local default,
+ * returns a new Title which points to the local default. Otherwise, returns $this.
+ */
+ function fixSpecialName() {
+ if ( $this->getNamespace() == NS_SPECIAL ) {
+ $canonicalName = SpecialPage::resolveAlias( $this->mDbkeyform );
+ if ( $canonicalName ) {
+ $localName = SpecialPage::getLocalNameFor( $canonicalName );
+ if ( $localName != $this->mDbkeyform ) {
+ return Title::makeTitle( NS_SPECIAL, $localName );
+ }
+ }
+ }
+ return $this;
+ }
}
?>
diff --git a/includes/User.php b/includes/User.php
index aa964d22..35ff8299 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -9,43 +9,31 @@
define( 'USER_TOKEN_LENGTH', 32 );
# Serialized record version
-define( 'MW_USER_VERSION', 3 );
+define( 'MW_USER_VERSION', 4 );
+
+# Some punctuation to prevent editing from broken text-mangling proxies.
+# FIXME: this is embedded unescaped into HTML attributes in various
+# places, so we can't safely include ' or " even though we really should.
+define( 'EDIT_TOKEN_SUFFIX', '\\' );
+
+/**
+ * Thrown by User::setPassword() on error
+ */
+class PasswordError extends MWException {
+ // NOP
+}
/**
*
* @package MediaWiki
*/
class User {
- /*
- * When adding a new private variable, dont forget to add it to __sleep()
- */
- /**@{{
- * @private
- */
- var $mBlockedby; //!<
- var $mBlockreason; //!<
- var $mBlock; //!<
- var $mDataLoaded; //!<
- var $mEmail; //!<
- var $mEmailAuthenticated; //!<
- var $mGroups; //!<
- var $mHash; //!<
- var $mId; //!<
- var $mName; //!<
- var $mNewpassword; //!<
- var $mNewtalk; //!<
- var $mOptions; //!<
- var $mPassword; //!<
- var $mRealName; //!<
- var $mRegistration; //!<
- var $mRights; //!<
- var $mSkin; //!<
- var $mToken; //!<
- var $mTouched; //!<
- var $mDatePreference; // !<
- var $mVersion; //!< serialized version
- /**@}} */
+ /**
+ * A list of default user toggles, i.e. boolean user preferences that are
+ * displayed by Special:Preferences as checkboxes. This list can be
+ * extended via the UserToggles hook or $wgContLang->getExtraUserToggles().
+ */
static public $mToggles = array(
'highlightbroken',
'justify',
@@ -62,6 +50,8 @@ class User {
'editwidth',
'watchcreations',
'watchdefault',
+ 'watchmoves',
+ 'watchdeletion',
'minordefault',
'previewontop',
'previewonfirst',
@@ -76,53 +66,200 @@ class User {
'externaldiff',
'showjumplinks',
'uselivepreview',
- 'autopatrol',
'forceeditsummary',
'watchlisthideown',
'watchlisthidebots',
- );
+ 'watchlisthideminor',
+ 'ccmeonemails',
+ );
+
+ /**
+ * List of member variables which are saved to the shared cache (memcached).
+ * Any operation which changes the corresponding database fields must
+ * call a cache-clearing function.
+ */
+ static $mCacheVars = array(
+ # user table
+ 'mId',
+ 'mName',
+ 'mRealName',
+ 'mPassword',
+ 'mNewpassword',
+ 'mNewpassTime',
+ 'mEmail',
+ 'mOptions',
+ 'mTouched',
+ 'mToken',
+ 'mEmailAuthenticated',
+ 'mEmailToken',
+ 'mEmailTokenExpires',
+ 'mRegistration',
+
+ # user_group table
+ 'mGroups',
+ );
+
+ /**
+ * The cache variable declarations
+ */
+ var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime,
+ $mEmail, $mOptions, $mTouched, $mToken, $mEmailAuthenticated,
+ $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups;
- /** Constructor using User:loadDefaults() */
- function User() {
- $this->loadDefaults();
- $this->mVersion = MW_USER_VERSION;
+ /**
+ * Whether the cache variables have been loaded
+ */
+ var $mDataLoaded;
+
+ /**
+ * Initialisation data source if mDataLoaded==false. May be one of:
+ * defaults anonymous user initialised from class defaults
+ * name initialise from mName
+ * id initialise from mId
+ * session log in from cookies or session if possible
+ *
+ * Use the User::newFrom*() family of functions to set this.
+ */
+ var $mFrom;
+
+ /**
+ * Lazy-initialised variables, invalidated with clearInstanceCache
+ */
+ var $mNewtalk, $mDatePreference, $mBlockedby, $mHash, $mSkin, $mRights,
+ $mBlockreason, $mBlock, $mEffectiveGroups;
+
+ /**
+ * Lightweight constructor for anonymous user
+ * Use the User::newFrom* factory functions for other kinds of users
+ */
+ function User() {
+ $this->clearInstanceCache( 'defaults' );
}
/**
- * Static factory method
- * @param string $name Username, validated by Title:newFromText()
- * @param bool $validate Validate username
- * @return User
- * @static
+ * Load the user table data for this object from the source given by mFrom
*/
- function newFromName( $name, $validate = true ) {
- # Force usernames to capital
- global $wgContLang;
- $name = $wgContLang->ucfirst( $name );
+ function load() {
+ if ( $this->mDataLoaded ) {
+ return;
+ }
+ wfProfileIn( __METHOD__ );
- # Clean up name according to title rules
- $t = Title::newFromText( $name );
- if( is_null( $t ) ) {
- return null;
+ # Set it now to avoid infinite recursion in accessors
+ $this->mDataLoaded = true;
+
+ switch ( $this->mFrom ) {
+ case 'defaults':
+ $this->loadDefaults();
+ break;
+ case 'name':
+ $this->mId = self::idFromName( $this->mName );
+ if ( !$this->mId ) {
+ # Nonexistent user placeholder object
+ $this->loadDefaults( $this->mName );
+ } else {
+ $this->loadFromId();
+ }
+ break;
+ case 'id':
+ $this->loadFromId();
+ break;
+ case 'session':
+ $this->loadFromSession();
+ break;
+ default:
+ throw new MWException( "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
}
+ wfProfileOut( __METHOD__ );
+ }
- # Reject various classes of invalid names
- $canonicalName = $t->getText();
- global $wgAuth;
- $canonicalName = $wgAuth->getCanonicalName( $t->getText() );
+ /**
+ * Load user table data given mId
+ * @return false if the ID does not exist, true otherwise
+ * @private
+ */
+ function loadFromId() {
+ global $wgMemc;
+ if ( $this->mId == 0 ) {
+ $this->loadDefaults();
+ return false;
+ }
+
+ # Try cache
+ $key = wfMemcKey( 'user', 'id', $this->mId );
+ $data = $wgMemc->get( $key );
+
+ if ( !is_array( $data ) || $data['mVersion'] < MW_USER_VERSION ) {
+ # Object is expired, load from DB
+ $data = false;
+ }
+
+ if ( !$data ) {
+ wfDebug( "Cache miss for user {$this->mId}\n" );
+ # Load from DB
+ if ( !$this->loadFromDatabase() ) {
+ # Can't load from ID, user is anonymous
+ return false;
+ }
+
+ # Save to cache
+ $data = array();
+ foreach ( self::$mCacheVars as $name ) {
+ $data[$name] = $this->$name;
+ }
+ $data['mVersion'] = MW_USER_VERSION;
+ $wgMemc->set( $key, $data );
+ } else {
+ wfDebug( "Got user {$this->mId} from cache\n" );
+ # Restore from cache
+ foreach ( self::$mCacheVars as $name ) {
+ $this->$name = $data[$name];
+ }
+ }
+ return true;
+ }
- if( $validate && !User::isValidUserName( $canonicalName ) ) {
+ /**
+ * Static factory method for creation from username.
+ *
+ * This is slightly less efficient than newFromId(), so use newFromId() if
+ * you have both an ID and a name handy.
+ *
+ * @param string $name Username, validated by Title:newFromText()
+ * @param mixed $validate Validate username. Takes the same parameters as
+ * User::getCanonicalName(), except that true is accepted as an alias
+ * for 'valid', for BC.
+ *
+ * @return User object, or null if the username is invalid. If the username
+ * is not present in the database, the result will be a user object with
+ * a name, zero user ID and default settings.
+ * @static
+ */
+ static function newFromName( $name, $validate = 'valid' ) {
+ if ( $validate === true ) {
+ $validate = 'valid';
+ }
+ $name = self::getCanonicalName( $name, $validate );
+ if ( $name === false ) {
return null;
+ } else {
+ # Create unloaded user object
+ $u = new User;
+ $u->mName = $name;
+ $u->mFrom = 'name';
+ return $u;
}
+ }
- $u = new User();
- $u->setName( $canonicalName );
- $u->setId( $u->idFromName( $canonicalName ) );
+ static function newFromId( $id ) {
+ $u = new User;
+ $u->mId = $id;
+ $u->mFrom = 'id';
return $u;
}
/**
- * Factory method to fetch whichever use has a given email confirmation code.
+ * Factory method to fetch whichever user has a given email confirmation code.
* This code is generated when an account is created or its e-mail address
* has changed.
*
@@ -132,44 +269,30 @@ class User {
* @return User
* @static
*/
- function newFromConfirmationCode( $code ) {
+ static function newFromConfirmationCode( $code ) {
$dbr =& wfGetDB( DB_SLAVE );
- $name = $dbr->selectField( 'user', 'user_name', array(
+ $id = $dbr->selectField( 'user', 'user_id', array(
'user_email_token' => md5( $code ),
'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ),
) );
- if( is_string( $name ) ) {
- return User::newFromName( $name );
+ if( $id !== false ) {
+ return User::newFromId( $id );
} else {
return null;
}
}
-
+
/**
- * Serialze sleep function, for better cache efficiency and avoidance of
- * silly "incomplete type" errors when skins are cached. The array should
- * contain names of private variables (see at top of User.php).
+ * Create a new user object using data from session or cookies. If the
+ * login credentials are invalid, the result is an anonymous user.
+ *
+ * @return User
+ * @static
*/
- function __sleep() {
- return array(
-'mDataLoaded',
-'mEmail',
-'mEmailAuthenticated',
-'mGroups',
-'mHash',
-'mId',
-'mName',
-'mNewpassword',
-'mNewtalk',
-'mOptions',
-'mPassword',
-'mRealName',
-'mRegistration',
-'mRights',
-'mToken',
-'mTouched',
-'mVersion',
-);
+ static function newFromSession() {
+ $user = new User;
+ $user->mFrom = 'session';
+ return $user;
}
/**
@@ -178,7 +301,7 @@ class User {
* @return string Nickname of a user
* @static
*/
- function whoIs( $id ) {
+ static function whoIs( $id ) {
$dbr =& wfGetDB( DB_SLAVE );
return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ), 'User::whoIs' );
}
@@ -189,7 +312,7 @@ class User {
* @return string Realname of a user
* @static
*/
- function whoIsReal( $id ) {
+ static function whoIsReal( $id ) {
$dbr =& wfGetDB( DB_SLAVE );
return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ), 'User::whoIsReal' );
}
@@ -200,16 +323,14 @@ class User {
* @return integer|null Database user id (null: if non existent
* @static
*/
- function idFromName( $name ) {
- $fname = "User::idFromName";
-
+ static function idFromName( $name ) {
$nt = Title::newFromText( $name );
if( is_null( $nt ) ) {
# Illegal name
return null;
}
$dbr =& wfGetDB( DB_SLAVE );
- $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), $fname );
+ $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), __METHOD__ );
if ( $s === false ) {
return 0;
@@ -237,8 +358,8 @@ class User {
* @param string $name Nickname of a user
* @return bool
*/
- function isIP( $name ) {
- return preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/",$name);
+ static function isIP( $name ) {
+ return preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/',$name);
/*return preg_match("/^
(?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
(?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
@@ -259,7 +380,7 @@ class User {
* @return bool
* @static
*/
- function isValidUserName( $name ) {
+ static function isValidUserName( $name ) {
global $wgContLang, $wgMaxNameChars;
if ( $name == ''
@@ -343,7 +464,7 @@ class User {
* @return bool
* @static
*/
- function isValidPassword( $password ) {
+ static function isValidPassword( $password ) {
global $wgMinimalPasswordLength;
return strlen( $password ) >= $wgMinimalPasswordLength;
}
@@ -362,35 +483,85 @@ class User {
* @static
* @return bool
*/
- function isValidEmailAddr ( $addr ) {
+ static function isValidEmailAddr ( $addr ) {
return ( trim( $addr ) != '' ) &&
(false !== strpos( $addr, '@' ) );
}
/**
+ * Given unvalidated user input, return a canonical username, or false if
+ * the username is invalid.
+ * @param string $name
+ * @param mixed $validate Type of validation to use:
+ * false No validation
+ * 'valid' Valid for batch processes
+ * 'usable' Valid for batch processes and login
+ * 'creatable' Valid for batch processes, login and account creation
+ */
+ static function getCanonicalName( $name, $validate = 'valid' ) {
+ # Force usernames to capital
+ global $wgContLang;
+ $name = $wgContLang->ucfirst( $name );
+
+ # Clean up name according to title rules
+ $t = Title::newFromText( $name );
+ if( is_null( $t ) ) {
+ return false;
+ }
+
+ # Reject various classes of invalid names
+ $name = $t->getText();
+ global $wgAuth;
+ $name = $wgAuth->getCanonicalName( $t->getText() );
+
+ switch ( $validate ) {
+ case false:
+ break;
+ case 'valid':
+ if ( !User::isValidUserName( $name ) ) {
+ $name = false;
+ }
+ break;
+ case 'usable':
+ if ( !User::isUsableName( $name ) ) {
+ $name = false;
+ }
+ break;
+ case 'creatable':
+ if ( !User::isCreatableName( $name ) ) {
+ $name = false;
+ }
+ break;
+ default:
+ throw new MWException( 'Invalid parameter value for $validate in '.__METHOD__ );
+ }
+ return $name;
+ }
+
+ /**
* Count the number of edits of a user
*
* @param int $uid The user ID to check
* @return int
+ * @static
*/
- function edits( $uid ) {
- $fname = 'User::edits';
-
+ static function edits( $uid ) {
$dbr =& wfGetDB( DB_SLAVE );
return $dbr->selectField(
'revision', 'count(*)',
array( 'rev_user' => $uid ),
- $fname
+ __METHOD__
);
}
/**
- * probably return a random password
- * @return string probably a random password
+ * Return a random password. Sourced from mt_rand, so it's not particularly secure.
+ * @todo: hash random numbers to improve security, like generateToken()
+ *
+ * @return string
* @static
- * @todo Check what is doing really [AV]
*/
- function randomPassword() {
+ static function randomPassword() {
global $wgMinimalPasswordLength;
$pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
$l = strlen( $pwchars ) - 1;
@@ -405,57 +576,196 @@ class User {
}
/**
- * Set properties to default
- * Used at construction. It will load per language default settings only
- * if we have an available language object.
+ * Set cached properties to default. Note: this no longer clears
+ * uncached lazy-initialised properties. The constructor does that instead.
+ *
+ * @private
*/
- function loadDefaults() {
- static $n=0;
- $n++;
- $fname = 'User::loadDefaults' . $n;
- wfProfileIn( $fname );
+ function loadDefaults( $name = false ) {
+ wfProfileIn( __METHOD__ );
global $wgCookiePrefix;
- global $wgNamespacesToBeSearchedDefault;
$this->mId = 0;
- $this->mNewtalk = -1;
- $this->mName = false;
- $this->mRealName = $this->mEmail = '';
- $this->mEmailAuthenticated = null;
+ $this->mName = $name;
+ $this->mRealName = '';
$this->mPassword = $this->mNewpassword = '';
- $this->mRights = array();
- $this->mGroups = array();
- $this->mOptions = null;
- $this->mDatePreference = null;
-
- unset( $this->mSkin );
- $this->mDataLoaded = false;
- $this->mBlockedby = -1; # Unset
- $this->setToken(); # Random
- $this->mHash = false;
+ $this->mNewpassTime = null;
+ $this->mEmail = '';
+ $this->mOptions = null; # Defer init
if ( isset( $_COOKIE[$wgCookiePrefix.'LoggedOut'] ) ) {
$this->mTouched = wfTimestamp( TS_MW, $_COOKIE[$wgCookiePrefix.'LoggedOut'] );
- }
- else {
+ } else {
$this->mTouched = '0'; # Allow any pages to be cached
}
+ $this->setToken(); # Random
+ $this->mEmailAuthenticated = null;
+ $this->mEmailToken = '';
+ $this->mEmailTokenExpires = null;
$this->mRegistration = wfTimestamp( TS_MW );
+ $this->mGroups = array();
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Initialise php session
+ * @deprecated use wfSetupSession()
+ */
+ function SetupSession() {
+ wfSetupSession();
+ }
+
+ /**
+ * Load user data from the session or login cookie. If there are no valid
+ * credentials, initialises the user as an anon.
+ * @return true if the user is logged in, false otherwise
+ *
+ * @private
+ */
+ function loadFromSession() {
+ global $wgMemc, $wgCookiePrefix;
+
+ if ( isset( $_SESSION['wsUserID'] ) ) {
+ if ( 0 != $_SESSION['wsUserID'] ) {
+ $sId = $_SESSION['wsUserID'];
+ } else {
+ $this->loadDefaults();
+ return false;
+ }
+ } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserID"] ) ) {
+ $sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] );
+ $_SESSION['wsUserID'] = $sId;
+ } else {
+ $this->loadDefaults();
+ return false;
+ }
+ if ( isset( $_SESSION['wsUserName'] ) ) {
+ $sName = $_SESSION['wsUserName'];
+ } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserName"] ) ) {
+ $sName = $_COOKIE["{$wgCookiePrefix}UserName"];
+ $_SESSION['wsUserName'] = $sName;
+ } else {
+ $this->loadDefaults();
+ return false;
+ }
+
+ $passwordCorrect = FALSE;
+ $this->mId = $sId;
+ if ( !$this->loadFromId() ) {
+ # Not a valid ID, loadFromId has switched the object to anon for us
+ return false;
+ }
+
+ if ( isset( $_SESSION['wsToken'] ) ) {
+ $passwordCorrect = $_SESSION['wsToken'] == $this->mToken;
+ $from = 'session';
+ } else if ( isset( $_COOKIE["{$wgCookiePrefix}Token"] ) ) {
+ $passwordCorrect = $this->mToken == $_COOKIE["{$wgCookiePrefix}Token"];
+ $from = 'cookie';
+ } else {
+ # No session or persistent login cookie
+ $this->loadDefaults();
+ return false;
+ }
+
+ if ( ( $sName == $this->mName ) && $passwordCorrect ) {
+ wfDebug( "Logged in from $from\n" );
+ return true;
+ } else {
+ # Invalid credentials
+ wfDebug( "Can't log in from $from, invalid credentials\n" );
+ $this->loadDefaults();
+ return false;
+ }
+ }
+
+ /**
+ * Load user and user_group data from the database
+ * $this->mId must be set, this is how the user is identified.
+ *
+ * @return true if the user exists, false if the user is anonymous
+ * @private
+ */
+ function loadFromDatabase() {
+ # Paranoia
+ $this->mId = intval( $this->mId );
+
+ /** Anonymous user */
+ if( !$this->mId ) {
+ $this->loadDefaults();
+ return false;
+ }
- wfProfileOut( $fname );
+ $dbr =& wfGetDB( DB_MASTER );
+ $s = $dbr->selectRow( 'user', '*', array( 'user_id' => $this->mId ), __METHOD__ );
+
+ if ( $s !== false ) {
+ # Initialise user table data
+ $this->mName = $s->user_name;
+ $this->mRealName = $s->user_real_name;
+ $this->mPassword = $s->user_password;
+ $this->mNewpassword = $s->user_newpassword;
+ $this->mNewpassTime = wfTimestampOrNull( TS_MW, $s->user_newpass_time );
+ $this->mEmail = $s->user_email;
+ $this->decodeOptions( $s->user_options );
+ $this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
+ $this->mToken = $s->user_token;
+ $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $s->user_email_authenticated );
+ $this->mEmailToken = $s->user_email_token;
+ $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $s->user_email_token_expires );
+ $this->mRegistration = wfTimestampOrNull( TS_MW, $s->user_registration );
+
+ # Load group data
+ $res = $dbr->select( 'user_groups',
+ array( 'ug_group' ),
+ array( 'ug_user' => $this->mId ),
+ __METHOD__ );
+ $this->mGroups = array();
+ while( $row = $dbr->fetchObject( $res ) ) {
+ $this->mGroups[] = $row->ug_group;
+ }
+ return true;
+ } else {
+ # Invalid user_id
+ $this->mId = 0;
+ $this->loadDefaults();
+ return false;
+ }
+ }
+
+ /**
+ * Clear various cached data stored in this object.
+ * @param string $reloadFrom Reload user and user_groups table data from a
+ * given source. May be "name", "id", "defaults", "session" or false for
+ * no reload.
+ */
+ function clearInstanceCache( $reloadFrom = false ) {
+ $this->mNewtalk = -1;
+ $this->mDatePreference = null;
+ $this->mBlockedby = -1; # Unset
+ $this->mHash = false;
+ $this->mSkin = null;
+ $this->mRights = null;
+ $this->mEffectiveGroups = null;
+
+ if ( $reloadFrom ) {
+ $this->mDataLoaded = false;
+ $this->mFrom = $reloadFrom;
+ }
}
/**
* Combine the language default options with any site-specific options
* and add the default language variants.
- *
+ * Not really private cause it's called by Language class
* @return array
* @static
* @private
*/
- function getDefaultOptions() {
+ static function getDefaultOptions() {
global $wgNamespacesToBeSearchedDefault;
/**
* Site defaults will override the global/language defaults
@@ -520,18 +830,22 @@ class User {
return;
}
- $fname = 'User::getBlockedStatus';
- wfProfileIn( $fname );
- wfDebug( "$fname: checking...\n" );
+ wfProfileIn( __METHOD__ );
+ wfDebug( __METHOD__.": checking...\n" );
$this->mBlockedby = 0;
$ip = wfGetIP();
+ if ($this->isAllowed( 'ipblock-exempt' ) ) {
+ # Exempt from all types of IP-block
+ $ip = '';
+ }
+
# User/IP blocking
$this->mBlock = new Block();
$this->mBlock->fromMaster( !$bFromSlave );
if ( $this->mBlock->load( $ip , $this->mId ) ) {
- wfDebug( "$fname: Found block.\n" );
+ wfDebug( __METHOD__.": Found block.\n" );
$this->mBlockedby = $this->mBlock->mBy;
$this->mBlockreason = $this->mBlock->mReason;
if ( $this->isLoggedIn() ) {
@@ -539,7 +853,7 @@ class User {
}
} else {
$this->mBlock = null;
- wfDebug( "$fname: No block.\n" );
+ wfDebug( __METHOD__.": No block.\n" );
}
# Proxy blocking
@@ -563,22 +877,23 @@ class User {
# Extensions
wfRunHooks( 'GetBlockedStatus', array( &$this ) );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
}
function inSorbsBlacklist( $ip ) {
- global $wgEnableSorbs;
+ global $wgEnableSorbs, $wgSorbsUrl;
+
return $wgEnableSorbs &&
- $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
+ $this->inDnsBlacklist( $ip, $wgSorbsUrl );
}
function inDnsBlacklist( $ip, $base ) {
- $fname = 'User::inDnsBlacklist';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
$found = false;
$host = '';
+ $m = array();
if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) {
# Make hostname
for ( $i=4; $i>=1; $i-- ) {
@@ -597,7 +912,7 @@ class User {
}
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $found;
}
@@ -612,6 +927,13 @@ class User {
* @public
*/
function pingLimiter( $action='edit' ) {
+
+ # Call the 'PingLimiter' hook
+ $result = false;
+ if( !wfRunHooks( 'PingLimiter', array( &$this, $action, $result ) ) ) {
+ return $result;
+ }
+
global $wgRateLimits, $wgRateLimitsExcludedGroups;
if( !isset( $wgRateLimits[$action] ) ) {
return false;
@@ -624,8 +946,7 @@ class User {
}
global $wgMemc, $wgRateLimitLog;
- $fname = 'User::pingLimiter';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
$limits = $wgRateLimits[$action];
$keys = array();
@@ -646,6 +967,7 @@ class User {
if( isset( $limits['ip'] ) ) {
$keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
}
+ $matches = array();
if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
$subnet = $matches[1];
$keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
@@ -659,22 +981,22 @@ class User {
$count = $wgMemc->get( $key );
if( $count ) {
if( $count > $max ) {
- wfDebug( "$fname: tripped! $key at $count $summary\n" );
+ wfDebug( __METHOD__.": tripped! $key at $count $summary\n" );
if( $wgRateLimitLog ) {
@error_log( wfTimestamp( TS_MW ) . ' ' . wfWikiID() . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
}
$triggered = true;
} else {
- wfDebug( "$fname: ok. $key at $count $summary\n" );
+ wfDebug( __METHOD__.": ok. $key at $count $summary\n" );
}
} else {
- wfDebug( "$fname: adding record for $key $summary\n" );
+ wfDebug( __METHOD__.": adding record for $key $summary\n" );
$wgMemc->add( $key, 1, intval( $period ) );
}
$wgMemc->incr( $key );
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $triggered;
}
@@ -693,20 +1015,19 @@ class User {
*/
function isBlockedFrom( $title, $bFromSlave = false ) {
global $wgBlockAllowsUTEdit;
- $fname = 'User::isBlockedFrom';
- wfProfileIn( $fname );
- wfDebug( "$fname: enter\n" );
+ wfProfileIn( __METHOD__ );
+ wfDebug( __METHOD__.": enter\n" );
if ( $wgBlockAllowsUTEdit && $title->getText() === $this->getName() &&
$title->getNamespace() == NS_USER_TALK )
{
$blocked = false;
- wfDebug( "$fname: self-talk page, ignoring any blocks\n" );
+ wfDebug( __METHOD__.": self-talk page, ignoring any blocks\n" );
} else {
- wfDebug( "$fname: asking isBlocked()\n" );
+ wfDebug( __METHOD__.": asking isBlocked()\n" );
$blocked = $this->isBlocked( $bFromSlave );
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $blocked;
}
@@ -729,174 +1050,55 @@ class User {
}
/**
- * Initialise php session
- * @deprecated use wfSetupSession()
+ * Get the user ID. Returns 0 if the user is anonymous or nonexistent.
*/
- function SetupSession() {
- wfSetupSession();
- }
-
- /**
- * Create a new user object using data from session
- * @static
- */
- function loadFromSession() {
- global $wgMemc, $wgCookiePrefix;
-
- if ( isset( $_SESSION['wsUserID'] ) ) {
- if ( 0 != $_SESSION['wsUserID'] ) {
- $sId = $_SESSION['wsUserID'];
- } else {
- return new User();
- }
- } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserID"] ) ) {
- $sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] );
- $_SESSION['wsUserID'] = $sId;
- } else {
- return new User();
- }
- if ( isset( $_SESSION['wsUserName'] ) ) {
- $sName = $_SESSION['wsUserName'];
- } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserName"] ) ) {
- $sName = $_COOKIE["{$wgCookiePrefix}UserName"];
- $_SESSION['wsUserName'] = $sName;
- } else {
- return new User();
- }
-
- $passwordCorrect = FALSE;
- $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;
- }
- if($makenew = !$user) {
- wfDebug( "User::loadFromSession() unable to load from memcached\n" );
- $user = new User();
- $user->mId = $sId;
- $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'] ) ) {
- $passwordCorrect = $_SESSION['wsToken'] == $user->mToken;
- } else if ( isset( $_COOKIE["{$wgCookiePrefix}Token"] ) ) {
- $passwordCorrect = $user->mToken == $_COOKIE["{$wgCookiePrefix}Token"];
- } else {
- return new User(); # Can't log in from session
- }
-
- if ( ( $sName == $user->mName ) && $passwordCorrect ) {
- if($makenew) {
- if($wgMemc->set( $key, $user ))
- wfDebug( "User::loadFromSession() successfully saved user\n" );
- else
- wfDebug( "User::loadFromSession() unable to save to memcached\n" );
- }
- return $user;
- }
- return new User(); # Can't log in from session
+ function getID() {
+ $this->load();
+ return $this->mId;
}
/**
- * Load a user from the database
+ * Set the user and reload all fields according to that ID
+ * @deprecated use User::newFromId()
*/
- function loadFromDatabase() {
- $fname = "User::loadFromDatabase";
-
- # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
- # loading in a command line script, don't assume all command line scripts need it like this
- #if ( $this->mDataLoaded || $wgCommandLineMode ) {
- if ( $this->mDataLoaded ) {
- return;
- }
-
- # Paranoia
- $this->mId = intval( $this->mId );
-
- /** Anonymous user */
- if( !$this->mId ) {
- /** Get rights */
- $this->mRights = $this->getGroupPermissions( array( '*' ) );
- $this->mDataLoaded = true;
- return;
- } # the following stuff is for non-anonymous users only
-
- $dbr =& wfGetDB( DB_SLAVE );
- $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
- 'user_email_authenticated',
- 'user_real_name','user_options','user_touched', 'user_token', 'user_registration' ),
- array( 'user_id' => $this->mId ), $fname );
-
- if ( $s !== false ) {
- $this->mName = $s->user_name;
- $this->mEmail = $s->user_email;
- $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $s->user_email_authenticated );
- $this->mRealName = $s->user_real_name;
- $this->mPassword = $s->user_password;
- $this->mNewpassword = $s->user_newpassword;
- $this->decodeOptions( $s->user_options );
- $this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
- $this->mToken = $s->user_token;
- $this->mRegistration = wfTimestampOrNull( TS_MW, $s->user_registration );
-
- $res = $dbr->select( 'user_groups',
- array( 'ug_group' ),
- array( 'ug_user' => $this->mId ),
- $fname );
- $this->mGroups = array();
- while( $row = $dbr->fetchObject( $res ) ) {
- $this->mGroups[] = $row->ug_group;
- }
- $implicitGroups = array( '*', 'user' );
-
- global $wgAutoConfirmAge;
- $accountAge = time() - wfTimestampOrNull( TS_UNIX, $this->mRegistration );
- if( $accountAge >= $wgAutoConfirmAge ) {
- $implicitGroups[] = 'autoconfirmed';
- }
-
- # Implicit group for users whose email addresses are confirmed
- global $wgEmailAuthentication;
- if( $this->isValidEmailAddr( $this->mEmail ) ) {
- if( $wgEmailAuthentication ) {
- if( $this->mEmailAuthenticated )
- $implicitGroups[] = 'emailconfirmed';
- } else {
- $implicitGroups[] = 'emailconfirmed';
- }
- }
-
- $effectiveGroups = array_merge( $implicitGroups, $this->mGroups );
- $this->mRights = $this->getGroupPermissions( $effectiveGroups );
- }
-
- $this->mDataLoaded = true;
- }
-
- function getID() { return $this->mId; }
function setID( $v ) {
$this->mId = $v;
- $this->mDataLoaded = false;
+ $this->clearInstanceCache( 'id' );
}
+ /**
+ * Get the user name, or the IP for anons
+ */
function getName() {
- $this->loadFromDatabase();
- if ( $this->mName === false ) {
- $this->mName = wfGetIP();
+ if ( !$this->mDataLoaded && $this->mFrom == 'name' ) {
+ # Special case optimisation
+ return $this->mName;
+ } else {
+ $this->load();
+ if ( $this->mName === false ) {
+ $this->mName = wfGetIP();
+ }
+ return $this->mName;
}
- return $this->mName;
}
+ /**
+ * Set the user name.
+ *
+ * This does not reload fields from the database according to the given
+ * name. Rather, it is used to create a temporary "nonexistent user" for
+ * later addition to the database. It can also be used to set the IP
+ * address for an anonymous user to something other than the current
+ * remote IP.
+ *
+ * User::newFromName() has rougly the same function, when the named user
+ * does not exist.
+ */
function setName( $str ) {
- $this->loadFromDatabase();
+ $this->load();
$this->mName = $str;
}
-
/**
* Return the title dbkey form of the name, for eg user pages.
* @return string
@@ -907,7 +1109,7 @@ class User {
}
function getNewtalk() {
- $this->loadFromDatabase();
+ $this->load();
# Load the newtalk status if it is unloaded (mNewtalk=-1)
if( $this->mNewtalk === -1 ) {
@@ -960,10 +1162,9 @@ class User {
* @private
*/
function checkNewtalk( $field, $id ) {
- $fname = 'User::checkNewtalk';
$dbr =& wfGetDB( DB_SLAVE );
$ok = $dbr->selectField( 'user_newtalk', $field,
- array( $field => $id ), $fname );
+ array( $field => $id ), __METHOD__ );
return $ok !== false;
}
@@ -974,17 +1175,16 @@ class User {
* @private
*/
function updateNewtalk( $field, $id ) {
- $fname = 'User::updateNewtalk';
if( $this->checkNewtalk( $field, $id ) ) {
- wfDebug( "$fname already set ($field, $id), ignoring\n" );
+ wfDebug( __METHOD__." already set ($field, $id), ignoring\n" );
return false;
}
$dbw =& wfGetDB( DB_MASTER );
$dbw->insert( 'user_newtalk',
array( $field => $id ),
- $fname,
+ __METHOD__,
'IGNORE' );
- wfDebug( "$fname: set on ($field, $id)\n" );
+ wfDebug( __METHOD__.": set on ($field, $id)\n" );
return true;
}
@@ -995,16 +1195,15 @@ class User {
* @private
*/
function deleteNewtalk( $field, $id ) {
- $fname = 'User::deleteNewtalk';
if( !$this->checkNewtalk( $field, $id ) ) {
- wfDebug( "$fname: already gone ($field, $id), ignoring\n" );
+ wfDebug( __METHOD__.": already gone ($field, $id), ignoring\n" );
return false;
}
$dbw =& wfGetDB( DB_MASTER );
$dbw->delete( 'user_newtalk',
array( $field => $id ),
- $fname );
- wfDebug( "$fname: killed on ($field, $id)\n" );
+ __METHOD__ );
+ wfDebug( __METHOD__.": killed on ($field, $id)\n" );
return true;
}
@@ -1017,11 +1216,9 @@ class User {
return;
}
- $this->loadFromDatabase();
+ $this->load();
$this->mNewtalk = $val;
- $fname = 'User::setNewtalk';
-
if( $this->isAnon() ) {
$field = 'user_ip';
$id = $this->getName();
@@ -1070,7 +1267,7 @@ class User {
*
* Called implicitly from invalidateCache() and saveSettings().
*/
- private function clearUserCache() {
+ private function clearSharedCache() {
if( $this->mId ) {
global $wgMemc;
$wgMemc->delete( wfMemcKey( 'user', 'id', $this->mId ) );
@@ -1083,6 +1280,7 @@ class User {
* for reload on the next hit.
*/
function invalidateCache() {
+ $this->load();
if( $this->mId ) {
$this->mTouched = self::newTouchedTimestamp();
@@ -1092,12 +1290,12 @@ class User {
array( 'user_id' => $this->mId ),
__METHOD__ );
- $this->clearUserCache();
+ $this->clearSharedCache();
}
}
function validateCache( $timestamp ) {
- $this->loadFromDatabase();
+ $this->load();
return ($timestamp >= $this->mTouched);
}
@@ -1108,20 +1306,66 @@ class User {
* @return string Encrypted password.
*/
function encryptPassword( $p ) {
+ $this->load();
return wfEncryptPassword( $this->mId, $p );
}
- # Set the password and reset the random token
+ /**
+ * Set the password and reset the random token
+ * Calls through to authentication plugin if necessary;
+ * will have no effect if the auth plugin refuses to
+ * pass the change through or if the legal password
+ * checks fail.
+ *
+ * As a special case, setting the password to null
+ * wipes it, so the account cannot be logged in until
+ * a new password is set, for instance via e-mail.
+ *
+ * @param string $str
+ * @throws PasswordError on failure
+ */
function setPassword( $str ) {
- $this->loadFromDatabase();
+ global $wgAuth;
+
+ if( $str !== null ) {
+ if( !$wgAuth->allowPasswordChange() ) {
+ throw new PasswordError( wfMsg( 'password-change-forbidden' ) );
+ }
+
+ if( !$this->isValidPassword( $str ) ) {
+ global $wgMinimalPasswordLength;
+ throw new PasswordError( wfMsg( 'passwordtooshort',
+ $wgMinimalPasswordLength ) );
+ }
+ }
+
+ if( !$wgAuth->setPassword( $this, $str ) ) {
+ throw new PasswordError( wfMsg( 'externaldberror' ) );
+ }
+
+ $this->load();
$this->setToken();
- $this->mPassword = $this->encryptPassword( $str );
+
+ if( $str === null ) {
+ // Save an invalid hash...
+ $this->mPassword = '';
+ } else {
+ $this->mPassword = $this->encryptPassword( $str );
+ }
$this->mNewpassword = '';
+ $this->mNewpassTime = null;
+
+ return true;
}
- # Set the random token (used for persistent authentication)
+ /**
+ * Set the random token (used for persistent authentication)
+ * Called from loadDefaults() among other places.
+ * @private
+ */
function setToken( $token = false ) {
global $wgSecretKey, $wgProxyKey;
+ $this->load();
if ( !$token ) {
if ( $wgSecretKey ) {
$key = $wgSecretKey;
@@ -1136,55 +1380,81 @@ class User {
}
}
-
function setCookiePassword( $str ) {
- $this->loadFromDatabase();
+ $this->load();
$this->mCookiePassword = md5( $str );
}
- function setNewpassword( $str ) {
- $this->loadFromDatabase();
+ /**
+ * Set the password for a password reminder or new account email
+ * Sets the user_newpass_time field if $throttle is true
+ */
+ function setNewpassword( $str, $throttle = true ) {
+ $this->load();
$this->mNewpassword = $this->encryptPassword( $str );
+ if ( $throttle ) {
+ $this->mNewpassTime = wfTimestampNow();
+ }
}
+ /**
+ * Returns true if a password reminder email has already been sent within
+ * the last $wgPasswordReminderResendTime hours
+ */
+ function isPasswordReminderThrottled() {
+ global $wgPasswordReminderResendTime;
+ $this->load();
+ if ( !$this->mNewpassTime || !$wgPasswordReminderResendTime ) {
+ return false;
+ }
+ $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgPasswordReminderResendTime * 3600;
+ return time() < $expiry;
+ }
+
function getEmail() {
- $this->loadFromDatabase();
+ $this->load();
return $this->mEmail;
}
function getEmailAuthenticationTimestamp() {
- $this->loadFromDatabase();
+ $this->load();
return $this->mEmailAuthenticated;
}
function setEmail( $str ) {
- $this->loadFromDatabase();
+ $this->load();
$this->mEmail = $str;
}
function getRealName() {
- $this->loadFromDatabase();
+ $this->load();
return $this->mRealName;
}
function setRealName( $str ) {
- $this->loadFromDatabase();
+ $this->load();
$this->mRealName = $str;
}
/**
* @param string $oname The option to check
+ * @param string $defaultOverride A default value returned if the option does not exist
* @return string
*/
- function getOption( $oname ) {
- $this->loadFromDatabase();
+ function getOption( $oname, $defaultOverride = '' ) {
+ $this->load();
+
if ( is_null( $this->mOptions ) ) {
+ if($defaultOverride != '') {
+ return $defaultOverride;
+ }
$this->mOptions = User::getDefaultOptions();
}
+
if ( array_key_exists( $oname, $this->mOptions ) ) {
return trim( $this->mOptions[$oname] );
} else {
- return '';
+ return $defaultOverride;
}
}
@@ -1228,7 +1498,7 @@ class User {
}
function setOption( $oname, $val ) {
- $this->loadFromDatabase();
+ $this->load();
if ( is_null( $this->mOptions ) ) {
$this->mOptions = User::getDefaultOptions();
}
@@ -1245,7 +1515,9 @@ class User {
}
function getRights() {
- $this->loadFromDatabase();
+ if ( is_null( $this->mRights ) ) {
+ $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
+ }
return $this->mRights;
}
@@ -1255,7 +1527,7 @@ class User {
* @return array of strings
*/
function getGroups() {
- $this->loadFromDatabase();
+ $this->load();
return $this->mGroups;
}
@@ -1263,14 +1535,36 @@ class User {
* Get the list of implicit group memberships this user has.
* This includes all explicit groups, plus 'user' if logged in
* and '*' for all accounts.
+ * @param boolean $recache Don't use the cache
* @return array of strings
*/
- function getEffectiveGroups() {
- $base = array( '*' );
- if( $this->isLoggedIn() ) {
- $base[] = 'user';
+ function getEffectiveGroups( $recache = false ) {
+ if ( $recache || is_null( $this->mEffectiveGroups ) ) {
+ $this->load();
+ $this->mEffectiveGroups = $this->mGroups;
+ $this->mEffectiveGroups[] = '*';
+ if( $this->mId ) {
+ $this->mEffectiveGroups[] = 'user';
+
+ global $wgAutoConfirmAge;
+ $accountAge = time() - wfTimestampOrNull( TS_UNIX, $this->mRegistration );
+ if( $accountAge >= $wgAutoConfirmAge ) {
+ $this->mEffectiveGroups[] = 'autoconfirmed';
+ }
+
+ # Implicit group for users whose email addresses are confirmed
+ global $wgEmailAuthentication;
+ if( self::isValidEmailAddr( $this->mEmail ) ) {
+ if( $wgEmailAuthentication ) {
+ if( $this->mEmailAuthenticated )
+ $this->mEffectiveGroups[] = 'emailconfirmed';
+ } else {
+ $this->mEffectiveGroups[] = 'emailconfirmed';
+ }
+ }
+ }
}
- return array_merge( $base, $this->getGroups() );
+ return $this->mEffectiveGroups;
}
/**
@@ -1279,17 +1573,20 @@ class User {
* @string $group
*/
function addGroup( $group ) {
+ $this->load();
$dbw =& wfGetDB( DB_MASTER );
- $dbw->insert( 'user_groups',
- array(
- 'ug_user' => $this->getID(),
- 'ug_group' => $group,
- ),
- 'User::addGroup',
- array( 'IGNORE' ) );
+ if( $this->getId() ) {
+ $dbw->insert( 'user_groups',
+ array(
+ 'ug_user' => $this->getID(),
+ 'ug_group' => $group,
+ ),
+ 'User::addGroup',
+ array( 'IGNORE' ) );
+ }
- $this->mGroups = array_merge( $this->mGroups, array( $group ) );
- $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );
+ $this->mGroups[] = $group;
+ $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) );
$this->invalidateCache();
}
@@ -1300,6 +1597,7 @@ class User {
* @string $group
*/
function removeGroup( $group ) {
+ $this->load();
$dbw =& wfGetDB( DB_MASTER );
$dbw->delete( 'user_groups',
array(
@@ -1309,7 +1607,7 @@ class User {
'User::removeGroup' );
$this->mGroups = array_diff( $this->mGroups, array( $group ) );
- $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );
+ $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) );
$this->invalidateCache();
}
@@ -1353,8 +1651,7 @@ class User {
// In the spirit of DWIM
return true;
- $this->loadFromDatabase();
- return in_array( $action , $this->mRights );
+ return in_array( $action, $this->getRights() );
}
/**
@@ -1362,17 +1659,16 @@ class User {
* @todo FIXME : need to check the old failback system [AV]
*/
function &getSkin() {
- global $IP, $wgRequest;
+ global $wgRequest;
if ( ! isset( $this->mSkin ) ) {
- $fname = 'User::getSkin';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
# get the user skin
$userSkin = $this->getOption( 'skin' );
$userSkin = $wgRequest->getVal('useskin', $userSkin);
$this->mSkin =& Skin::newFromKey( $userSkin );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
}
return $this->mSkin;
}
@@ -1416,6 +1712,10 @@ class User {
function clearNotification( &$title ) {
global $wgUser, $wgUseEnotif;
+ # Do nothing if the database is locked to writes
+ if( wfReadOnly() ) {
+ return;
+ }
if ($title->getNamespace() == NS_USER_TALK &&
$title->getText() == $this->getName() ) {
@@ -1451,7 +1751,7 @@ class User {
// any matching rows
if ( $watched ) {
$dbw =& wfGetDB( DB_MASTER );
- $success = $dbw->update( 'watchlist',
+ $dbw->update( 'watchlist',
array( /* SET */
'wl_notificationtimestamp' => NULL
), array( /* WHERE */
@@ -1482,7 +1782,7 @@ class User {
if( $currentUser != 0 ) {
$dbw =& wfGetDB( DB_MASTER );
- $success = $dbw->update( 'watchlist',
+ $dbw->update( 'watchlist',
array( /* SET */
'wl_notificationtimestamp' => NULL
), array( /* WHERE */
@@ -1500,6 +1800,7 @@ class User {
* @return string Encoding options
*/
function encodeOptions() {
+ $this->load();
if ( is_null( $this->mOptions ) ) {
$this->mOptions = User::getDefaultOptions();
}
@@ -1515,11 +1816,10 @@ class User {
* @private
*/
function decodeOptions( $str ) {
- global $wgLang;
-
$this->mOptions = array();
$a = explode( "\n", $str );
foreach ( $a as $s ) {
+ $m = array();
if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
$this->mOptions[$m[1]] = $m[2];
}
@@ -1528,8 +1828,8 @@ class User {
function setCookies() {
global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookiePrefix;
+ $this->load();
if ( 0 == $this->mId ) return;
- $this->loadFromDatabase();
$exp = time() + $wgCookieExpiration;
$_SESSION['wsUserID'] = $this->mId;
@@ -1548,12 +1848,11 @@ class User {
/**
* Logout user
- * It will clean the session cookie
+ * Clears the cookies and session, resets the instance cache
*/
function logout() {
global $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookiePrefix;
- $this->loadDefaults();
- $this->setLoaded( true );
+ $this->clearInstanceCache( 'defaults' );
$_SESSION['wsUserID'] = 0;
@@ -1569,8 +1868,7 @@ class User {
* @fixme Only rarely do all these fields need to be set!
*/
function saveSettings() {
- $fname = 'User::saveSettings';
-
+ $this->load();
if ( wfReadOnly() ) { return; }
if ( 0 == $this->mId ) { return; }
@@ -1582,6 +1880,7 @@ class User {
'user_name' => $this->mName,
'user_password' => $this->mPassword,
'user_newpassword' => $this->mNewpassword,
+ 'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ),
'user_real_name' => $this->mRealName,
'user_email' => $this->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
@@ -1590,9 +1889,9 @@ class User {
'user_token' => $this->mToken
), array( /* WHERE */
'user_id' => $this->mId
- ), $fname
+ ), __METHOD__
);
- $this->clearUserCache();
+ $this->clearSharedCache();
}
@@ -1600,14 +1899,11 @@ class User {
* Checks if a user with the given name exists, returns the ID
*/
function idForName() {
- $fname = 'User::idForName';
-
- $gotid = 0;
$s = trim( $this->getName() );
if ( 0 == strcmp( '', $s ) ) return 0;
$dbr =& wfGetDB( DB_SLAVE );
- $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
+ $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), __METHOD__ );
if ( $id === false ) {
$id = 0;
}
@@ -1615,10 +1911,61 @@ class User {
}
/**
- * Add user object to the database
+ * Add a user to the database, return the user object
+ *
+ * @param string $name The user's name
+ * @param array $params Associative array of non-default parameters to save to the database:
+ * password The user's password. Password logins will be disabled if this is omitted.
+ * newpassword A temporary password mailed to the user
+ * email The user's email address
+ * email_authenticated The email authentication timestamp
+ * real_name The user's real name
+ * options An associative array of non-default options
+ * token Random authentication token. Do not set.
+ * registration Registration timestamp. Do not set.
+ *
+ * @return User object, or null if the username already exists
+ */
+ static function createNew( $name, $params = array() ) {
+ $user = new User;
+ $user->load();
+ if ( isset( $params['options'] ) ) {
+ $user->mOptions = $params['options'] + $user->mOptions;
+ unset( $params['options'] );
+ }
+ $dbw =& wfGetDB( DB_MASTER );
+ $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
+ $fields = array(
+ 'user_id' => $seqVal,
+ 'user_name' => $name,
+ 'user_password' => $user->mPassword,
+ 'user_newpassword' => $user->mNewpassword,
+ 'user_newpass_time' => $dbw->timestamp( $user->mNewpassTime ),
+ 'user_email' => $user->mEmail,
+ 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
+ 'user_real_name' => $user->mRealName,
+ 'user_options' => $user->encodeOptions(),
+ 'user_token' => $user->mToken,
+ 'user_registration' => $dbw->timestamp( $user->mRegistration ),
+ 'user_editcount' => 0,
+ );
+ foreach ( $params as $name => $value ) {
+ $fields["user_$name"] = $value;
+ }
+ $dbw->insert( 'user', $fields, __METHOD__, array( 'IGNORE' ) );
+ if ( $dbw->affectedRows() ) {
+ $newUser = User::newFromId( $dbw->insertId() );
+ } else {
+ $newUser = null;
+ }
+ return $newUser;
+ }
+
+ /**
+ * Add an existing user object to the database
*/
function addToDatabase() {
- $fname = 'User::addToDatabase';
+ $this->load();
$dbw =& wfGetDB( DB_MASTER );
$seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
$dbw->insert( 'user',
@@ -1627,23 +1974,29 @@ class User {
'user_name' => $this->mName,
'user_password' => $this->mPassword,
'user_newpassword' => $this->mNewpassword,
+ 'user_newpass_time' => $dbw->timestamp( $this->mNewpassTime ),
'user_email' => $this->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_real_name' => $this->mRealName,
'user_options' => $this->encodeOptions(),
'user_token' => $this->mToken,
'user_registration' => $dbw->timestamp( $this->mRegistration ),
- ), $fname
+ 'user_editcount' => 0,
+ ), __METHOD__
);
$this->mId = $dbw->insertId();
+
+ # Clear instance cache other than user table data, which is already accurate
+ $this->clearInstanceCache();
}
+ /**
+ * If the (non-anonymous) user is blocked, this function will block any IP address
+ * that they successfully log on from.
+ */
function spreadBlock() {
- # If the (non-anonymous) user is blocked, this function will block any IP address
- # that they successfully log on from.
- $fname = 'User::spreadBlock';
-
- wfDebug( "User:spreadBlock()\n" );
+ wfDebug( __METHOD__."()\n" );
+ $this->load();
if ( $this->mId == 0 ) {
return;
}
@@ -1653,41 +2006,7 @@ class User {
return;
}
- # Check if this IP address is already blocked
- $ipblock = Block::newFromDB( wfGetIP() );
- 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
- if ($userblock->mExpiry &&
- ($userblock->mExpiry < Block::getAutoblockExpiry($ipblock->mTimestamp))) {
- return;
- }
- # Just update the timestamp
- $ipblock->updateTimestamp();
- return;
- } else {
- $ipblock = new Block;
- }
-
- # Make a new block object with the desired properties
- wfDebug( "Autoblocking {$this->mName}@" . wfGetIP() . "\n" );
- $ipblock->mAddress = wfGetIP();
- $ipblock->mUser = 0;
- $ipblock->mBy = $userblock->mBy;
- $ipblock->mReason = wfMsg( 'autoblocker', $this->getName(), $userblock->mReason );
- $ipblock->mTimestamp = wfTimestampNow();
- $ipblock->mAuto = 1;
- # If the user is already blocked with an expiry date, we don't
- # want to pile on top of that!
- if($userblock->mExpiry) {
- $ipblock->mExpiry = min ( $userblock->mExpiry, Block::getAutoblockExpiry( $ipblock->mTimestamp ));
- } else {
- $ipblock->mExpiry = Block::getAutoblockExpiry( $ipblock->mTimestamp );
- }
-
- # Insert it
- $ipblock->insert();
+ $userblock->doAutoblock( wfGetIp() );
}
@@ -1705,7 +2024,7 @@ class User {
* @return string
*/
function getPageRenderingHash() {
- global $wgContLang, $wgUseDynamicDates;
+ global $wgContLang, $wgUseDynamicDates, $wgLang;
if( $this->mHash ){
return $this->mHash;
}
@@ -1719,7 +2038,7 @@ class User {
$confstr .= '!' . $this->getDatePreference();
}
$confstr .= '!' . ($this->getOption( 'numberheadings' ) ? '1' : '');
- $confstr .= '!' . $this->getOption( 'language' );
+ $confstr .= '!' . $wgLang->getCode();
$confstr .= '!' . $this->getOption( 'thumbsize' );
// add in language specific options, if any
$extra = $wgContLang->getExtraHashOptions();
@@ -1743,12 +2062,9 @@ class User {
}
/**
- * Set mDataLoaded, return previous value
- * Use this to prevent DB access in command-line scripts or similar situations
+ * @deprecated
*/
- function setLoaded( $loaded ) {
- return wfSetVar( $this->mDataLoaded, $loaded );
- }
+ function setLoaded( $loaded ) {}
/**
* Get this user's personal page title.
@@ -1800,15 +2116,15 @@ class User {
* @return bool True if the given password is correct otherwise False.
*/
function checkPassword( $password ) {
- global $wgAuth, $wgMinimalPasswordLength;
- $this->loadFromDatabase();
+ global $wgAuth;
+ $this->load();
// Even though we stop people from creating passwords that
// are shorter than this, doesn't mean people wont be able
// to. Certain authentication plugins do NOT want to save
// domain passwords in a mysql database, so we should
// check this (incase $wgAuth->strict() is false).
- if( strlen( $password ) < $wgMinimalPasswordLength ) {
+ if( !$this->isValidPassword( $password ) ) {
return false;
}
@@ -1821,8 +2137,6 @@ class User {
$ep = $this->encryptPassword( $password );
if ( 0 == strcmp( $ep, $this->mPassword ) ) {
return true;
- } elseif ( ($this->mNewpassword != '') && (0 == strcmp( $ep, $this->mNewpassword )) ) {
- return true;
} 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
@@ -1833,6 +2147,16 @@ class User {
}
return false;
}
+
+ /**
+ * Check if the given clear-text password matches the temporary password
+ * sent by e-mail for password reset operations.
+ * @return bool
+ */
+ function checkTemporaryPassword( $plaintext ) {
+ $hash = $this->encryptPassword( $plaintext );
+ return $hash === $this->mNewpassword;
+ }
/**
* Initialize (if necessary) and return a session token value
@@ -1855,7 +2179,7 @@ class User {
if( is_array( $salt ) ) {
$salt = implode( '|', $salt );
}
- return md5( $token . $salt );
+ return md5( $token . $salt ) . EDIT_TOKEN_SUFFIX;
}
/**
@@ -1896,6 +2220,7 @@ class User {
*/
function sendConfirmationMail() {
global $wgContLang;
+ $expiration = null; // gets passed-by-ref and defined in next line.
$url = $this->confirmationTokenUrl( $expiration );
return $this->sendMail( wfMsg( 'confirmemail_subject' ),
wfMsg( 'confirmemail_body',
@@ -1940,8 +2265,6 @@ class User {
* @private
*/
function confirmationToken( &$expiration ) {
- $fname = 'User::confirmationToken';
-
$now = time();
$expires = $now + 7 * 24 * 60 * 60;
$expiration = wfTimestamp( TS_MW, $expires );
@@ -1954,7 +2277,7 @@ class User {
array( 'user_email_token' => $hash,
'user_email_token_expires' => $dbw->timestamp( $expires ) ),
array( 'user_id' => $this->mId ),
- $fname );
+ __METHOD__ );
return $token;
}
@@ -1968,7 +2291,7 @@ class User {
*/
function confirmationTokenUrl( &$expiration ) {
$token = $this->confirmationToken( $expiration );
- $title = Title::makeTitle( NS_SPECIAL, 'Confirmemail/' . $token );
+ $title = SpecialPage::getTitleFor( 'Confirmemail', $token );
return $title->getFullUrl();
}
@@ -1976,7 +2299,7 @@ class User {
* Mark the e-mail address confirmed and save.
*/
function confirmEmail() {
- $this->loadFromDatabase();
+ $this->load();
$this->mEmailAuthenticated = wfTimestampNow();
$this->saveSettings();
return true;
@@ -2012,12 +2335,12 @@ class User {
*/
function isEmailConfirmed() {
global $wgEmailAuthentication;
- $this->loadFromDatabase();
+ $this->load();
$confirmed = true;
if( wfRunHooks( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) {
if( $this->isAnon() )
return false;
- if( !$this->isValidEmailAddr( $this->mEmail ) )
+ if( !self::isValidEmailAddr( $this->mEmail ) )
return false;
if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
return false;
@@ -2026,6 +2349,18 @@ class User {
return $confirmed;
}
}
+
+ /**
+ * Return true if there is an outstanding request for e-mail confirmation.
+ * @return bool
+ */
+ function isEmailConfirmationPending() {
+ global $wgEmailAuthentication;
+ return $wgEmailAuthentication &&
+ !$this->isEmailConfirmed() &&
+ $this->mEmailToken &&
+ $this->mEmailTokenExpires > wfTimestamp();
+ }
/**
* @param array $groups list of groups
@@ -2145,6 +2480,48 @@ class User {
return $text;
}
}
+
+ /**
+ * Increment the user's edit-count field.
+ * Will have no effect for anonymous users.
+ */
+ function incEditCount() {
+ if( !$this->isAnon() ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'user',
+ array( 'user_editcount=user_editcount+1' ),
+ array( 'user_id' => $this->getId() ),
+ __METHOD__ );
+
+ // Lazy initialization check...
+ if( $dbw->affectedRows() == 0 ) {
+ // Pull from a slave to be less cruel to servers
+ // Accuracy isn't the point anyway here
+ $dbr = wfGetDB( DB_SLAVE );
+ $count = $dbr->selectField( 'revision',
+ 'COUNT(rev_user)',
+ array( 'rev_user' => $this->getId() ),
+ __METHOD__ );
+
+ // Now here's a goddamn hack...
+ if( $dbr !== $dbw ) {
+ // If we actually have a slave server, the count is
+ // at least one behind because the current transaction
+ // has not been committed and replicated.
+ $count++;
+ } else {
+ // But if DB_SLAVE is selecting the master, then the
+ // count we just read includes the revision that was
+ // just added in the working transaction.
+ }
+
+ $dbw->update( 'user',
+ array( 'user_editcount' => $count ),
+ array( 'user_id' => $this->getId() ),
+ __METHOD__ );
+ }
+ }
+ }
}
?>
diff --git a/includes/UserMailer.php b/includes/UserMailer.php
index 78a8be91..0101f744 100644
--- a/includes/UserMailer.php
+++ b/includes/UserMailer.php
@@ -39,7 +39,7 @@ class MailAddress {
* @param string $name Human-readable name if a string address is given
*/
function MailAddress( $address, $name=null ) {
- if( is_object( $address ) && is_a( $address, 'User' ) ) {
+ if( is_object( $address ) && $address instanceof User ) {
$this->address = $address->getEmail();
$this->name = $address->getName();
} else {
@@ -125,14 +125,23 @@ function userMailer( $to, $from, $subject, $body, $replyto=false ) {
} else {
# In the following $headers = expression we removed "Reply-To: {$from}\r\n" , because it is treated differently
# (fifth parameter of the PHP mail function, see some lines below)
+
+ # Line endings need to be different on Unix and Windows due to
+ # the bug described at http://trac.wordpress.org/ticket/2603
+ if ( wfIsWindows() ) {
+ $body = str_replace( "\n", "\r\n", $body );
+ $endl = "\r\n";
+ } else {
+ $endl = "\n";
+ }
$headers =
- "MIME-Version: 1.0\n" .
- "Content-type: text/plain; charset={$wgOutputEncoding}\n" .
- "Content-Transfer-Encoding: 8bit\n" .
- "X-Mailer: MediaWiki mailer\n".
- 'From: ' . $from->toString() . "\n";
+ "MIME-Version: 1.0$endl" .
+ "Content-type: text/plain; charset={$wgOutputEncoding}$endl" .
+ "Content-Transfer-Encoding: 8bit$endl" .
+ "X-Mailer: MediaWiki mailer$endl".
+ 'From: ' . $from->toString();
if ($replyto) {
- $headers .= "Reply-To: $replyto\n";
+ $headers .= "{$endl}Reply-To: $replyto";
}
$dest = $to->toString();
@@ -158,7 +167,7 @@ function userMailer( $to, $from, $subject, $body, $replyto=false ) {
*/
function mailErrorHandler( $code, $string ) {
global $wgErrorString;
- $wgErrorString = preg_replace( "/^mail\(\): /", '', $string );
+ $wgErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string );
}
@@ -239,7 +248,6 @@ class EmailNotification {
}
if( $userCondition ) {
$dbr =& wfGetDB( DB_MASTER );
- extract( $dbr->tableNames( 'watchlist' ) );
$res = $dbr->select( 'watchlist', array( 'wl_user' ),
array(
@@ -373,7 +381,7 @@ class EmailNotification {
} else {
$subject = str_replace('$PAGEEDITOR', $name, $subject);
$keys['$PAGEEDITOR'] = $name;
- $emailPage = Title::makeTitle( NS_SPECIAL, 'Emailuser/' . $name );
+ $emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $name );
$keys['$PAGEEDITOR_EMAIL'] = $emailPage->getFullUrl();
}
$userPage = $wgUser->getUserPage();
diff --git a/includes/WebRequest.php b/includes/WebRequest.php
index 32307ed2..35336954 100644
--- a/includes/WebRequest.php
+++ b/includes/WebRequest.php
@@ -47,10 +47,15 @@ class WebRequest {
function WebRequest() {
$this->checkMagicQuotes();
global $wgUsePathInfo;
- if( isset( $_SERVER['PATH_INFO'] ) && ($_SERVER['PATH_INFO'] != '') && $wgUsePathInfo ) {
- # Stuff it!
- $_GET['title'] = $_REQUEST['title'] =
- substr( $_SERVER['PATH_INFO'], 1 );
+ if ( $wgUsePathInfo ) {
+ if ( isset( $_SERVER['ORIG_PATH_INFO'] ) && $_SERVER['ORIG_PATH_INFO'] != '' ) {
+ # Mangled PATH_INFO
+ # http://bugs.php.net/bug.php?id=31892
+ # Also reported when ini_get('cgi.fix_pathinfo')==false
+ $_GET['title'] = $_REQUEST['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
+ } elseif ( isset( $_SERVER['PATH_INFO'] ) && ($_SERVER['PATH_INFO'] != '') && $wgUsePathInfo ) {
+ $_GET['title'] = $_REQUEST['title'] = substr( $_SERVER['PATH_INFO'], 1 );
+ }
}
}
diff --git a/includes/WebStart.php b/includes/WebStart.php
index 0c71ce53..37582290 100644
--- a/includes/WebStart.php
+++ b/includes/WebStart.php
@@ -4,6 +4,16 @@
# starts the profiler and loads the configuration, and optionally loads
# Setup.php depending on whether MW_NO_SETUP is defined.
+# Test for PHP bug which breaks PHP 5.0.x on 64-bit...
+# As of 1.8 this breaks lots of common operations instead
+# of just some rare ones like export.
+$borked = str_replace( 'a', 'b', array( -1 => -1 ) );
+if( !isset( $borked[-1] ) ) {
+ echo "PHP 5.0.x is buggy on your 64-bit system; you must upgrade to PHP 5.1.x\n" .
+ "or higher. ABORTING. (http://bugs.php.net/bug.php?id=34879 for details)\n";
+ die( -1 );
+}
+
# Protect against register_globals
# This must be done before any globals are set by the code
if ( ini_get( 'register_globals' ) ) {
diff --git a/includes/Wiki.php b/includes/Wiki.php
index 401756be..4fa421a6 100644
--- a/includes/Wiki.php
+++ b/includes/Wiki.php
@@ -71,7 +71,7 @@ class MediaWiki {
if ( '' == $title && 'delete' != $action ) {
- $ret = Title::newFromText( wfMsgForContent( 'mainpage' ) );
+ $ret = Title::newMainPage();
} elseif ( $curid = $request->getInt( 'curid' ) ) {
# URLs like this are generated by RC, because rc_title isn't always accurate
$ret = Title::newFromID( $curid );
@@ -99,7 +99,7 @@ class MediaWiki {
if( !is_null( $search ) && $search !== '' ) {
// Compatibility with old search URLs which didn't use Special:Search
// Do this above the read whitelist check for security...
- $title = Title::makeTitle( NS_SPECIAL, 'Search' );
+ $title = SpecialPage::getTitleFor( 'Search' );
}
$this->setVal( 'Search', $search );
@@ -125,10 +125,10 @@ class MediaWiki {
$action = $this->getVal('Action');
if( !$this->getVal('DisableInternalSearch') && !is_null( $search ) && $search !== '' ) {
require_once( 'includes/SpecialSearch.php' );
- $title = Title::makeTitle( NS_SPECIAL, 'Search' );
+ $title = SpecialPage::getTitleFor( 'Search' );
wfSpecialSearch();
} else if( !$title or $title->getDBkey() == '' ) {
- $title = Title::makeTitle( NS_SPECIAL, 'Badtitle' );
+ $title = SpecialPage::getTitleFor( 'Badtitle' );
# Die now before we mess up $wgArticle and the skin stops working
throw new ErrorPageError( 'badtitle', 'badtitletext' );
} else if ( $title->getInterwiki() != '' ) {
@@ -141,16 +141,43 @@ class MediaWiki {
if ( !preg_match( '/^' . preg_quote( $this->getVal('Server'), '/' ) . '/', $url ) && $title->isLocal() ) {
$output->redirect( $url );
} else {
- $title = Title::makeTitle( NS_SPECIAL, 'Badtitle' );
+ $title = SpecialPage::getTitleFor( 'Badtitle' );
throw new ErrorPageError( 'badtitle', 'badtitletext' );
}
} else if ( ( $action == 'view' ) &&
(!isset( $this->GET['title'] ) || $title->getPrefixedDBKey() != $this->GET['title'] ) &&
!count( array_diff( array_keys( $this->GET ), array( 'action', 'title' ) ) ) )
{
- /* Redirect to canonical url, make it a 301 to allow caching */
- $output->setSquidMaxage( 1200 );
- $output->redirect( $title->getFullURL(), '301');
+ $targetUrl = $title->getFullURL();
+ // Redirect to canonical url, make it a 301 to allow caching
+ global $wgServer, $wgUsePathInfo;
+ if( isset( $_SERVER['REQUEST_URI'] ) &&
+ $targetUrl == $wgServer . $_SERVER['REQUEST_URI'] ) {
+ $message = "Redirect loop detected!\n\n" .
+ "This means the wiki got confused about what page was " .
+ "requested; this sometimes happens when moving a wiki " .
+ "to a new server or changing the server configuration.\n\n";
+
+ if( $wgUsePathInfo ) {
+ $message .= "The wiki is trying to interpret the page " .
+ "title from the URL path portion (PATH_INFO), which " .
+ "sometimes fails depending on the web server. Try " .
+ "setting \"\$wgUsePathInfo = false;\" in your " .
+ "LocalSettings.php, or check that \$wgArticlePath " .
+ "is correct.";
+ } else {
+ $message .= "Your web server was detected as possibly not " .
+ "supporting URL path components (PATH_INFO) correctly; " .
+ "check your LocalSettings.php for a customized " .
+ "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
+ "to true.";
+ }
+ wfHttpError( 500, "Internal error", $message );
+ return false;
+ } else {
+ $output->setSquidMaxage( 1200 );
+ $output->redirect( $targetUrl, '301');
+ }
} else if ( NS_SPECIAL == $title->getNamespace() ) {
/* actions that need to be made when we have a special pages */
SpecialPage::executePath( $title );
diff --git a/includes/WikiError.php b/includes/WikiError.php
index 1b2c03bf..029184d4 100644
--- a/includes/WikiError.php
+++ b/includes/WikiError.php
@@ -59,8 +59,8 @@ class WikiError {
* @return bool
* @static
*/
- function isError( &$object ) {
- return is_a( $object, 'WikiError' );
+ public static function isError( $object ) {
+ return $object instanceof WikiError;
}
}
diff --git a/includes/Xml.php b/includes/Xml.php
index 34574458..67dda7fe 100644
--- a/includes/Xml.php
+++ b/includes/Xml.php
@@ -128,10 +128,13 @@ class Xml {
* @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 );
+ return self::element( 'input', array_merge(
+ array(
+ 'name' => $name,
+ 'type' => 'checkbox',
+ 'value' => 1 ),
+ self::attrib( 'checked', $checked ),
+ $attribs ) );
}
/**
@@ -255,6 +258,33 @@ class Xml {
}
/**
+ * Encode a variable of unknown type to JavaScript.
+ * Doesn't support hashtables just yet.
+ */
+ public static function encodeJsVar( $value ) {
+ if ( is_bool( $value ) ) {
+ $s = $value ? 'true' : 'false';
+ } elseif ( is_null( $value ) ) {
+ $s = 'null';
+ } elseif ( is_int( $value ) ) {
+ $s = $value;
+ } elseif ( is_array( $value ) ) {
+ $s = '[';
+ foreach ( $value as $name => $elt ) {
+ if ( $s != '[' ) {
+ $s .= ', ';
+ }
+ $s .= self::encodeJsVar( $elt );
+ }
+ $s .= ']';
+ } else {
+ $s = '"' . self::escapeJsString( $value ) . '"';
+ }
+ return $s;
+ }
+
+
+ /**
* Check if a string is well-formed XML.
* Must include the surrounding tag.
*
@@ -270,8 +300,8 @@ class Xml {
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 );
+ //$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 );
@@ -297,5 +327,19 @@ class Xml {
'</html>';
return Xml::isWellFormed( $html );
}
+
+ /**
+ * Replace " > and < with their respective HTML entities ( &quot;,
+ * &gt;, &lt;)
+ *
+ * @param $in String: text that might contain HTML tags.
+ * @return string Escaped string
+ */
+ public static function escapeTagsOnly( $in ) {
+ return str_replace(
+ array( '"', '>', '<' ),
+ array( '&quot;', '&gt;', '&lt;' ),
+ $in );
+ }
}
?>
diff --git a/includes/ZhClient.php b/includes/ZhClient.php
index 0451ce81..9c9461d5 100644
--- a/includes/ZhClient.php
+++ b/includes/ZhClient.php
@@ -38,6 +38,7 @@ class ZhClient {
*/
function connect() {
wfSuppressWarnings();
+ $errno = $errstr = '';
$this->mFP = fsockopen($this->mHost, $this->mPort, $errno, $errstr, 30);
wfRestoreWarnings();
if(!$this->mFP) {
@@ -115,7 +116,6 @@ class ZhClient {
foreach($info as $variant) {
list($code, $len) = explode(' ', $variant);
$ret[strtolower($code)] = substr($data, $i, $len);
- $r = $ret[strtolower($code)];
$i+=$len;
}
return $ret;
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index f578f41b..1a9c1e3d 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -35,6 +35,11 @@ abstract class ApiBase {
const PARAM_MAX2 = 4;
const PARAM_MIN = 5;
+ const LIMIT_BIG1 = 500; // Fast query, user's limit
+ const LIMIT_BIG2 = 5000; // Fast query, bot's limit
+ const LIMIT_SML1 = 50; // Slow query, user's limit
+ const LIMIT_SML2 = 500; // Slow query, bot's limit
+
private $mMainModule, $mModuleName, $mParamPrefix;
/**
@@ -42,7 +47,7 @@ abstract class ApiBase {
*/
public function __construct($mainModule, $moduleName, $paramPrefix = '') {
$this->mMainModule = $mainModule;
- $this->mModuleName = $moduleName;
+ $this->mModuleName = $moduleName;
$this->mParamPrefix = $paramPrefix;
}
@@ -51,12 +56,22 @@ abstract class ApiBase {
*/
public abstract function execute();
- /**
- * Get the name of the query being executed by this instance
- */
- public function getModuleName() {
- return $this->mModuleName;
- }
+ /**
+ * Get the name of the module being executed by this instance
+ */
+ public function getModuleName() {
+ return $this->mModuleName;
+ }
+
+ /**
+ * Get the name of the module as shown in the profiler log
+ */
+ public function getModuleProfileName($db = false) {
+ if ($db)
+ return 'API:' . $this->mModuleName . '-DB';
+ else
+ return 'API:' . $this->mModuleName;
+ }
/**
* Get main module
@@ -91,6 +106,15 @@ abstract class ApiBase {
}
/**
+ * If the module may only be used with a certain format module,
+ * it should override this method to return an instance of that formatter.
+ * A value of null means the default format will be used.
+ */
+ public function getCustomPrinter() {
+ return null;
+ }
+
+ /**
* Generates help message for this module, or false if there is no description
*/
public function makeHelpMsg() {
@@ -126,8 +150,17 @@ abstract class ApiBase {
if ($this->getMain()->getShowVersions()) {
$versions = $this->getVersion();
- if (is_array($versions))
+ $pattern = '(\$.*) ([0-9a-z_]+\.php) (.*\$)';
+ $replacement = '\\0' . "\n " . 'http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/api/\\2';
+
+ if (is_array($versions)) {
+ foreach ($versions as &$v)
+ $v = eregi_replace($pattern, $replacement, $v);
$versions = implode("\n ", $versions);
+ }
+ else
+ $versions = eregi_replace($pattern, $replacement, $versions);
+
$msg .= "Version:\n $versions\n";
}
}
@@ -141,10 +174,32 @@ abstract class ApiBase {
$paramsDescription = $this->getParamDescription();
$msg = '';
- foreach (array_keys($params) as $paramName) {
+ $paramPrefix = "\n" . str_repeat(' ', 19);
+ foreach ($params as $paramName => $paramSettings) {
$desc = isset ($paramsDescription[$paramName]) ? $paramsDescription[$paramName] : '';
if (is_array($desc))
- $desc = implode("\n" . str_repeat(' ', 19), $desc);
+ $desc = implode($paramPrefix, $desc);
+
+ @ $type = $paramSettings[self :: PARAM_TYPE];
+ if (isset ($type)) {
+ if (isset ($paramSettings[self :: PARAM_ISMULTI]))
+ $prompt = 'Values (separate with \'|\'): ';
+ else
+ $prompt = 'One value: ';
+
+ if (is_array($type)) {
+ $desc .= $paramPrefix . $prompt . implode(', ', $type);
+ }
+ elseif ($type == 'namespace') {
+ // Special handling because namespaces are type-limited, yet they are not given
+ $desc .= $paramPrefix . $prompt . implode(', ', ApiBase :: getValidNamespaces());
+ }
+ }
+
+ $default = is_array($paramSettings) ? (isset ($paramSettings[self :: PARAM_DFLT]) ? $paramSettings[self :: PARAM_DFLT] : null) : $paramSettings;
+ if (!is_null($default) && $default !== false)
+ $desc .= $paramPrefix . "Default: $default";
+
$msg .= sprintf(" %-14s - %s\n", $this->encodeParamName($paramName), $desc);
}
return $msg;
@@ -180,7 +235,7 @@ abstract class ApiBase {
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
@@ -213,13 +268,26 @@ abstract class ApiBase {
return $this->getParameterFromSettings($paramName, $paramSettings);
}
+ public static function getValidNamespaces() {
+ static $mValidNamespaces = null;
+ if (is_null($mValidNamespaces)) {
+
+ global $wgContLang;
+ $mValidNamespaces = array ();
+ foreach (array_keys($wgContLang->getNamespaces()) as $ns) {
+ if ($ns >= 0)
+ $mValidNamespaces[] = $ns;
+ }
+ }
+ return $mValidNamespaces;
+ }
+
/**
* 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);
@@ -248,48 +316,58 @@ abstract class ApiBase {
ApiBase :: dieDebug(__METHOD__, "Boolean param $paramName's default is set to '$default'");
}
- $value = $wgRequest->getCheck($paramName);
- } else
- $value = $wgRequest->getVal($paramName, $default);
+ $value = $this->getMain()->getRequest()->getCheck($paramName);
+ } else {
+ $value = $this->getMain()->getRequest()->getVal($paramName, $default);
+
+ if (isset ($value) && $type == 'namespace')
+ $type = ApiBase :: getValidNamespaces();
+ }
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");
-
+ if (isset ($value)) {
+ if (!is_array($type)) {
+ 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");
+ $value = wfTimestamp(TS_UNIX, $value);
+ if ($value === 0)
+ $this->dieUsage("Invalid value '$value' for timestamp parameter $paramName", "badtimestamp_{$paramName}");
+ $value = wfTimestamp(TS_MW, $value);
+ break;
+ default :
+ ApiBase :: dieDebug(__METHOD__, "Param $paramName's type is unknown - $type");
+ }
}
+
+ // There should never be any duplicate values in a list
+ if (is_array($value))
+ $value = array_unique($value);
}
return $value;
@@ -314,7 +392,7 @@ abstract class ApiBase {
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");
+ $this->dieUsage('Unrecognised value' . (count($unknownValues) > 1 ? "s" : "") . " for parameter '$valueName'", "unknown_$valueName");
}
}
@@ -325,8 +403,6 @@ abstract class ApiBase {
* 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);
}
@@ -345,7 +421,7 @@ abstract class ApiBase {
* Call main module's error handler
*/
public function dieUsage($description, $errorCode, $httpRespCode = 0) {
- $this->getMain()->mainDieUsage($description, $this->encodeParamName($errorCode), $httpRespCode);
+ throw new UsageException($description, $this->encodeParamName($errorCode), $httpRespCode);
}
/**
@@ -367,6 +443,7 @@ abstract class ApiBase {
if ($this->mTimeIn !== 0)
ApiBase :: dieDebug(__METHOD__, 'called twice without calling profileOut()');
$this->mTimeIn = microtime(true);
+ wfProfileIn($this->getModuleProfileName());
}
/**
@@ -380,6 +457,19 @@ abstract class ApiBase {
$this->mModuleTime += microtime(true) - $this->mTimeIn;
$this->mTimeIn = 0;
+ wfProfileOut($this->getModuleProfileName());
+ }
+
+ /**
+ * When modules crash, sometimes it is needed to do a profileOut() regardless
+ * of the profiling state the module was in. This method does such cleanup.
+ */
+ public function safeProfileOut() {
+ if ($this->mTimeIn !== 0) {
+ if ($this->mDBTimeIn !== 0)
+ $this->profileDBOut();
+ $this->profileOut();
+ }
}
/**
@@ -405,6 +495,7 @@ abstract class ApiBase {
if ($this->mDBTimeIn !== 0)
ApiBase :: dieDebug(__METHOD__, 'called twice without calling profileDBOut()');
$this->mDBTimeIn = microtime(true);
+ wfProfileIn($this->getModuleProfileName(true));
}
/**
@@ -421,6 +512,7 @@ abstract class ApiBase {
$this->mDBTime += $time;
$this->getMain()->mDBTime += $time;
+ wfProfileOut($this->getModuleProfileName(true));
}
/**
@@ -433,9 +525,9 @@ abstract class ApiBase {
}
public abstract function getVersion();
-
+
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiBase.php 16757 2006-10-03 05:41:55Z yurik $';
+ return __CLASS__ . ': $Id: ApiBase.php 17880 2006-11-23 08:25:56Z nickj $';
}
}
?> \ No newline at end of file
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
new file mode 100644
index 00000000..7d1c1519
--- /dev/null
+++ b/includes/api/ApiFeedWatchlist.php
@@ -0,0 +1,125 @@
+<?php
+
+
+/*
+ * Created on Oct 13, 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 ApiFeedWatchlist extends ApiBase {
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action);
+ }
+
+ public function getCustomPrinter() {
+ return new ApiFormatFeedWrapper($this->getMain());
+ }
+
+ public function execute() {
+ $feedformat = null;
+ extract($this->extractRequestParams());
+
+ // limit to 1 day
+ $startTime = wfTimestamp(TS_MW, time() - intval(1 * 86400));
+
+ // Prepare nested request
+ $params = new FauxRequest(array (
+ 'action' => 'query',
+ 'meta' => 'siteinfo',
+ 'siprop' => 'general',
+ 'list' => 'watchlist',
+ 'wlprop' => 'user|comment|timestamp',
+ 'wlstart' => $startTime,
+ 'wllimit' => 50
+ ));
+
+ // Execute
+ $module = new ApiMain($params);
+ $module->execute();
+
+ // Get data array
+ $data = $module->getResultData();
+
+ $feedItems = array ();
+ foreach ($data['query']['watchlist'] as $info) {
+ $feedItems[] = $this->createFeedItem($info);
+ }
+
+ global $wgFeedClasses, $wgSitename, $wgContLanguageCode;
+ $feedTitle = $wgSitename . ' - ' . wfMsgForContent('watchlist') . ' [' . $wgContLanguageCode . ']';
+ $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullUrl();
+
+ $feed = new $wgFeedClasses[$feedformat] ($feedTitle, htmlspecialchars(wfMsgForContent('watchlist')), $feedUrl);
+
+ ApiFormatFeedWrapper :: setResult($this->getResult(), $feed, $feedItems);
+ }
+
+ private function createFeedItem($info) {
+ $titleStr = $info['title'];
+ $title = Title :: newFromText($titleStr);
+ $titleUrl = $title->getFullUrl();
+ $comment = isset( $info['comment'] ) ? $info['comment'] : null;
+ $timestamp = $info['timestamp'];
+ $user = $info['user'];
+
+ $completeText = "$comment ($user)";
+
+ return new FeedItem($titleStr, $completeText, $titleUrl, $timestamp, $user);
+ }
+
+ protected function getAllowedParams() {
+ global $wgFeedClasses;
+ $feedFormatNames = array_keys($wgFeedClasses);
+ return array (
+ 'feedformat' => array (
+ ApiBase :: PARAM_DFLT => 'rss',
+ ApiBase :: PARAM_TYPE => $feedFormatNames
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'feedformat' => 'The format of the feed'
+ );
+ }
+
+ protected function getDescription() {
+ return 'This module returns a watchlist feed';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=feedwatchlist'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFeedWatchlist.php 17987 2006-11-29 05:45:03Z nickj $';
+ }
+}
+?>
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index 6f5b4aca..611960d3 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -75,32 +75,40 @@ abstract class ApiFormatBase extends ApiBase {
function initPrinter($isError) {
$isHtml = $this->getIsHtml();
$mime = $isHtml ? 'text/html' : $this->getMimeType();
+
+ // Some printers (ex. Feed) do their own header settings,
+ // in which case $mime will be set to null
+ if (is_null($mime))
+ return; // skip any initialization
+
header("Content-Type: $mime; charset=utf-8;");
if ($isHtml) {
?>
- <html>
- <head>
- <title>MediaWiki API</title>
- </head>
- <body>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<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>
+<br/>
+<small>
+You are looking at the HTML representation of the <?=$this->mFormat?> format.<br/>
+HTML is good for debugging, but probably is not suitable for your application.<br/>
+Please see "format" parameter documentation at the <a href='api.php'>API help</a>
+for more information.
+</small>
<?php
}
?>
- <pre>
+<pre>
<?php
@@ -113,8 +121,10 @@ abstract class ApiFormatBase extends ApiBase {
public function closePrinter() {
if ($this->getIsHtml()) {
?>
- </pre>
- </body>
+
+</pre>
+</body>
+</html>
<?php
@@ -134,9 +144,10 @@ abstract class ApiFormatBase extends ApiBase {
*/
protected function formatHTML($text) {
// encode all tags as safe blue strings
- $text = ereg_replace('\<([^>]+)\>', '<font color=blue>&lt;\1&gt;</font>', $text);
+ $text = ereg_replace('\<([^>]+)\>', '<span style="color:blue;">&lt;\1&gt;</span>', $text);
// identify URLs
- $text = ereg_replace("[a-zA-Z]+://[^ '()<\n]+", '<a href="\\0">\\0</a>', $text);
+ $protos = "http|https|ftp|gopher";
+ $text = ereg_replace("($protos)://[^ '\"()<\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
@@ -151,11 +162,71 @@ abstract class ApiFormatBase extends ApiBase {
* Returns usage examples for this format.
*/
protected function getExamples() {
- return 'api.php?action=query&meta=siteinfo&si=namespaces&format=' . $this->getModuleName();
+ return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName();
+ }
+
+ protected function getDescription() {
+ return $this->getIsHtml() ? ' (pretty-print in HTML)' : '';
}
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiFormatBase.php 16757 2006-10-03 05:41:55Z yurik $';
+ return __CLASS__ . ': $Id: ApiFormatBase.php 17374 2006-11-03 06:53:47Z yurik $';
+ }
+}
+
+/**
+ * This printer is used to wrap an instance of the Feed class
+ */
+class ApiFormatFeedWrapper extends ApiFormatBase {
+
+ public function __construct($main) {
+ parent :: __construct($main, 'feed');
+ }
+
+ /**
+ * Call this method to initialize output data
+ */
+ public static function setResult($result, $feed, $feedItems) {
+ // Store output in the Result data.
+ // This way we can check during execution if any error has occured
+ $data = & $result->getData();
+ $data['_feed'] = $feed;
+ $data['_feeditems'] = $feedItems;
+ }
+
+ /**
+ * Feed does its own headers
+ */
+ public function getMimeType() {
+ return null;
+ }
+
+ /**
+ * Optimization - no need to sanitize data that will not be needed
+ */
+ public function getNeedsRawData() {
+ return true;
+ }
+
+ public function execute() {
+ $data = $this->getResultData();
+ if (isset ($data['_feed']) && isset ($data['_feeditems'])) {
+ $feed = $data['_feed'];
+ $items = $data['_feeditems'];
+
+ $feed->outHeader();
+ foreach ($items as & $item)
+ $feed->outItem($item);
+ $feed->outFooter();
+ } else {
+ // Error has occured, print something usefull
+ // TODO: make this error more informative using ApiBase :: dieDebug() or similar
+ wfHttpError(500, 'Internal Server Error', '');
+ }
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFormatBase.php 17374 2006-11-03 06:53:47Z yurik $';
}
}
-?> \ No newline at end of file
+?>
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
index fdc29cf2..45c735c8 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -31,26 +31,39 @@ if (!defined('MEDIAWIKI')) {
class ApiFormatJson extends ApiFormatBase {
+ private $mIsRaw;
+
public function __construct($main, $format) {
parent :: __construct($main, $format);
+ $this->mIsRaw = ($format === 'rawfm');
}
public function getMimeType() {
return 'application/json';
}
+ public function getNeedsRawData() {
+ return $this->mIsRaw;
+ }
+
public function execute() {
- require ('ApiFormatJson_json.php');
- $json = new Services_JSON();
- $this->printText($json->encode($this->getResultData(), true));
+ if (!function_exists('json_encode') || $this->getIsHtml()) {
+ $json = new Services_JSON();
+ $this->printText($json->encode($this->getResultData(), $this->getIsHtml()));
+ } else {
+ $this->printText(json_encode($this->getResultData()));
+ }
}
protected function getDescription() {
- return 'Output data in JSON format';
+ if ($this->mIsRaw)
+ return 'Output data with the debuging elements in JSON format' . parent :: getDescription();
+ else
+ return 'Output data in JSON format' . parent :: getDescription();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatJson.php 16725 2006-10-01 21:20:55Z yurik $';
+ return __CLASS__ . ': $Id: ApiFormatJson.php 17374 2006-11-03 06:53:47Z yurik $';
}
}
?> \ No newline at end of file
diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php
new file mode 100644
index 00000000..938ba032
--- /dev/null
+++ b/includes/api/ApiFormatPhp.php
@@ -0,0 +1,54 @@
+<?php
+
+
+/*
+ * Created on Oct 22, 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 ApiFormatPhp extends ApiFormatBase {
+
+ public function __construct($main, $format) {
+ parent :: __construct($main, $format);
+ }
+
+ public function getMimeType() {
+ return 'application/vnd.php.serialized';
+ }
+
+ public function execute() {
+ $this->printText(serialize($this->getResultData()));
+ }
+
+ protected function getDescription() {
+ return 'Output data in serialized PHP format' . parent :: getDescription();
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFormatPhp.php 17374 2006-11-03 06:53:47Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
new file mode 100644
index 00000000..e97b996c
--- /dev/null
+++ b/includes/api/ApiFormatWddx.php
@@ -0,0 +1,89 @@
+<?php
+
+
+/*
+ * Created on Oct 22, 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 ApiFormatWddx extends ApiFormatBase {
+
+ public function __construct($main, $format) {
+ parent :: __construct($main, $format);
+ }
+
+ public function getMimeType() {
+ return 'text/xml';
+ }
+
+ public function execute() {
+ if (function_exists('wddx_serialize_value')) {
+ $this->printText(wddx_serialize_value($this->getResultData()));
+ } else {
+ $this->printText('<?xml version="1.0" encoding="utf-8"?>');
+ $this->printText('<wddxPacket version="1.0"><header/><data>');
+ $this->slowWddxPrinter($this->getResultData());
+ $this->printText('</data></wddxPacket>');
+ }
+ }
+
+ /**
+ * Recursivelly go through the object and output its data in WDDX format.
+ */
+ function slowWddxPrinter($elemValue) {
+ switch (gettype($elemValue)) {
+ case 'array' :
+ $this->printText('<struct>');
+ foreach ($elemValue as $subElemName => $subElemValue) {
+ $this->printText(wfElement('var', array (
+ 'name' => $subElemName
+ ), null));
+ $this->slowWddxPrinter($subElemValue);
+ $this->printText('</var>');
+ }
+ $this->printText('</struct>');
+ break;
+ case 'integer' :
+ case 'double' :
+ $this->printText(wfElement('number', null, $elemValue));
+ break;
+ case 'string' :
+ $this->printText(wfElement('string', null, $elemValue));
+ break;
+ default :
+ ApiBase :: dieDebug(__METHOD__, 'Unknown type ' . gettype($elemValue));
+ }
+ }
+
+ protected function getDescription() {
+ return 'Output data in WDDX format' . parent :: getDescription();
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFormatWddx.php 17374 2006-11-03 06:53:47Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index 6aa08e00..2326ba42 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -31,6 +31,8 @@ if (!defined('MEDIAWIKI')) {
class ApiFormatXml extends ApiFormatBase {
+ private $mRootElemName = 'api';
+
public function __construct($main, $format) {
parent :: __construct($main, $format);
}
@@ -42,18 +44,14 @@ class ApiFormatXml extends ApiFormatBase {
public function getNeedsRawData() {
return true;
}
+
+ public function setRootElement($rootElemName) {
+ $this->mRootElemName = $rootElemName;
+ }
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->recXmlPrint($this->mRootElemName, $this->getResultData(), $this->getIsHtml() ? -2 : null);
}
/**
@@ -98,8 +96,6 @@ class ApiFormatXml extends ApiFormatBase {
$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)) {
@@ -109,7 +105,7 @@ class ApiFormatXml extends ApiFormatBase {
}
if (is_null($subElemIndName) && !empty ($indElements))
- ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has integer keys without _element value");
+ ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName().");
if (!empty ($subElements) && !empty ($indElements) && !is_null($subElemContent))
ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has content and subelements");
@@ -139,23 +135,11 @@ class ApiFormatXml extends ApiFormatBase {
}
}
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'
- );
+ return 'Output data in XML format' . parent :: getDescription();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatXml.php 16725 2006-10-01 21:20:55Z yurik $';
+ return __CLASS__ . ': $Id: ApiFormatXml.php 17374 2006-11-03 06:53:47Z yurik $';
}
}
?> \ No newline at end of file
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
index bd74f01a..2371903f 100644
--- a/includes/api/ApiFormatYaml.php
+++ b/includes/api/ApiFormatYaml.php
@@ -40,16 +40,15 @@ class ApiFormatYaml extends ApiFormatBase {
}
public function execute() {
- require ('ApiFormatYaml_spyc.php');
$this->printText(Spyc :: YAMLDump($this->getResultData()));
}
protected function getDescription() {
- return 'Output data in YAML format';
+ return 'Output data in YAML format' . parent :: getDescription();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatYaml.php 16725 2006-10-01 21:20:55Z yurik $';
+ return __CLASS__ . ': $Id: ApiFormatYaml.php 17374 2006-11-03 06:53:47Z yurik $';
}
}
?> \ No newline at end of file
diff --git a/includes/api/ApiFormatYaml_spyc.php b/includes/api/ApiFormatYaml_spyc.php
index 05a39e23..1ec8af48 100644
--- a/includes/api/ApiFormatYaml_spyc.php
+++ b/includes/api/ApiFormatYaml_spyc.php
@@ -463,6 +463,7 @@
* @param string $line A line from the YAML file
*/
function _getIndent($line) {
+ $match = array();
preg_match('/^\s{1,}/',$line,$match);
if (!empty($match[0])) {
$indent = substr_count($match[0],' ');
@@ -500,6 +501,7 @@
} elseif (preg_match('/^(.+):/',$line,$key)) {
// It's a key/value pair most likely
// If the key is in double quotes pull it out
+ $matches = array();
if (preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
$value = trim(str_replace($matches[1],'',$line));
$key = $matches[2];
@@ -529,6 +531,7 @@
* @return mixed
*/
function _toType($value) {
+ $matches = array();
if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) {
$value = (string)preg_replace('/(\'\'|\\\\\')/',"'",end($matches));
$value = preg_replace('/\\\\"/','"',$value);
@@ -596,6 +599,7 @@
// Check for strings
$regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
+ $strings = array();
if (preg_match_all($regex,$inline,$strings)) {
$saved_strings[] = $strings[0][0];
$inline = preg_replace($regex,'YAMLString',$inline);
@@ -603,12 +607,14 @@
unset($regex);
// Check for sequences
+ $seqs = array();
if (preg_match_all('/\[(.+)\]/U',$inline,$seqs)) {
$inline = preg_replace('/\[(.+)\]/U','YAMLSeq',$inline);
$seqs = $seqs[0];
}
// Check for mappings
+ $maps = array();
if (preg_match_all('/{(.+)}/U',$inline,$maps)) {
$inline = preg_replace('/{(.+)}/U','YAMLMap',$inline);
$maps = $maps[0];
@@ -704,6 +710,7 @@
function _linkRef(&$n,$key,$k = NULL,$v = NULL) {
if (empty($k) && empty($v)) {
// Look for &refs
+ $matches = array();
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);
@@ -837,7 +844,7 @@
$ret = array();
foreach($keys as $key) {
- list($unused,$val) = each($vals);
+ 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)) {
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
index 2aa571c1..d9697dc3 100644
--- a/includes/api/ApiLogin.php
+++ b/includes/api/ApiLogin.php
@@ -89,8 +89,8 @@ class ApiLogin extends ApiBase {
protected function getAllowedParams() {
return array (
- 'name' => '',
- 'password' => '',
+ 'name' => null,
+ 'password' => null,
'domain' => null
);
}
@@ -116,7 +116,7 @@ class ApiLogin extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiLogin.php 16757 2006-10-03 05:41:55Z yurik $';
+ return __CLASS__ . ': $Id: ApiLogin.php 17065 2006-10-17 02:11:29Z yurik $';
}
}
?>
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index 046d7d7c..606f022b 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -29,36 +29,79 @@ if (!defined('MEDIAWIKI')) {
require_once ('ApiBase.php');
}
+/**
+ * This is the main API class, used for both external and internal processing.
+ */
class ApiMain extends ApiBase {
+ /**
+ * When no format parameter is given, this format will be used
+ */
+ const API_DEFAULT_FORMAT = 'xmlfm';
+
+ /**
+ * List of available modules: action name => module class
+ */
+ private static $Modules = array (
+ 'help' => 'ApiHelp',
+ 'login' => 'ApiLogin',
+ 'opensearch' => 'ApiOpenSearch',
+ 'feedwatchlist' => 'ApiFeedWatchlist',
+ 'query' => 'ApiQuery'
+ );
+
+ /**
+ * List of available formats: format name => format class
+ */
+ private static $Formats = array (
+ 'json' => 'ApiFormatJson',
+ 'jsonfm' => 'ApiFormatJson',
+ 'php' => 'ApiFormatPhp',
+ 'phpfm' => 'ApiFormatPhp',
+ 'wddx' => 'ApiFormatWddx',
+ 'wddxfm' => 'ApiFormatWddx',
+ 'xml' => 'ApiFormatXml',
+ 'xmlfm' => 'ApiFormatXml',
+ 'yaml' => 'ApiFormatYaml',
+ 'yamlfm' => 'ApiFormatYaml',
+ 'rawfm' => 'ApiFormatJson'
+ );
+
private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames;
- private $mApiStartTime, $mResult, $mShowVersions, $mEnableWrite;
+ private $mResult, $mShowVersions, $mEnableWrite, $mRequest, $mInternalMode, $mSquidMaxage;
/**
* Constructor
- * $apiStartTime - time of the originating call for profiling purposes
- * $modules - an array of actions (keys) and classes that handle them (values)
+ * @param $request object - if this is an instance of FauxRequest, errors are thrown and no printing occurs
+ * @param $enableWrite bool should be set to true if the api may modify data
*/
- public function __construct($apiStartTime, $modules, $formats, $enableWrite) {
+ public function __construct($request, $enableWrite = false) {
+
+ $this->mInternalMode = ($request instanceof FauxRequest);
+
// Special handling for the main module: $parent === $this
- parent :: __construct($this, 'main');
+ parent :: __construct($this, $this->mInternalMode ? 'main_int' : 'main');
+
+ $this->mModules = self :: $Modules;
+ $this->mModuleNames = array_keys($this->mModules); // todo: optimize
+ $this->mFormats = self :: $Formats;
+ $this->mFormatNames = array_keys($this->mFormats); // todo: optimize
- $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;
+
+ $this->mRequest = & $request;
+
+ $this->mSquidMaxage = 0;
}
- public function & getResult() {
- return $this->mResult;
+ public function & getRequest() {
+ return $this->mRequest;
}
- public function getShowVersions() {
- return $this->mShowVersions;
+ public function getResult() {
+ return $this->mResult;
}
public function requestWriteMode() {
@@ -67,94 +110,180 @@ class ApiMain extends ApiBase {
'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
- );
+ public function setCacheMaxAge($maxage) {
+ $this->mSquidMaxage = $maxage;
}
- 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 createPrinterByName($format) {
+ return new $this->mFormats[$format] ($this, $format);
}
public function execute() {
$this->profileIn();
- $action = $format = $version = null;
- try {
- extract($this->extractRequestParams());
- $this->mShowVersions = $version;
+ if ($this->mInternalMode)
+ $this->executeAction();
+ else
+ $this->executeActionWithErrorHandling();
+ $this->profileOut();
+ }
- // Create an appropriate printer
- $this->mPrinter = new $this->mFormats[$format] ($this, $format);
+ protected function executeActionWithErrorHandling() {
- // Instantiate and execute module requested by the user
- $module = new $this->mModules[$action] ($this, $action);
- $module->profileIn();
- $module->execute();
- $module->profileOut();
- $this->printResult(false);
+ // In case an error occurs during data output,
+ // this clear the output buffer and print just the error information
+ ob_start();
- } catch (UsageException $e) {
+ try {
+ $this->executeAction();
+ } catch (Exception $e) {
+ //
+ // Handle any kind of exception by outputing properly formatted error message.
+ // If this fails, an unhandled exception should be thrown so that global error
+ // handler will process and log it.
+ //
+
+ // Error results should not be cached
+ $this->setCacheMaxAge(0);
// 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);
+ if (!isset ($this->mPrinter)) {
+ $this->mPrinter = $this->createPrinterByName(self :: API_DEFAULT_FORMAT);
+ if ($this->mPrinter->getNeedsRawData())
+ $this->getResult()->setRawMode();
+ }
+
+ if ($e instanceof UsageException) {
+ //
+ // User entered incorrect parameters - print usage screen
+ //
+ $errMessage = array (
+ 'code' => $e->getCodeString(), 'info' => $e->getMessage());
+ ApiResult :: setContent($errMessage, $this->makeHelpMsg());
+
+ } else {
+ //
+ // Something is seriously wrong
+ //
+ $errMessage = array (
+ 'code' => 'internal_api_error',
+ 'info' => "Exception Caught: {$e->getMessage()}"
+ );
+ ApiResult :: setContent($errMessage, "\n\n{$e->getTraceAsString()}\n\n");
+ }
+
+ $headerStr = 'MediaWiki-API-Error: ' . $errMessage['code'];
+ if ($e->getCode() === 0)
+ header($headerStr, true);
+ else
+ header($headerStr, true, $e->getCode());
+
+ // Reset and print just the error message
+ ob_clean();
+ $this->getResult()->reset();
+ $this->getResult()->addValue(null, 'error', $errMessage);
+
+ // If the error occured during printing, do a printer->profileOut()
+ $this->mPrinter->safeProfileOut();
$this->printResult(true);
+ }
+ // Set the cache expiration at the last moment, as any errors may change the expiration.
+ // if $this->mSquidMaxage == 0, the expiry time is set to the first second of unix epoch
+ $expires = $this->mSquidMaxage == 0 ? 1 : time() + $this->mSquidMaxage;
+ header('Expires: ' . wfTimestamp(TS_RFC2822, $expires));
+ header('Cache-Control: s-maxage=' . $this->mSquidMaxage . ', must-revalidate, max-age=0');
+
+ ob_end_flush();
+ }
+
+ /**
+ * Execute the actual module, without any error handling
+ */
+ protected function executeAction() {
+ $action = $format = $version = null;
+ extract($this->extractRequestParams());
+ $this->mShowVersions = $version;
+
+ // Instantiate the module requested by the user
+ $module = new $this->mModules[$action] ($this, $action);
+
+ if (!$this->mInternalMode) {
+
+ // See if custom printer is used
+ $this->mPrinter = $module->getCustomPrinter();
+ if (is_null($this->mPrinter)) {
+ // Create an appropriate printer
+ $this->mPrinter = $this->createPrinterByName($format);
+ }
+
+ if ($this->mPrinter->getNeedsRawData())
+ $this->getResult()->setRawMode();
+ }
+
+ // Execute
+ $module->profileIn();
+ $module->execute();
+ $module->profileOut();
+
+ if (!$this->mInternalMode) {
+ // Print result data
+ $this->printResult(false);
}
- $this->profileOut();
}
/**
* Internal printer
*/
- private function printResult($isError) {
+ protected 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 getAllowedParams() {
+ return array (
+ 'format' => array (
+ ApiBase :: PARAM_DFLT => ApiMain :: 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'
+ );
+ }
+
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',
+ '',
+ 'Status: ALPHA -- all features shown on this page should be working,',
+ ' but the API is still in active development, and may change at any time.',
+ ' Make sure you monitor changes to this page, wikitech-l mailing list,',
+ ' or the source code in the includes/api directory for any changes.',
''
);
}
-
- 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
+
+ protected function getCredits() {
+ return array(
+ 'This API is being implemented by Yuri Astrakhan [[User:Yurik]] / FirstnameLastname@gmail.com',
+ 'Please leave your comments and suggestions at http://meta.wikimedia.org/wiki/API'
);
- ApiResult :: setContent($data, $this->makeHelpMsg());
- $this->mResult->addValue(null, 'error', $data);
-
- throw new UsageException($description, $errorCode);
}
/**
@@ -167,7 +296,7 @@ class ApiMain extends ApiBase {
$astriks = str_repeat('*** ', 10);
$msg .= "\n\n$astriks Modules $astriks\n\n";
- foreach ($this->mModules as $moduleName => $moduleClass) {
+ foreach( $this->mModules as $moduleName => $unused ) {
$msg .= "* action=$moduleName *";
$module = new $this->mModules[$moduleName] ($this, $moduleName);
$msg2 = $module->makeHelpMsg();
@@ -177,14 +306,17 @@ class ApiMain extends ApiBase {
}
$msg .= "\n$astriks Formats $astriks\n\n";
- foreach ($this->mFormats as $moduleName => $moduleClass) {
- $msg .= "* format=$moduleName *";
- $module = new $this->mFormats[$moduleName] ($this, $moduleName);
+ foreach( $this->mFormats as $formatName => $unused ) {
+ $msg .= "* format=$formatName *";
+ $module = $this->createPrinterByName($formatName);
$msg2 = $module->makeHelpMsg();
if ($msg2 !== false)
$msg .= $msg2;
$msg .= "\n";
}
+
+ $msg .= "\n*** Credits: ***\n " . implode("\n ", $this->getCredits()) . "\n";
+
return $msg;
}
@@ -198,12 +330,17 @@ class ApiMain extends ApiBase {
return $this->mIsBot;
}
+ public function getShowVersions() {
+ return $this->mShowVersions;
+ }
+
public function getVersion() {
$vers = array ();
- $vers[] = __CLASS__ . ': $Id: ApiMain.php 16820 2006-10-06 01:02:14Z yurik $';
+ $vers[] = __CLASS__ . ': $Id: ApiMain.php 17987 2006-11-29 05:45:03Z nickj $';
$vers[] = ApiBase :: getBaseVersion();
$vers[] = ApiFormatBase :: getBaseVersion();
$vers[] = ApiQueryBase :: getBaseVersion();
+ $vers[] = ApiFormatFeedWrapper :: getVersion(); // not accessible with format=xxx
return $vers;
}
}
@@ -213,14 +350,17 @@ class ApiMain extends ApiBase {
*/
class UsageException extends Exception {
- private $codestr;
+ private $mCodestr;
- public function __construct($message, $codestr) {
- parent :: __construct($message);
- $this->codestr = $codestr;
+ public function __construct($message, $codestr, $code = 0) {
+ parent :: __construct($message, $code);
+ $this->mCodestr = $codestr;
+ }
+ public function getCodeString() {
+ return $this->mCodestr;
}
public function __toString() {
- return "{$this->codestr}: {$this->message}";
+ return "{$this->getCodeString()}: {$this->getMessage()}";
}
}
-?> \ No newline at end of file
+?>
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
new file mode 100644
index 00000000..a5a13a7b
--- /dev/null
+++ b/includes/api/ApiOpenSearch.php
@@ -0,0 +1,109 @@
+<?php
+
+
+/*
+ * Created on Oct 13, 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 ApiOpenSearch extends ApiBase {
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action);
+ }
+
+ public function getCustomPrinter() {
+ return $this->getMain()->createPrinterByName('json');
+ }
+
+ public function execute() {
+ $search = null;
+ extract($this->ExtractRequestParams());
+
+ // Open search results may be stored for a very long time
+ $this->getMain()->setCacheMaxAge(1200);
+
+ $title = Title :: newFromText($search);
+ if(!$title)
+ return; // Return empty result
+
+ // Prepare nested request
+ $params = new FauxRequest(array (
+ 'action' => 'query',
+ 'list' => 'allpages',
+ 'apnamespace' => $title->getNamespace(),
+ 'aplimit' => 10,
+ 'apprefix' => $title->getDBkey()
+ ));
+
+ // Execute
+ $module = new ApiMain($params);
+ $module->execute();
+
+ // Get resulting data
+ $data = $module->getResultData();
+
+ // Reformat useful data for future printing by JSON engine
+ $srchres = array ();
+ foreach ($data['query']['allpages'] as & $pageinfo) {
+ // Note: this data will no be printable by the xml engine
+ // because it does not support lists of unnamed items
+ $srchres[] = $pageinfo['title'];
+ }
+
+ // Set top level elements
+ $result = $this->getResult();
+ $result->addValue(null, 0, $search);
+ $result->addValue(null, 1, $srchres);
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'search' => null
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'search' => 'Search string'
+ );
+ }
+
+ protected function getDescription() {
+ return 'This module implements OpenSearch protocol';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=opensearch&search=Te'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiOpenSearch.php 17880 2006-11-23 08:25:56Z nickj $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index d2384b39..4728a9f8 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -32,8 +32,9 @@ if (!defined('MEDIAWIKI')) {
class ApiPageSet extends ApiQueryBase {
private $mAllPages; // [ns][dbkey] => page_id or 0 when missing
- private $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles, $mNormalizedTitles;
+ private $mTitles, $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles, $mNormalizedTitles;
private $mResolveRedirects, $mPendingRedirectIDs;
+ private $mGoodRevIDs, $mMissingRevIDs;
private $mRequestedPageFields;
@@ -41,11 +42,14 @@ class ApiPageSet extends ApiQueryBase {
parent :: __construct($query, __CLASS__);
$this->mAllPages = array ();
+ $this->mTitles = array();
$this->mGoodTitles = array ();
$this->mMissingTitles = array ();
$this->mMissingPageIDs = array ();
$this->mRedirectTitles = array ();
$this->mNormalizedTitles = array ();
+ $this->mGoodRevIDs = array();
+ $this->mMissingRevIDs = array();
$this->mRequestedPageFields = array ();
$this->mResolveRedirects = $resolveRedirects;
@@ -86,6 +90,21 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * All Title objects provided.
+ * @return array of Title objects
+ */
+ public function getTitles() {
+ return $this->mTitles;
+ }
+
+ /**
+ * Returns the number of unique pages (not revisions) in the set.
+ */
+ public function getTitleCount() {
+ return count($this->mTitles);
+ }
+
+ /**
* Title objects that were found in the database.
* @return array page_id (int) => Title (obj)
*/
@@ -94,10 +113,10 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Returns the number of unique pages (not revisions) in the set.
+ * Returns the number of found unique pages (not revisions) in the set.
*/
public function getGoodTitleCount() {
- return count($this->getGoodTitles());
+ return count($this->mGoodTitles);
}
/**
@@ -135,16 +154,25 @@ class ApiPageSet extends ApiQueryBase {
/**
* Get the list of revision IDs (requested with revids= parameter)
+ * @return array revID (int) => pageID (int)
*/
public function getRevisionIDs() {
- $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented');
+ return $this->mGoodRevIDs;
+ }
+
+ /**
+ * Revision IDs that were not found in the database
+ * @return array of revision IDs
+ */
+ public function getMissingRevisionIDs() {
+ return $this->mMissingRevIDs;
}
/**
* Returns the number of revisions (requested with revids= parameter)
*/
public function getRevisionCount() {
- return 0; // TODO: implement
+ return count($this->getRevisionIDs());
}
/**
@@ -178,6 +206,8 @@ class ApiPageSet extends ApiQueryBase {
$this->initFromPageIds($pageids);
break;
case 'revids' :
+ if($this->mResolveRedirects)
+ $this->dieUsage('revids may not be used with redirect resolution', 'params');
$this->initFromRevIDs($revids);
break;
default :
@@ -216,23 +246,39 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * Initialize PageSet from a list of Revision IDs
+ */
+ public function populateFromRevisionIDs($revIDs) {
+ $this->profileIn();
+ $revIDs = array_map('intval', $revIDs); // paranoia
+ $this->initFromRevIDs($revIDs);
+ $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;
+
+ // skip any pages that user has no rights to read
+ if ($title->userCanRead()) {
- if ($this->mResolveRedirects && $row->page_is_redirect == '1') {
- $this->mPendingRedirectIDs[$pageId] = $title;
- } else {
- $this->mGoodTitles[$pageId] = $title;
+ $pageId = intval($row->page_id);
+ $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId;
+ $this->mTitles[] = $title;
+
+ 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;
}
-
- foreach ($this->mRequestedPageFields as $fieldName => & $fieldValues)
- $fieldValues[$pageId] = $row-> $fieldName;
}
public function finishPageSetGeneration() {
@@ -256,10 +302,13 @@ class ApiPageSet extends ApiQueryBase {
* #6 Repeat from step #1
*/
private function initFromTitles($titles) {
- $db = $this->getDB();
// Get validated and normalized title objects
$linkBatch = $this->processTitlesStrArray($titles);
+ if($linkBatch->isEmpty())
+ return;
+
+ $db = & $this->getDB();
$set = $linkBatch->constructSet('page', $db);
// Get pageIDs data from the `page` table
@@ -275,12 +324,15 @@ class ApiPageSet extends ApiQueryBase {
}
private function initFromPageIds($pageids) {
- $db = $this->getDB();
-
+ if(empty($pageids))
+ return;
+
$set = array (
'page_id' => $pageids
);
+ $db = & $this->getDB();
+
// Get pageIDs data from the `page` table
$this->profileDBIn();
$res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__);
@@ -306,7 +358,7 @@ class ApiPageSet extends ApiQueryBase {
*/
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');
+ ApiBase :: dieDebug(__METHOD__, 'Missing $processTitles parameter when $remaining is provided');
while ($row = $db->fetchObject($res)) {
@@ -330,9 +382,11 @@ class ApiPageSet extends ApiQueryBase {
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);
+ foreach ( $dbkeys as $dbkey => $unused ) {
+ $title = Title :: makeTitle($ns, $dbkey);
+ $this->mMissingTitles[] = $title;
$this->mAllPages[$ns][$dbkey] = 0;
+ $this->mTitles[] = $title;
}
}
}
@@ -348,13 +402,43 @@ class ApiPageSet extends ApiQueryBase {
}
private function initFromRevIDs($revids) {
- $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented');
+
+ if(empty($revids))
+ return;
+
+ $db = & $this->getDB();
+ $pageids = array();
+ $remaining = array_flip($revids);
+
+ $tables = array('revision');
+ $fields = array('rev_id','rev_page');
+ $where = array('rev_deleted' => 0, 'rev_id' => $revids);
+
+ // Get pageIDs data from the `page` table
+ $this->profileDBIn();
+ $res = $db->select( $tables, $fields, $where, __METHOD__ );
+ while ( $row = $db->fetchObject( $res ) ) {
+ $revid = intval($row->rev_id);
+ $pageid = intval($row->rev_page);
+ $this->mGoodRevIDs[$revid] = $pageid;
+ $pageids[$pageid] = '';
+ unset($remaining[$revid]);
+ }
+ $db->freeResult( $res );
+ $this->profileDBOut();
+
+ $this->mMissingRevIDs = array_keys($remaining);
+
+ // Populate all the page information
+ if($this->mResolveRedirects)
+ ApiBase :: dieDebug(__METHOD__, 'revids may not be used with redirect resolution');
+ $this->initFromPageIds(array_keys($pageids));
}
private function resolvePendingRedirects() {
if($this->mResolveRedirects) {
- $db = $this->getDB();
+ $db = & $this->getDB();
$pageFlds = $this->getPageTableFields();
// Repeat until all redirects have been resolved
@@ -386,7 +470,7 @@ class ApiPageSet extends ApiQueryBase {
private function getRedirectTargets() {
$linkBatch = new LinkBatch();
- $db = $this->getDB();
+ $db = & $this->getDB();
// find redirect targets for all redirect pages
$this->profileDBIn();
@@ -443,7 +527,7 @@ class ApiPageSet extends ApiQueryBase {
// All IDs must exist in the page table
if (!empty($this->mPendingRedirectIDs[$plfrom]))
- $this->dieDebug('Invalid redirect IDs were found');
+ ApiBase :: dieDebug(__METHOD__, 'Invalid redirect IDs were found');
return $linkBatch;
}
@@ -508,7 +592,7 @@ class ApiPageSet extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiPageSet.php 16820 2006-10-06 01:02:14Z yurik $';
+ return __CLASS__ . ': $Id: ApiPageSet.php 17929 2006-11-25 17:11:58Z tstarling $';
}
}
-?> \ No newline at end of file
+?>
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index 985bde63..e7b7f351 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -45,15 +45,19 @@ class ApiQuery extends ApiBase {
// 'templates' => 'ApiQueryTemplates',
private $mQueryListModules = array (
- 'allpages' => 'ApiQueryAllpages'
+ 'allpages' => 'ApiQueryAllpages',
+ 'logevents' => 'ApiQueryLogEvents',
+ 'watchlist' => 'ApiQueryWatchlist',
+ 'recentchanges' => 'ApiQueryRecentChanges',
+ 'backlinks' => 'ApiQueryBacklinks',
+ 'embeddedin' => 'ApiQueryBacklinks',
+ 'imagelinks' => 'ApiQueryBacklinks',
+ 'usercontribs' => 'ApiQueryContributions'
);
- // 'backlinks' => 'ApiQueryBacklinks',
// 'categorymembers' => 'ApiQueryCategorymembers',
// 'embeddedin' => 'ApiQueryEmbeddedin',
// 'imagelinks' => 'ApiQueryImagelinks',
- // 'logevents' => 'ApiQueryLogevents',
// 'recentchanges' => 'ApiQueryRecentchanges',
- // 'usercontribs' => 'ApiQueryUsercontribs',
// 'users' => 'ApiQueryUsers',
// 'watchlist' => 'ApiQueryWatchlist',
@@ -75,9 +79,12 @@ class ApiQuery extends ApiBase {
$this->mAllowedGenerators = array_merge($this->mListModuleNames, $this->mPropModuleNames);
}
- public function getDB() {
- if (!isset ($this->mSlaveDB))
+ public function & getDB() {
+ if (!isset ($this->mSlaveDB)) {
+ $this->profileDBIn();
$this->mSlaveDB = & wfGetDB(DB_SLAVE);
+ $this->profileDBOut();
+ }
return $this->mSlaveDB;
}
@@ -151,6 +158,7 @@ class ApiQuery extends ApiBase {
private function outputGeneralPageInfo() {
$pageSet = $this->getPageSet();
+ $result = $this->getResult();
// Title normalizations
$normValues = array ();
@@ -162,8 +170,8 @@ class ApiQuery extends ApiBase {
}
if (!empty ($normValues)) {
- ApiResult :: setIndexedTagName($normValues, 'n');
- $this->getResult()->addValue('query', 'normalized', $normValues);
+ $result->setIndexedTagName($normValues, 'n');
+ $result->addValue('query', 'normalized', $normValues);
}
// Show redirect information
@@ -176,8 +184,23 @@ class ApiQuery extends ApiBase {
}
if (!empty ($redirValues)) {
- ApiResult :: setIndexedTagName($redirValues, 'r');
- $this->getResult()->addValue('query', 'redirects', $redirValues);
+ $result->setIndexedTagName($redirValues, 'r');
+ $result->addValue('query', 'redirects', $redirValues);
+ }
+
+ //
+ // Missing revision elements
+ //
+ $missingRevIDs = $pageSet->getMissingRevisionIDs();
+ if (!empty ($missingRevIDs)) {
+ $revids = array ();
+ foreach ($missingRevIDs as $revid) {
+ $revids[$revid] = array (
+ 'revid' => $revid
+ );
+ }
+ $result->setIndexedTagName($revids, 'rev');
+ $result->addValue('query', 'badrevids', $revids);
}
//
@@ -195,7 +218,7 @@ class ApiQuery extends ApiBase {
// Report any missing page ids
foreach ($pageSet->getMissingPageIDs() as $pageid) {
$pages[$pageid] = array (
- 'id' => $pageid,
+ 'pageid' => $pageid,
'missing' => ''
);
}
@@ -203,12 +226,13 @@ class ApiQuery extends ApiBase {
// Output general page information for found titles
foreach ($pageSet->getGoodTitles() as $pageid => $title) {
$pages[$pageid] = array (
- 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText(), 'id' => $pageid);
+ 'pageid' => $pageid,
+ 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText());
}
if (!empty ($pages)) {
- ApiResult :: setIndexedTagName($pages, 'page');
- $this->getResult()->addValue('query', 'pages', $pages);
+ $result->setIndexedTagName($pages, 'page');
+ $result->addValue('query', 'pages', $pages);
}
}
@@ -238,13 +262,13 @@ class ApiQuery extends ApiBase {
// 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;
}
@@ -346,7 +370,7 @@ class ApiQuery extends ApiBase {
public function getVersion() {
$psModule = new ApiPageSet($this);
$vers = array ();
- $vers[] = __CLASS__ . ': $Id: ApiQuery.php 16820 2006-10-06 01:02:14Z yurik $';
+ $vers[] = __CLASS__ . ': $Id: ApiQuery.php 17374 2006-11-03 06:53:47Z yurik $';
$vers[] = $psModule->getVersion();
return $vers;
}
diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php
index 51330d62..9c076e65 100644
--- a/includes/api/ApiQueryAllpages.php
+++ b/includes/api/ApiQueryAllpages.php
@@ -42,95 +42,84 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
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();
+ wfProfileIn($this->getModuleProfileName() . '-getDB');
+ $db = & $this->getDB();
+ wfProfileOut($this->getModuleProfileName() . '-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;
- }
+ wfProfileIn($this->getModuleProfileName() . '-parseParams');
+ $limit = $from = $namespace = $filterredir = $prefix = null;
+ extract($this->extractRequestParams());
+
+ $this->addTables('page');
+ if (!$this->addWhereIf('page_is_redirect = 1', $filterredir === 'redirects'))
+ $this->addWhereIf('page_is_redirect = 0', $filterredir === 'nonredirects');
+ $this->addWhereFld('page_namespace', $namespace);
+ if (isset ($from))
+ $this->addWhere('page_title>=' . $db->addQuotes(ApiQueryBase :: titleToKey($from)));
+ if (isset ($prefix))
+ $this->addWhere("page_title LIKE '{$db->strencode(ApiQueryBase :: titleToKey($prefix))}%'");
if (is_null($resultPageSet)) {
- $fields = array (
+ $this->addFields(array (
'page_id',
'page_namespace',
'page_title'
- );
+ ));
} else {
- $fields = $resultPageSet->getPageTableFields();
+ $this->addFields($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();
+ $this->addOption('USE INDEX', 'name_title');
+ $this->addOption('LIMIT', $limit +1);
+ $this->addOption('ORDER BY', 'page_namespace, page_title');
+
+ wfProfileOut($this->getModuleProfileName() . '-parseParams');
+
+ $res = $this->select(__METHOD__);
+
+ wfProfileIn($this->getModuleProfileName() . '-saveResults');
$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);
+ $this->setContinueEnumParameter('from', ApiQueryBase :: keyToTitle($row->page_title));
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);
- }
+ if (is_null($resultPageSet)) {
+ $vals = $this->addRowInfo('page', $row);
+ if ($vals)
+ $data[intval($row->page_id)] = $vals;
+ } else {
+ $resultPageSet->processDbRow($row);
}
}
$db->freeResult($res);
if (is_null($resultPageSet)) {
- ApiResult :: setIndexedTagName($data, 'p');
- $this->getResult()->addValue('query', 'allpages', $data);
+ $result = $this->getResult();
+ $result->setIndexedTagName($data, 'p');
+ $result->addValue('query', $this->getModuleName(), $data);
}
+
+ wfProfileOut($this->getModuleProfileName() . '-saveResults');
}
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,
+ 'prefix' => null,
'namespace' => array (
ApiBase :: PARAM_DFLT => 0,
- ApiBase :: PARAM_TYPE => $validNamespaces
+ ApiBase :: PARAM_TYPE => 'namespace'
),
'filterredir' => array (
ApiBase :: PARAM_DFLT => 'all',
@@ -144,8 +133,8 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
ApiBase :: PARAM_DFLT => 10,
ApiBase :: PARAM_TYPE => 'limit',
ApiBase :: PARAM_MIN => 1,
- ApiBase :: PARAM_MAX1 => 500,
- ApiBase :: PARAM_MAX2 => 5000
+ ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1,
+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
)
);
}
@@ -153,9 +142,10 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
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'
+ 'prefix' => 'Search for all page titles that begin with this value.',
+ 'namespace' => 'The namespace to enumerate.',
+ 'filterredir' => 'Which pages to list.',
+ 'limit' => 'How many total pages to return.'
);
}
@@ -166,8 +156,8 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
protected function getExamples() {
return array (
'Simple Use',
- ' api.php?action=query&list=allpages',
- ' api.php?action=query&list=allpages&apfrom=B&aplimit=5',
+ ' Show a list of pages starting at the letter "B"',
+ ' api.php?action=query&list=allpages&apfrom=B',
'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',
@@ -177,7 +167,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllpages.php 16820 2006-10-06 01:02:14Z yurik $';
+ return __CLASS__ . ': $Id: ApiQueryAllpages.php 17880 2006-11-23 08:25:56Z nickj $';
}
}
-?> \ No newline at end of file
+?>
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
new file mode 100644
index 00000000..413068f8
--- /dev/null
+++ b/includes/api/ApiQueryBacklinks.php
@@ -0,0 +1,358 @@
+<?php
+
+
+/*
+ * Created on Oct 16, 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 ApiQueryBacklinks extends ApiQueryGeneratorBase {
+
+ private $rootTitle, $contRedirs, $contLevel, $contTitle, $contID;
+
+ // output element name, database column field prefix, database table
+ private $backlinksSettings = array (
+ 'backlinks' => array (
+ 'code' => 'bl',
+ 'prefix' => 'pl',
+ 'linktbl' => 'pagelinks'
+ ),
+ 'embeddedin' => array (
+ 'code' => 'ei',
+ 'prefix' => 'tl',
+ 'linktbl' => 'templatelinks'
+ ),
+ 'imagelinks' => array (
+ 'code' => 'il',
+ 'prefix' => 'il',
+ 'linktbl' => 'imagelinks'
+ )
+ );
+
+ public function __construct($query, $moduleName) {
+ $code = $prefix = $linktbl = null;
+ extract($this->backlinksSettings[$moduleName]);
+
+ parent :: __construct($query, $moduleName, $code);
+ $this->bl_ns = $prefix . '_namespace';
+ $this->bl_from = $prefix . '_from';
+ $this->bl_tables = array (
+ $linktbl,
+ 'page'
+ );
+ $this->bl_code = $code;
+
+ $this->hasNS = $moduleName !== 'imagelinks';
+ if ($this->hasNS) {
+ $this->bl_title = $prefix . '_title';
+ $this->bl_sort = "{$this->bl_ns}, {$this->bl_title}, {$this->bl_from}";
+ $this->bl_fields = array (
+ $this->bl_ns,
+ $this->bl_title
+ );
+ } else {
+ $this->bl_title = $prefix . '_to';
+ $this->bl_sort = "{$this->bl_title}, {$this->bl_from}";
+ $this->bl_fields = array (
+ $this->bl_title
+ );
+ }
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator($resultPageSet) {
+ $this->run($resultPageSet);
+ }
+
+ private function run($resultPageSet = null) {
+ $continue = $namespace = $redirect = $limit = null;
+ extract($this->extractRequestParams());
+
+ if ($redirect)
+ ApiBase :: dieDebug(__METHOD__, 'Redirect is not yet been implemented', 'notimplemented');
+
+ $this->processContinue($continue, $redirect);
+
+ $this->addFields($this->bl_fields);
+ if (is_null($resultPageSet))
+ $this->addFields(array (
+ 'page_id',
+ 'page_namespace',
+ 'page_title'
+ ));
+ else
+ $this->addFields($resultPageSet->getPageTableFields()); // will include page_id
+
+ $this->addTables($this->bl_tables);
+ $this->addWhere($this->bl_from . '=page_id');
+
+ if ($this->hasNS)
+ $this->addWhereFld($this->bl_ns, $this->rootTitle->getNamespace());
+ $this->addWhereFld($this->bl_title, $this->rootTitle->getDBkey());
+ $this->addWhereFld('page_namespace', $namespace);
+ $this->addOption('LIMIT', $limit +1);
+ $this->addOption('ORDER BY', $this->bl_sort);
+
+ if ($redirect)
+ $this->addWhereFld('page_is_redirect', 0);
+
+ $db = & $this->getDB();
+ if (!is_null($continue)) {
+ $plfrm = intval($this->contID);
+ if ($this->contLevel == 0) {
+ // For the first level, there is only one target title, so no need for complex filtering
+ $this->addWhere($this->bl_from . '>=' . $plfrm);
+ } else {
+ $ns = $this->contTitle->getNamespace();
+ $t = $db->addQuotes($this->contTitle->getDBkey());
+ $whereWithoutNS = "{$this->bl_title}>$t OR ({$this->bl_title}=$t AND {$this->bl_from}>=$plfrm))";
+
+ if ($this->hasNS)
+ $this->addWhere("{$this->bl_ns}>$ns OR ({$this->bl_ns}=$ns AND ($whereWithoutNS)");
+ else
+ $this->addWhere($whereWithoutNS);
+ }
+ }
+
+ $res = $this->select(__METHOD__);
+
+ $count = 0;
+ $data = array ();
+ 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 ($redirect) {
+ $ns = $row-> {
+ $this->bl_ns };
+ $t = $row-> {
+ $this->bl_title };
+ $continue = $this->getContinueRedirStr(false, 0, $ns, $t, $row->page_id);
+ } else
+ $continue = $this->getContinueStr($row->page_id);
+ $this->setContinueEnumParameter('continue', $continue);
+ break;
+ }
+
+ if (is_null($resultPageSet)) {
+ $vals = $this->addRowInfo('page', $row);
+ if ($vals)
+ $data[intval($row->page_id)] = $vals;
+ } else {
+ $resultPageSet->processDbRow($row);
+ }
+ }
+ $db->freeResult($res);
+
+ if (is_null($resultPageSet)) {
+ $result = $this->getResult();
+ $result->setIndexedTagName($data, $this->bl_code);
+ $result->addValue('query', $this->getModuleName(), $data);
+ }
+ }
+
+ protected function processContinue($continue, $redirect) {
+ $pageSet = $this->getPageSet();
+ $count = $pageSet->getTitleCount();
+ if (!is_null($continue)) {
+ if ($count !== 0)
+ $this->dieUsage("When continuing the {$this->getModuleName()} query, no other titles may be provided", 'titles_on_continue');
+ $this->parseContinueParam($continue, $redirect);
+
+ // Skip all completed links
+
+ } else {
+ if ($count !== 1)
+ $this->dieUsage("The {$this->getModuleName()} query requires one title to start", 'bad_title_count');
+ $this->rootTitle = current($pageSet->getTitles()); // only one title there
+ }
+
+ // only image titles are allowed for the root
+ if (!$this->hasNS && $this->rootTitle->getNamespace() !== NS_IMAGE)
+ $this->dieUsage("The title for {$this->getModuleName()} query must be an image", 'bad_image_title');
+ }
+
+ protected function parseContinueParam($continue, $redirect) {
+ $continueList = explode('|', $continue);
+ if ($redirect) {
+ //
+ // expected redirect-mode parameter:
+ // ns|db_key|step|level|ns|db_key|id
+ // ns+db_key -- the root title
+ // step = 1 or 2 - which step to continue from - 1-titles, 2-redirects
+ // level -- how many levels to follow before starting enumerating.
+ // if level > 0 -- ns+title to continue from, otherwise skip these
+ // id = last page_id to continue from
+ //
+ if (count($continueList) > 4) {
+ $rootNs = intval($continueList[0]);
+ if (($rootNs !== 0 || $continueList[0] === '0') && !empty ($continueList[1])) {
+ $this->rootTitle = Title :: makeTitleSafe($rootNs, $continueList[1]);
+ if ($this->rootTitle && $this->rootTitle->userCanRead()) {
+
+ $step = intval($continueList[2]);
+ if ($step === 1 || $step === 2) {
+ $this->contRedirs = ($step === 2);
+
+ $level = intval($continueList[3]);
+ if ($level !== 0 || $continueList[3] === '0') {
+ $this->contLevel = $level;
+
+ if ($level === 0) {
+ if (count($continueList) === 5) {
+ $contID = intval($continueList[4]);
+ if ($contID !== 0 || $continueList[4] === '0') {
+ $this->contID = $contID;
+ return; // done
+ }
+ }
+ } else {
+ if (count($continueList) === 7) {
+ $contNs = intval($continueList[4]);
+ if (($contNs !== 0 || $continueList[4] === '0') && !empty ($continueList[5])) {
+ $this->contTitle = Title :: makeTitleSafe($contNs, $continueList[5]);
+
+ $contID = intval($continueList[6]);
+ if ($contID !== 0 || $continueList[6] === '0') {
+ $this->contID = $contID;
+ return; // done
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ //
+ // expected non-redirect-mode parameter:
+ // ns|db_key|id
+ // ns+db_key -- the root title
+ // id = last page_id to continue from
+ //
+ if (count($continueList) === 3) {
+ $rootNs = intval($continueList[0]);
+ if (($rootNs !== 0 || $continueList[0] === '0') && !empty ($continueList[1])) {
+ $this->rootTitle = Title :: makeTitleSafe($rootNs, $continueList[1]);
+ if ($this->rootTitle && $this->rootTitle->userCanRead()) {
+
+ $contID = intval($continueList[2]);
+ if ($contID !== 0) {
+ $this->contID = $contID;
+ return; // done
+ }
+ }
+ }
+ }
+ }
+
+ $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
+ }
+
+ protected function getContinueStr($lastPageID) {
+ return $this->rootTitle->getNamespace() .
+ '|' . $this->rootTitle->getDBkey() .
+ '|' . $lastPageID;
+ }
+
+ protected function getContinueRedirStr($isRedirPhase, $level, $ns, $title, $lastPageID) {
+ return $this->rootTitle->getNamespace() .
+ '|' . $this->rootTitle->getDBkey() .
+ '|' . ($isRedirPhase ? 1 : 2) .
+ '|' . $level .
+ ($level > 0 ? ('|' . $ns . '|' . $title) : '') .
+ '|' . $lastPageID;
+ }
+
+ protected function getAllowedParams() {
+
+ return array (
+ 'continue' => null,
+ 'namespace' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => 'namespace'
+ ),
+ 'redirect' => false,
+ 'limit' => array (
+ ApiBase :: PARAM_DFLT => 10,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 1,
+ ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1,
+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'continue' => 'When more results are available, use this to continue.',
+ 'namespace' => 'The namespace to enumerate.',
+ 'redirect' => 'If linking page is a redirect, find all pages that link to that redirect (not implemented)',
+ 'limit' => 'How many total pages to return.'
+ );
+ }
+
+ protected function getDescription() {
+ switch ($this->getModuleName()) {
+ case 'backlinks' :
+ return 'Find all pages that link to the given page';
+ case 'embeddedin' :
+ return 'Find all pages that embed (transclude) the given title';
+ case 'imagelinks' :
+ return 'Find all pages that use the given image title.';
+ default :
+ ApiBase :: dieDebug(__METHOD__, 'Unknown module name');
+ }
+ }
+
+ protected function getExamples() {
+ static $examples = array (
+ 'backlinks' => array (
+ "api.php?action=query&list=backlinks&titles=Main%20Page",
+ "api.php?action=query&generator=backlinks&titles=Main%20Page&prop=info"
+ ),
+ 'embeddedin' => array (
+ "api.php?action=query&list=embeddedin&titles=Template:Stub",
+ "api.php?action=query&generator=embeddedin&titles=Template:Stub&prop=info"
+ ),
+ 'imagelinks' => array (
+ "api.php?action=query&list=imagelinks&titles=Image:Albert%20Einstein%20Head.jpg",
+ "api.php?action=query&generator=imagelinks&titles=Image:Albert%20Einstein%20Head.jpg&prop=info"
+ )
+ );
+
+ return $examples[$this->getModuleName()];
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryBacklinks.php 17880 2006-11-23 08:25:56Z nickj $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 574f742e..ae4edf98 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -31,11 +31,260 @@ if (!defined('MEDIAWIKI')) {
abstract class ApiQueryBase extends ApiBase {
- private $mQueryModule;
-
+ private $mQueryModule, $tables, $where, $fields, $options;
+
public function __construct($query, $moduleName, $paramPrefix = '') {
parent :: __construct($query->getMain(), $moduleName, $paramPrefix);
$this->mQueryModule = $query;
+ $this->resetQueryParams();
+ }
+
+ protected function resetQueryParams() {
+ $this->tables = array ();
+ $this->where = array ();
+ $this->fields = array ();
+ $this->options = array ();
+ }
+
+ protected function addTables($value) {
+ if (is_array($value))
+ $this->tables = array_merge($this->tables, $value);
+ else
+ $this->tables[] = $value;
+ }
+
+ protected function addFields($value) {
+ if (is_array($value))
+ $this->fields = array_merge($this->fields, $value);
+ else
+ $this->fields[] = $value;
+ }
+
+ protected function addFieldsIf($value, $condition) {
+ if ($condition) {
+ $this->addFields($value);
+ return true;
+ }
+ return false;
+ }
+
+ protected function addWhere($value) {
+ if (is_array($value))
+ $this->where = array_merge($this->where, $value);
+ else
+ $this->where[] = $value;
+ }
+
+ protected function addWhereIf($value, $condition) {
+ if ($condition) {
+ $this->addWhere($value);
+ return true;
+ }
+ return false;
+ }
+
+ protected function addWhereFld($field, $value) {
+ if (!is_null($value))
+ $this->where[$field] = $value;
+ }
+
+ protected function addWhereRange($field, $dir, $start, $end) {
+ $isDirNewer = ($dir === 'newer');
+ $after = ($isDirNewer ? '>=' : '<=');
+ $before = ($isDirNewer ? '<=' : '>=');
+ $db = $this->getDB();
+
+ if (!is_null($start))
+ $this->addWhere($field . $after . $db->addQuotes($start));
+
+ if (!is_null($end))
+ $this->addWhere($field . $before . $db->addQuotes($end));
+
+ $this->addOption('ORDER BY', $field . ($isDirNewer ? '' : ' DESC'));
+ }
+
+ protected function addOption($name, $value = null) {
+ if (is_null($value))
+ $this->options[] = $name;
+ else
+ $this->options[$name] = $value;
+ }
+
+ protected function select($method) {
+
+ // getDB has its own profileDBIn/Out calls
+ $db = $this->getDB();
+
+ $this->profileDBIn();
+ $res = $db->select($this->tables, $this->fields, $this->where, $method, $this->options);
+ $this->profileDBOut();
+
+ return $res;
+ }
+
+ protected function addRowInfo($prefix, $row) {
+
+ $vals = array ();
+
+ // ID
+ if ( isset( $row-> { $prefix . '_id' } ) )
+ $vals[$prefix . 'id'] = intval( $row-> { $prefix . '_id' } );
+
+ // Title
+ $title = ApiQueryBase :: addRowInfo_title($row, $prefix . '_namespace', $prefix . '_title');
+ if ($title) {
+ if (!$title->userCanRead())
+ return false;
+ $vals['ns'] = $title->getNamespace();
+ $vals['title'] = $title->getPrefixedText();
+ }
+
+ switch ($prefix) {
+
+ case 'page' :
+ // page_is_redirect
+ @ $tmp = $row->page_is_redirect;
+ if ($tmp)
+ $vals['redirect'] = '';
+
+ break;
+
+ case 'rc' :
+ // PageId
+ @ $tmp = $row->rc_cur_id;
+ if (!is_null($tmp))
+ $vals['pageid'] = intval($tmp);
+
+ @ $tmp = $row->rc_this_oldid;
+ if (!is_null($tmp))
+ $vals['revid'] = intval($tmp);
+
+ if ( isset( $row->rc_last_oldid ) )
+ $vals['old_revid'] = intval( $row->rc_last_oldid );
+
+ $title = ApiQueryBase :: addRowInfo_title($row, 'rc_moved_to_ns', 'rc_moved_to_title');
+ if ($title) {
+ if (!$title->userCanRead())
+ return false;
+ $vals['new_ns'] = $title->getNamespace();
+ $vals['new_title'] = $title->getPrefixedText();
+ }
+
+ if ( isset( $row->rc_patrolled ) )
+ $vals['patrolled'] = '';
+
+ break;
+
+ case 'log' :
+ // PageId
+ @ $tmp = $row->page_id;
+ if (!is_null($tmp))
+ $vals['pageid'] = intval($tmp);
+
+ if ($row->log_params !== '') {
+ $params = explode("\n", $row->log_params);
+ if ($row->log_type == 'move' && isset ($params[0])) {
+ $newTitle = Title :: newFromText($params[0]);
+ if ($newTitle) {
+ $vals['new_ns'] = $newTitle->getNamespace();
+ $vals['new_title'] = $newTitle->getPrefixedText();
+ $params = null;
+ }
+ }
+
+ if (!empty ($params)) {
+ $this->getResult()->setIndexedTagName($params, 'param');
+ $vals = array_merge($vals, $params);
+ }
+ }
+
+ break;
+
+ case 'rev' :
+ // PageID
+ @ $tmp = $row->rev_page;
+ if (!is_null($tmp))
+ $vals['pageid'] = intval($tmp);
+ }
+
+ // Type
+ @ $tmp = $row-> {
+ $prefix . '_type' };
+ if (!is_null($tmp))
+ $vals['type'] = $tmp;
+
+ // Action
+ @ $tmp = $row-> {
+ $prefix . '_action' };
+ if (!is_null($tmp))
+ $vals['action'] = $tmp;
+
+ // Old ID
+ @ $tmp = $row-> {
+ $prefix . '_text_id' };
+ if (!is_null($tmp))
+ $vals['oldid'] = intval($tmp);
+
+ // User Name / Anon IP
+ @ $tmp = $row-> {
+ $prefix . '_user_text' };
+ if (is_null($tmp))
+ @ $tmp = $row->user_name;
+ if (!is_null($tmp)) {
+ $vals['user'] = $tmp;
+ @ $tmp = !$row-> {
+ $prefix . '_user' };
+ if (!is_null($tmp) && $tmp)
+ $vals['anon'] = '';
+ }
+
+ // Bot Edit
+ @ $tmp = $row-> {
+ $prefix . '_bot' };
+ if (!is_null($tmp) && $tmp)
+ $vals['bot'] = '';
+
+ // New Edit
+ @ $tmp = $row-> {
+ $prefix . '_new' };
+ if (is_null($tmp))
+ @ $tmp = $row-> {
+ $prefix . '_is_new' };
+ if (!is_null($tmp) && $tmp)
+ $vals['new'] = '';
+
+ // Minor Edit
+ @ $tmp = $row-> {
+ $prefix . '_minor_edit' };
+ if (is_null($tmp))
+ @ $tmp = $row-> {
+ $prefix . '_minor' };
+ if (!is_null($tmp) && $tmp)
+ $vals['minor'] = '';
+
+ // Timestamp
+ @ $tmp = $row-> {
+ $prefix . '_timestamp' };
+ if (!is_null($tmp))
+ $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $tmp);
+
+ // Comment
+ @ $tmp = $row-> {
+ $prefix . '_comment' };
+ if (!empty ($tmp)) // optimize bandwidth
+ $vals['comment'] = $tmp;
+
+ return $vals;
+ }
+
+ private static function addRowInfo_title($row, $nsfld, $titlefld) {
+ if ( isset( $row-> $nsfld ) ) {
+ $ns = $row-> $nsfld;
+ @ $title = $row-> $titlefld;
+ if (!empty ($title))
+ return Title :: makeTitle($ns, $title);
+ }
+ return false;
}
/**
@@ -46,12 +295,19 @@ abstract class ApiQueryBase extends ApiBase {
}
/**
- * Get the main Query module
+ * Get the main Query module
*/
public function getQuery() {
return $this->mQueryModule;
}
+ protected function setContinueEnumParameter($paramName, $paramValue) {
+ $msg = array (
+ $this->encodeParamName($paramName
+ ) => $paramValue);
+ $this->getResult()->addValue('query-continue', $this->getModuleName(), $msg);
+ }
+
/**
* Get the Query database connection (readonly)
*/
@@ -67,6 +323,11 @@ abstract class ApiQueryBase extends ApiBase {
return $this->mQueryModule->getPageSet();
}
+ /**
+ * This is a very simplistic utility function
+ * to convert a non-namespaced title string to a db key.
+ * It will replace all ' ' with '_'
+ */
public static function titleToKey($title) {
return str_replace(' ', '_', $title);
}
@@ -76,7 +337,7 @@ abstract class ApiQueryBase extends ApiBase {
}
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiQueryBase.php 16757 2006-10-03 05:41:55Z yurik $';
+ return __CLASS__ . ': $Id: ApiQueryBase.php 17987 2006-11-29 05:45:03Z nickj $';
}
}
@@ -86,7 +347,7 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
public function __construct($query, $moduleName, $paramPrefix = '') {
parent :: __construct($query, $moduleName, $paramPrefix);
- $mIsGenerator = false;
+ $this->mIsGenerator = false;
}
public function setGeneratorMode() {
@@ -109,4 +370,4 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
*/
public abstract function executeGenerator($resultPageSet);
}
-?> \ No newline at end of file
+?>
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index de651b00..d93d37a2 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -46,14 +46,17 @@ class ApiQueryInfo extends ApiQueryBase {
$pageSet = $this->getPageSet();
$titles = $pageSet->getGoodTitles();
- $result = & $this->getResult();
+ $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]);
+ foreach ( $titles as $pageid => $unused ) {
+ $pageInfo = array (
+ 'touched' => wfTimestamp(TS_ISO_8601, $pageTouched[$pageid]),
+ 'lastrevid' => intval($pageLatest[$pageid])
+ );
if ($pageIsRedir[$pageid])
$pageInfo['redirect'] = '';
@@ -76,7 +79,7 @@ class ApiQueryInfo extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryInfo.php 16757 2006-10-03 05:41:55Z yurik $';
+ return __CLASS__ . ': $Id: ApiQueryInfo.php 17929 2006-11-25 17:11:58Z tstarling $';
}
}
-?> \ No newline at end of file
+?>
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
new file mode 100644
index 00000000..243f96fa
--- /dev/null
+++ b/includes/api/ApiQueryLogEvents.php
@@ -0,0 +1,173 @@
+<?php
+
+
+/*
+ * Created on Oct 16, 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 ApiQueryLogEvents extends ApiQueryBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'le');
+ }
+
+ public function execute() {
+ $limit = $type = $start = $end = $dir = $user = $title = null;
+ extract($this->extractRequestParams());
+
+ $db = & $this->getDB();
+
+ list($tbl_logging, $tbl_page, $tbl_user) = $db->tableNamesN('logging', 'page', 'user');
+
+ $this->addOption('STRAIGHT_JOIN');
+ $this->addTables("$tbl_logging LEFT OUTER JOIN $tbl_page ON " .
+ "log_namespace=page_namespace AND log_title=page_title " .
+ "INNER JOIN $tbl_user ON user_id=log_user");
+
+ $this->addFields(array (
+ 'log_type',
+ 'log_action',
+ 'log_timestamp',
+ 'log_user',
+ 'user_name',
+ 'log_namespace',
+ 'log_title',
+ 'page_id',
+ 'log_comment',
+ 'log_params'
+ ));
+
+ $this->addWhereFld('log_type', $type);
+ $this->addWhereRange('log_timestamp', $dir, $start, $end);
+ $this->addOption('LIMIT', $limit +1);
+
+ if (!is_null($user)) {
+ $userid = $db->selectField('user', 'user_id', array (
+ 'user_name' => $user
+ ));
+ if (!$userid)
+ $this->dieUsage("User name $user not found", 'param_user');
+ $this->addWhereFld('log_user', $userid);
+ }
+
+ if (!is_null($title)) {
+ $titleObj = Title :: newFromText($title);
+ if (is_null($titleObj))
+ $this->dieUsage("Bad title value '$title'", 'param_title');
+ $this->addWhereFld('log_namespace', $titleObj->getNamespace());
+ $this->addWhereFld('log_title', $titleObj->getDBkey());
+ }
+
+ $data = array ();
+ $count = 0;
+ $res = $this->select(__METHOD__);
+ while ($row = $db->fetchObject($res)) {
+ if (++ $count > $limit) {
+ // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter('start', $row->log_timestamp);
+ break;
+ }
+
+ $vals = $this->addRowInfo('log', $row);
+ if($vals)
+ $data[] = $vals;
+ }
+ $db->freeResult($res);
+
+ $this->getResult()->setIndexedTagName($data, 'item');
+ $this->getResult()->addValue('query', $this->getModuleName(), $data);
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'type' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => array (
+ 'block',
+ 'protect',
+ 'rights',
+ 'delete',
+ 'upload',
+ 'move',
+ 'import',
+ 'renameuser',
+ 'newusers',
+ 'makebot'
+ )
+ ),
+ 'start' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'end' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'dir' => array (
+ ApiBase :: PARAM_DFLT => 'older',
+ ApiBase :: PARAM_TYPE => array (
+ 'newer',
+ 'older'
+ )
+ ),
+ 'user' => null,
+ 'title' => null,
+ 'limit' => array (
+ ApiBase :: PARAM_DFLT => 10,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 1,
+ ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1,
+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'type' => 'Filter log entries to only this type(s)',
+ 'start' => 'The timestamp to start enumerating from.',
+ 'end' => 'The timestamp to end enumerating.',
+ 'dir' => 'In which direction to enumerate.',
+ 'user' => 'Filter entries to those made by the given user.',
+ 'title' => 'Filter entries to those related to a page.',
+ 'limit' => 'How many total event entries to return.'
+ );
+ }
+
+ protected function getDescription() {
+ return 'Get events from logs.';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=query&list=logevents'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryLogEvents.php 17952 2006-11-27 08:36:57Z nickj $';
+ }
+}
+?>
diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php
new file mode 100644
index 00000000..38f51b05
--- /dev/null
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -0,0 +1,187 @@
+<?php
+
+
+/*
+ * Created on Oct 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 ('ApiQueryBase.php');
+}
+
+class ApiQueryRecentChanges extends ApiQueryBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'rc');
+ }
+
+ public function execute() {
+ $limit = $prop = $namespace = $show = $dir = $start = $end = null;
+ extract($this->extractRequestParams());
+
+ $this->addTables('recentchanges');
+ $this->addWhereRange('rc_timestamp', $dir, $start, $end);
+ $this->addWhereFld('rc_namespace', $namespace);
+
+ if (!is_null($show)) {
+ $show = array_flip($show);
+ if ((isset ($show['minor']) && isset ($show['!minor'])) || (isset ($show['bot']) && isset ($show['!bot'])) || (isset ($show['anon']) && isset ($show['!anon'])))
+ $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show');
+
+ $this->addWhereIf('rc_minor = 0', isset ($show['!minor']));
+ $this->addWhereIf('rc_minor != 0', isset ($show['minor']));
+ $this->addWhereIf('rc_bot = 0', isset ($show['!bot']));
+ $this->addWhereIf('rc_bot != 0', isset ($show['bot']));
+ $this->addWhereIf('rc_user = 0', isset ($show['anon']));
+ $this->addWhereIf('rc_user != 0', isset ($show['!anon']));
+ }
+
+ $this->addFields(array (
+ 'rc_timestamp',
+ 'rc_namespace',
+ 'rc_title',
+ 'rc_cur_id',
+ 'rc_this_oldid',
+ 'rc_last_oldid',
+ 'rc_type',
+ 'rc_moved_to_ns',
+ 'rc_moved_to_title'
+ ));
+
+ if (!is_null($prop)) {
+ $prop = array_flip($prop);
+ $this->addFieldsIf('rc_comment', isset ($prop['comment']));
+ if (isset ($prop['user'])) {
+ $this->addFields('rc_user');
+ $this->addFields('rc_user_text');
+ }
+ if (isset ($prop['flags'])) {
+ $this->addFields('rc_minor');
+ $this->addFields('rc_bot');
+ $this->addFields('rc_new');
+ }
+ }
+
+ $this->addOption('LIMIT', $limit +1);
+ $this->addOption('USE INDEX', 'rc_timestamp');
+
+ $data = array ();
+ $count = 0;
+ $db = & $this->getDB();
+ $res = $this->select(__METHOD__);
+ while ($row = $db->fetchObject($res)) {
+ if (++ $count > $limit) {
+ // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter('start', $row->rc_timestamp);
+ break;
+ }
+
+ $vals = $this->addRowInfo('rc', $row);
+ if ($vals)
+ $data[] = $vals;
+ }
+ $db->freeResult($res);
+
+ $result = $this->getResult();
+ $result->setIndexedTagName($data, 'rc');
+ $result->addValue('query', $this->getModuleName(), $data);
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'start' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'end' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'dir' => array (
+ ApiBase :: PARAM_DFLT => 'older',
+ ApiBase :: PARAM_TYPE => array (
+ 'newer',
+ 'older'
+ )
+ ),
+ 'namespace' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => 'namespace'
+ ),
+ 'prop' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => array (
+ 'user',
+ 'comment',
+ 'flags'
+ )
+ ),
+ 'show' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => array (
+ 'minor',
+ '!minor',
+ 'bot',
+ '!bot',
+ 'anon',
+ '!anon'
+ )
+ ),
+ 'limit' => array (
+ ApiBase :: PARAM_DFLT => 10,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 1,
+ ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1,
+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'start' => 'The timestamp to start enumerating from.',
+ 'end' => 'The timestamp to end enumerating.',
+ 'dir' => 'In which direction to enumerate.',
+ 'namespace' => 'Filter log entries to only this namespace(s)',
+ 'prop' => 'Include additional pieces of information',
+ 'show' => array (
+ 'Show only items that meet this criteria.',
+ 'For example, to see only minor edits done by logged-in users, set show=minor|!anon'
+ ),
+ 'limit' => 'How many total pages to return.'
+ );
+ }
+
+ protected function getDescription() {
+ return 'Enumerate recent changes';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=query&list=recentchanges'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 17880 2006-11-23 08:25:56Z nickj $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index f6097bad..3f678ff7 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -39,16 +39,11 @@ class ApiQueryRevisions extends ApiQueryBase {
$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));
+ $enumRevMode = (!is_null($limit) || !is_null($startid) || !is_null($endid) || $dir === 'newer' || !is_null($start) || !is_null($end));
$pageSet = $this->getPageSet();
$pageCount = $pageSet->getGoodTitleCount();
@@ -58,57 +53,38 @@ class ApiQueryRevisions extends ApiQueryBase {
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)
+ if ($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 (
+ $this->addTables('revision');
+ $this->addFields(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");
- }
+ ));
+ $this->addWhere('rev_deleted=0');
+
+ $showContent = false;
+
+ if (!is_null($prop)) {
+ $prop = array_flip($prop);
+ $this->addFieldsIf('rev_timestamp', isset ($prop['timestamp']));
+ $this->addFieldsIf('rev_comment', isset ($prop['comment']));
+ if (isset ($prop['user'])) {
+ $this->addFields('rev_user');
+ $this->addFields('rev_user_text');
+ }
+ if (isset ($prop['content'])) {
+ $this->addTables('text');
+ $this->addWhere('rev_text_id=old_id');
+ $this->addFields('old_id');
+ $this->addFields('old_text');
+ $this->addFields('old_flags');
+ $showContent = true;
}
}
@@ -118,10 +94,10 @@ class ApiQueryRevisions extends ApiQueryBase {
if ($enumRevMode) {
// This is mostly to prevent parameter errors (and optimize sql?)
- if ($startid !== 0 && isset ($start))
+ if (!is_null($startid) && !is_null($start))
$this->dieUsage('start and startid cannot be used together', 'badparams');
- if ($endid !== 0 && isset ($end))
+ if (!is_null($endid) && !is_null($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
@@ -130,40 +106,30 @@ class ApiQueryRevisions extends ApiQueryBase {
// 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);
+ if (is_null($startid))
+ $this->addWhereRange('rev_timestamp', $dir, $start, $end);
+ else
+ $this->addWhereRange('rev_id', $dir, $startid, $endid);
// must manually initialize unset limit
- if (!isset ($limit))
+ if (is_null($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()));
-
+ $this->addWhereFld('rev_page', current(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->addTables('page');
+ $this->addWhere('page_id=rev_page');
+ $this->addWhere('page_latest=rev_id');
$this->validateLimit('page_count', $pageCount, 1, $userMax, $botMax);
// Get all page IDs
- $conds['page_id'] = array_keys($pageSet->getGoodTitles());
+ $this->addWhereFld('page_id', array_keys($pageSet->getGoodTitles()));
$limit = $pageCount; // assumption testing -- we should never get more then $pageCount rows.
}
@@ -171,72 +137,51 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->validateLimit('rev_count', $revCount, 1, $userMax, $botMax);
// Get all revision IDs
- $conds['rev_id'] = array_keys($pageSet->getRevisionIDs());
+ $this->addWhereFld('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();
+ $this->addOption('LIMIT', $limit +1);
$data = array ();
$count = 0;
+ $res = $this->select(__METHOD__);
+
+ $db = & $this->getDB();
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);
+ $this->setContinueEnumParameter('startid', $row->rev_id);
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;
+ $vals = $this->addRowInfo('rev', $row);
+ if ($vals) {
+ if ($showContent)
+ ApiResult :: setContent($vals, Revision :: getRevisionText($row));
- if ($showContent) {
- ApiResult :: setContent($vals, Revision :: getRevisionText($row));
+ $this->getResult()->addValue(array (
+ 'query',
+ 'pages',
+ intval($row->rev_page
+ ), 'revisions'), intval($row->rev_id), $vals);
}
-
- $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');
+ // Ensure that all revisions are shown as '<rev>' elements
+ $result = $this->getResult();
+ if ($result->getIsRawMode()) {
+ $data = $result->getData();
+ foreach ($data['query']['pages'] as & $page) {
+ if (is_array($page) && array_key_exists('revisions', $page)) {
+ $result->setIndexedTagName($page['revisions'], 'rev');
+ }
}
}
}
@@ -253,14 +198,17 @@ class ApiQueryRevisions extends ApiQueryBase {
)
),
'limit' => array (
- ApiBase :: PARAM_DFLT => 0,
ApiBase :: PARAM_TYPE => 'limit',
- ApiBase :: PARAM_MIN => 0,
- ApiBase :: PARAM_MAX1 => 50,
- ApiBase :: PARAM_MAX2 => 500
+ ApiBase :: PARAM_MIN => 1,
+ ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_SML1,
+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_SML2
+ ),
+ 'startid' => array (
+ ApiBase :: PARAM_TYPE => 'integer'
+ ),
+ 'endid' => array (
+ ApiBase :: PARAM_TYPE => 'integer'
),
- 'startid' => 0,
- 'endid' => 0,
'start' => array (
ApiBase :: PARAM_TYPE => 'timestamp'
),
@@ -279,7 +227,7 @@ class ApiQueryRevisions extends ApiQueryBase {
protected function getParamDescription() {
return array (
- 'prop' => 'Which properties to get for each revision: user|timestamp|comment|content',
+ 'prop' => 'Which properties to get for each revision.',
'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)',
@@ -314,7 +262,7 @@ class ApiQueryRevisions extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryRevisions.php 16757 2006-10-03 05:41:55Z yurik $';
+ return __CLASS__ . ': $Id: ApiQueryRevisions.php 17374 2006-11-03 06:53:47Z yurik $';
}
}
-?> \ No newline at end of file
+?>
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
index 27c3f187..9e8c11ff 100644
--- a/includes/api/ApiQuerySiteinfo.php
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -44,7 +44,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
case 'general' :
- global $wgSitename, $wgVersion, $wgCapitalLinks;
+ global $wgSitename, $wgVersion, $wgCapitalLinks, $wgRightsCode, $wgRightsText;
$data = array ();
$mainPage = Title :: newFromText(wfMsgForContent('mainpage'));
$data['mainpage'] = $mainPage->getText();
@@ -52,6 +52,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['sitename'] = $wgSitename;
$data['generator'] = "MediaWiki $wgVersion";
$data['case'] = $wgCapitalLinks ? 'first-letter' : 'case-sensitive'; // 'case-insensitive' option is reserved for future
+ if (isset($wgRightsCode))
+ $data['rightscode'] = $wgRightsCode;
+ $data['rights'] = $wgRightsText;
$this->getResult()->addValue('query', $p, $data);
break;
@@ -65,10 +68,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
);
ApiResult :: setContent($data[$ns], $title);
}
- ApiResult :: setIndexedTagName($data, 'ns');
+ $this->getResult()->setIndexedTagName($data, 'ns');
$this->getResult()->addValue('query', $p, $data);
break;
-
+
default :
ApiBase :: dieDebug(__METHOD__, "Unknown prop=$p");
}
@@ -107,7 +110,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 16757 2006-10-03 05:41:55Z yurik $';
+ return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 17265 2006-10-27 03:50:34Z yurik $';
}
}
?> \ No newline at end of file
diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php
new file mode 100644
index 00000000..4f63cadb
--- /dev/null
+++ b/includes/api/ApiQueryUserContributions.php
@@ -0,0 +1,175 @@
+<?php
+
+
+/*
+ * Created on Oct 16, 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 ApiQueryContributions extends ApiQueryBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'uc');
+ }
+
+ public function execute() {
+
+ //Blank all our variables
+ $limit = $user = $start = $end = $dir = null;
+
+ //Get our parameters out
+ extract($this->extractRequestParams());
+
+ //Get a database instance
+ $db = & $this->getDB();
+
+ if (is_null($user))
+ $this->dieUsage("User parameter may not be empty", 'param_user');
+ $userid = $db->selectField('user', 'user_id', array (
+ 'user_name' => $user
+ ));
+ if (!$userid)
+ $this->dieUsage("User name $user not found", 'param_user');
+
+ //Get the table names
+ list ($tbl_page, $tbl_revision) = $db->tableNamesN('page', 'revision');
+
+ //We're after the revision table, and the corresponding page row for
+ //anything we retrieve.
+ $this->addTables("$tbl_revision LEFT OUTER JOIN $tbl_page ON " .
+ "page_id=rev_page");
+
+ //We want to know the namespace, title, new-ness, and ID of a page,
+ // and the id, text-id, timestamp, minor-status, summary and page
+ // of a revision.
+ $this->addFields(array('page_namespace', 'page_title', 'page_is_new',
+ 'rev_id', 'rev_text_id', 'rev_timestamp', 'rev_minor_edit',
+ 'rev_comment', 'rev_page'));
+
+ // We only want pages by the specified user.
+ $this->addWhereFld('rev_user_text', $user);
+ // ... and in the specified timeframe.
+ $this->addWhereRange('rev_timestamp', $dir, $start, $end );
+
+ $this->addOption('LIMIT', $limit + 1);
+
+ //Initialise some variables
+ $data = array ();
+ $count = 0;
+
+ //Do the actual query.
+ $res = $this->select( __METHOD__ );
+
+ //Fetch each row
+ while ( $row = $db->fetchObject( $res ) ) {
+ if (++ $count > $limit) {
+ // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter('start', $row->rev_timestamp);
+ break;
+ }
+
+ //There's a fancy function in ApiQueryBase that does
+ // most of the work for us. Use that for the page
+ // and revision.
+ $revvals = $this->addRowInfo('rev', $row);
+ $pagevals = $this->addRowInfo('page', $row);
+
+ //If we got data on the revision only, use only
+ // that data.
+ if($revvals && !$pagevals) {
+ $data[] = $revvals;
+ }
+ //If we got data on the page only, use only
+ // that data.
+ else if($pagevals && !$revvals) {
+ $data[] = $pagevals;
+ }
+ //... and if we got data on both the revision and
+ // the page, merge the data and send it out.
+ else if($pagevals && $revvals) {
+ $data[] = array_merge($revvals, $pagevals);
+ }
+ }
+
+ //Free the database record so the connection can get on with other stuff
+ $db->freeResult($res);
+
+ //And send the whole shebang out as output.
+ $this->getResult()->setIndexedTagName($data, 'item');
+ $this->getResult()->addValue('query', $this->getModuleName(), $data);
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'limit' => array (
+ ApiBase :: PARAM_DFLT => 10,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 1,
+ ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1,
+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ ),
+ 'start' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'end' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'user' => null,
+ 'dir' => array (
+ ApiBase :: PARAM_DFLT => 'older',
+ ApiBase :: PARAM_TYPE => array (
+ 'newer',
+ 'older'
+ )
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'limit' => 'The maximum number of contributions to return.',
+ 'start' => 'The start timestamp to return from.',
+ 'end' => 'The end timestamp to return to.',
+ 'user' => 'The user to retrieve contributions for.',
+ 'dir' => 'The direction to search (older or newer).'
+ );
+ }
+
+ protected function getDescription() {
+ return 'Get edits by a user..';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=query&list=usercontribs&ucuser=YurikBot'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryUserContributions.php 17952 2006-11-27 08:36:57Z nickj $';
+ }
+}
+?>
diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php
new file mode 100644
index 00000000..67564d62
--- /dev/null
+++ b/includes/api/ApiQueryWatchlist.php
@@ -0,0 +1,234 @@
+<?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 ApiQueryWatchlist extends ApiQueryGeneratorBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'wl');
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator($resultPageSet) {
+ $this->run($resultPageSet);
+ }
+
+ private function run($resultPageSet = null) {
+ global $wgUser;
+
+ if (!$wgUser->isLoggedIn())
+ $this->dieUsage('You must be logged-in to have a watchlist', 'notloggedin');
+
+ $allrev = $start = $end = $namespace = $dir = $limit = $prop = null;
+ extract($this->extractRequestParams());
+
+ $patrol = $timestamp = $user = $comment = false;
+ if (!is_null($prop)) {
+ if (!is_null($resultPageSet))
+ $this->dieUsage('prop parameter may not be used in a generator', 'params');
+
+ $user = (false !== array_search('user', $prop));
+ $comment = (false !== array_search('comment', $prop));
+ $timestamp = (false !== array_search('timestamp', $prop)); // TODO: $timestamp not currently being used.
+ $patrol = (false !== array_search('patrol', $prop));
+
+ if ($patrol) {
+ global $wgUseRCPatrol, $wgUser;
+ if (!$wgUseRCPatrol || !$wgUser->isAllowed('patrol'))
+ $this->dieUsage('patrol property is not available', 'patrol');
+ }
+ }
+
+ if (is_null($resultPageSet)) {
+ $this->addFields(array (
+ 'rc_cur_id',
+ 'rc_this_oldid',
+ 'rc_namespace',
+ 'rc_title',
+ 'rc_new',
+ 'rc_minor',
+ 'rc_timestamp'
+ ));
+
+ $this->addFieldsIf('rc_user', $user);
+ $this->addFieldsIf('rc_user_text', $user);
+ $this->addFieldsIf('rc_comment', $comment);
+ $this->addFieldsIf('rc_patrolled', $patrol);
+ }
+ elseif ($allrev) {
+ $this->addFields(array (
+ 'rc_this_oldid',
+ 'rc_namespace',
+ 'rc_title',
+ 'rc_timestamp'
+ ));
+ } else {
+ $this->addFields(array (
+ 'rc_cur_id',
+ 'rc_namespace',
+ 'rc_title',
+ 'rc_timestamp'
+ ));
+ }
+
+ $this->addTables(array (
+ 'watchlist',
+ 'page',
+ 'recentchanges'
+ ));
+
+ $userId = $wgUser->getID();
+ $this->addWhere(array (
+ 'wl_namespace = rc_namespace',
+ 'wl_title = rc_title',
+ 'rc_cur_id = page_id',
+ 'wl_user' => $userId
+ ));
+ $this->addWhereRange('rc_timestamp', $dir, $start, $end);
+ $this->addWhereFld('wl_namespace', $namespace);
+ $this->addWhereIf('rc_this_oldid=page_latest', !$allrev);
+ $this->addWhereIf("rc_timestamp > ''", !isset ($start) && !isset ($end));
+
+ $this->addOption('LIMIT', $limit +1);
+
+ $data = array ();
+ $count = 0;
+ $res = $this->select(__METHOD__);
+
+ $db = $this->getDB();
+ while ($row = $db->fetchObject($res)) {
+ if (++ $count > $limit) {
+ // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter('start', $row->rc_timestamp);
+ break;
+ }
+
+ if (is_null($resultPageSet)) {
+ $vals = $this->addRowInfo('rc', $row);
+ if($vals)
+ $data[] = $vals;
+ } else {
+ $title = Title :: makeTitle($row->rc_namespace, $row->rc_title);
+ // skip any pages that user has no rights to read
+ if ($title->userCanRead()) {
+ if ($allrev) {
+ $data[] = intval($row->rc_this_oldid);
+ } else {
+ $data[] = intval($row->rc_cur_id);
+ }
+ }
+ }
+ }
+
+ $db->freeResult($res);
+
+ if (is_null($resultPageSet)) {
+ $this->getResult()->setIndexedTagName($data, 'item');
+ $this->getResult()->addValue('query', $this->getModuleName(), $data);
+ }
+ elseif ($allrev) {
+ $resultPageSet->populateFromRevisionIDs($data);
+ } else {
+ $resultPageSet->populateFromPageIDs($data);
+ }
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'allrev' => false,
+ 'start' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'end' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'namespace' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => 'namespace'
+ ),
+ 'dir' => array (
+ ApiBase :: PARAM_DFLT => 'older',
+ ApiBase :: PARAM_TYPE => array (
+ 'newer',
+ 'older'
+ )
+ ),
+ 'limit' => array (
+ ApiBase :: PARAM_DFLT => 10,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 1,
+ ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1,
+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ ),
+ 'prop' => array (
+ APIBase :: PARAM_ISMULTI => true,
+ APIBase :: PARAM_TYPE => array (
+ 'user',
+ 'comment',
+ 'timestamp',
+ 'patrol'
+ )
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'allrev' => 'Include multiple revisions of the same page within given timeframe.',
+ 'start' => 'The timestamp to start enumerating from.',
+ 'end' => 'The timestamp to end enumerating.',
+ 'namespace' => 'Filter changes to only the given namespace(s).',
+ 'dir' => 'In which direction to enumerate pages.',
+ 'limit' => 'How many total pages to return per request.',
+ 'prop' => 'Which additional items to get (non-generator mode only).'
+ );
+ }
+
+ protected function getDescription() {
+ return '';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=query&list=watchlist',
+ 'api.php?action=query&list=watchlist&wlallrev',
+ 'api.php?action=query&generator=watchlist&prop=info',
+ 'api.php?action=query&generator=watchlist&gwlallrev&prop=revisions&rvprop=timestamp|user'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryWatchlist.php 17987 2006-11-29 05:45:03Z nickj $';
+ }
+}
+?>
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
index 67fbf41e..c9bfcfb9 100644
--- a/includes/api/ApiResult.php
+++ b/includes/api/ApiResult.php
@@ -31,21 +31,34 @@ if (!defined('MEDIAWIKI')) {
class ApiResult extends ApiBase {
- private $mData;
+ private $mData, $mIsRawMode;
/**
* Constructor
*/
public function __construct($main) {
parent :: __construct($main, 'result');
- $this->Reset();
+ $this->mIsRawMode = false;
+ $this->reset();
}
- public function Reset() {
+ public function reset() {
$this->mData = array ();
}
+
+ /**
+ * Call this function when special elements such as '_element'
+ * are needed by the formatter, for example in XML printing.
+ */
+ public function setRawMode() {
+ $this->mIsRawMode = true;
+ }
+
+ public function getIsRawMode() {
+ return $this->mIsRawMode;
+ }
- function & getData() {
+ public function & getData() {
return $this->mData;
}
@@ -73,11 +86,19 @@ class ApiResult extends ApiBase {
/**
* Adds the content element to the array.
* Use this function instead of hardcoding the '*' element.
+ * @param string $subElemName when present, content element is created as a sub item of the arr.
+ * Use this parameter to create elements in format <elem>text</elem> without attributes
*/
- public static function setContent(& $arr, $value) {
+ public static function setContent(& $arr, $value, $subElemName = null) {
if (is_array($value))
ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
- ApiResult :: setElement($arr, '*', $value);
+ if (is_null($subElemName)) {
+ ApiResult :: setElement($arr, '*', $value);
+ } else {
+ if (!isset ($arr[$subElemName]))
+ $arr[$subElemName] = array ();
+ ApiResult :: setElement($arr[$subElemName], '*', $value);
+ }
}
// public static function makeContentElement($tag, $value) {
@@ -89,10 +110,13 @@ class ApiResult extends ApiBase {
* 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
+ public function setIndexedTagName(& $arr, $tag) {
+ // In raw mode, add the '_element', otherwise just ignore
+ if (!$this->getIsRawMode())
+ return;
if ($arr === null || $tag === null || !is_array($arr) || is_array($tag))
ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
+ // Do not use setElement() as it is ok to call this more than once
$arr['_element'] = $tag;
}
@@ -105,7 +129,7 @@ class ApiResult extends ApiBase {
$data = & $this->getData();
- if (isset ($path)) {
+ if (!is_null($path)) {
if (is_array($path)) {
foreach ($path as $p) {
if (!isset ($data[$p]))
@@ -122,32 +146,12 @@ class ApiResult extends ApiBase {
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 $';
+ return __CLASS__ . ': $Id: ApiResult.php 17076 2006-10-18 05:35:24Z yurik $';
}
}
?> \ No newline at end of file
diff --git a/includes/cbt/CBTCompiler.php b/includes/cbt/CBTCompiler.php
index 4ef8ee4a..59088bed 100644
--- a/includes/cbt/CBTCompiler.php
+++ b/includes/cbt/CBTCompiler.php
@@ -75,7 +75,6 @@ class CBTCompiler {
* Returns true on success, error message on failure
*/
function compile() {
- $fname = 'CBTProcessor::compile';
$this->mLastError = false;
$this->mOps = array();
@@ -222,7 +221,6 @@ class CBTCompiler {
if ( $char == '{' ) {
// Switch to text mode
++$p;
- $tokenStart = $p;
$this->doOpenText( $p, $end );
++$argCount;
} elseif ( $char == '}' ) {
@@ -292,7 +290,7 @@ class CBTCompiler {
wfProfileIn( $fname );
$stack = array();
- foreach( $this->mOps as $index => $op ) {
+ foreach( $this->mOps as $op ) {
switch( $op->opcode ) {
case CBT_PUSH:
$stack[] = $this->phpQuote( $op->arg1 );
diff --git a/includes/memcached-client.php b/includes/memcached-client.php
index b1ba778a..2c5cc6be 100644
--- a/includes/memcached-client.php
+++ b/includes/memcached-client.php
@@ -451,7 +451,8 @@ class memcached
return false;
$this->stats['get_multi']++;
-
+ $sock_keys = array();
+
foreach ($keys as $key)
{
$sock = $this->get_sock($key);
@@ -697,6 +698,7 @@ class memcached
list ($ip, $port) = explode(":", $host);
$sock = false;
$timeout = $this->_connect_timeout;
+ $errno = $errstr = null;
for ($i = 0; !$sock && $i < $this->_connect_attempts; $i++) {
if ($i > 0) {
# Sleep until the timeout, in case it failed fast
@@ -740,7 +742,7 @@ class memcached
function _dead_sock ($sock)
{
$host = array_search($sock, $this->_cache_sock);
- @list ($ip, $port) = explode(":", $host);
+ @list ($ip, /* $port */) = explode(":", $host);
$this->_host_dead[$ip] = time() + 30 + intval(rand(0, 10));
$this->_host_dead[$host] = $this->_host_dead[$ip];
unset($this->_cache_sock[$host]);
@@ -849,6 +851,7 @@ class memcached
stream_set_timeout($sock, 1, 0);
$line = fgets($sock);
+ $match = array();
if (!preg_match('/^(\d+)/', $line, $match))
return null;
return $match[1];
@@ -1001,8 +1004,9 @@ class memcached
if (isset($this->_cache_sock[$host]))
return $this->_cache_sock[$host];
+ $sock = null;
$now = time();
- list ($ip, $port) = explode (":", $host);
+ list ($ip, /* $port */) = explode (":", $host);
if (isset($this->_host_dead[$host]) && $this->_host_dead[$host] > $now ||
isset($this->_host_dead[$ip]) && $this->_host_dead[$ip] > $now)
return null;
diff --git a/includes/normal/RandomTest.php b/includes/normal/RandomTest.php
index e2601366..b86ab7c3 100644
--- a/includes/normal/RandomTest.php
+++ b/includes/normal/RandomTest.php
@@ -68,6 +68,7 @@ function showDiffs( $a, $b ) {
$diffs = new Diff( $ota, $nta );
$formatter = new TableDiffFormatter();
$funky = $formatter->format( $diffs );
+ $matches = array();
preg_match_all( '/<span class="diffchange">(.*?)<\/span>/', $funky, $matches );
foreach( $matches[1] as $bit ) {
$hex = bin2hex( $bit );
diff --git a/includes/normal/Utf8Test.php b/includes/normal/Utf8Test.php
index 71069598..34ab69c8 100644
--- a/includes/normal/Utf8Test.php
+++ b/includes/normal/Utf8Test.php
@@ -45,6 +45,7 @@ if( !$in ) {
$columns = 0;
while( false !== ( $line = fgets( $in ) ) ) {
+ $matches = array();
if( preg_match( '/^(Here come the tests:\s*)\|$/', $line, $matches ) ) {
$columns = strpos( $line, '|' );
break;
@@ -86,6 +87,7 @@ $failed = 0;
$success = 0;
$total = 0;
while( false !== ( $line = fgets( $in ) ) ) {
+ $matches = array();
if( preg_match( '/^(\d+)\s+(.*?)\s*\|/', $line, $matches ) ) {
$section = $matches[1];
print $line;
diff --git a/includes/normal/UtfNormal.php b/includes/normal/UtfNormal.php
index af3809d5..d8eac7b8 100644
--- a/includes/normal/UtfNormal.php
+++ b/includes/normal/UtfNormal.php
@@ -124,8 +124,9 @@ class UtfNormal {
*
* @param string $string a UTF-8 string
* @return string a clean, shiny, normalized UTF-8 string
+ * @static
*/
- function cleanUp( $string ) {
+ static function cleanUp( $string ) {
if( NORMALIZE_ICU ) {
# We exclude a few chars that ICU would not.
$string = preg_replace(
@@ -153,8 +154,9 @@ class UtfNormal {
*
* @param string $string a valid UTF-8 string. Input is not validated.
* @return string a UTF-8 string in normal form C
+ * @static
*/
- function toNFC( $string ) {
+ static function toNFC( $string ) {
if( NORMALIZE_ICU )
return utf8_normalize( $string, UNORM_NFC );
elseif( UtfNormal::quickIsNFC( $string ) )
@@ -169,8 +171,9 @@ class UtfNormal {
*
* @param string $string a valid UTF-8 string. Input is not validated.
* @return string a UTF-8 string in normal form D
+ * @static
*/
- function toNFD( $string ) {
+ static function toNFD( $string ) {
if( NORMALIZE_ICU )
return utf8_normalize( $string, UNORM_NFD );
elseif( preg_match( '/[\x80-\xff]/', $string ) )
@@ -186,8 +189,9 @@ class UtfNormal {
*
* @param string $string a valid UTF-8 string. Input is not validated.
* @return string a UTF-8 string in normal form KC
+ * @static
*/
- function toNFKC( $string ) {
+ static function toNFKC( $string ) {
if( NORMALIZE_ICU )
return utf8_normalize( $string, UNORM_NFKC );
elseif( preg_match( '/[\x80-\xff]/', $string ) )
@@ -203,8 +207,9 @@ class UtfNormal {
*
* @param string $string a valid UTF-8 string. Input is not validated.
* @return string a UTF-8 string in normal form KD
+ * @static
*/
- function toNFKD( $string ) {
+ static function toNFKD( $string ) {
if( NORMALIZE_ICU )
return utf8_normalize( $string, UNORM_NFKD );
elseif( preg_match( '/[\x80-\xff]/', $string ) )
@@ -216,10 +221,10 @@ class UtfNormal {
/**
* Load the basic composition data if necessary
* @private
+ * @static
*/
- function loadData() {
- # fixme : are $utfCanonicalComp, $utfCanonicalDecomp really used?
- global $utfCombiningClass, $utfCanonicalComp, $utfCanonicalDecomp;
+ static function loadData() {
+ global $utfCombiningClass;
if( !isset( $utfCombiningClass ) ) {
require_once( 'UtfNormalData.inc' );
}
@@ -230,8 +235,9 @@ class UtfNormal {
* Returns false if not or uncertain.
* @param string $string a valid UTF-8 string. Input is not validated.
* @return bool
+ * @static
*/
- function quickIsNFC( $string ) {
+ static function quickIsNFC( $string ) {
# ASCII is always valid NFC!
# If it's pure ASCII, let it through.
if( !preg_match( '/[\x80-\xff]/', $string ) ) return true;
@@ -270,8 +276,9 @@ class UtfNormal {
* Returns true if the string is _definitely_ in NFC.
* Returns false if not or uncertain.
* @param string $string a UTF-8 string, altered on output to be valid UTF-8 safe for XML.
+ * @static
*/
- function quickIsNFCVerify( &$string ) {
+ static function quickIsNFCVerify( &$string ) {
# Screen out some characters that eg won't be allowed in XML
$string = preg_replace( '/[\x00-\x08\x0b\x0c\x0e-\x1f]/', UTF8_REPLACEMENT, $string );
@@ -321,6 +328,7 @@ class UtfNormal {
# large ASCII parts can be handled much more quickly.
# Don't chop up Unicode areas for punctuation, though,
# that wastes energy.
+ $matches = array();
preg_match_all(
'/([\x00-\x7f]+|[\x80-\xff][\x00-\x40\x5b-\x5f\x7b-\xff]*)/',
$string, $matches );
@@ -488,8 +496,9 @@ class UtfNormal {
* @param string $string
* @return string
* @private
+ * @static
*/
- function NFC( $string ) {
+ static function NFC( $string ) {
return UtfNormal::fastCompose( UtfNormal::NFD( $string ) );
}
@@ -497,8 +506,9 @@ class UtfNormal {
* @param string $string
* @return string
* @private
+ * @static
*/
- function NFD( $string ) {
+ static function NFD( $string ) {
UtfNormal::loadData();
global $utfCanonicalDecomp;
return UtfNormal::fastCombiningSort(
@@ -509,8 +519,9 @@ class UtfNormal {
* @param string $string
* @return string
* @private
+ * @static
*/
- function NFKC( $string ) {
+ static function NFKC( $string ) {
return UtfNormal::fastCompose( UtfNormal::NFKD( $string ) );
}
@@ -518,8 +529,9 @@ class UtfNormal {
* @param string $string
* @return string
* @private
+ * @static
*/
- function NFKD( $string ) {
+ static function NFKD( $string ) {
global $utfCompatibilityDecomp;
if( !isset( $utfCompatibilityDecomp ) ) {
require_once( 'UtfNormalDataK.inc' );
@@ -537,8 +549,9 @@ class UtfNormal {
* @param string $string Valid UTF-8 string
* @param array $map hash of expanded decomposition map
* @return string a UTF-8 string decomposed, not yet normalized (needs sorting)
+ * @static
*/
- function fastDecompose( $string, &$map ) {
+ static function fastDecompose( $string, $map ) {
UtfNormal::loadData();
$len = strlen( $string );
$out = '';
@@ -597,8 +610,9 @@ class UtfNormal {
* @private
* @param string $string a valid, decomposed UTF-8 string. Input is not validated.
* @return string a UTF-8 string with combining characters sorted in canonical order
+ * @static
*/
- function fastCombiningSort( $string ) {
+ static function fastCombiningSort( $string ) {
UtfNormal::loadData();
global $utfCombiningClass;
$len = strlen( $string );
@@ -646,8 +660,9 @@ class UtfNormal {
* @private
* @param string $string a valid UTF-8 string in sorted normal form D or KD. Input is not validated.
* @return string a UTF-8 string with canonical precomposed characters used where possible
+ * @static
*/
- function fastCompose( $string ) {
+ static function fastCompose( $string ) {
UtfNormal::loadData();
global $utfCanonicalComp, $utfCombiningClass;
$len = strlen( $string );
@@ -778,8 +793,9 @@ class UtfNormal {
* interate through a string without really doing anything of substance.
* @param string $string
* @return string
+ * @static
*/
- function placebo( $string ) {
+ static function placebo( $string ) {
$len = strlen( $string );
$out = '';
for( $i = 0; $i < $len; $i++ ) {
@@ -789,4 +805,4 @@ class UtfNormal {
}
}
-?>
+?> \ No newline at end of file
diff --git a/includes/normal/UtfNormalGenerate.php b/includes/normal/UtfNormalGenerate.php
index 688a80f1..f0eb5330 100644
--- a/includes/normal/UtfNormalGenerate.php
+++ b/includes/normal/UtfNormalGenerate.php
@@ -43,6 +43,7 @@ if( !$in ) {
print "Initializing normalization quick check tables...\n";
$checkNFC = array();
while( false !== ($line = fgets( $in ) ) ) {
+ $matches = array();
if( preg_match( '/^([0-9A-F]+)(?:..([0-9A-F]+))?\s*;\s*(NFC_QC)\s*;\s*([MN])/', $line, $matches ) ) {
list( $junk, $first, $last, $prop, $value ) = $matches;
#print "$first $last $prop $value\n";
diff --git a/includes/normal/UtfNormalTest.php b/includes/normal/UtfNormalTest.php
index 6d95bf85..1181b633 100644
--- a/includes/normal/UtfNormalTest.php
+++ b/includes/normal/UtfNormalTest.php
@@ -73,6 +73,7 @@ $testedChars = array();
while( false !== ( $line = fgets( $in ) ) ) {
list( $data, $comment ) = explode( '#', $line );
if( $data === '' ) continue;
+ $matches = array();
if( preg_match( '/@Part([\d])/', $data, $matches ) ) {
if( $matches[1] > 0 ) {
$ok = reportResults( $total, $success, $failure ) && $ok;
@@ -236,7 +237,6 @@ function testInvariant( &$u, $char, $desc, $reportFailure = false ) {
$result = verbosify( $char, $u->toNFD( $char ), 1, 'NFD', $reportFailure ) && $result;
$result = verbosify( $char, $u->toNFKC( $char ), 1, 'NFKC', $reportFailure ) && $result;
$result = verbosify( $char, $u->toNFKD( $char ), 1, 'NFKD', $reportFailure ) && $result;
- $c = $char;
$result = verbosify( $char, $u->cleanUp( $char ), 1, 'cleanUp', $reportFailure ) && $result;
global $verbose;
if( $verbose && !$result && !$reportFailure ) {
diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php
index 83ef4920..953fbd47 100644
--- a/includes/templates/Userlogin.php
+++ b/includes/templates/Userlogin.php
@@ -78,7 +78,7 @@ class UserloginTemplate extends QuickTemplate {
<tr>
<td></td>
<td align='left' style="white-space:nowrap">
- <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"
+ <input type='submit' name="wpLoginattempt" id="wpLoginattempt" tabindex="5" value="<?php $this->msg('login') ?>" />&nbsp;<?php if( $this->data['useemail'] && $this->data['canreset']) { ?><input type='submit' name="wpMailmypassword" id="wpMailmypassword"
tabindex="6"
value="<?php $this->msg('mailmypassword') ?>" />
<?php } ?>