From ca32f08966f1b51fcb19460f0996bb0c4048e6fe Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 3 Dec 2011 13:29:22 +0100 Subject: Update to MediaWiki 1.18.0 * also update ArchLinux skin to chagnes in MonoBook * Use only css to hide our menu bar when printing --- includes/Article.php | 4023 +++++++++----------------------------------------- 1 file changed, 696 insertions(+), 3327 deletions(-) (limited to 'includes/Article.php') diff --git a/includes/Article.php b/includes/Article.php index 3e8cfd5e..a0cc6a95 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -5,7 +5,11 @@ */ /** - * Class representing a MediaWiki article and history. + * Class for viewing MediaWiki article and history. + * + * This maintains WikiPage functions for backwards compatibility. + * + * @TODO: move and rewrite code to an Action class * * See design.txt for an overview. * Note: edit user interface and cache support functions have been @@ -13,198 +17,125 @@ * * @internal documentation reviewed 15 Mar 2010 */ -class Article { +class Article extends Page { /**@{{ * @private */ - var $mComment = ''; // !< + + /** + * @var IContextSource + */ + protected $mContext; + + /** + * @var WikiPage + */ + protected $mPage; + var $mContent; // !< var $mContentLoaded = false; // !< - var $mCounter = -1; // !< Not loaded - var $mCurID = -1; // !< Not loaded - var $mDataLoaded = false; // !< - var $mForUpdate = false; // !< - var $mGoodAdjustment = 0; // !< - var $mIsRedirect = false; // !< - var $mLatest = false; // !< - var $mMinorEdit; // !< var $mOldId; // !< - var $mPreparedEdit = false; // !< Title object if set - var $mRedirectedFrom = null; // !< Title object if set - var $mRedirectTarget = null; // !< Title object if set + + /** + * @var Title + */ + var $mRedirectedFrom = null; + + /** + * @var mixed: boolean false or URL string + */ var $mRedirectUrl = false; // !< var $mRevIdFetched = 0; // !< - var $mRevision; // !< Revision object if set - var $mTimestamp = ''; // !< - var $mTitle; // !< Title object - var $mTotalAdjustment = 0; // !< - var $mTouched = '19700101000000'; // !< - var $mUser = -1; // !< Not loaded - var $mUserText = ''; // !< username from Revision if set - var $mParserOptions; // !< ParserOptions object - var $mParserOutput; // !< ParserCache object if set + + /** + * @var Revision + */ + var $mRevision = null; + + /** + * @var ParserOutput + */ + var $mParserOutput; + /**@}}*/ /** * Constructor and clear the article - * @param $title Reference to a Title object. + * @param $title Title Reference to a Title object. * @param $oldId Integer revision ID, null to fetch from request, zero for current */ public function __construct( Title $title, $oldId = null ) { - // FIXME: does the reference play any role here? - $this->mTitle =& $title; $this->mOldId = $oldId; + $this->mPage = $this->newPage( $title ); + } + + protected function newPage( Title $title ) { + return new WikiPage( $title ); } /** - * Constructor from an page id - * @param $id The article ID to load + * Constructor from a page id + * @param $id Int article ID to load */ public static function newFromID( $id ) { $t = Title::newFromID( $id ); - # FIXME: doesn't inherit right + # @todo FIXME: Doesn't inherit right return $t == null ? null : new self( $t ); # return $t == null ? null : new static( $t ); // PHP 5.3 } /** - * Tell the page view functions that this view was redirected - * from another page on the wiki. - * @param $from Title object. - */ - public function setRedirectedFrom( Title $from ) { - $this->mRedirectedFrom = $from; - } - - /** - * If this page is a redirect, get its target + * Create an Article object of the appropriate class for the given page. * - * The target will be fetched from the redirect table if possible. - * If this page doesn't have an entry there, call insertRedirect() - * @return mixed Title object, or null if this page is not a redirect + * @param $title Title + * @param $context IContextSource + * @return Article object */ - public function getRedirectTarget() { - if ( !$this->mTitle->isRedirect() ) { - return null; - } - - if ( $this->mRedirectTarget !== null ) { - return $this->mRedirectTarget; + public static function newFromTitle( $title, IContextSource $context ) { + if ( NS_MEDIA == $title->getNamespace() ) { + // FIXME: where should this go? + $title = Title::makeTitle( NS_FILE, $title->getDBkey() ); } - # Query the redirect table - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( 'redirect', - array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ), - array( 'rd_from' => $this->getID() ), - __METHOD__ - ); - - // rd_fragment and rd_interwiki were added later, populate them if empty - if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) { - return $this->mRedirectTarget = Title::makeTitle( - $row->rd_namespace, $row->rd_title, - $row->rd_fragment, $row->rd_interwiki ); + $page = null; + wfRunHooks( 'ArticleFromTitle', array( &$title, &$page ) ); + if ( !$page ) { + switch( $title->getNamespace() ) { + case NS_FILE: + $page = new ImagePage( $title ); + break; + case NS_CATEGORY: + $page = new CategoryPage( $title ); + break; + default: + $page = new Article( $title ); + } } + $page->setContext( $context ); - # This page doesn't have an entry in the redirect table - return $this->mRedirectTarget = $this->insertRedirect(); - } - - /** - * Insert an entry for this page into the redirect table. - * - * Don't call this function directly unless you know what you're doing. - * @return Title object or null if not a redirect - */ - public function insertRedirect() { - // recurse through to only get the final target - $retval = Title::newFromRedirectRecurse( $this->getContent() ); - if ( !$retval ) { - return null; - } - $this->insertRedirectEntry( $retval ); - return $retval; - } - - /** - * Insert or update the redirect table entry for this page to indicate - * it redirects to $rt . - * @param $rt Title redirect target - */ - public function insertRedirectEntry( $rt ) { - $dbw = wfGetDB( DB_MASTER ); - $dbw->replace( 'redirect', array( 'rd_from' ), - array( - 'rd_from' => $this->getID(), - 'rd_namespace' => $rt->getNamespace(), - 'rd_title' => $rt->getDBkey(), - 'rd_fragment' => $rt->getFragment(), - 'rd_interwiki' => $rt->getInterwiki(), - ), - __METHOD__ - ); + return $page; } /** - * Get the Title object or URL this page redirects to + * Create an Article object of the appropriate class for the given page. * - * @return mixed false, Title of in-wiki target, or string with URL + * @param $page WikiPage + * @param $context IContextSource + * @return Article object */ - public function followRedirect() { - return $this->getRedirectURL( $this->getRedirectTarget() ); + public static function newFromWikiPage( WikiPage $page, IContextSource $context ) { + $article = self::newFromTitle( $page->getTitle(), $context ); + $article->mPage = $page; // override to keep process cached vars + return $article; } /** - * Get the Title object this text redirects to - * - * @param $text string article content containing redirect info - * @return mixed false, Title of in-wiki target, or string with URL - * @deprecated - */ - public function followRedirectText( $text ) { - // recurse through to only get the final target - return $this->getRedirectURL( Title::newFromRedirectRecurse( $text ) ); - } - - /** - * Get the Title object or URL to use for a redirect. We use Title - * objects for same-wiki, non-special redirects and URLs for everything - * else. - * @param $rt Title Redirect target - * @return mixed false, Title object of local target, or string with URL + * Tell the page view functions that this view was redirected + * from another page on the wiki. + * @param $from Title object. */ - public function getRedirectURL( $rt ) { - if ( $rt ) { - if ( $rt->getInterwiki() != '' ) { - if ( $rt->isLocal() ) { - // Offsite wikis need an HTTP redirect. - // - // This can be hard to reverse and may produce loops, - // so they may be disabled in the site configuration. - $source = $this->mTitle->getFullURL( 'redirect=no' ); - return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) ); - } - } else { - if ( $rt->getNamespace() == NS_SPECIAL ) { - // 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->isSpecial( 'Userlogout' ) ) { - // rolleyes - } else { - return $rt->getFullURL(); - } - } - - return $rt; - } - } - - // No or invalid redirect - return false; + public function setRedirectedFrom( Title $from ) { + $this->mRedirectedFrom = $from; } /** @@ -212,31 +143,22 @@ class Article { * @return Title object of this page */ public function getTitle() { - return $this->mTitle; + return $this->mPage->getTitle(); } /** * Clear the object - * FIXME: shouldn't this be public? + * @todo FIXME: Shouldn't this be public? * @private */ public function clear() { - $this->mDataLoaded = false; $this->mContentLoaded = false; - $this->mCurID = $this->mUser = $this->mCounter = -1; # Not loaded $this->mRedirectedFrom = null; # Title object if set - $this->mRedirectTarget = null; # Title object if set - $this->mUserText = - $this->mTimestamp = $this->mComment = ''; - $this->mGoodAdjustment = $this->mTotalAdjustment = 0; - $this->mTouched = '19700101000000'; - $this->mForUpdate = false; - $this->mIsRedirect = false; $this->mRevIdFetched = 0; $this->mRedirectUrl = false; - $this->mLatest = false; - $this->mPreparedEdit = false; + + $this->mPage->clear(); } /** @@ -250,20 +172,18 @@ class Article { * @return Return the text of this revision */ public function getContent() { - global $wgUser, $wgContLang, $wgMessageCache; + global $wgUser; wfProfileIn( __METHOD__ ); - if ( $this->getID() === 0 ) { + if ( $this->mPage->getID() === 0 ) { # If this is a MediaWiki:x message, then load the messages # and return the message value for x. - if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { - # If this is a system message, get the default text. - list( $message, $lang ) = $wgMessageCache->figureMessage( $wgContLang->lcfirst( $this->mTitle->getText() ) ); - $text = wfMsgGetKey( $message, false, $lang, false ); - - if ( wfEmptyMsg( $message, $text ) ) + if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) { + $text = $this->getTitle()->getDefaultMessageText(); + if ( $text === false ) { $text = ''; + } } else { $text = wfMsgExt( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', 'parsemag' ); } @@ -278,71 +198,6 @@ class Article { } } - /** - * Get the text of the current revision. No side-effects... - * - * @return Return the text of the current revision - */ - public function getRawText() { - // Check process cache for current revision - if ( $this->mContentLoaded && $this->mOldId == 0 ) { - return $this->mContent; - } - - $rev = Revision::newFromTitle( $this->mTitle ); - $text = $rev ? $rev->getRawText() : false; - - return $text; - } - - /** - * This function returns the text of a section, specified by a number ($section). - * A section is text under a heading like == Heading == or \Heading\, or - * the first section before any such heading (section 0). - * - * If a section contains subsections, these are also returned. - * - * @param $text String: text to look in - * @param $section Integer: section number - * @return string text of the requested section - * @deprecated - */ - public function getSection( $text, $section ) { - global $wgParser; - return $wgParser->getSection( $text, $section ); - } - - /** - * Get the text that needs to be saved in order to undo all revisions - * between $undo and $undoafter. Revisions must belong to the same page, - * must exist and must not be deleted - * @param $undo Revision - * @param $undoafter Revision Must be an earlier revision than $undo - * @return mixed string on success, false on failure - */ - public function getUndoText( Revision $undo, Revision $undoafter = null ) { - $currentRev = Revision::newFromTitle( $this->mTitle ); - if ( !$currentRev ) { - return false; // no page - } - $undo_text = $undo->getText(); - $undoafter_text = $undoafter->getText(); - $cur_text = $currentRev->getText(); - - if ( $cur_text == $undo_text ) { - # No use doing a merge if it's just a straight revert. - return $undoafter_text; - } - - $undone_text = ''; - - if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) { - return false; - } - - return $undone_text; - } - /** * @return int The oldid of the article that is to be shown, 0 for the * current revision @@ -370,14 +225,14 @@ class Article { if ( isset( $oldid ) ) { $oldid = intval( $oldid ); if ( $wgRequest->getVal( 'direction' ) == 'next' ) { - $nextid = $this->mTitle->getNextRevisionID( $oldid ); + $nextid = $this->getTitle()->getNextRevisionID( $oldid ); if ( $nextid ) { $oldid = $nextid; } else { - $this->mRedirectUrl = $this->mTitle->getFullURL( 'redirect=no' ); + $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' ); } } elseif ( $wgRequest->getVal( 'direction' ) == 'prev' ) { - $previd = $this->mTitle->getPreviousRevisionID( $oldid ); + $previd = $this->getTitle()->getPreviousRevisionID( $oldid ); if ( $previd ) { $oldid = $previd; } @@ -401,101 +256,11 @@ class Article { wfProfileIn( __METHOD__ ); - $oldid = $this->getOldID(); - $this->mOldId = $oldid; - $this->fetchContent( $oldid ); + $this->fetchContent( $this->getOldID() ); wfProfileOut( __METHOD__ ); } - /** - * Fetch a page record with the given conditions - * @param $dbr Database object - * @param $conditions Array - * @return mixed Database result resource, or false on failure - */ - protected function pageData( $dbr, $conditions ) { - $fields = array( - 'page_id', - 'page_namespace', - 'page_title', - 'page_restrictions', - 'page_counter', - 'page_is_redirect', - 'page_is_new', - 'page_random', - 'page_touched', - 'page_latest', - 'page_len', - ); - - wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) ); - - $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__ ); - - wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) ); - - return $row; - } - - /** - * Fetch a page record matching the Title object's namespace and title - * using a sanitized title string - * - * @param $dbr Database object - * @param $title Title object - * @return mixed Database result resource, or false on failure - */ - public function pageDataFromTitle( $dbr, $title ) { - return $this->pageData( $dbr, array( - 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDBkey() ) ); - } - - /** - * Fetch a page record matching the requested ID - * - * @param $dbr Database - * @param $id Integer - */ - protected function pageDataFromId( $dbr, $id ) { - return $this->pageData( $dbr, array( 'page_id' => $id ) ); - } - - /** - * Set the general counter, title etc data loaded from - * some source. - * - * @param $data Database row object or "fromdb" - */ - public function loadPageData( $data = 'fromdb' ) { - if ( $data === 'fromdb' ) { - $dbr = wfGetDB( DB_MASTER ); - $data = $this->pageDataFromId( $dbr, $this->getId() ); - } - - $lc = LinkCache::singleton(); - - if ( $data ) { - $lc->addGoodLinkObj( $data->page_id, $this->mTitle, $data->page_len, $data->page_is_redirect, $data->page_latest ); - - $this->mTitle->mArticleID = intval( $data->page_id ); - - # Old-fashioned restrictions - $this->mTitle->loadRestrictions( $data->page_restrictions ); - - $this->mCounter = intval( $data->page_counter ); - $this->mTouched = wfTimestamp( TS_MW, $data->page_touched ); - $this->mIsRedirect = intval( $data->page_is_redirect ); - $this->mLatest = intval( $data->page_latest ); - } else { - $lc->addBadLinkObj( $this->mTitle ); - $this->mTitle->mArticleID = 0; - } - - $this->mDataLoaded = true; - } - /** * Get text of an article from database * Does *NOT* follow redirects. @@ -508,57 +273,44 @@ class Article { return $this->mContent; } - $dbr = wfGetDB( DB_MASTER ); - # Pre-fill content with error message so that if something # fails we'll have something telling us what we intended. - $t = $this->mTitle->getPrefixedText(); + $t = $this->getTitle()->getPrefixedText(); $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : ''; $this->mContent = wfMsgNoTrans( 'missing-article', $t, $d ) ; if ( $oldid ) { $revision = Revision::newFromId( $oldid ); - if ( $revision === null ) { + if ( !$revision ) { wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" ); return false; } - - $data = $this->pageDataFromId( $dbr, $revision->getPage() ); - - if ( !$data ) { - wfDebug( __METHOD__ . " failed to get page data linked to revision id $oldid\n" ); - return false; - } - - $this->mTitle = Title::makeTitle( $data->page_namespace, $data->page_title ); - $this->loadPageData( $data ); - } else { - if ( !$this->mDataLoaded ) { - $data = $this->pageDataFromTitle( $dbr, $this->mTitle ); - - if ( !$data ) { - wfDebug( __METHOD__ . " failed to find page data for title " . $this->mTitle->getPrefixedText() . "\n" ); + // Revision title doesn't match the page title given? + if ( $this->mPage->getID() != $revision->getPage() ) { + $function = array( get_class( $this->mPage ), 'newFromID' ); + $this->mPage = call_user_func( $function, $revision->getPage() ); + if ( !$this->mPage->getId() ) { + wfDebug( __METHOD__ . " failed to get page data linked to revision id $oldid\n" ); return false; } - - $this->loadPageData( $data ); } - $revision = Revision::newFromId( $this->mLatest ); - if ( $revision === null ) { - wfDebug( __METHOD__ . " failed to retrieve current page, rev_id {$this->mLatest}\n" ); + } else { + if ( !$this->mPage->getLatest() ) { + wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" ); + return false; + } + + $revision = $this->mPage->getRevision(); + if ( !$revision ) { + wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" ); return false; } } - // FIXME: Horrible, horrible! This content-loading interface just plain sucks. + // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks. // We should instead work with the Revision object when we need it... $this->mContent = $revision->getText( Revision::FOR_THIS_USER ); // Loads if user is allowed - $this->mUser = $revision->getUser(); - $this->mUserText = $revision->getUserText(); - $this->mComment = $revision->getComment(); - $this->mTimestamp = wfTimestamp( TS_MW, $revision->getTimestamp() ); - $this->mRevIdFetched = $revision->getId(); $this->mContentLoaded = true; $this->mRevision =& $revision; @@ -569,118 +321,11 @@ class Article { } /** - * Read/write accessor to select FOR UPDATE - * - * @param $x Mixed: FIXME - * @return mixed value of $x, or value stored in Article::mForUpdate - */ - public function forUpdate( $x = null ) { - return wfSetVar( $this->mForUpdate, $x ); - } - - /** - * Get options for all SELECT statements - * - * @param $options Array: an optional options array which'll be appended to - * the default - * @return Array: options - */ - protected function getSelectOptions( $options = '' ) { - if ( $this->mForUpdate ) { - if ( is_array( $options ) ) { - $options[] = 'FOR UPDATE'; - } else { - $options = 'FOR UPDATE'; - } - } - - return $options; - } - - /** - * @return int Page ID - */ - public function getID() { - return $this->mTitle->getArticleID(); - } - - /** - * @return bool Whether or not the page exists in the database - */ - public function exists() { - return $this->getId() > 0; - } - - /** - * Check if this page is something we're going to be showing - * some sort of sensible content for. If we return false, page - * views (plain action=view) will return an HTTP 404 response, - * so spiders and robots can know they're following a bad link. - * - * @return bool - */ - public function hasViewableContent() { - return $this->exists() || $this->mTitle->isAlwaysKnown(); - } - - /** - * @return int The view count for the page - */ - public function getCount() { - if ( -1 == $this->mCounter ) { - $id = $this->getID(); - - if ( $id == 0 ) { - $this->mCounter = 0; - } else { - $dbr = wfGetDB( DB_SLAVE ); - $this->mCounter = $dbr->selectField( 'page', - 'page_counter', - array( 'page_id' => $id ), - __METHOD__, - $this->getSelectOptions() - ); - } - } - - return $this->mCounter; - } - - /** - * Determine whether a page would be suitable for being counted as an - * article in the site_stats table based on the title & its content - * - * @param $text String: text to analyze - * @return bool - */ - public function isCountable( $text ) { - global $wgUseCommaCount; - - $token = $wgUseCommaCount ? ',' : '[['; - - return $this->mTitle->isContentPage() && !$this->isRedirect( $text ) && in_string( $token, $text ); - } - - /** - * Tests if the article text represents a redirect - * - * @param $text mixed string containing article contents, or boolean - * @return bool + * No-op + * @deprecated since 1.18 */ - public function isRedirect( $text = false ) { - if ( $text === false ) { - if ( $this->mDataLoaded ) { - return $this->mIsRedirect; - } - - // Apparently loadPageData was never called - $this->loadContent(); - $titleObj = Title::newFromRedirectRecurse( $this->fetchContent() ); - } else { - $titleObj = Title::newFromRedirect( $text ); - } - - return $titleObj !== null; + public function forUpdate() { + wfDeprecated( __METHOD__ ); } /** @@ -694,80 +339,7 @@ class Article { return true; } - return $this->exists() && isset( $this->mRevision ) && $this->mRevision->isCurrent(); - } - - /** - * Loads everything except the text - * This isn't necessary for all uses, so it's only done if needed. - */ - protected function loadLastEdit() { - if ( -1 != $this->mUser ) { - return; - } - - # New or non-existent articles have no user information - $id = $this->getID(); - if ( 0 == $id ) { - return; - } - - $this->mLastRevision = Revision::loadFromPageId( wfGetDB( DB_MASTER ), $id ); - if ( !is_null( $this->mLastRevision ) ) { - $this->mUser = $this->mLastRevision->getUser(); - $this->mUserText = $this->mLastRevision->getUserText(); - $this->mTimestamp = $this->mLastRevision->getTimestamp(); - $this->mComment = $this->mLastRevision->getComment(); - $this->mMinorEdit = $this->mLastRevision->isMinor(); - $this->mRevIdFetched = $this->mLastRevision->getId(); - } - } - - /** - * @return string GMT timestamp of last article revision - **/ - - public function getTimestamp() { - // Check if the field has been filled by ParserCache::get() - if ( !$this->mTimestamp ) { - $this->loadLastEdit(); - } - - return wfTimestamp( TS_MW, $this->mTimestamp ); - } - - /** - * @return int user ID for the user that made the last article revision - */ - public function getUser() { - $this->loadLastEdit(); - return $this->mUser; - } - - /** - * @return string username of the user that made the last article revision - */ - public function getUserText() { - $this->loadLastEdit(); - return $this->mUserText; - } - - /** - * @return string Comment stored for the last article revision - */ - public function getComment() { - $this->loadLastEdit(); - return $this->mComment; - } - - /** - * Returns true if last revision was marked as "minor edit" - * - * @return boolean Minor edit indicator for the last article revision. - */ - public function getMinorEdit() { - $this->loadLastEdit(); - return $this->mMinorEdit; + return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent(); } /** @@ -776,52 +348,11 @@ class Article { * @return int revision ID of last article revision */ public function getRevIdFetched() { - $this->loadLastEdit(); - return $this->mRevIdFetched; - } - - /** - * FIXME: this does what? - * @param $limit Integer: default 0. - * @param $offset Integer: default 0. - * @return UserArrayFromResult object with User objects of article contributors for requested range - */ - public function getContributors( $limit = 0, $offset = 0 ) { - # FIXME: this is expensive; cache this info somewhere. - - $dbr = wfGetDB( DB_SLAVE ); - $revTable = $dbr->tableName( 'revision' ); - $userTable = $dbr->tableName( 'user' ); - - $pageId = $this->getId(); - - $user = $this->getUser(); - - if ( $user ) { - $excludeCond = "AND rev_user != $user"; + if ( $this->mRevIdFetched ) { + return $this->mRevIdFetched; } else { - $userText = $dbr->addQuotes( $this->getUserText() ); - $excludeCond = "AND rev_user_text != $userText"; - } - - $deletedBit = $dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER ); // username hidden? - - $sql = "SELECT {$userTable}.*, rev_user_text as user_name, MAX(rev_timestamp) as timestamp - FROM $revTable LEFT JOIN $userTable ON rev_user = user_id - WHERE rev_page = $pageId - $excludeCond - AND $deletedBit = 0 - GROUP BY rev_user, rev_user_text - ORDER BY timestamp DESC"; - - if ( $limit > 0 ) { - $sql = $dbr->limitResult( $sql, $limit, $offset ); + return $this->mPage->getLatest(); } - - $sql .= ' ' . $this->getSelectOptions(); - $res = $dbr->query( $sql, __METHOD__ ); - - return new UserArrayFromResult( $res ); } /** @@ -836,67 +367,68 @@ class Article { # Get variables from query string $oldid = $this->getOldID(); - $parserCache = ParserCache::singleton(); - $parserOptions = $this->getParserOptions(); + # getOldID may want us to redirect somewhere else + if ( $this->mRedirectUrl ) { + $wgOut->redirect( $this->mRedirectUrl ); + wfDebug( __METHOD__ . ": redirecting due to oldid\n" ); + wfProfileOut( __METHOD__ ); + + return; + } + + $wgOut->setArticleFlag( true ); + # Set page title (may be overridden by DISPLAYTITLE) + $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() ); + + # If we got diff in the query, we want to see a diff page instead of the article. + if ( $wgRequest->getCheck( 'diff' ) ) { + wfDebug( __METHOD__ . ": showing diff page\n" ); + $this->showDiffPage(); + wfProfileOut( __METHOD__ ); + + return; + } + + # Allow frames by default + $wgOut->allowClickjacking(); + + $parserCache = ParserCache::singleton(); + + $parserOptions = $this->mPage->getParserOptions(); # Render printable version, use printable version cache if ( $wgOut->isPrintable() ) { $parserOptions->setIsPrintable( true ); $parserOptions->setEditSection( false ); - } else if ( $wgUseETag && !$this->mTitle->quickUserCan( 'edit' ) ) { + } elseif ( $wgUseETag && !$this->getTitle()->quickUserCan( 'edit' ) ) { $parserOptions->setEditSection( false ); } # Try client and file cache - if ( $oldid === 0 && $this->checkTouched() ) { + if ( $oldid === 0 && $this->mPage->checkTouched() ) { if ( $wgUseETag ) { $wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) ); } # Is it client cached? - if ( $wgOut->checkLastModified( $this->getTouched() ) ) { + if ( $wgOut->checkLastModified( $this->mPage->getTouched() ) ) { wfDebug( __METHOD__ . ": done 304\n" ); wfProfileOut( __METHOD__ ); return; # Try file cache - } else if ( $wgUseFileCache && $this->tryFileCache() ) { + } elseif ( $wgUseFileCache && $this->tryFileCache() ) { wfDebug( __METHOD__ . ": done file cache\n" ); # tell wgOut that output is taken care of $wgOut->disable(); - $this->viewUpdates(); + $this->mPage->viewUpdates(); wfProfileOut( __METHOD__ ); return; } } - # getOldID may want us to redirect somewhere else - if ( $this->mRedirectUrl ) { - $wgOut->redirect( $this->mRedirectUrl ); - wfDebug( __METHOD__ . ": redirecting due to oldid\n" ); - wfProfileOut( __METHOD__ ); - - return; - } - - $wgOut->setArticleFlag( true ); - # Set page title (may be overridden by DISPLAYTITLE) - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); - - # If we got diff in the query, we want to see a diff page instead of the article. - if ( $wgRequest->getCheck( 'diff' ) ) { - wfDebug( __METHOD__ . ": showing diff page\n" ); - $this->showDiffPage(); - wfProfileOut( __METHOD__ ); - - return; - } - - # Allow frames by default - $wgOut->allowClickjacking(); - - if ( !$wgUseETag && !$this->mTitle->quickUserCan( 'edit' ) ) { + if ( !$wgUseETag && !$this->getTitle()->quickUserCan( 'edit' ) ) { $parserOptions->setEditSection( false ); } @@ -931,14 +463,18 @@ class Article { $wgOut->addParserOutput( $this->mParserOutput ); # Ensure that UI elements requiring revision ID have # the correct version information. - $wgOut->setRevisionId( $this->mLatest ); + $wgOut->setRevisionId( $this->mPage->getLatest() ); $outputDone = true; + # Preload timestamp to avoid a DB hit + if ( isset( $this->mParserOutput->mTimestamp ) ) { + $this->mPage->setTimestamp( $this->mParserOutput->mTimestamp ); + } } } break; case 3: $text = $this->getContent(); - if ( $text === false || $this->getID() == 0 ) { + if ( $text === false || $this->mPage->getID() == 0 ) { wfDebug( __METHOD__ . ": showing missing article\n" ); $this->showMissingArticle(); wfProfileOut( __METHOD__ ); @@ -946,7 +482,7 @@ class Article { } # Another whitelist check in case oldid is altering the title - if ( !$this->mTitle->userCanRead() ) { + if ( !$this->getTitle()->userCanRead() ) { wfDebug( __METHOD__ . ": denied on secondary read check\n" ); $wgOut->loginToUse(); $wgOut->output(); @@ -966,14 +502,14 @@ class Article { } # If this "old" version is the current, then try the parser cache... - if ( $oldid === $this->getLatest() && $this->useParserCache( false ) ) { + if ( $oldid === $this->mPage->getLatest() && $this->useParserCache( false ) ) { $this->mParserOutput = $parserCache->get( $this, $parserOptions ); if ( $this->mParserOutput ) { - wfDebug( __METHOD__ . ": showing parser cache for current rev permalink\n" ); + wfDebug( __METHOD__ . ": showing parser cache for current rev permalink\n" ); $wgOut->addParserOutput( $this->mParserOutput ); - $wgOut->setRevisionId( $this->mLatest ); + $wgOut->setRevisionId( $this->mPage->getLatest() ); $outputDone = true; - break; + break; } } } @@ -983,7 +519,7 @@ class Article { $wgOut->setRevisionId( $this->getRevIdFetched() ); # Pages containing custom CSS or JavaScript get special treatment - if ( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) { + if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) { wfDebug( __METHOD__ . ": showing CSS/JS source\n" ); $this->showCssOrJsPage(); $outputDone = true; @@ -995,7 +531,7 @@ class Article { # Don't append the subtitle if this was an old revision $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) ); # Parse just to get categories, displaytitle, etc. - $this->mParserOutput = $wgParser->parse( $text, $this->mTitle, $parserOptions ); + $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions ); $wgOut->addParserOutputNoText( $this->mParserOutput ); $outputDone = true; } @@ -1007,7 +543,7 @@ class Article { $key = $parserCache->getKey( $this, $parserOptions ); $poolArticleView = new PoolWorkArticleView( $this, $key, $useParserCache, $parserOptions ); - + if ( !$poolArticleView->execute() ) { # Connection or timeout error wfProfileOut( __METHOD__ ); @@ -1035,10 +571,11 @@ class Article { # tents of 'pagetitle-view-mainpage' instead of the default (if # that's not empty). # This message always exists because it is in the i18n files - if ( $this->mTitle->equals( Title::newMainPage() ) - && ( $m = wfMsgForContent( 'pagetitle-view-mainpage' ) ) !== '' ) - { - $wgOut->setHTMLTitle( $m ); + if ( $this->getTitle()->equals( Title::newMainPage() ) ) { + $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage(); + if ( !$msg->isDisabled() ) { + $wgOut->setHTMLTitle( $msg->title( $this->getTitle() )->text() ); + } } # Now that we've filled $this->mParserOutput, we know whether @@ -1048,7 +585,7 @@ class Article { $wgOut->setFollowPolicy( $policy['follow'] ); $this->showViewFooter(); - $this->viewUpdates(); + $this->mPage->viewUpdates(); wfProfileOut( __METHOD__ ); } @@ -1066,16 +603,14 @@ class Article { $unhide = $wgRequest->getInt( 'unhide' ) == 1; $oldid = $this->getOldID(); - $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $unhide ); + $de = new DifferenceEngine( $this->getTitle(), $oldid, $diff, $rcid, $purge, $unhide ); // DifferenceEngine directly fetched the revision: $this->mRevIdFetched = $de->mNewid; $de->showDiffPage( $diffOnly ); - // Needed to get the page's current revision - $this->loadPageData(); - if ( $diff == 0 || $diff == $this->mLatest ) { + if ( $diff == 0 || $diff == $this->mPage->getLatest() ) { # Run view updates for current revision only - $this->viewUpdates(); + $this->mPage->viewUpdates(); } } @@ -1087,15 +622,19 @@ class Article { * page views. */ protected function showCssOrJsPage() { - global $wgOut; + global $wgOut, $wgLang; - $wgOut->wrapWikiMsg( "
\n$1\n
", 'clearyourcache' ); + $dir = $wgLang->getDir(); + $lang = $wgLang->getCode(); + + $wgOut->wrapWikiMsg( "
\n$1\n
", + 'clearyourcache' ); // Give hooks a chance to customise the output - if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) { + if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) { // Wrap the whole lot in a
 and don't parse
 			$m = array();
-			preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
+			preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m );
 			$wgOut->addHTML( "
\n" );
 			$wgOut->addHTML( htmlspecialchars( $this->mContent ) );
 			$wgOut->addHTML( "\n
\n" ); @@ -1112,13 +651,12 @@ class Article { global $wgOut, $wgArticleRobotPolicies, $wgNamespaceRobotPolicies; global $wgDefaultRobotPolicy, $wgRequest; - $ns = $this->mTitle->getNamespace(); + $ns = $this->getTitle()->getNamespace(); if ( $ns == NS_USER || $ns == NS_USER_TALK ) { # Don't index user and user talk pages for blocked users (bug 11443) - if ( !$this->mTitle->isSubpage() ) { - $block = new Block(); - if ( $block->load( $this->mTitle->getText() ) ) { + if ( !$this->getTitle()->isSubpage() ) { + if ( Block::newFromTarget( null, $this->getTitle()->getText() ) instanceof Block ) { return array( 'index' => 'noindex', 'follow' => 'nofollow' @@ -1127,7 +665,7 @@ class Article { } } - if ( $this->getID() === 0 || $this->getOldID() ) { + if ( $this->mPage->getID() === 0 || $this->getOldID() ) { # Non-articles (special pages etc), and old revisions return array( 'index' => 'noindex', @@ -1157,7 +695,7 @@ class Article { self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] ) ); } - if ( $this->mTitle->canUseNoindex() && is_object( $this->mParserOutput ) && $this->mParserOutput->getIndexPolicy() ) { + if ( $this->getTitle()->canUseNoindex() && is_object( $this->mParserOutput ) && $this->mParserOutput->getIndexPolicy() ) { # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates # a final sanity check that we have really got the parser output. $policy = array_merge( @@ -1166,11 +704,11 @@ class Article { ); } - if ( isset( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) ) { + if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) { # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__ $policy = array_merge( $policy, - self::formatRobotPolicy( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) + self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ); } @@ -1182,7 +720,7 @@ class Article { * merging of several policies using array_merge(). * @param $policy Mixed, returns empty array on null/false/'', transparent * to already-converted arrays, converts String. - * @return associative Array: 'index' => , 'follow' => + * @return Array: 'index' => , 'follow' => */ public static function formatRobotPolicy( $policy ) { if ( is_array( $policy ) ) { @@ -1214,16 +752,15 @@ class Article { * @return boolean */ public function showRedirectedFromHeader() { - global $wgOut, $wgUser, $wgRequest, $wgRedirectSources; + global $wgOut, $wgRequest, $wgRedirectSources; $rdfrom = $wgRequest->getVal( 'rdfrom' ); - $sk = $wgUser->getSkin(); if ( isset( $this->mRedirectedFrom ) ) { // This is an internally redirected page view. // We'll need a backlink to the source page for navigation. if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) { - $redir = $sk->link( + $redir = Linker::link( $this->mRedirectedFrom, null, array(), @@ -1235,14 +772,14 @@ class Article { $wgOut->setSubtitle( $s ); // Set the fragment if one was specified in the redirect - if ( strval( $this->mTitle->getFragment() ) != '' ) { - $fragment = Xml::escapeJsString( $this->mTitle->getFragmentForURL() ); + if ( strval( $this->getTitle()->getFragment() ) != '' ) { + $fragment = Xml::escapeJsString( $this->getTitle()->getFragmentForURL() ); $wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" ); } // Add a tag $wgOut->addLink( array( 'rel' => 'canonical', - 'href' => $this->mTitle->getLocalURL() ) + 'href' => $this->getTitle()->getLocalURL() ) ); return true; @@ -1251,7 +788,7 @@ class Article { // This is an externally redirected view, from some other wiki. // If it was reported from a trusted site, supply a backlink. if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) { - $redir = $sk->makeExternalLink( $rdfrom, $rdfrom ); + $redir = Linker::makeExternalLink( $rdfrom, $rdfrom ); $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir ); $wgOut->setSubtitle( $s ); @@ -1269,9 +806,8 @@ class Article { public function showNamespaceHeader() { global $wgOut; - if ( $this->mTitle->isTalkPage() ) { - $msg = wfMsgNoTrans( 'talkpageheader' ); - if ( $msg !== '-' && !wfEmptyMsg( 'talkpageheader', $msg ) ) { + if ( $this->getTitle()->isTalkPage() ) { + if ( !wfMessage( 'talkpageheader' )->isDisabled() ) { $wgOut->wrapWikiMsg( "
\n$1\n
", array( 'talkpageheader' ) ); } } @@ -1284,7 +820,7 @@ class Article { global $wgOut, $wgUseTrackbacks; # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page - if ( $this->mTitle->getNamespace() == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) { + if ( $this->getTitle()->getNamespace() == NS_USER_TALK && IP::isValid( $this->getTitle()->getText() ) ) { $wgOut->addWikiMsg( 'anontalkpagetext' ); } @@ -1296,6 +832,9 @@ class Article { if ( $wgUseTrackbacks ) { $this->addTrackbacks(); } + + wfRunHooks( 'ArticleViewFooter', array( $this ) ); + } /** @@ -1308,11 +847,10 @@ class Article { $rcid = $wgRequest->getVal( 'rcid' ); - if ( !$rcid || !$this->mTitle->quickUserCan( 'patrol' ) ) { + if ( !$rcid || !$this->getTitle()->quickUserCan( 'patrol' ) ) { return; } - $sk = $wgUser->getSkin(); $token = $wgUser->editToken( $rcid ); $wgOut->preventClickjacking(); @@ -1320,8 +858,8 @@ class Article { "