r91123 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r91122‎ | r91123 | r91124 >
Date:22:09, 29 June 2011
Author:aaron
Status:resolved (Comments)
Tags:
Comment:
* Split off WikiPage class from Article, WikiFilePage class from ImagePage, and WikiCategoryPage from CategoryPage.
* WikiPage functions/fields are "magically" part of Article when accessed for b/c. Magic is kind of ugly but there are too many callers to make breaking changes atm. Some functions are just wrappers for WikiPage ones (were magic won't work).
* Added newFromID() to each WikiPage subclass (works around pre-existing inheritance problem).
* Added Page class for convenient type hinting and changed hints from Article -> Page. This lets things use WikiPage objects without getting type errors.
* Updated FlaggedPage to extend WikiPage. Worked around getOldIdFromRequest().
* Added setTimestamp() to WikiPage and moved some timestamp setting code from ParserCache to Article.
* Removed ampersands from $dbw arguments.
* @TODO: dependency inject user objects for WikiPage

The idea is to migrate things to use WikiPage, as the run-of-the-mill "new Article()" call doesn't care about $oldid and $wgRequest. After that, Article, ImagePage, and CategoryPage can be rewritten as an Action class or something sane (a Viewer class of sorts).
Modified paths:
  • /trunk/extensions/FlaggedRevs/dataclasses/FRInclusionCache.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FRUserCounters.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FlaggedPage.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevs.class.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevs.hooks.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/presentation/FlaggedPageView.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/presentation/FlaggedRevsUI.hooks.php (modified) (history)
  • /trunk/extensions/FlaggedRevs/presentation/FlaggedRevsXML.php (modified) (history)
  • /trunk/phase3/includes/Action.php (modified) (history)
  • /trunk/phase3/includes/Article.php (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/CategoryPage.php (modified) (history)
  • /trunk/phase3/includes/ImagePage.php (modified) (history)
  • /trunk/phase3/includes/Metadata.php (modified) (history)
  • /trunk/phase3/includes/ProtectionForm.php (modified) (history)
  • /trunk/phase3/includes/RawPage.php (modified) (history)
  • /trunk/phase3/includes/Wiki.php (modified) (history)
  • /trunk/phase3/includes/WikiCategoryPage.php (added) (history)
  • /trunk/phase3/includes/WikiFilePage.php (added) (history)
  • /trunk/phase3/includes/WikiPage.php (added) (history)
  • /trunk/phase3/includes/actions/CreditsAction.php (modified) (history)
  • /trunk/phase3/includes/parser/ParserCache.php (modified) (history)
  • /trunk/phase3/tests/phpunit/includes/ArticleTest.php (added) (history)

Diff [purge]

Index: trunk/phase3/tests/phpunit/includes/ArticleTest.php
@@ -0,0 +1,34 @@
 2+<?php
 3+
 4+class ArticleTest extends MediaWikiTestCase {
 5+ function testBCMagic() {
 6+ $title = Title::makeTitle( NS_MAIN, 'somePage' );
 7+ $article = new Article( $title );
 8+
 9+ $this->assertEquals( -1, $article->mCounter, "Article __get magic" );
 10+
 11+ $article->mCounter = 2;
 12+ $this->assertEquals( 2, $article->mCounter, "Article __set magic" );
 13+
 14+ $this->assertEquals( 2, $article->getCount(), "Article __call magic" );
 15+
 16+ $this->assertEquals( WikiPage::selectFields(), Article::selectFields(),
 17+ "Article static functions" );
 18+ $this->assertEquals( null, Article::onArticleCreate( $title ),
 19+ "Article static functions" );
 20+ $this->assertEquals( null, Article::onArticleDelete( $title ),
 21+ "Article static functions" );
 22+ $this->assertEquals( null, ImagePage::onArticleEdit( $title ),
 23+ "Article static functions" );
 24+ $this->assertTrue( is_string( CategoryPage::getAutosummary( '', '', 0 ) ),
 25+ "Article static functions" );
 26+
 27+ $article->ext_someNewProperty = 12;
 28+ $this->assertEquals( 12, $article->ext_someNewProperty,
 29+ "Article get/set magic on new field" );
 30+
 31+ $article->ext_someNewProperty = -8;
 32+ $this->assertEquals( -8, $article->ext_someNewProperty,
 33+ "Article get/set magic on update to new field" );
 34+ }
 35+}
Property changes on: trunk/phase3/tests/phpunit/includes/ArticleTest.php
___________________________________________________________________
Added: svn:eol-style
136 + native
Index: trunk/phase3/includes/ProtectionForm.php
@@ -57,7 +57,7 @@
5858 /** Map of action to the expiry time of the existing protection */
5959 var $mExistingExpiry = array();
6060
61 - function __construct( Article $article ) {
 61+ function __construct( Page $article ) {
6262 global $wgUser;
6363 // Set instance variables.
6464 $this->mArticle = $article;
Index: trunk/phase3/includes/CategoryPage.php
@@ -1,6 +1,6 @@
22 <?php
33 /**
4 - * Special handling for category description pages.
 4+ * Class for viewing MediaWiki category description pages.
55 * Modelled after ImagePage.php.
66 *
77 * @file
@@ -17,6 +17,22 @@
1818 # Subclasses can change this to override the viewer class.
1919 protected $mCategoryViewerClass = 'CategoryViewer';
2020
 21+ protected function newPage( Title $title ) {
 22+ // Overload mPage with a category-specific page
 23+ return new WikiCategoryPage( $title );
 24+ }
 25+
 26+ /**
 27+ * Constructor from a page id
 28+ * @param $id Int article ID to load
 29+ */
 30+ public static function newFromID( $id ) {
 31+ $t = Title::newFromID( $id );
 32+ # @todo FIXME: Doesn't inherit right
 33+ return $t == null ? null : new self( $t );
 34+ # return $t == null ? null : new static( $t ); // PHP 5.3
 35+ }
 36+
2137 function view() {
2238 global $wgRequest, $wgUser;
2339
@@ -42,27 +58,6 @@
4359 }
4460 }
4561
46 - /**
47 - * Don't return a 404 for categories in use.
48 - * In use defined as: either the actual page exists
49 - * or the category currently has members.
50 - */
51 - function hasViewableContent() {
52 - if ( parent::hasViewableContent() ) {
53 - return true;
54 - } else {
55 - $cat = Category::newFromTitle( $this->mTitle );
56 - // If any of these are not 0, then has members
57 - if ( $cat->getPageCount()
58 - || $cat->getSubcatCount()
59 - || $cat->getFileCount()
60 - ) {
61 - return true;
62 - }
63 - }
64 - return false;
65 - }
66 -
6762 function openShowCategory() {
6863 # For overloading
6964 }
Index: trunk/phase3/includes/Article.php
@@ -5,15 +5,19 @@
66 */
77
88 /**
9 - * Class representing a MediaWiki article and history.
 9+ * Class for viewing MediaWiki article and history.
1010 *
 11+ * This maintains WikiPage functions for backwards compatibility.
 12+ *
 13+ * @TODO: move and rewrite code to an Action class
 14+ *
1115 * See design.txt for an overview.
1216 * Note: edit user interface and cache support functions have been
1317 * moved to separate EditPage and HTMLFileCache classes.
1418 *
1519 * @internal documentation reviewed 15 Mar 2010
1620 */
17 -class Article {
 21+class Article extends Page {
1822 /**@{{
1923 * @private
2024 */
@@ -23,14 +27,14 @@
2428 */
2529 protected $mContext;
2630
 31+ /**
 32+ * @var WikiPage
 33+ */
 34+ protected $mPage;
 35+
2736 var $mContent; // !<
2837 var $mContentLoaded = false; // !<
29 - var $mCounter = -1; // !< Not loaded
30 - var $mDataLoaded = false; // !<
31 - var $mIsRedirect = false; // !<
32 - var $mLatest = false; // !<
3338 var $mOldId; // !<
34 - var $mPreparedEdit = false;
3539
3640 /**
3741 * @var Title
@@ -38,11 +42,6 @@
3943 var $mRedirectedFrom = null;
4044
4145 /**
42 - * @var Title
43 - */
44 - var $mRedirectTarget = null;
45 -
46 - /**
4746 * @var mixed: boolean false or URL string
4847 */
4948 var $mRedirectUrl = false; // !<
@@ -51,23 +50,9 @@
5251 /**
5352 * @var Revision
5453 */
55 - var $mLastRevision = null;
56 -
57 - /**
58 - * @var Revision
59 - */
6054 var $mRevision = null;
6155
62 - var $mTimestamp = ''; // !<
63 - var $mTitle; // !< Title object
64 - var $mTouched = '19700101000000'; // !<
65 -
6656 /**
67 - * @var ParserOptions: ParserOptions object for $wgUser articles
68 - */
69 - var $mParserOptions;
70 -
71 - /**
7257 * @var ParserOutput
7358 */
7459 var $mParserOutput;
@@ -80,12 +65,26 @@
8166 * @param $oldId Integer revision ID, null to fetch from request, zero for current
8267 */
8368 public function __construct( Title $title, $oldId = null ) {
84 - // @todo FIXME: Does the reference play any role here?
85 - $this->mTitle =& $title;
8669 $this->mOldId = $oldId;
 70+ $this->mPage = $this->newPage( $title );
8771 }
8872
 73+ protected function newPage( Title $title ) {
 74+ return new WikiPage( $title );
 75+ }
 76+
8977 /**
 78+ * Constructor from a page id
 79+ * @param $id Int article ID to load
 80+ */
 81+ public static function newFromID( $id ) {
 82+ $t = Title::newFromID( $id );
 83+ # @todo FIXME: Doesn't inherit right
 84+ return $t == null ? null : new self( $t );
 85+ # return $t == null ? null : new static( $t ); // PHP 5.3
 86+ }
 87+
 88+ /**
9089 * Create an Article object of the appropriate class for the given page.
9190 *
9291 * @param $title Title
@@ -118,17 +117,6 @@
119118 }
120119
121120 /**
122 - * Constructor from an page id
123 - * @param $id Int article ID to load
124 - */
125 - public static function newFromID( $id ) {
126 - $t = Title::newFromID( $id );
127 - # @todo FIXME: Doesn't inherit right
128 - return $t == null ? null : new self( $t );
129 - # return $t == null ? null : new static( $t ); // PHP 5.3
130 - }
131 -
132 - /**
133121 * Tell the page view functions that this view was redirected
134122 * from another page on the wiki.
135123 * @param $from Title object.
@@ -138,85 +126,6 @@
139127 }
140128
141129 /**
142 - * If this page is a redirect, get its target
143 - *
144 - * The target will be fetched from the redirect table if possible.
145 - * If this page doesn't have an entry there, call insertRedirect()
146 - * @return Title|mixed object, or null if this page is not a redirect
147 - */
148 - public function getRedirectTarget() {
149 - if ( !$this->mTitle->isRedirect() ) {
150 - return null;
151 - }
152 -
153 - if ( $this->mRedirectTarget !== null ) {
154 - return $this->mRedirectTarget;
155 - }
156 -
157 - # Query the redirect table
158 - $dbr = wfGetDB( DB_SLAVE );
159 - $row = $dbr->selectRow( 'redirect',
160 - array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
161 - array( 'rd_from' => $this->getID() ),
162 - __METHOD__
163 - );
164 -
165 - // rd_fragment and rd_interwiki were added later, populate them if empty
166 - if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
167 - return $this->mRedirectTarget = Title::makeTitle(
168 - $row->rd_namespace, $row->rd_title,
169 - $row->rd_fragment, $row->rd_interwiki );
170 - }
171 -
172 - # This page doesn't have an entry in the redirect table
173 - return $this->mRedirectTarget = $this->insertRedirect();
174 - }
175 -
176 - /**
177 - * Insert an entry for this page into the redirect table.
178 - *
179 - * Don't call this function directly unless you know what you're doing.
180 - * @return Title object or null if not a redirect
181 - */
182 - public function insertRedirect() {
183 - // recurse through to only get the final target
184 - $retval = Title::newFromRedirectRecurse( $this->getRawText() );
185 - if ( !$retval ) {
186 - return null;
187 - }
188 - $this->insertRedirectEntry( $retval );
189 - return $retval;
190 - }
191 -
192 - /**
193 - * Insert or update the redirect table entry for this page to indicate
194 - * it redirects to $rt .
195 - * @param $rt Title redirect target
196 - */
197 - public function insertRedirectEntry( $rt ) {
198 - $dbw = wfGetDB( DB_MASTER );
199 - $dbw->replace( 'redirect', array( 'rd_from' ),
200 - array(
201 - 'rd_from' => $this->getID(),
202 - 'rd_namespace' => $rt->getNamespace(),
203 - 'rd_title' => $rt->getDBkey(),
204 - 'rd_fragment' => $rt->getFragment(),
205 - 'rd_interwiki' => $rt->getInterwiki(),
206 - ),
207 - __METHOD__
208 - );
209 - }
210 -
211 - /**
212 - * Get the Title object or URL this page redirects to
213 - *
214 - * @return mixed false, Title of in-wiki target, or string with URL
215 - */
216 - public function followRedirect() {
217 - return $this->getRedirectURL( $this->getRedirectTarget() );
218 - }
219 -
220 - /**
221130 * Get the Title object this text redirects to
222131 *
223132 * @param $text string article content containing redirect info
@@ -229,51 +138,11 @@
230139 }
231140
232141 /**
233 - * Get the Title object or URL to use for a redirect. We use Title
234 - * objects for same-wiki, non-special redirects and URLs for everything
235 - * else.
236 - * @param $rt Title Redirect target
237 - * @return mixed false, Title object of local target, or string with URL
238 - */
239 - public function getRedirectURL( $rt ) {
240 - if ( $rt ) {
241 - if ( $rt->getInterwiki() != '' ) {
242 - if ( $rt->isLocal() ) {
243 - // Offsite wikis need an HTTP redirect.
244 - //
245 - // This can be hard to reverse and may produce loops,
246 - // so they may be disabled in the site configuration.
247 - $source = $this->mTitle->getFullURL( 'redirect=no' );
248 - return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
249 - }
250 - } else {
251 - if ( $rt->getNamespace() == NS_SPECIAL ) {
252 - // Gotta handle redirects to special pages differently:
253 - // Fill the HTTP response "Location" header and ignore
254 - // the rest of the page we're on.
255 - //
256 - // This can be hard to reverse, so they may be disabled.
257 - if ( $rt->isSpecial( 'Userlogout' ) ) {
258 - // rolleyes
259 - } else {
260 - return $rt->getFullURL();
261 - }
262 - }
263 -
264 - return $rt;
265 - }
266 - }
267 -
268 - // No or invalid redirect
269 - return false;
270 - }
271 -
272 - /**
273142 * Get the title object of the article
274143 * @return Title object of this page
275144 */
276145 public function getTitle() {
277 - return $this->mTitle;
 146+ return $this->mPage->getTitle();
278147 }
279148
280149 /**
@@ -282,20 +151,13 @@
283152 * @private
284153 */
285154 public function clear() {
286 - $this->mDataLoaded = false;
287155 $this->mContentLoaded = false;
288156
289 - $this->mCounter = -1; # Not loaded
290157 $this->mRedirectedFrom = null; # Title object if set
291 - $this->mRedirectTarget = null; # Title object if set
292 - $this->mLastRevision = null; # Latest revision
293 - $this->mTimestamp = '';
294 - $this->mTouched = '19700101000000';
295 - $this->mIsRedirect = false;
296158 $this->mRevIdFetched = 0;
297159 $this->mRedirectUrl = false;
298 - $this->mLatest = false;
299 - $this->mPreparedEdit = false;
 160+
 161+ $this->mPage->clear();
300162 }
301163
302164 /**
@@ -313,11 +175,11 @@
314176
315177 wfProfileIn( __METHOD__ );
316178
317 - if ( $this->getID() === 0 ) {
 179+ if ( $this->mPage->getID() === 0 ) {
318180 # If this is a MediaWiki:x message, then load the messages
319181 # and return the message value for x.
320 - if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
321 - $text = $this->mTitle->getDefaultMessageText();
 182+ if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
 183+ $text = $this->getTitle()->getDefaultMessageText();
322184 if ( $text === false ) {
323185 $text = '';
324186 }
@@ -336,53 +198,6 @@
337199 }
338200
339201 /**
340 - * Get the text of the current revision. No side-effects...
341 - *
342 - * @return Return the text of the current revision
343 - */
344 - public function getRawText() {
345 - // Check process cache for current revision
346 - if ( $this->mContentLoaded && $this->mOldId == 0 ) {
347 - return $this->mContent;
348 - }
349 -
350 - $rev = Revision::newFromTitle( $this->mTitle );
351 - $text = $rev ? $rev->getRawText() : false;
352 -
353 - return $text;
354 - }
355 -
356 - /**
357 - * Get the text that needs to be saved in order to undo all revisions
358 - * between $undo and $undoafter. Revisions must belong to the same page,
359 - * must exist and must not be deleted
360 - * @param $undo Revision
361 - * @param $undoafter Revision Must be an earlier revision than $undo
362 - * @return mixed string on success, false on failure
363 - */
364 - public function getUndoText( Revision $undo, Revision $undoafter = null ) {
365 - $cur_text = $this->getRawText();
366 - if ( $cur_text === false ) {
367 - return false; // no page
368 - }
369 - $undo_text = $undo->getText();
370 - $undoafter_text = $undoafter->getText();
371 -
372 - if ( $cur_text == $undo_text ) {
373 - # No use doing a merge if it's just a straight revert.
374 - return $undoafter_text;
375 - }
376 -
377 - $undone_text = '';
378 -
379 - if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
380 - return false;
381 - }
382 -
383 - return $undone_text;
384 - }
385 -
386 - /**
387202 * @return int The oldid of the article that is to be shown, 0 for the
388203 * current revision
389204 */
@@ -409,14 +224,14 @@
410225 if ( isset( $oldid ) ) {
411226 $oldid = intval( $oldid );
412227 if ( $wgRequest->getVal( 'direction' ) == 'next' ) {
413 - $nextid = $this->mTitle->getNextRevisionID( $oldid );
 228+ $nextid = $this->getTitle()->getNextRevisionID( $oldid );
414229 if ( $nextid ) {
415230 $oldid = $nextid;
416231 } else {
417 - $this->mRedirectUrl = $this->mTitle->getFullURL( 'redirect=no' );
 232+ $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
418233 }
419234 } elseif ( $wgRequest->getVal( 'direction' ) == 'prev' ) {
420 - $previd = $this->mTitle->getPreviousRevisionID( $oldid );
 235+ $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
421236 if ( $previd ) {
422237 $oldid = $previd;
423238 }
@@ -446,103 +261,6 @@
447262 }
448263
449264 /**
450 - * Return the list of revision fields that should be selected to create
451 - * a new page.
452 - */
453 - public static function selectFields() {
454 - return array(
455 - 'page_id',
456 - 'page_namespace',
457 - 'page_title',
458 - 'page_restrictions',
459 - 'page_counter',
460 - 'page_is_redirect',
461 - 'page_is_new',
462 - 'page_random',
463 - 'page_touched',
464 - 'page_latest',
465 - 'page_len',
466 - );
467 - }
468 -
469 - /**
470 - * Fetch a page record with the given conditions
471 - * @param $dbr DatabaseBase object
472 - * @param $conditions Array
473 - * @return mixed Database result resource, or false on failure
474 - */
475 - protected function pageData( $dbr, $conditions ) {
476 - $fields = self::selectFields();
477 -
478 - wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
479 -
480 - $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__ );
481 -
482 - wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
483 -
484 - return $row;
485 - }
486 -
487 - /**
488 - * Fetch a page record matching the Title object's namespace and title
489 - * using a sanitized title string
490 - *
491 - * @param $dbr DatabaseBase object
492 - * @param $title Title object
493 - * @return mixed Database result resource, or false on failure
494 - */
495 - protected function pageDataFromTitle( $dbr, $title ) {
496 - return $this->pageData( $dbr, array(
497 - 'page_namespace' => $title->getNamespace(),
498 - 'page_title' => $title->getDBkey() ) );
499 - }
500 -
501 - /**
502 - * Fetch a page record matching the requested ID
503 - *
504 - * @param $dbr DatabaseBase
505 - * @param $id Integer
506 - * @return mixed Database result resource, or false on failure
507 - */
508 - protected function pageDataFromId( $dbr, $id ) {
509 - return $this->pageData( $dbr, array( 'page_id' => $id ) );
510 - }
511 -
512 - /**
513 - * Set the general counter, title etc data loaded from
514 - * some source.
515 - *
516 - * @param $data Object|String $res->fetchObject() object or the string "fromdb" to reload
517 - */
518 - public function loadPageData( $data = 'fromdb' ) {
519 - if ( $data === 'fromdb' ) {
520 - $dbr = wfGetDB( DB_SLAVE );
521 - $data = $this->pageDataFromTitle( $dbr, $this->mTitle );
522 - }
523 -
524 - $lc = LinkCache::singleton();
525 -
526 - if ( $data ) {
527 - $lc->addGoodLinkObj( $data->page_id, $this->mTitle, $data->page_len, $data->page_is_redirect, $data->page_latest );
528 -
529 - $this->mTitle->mArticleID = intval( $data->page_id );
530 -
531 - # Old-fashioned restrictions
532 - $this->mTitle->loadRestrictions( $data->page_restrictions );
533 -
534 - $this->mCounter = intval( $data->page_counter );
535 - $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
536 - $this->mIsRedirect = intval( $data->page_is_redirect );
537 - $this->mLatest = intval( $data->page_latest );
538 - } else {
539 - $lc->addBadLinkObj( $this->mTitle );
540 - $this->mTitle->mArticleID = 0;
541 - }
542 -
543 - $this->mDataLoaded = true;
544 - }
545 -
546 - /**
547265 * Get text of an article from database
548266 * Does *NOT* follow redirects.
549267 *
@@ -556,7 +274,7 @@
557275
558276 # Pre-fill content with error message so that if something
559277 # fails we'll have something telling us what we intended.
560 - $t = $this->mTitle->getPrefixedText();
 278+ $t = $this->getTitle()->getPrefixedText();
561279 $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
562280 $this->mContent = wfMsgNoTrans( 'missing-article', $t, $d ) ;
563281
@@ -567,30 +285,27 @@
568286 return false;
569287 }
570288
571 - if ( !$this->mDataLoaded || $this->getID() != $revision->getPage() ) {
572 - $data = $this->pageDataFromId( wfGetDB( DB_SLAVE ), $revision->getPage() );
 289+ if ( $this->mPage->getID() != $revision->getPage() ) {
 290+ $data = $this->mPage->pageDataFromId( wfGetDB( DB_SLAVE ), $revision->getPage() );
573291
574292 if ( !$data ) {
575293 wfDebug( __METHOD__ . " failed to get page data linked to revision id $oldid\n" );
576294 return false;
577295 }
578296
579 - $this->mTitle = Title::makeTitle( $data->page_namespace, $data->page_title );
580 - $this->loadPageData( $data );
 297+ $title = Title::makeTitle( $data->page_namespace, $data->page_title );
 298+ $this->mPage = new WikiPage( $title );
 299+ $this->mPage->loadPageData( $data );
581300 }
582301 } else {
583 - if ( !$this->mDataLoaded ) {
584 - $this->loadPageData();
585 - }
586 -
587 - if ( $this->mLatest === false ) {
588 - wfDebug( __METHOD__ . " failed to find page data for title " . $this->mTitle->getPrefixedText() . "\n" );
 302+ if ( $this->mPage->getLatest() === false ) {
 303+ wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" );
589304 return false;
590305 }
591306
592 - $revision = Revision::newFromId( $this->mLatest );
 307+ $revision = $this->mPage->getRevision();
593308 if ( $revision === null ) {
594 - wfDebug( __METHOD__ . " failed to retrieve current page, rev_id {$this->mLatest}\n" );
 309+ wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
595310 return false;
596311 }
597312 }
@@ -599,10 +314,6 @@
600315 // We should instead work with the Revision object when we need it...
601316 $this->mContent = $revision->getText( Revision::FOR_THIS_USER ); // Loads if user is allowed
602317
603 - if ( $revision->getId() == $this->mLatest ) {
604 - $this->setLastEdit( $revision );
605 - }
606 -
607318 $this->mRevIdFetched = $revision->getId();
608319 $this->mContentLoaded = true;
609320 $this->mRevision =& $revision;
@@ -621,115 +332,6 @@
622333 }
623334
624335 /**
625 - * @return int Page ID
626 - */
627 - public function getID() {
628 - return $this->mTitle->getArticleID();
629 - }
630 -
631 - /**
632 - * @return bool Whether or not the page exists in the database
633 - */
634 - public function exists() {
635 - return $this->getId() > 0;
636 - }
637 -
638 - /**
639 - * Check if this page is something we're going to be showing
640 - * some sort of sensible content for. If we return false, page
641 - * views (plain action=view) will return an HTTP 404 response,
642 - * so spiders and robots can know they're following a bad link.
643 - *
644 - * @return bool
645 - */
646 - public function hasViewableContent() {
647 - return $this->exists() || $this->mTitle->isAlwaysKnown();
648 - }
649 -
650 - /**
651 - * @return int The view count for the page
652 - */
653 - public function getCount() {
654 - if ( -1 == $this->mCounter ) {
655 - $id = $this->getID();
656 -
657 - if ( $id == 0 ) {
658 - $this->mCounter = 0;
659 - } else {
660 - $dbr = wfGetDB( DB_SLAVE );
661 - $this->mCounter = $dbr->selectField( 'page',
662 - 'page_counter',
663 - array( 'page_id' => $id ),
664 - __METHOD__
665 - );
666 - }
667 - }
668 -
669 - return $this->mCounter;
670 - }
671 -
672 - /**
673 - * Determine whether a page would be suitable for being counted as an
674 - * article in the site_stats table based on the title & its content
675 - *
676 - * @param $editInfo Object or false: object returned by prepareTextForEdit(),
677 - * if false, the current database state will be used
678 - * @return Boolean
679 - */
680 - public function isCountable( $editInfo = false ) {
681 - global $wgArticleCountMethod;
682 -
683 - if ( !$this->mTitle->isContentPage() ) {
684 - return false;
685 - }
686 -
687 - $text = $editInfo ? $editInfo->pst : false;
688 -
689 - if ( $this->isRedirect( $text ) ) {
690 - return false;
691 - }
692 -
693 - switch ( $wgArticleCountMethod ) {
694 - case 'any':
695 - return true;
696 - case 'comma':
697 - if ( $text === false ) {
698 - $text = $this->getRawText();
699 - }
700 - return strpos( $text, ',' ) !== false;
701 - case 'link':
702 - if ( $editInfo ) {
703 - // ParserOutput::getLinks() is a 2D array of page links, so
704 - // to be really correct we would need to recurse in the array
705 - // but the main array should only have items in it if there are
706 - // links.
707 - return (bool)count( $editInfo->output->getLinks() );
708 - } else {
709 - return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
710 - array( 'pl_from' => $this->getId() ), __METHOD__ );
711 - }
712 - }
713 - }
714 -
715 - /**
716 - * Tests if the article text represents a redirect
717 - *
718 - * @param $text mixed string containing article contents, or boolean
719 - * @return bool
720 - */
721 - public function isRedirect( $text = false ) {
722 - if ( $text === false ) {
723 - if ( !$this->mDataLoaded ) {
724 - $this->loadPageData();
725 - }
726 -
727 - return (bool)$this->mIsRedirect;
728 - } else {
729 - return Title::newFromRedirect( $text ) !== null;
730 - }
731 - }
732 -
733 - /**
734336 * Returns true if the currently-referenced revision is the current edit
735337 * to this page (and it exists).
736338 * @return bool
@@ -740,112 +342,10 @@
741343 return true;
742344 }
743345
744 - return $this->exists() && $this->mRevision && $this->mRevision->isCurrent();
 346+ return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
745347 }
746348
747349 /**
748 - * Loads everything except the text
749 - * This isn't necessary for all uses, so it's only done if needed.
750 - */
751 - protected function loadLastEdit() {
752 - if ( $this->mLastRevision !== null ) {
753 - return; // already loaded
754 - }
755 -
756 - # New or non-existent articles have no user information
757 - $id = $this->getID();
758 - if ( 0 == $id ) {
759 - return;
760 - }
761 -
762 - $revision = Revision::loadFromPageId( wfGetDB( DB_MASTER ), $id );
763 - if ( $revision ) {
764 - $this->setLastEdit( $revision );
765 - }
766 - }
767 -
768 - /**
769 - * Set the latest revision
770 - */
771 - protected function setLastEdit( Revision $revision ) {
772 - $this->mLastRevision = $revision;
773 - $this->mTimestamp = $revision->getTimestamp();
774 - }
775 -
776 - /**
777 - * @return string GMT timestamp of last article revision
778 - */
779 - public function getTimestamp() {
780 - // Check if the field has been filled by ParserCache::get()
781 - if ( !$this->mTimestamp ) {
782 - $this->loadLastEdit();
783 - }
784 - return wfTimestamp( TS_MW, $this->mTimestamp );
785 - }
786 -
787 - /**
788 - * @param $audience Integer: one of:
789 - * Revision::FOR_PUBLIC to be displayed to all users
790 - * Revision::FOR_THIS_USER to be displayed to $wgUser
791 - * Revision::RAW get the text regardless of permissions
792 - * @return int user ID for the user that made the last article revision
793 - */
794 - public function getUser( $audience = Revision::FOR_PUBLIC ) {
795 - $this->loadLastEdit();
796 - if ( $this->mLastRevision ) {
797 - return $this->mLastRevision->getUser( $audience );
798 - } else {
799 - return -1;
800 - }
801 - }
802 -
803 - /**
804 - * @param $audience Integer: one of:
805 - * Revision::FOR_PUBLIC to be displayed to all users
806 - * Revision::FOR_THIS_USER to be displayed to $wgUser
807 - * Revision::RAW get the text regardless of permissions
808 - * @return string username of the user that made the last article revision
809 - */
810 - public function getUserText( $audience = Revision::FOR_PUBLIC ) {
811 - $this->loadLastEdit();
812 - if ( $this->mLastRevision ) {
813 - return $this->mLastRevision->getUserText( $audience );
814 - } else {
815 - return '';
816 - }
817 - }
818 -
819 - /**
820 - * @param $audience Integer: one of:
821 - * Revision::FOR_PUBLIC to be displayed to all users
822 - * Revision::FOR_THIS_USER to be displayed to $wgUser
823 - * Revision::RAW get the text regardless of permissions
824 - * @return string Comment stored for the last article revision
825 - */
826 - public function getComment( $audience = Revision::FOR_PUBLIC ) {
827 - $this->loadLastEdit();
828 - if ( $this->mLastRevision ) {
829 - return $this->mLastRevision->getComment( $audience );
830 - } else {
831 - return '';
832 - }
833 - }
834 -
835 - /**
836 - * Returns true if last revision was marked as "minor edit"
837 - *
838 - * @return boolean Minor edit indicator for the last article revision.
839 - */
840 - public function getMinorEdit() {
841 - $this->loadLastEdit();
842 - if ( $this->mLastRevision ) {
843 - return $this->mLastRevision->isMinor();
844 - } else {
845 - return false;
846 - }
847 - }
848 -
849 - /**
850350 * Use this to fetch the rev ID used on page views
851351 *
852352 * @return int revision ID of last article revision
@@ -854,62 +354,11 @@
855355 if ( $this->mRevIdFetched ) {
856356 return $this->mRevIdFetched;
857357 } else {
858 - return $this->getLatest();
 358+ return $this->mPage->getLatest();
859359 }
860360 }
861361
862362 /**
863 - * Get a list of users who have edited this article, not including the user who made
864 - * the most recent revision, which you can get from $article->getUser() if you want it
865 - * @return UserArrayFromResult
866 - */
867 - public function getContributors() {
868 - # @todo FIXME: This is expensive; cache this info somewhere.
869 -
870 - $dbr = wfGetDB( DB_SLAVE );
871 -
872 - if ( $dbr->implicitGroupby() ) {
873 - $realNameField = 'user_real_name';
874 - } else {
875 - $realNameField = 'FIRST(user_real_name) AS user_real_name';
876 - }
877 -
878 - $tables = array( 'revision', 'user' );
879 -
880 - $fields = array(
881 - 'rev_user as user_id',
882 - 'rev_user_text AS user_name',
883 - $realNameField,
884 - 'MAX(rev_timestamp) AS timestamp',
885 - );
886 -
887 - $conds = array( 'rev_page' => $this->getId() );
888 -
889 - // The user who made the top revision gets credited as "this page was last edited by
890 - // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
891 - $user = $this->getUser();
892 - if ( $user ) {
893 - $conds[] = "rev_user != $user";
894 - } else {
895 - $conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
896 - }
897 -
898 - $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0"; // username hidden?
899 -
900 - $jconds = array(
901 - 'user' => array( 'LEFT JOIN', 'rev_user = user_id' ),
902 - );
903 -
904 - $options = array(
905 - 'GROUP BY' => array( 'rev_user', 'rev_user_text' ),
906 - 'ORDER BY' => 'timestamp DESC',
907 - );
908 -
909 - $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $jconds );
910 - return new UserArrayFromResult( $res );
911 - }
912 -
913 - /**
914363 * This is the default action of the index.php entry point: just view the
915364 * page of the given title.
916365 */
@@ -933,7 +382,7 @@
934383
935384 $wgOut->setArticleFlag( true );
936385 # Set page title (may be overridden by DISPLAYTITLE)
937 - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
 386+ $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
938387
939388 # If we got diff in the query, we want to see a diff page instead of the article.
940389 if ( $wgRequest->getCheck( 'diff' ) ) {
@@ -949,23 +398,23 @@
950399
951400 $parserCache = ParserCache::singleton();
952401
953 - $parserOptions = $this->getParserOptions();
 402+ $parserOptions = $this->mPage->getParserOptions();
954403 # Render printable version, use printable version cache
955404 if ( $wgOut->isPrintable() ) {
956405 $parserOptions->setIsPrintable( true );
957406 $parserOptions->setEditSection( false );
958 - } elseif ( $wgUseETag && !$this->mTitle->quickUserCan( 'edit' ) ) {
 407+ } elseif ( $wgUseETag && !$this->getTitle()->quickUserCan( 'edit' ) ) {
959408 $parserOptions->setEditSection( false );
960409 }
961410
962411 # Try client and file cache
963 - if ( $oldid === 0 && $this->checkTouched() ) {
 412+ if ( $oldid === 0 && $this->mPage->checkTouched() ) {
964413 if ( $wgUseETag ) {
965414 $wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) );
966415 }
967416
968417 # Is it client cached?
969 - if ( $wgOut->checkLastModified( $this->getTouched() ) ) {
 418+ if ( $wgOut->checkLastModified( $this->mPage->getTouched() ) ) {
970419 wfDebug( __METHOD__ . ": done 304\n" );
971420 wfProfileOut( __METHOD__ );
972421
@@ -975,14 +424,14 @@
976425 wfDebug( __METHOD__ . ": done file cache\n" );
977426 # tell wgOut that output is taken care of
978427 $wgOut->disable();
979 - $this->viewUpdates();
 428+ $this->mPage->viewUpdates();
980429 wfProfileOut( __METHOD__ );
981430
982431 return;
983432 }
984433 }
985434
986 - if ( !$wgUseETag && !$this->mTitle->quickUserCan( 'edit' ) ) {
 435+ if ( !$wgUseETag && !$this->getTitle()->quickUserCan( 'edit' ) ) {
987436 $parserOptions->setEditSection( false );
988437 }
989438
@@ -1017,14 +466,18 @@
1018467 $wgOut->addParserOutput( $this->mParserOutput );
1019468 # Ensure that UI elements requiring revision ID have
1020469 # the correct version information.
1021 - $wgOut->setRevisionId( $this->mLatest );
 470+ $wgOut->setRevisionId( $this->mPage->getLatest() );
1022471 $outputDone = true;
 472+ # Preload timestamp to avoid a DB hit
 473+ if ( isset( $this->mParserOutput->mTimestamp ) ) {
 474+ $this->mPage->setTimestamp( $this->mParserOutput->mTimestamp );
 475+ }
1023476 }
1024477 }
1025478 break;
1026479 case 3:
1027480 $text = $this->getContent();
1028 - if ( $text === false || $this->getID() == 0 ) {
 481+ if ( $text === false || $this->mPage->getID() == 0 ) {
1029482 wfDebug( __METHOD__ . ": showing missing article\n" );
1030483 $this->showMissingArticle();
1031484 wfProfileOut( __METHOD__ );
@@ -1032,7 +485,7 @@
1033486 }
1034487
1035488 # Another whitelist check in case oldid is altering the title
1036 - if ( !$this->mTitle->userCanRead() ) {
 489+ if ( !$this->getTitle()->userCanRead() ) {
1037490 wfDebug( __METHOD__ . ": denied on secondary read check\n" );
1038491 $wgOut->loginToUse();
1039492 $wgOut->output();
@@ -1052,12 +505,12 @@
1053506 }
1054507
1055508 # If this "old" version is the current, then try the parser cache...
1056 - if ( $oldid === $this->getLatest() && $this->useParserCache( false ) ) {
 509+ if ( $oldid === $this->mPage->getLatest() && $this->useParserCache( false ) ) {
1057510 $this->mParserOutput = $parserCache->get( $this, $parserOptions );
1058511 if ( $this->mParserOutput ) {
1059512 wfDebug( __METHOD__ . ": showing parser cache for current rev permalink\n" );
1060513 $wgOut->addParserOutput( $this->mParserOutput );
1061 - $wgOut->setRevisionId( $this->mLatest );
 514+ $wgOut->setRevisionId( $this->mPage->getLatest() );
1062515 $outputDone = true;
1063516 break;
1064517 }
@@ -1069,7 +522,7 @@
1070523 $wgOut->setRevisionId( $this->getRevIdFetched() );
1071524
1072525 # Pages containing custom CSS or JavaScript get special treatment
1073 - if ( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) {
 526+ if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
1074527 wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
1075528 $this->showCssOrJsPage();
1076529 $outputDone = true;
@@ -1081,7 +534,7 @@
1082535 # Don't append the subtitle if this was an old revision
1083536 $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) );
1084537 # Parse just to get categories, displaytitle, etc.
1085 - $this->mParserOutput = $wgParser->parse( $text, $this->mTitle, $parserOptions );
 538+ $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
1086539 $wgOut->addParserOutputNoText( $this->mParserOutput );
1087540 $outputDone = true;
1088541 }
@@ -1121,10 +574,10 @@
1122575 # tents of 'pagetitle-view-mainpage' instead of the default (if
1123576 # that's not empty).
1124577 # This message always exists because it is in the i18n files
1125 - if ( $this->mTitle->equals( Title::newMainPage() ) ) {
 578+ if ( $this->getTitle()->equals( Title::newMainPage() ) ) {
1126579 $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
1127580 if ( !$msg->isDisabled() ) {
1128 - $wgOut->setHTMLTitle( $msg->title( $this->mTitle )->text() );
 581+ $wgOut->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
1129582 }
1130583 }
1131584
@@ -1135,7 +588,7 @@
1136589 $wgOut->setFollowPolicy( $policy['follow'] );
1137590
1138591 $this->showViewFooter();
1139 - $this->viewUpdates();
 592+ $this->mPage->viewUpdates();
1140593 wfProfileOut( __METHOD__ );
1141594 }
1142595
@@ -1153,14 +606,14 @@
1154607 $unhide = $wgRequest->getInt( 'unhide' ) == 1;
1155608 $oldid = $this->getOldID();
1156609
1157 - $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $unhide );
 610+ $de = new DifferenceEngine( $this->getTitle(), $oldid, $diff, $rcid, $purge, $unhide );
1158611 // DifferenceEngine directly fetched the revision:
1159612 $this->mRevIdFetched = $de->mNewid;
1160613 $de->showDiffPage( $diffOnly );
1161614
1162 - if ( $diff == 0 || $diff == $this->getLatest() ) {
 615+ if ( $diff == 0 || $diff == $this->mPage->getLatest() ) {
1163616 # Run view updates for current revision only
1164 - $this->viewUpdates();
 617+ $this->mPage->viewUpdates();
1165618 }
1166619 }
1167620
@@ -1177,10 +630,10 @@
1178631 $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache'>\n$1\n</div>", 'clearyourcache' );
1179632
1180633 // Give hooks a chance to customise the output
1181 - if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) {
 634+ if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) {
1182635 // Wrap the whole lot in a <pre> and don't parse
1183636 $m = array();
1184 - preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
 637+ preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m );
1185638 $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
1186639 $wgOut->addHTML( htmlspecialchars( $this->mContent ) );
1187640 $wgOut->addHTML( "\n</pre>\n" );
@@ -1197,12 +650,12 @@
1198651 global $wgOut, $wgArticleRobotPolicies, $wgNamespaceRobotPolicies;
1199652 global $wgDefaultRobotPolicy, $wgRequest;
1200653
1201 - $ns = $this->mTitle->getNamespace();
 654+ $ns = $this->getTitle()->getNamespace();
1202655
1203656 if ( $ns == NS_USER || $ns == NS_USER_TALK ) {
1204657 # Don't index user and user talk pages for blocked users (bug 11443)
1205 - if ( !$this->mTitle->isSubpage() ) {
1206 - if ( Block::newFromTarget( null, $this->mTitle->getText() ) instanceof Block ) {
 658+ if ( !$this->getTitle()->isSubpage() ) {
 659+ if ( Block::newFromTarget( null, $this->getTitle()->getText() ) instanceof Block ) {
1207660 return array(
1208661 'index' => 'noindex',
1209662 'follow' => 'nofollow'
@@ -1211,7 +664,7 @@
1212665 }
1213666 }
1214667
1215 - if ( $this->getID() === 0 || $this->getOldID() ) {
 668+ if ( $this->mPage->getID() === 0 || $this->getOldID() ) {
1216669 # Non-articles (special pages etc), and old revisions
1217670 return array(
1218671 'index' => 'noindex',
@@ -1241,7 +694,7 @@
1242695 self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
1243696 );
1244697 }
1245 - if ( $this->mTitle->canUseNoindex() && is_object( $this->mParserOutput ) && $this->mParserOutput->getIndexPolicy() ) {
 698+ if ( $this->getTitle()->canUseNoindex() && is_object( $this->mParserOutput ) && $this->mParserOutput->getIndexPolicy() ) {
1246699 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1247700 # a final sanity check that we have really got the parser output.
1248701 $policy = array_merge(
@@ -1250,11 +703,11 @@
1251704 );
1252705 }
1253706
1254 - if ( isset( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) ) {
 707+ if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
1255708 # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
1256709 $policy = array_merge(
1257710 $policy,
1258 - self::formatRobotPolicy( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] )
 711+ self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
1259712 );
1260713 }
1261714
@@ -1318,14 +771,14 @@
1319772 $wgOut->setSubtitle( $s );
1320773
1321774 // Set the fragment if one was specified in the redirect
1322 - if ( strval( $this->mTitle->getFragment() ) != '' ) {
1323 - $fragment = Xml::escapeJsString( $this->mTitle->getFragmentForURL() );
 775+ if ( strval( $this->getTitle()->getFragment() ) != '' ) {
 776+ $fragment = Xml::escapeJsString( $this->getTitle()->getFragmentForURL() );
1324777 $wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" );
1325778 }
1326779
1327780 // Add a <link rel="canonical"> tag
1328781 $wgOut->addLink( array( 'rel' => 'canonical',
1329 - 'href' => $this->mTitle->getLocalURL() )
 782+ 'href' => $this->getTitle()->getLocalURL() )
1330783 );
1331784
1332785 return true;
@@ -1352,7 +805,7 @@
1353806 public function showNamespaceHeader() {
1354807 global $wgOut;
1355808
1356 - if ( $this->mTitle->isTalkPage() ) {
 809+ if ( $this->getTitle()->isTalkPage() ) {
1357810 if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
1358811 $wgOut->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1\n</div>", array( 'talkpageheader' ) );
1359812 }
@@ -1366,7 +819,7 @@
1367820 global $wgOut, $wgUseTrackbacks;
1368821
1369822 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1370 - if ( $this->mTitle->getNamespace() == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) {
 823+ if ( $this->getTitle()->getNamespace() == NS_USER_TALK && IP::isValid( $this->getTitle()->getText() ) ) {
1371824 $wgOut->addWikiMsg( 'anontalkpagetext' );
1372825 }
1373826
@@ -1393,7 +846,7 @@
1394847
1395848 $rcid = $wgRequest->getVal( 'rcid' );
1396849
1397 - if ( !$rcid || !$this->mTitle->quickUserCan( 'patrol' ) ) {
 850+ if ( !$rcid || !$this->getTitle()->quickUserCan( 'patrol' ) ) {
1398851 return;
1399852 }
1400853
@@ -1405,7 +858,7 @@
1406859 wfMsgHtml(
1407860 'markaspatrolledlink',
1408861 Linker::link(
1409 - $this->mTitle,
 862+ $this->getTitle(),
1410863 wfMsgHtml( 'markaspatrolledtext' ),
1411864 array(),
1412865 array(
@@ -1428,8 +881,8 @@
1429882 global $wgOut, $wgRequest, $wgUser;
1430883
1431884 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1432 - if ( $this->mTitle->getNamespace() == NS_USER || $this->mTitle->getNamespace() == NS_USER_TALK ) {
1433 - $parts = explode( '/', $this->mTitle->getText() );
 885+ if ( $this->getTitle()->getNamespace() == NS_USER || $this->getTitle()->getNamespace() == NS_USER_TALK ) {
 886+ $parts = explode( '/', $this->getTitle()->getText() );
1434887 $rootPart = $parts[0];
1435888 $user = User::newFromName( $rootPart, false /* allow IP users*/ );
1436889 $ip = User::isIP( $rootPart );
@@ -1458,7 +911,7 @@
1459912 wfRunHooks( 'ShowMissingArticle', array( $this ) );
1460913
1461914 # Show delete and move logs
1462 - LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle->getPrefixedText(), '',
 915+ LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->getTitle()->getPrefixedText(), '',
1463916 array( 'lim' => 10,
1464917 'conds' => array( "log_action != 'revision'" ),
1465918 'showIfEmpty' => false,
@@ -1469,14 +922,14 @@
1470923 $oldid = $this->getOldID();
1471924 if ( $oldid ) {
1472925 $text = wfMsgNoTrans( 'missing-article',
1473 - $this->mTitle->getPrefixedText(),
 926+ $this->getTitle()->getPrefixedText(),
1474927 wfMsgNoTrans( 'missingarticle-rev', $oldid ) );
1475 - } elseif ( $this->mTitle->getNamespace() === NS_MEDIAWIKI ) {
 928+ } elseif ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) {
1476929 // Use the default message text
1477 - $text = $this->mTitle->getDefaultMessageText();
 930+ $text = $this->getTitle()->getDefaultMessageText();
1478931 } else {
1479 - $createErrors = $this->mTitle->getUserPermissionsErrors( 'create', $wgUser );
1480 - $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
 932+ $createErrors = $this->getTitle()->getUserPermissionsErrors( 'create', $wgUser );
 933+ $editErrors = $this->getTitle()->getUserPermissionsErrors( 'edit', $wgUser );
1481934 $errors = array_merge( $createErrors, $editErrors );
1482935
1483936 if ( !count( $errors ) ) {
@@ -1487,7 +940,7 @@
1488941 }
1489942 $text = "<div class='noarticletext'>\n$text\n</div>";
1490943
1491 - if ( !$this->hasViewableContent() ) {
 944+ if ( !$this->mPage->hasViewableContent() ) {
1492945 // If there's no backing content, send a 404 Not Found
1493946 // for better machine handling of broken links.
1494947 $wgRequest->response()->header( "HTTP/1.1 404 Not Found" );
@@ -1520,7 +973,7 @@
1521974 } elseif ( $wgRequest->getInt( 'unhide' ) != 1 ) {
1522975 # Give explanation and add a link to view the revision...
1523976 $oldid = intval( $this->getOldID() );
1524 - $link = $this->mTitle->getFullUrl( "oldid={$oldid}&unhide=1" );
 977+ $link = $this->getTitle()->getFullUrl( "oldid={$oldid}&unhide=1" );
1525978 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1526979 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1527980 $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
@@ -1538,35 +991,19 @@
1539992 }
1540993
1541994 /**
1542 - * Should the parser cache be used?
1543 - *
1544 - * @return boolean
1545 - */
1546 - public function useParserCache( $oldid ) {
1547 - global $wgUser, $wgEnableParserCache;
1548 -
1549 - return $wgEnableParserCache
1550 - && $wgUser->getStubThreshold() == 0
1551 - && $this->exists()
1552 - && empty( $oldid )
1553 - && !$this->mTitle->isCssOrJsPage()
1554 - && !$this->mTitle->isCssJsSubpage();
1555 - }
1556 -
1557 - /**
1558995 * Execute the uncached parse for action=view
1559996 */
1560997 public function doViewParse() {
1561998 global $wgOut;
1562999
15631000 $oldid = $this->getOldID();
1564 - $parserOptions = $this->getParserOptions();
 1001+ $parserOptions = $this->mPage->getParserOptions();
15651002
15661003 # Render printable version, use printable version cache
15671004 $parserOptions->setIsPrintable( $wgOut->isPrintable() );
15681005
15691006 # Don't show section-edit links on old revisions... this way lies madness.
1570 - if ( !$this->isCurrent() || $wgOut->isPrintable() || !$this->mTitle->quickUserCan( 'edit' ) ) {
 1007+ if ( !$this->isCurrent() || $wgOut->isPrintable() || !$this->getTitle()->quickUserCan( 'edit' ) ) {
15711008 $parserOptions->setEditSection( false );
15721009 }
15731010
@@ -1587,7 +1024,7 @@
15881025 public function tryDirtyCache() {
15891026 global $wgOut;
15901027 $parserCache = ParserCache::singleton();
1591 - $options = $this->getParserOptions();
 1028+ $options = $this->mPage->getParserOptions();
15921029
15931030 if ( $wgOut->isPrintable() ) {
15941031 $options->setIsPrintable( true );
@@ -1670,7 +1107,7 @@
16711108 $dbr = wfGetDB( DB_SLAVE );
16721109 $tbs = $dbr->select( 'trackbacks',
16731110 array( 'tb_id', 'tb_title', 'tb_url', 'tb_ex', 'tb_name' ),
1674 - array( 'tb_page' => $this->getID() )
 1111+ array( 'tb_page' => $this->mPage->getID() )
16751112 );
16761113
16771114 if ( !$dbr->numRows( $tbs ) ) {
@@ -1684,7 +1121,7 @@
16851122 $rmvtxt = "";
16861123
16871124 if ( $this->getContext()->getUser()->isAllowed( 'trackback' ) ) {
1688 - $delurl = $this->mTitle->getFullURL( "action=deletetrackback&tbid=" .
 1125+ $delurl = $this->getTitle()->getFullURL( "action=deletetrackback&tbid=" .
16891126 $o->tb_id . "&token=" . urlencode( $this->getContext()->getUser()->editToken() ) );
16901127 $rmvtxt = wfMsg( 'trackbackremove', htmlspecialchars( $delurl ) );
16911128 }
@@ -1728,41 +1165,6 @@
17291166 }
17301167
17311168 /**
1732 - * Perform the actions of a page purging
1733 - */
1734 - public function doPurge() {
1735 - global $wgUseSquid;
1736 -
1737 - if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ){
1738 - return false;
1739 - }
1740 -
1741 - // Invalidate the cache
1742 - $this->mTitle->invalidateCache();
1743 - $this->clear();
1744 -
1745 - if ( $wgUseSquid ) {
1746 - // Commit the transaction before the purge is sent
1747 - $dbw = wfGetDB( DB_MASTER );
1748 - $dbw->commit();
1749 -
1750 - // Send purge
1751 - $update = SquidUpdate::newSimplePurge( $this->mTitle );
1752 - $update->doUpdate();
1753 - }
1754 -
1755 - if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
1756 - if ( $this->getID() == 0 ) {
1757 - $text = false;
1758 - } else {
1759 - $text = $this->getRawText();
1760 - }
1761 -
1762 - MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
1763 - }
1764 - }
1765 -
1766 - /**
17671169 * Mark this particular edit/page as patrolled
17681170 * @deprecated since 1.19
17691171 */
@@ -1789,7 +1191,7 @@
17901192 */
17911193 public function doWatch() {
17921194 global $wgUser;
1793 - return WatchAction::doWatch( $this->mTitle, $wgUser );
 1195+ return WatchAction::doWatch( $this->getTitle(), $wgUser );
17941196 }
17951197
17961198 /**
@@ -1808,7 +1210,7 @@
18091211 */
18101212 public function doUnwatch() {
18111213 global $wgUser;
1812 - return WatchAction::doUnwatch( $this->mTitle, $wgUser );
 1214+ return WatchAction::doUnwatch( $this->getTitle(), $wgUser );
18131215 }
18141216
18151217 /**
@@ -1835,524 +1237,6 @@
18361238 }
18371239
18381240 /**
1839 - * Insert a new empty page record for this article.
1840 - * This *must* be followed up by creating a revision
1841 - * and running $this->updateRevisionOn( ... );
1842 - * or else the record will be left in a funky state.
1843 - * Best if all done inside a transaction.
1844 - *
1845 - * @param $dbw DatabaseBase
1846 - * @return int The newly created page_id key, or false if the title already existed
1847 - * @private
1848 - */
1849 - public function insertOn( $dbw ) {
1850 - wfProfileIn( __METHOD__ );
1851 -
1852 - $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
1853 - $dbw->insert( 'page', array(
1854 - 'page_id' => $page_id,
1855 - 'page_namespace' => $this->mTitle->getNamespace(),
1856 - 'page_title' => $this->mTitle->getDBkey(),
1857 - 'page_counter' => 0,
1858 - 'page_restrictions' => '',
1859 - 'page_is_redirect' => 0, # Will set this shortly...
1860 - 'page_is_new' => 1,
1861 - 'page_random' => wfRandom(),
1862 - 'page_touched' => $dbw->timestamp(),
1863 - 'page_latest' => 0, # Fill this in shortly...
1864 - 'page_len' => 0, # Fill this in shortly...
1865 - ), __METHOD__, 'IGNORE' );
1866 -
1867 - $affected = $dbw->affectedRows();
1868 -
1869 - if ( $affected ) {
1870 - $newid = $dbw->insertId();
1871 - $this->mTitle->resetArticleID( $newid );
1872 - }
1873 - wfProfileOut( __METHOD__ );
1874 -
1875 - return $affected ? $newid : false;
1876 - }
1877 -
1878 - /**
1879 - * Update the page record to point to a newly saved revision.
1880 - *
1881 - * @param $dbw DatabaseBase: object
1882 - * @param $revision Revision: For ID number, and text used to set
1883 - length and redirect status fields
1884 - * @param $lastRevision Integer: if given, will not overwrite the page field
1885 - * when different from the currently set value.
1886 - * Giving 0 indicates the new page flag should be set
1887 - * on.
1888 - * @param $lastRevIsRedirect Boolean: if given, will optimize adding and
1889 - * removing rows in redirect table.
1890 - * @return bool true on success, false on failure
1891 - * @private
1892 - */
1893 - public function updateRevisionOn( &$dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
1894 - wfProfileIn( __METHOD__ );
1895 -
1896 - $text = $revision->getText();
1897 - $rt = Title::newFromRedirectRecurse( $text );
1898 -
1899 - $conditions = array( 'page_id' => $this->getId() );
1900 -
1901 - if ( !is_null( $lastRevision ) ) {
1902 - # An extra check against threads stepping on each other
1903 - $conditions['page_latest'] = $lastRevision;
1904 - }
1905 -
1906 - $dbw->update( 'page',
1907 - array( /* SET */
1908 - 'page_latest' => $revision->getId(),
1909 - 'page_touched' => $dbw->timestamp(),
1910 - 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
1911 - 'page_is_redirect' => $rt !== null ? 1 : 0,
1912 - 'page_len' => strlen( $text ),
1913 - ),
1914 - $conditions,
1915 - __METHOD__ );
1916 -
1917 - $result = $dbw->affectedRows() != 0;
1918 - if ( $result ) {
1919 - $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
1920 - }
1921 -
1922 - wfProfileOut( __METHOD__ );
1923 - return $result;
1924 - }
1925 -
1926 - /**
1927 - * Add row to the redirect table if this is a redirect, remove otherwise.
1928 - *
1929 - * @param $dbw DatabaseBase
1930 - * @param $redirectTitle Title object pointing to the redirect target,
1931 - * or NULL if this is not a redirect
1932 - * @param $lastRevIsRedirect If given, will optimize adding and
1933 - * removing rows in redirect table.
1934 - * @return bool true on success, false on failure
1935 - * @private
1936 - */
1937 - public function updateRedirectOn( &$dbw, $redirectTitle, $lastRevIsRedirect = null ) {
1938 - // Always update redirects (target link might have changed)
1939 - // Update/Insert if we don't know if the last revision was a redirect or not
1940 - // Delete if changing from redirect to non-redirect
1941 - $isRedirect = !is_null( $redirectTitle );
1942 -
1943 - if ( !$isRedirect && !is_null( $lastRevIsRedirect ) && $lastRevIsRedirect === $isRedirect ) {
1944 - return true;
1945 - }
1946 -
1947 - wfProfileIn( __METHOD__ );
1948 - if ( $isRedirect ) {
1949 - $this->insertRedirectEntry( $redirectTitle );
1950 - } else {
1951 - // This is not a redirect, remove row from redirect table
1952 - $where = array( 'rd_from' => $this->getId() );
1953 - $dbw->delete( 'redirect', $where, __METHOD__ );
1954 - }
1955 -
1956 - if ( $this->getTitle()->getNamespace() == NS_FILE ) {
1957 - RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
1958 - }
1959 - wfProfileOut( __METHOD__ );
1960 -
1961 - return ( $dbw->affectedRows() != 0 );
1962 - }
1963 -
1964 - /**
1965 - * If the given revision is newer than the currently set page_latest,
1966 - * update the page record. Otherwise, do nothing.
1967 - *
1968 - * @param $dbw Database object
1969 - * @param $revision Revision object
1970 - * @return mixed
1971 - */
1972 - public function updateIfNewerOn( &$dbw, $revision ) {
1973 - wfProfileIn( __METHOD__ );
1974 -
1975 - $row = $dbw->selectRow(
1976 - array( 'revision', 'page' ),
1977 - array( 'rev_id', 'rev_timestamp', 'page_is_redirect' ),
1978 - array(
1979 - 'page_id' => $this->getId(),
1980 - 'page_latest=rev_id' ),
1981 - __METHOD__ );
1982 -
1983 - if ( $row ) {
1984 - if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
1985 - wfProfileOut( __METHOD__ );
1986 - return false;
1987 - }
1988 - $prev = $row->rev_id;
1989 - $lastRevIsRedirect = (bool)$row->page_is_redirect;
1990 - } else {
1991 - # No or missing previous revision; mark the page as new
1992 - $prev = 0;
1993 - $lastRevIsRedirect = null;
1994 - }
1995 -
1996 - $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
1997 -
1998 - wfProfileOut( __METHOD__ );
1999 - return $ret;
2000 - }
2001 -
2002 - /**
2003 - * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...)
2004 - * @param $text String: new text of the section
2005 - * @param $summary String: new section's subject, only if $section is 'new'
2006 - * @param $edittime String: revision timestamp or null to use the current revision
2007 - * @return string Complete article text, or null if error
2008 - */
2009 - public function replaceSection( $section, $text, $summary = '', $edittime = null ) {
2010 - wfProfileIn( __METHOD__ );
2011 -
2012 - if ( strval( $section ) == '' ) {
2013 - // Whole-page edit; let the whole text through
2014 - } else {
2015 - if ( is_null( $edittime ) ) {
2016 - $rev = Revision::newFromTitle( $this->mTitle );
2017 - } else {
2018 - $dbw = wfGetDB( DB_MASTER );
2019 - $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
2020 - }
2021 -
2022 - if ( !$rev ) {
2023 - wfDebug( "Article::replaceSection asked for bogus section (page: " .
2024 - $this->getId() . "; section: $section; edittime: $edittime)\n" );
2025 - wfProfileOut( __METHOD__ );
2026 - return null;
2027 - }
2028 -
2029 - $oldtext = $rev->getText();
2030 -
2031 - if ( $section == 'new' ) {
2032 - # Inserting a new section
2033 - $subject = $summary ? wfMsgForContent( 'newsectionheaderdefaultlevel', $summary ) . "\n\n" : '';
2034 - $text = strlen( trim( $oldtext ) ) > 0
2035 - ? "{$oldtext}\n\n{$subject}{$text}"
2036 - : "{$subject}{$text}";
2037 - } else {
2038 - # Replacing an existing section; roll out the big guns
2039 - global $wgParser;
2040 -
2041 - $text = $wgParser->replaceSection( $oldtext, $section, $text );
2042 - }
2043 - }
2044 -
2045 - wfProfileOut( __METHOD__ );
2046 - return $text;
2047 - }
2048 -
2049 - /**
2050 - * Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
2051 - * @param $flags Int
2052 - * @return Int updated $flags
2053 - */
2054 - function checkFlags( $flags ) {
2055 - if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
2056 - if ( $this->mTitle->getArticleID() ) {
2057 - $flags |= EDIT_UPDATE;
2058 - } else {
2059 - $flags |= EDIT_NEW;
2060 - }
2061 - }
2062 -
2063 - return $flags;
2064 - }
2065 -
2066 - /**
2067 - * Article::doEdit()
2068 - *
2069 - * Change an existing article or create a new article. Updates RC and all necessary caches,
2070 - * optionally via the deferred update array.
2071 - *
2072 - * $wgUser must be set before calling this function.
2073 - *
2074 - * @param $text String: new text
2075 - * @param $summary String: edit summary
2076 - * @param $flags Integer bitfield:
2077 - * EDIT_NEW
2078 - * Article is known or assumed to be non-existent, create a new one
2079 - * EDIT_UPDATE
2080 - * Article is known or assumed to be pre-existing, update it
2081 - * EDIT_MINOR
2082 - * Mark this edit minor, if the user is allowed to do so
2083 - * EDIT_SUPPRESS_RC
2084 - * Do not log the change in recentchanges
2085 - * EDIT_FORCE_BOT
2086 - * Mark the edit a "bot" edit regardless of user rights
2087 - * EDIT_DEFER_UPDATES
2088 - * Defer some of the updates until the end of index.php
2089 - * EDIT_AUTOSUMMARY
2090 - * Fill in blank summaries with generated text where possible
2091 - *
2092 - * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
2093 - * If EDIT_UPDATE is specified and the article doesn't exist, the function will an
2094 - * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
2095 - * edit-already-exists error will be returned. These two conditions are also possible with
2096 - * auto-detection due to MediaWiki's performance-optimised locking strategy.
2097 - *
2098 - * @param $baseRevId the revision ID this edit was based off, if any
2099 - * @param $user User (optional), $wgUser will be used if not passed
2100 - *
2101 - * @return Status object. Possible errors:
2102 - * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
2103 - * edit-gone-missing: In update mode, but the article didn't exist
2104 - * edit-conflict: In update mode, the article changed unexpectedly
2105 - * edit-no-change: Warning that the text was the same as before
2106 - * edit-already-exists: In creation mode, but the article already exists
2107 - *
2108 - * Extensions may define additional errors.
2109 - *
2110 - * $return->value will contain an associative array with members as follows:
2111 - * new: Boolean indicating if the function attempted to create a new article
2112 - * revision: The revision object for the inserted revision, or null
2113 - *
2114 - * Compatibility note: this function previously returned a boolean value indicating success/failure
2115 - */
2116 - public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
2117 - global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries;
2118 -
2119 - # Low-level sanity check
2120 - if ( $this->mTitle->getText() === '' ) {
2121 - throw new MWException( 'Something is trying to edit an article with an empty title' );
2122 - }
2123 -
2124 - wfProfileIn( __METHOD__ );
2125 -
2126 - $user = is_null( $user ) ? $wgUser : $user;
2127 - $status = Status::newGood( array() );
2128 -
2129 - # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already
2130 - $this->loadPageData();
2131 -
2132 - $flags = $this->checkFlags( $flags );
2133 -
2134 - if ( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text, &$summary,
2135 - $flags & EDIT_MINOR, null, null, &$flags, &$status ) ) )
2136 - {
2137 - wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
2138 -
2139 - if ( $status->isOK() ) {
2140 - $status->fatal( 'edit-hook-aborted' );
2141 - }
2142 -
2143 - wfProfileOut( __METHOD__ );
2144 - return $status;
2145 - }
2146 -
2147 - # Silently ignore EDIT_MINOR if not allowed
2148 - $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
2149 - $bot = $flags & EDIT_FORCE_BOT;
2150 -
2151 - $oldtext = $this->getRawText(); // current revision
2152 - $oldsize = strlen( $oldtext );
2153 -
2154 - # Provide autosummaries if one is not provided and autosummaries are enabled.
2155 - if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
2156 - $summary = $this->getAutosummary( $oldtext, $text, $flags );
2157 - }
2158 -
2159 - $editInfo = $this->prepareTextForEdit( $text, null, $user );
2160 - $text = $editInfo->pst;
2161 - $newsize = strlen( $text );
2162 -
2163 - $dbw = wfGetDB( DB_MASTER );
2164 - $now = wfTimestampNow();
2165 - $this->mTimestamp = $now;
2166 -
2167 - if ( $flags & EDIT_UPDATE ) {
2168 - # Update article, but only if changed.
2169 - $status->value['new'] = false;
2170 -
2171 - # Make sure the revision is either completely inserted or not inserted at all
2172 - if ( !$wgDBtransactions ) {
2173 - $userAbort = ignore_user_abort( true );
2174 - }
2175 -
2176 - $changed = ( strcmp( $text, $oldtext ) != 0 );
2177 -
2178 - if ( $changed ) {
2179 - if ( !$this->mLatest ) {
2180 - # Article gone missing
2181 - wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
2182 - $status->fatal( 'edit-gone-missing' );
2183 -
2184 - wfProfileOut( __METHOD__ );
2185 - return $status;
2186 - }
2187 -
2188 - $revision = new Revision( array(
2189 - 'page' => $this->getId(),
2190 - 'comment' => $summary,
2191 - 'minor_edit' => $isminor,
2192 - 'text' => $text,
2193 - 'parent_id' => $this->mLatest,
2194 - 'user' => $user->getId(),
2195 - 'user_text' => $user->getName(),
2196 - 'timestamp' => $now
2197 - ) );
2198 -
2199 - $dbw->begin();
2200 - $revisionId = $revision->insertOn( $dbw );
2201 -
2202 - # Update page
2203 - #
2204 - # Note that we use $this->mLatest instead of fetching a value from the master DB
2205 - # during the course of this function. This makes sure that EditPage can detect
2206 - # edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
2207 - # before this function is called. A previous function used a separate query, this
2208 - # creates a window where concurrent edits can cause an ignored edit conflict.
2209 - $ok = $this->updateRevisionOn( $dbw, $revision, $this->mLatest );
2210 -
2211 - if ( !$ok ) {
2212 - /* Belated edit conflict! Run away!! */
2213 - $status->fatal( 'edit-conflict' );
2214 -
2215 - # Delete the invalid revision if the DB is not transactional
2216 - if ( !$wgDBtransactions ) {
2217 - $dbw->delete( 'revision', array( 'rev_id' => $revisionId ), __METHOD__ );
2218 - }
2219 -
2220 - $revisionId = 0;
2221 - $dbw->rollback();
2222 - } else {
2223 - global $wgUseRCPatrol;
2224 - wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
2225 - # Update recentchanges
2226 - if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
2227 - # Mark as patrolled if the user can do so
2228 - $patrolled = $wgUseRCPatrol && !count(
2229 - $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
2230 - # Add RC row to the DB
2231 - $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
2232 - $this->mLatest, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
2233 - $revisionId, $patrolled
2234 - );
2235 -
2236 - # Log auto-patrolled edits
2237 - if ( $patrolled ) {
2238 - PatrolLog::record( $rc, true );
2239 - }
2240 - }
2241 - $user->incEditCount();
2242 - $dbw->commit();
2243 - }
2244 - } else {
2245 - $status->warning( 'edit-no-change' );
2246 - $revision = null;
2247 - // Keep the same revision ID, but do some updates on it
2248 - $revisionId = $this->getLatest();
2249 - // Update page_touched, this is usually implicit in the page update
2250 - // Other cache updates are done in onArticleEdit()
2251 - $this->mTitle->invalidateCache();
2252 - }
2253 -
2254 - if ( !$wgDBtransactions ) {
2255 - ignore_user_abort( $userAbort );
2256 - }
2257 -
2258 - // Now that ignore_user_abort is restored, we can respond to fatal errors
2259 - if ( !$status->isOK() ) {
2260 - wfProfileOut( __METHOD__ );
2261 - return $status;
2262 - }
2263 -
2264 - # Invalidate cache of this article and all pages using this article
2265 - # as a template. Partly deferred.
2266 - Article::onArticleEdit( $this->mTitle );
2267 - # Update links tables, site stats, etc.
2268 - $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed, $user );
2269 - } else {
2270 - # Create new article
2271 - $status->value['new'] = true;
2272 -
2273 - $dbw->begin();
2274 -
2275 - # Add the page record; stake our claim on this title!
2276 - # This will return false if the article already exists
2277 - $newid = $this->insertOn( $dbw );
2278 -
2279 - if ( $newid === false ) {
2280 - $dbw->rollback();
2281 - $status->fatal( 'edit-already-exists' );
2282 -
2283 - wfProfileOut( __METHOD__ );
2284 - return $status;
2285 - }
2286 -
2287 - # Save the revision text...
2288 - $revision = new Revision( array(
2289 - 'page' => $newid,
2290 - 'comment' => $summary,
2291 - 'minor_edit' => $isminor,
2292 - 'text' => $text,
2293 - 'user' => $user->getId(),
2294 - 'user_text' => $user->getName(),
2295 - 'timestamp' => $now
2296 - ) );
2297 - $revisionId = $revision->insertOn( $dbw );
2298 -
2299 - $this->mTitle->resetArticleID( $newid );
2300 - # Update the LinkCache. Resetting the Title ArticleID means it will rely on having that already cached
2301 - # @todo FIXME?
2302 - LinkCache::singleton()->addGoodLinkObj( $newid, $this->mTitle, strlen( $text ), (bool)Title::newFromRedirect( $text ), $revisionId );
2303 -
2304 - # Update the page record with revision data
2305 - $this->updateRevisionOn( $dbw, $revision, 0 );
2306 -
2307 - wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
2308 -
2309 - # Update recentchanges
2310 - if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
2311 - global $wgUseRCPatrol, $wgUseNPPatrol;
2312 -
2313 - # Mark as patrolled if the user can do so
2314 - $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
2315 - $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
2316 - # Add RC row to the DB
2317 - $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
2318 - '', strlen( $text ), $revisionId, $patrolled );
2319 -
2320 - # Log auto-patrolled edits
2321 - if ( $patrolled ) {
2322 - PatrolLog::record( $rc, true );
2323 - }
2324 - }
2325 - $user->incEditCount();
2326 - $dbw->commit();
2327 -
2328 - # Update links, etc.
2329 - $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, true, $user, true );
2330 -
2331 - # Clear caches
2332 - Article::onArticleCreate( $this->mTitle );
2333 -
2334 - wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
2335 - $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
2336 - }
2337 -
2338 - # Do updates right now unless deferral was requested
2339 - if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
2340 - wfDoUpdates();
2341 - }
2342 -
2343 - // Return the new revision (or null) to the caller
2344 - $status->value['revision'] = $revision;
2345 -
2346 - wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
2347 - $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
2348 -
2349 - # Promote user to any groups they meet the criteria for
2350 - $user->addAutopromoteOnceGroups( 'onEdit' );
2351 -
2352 - wfProfileOut( __METHOD__ );
2353 - return $status;
2354 - }
2355 -
2356 - /**
23571241 * Output a redirect back to the article.
23581242 * This is typically used after an edit.
23591243 *
@@ -2373,218 +1257,10 @@
23741258 $query = $extraQuery;
23751259 }
23761260
2377 - $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor );
 1261+ $wgOut->redirect( $this->getTitle()->getFullURL( $query ) . $sectionAnchor );
23781262 }
23791263
23801264 /**
2381 - * Update the article's restriction field, and leave a log entry.
2382 - *
2383 - * @param $limit Array: set of restriction keys
2384 - * @param $reason String
2385 - * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
2386 - * @param $expiry Array: per restriction type expiration
2387 - * @return bool true on success
2388 - */
2389 - public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
2390 - global $wgUser, $wgContLang;
2391 -
2392 - $restrictionTypes = $this->mTitle->getRestrictionTypes();
2393 -
2394 - $id = $this->mTitle->getArticleID();
2395 -
2396 - if ( $id <= 0 ) {
2397 - wfDebug( "updateRestrictions failed: article id $id <= 0\n" );
2398 - return false;
2399 - }
2400 -
2401 - if ( wfReadOnly() ) {
2402 - wfDebug( "updateRestrictions failed: read-only\n" );
2403 - return false;
2404 - }
2405 -
2406 - if ( !$this->mTitle->userCan( 'protect' ) ) {
2407 - wfDebug( "updateRestrictions failed: insufficient permissions\n" );
2408 - return false;
2409 - }
2410 -
2411 - if ( !$cascade ) {
2412 - $cascade = false;
2413 - }
2414 -
2415 - // Take this opportunity to purge out expired restrictions
2416 - Title::purgeExpiredRestrictions();
2417 -
2418 - # @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
2419 - # we expect a single selection, but the schema allows otherwise.
2420 - $current = array();
2421 - $updated = Article::flattenRestrictions( $limit );
2422 - $changed = false;
2423 -
2424 - foreach ( $restrictionTypes as $action ) {
2425 - if ( isset( $expiry[$action] ) ) {
2426 - # Get current restrictions on $action
2427 - $aLimits = $this->mTitle->getRestrictions( $action );
2428 - $current[$action] = implode( '', $aLimits );
2429 - # Are any actual restrictions being dealt with here?
2430 - $aRChanged = count( $aLimits ) || !empty( $limit[$action] );
2431 -
2432 - # If something changed, we need to log it. Checking $aRChanged
2433 - # assures that "unprotecting" a page that is not protected does
2434 - # not log just because the expiry was "changed".
2435 - if ( $aRChanged && $this->mTitle->mRestrictionsExpiry[$action] != $expiry[$action] ) {
2436 - $changed = true;
2437 - }
2438 - }
2439 - }
2440 -
2441 - $current = Article::flattenRestrictions( $current );
2442 -
2443 - $changed = ( $changed || $current != $updated );
2444 - $changed = $changed || ( $updated && $this->mTitle->areRestrictionsCascading() != $cascade );
2445 - $protect = ( $updated != '' );
2446 -
2447 - # If nothing's changed, do nothing
2448 - if ( $changed ) {
2449 - if ( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser, $limit, $reason ) ) ) {
2450 - $dbw = wfGetDB( DB_MASTER );
2451 -
2452 - # Prepare a null revision to be added to the history
2453 - $modified = $current != '' && $protect;
2454 -
2455 - if ( $protect ) {
2456 - $comment_type = $modified ? 'modifiedarticleprotection' : 'protectedarticle';
2457 - } else {
2458 - $comment_type = 'unprotectedarticle';
2459 - }
2460 -
2461 - $comment = $wgContLang->ucfirst( wfMsgForContent( $comment_type, $this->mTitle->getPrefixedText() ) );
2462 -
2463 - # Only restrictions with the 'protect' right can cascade...
2464 - # Otherwise, people who cannot normally protect can "protect" pages via transclusion
2465 - $editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' );
2466 -
2467 - # The schema allows multiple restrictions
2468 - if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) ) {
2469 - $cascade = false;
2470 - }
2471 -
2472 - $cascade_description = '';
2473 -
2474 - if ( $cascade ) {
2475 - $cascade_description = ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']';
2476 - }
2477 -
2478 - if ( $reason ) {
2479 - $comment .= ": $reason";
2480 - }
2481 -
2482 - $editComment = $comment;
2483 - $encodedExpiry = array();
2484 - $protect_description = '';
2485 - foreach ( $limit as $action => $restrictions ) {
2486 - if ( !isset( $expiry[$action] ) )
2487 - $expiry[$action] = $dbw->getInfinity();
2488 -
2489 - $encodedExpiry[$action] = $dbw->encodeExpiry( $expiry[$action] );
2490 - if ( $restrictions != '' ) {
2491 - $protect_description .= "[$action=$restrictions] (";
2492 - if ( $encodedExpiry[$action] != 'infinity' ) {
2493 - $protect_description .= wfMsgForContent( 'protect-expiring',
2494 - $wgContLang->timeanddate( $expiry[$action], false, false ) ,
2495 - $wgContLang->date( $expiry[$action], false, false ) ,
2496 - $wgContLang->time( $expiry[$action], false, false ) );
2497 - } else {
2498 - $protect_description .= wfMsgForContent( 'protect-expiry-indefinite' );
2499 - }
2500 -
2501 - $protect_description .= ') ';
2502 - }
2503 - }
2504 - $protect_description = trim( $protect_description );
2505 -
2506 - if ( $protect_description && $protect ) {
2507 - $editComment .= " ($protect_description)";
2508 - }
2509 -
2510 - if ( $cascade ) {
2511 - $editComment .= "$cascade_description";
2512 - }
2513 -
2514 - # Update restrictions table
2515 - foreach ( $limit as $action => $restrictions ) {
2516 - if ( $restrictions != '' ) {
2517 - $dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ),
2518 - array( 'pr_page' => $id,
2519 - 'pr_type' => $action,
2520 - 'pr_level' => $restrictions,
2521 - 'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
2522 - 'pr_expiry' => $encodedExpiry[$action]
2523 - ),
2524 - __METHOD__
2525 - );
2526 - } else {
2527 - $dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
2528 - 'pr_type' => $action ), __METHOD__ );
2529 - }
2530 - }
2531 -
2532 - # Insert a null revision
2533 - $nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true );
2534 - $nullRevId = $nullRevision->insertOn( $dbw );
2535 -
2536 - $latest = $this->getLatest();
2537 - # Update page record
2538 - $dbw->update( 'page',
2539 - array( /* SET */
2540 - 'page_touched' => $dbw->timestamp(),
2541 - 'page_restrictions' => '',
2542 - 'page_latest' => $nullRevId
2543 - ), array( /* WHERE */
2544 - 'page_id' => $id
2545 - ), 'Article::protect'
2546 - );
2547 -
2548 - wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $wgUser ) );
2549 - wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) );
2550 -
2551 - # Update the protection log
2552 - $log = new LogPage( 'protect' );
2553 - if ( $protect ) {
2554 - $params = array( $protect_description, $cascade ? 'cascade' : '' );
2555 - $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason ), $params );
2556 - } else {
2557 - $log->addEntry( 'unprotect', $this->mTitle, $reason );
2558 - }
2559 - } # End hook
2560 - } # End "changed" check
2561 -
2562 - return true;
2563 - }
2564 -
2565 - /**
2566 - * Take an array of page restrictions and flatten it to a string
2567 - * suitable for insertion into the page_restrictions field.
2568 - * @param $limit Array
2569 - * @return String
2570 - */
2571 - protected static function flattenRestrictions( $limit ) {
2572 - if ( !is_array( $limit ) ) {
2573 - throw new MWException( 'Article::flattenRestrictions given non-array restriction set' );
2574 - }
2575 -
2576 - $bits = array();
2577 - ksort( $limit );
2578 -
2579 - foreach ( $limit as $action => $restrictions ) {
2580 - if ( $restrictions != '' ) {
2581 - $bits[] = "$action=$restrictions";
2582 - }
2583 - }
2584 -
2585 - return implode( ':', $bits );
2586 - }
2587 -
2588 - /**
25891265 * Auto-generates a deletion reason
25901266 *
25911267 * @param &$hasHistory Boolean: whether the page has a history
@@ -2596,7 +1272,7 @@
25971273
25981274 $dbw = wfGetDB( DB_MASTER );
25991275 // Get the last revision
2600 - $rev = Revision::newFromTitle( $this->mTitle );
 1276+ $rev = Revision::newFromTitle( $this->getTitle() );
26011277
26021278 if ( is_null( $rev ) ) {
26031279 return false;
@@ -2620,7 +1296,7 @@
26211297 // Find out if there was only one contributor
26221298 // Only scan the last 20 revisions
26231299 $res = $dbw->select( 'revision', 'rev_user_text',
2624 - array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
 1300+ array( 'rev_page' => $this->mPage->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
26251301 __METHOD__,
26261302 array( 'LIMIT' => 20 )
26271303 );
@@ -2713,7 +1389,7 @@
27141390 }
27151391
27161392 # Check permissions
2717 - $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $this->getContext()->getUser() );
 1393+ $permission_errors = $this->getTitle()->getUserPermissionsErrors( 'delete', $this->getContext()->getUser() );
27181394
27191395 if ( count( $permission_errors ) > 0 ) {
27201396 $wgOut->showPermissionsErrorPage( $permission_errors );
@@ -2721,11 +1397,11 @@
27221398 return;
27231399 }
27241400
2725 - $wgOut->setPagetitle( wfMsg( 'delete-confirm', $this->mTitle->getPrefixedText() ) );
 1401+ $wgOut->setPagetitle( wfMsg( 'delete-confirm', $this->getTitle()->getPrefixedText() ) );
27261402
27271403 # Better double-check that it hasn't been deleted yet!
27281404 $dbw = wfGetDB( DB_MASTER );
2729 - $conds = $this->mTitle->pageCond();
 1405+ $conds = $this->getTitle()->pageCond();
27301406 $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
27311407 if ( $latest === false ) {
27321408 $wgOut->showFatalError(
@@ -2733,22 +1409,22 @@
27341410 'div',
27351411 array( 'class' => 'error mw-error-cannotdelete' ),
27361412 wfMsgExt( 'cannotdelete', array( 'parse' ),
2737 - wfEscapeWikiText( $this->mTitle->getPrefixedText() ) )
 1413+ wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
27381414 )
27391415 );
27401416 $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
27411417 LogEventsList::showLogExtract(
27421418 $wgOut,
27431419 'delete',
2744 - $this->mTitle->getPrefixedText()
 1420+ $this->getTitle()->getPrefixedText()
27451421 );
27461422
27471423 return;
27481424 }
27491425
27501426 # Hack for big sites
2751 - $bigHistory = $this->isBigDeletion();
2752 - if ( $bigHistory && !$this->mTitle->userCan( 'bigdelete' ) ) {
 1427+ $bigHistory = $this->mPage->isBigDeletion();
 1428+ if ( $bigHistory && !$this->getTitle()->userCan( 'bigdelete' ) ) {
27531429 global $wgLang, $wgDeleteRevisionsLimit;
27541430
27551431 $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
@@ -2762,7 +1438,7 @@
27631439
27641440 if ( $wgRequest->getCheck( 'wpWatch' ) && $this->getContext()->getUser()->isLoggedIn() ) {
27651441 $this->doWatch();
2766 - } elseif ( $this->mTitle->userIsWatching() ) {
 1442+ } elseif ( $this->getTitle()->userIsWatching() ) {
27671443 $this->doUnwatch();
27681444 }
27691445
@@ -2779,11 +1455,11 @@
27801456 if ( $hasHistory && !$confirm ) {
27811457 global $wgLang;
27821458
2783 - $revisions = $this->estimateRevisionCount();
 1459+ $revisions = $this->mPage->estimateRevisionCount();
27841460 // @todo FIXME: i18n issue/patchwork message
27851461 $wgOut->addHTML( '<strong class="mw-delete-warning-revisions">' .
27861462 wfMsgExt( 'historywarning', array( 'parseinline' ), $wgLang->formatNum( $revisions ) ) .
2787 - wfMsgHtml( 'word-separator' ) . Linker::link( $this->mTitle,
 1463+ wfMsgHtml( 'word-separator' ) . Linker::link( $this->getTitle(),
27881464 wfMsgHtml( 'history' ),
27891465 array( 'rel' => 'archives' ),
27901466 array( 'action' => 'history' ) ) .
@@ -2801,86 +1477,6 @@
28021478 }
28031479
28041480 /**
2805 - * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
2806 - */
2807 - public function isBigDeletion() {
2808 - global $wgDeleteRevisionsLimit;
2809 -
2810 - if ( $wgDeleteRevisionsLimit ) {
2811 - $revCount = $this->estimateRevisionCount();
2812 -
2813 - return $revCount > $wgDeleteRevisionsLimit;
2814 - }
2815 -
2816 - return false;
2817 - }
2818 -
2819 - /**
2820 - * @return int approximate revision count
2821 - */
2822 - public function estimateRevisionCount() {
2823 - $dbr = wfGetDB( DB_SLAVE );
2824 -
2825 - // For an exact count...
2826 - // return $dbr->selectField( 'revision', 'COUNT(*)',
2827 - // array( 'rev_page' => $this->getId() ), __METHOD__ );
2828 - return $dbr->estimateRowCount( 'revision', '*',
2829 - array( 'rev_page' => $this->getId() ), __METHOD__ );
2830 - }
2831 -
2832 - /**
2833 - * Get the last N authors
2834 - * @param $num Integer: number of revisions to get
2835 - * @param $revLatest String: the latest rev_id, selected from the master (optional)
2836 - * @return array Array of authors, duplicates not removed
2837 - */
2838 - public function getLastNAuthors( $num, $revLatest = 0 ) {
2839 - wfProfileIn( __METHOD__ );
2840 - // First try the slave
2841 - // If that doesn't have the latest revision, try the master
2842 - $continue = 2;
2843 - $db = wfGetDB( DB_SLAVE );
2844 -
2845 - do {
2846 - $res = $db->select( array( 'page', 'revision' ),
2847 - array( 'rev_id', 'rev_user_text' ),
2848 - array(
2849 - 'page_namespace' => $this->mTitle->getNamespace(),
2850 - 'page_title' => $this->mTitle->getDBkey(),
2851 - 'rev_page = page_id'
2852 - ), __METHOD__,
2853 - array(
2854 - 'ORDER BY' => 'rev_timestamp DESC',
2855 - 'LIMIT' => $num
2856 - )
2857 - );
2858 -
2859 - if ( !$res ) {
2860 - wfProfileOut( __METHOD__ );
2861 - return array();
2862 - }
2863 -
2864 - $row = $db->fetchObject( $res );
2865 -
2866 - if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
2867 - $db = wfGetDB( DB_MASTER );
2868 - $continue--;
2869 - } else {
2870 - $continue = 0;
2871 - }
2872 - } while ( $continue );
2873 -
2874 - $authors = array( $row->rev_user_text );
2875 -
2876 - foreach ( $res as $row ) {
2877 - $authors[] = $row->rev_user_text;
2878 - }
2879 -
2880 - wfProfileOut( __METHOD__ );
2881 - return $authors;
2882 - }
2883 -
2884 - /**
28851481 * Output deletion confirmation dialog
28861482 * @todo FIXME: Move to another file?
28871483 * @param $reason String: prefilled reason
@@ -2890,7 +1486,7 @@
28911487
28921488 wfDebug( "Article::confirmDelete\n" );
28931489
2894 - $deleteBackLink = Linker::linkKnown( $this->mTitle );
 1490+ $deleteBackLink = Linker::linkKnown( $this->getTitle() );
28951491 $wgOut->setSubtitle( wfMsgHtml( 'delete-backlink', $deleteBackLink ) );
28961492 $wgOut->setRobotPolicy( 'noindex,nofollow' );
28971493 $wgOut->addWikiMsg( 'confirmdeletetext' );
@@ -2908,10 +1504,10 @@
29091505 } else {
29101506 $suppress = '';
29111507 }
2912 - $checkWatch = $this->getContext()->getUser()->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching();
 1508+ $checkWatch = $this->getContext()->getUser()->getBoolOption( 'watchdeletion' ) || $this->getTitle()->userIsWatching();
29131509
29141510 $form = Xml::openElement( 'form', array( 'method' => 'post',
2915 - 'action' => $this->mTitle->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
 1511+ 'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
29161512 Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
29171513 Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) .
29181514 Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
@@ -2980,7 +1576,7 @@
29811577 $wgOut->addHTML( $form );
29821578 $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
29831579 LogEventsList::showLogExtract( $wgOut, 'delete',
2984 - $this->mTitle->getPrefixedText()
 1580+ $this->getTitle()->getPrefixedText()
29851581 );
29861582 }
29871583
@@ -2990,11 +1586,11 @@
29911587 public function doDelete( $reason, $suppress = false ) {
29921588 global $wgOut;
29931589
2994 - $id = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
 1590+ $id = $this->getTitle()->getArticleID( Title::GAID_FOR_UPDATE );
29951591
29961592 $error = '';
2997 - if ( $this->doDeleteArticle( $reason, $suppress, $id, $error ) ) {
2998 - $deleted = $this->mTitle->getPrefixedText();
 1593+ if ( $this->mPage->doDeleteArticle( $reason, $suppress, $id, $error ) ) {
 1594+ $deleted = $this->getTitle()->getPrefixedText();
29991595
30001596 $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
30011597 $wgOut->setRobotPolicy( 'noindex,nofollow' );
@@ -3010,7 +1606,7 @@
30111607 'div',
30121608 array( 'class' => 'error mw-error-cannotdelete' ),
30131609 wfMsgExt( 'cannotdelete', array( 'parse' ),
3014 - wfEscapeWikiText( $this->mTitle->getPrefixedText() ) )
 1610+ wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
30151611 )
30161612 );
30171613
@@ -3019,7 +1615,7 @@
30201616 LogEventsList::showLogExtract(
30211617 $wgOut,
30221618 'delete',
3023 - $this->mTitle->getPrefixedText()
 1619+ $this->getTitle()->getPrefixedText()
30241620 );
30251621 } else {
30261622 $wgOut->showFatalError( $error );
@@ -3028,333 +1624,6 @@
30291625 }
30301626
30311627 /**
3032 - * Back-end article deletion
3033 - * Deletes the article with database consistency, writes logs, purges caches
3034 - *
3035 - * @param $reason string delete reason for deletion log
3036 - * @param suppress bitfield
3037 - * Revision::DELETED_TEXT
3038 - * Revision::DELETED_COMMENT
3039 - * Revision::DELETED_USER
3040 - * Revision::DELETED_RESTRICTED
3041 - * @param $id int article ID
3042 - * @param $commit boolean defaults to true, triggers transaction end
3043 - * @return boolean true if successful
3044 - */
3045 - public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) {
3046 - global $wgDeferredUpdateList, $wgUseTrackbacks;
3047 - global $wgUser;
3048 -
3049 - wfDebug( __METHOD__ . "\n" );
3050 -
3051 - if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$wgUser, &$reason, &$error ) ) ) {
3052 - return false;
3053 - }
3054 - $dbw = wfGetDB( DB_MASTER );
3055 - $t = $this->mTitle->getDBkey();
3056 - $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
3057 -
3058 - if ( $t === '' || $id == 0 ) {
3059 - return false;
3060 - }
3061 -
3062 - $u = new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 );
3063 - array_push( $wgDeferredUpdateList, $u );
3064 -
3065 - // Bitfields to further suppress the content
3066 - if ( $suppress ) {
3067 - $bitfield = 0;
3068 - // This should be 15...
3069 - $bitfield |= Revision::DELETED_TEXT;
3070 - $bitfield |= Revision::DELETED_COMMENT;
3071 - $bitfield |= Revision::DELETED_USER;
3072 - $bitfield |= Revision::DELETED_RESTRICTED;
3073 - } else {
3074 - $bitfield = 'rev_deleted';
3075 - }
3076 -
3077 - $dbw->begin();
3078 - // For now, shunt the revision data into the archive table.
3079 - // Text is *not* removed from the text table; bulk storage
3080 - // is left intact to avoid breaking block-compression or
3081 - // immutable storage schemes.
3082 - //
3083 - // For backwards compatibility, note that some older archive
3084 - // table entries will have ar_text and ar_flags fields still.
3085 - //
3086 - // In the future, we may keep revisions and mark them with
3087 - // the rev_deleted field, which is reserved for this purpose.
3088 - $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
3089 - array(
3090 - 'ar_namespace' => 'page_namespace',
3091 - 'ar_title' => 'page_title',
3092 - 'ar_comment' => 'rev_comment',
3093 - 'ar_user' => 'rev_user',
3094 - 'ar_user_text' => 'rev_user_text',
3095 - 'ar_timestamp' => 'rev_timestamp',
3096 - 'ar_minor_edit' => 'rev_minor_edit',
3097 - 'ar_rev_id' => 'rev_id',
3098 - 'ar_text_id' => 'rev_text_id',
3099 - 'ar_text' => '\'\'', // Be explicit to appease
3100 - 'ar_flags' => '\'\'', // MySQL's "strict mode"...
3101 - 'ar_len' => 'rev_len',
3102 - 'ar_page_id' => 'page_id',
3103 - 'ar_deleted' => $bitfield
3104 - ), array(
3105 - 'page_id' => $id,
3106 - 'page_id = rev_page'
3107 - ), __METHOD__
3108 - );
3109 -
3110 - # Delete restrictions for it
3111 - $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
3112 -
3113 - # Now that it's safely backed up, delete it
3114 - $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
3115 - $ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy
3116 -
3117 - if ( !$ok ) {
3118 - $dbw->rollback();
3119 - return false;
3120 - }
3121 -
3122 - # Fix category table counts
3123 - $cats = array();
3124 - $res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
3125 -
3126 - foreach ( $res as $row ) {
3127 - $cats [] = $row->cl_to;
3128 - }
3129 -
3130 - $this->updateCategoryCounts( array(), $cats );
3131 -
3132 - # If using cascading deletes, we can skip some explicit deletes
3133 - if ( !$dbw->cascadingDeletes() ) {
3134 - $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
3135 -
3136 - if ( $wgUseTrackbacks )
3137 - $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
3138 -
3139 - # Delete outgoing links
3140 - $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
3141 - $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
3142 - $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
3143 - $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
3144 - $dbw->delete( 'externallinks', array( 'el_from' => $id ) );
3145 - $dbw->delete( 'langlinks', array( 'll_from' => $id ) );
3146 - $dbw->delete( 'iwlinks', array( 'iwl_from' => $id ) );
3147 - $dbw->delete( 'redirect', array( 'rd_from' => $id ) );
3148 - }
3149 -
3150 - # If using cleanup triggers, we can skip some manual deletes
3151 - if ( !$dbw->cleanupTriggers() ) {
3152 - # Clean up recentchanges entries...
3153 - $dbw->delete( 'recentchanges',
3154 - array( 'rc_type != ' . RC_LOG,
3155 - 'rc_namespace' => $this->mTitle->getNamespace(),
3156 - 'rc_title' => $this->mTitle->getDBkey() ),
3157 - __METHOD__ );
3158 - $dbw->delete( 'recentchanges',
3159 - array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
3160 - __METHOD__ );
3161 - }
3162 -
3163 - # Clear caches
3164 - Article::onArticleDelete( $this->mTitle );
3165 -
3166 - # Clear the cached article id so the interface doesn't act like we exist
3167 - $this->mTitle->resetArticleID( 0 );
3168 -
3169 - # Log the deletion, if the page was suppressed, log it at Oversight instead
3170 - $logtype = $suppress ? 'suppress' : 'delete';
3171 - $log = new LogPage( $logtype );
3172 -
3173 - # Make sure logging got through
3174 - $log->addEntry( 'delete', $this->mTitle, $reason, array() );
3175 -
3176 - if ( $commit ) {
3177 - $dbw->commit();
3178 - }
3179 -
3180 - wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$wgUser, $reason, $id ) );
3181 - return true;
3182 - }
3183 -
3184 - /**
3185 - * Roll back the most recent consecutive set of edits to a page
3186 - * from the same user; fails if there are no eligible edits to
3187 - * roll back to, e.g. user is the sole contributor. This function
3188 - * performs permissions checks on $wgUser, then calls commitRollback()
3189 - * to do the dirty work
3190 - *
3191 - * @param $fromP String: Name of the user whose edits to rollback.
3192 - * @param $summary String: Custom summary. Set to default summary if empty.
3193 - * @param $token String: Rollback token.
3194 - * @param $bot Boolean: If true, mark all reverted edits as bot.
3195 - *
3196 - * @param $resultDetails Array: contains result-specific array of additional values
3197 - * 'alreadyrolled' : 'current' (rev)
3198 - * success : 'summary' (str), 'current' (rev), 'target' (rev)
3199 - *
3200 - * @return array of errors, each error formatted as
3201 - * array(messagekey, param1, param2, ...).
3202 - * On success, the array is empty. This array can also be passed to
3203 - * OutputPage::showPermissionsErrorPage().
3204 - */
3205 - public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails ) {
3206 - global $wgUser;
3207 -
3208 - $resultDetails = null;
3209 -
3210 - # Check permissions
3211 - $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
3212 - $rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $wgUser );
3213 - $errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
3214 -
3215 - if ( !$wgUser->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) ) {
3216 - $errors[] = array( 'sessionfailure' );
3217 - }
3218 -
3219 - if ( $wgUser->pingLimiter( 'rollback' ) || $wgUser->pingLimiter() ) {
3220 - $errors[] = array( 'actionthrottledtext' );
3221 - }
3222 -
3223 - # If there were errors, bail out now
3224 - if ( !empty( $errors ) ) {
3225 - return $errors;
3226 - }
3227 -
3228 - return $this->commitRollback( $fromP, $summary, $bot, $resultDetails );
3229 - }
3230 -
3231 - /**
3232 - * Backend implementation of doRollback(), please refer there for parameter
3233 - * and return value documentation
3234 - *
3235 - * NOTE: This function does NOT check ANY permissions, it just commits the
3236 - * rollback to the DB Therefore, you should only call this function direct-
3237 - * ly if you want to use custom permissions checks. If you don't, use
3238 - * doRollback() instead.
3239 - */
3240 - public function commitRollback( $fromP, $summary, $bot, &$resultDetails ) {
3241 - global $wgUseRCPatrol, $wgUser, $wgContLang;
3242 -
3243 - $dbw = wfGetDB( DB_MASTER );
3244 -
3245 - if ( wfReadOnly() ) {
3246 - return array( array( 'readonlytext' ) );
3247 - }
3248 -
3249 - # Get the last editor
3250 - $current = Revision::newFromTitle( $this->mTitle );
3251 - if ( is_null( $current ) ) {
3252 - # Something wrong... no page?
3253 - return array( array( 'notanarticle' ) );
3254 - }
3255 -
3256 - $from = str_replace( '_', ' ', $fromP );
3257 - # User name given should match up with the top revision.
3258 - # If the user was deleted then $from should be empty.
3259 - if ( $from != $current->getUserText() ) {
3260 - $resultDetails = array( 'current' => $current );
3261 - return array( array( 'alreadyrolled',
3262 - htmlspecialchars( $this->mTitle->getPrefixedText() ),
3263 - htmlspecialchars( $fromP ),
3264 - htmlspecialchars( $current->getUserText() )
3265 - ) );
3266 - }
3267 -
3268 - # Get the last edit not by this guy...
3269 - # Note: these may not be public values
3270 - $user = intval( $current->getRawUser() );
3271 - $user_text = $dbw->addQuotes( $current->getRawUserText() );
3272 - $s = $dbw->selectRow( 'revision',
3273 - array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
3274 - array( 'rev_page' => $current->getPage(),
3275 - "rev_user != {$user} OR rev_user_text != {$user_text}"
3276 - ), __METHOD__,
3277 - array( 'USE INDEX' => 'page_timestamp',
3278 - 'ORDER BY' => 'rev_timestamp DESC' )
3279 - );
3280 - if ( $s === false ) {
3281 - # No one else ever edited this page
3282 - return array( array( 'cantrollback' ) );
3283 - } elseif ( $s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER ) {
3284 - # Only admins can see this text
3285 - return array( array( 'notvisiblerev' ) );
3286 - }
3287 -
3288 - $set = array();
3289 - if ( $bot && $wgUser->isAllowed( 'markbotedits' ) ) {
3290 - # Mark all reverted edits as bot
3291 - $set['rc_bot'] = 1;
3292 - }
3293 -
3294 - if ( $wgUseRCPatrol ) {
3295 - # Mark all reverted edits as patrolled
3296 - $set['rc_patrolled'] = 1;
3297 - }
3298 -
3299 - if ( count( $set ) ) {
3300 - $dbw->update( 'recentchanges', $set,
3301 - array( /* WHERE */
3302 - 'rc_cur_id' => $current->getPage(),
3303 - 'rc_user_text' => $current->getUserText(),
3304 - "rc_timestamp > '{$s->rev_timestamp}'",
3305 - ), __METHOD__
3306 - );
3307 - }
3308 -
3309 - # Generate the edit summary if necessary
3310 - $target = Revision::newFromId( $s->rev_id );
3311 - if ( empty( $summary ) ) {
3312 - if ( $from == '' ) { // no public user name
3313 - $summary = wfMsgForContent( 'revertpage-nouser' );
3314 - } else {
3315 - $summary = wfMsgForContent( 'revertpage' );
3316 - }
3317 - }
3318 -
3319 - # Allow the custom summary to use the same args as the default message
3320 - $args = array(
3321 - $target->getUserText(), $from, $s->rev_id,
3322 - $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
3323 - $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
3324 - );
3325 - $summary = wfMsgReplaceArgs( $summary, $args );
3326 -
3327 - # Save
3328 - $flags = EDIT_UPDATE;
3329 -
3330 - if ( $wgUser->isAllowed( 'minoredit' ) ) {
3331 - $flags |= EDIT_MINOR;
3332 - }
3333 -
3334 - if ( $bot && ( $wgUser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
3335 - $flags |= EDIT_FORCE_BOT;
3336 - }
3337 -
3338 - # Actually store the edit
3339 - $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
3340 - if ( !empty( $status->value['revision'] ) ) {
3341 - $revId = $status->value['revision']->getId();
3342 - } else {
3343 - $revId = false;
3344 - }
3345 -
3346 - wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target, $current ) );
3347 -
3348 - $resultDetails = array(
3349 - 'summary' => $summary,
3350 - 'current' => $current,
3351 - 'target' => $target,
3352 - 'newid' => $revId
3353 - );
3354 -
3355 - return array();
3356 - }
3357 -
3358 - /**
33591628 * User interface for rollback operations
33601629 */
33611630 public function rollback() {
@@ -3362,7 +1631,7 @@
33631632
33641633 $details = null;
33651634
3366 - $result = $this->doRollback(
 1635+ $result = $this->mPage->doRollback(
33671636 $wgRequest->getVal( 'from' ),
33681637 $wgRequest->getText( 'summary' ),
33691638 $wgRequest->getVal( 'token' ),
@@ -3432,193 +1701,15 @@
34331702 $new = Linker::userLink( $target->getUser(), $target->getUserText() )
34341703 . Linker::userToolLinks( $target->getUser(), $target->getUserText() );
34351704 $wgOut->addHTML( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) );
3436 - $wgOut->returnToMain( false, $this->mTitle );
 1705+ $wgOut->returnToMain( false, $this->getTitle() );
34371706
34381707 if ( !$wgRequest->getBool( 'hidediff', false ) && !$wgUser->getBoolOption( 'norollbackdiff', false ) ) {
3439 - $de = new DifferenceEngine( $this->mTitle, $current->getId(), $newId, false, true );
 1708+ $de = new DifferenceEngine( $this->getTitle(), $current->getId(), $newId, false, true );
34401709 $de->showDiff( '', '' );
34411710 }
34421711 }
34431712
34441713 /**
3445 - * Do standard deferred updates after page view
3446 - */
3447 - public function viewUpdates() {
3448 - global $wgDeferredUpdateList, $wgDisableCounters, $wgUser;
3449 - if ( wfReadOnly() ) {
3450 - return;
3451 - }
3452 -
3453 - # Don't update page view counters on views from bot users (bug 14044)
3454 - if ( !$wgDisableCounters && !$wgUser->isAllowed( 'bot' ) && $this->getID() ) {
3455 - $wgDeferredUpdateList[] = new ViewCountUpdate( $this->getID() );
3456 - $wgDeferredUpdateList[] = new SiteStatsUpdate( 1, 0, 0 );
3457 - }
3458 -
3459 - # Update newtalk / watchlist notification status
3460 - $wgUser->clearNotification( $this->mTitle );
3461 - }
3462 -
3463 - /**
3464 - * Prepare text which is about to be saved.
3465 - * Returns a stdclass with source, pst and output members
3466 - */
3467 - public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
3468 - if ( $this->mPreparedEdit && $this->mPreparedEdit->newText == $text && $this->mPreparedEdit->revid == $revid ) {
3469 - // Already prepared
3470 - return $this->mPreparedEdit;
3471 - }
3472 -
3473 - global $wgParser;
3474 -
3475 - if( $user === null ) {
3476 - global $wgUser;
3477 - $user = $wgUser;
3478 - }
3479 - $popts = ParserOptions::newFromUser( $user );
3480 - wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
3481 -
3482 - $edit = (object)array();
3483 - $edit->revid = $revid;
3484 - $edit->newText = $text;
3485 - $edit->pst = $this->preSaveTransform( $text, $user, $popts );
3486 - $edit->popts = $this->getParserOptions( true );
3487 - $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
3488 - $edit->oldText = $this->getRawText();
3489 -
3490 - $this->mPreparedEdit = $edit;
3491 -
3492 - return $edit;
3493 - }
3494 -
3495 - /**
3496 - * Do standard deferred updates after page edit.
3497 - * Update links tables, site stats, search index and message cache.
3498 - * Purges pages that include this page if the text was changed here.
3499 - * Every 100th edit, prune the recent changes table.
3500 - *
3501 - * @private
3502 - * @param $text String: New text of the article
3503 - * @param $summary String: Edit summary
3504 - * @param $minoredit Boolean: Minor edit
3505 - * @param $timestamp_of_pagechange String timestamp associated with the page change
3506 - * @param $newid Integer: rev_id value of the new revision
3507 - * @param $changed Boolean: Whether or not the content actually changed
3508 - * @param $user User object: User doing the edit
3509 - * @param $created Boolean: Whether the edit created the page
3510 - */
3511 - public function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange, $newid,
3512 - $changed = true, User $user = null, $created = false )
3513 - {
3514 - global $wgDeferredUpdateList, $wgUser, $wgEnableParserCache;
3515 -
3516 - wfProfileIn( __METHOD__ );
3517 -
3518 - # Parse the text
3519 - # Be careful not to double-PST: $text is usually already PST-ed once
3520 - if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
3521 - wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
3522 - $editInfo = $this->prepareTextForEdit( $text, $newid, $user );
3523 - } else {
3524 - wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
3525 - $editInfo = $this->mPreparedEdit;
3526 - }
3527 -
3528 - # Save it to the parser cache
3529 - if ( $wgEnableParserCache ) {
3530 - $parserCache = ParserCache::singleton();
3531 - $parserCache->save( $editInfo->output, $this, $editInfo->popts );
3532 - }
3533 -
3534 - # Update the links tables
3535 - $u = new LinksUpdate( $this->mTitle, $editInfo->output );
3536 - $u->doUpdate();
3537 -
3538 - wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $changed ) );
3539 -
3540 - if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
3541 - if ( 0 == mt_rand( 0, 99 ) ) {
3542 - // Flush old entries from the `recentchanges` table; we do this on
3543 - // random requests so as to avoid an increase in writes for no good reason
3544 - global $wgRCMaxAge;
3545 -
3546 - $dbw = wfGetDB( DB_MASTER );
3547 - $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
3548 - $dbw->delete(
3549 - 'recentchanges',
3550 - array( "rc_timestamp < '$cutoff'" ),
3551 - __METHOD__
3552 - );
3553 - }
3554 - }
3555 -
3556 - $id = $this->getID();
3557 - $title = $this->mTitle->getPrefixedDBkey();
3558 - $shortTitle = $this->mTitle->getDBkey();
3559 -
3560 - if ( 0 == $id ) {
3561 - wfProfileOut( __METHOD__ );
3562 - return;
3563 - }
3564 -
3565 - if ( !$changed ) {
3566 - $good = 0;
3567 - $total = 0;
3568 - } elseif ( $created ) {
3569 - $good = (int)$this->isCountable( $editInfo );
3570 - $total = 1;
3571 - } else {
3572 - $good = (int)$this->isCountable( $editInfo ) - (int)$this->isCountable();
3573 - $total = 0;
3574 - }
3575 -
3576 - $wgDeferredUpdateList[] = new SiteStatsUpdate( 0, 1, $good, $total );
3577 - $wgDeferredUpdateList[] = new SearchUpdate( $id, $title, $text );
3578 -
3579 - # If this is another user's talk page, update newtalk
3580 - # Don't do this if $changed = false otherwise some idiot can null-edit a
3581 - # load of user talk pages and piss people off, nor if it's a minor edit
3582 - # by a properly-flagged bot.
3583 - if ( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed
3584 - && !( $minoredit && $wgUser->isAllowed( 'nominornewtalk' ) )
3585 - ) {
3586 - if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
3587 - $other = User::newFromName( $shortTitle, false );
3588 - if ( !$other ) {
3589 - wfDebug( __METHOD__ . ": invalid username\n" );
3590 - } elseif ( User::isIP( $shortTitle ) ) {
3591 - // An anonymous user
3592 - $other->setNewtalk( true );
3593 - } elseif ( $other->isLoggedIn() ) {
3594 - $other->setNewtalk( true );
3595 - } else {
3596 - wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
3597 - }
3598 - }
3599 - }
3600 -
3601 - if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
3602 - MessageCache::singleton()->replace( $shortTitle, $text );
3603 - }
3604 -
3605 - wfProfileOut( __METHOD__ );
3606 - }
3607 -
3608 - /**
3609 - * Perform article updates on a special page creation.
3610 - *
3611 - * @param $rev Revision object
3612 - *
3613 - * @todo This is a shitty interface function. Kill it and replace the
3614 - * other shitty functions like editUpdates and such so it's not needed
3615 - * anymore.
3616 - */
3617 - public function createUpdates( $rev ) {
3618 - $this->editUpdates( $rev->getText(), $rev->getComment(),
3619 - $rev->isMinor(), wfTimestamp(), $rev->getId(), true, null, true );
3620 - }
3621 -
3622 - /**
36231714 * Generate the navigation links when browsing through an article revisions
36241715 * It shows the information as:
36251716 * Revision as of \<date\>; view current revision
@@ -3644,7 +1735,7 @@
36451736 $revision = Revision::newFromId( $oldid );
36461737 $timestamp = $revision->getTimestamp();
36471738
3648 - $current = ( $oldid == $this->mLatest );
 1739+ $current = ( $oldid == $this->mPage->getLatest() );
36491740 $td = $wgLang->timeanddate( $timestamp, true );
36501741 $tddate = $wgLang->date( $timestamp, true );
36511742 $tdtime = $wgLang->time( $timestamp, true );
@@ -3652,7 +1743,7 @@
36531744 $lnk = $current
36541745 ? wfMsgHtml( 'currentrevisionlink' )
36551746 : Linker::link(
3656 - $this->mTitle,
 1747+ $this->getTitle(),
36571748 wfMsgHtml( 'currentrevisionlink' ),
36581749 array(),
36591750 $extraParams,
@@ -3661,7 +1752,7 @@
36621753 $curdiff = $current
36631754 ? wfMsgHtml( 'diff' )
36641755 : Linker::link(
3665 - $this->mTitle,
 1756+ $this->getTitle(),
36661757 wfMsgHtml( 'diff' ),
36671758 array(),
36681759 array(
@@ -3670,10 +1761,10 @@
36711762 ) + $extraParams,
36721763 array( 'known', 'noclasses' )
36731764 );
3674 - $prev = $this->mTitle->getPreviousRevisionID( $oldid ) ;
 1765+ $prev = $this->getTitle()->getPreviousRevisionID( $oldid ) ;
36751766 $prevlink = $prev
36761767 ? Linker::link(
3677 - $this->mTitle,
 1768+ $this->getTitle(),
36781769 wfMsgHtml( 'previousrevision' ),
36791770 array(),
36801771 array(
@@ -3685,7 +1776,7 @@
36861777 : wfMsgHtml( 'previousrevision' );
36871778 $prevdiff = $prev
36881779 ? Linker::link(
3689 - $this->mTitle,
 1780+ $this->getTitle(),
36901781 wfMsgHtml( 'diff' ),
36911782 array(),
36921783 array(
@@ -3698,7 +1789,7 @@
36991790 $nextlink = $current
37001791 ? wfMsgHtml( 'nextrevision' )
37011792 : Linker::link(
3702 - $this->mTitle,
 1793+ $this->getTitle(),
37031794 wfMsgHtml( 'nextrevision' ),
37041795 array(),
37051796 array(
@@ -3710,7 +1801,7 @@
37111802 $nextdiff = $current
37121803 ? wfMsgHtml( 'diff' )
37131804 : Linker::link(
3714 - $this->mTitle,
 1805+ $this->getTitle(),
37151806 wfMsgHtml( 'diff' ),
37161807 array(),
37171808 array(
@@ -3730,7 +1821,7 @@
37311822 } else {
37321823 $query = array(
37331824 'type' => 'revision',
3734 - 'target' => $this->mTitle->getPrefixedDbkey(),
 1825+ 'target' => $this->getTitle()->getPrefixedDbkey(),
37351826 'ids' => $oldid
37361827 );
37371828 $cdel = Linker::revDeleteLink( $query, $revision->isDeleted( File::DELETED_RESTRICTED ), $canHide );
@@ -3763,33 +1854,6 @@
37641855 $wgOut->addHTML( $r );
37651856 }
37661857
3767 - /**
3768 - * This function is called right before saving the wikitext,
3769 - * so we can do things like signatures and links-in-context.
3770 - *
3771 - * @param $text String article contents
3772 - * @param $user User object: user doing the edit, $wgUser will be used if
3773 - * null is given
3774 - * @param $popts ParserOptions object: parser options, default options for
3775 - * the user loaded if null given
3776 - * @return string article contents with altered wikitext markup (signatures
3777 - * converted, {{subst:}}, templates, etc.)
3778 - */
3779 - public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
3780 - global $wgParser;
3781 -
3782 - if ( $user === null ) {
3783 - global $wgUser;
3784 - $user = $wgUser;
3785 - }
3786 -
3787 - if ( $popts === null ) {
3788 - $popts = ParserOptions::newFromUser( $user );
3789 - }
3790 -
3791 - return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
3792 - }
3793 -
37941858 /* Caching functions */
37951859
37961860 /**
@@ -3809,8 +1873,8 @@
38101874
38111875 $called = true;
38121876 if ( $this->isFileCacheable() ) {
3813 - $cache = new HTMLFileCache( $this->mTitle );
3814 - if ( $cache->isFileCacheGood( $this->mTouched ) ) {
 1877+ $cache = new HTMLFileCache( $this->getTitle() );
 1878+ if ( $cache->isFileCacheGood( $this->mPage->getTouched() ) ) {
38151879 wfDebug( "Article::tryFileCache(): about to load file\n" );
38161880 $cache->loadFromFileCache();
38171881 return true;
@@ -3833,7 +1897,7 @@
38341898 $cacheable = false;
38351899
38361900 if ( HTMLFileCache::useFileCache() ) {
3837 - $cacheable = $this->getID() && !$this->mRedirectedFrom && !$this->mTitle->isRedirect();
 1901+ $cacheable = $this->mPage->getID() && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
38381902 // Extension may have reason to disable file caching on some pages.
38391903 if ( $cacheable ) {
38401904 $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) );
@@ -3843,162 +1907,6 @@
38441908 return $cacheable;
38451909 }
38461910
3847 - /**
3848 - * Loads page_touched and returns a value indicating if it should be used
3849 - * @return boolean true if not a redirect
3850 - */
3851 - public function checkTouched() {
3852 - if ( !$this->mDataLoaded ) {
3853 - $this->loadPageData();
3854 - }
3855 -
3856 - return !$this->mIsRedirect;
3857 - }
3858 -
3859 - /**
3860 - * Get the page_touched field
3861 - * @return string containing GMT timestamp
3862 - */
3863 - public function getTouched() {
3864 - if ( !$this->mDataLoaded ) {
3865 - $this->loadPageData();
3866 - }
3867 -
3868 - return $this->mTouched;
3869 - }
3870 -
3871 - /**
3872 - * Get the page_latest field
3873 - * @return integer rev_id of current revision
3874 - */
3875 - public function getLatest() {
3876 - if ( !$this->mDataLoaded ) {
3877 - $this->loadPageData();
3878 - }
3879 -
3880 - return (int)$this->mLatest;
3881 - }
3882 -
3883 - /**
3884 - * Edit an article without doing all that other stuff
3885 - * The article must already exist; link tables etc
3886 - * are not updated, caches are not flushed.
3887 - *
3888 - * @param $text String: text submitted
3889 - * @param $comment String: comment submitted
3890 - * @param $minor Boolean: whereas it's a minor modification
3891 - */
3892 - public function quickEdit( $text, $comment = '', $minor = 0 ) {
3893 - wfProfileIn( __METHOD__ );
3894 -
3895 - $dbw = wfGetDB( DB_MASTER );
3896 - $revision = new Revision( array(
3897 - 'page' => $this->getId(),
3898 - 'text' => $text,
3899 - 'comment' => $comment,
3900 - 'minor_edit' => $minor ? 1 : 0,
3901 - ) );
3902 - $revision->insertOn( $dbw );
3903 - $this->updateRevisionOn( $dbw, $revision );
3904 -
3905 - global $wgUser;
3906 - wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $wgUser ) );
3907 -
3908 - wfProfileOut( __METHOD__ );
3909 - }
3910 -
3911 - /**
3912 - * The onArticle*() functions are supposed to be a kind of hooks
3913 - * which should be called whenever any of the specified actions
3914 - * are done.
3915 - *
3916 - * This is a good place to put code to clear caches, for instance.
3917 - *
3918 - * This is called on page move and undelete, as well as edit
3919 - *
3920 - * @param $title Title object
3921 - */
3922 - public static function onArticleCreate( $title ) {
3923 - # Update existence markers on article/talk tabs...
3924 - if ( $title->isTalkPage() ) {
3925 - $other = $title->getSubjectPage();
3926 - } else {
3927 - $other = $title->getTalkPage();
3928 - }
3929 -
3930 - $other->invalidateCache();
3931 - $other->purgeSquid();
3932 -
3933 - $title->touchLinks();
3934 - $title->purgeSquid();
3935 - $title->deleteTitleProtection();
3936 - }
3937 -
3938 - /**
3939 - * Clears caches when article is deleted
3940 - *
3941 - * @param $title Title
3942 - */
3943 - public static function onArticleDelete( $title ) {
3944 - # Update existence markers on article/talk tabs...
3945 - if ( $title->isTalkPage() ) {
3946 - $other = $title->getSubjectPage();
3947 - } else {
3948 - $other = $title->getTalkPage();
3949 - }
3950 -
3951 - $other->invalidateCache();
3952 - $other->purgeSquid();
3953 -
3954 - $title->touchLinks();
3955 - $title->purgeSquid();
3956 -
3957 - # File cache
3958 - HTMLFileCache::clearFileCache( $title );
3959 -
3960 - # Messages
3961 - if ( $title->getNamespace() == NS_MEDIAWIKI ) {
3962 - MessageCache::singleton()->replace( $title->getDBkey(), false );
3963 - }
3964 -
3965 - # Images
3966 - if ( $title->getNamespace() == NS_FILE ) {
3967 - $update = new HTMLCacheUpdate( $title, 'imagelinks' );
3968 - $update->doUpdate();
3969 - }
3970 -
3971 - # User talk pages
3972 - if ( $title->getNamespace() == NS_USER_TALK ) {
3973 - $user = User::newFromName( $title->getText(), false );
3974 - $user->setNewtalk( false );
3975 - }
3976 -
3977 - # Image redirects
3978 - RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
3979 - }
3980 -
3981 - /**
3982 - * Purge caches on page update etc
3983 - *
3984 - * @param $title Title object
3985 - * @todo: verify that $title is always a Title object (and never false or null), add Title hint to parameter $title
3986 - */
3987 - public static function onArticleEdit( $title ) {
3988 - global $wgDeferredUpdateList;
3989 -
3990 - // Invalidate caches of articles which include this page
3991 - $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
3992 -
3993 - // Invalidate the caches of all pages which redirect here
3994 - $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
3995 -
3996 - # Purge squid for this page only
3997 - $title->purgeSquid();
3998 -
3999 - # Clear file cache for this page only
4000 - HTMLFileCache::clearFileCache( $title );
4001 - }
4002 -
40031911 /**#@-*/
40041912
40051913 /**
@@ -4011,114 +1919,6 @@
40121920 }
40131921
40141922 /**
4015 - * Return a list of templates used by this article.
4016 - * Uses the templatelinks table
4017 - *
4018 - * @return Array of Title objects
4019 - */
4020 - public function getUsedTemplates() {
4021 - $result = array();
4022 - $id = $this->mTitle->getArticleID();
4023 -
4024 - if ( $id == 0 ) {
4025 - return array();
4026 - }
4027 -
4028 - $dbr = wfGetDB( DB_SLAVE );
4029 - $res = $dbr->select( array( 'templatelinks' ),
4030 - array( 'tl_namespace', 'tl_title' ),
4031 - array( 'tl_from' => $id ),
4032 - __METHOD__ );
4033 -
4034 - if ( $res !== false ) {
4035 - foreach ( $res as $row ) {
4036 - $result[] = Title::makeTitle( $row->tl_namespace, $row->tl_title );
4037 - }
4038 - }
4039 -
4040 - return $result;
4041 - }
4042 -
4043 - /**
4044 - * Returns a list of hidden categories this page is a member of.
4045 - * Uses the page_props and categorylinks tables.
4046 - *
4047 - * @return Array of Title objects
4048 - */
4049 - public function getHiddenCategories() {
4050 - $result = array();
4051 - $id = $this->mTitle->getArticleID();
4052 -
4053 - if ( $id == 0 ) {
4054 - return array();
4055 - }
4056 -
4057 - $dbr = wfGetDB( DB_SLAVE );
4058 - $res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ),
4059 - array( 'cl_to' ),
4060 - array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
4061 - 'page_namespace' => NS_CATEGORY, 'page_title=cl_to' ),
4062 - __METHOD__ );
4063 -
4064 - if ( $res !== false ) {
4065 - foreach ( $res as $row ) {
4066 - $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
4067 - }
4068 - }
4069 -
4070 - return $result;
4071 - }
4072 -
4073 - /**
4074 - * Return an applicable autosummary if one exists for the given edit.
4075 - * @param $oldtext String: the previous text of the page.
4076 - * @param $newtext String: The submitted text of the page.
4077 - * @param $flags Int bitmask: a bitmask of flags submitted for the edit.
4078 - * @return string An appropriate autosummary, or an empty string.
4079 - */
4080 - public static function getAutosummary( $oldtext, $newtext, $flags ) {
4081 - global $wgContLang;
4082 -
4083 - # Decide what kind of autosummary is needed.
4084 -
4085 - # Redirect autosummaries
4086 - $ot = Title::newFromRedirect( $oldtext );
4087 - $rt = Title::newFromRedirect( $newtext );
4088 -
4089 - if ( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) {
4090 - return wfMsgForContent( 'autoredircomment', $rt->getFullText() );
4091 - }
4092 -
4093 - # New page autosummaries
4094 - if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
4095 - # If they're making a new article, give its text, truncated, in the summary.
4096 -
4097 - $truncatedtext = $wgContLang->truncate(
4098 - str_replace( "\n", ' ', $newtext ),
4099 - max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) );
4100 -
4101 - return wfMsgForContent( 'autosumm-new', $truncatedtext );
4102 - }
4103 -
4104 - # Blanking autosummaries
4105 - if ( $oldtext != '' && $newtext == '' ) {
4106 - return wfMsgForContent( 'autosumm-blank' );
4107 - } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
4108 - # Removing more than 90% of the article
4109 -
4110 - $truncatedtext = $wgContLang->truncate(
4111 - $newtext,
4112 - max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) );
4113 -
4114 - return wfMsgForContent( 'autosumm-replace', $truncatedtext );
4115 - }
4116 -
4117 - # If we reach this point, there's no applicable autosummary for our case, so our
4118 - # autosummary is empty.
4119 - return '';
4120 - }
4121 -
4122 - /**
41231923 * Add the primary page-view wikitext to the output buffer
41241924 * Saves the text into the parser cache if possible.
41251925 * Updates templatelinks if it is out of date.
@@ -4148,18 +1948,18 @@
41491949 global $wgParser, $wgEnableParserCache, $wgUseFileCache;
41501950
41511951 if ( !$parserOptions ) {
4152 - $parserOptions = $this->getParserOptions();
 1952+ $parserOptions = $this->mPage->getParserOptions();
41531953 }
41541954
41551955 $time = - wfTime();
4156 - $this->mParserOutput = $wgParser->parse( $text, $this->mTitle,
 1956+ $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(),
41571957 $parserOptions, true, true, $this->getRevIdFetched() );
41581958 $time += wfTime();
41591959
41601960 # Timing hack
41611961 if ( $time > 3 ) {
41621962 wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
4163 - $this->mTitle->getPrefixedDBkey() ) );
 1963+ $this->getTitle()->getPrefixedDBkey() ) );
41641964 }
41651965
41661966 if ( $wgEnableParserCache && $cache && $this->mParserOutput->isCacheable() ) {
@@ -4180,48 +1980,12 @@
41811981 }
41821982
41831983 /**
4184 - * Get parser options suitable for rendering the primary article wikitext
4185 - * @param $canonical boolean Determines that the generated options must not depend on user preferences (see bug 14404)
4186 - * @return mixed ParserOptions object or boolean false
4187 - */
4188 - public function getParserOptions( $canonical = false ) {
4189 - global $wgUser, $wgLanguageCode;
4190 -
4191 - if ( !$this->mParserOptions || $canonical ) {
4192 - $user = !$canonical ? $wgUser : new User;
4193 - $parserOptions = new ParserOptions( $user );
4194 - $parserOptions->setTidy( true );
4195 - $parserOptions->enableLimitReport();
4196 -
4197 - if ( $canonical ) {
4198 - $parserOptions->setUserLang( $wgLanguageCode ); # Must be set explicitely
4199 - return $parserOptions;
4200 - }
4201 - $this->mParserOptions = $parserOptions;
4202 - }
4203 - // Clone to allow modifications of the return value without affecting cache
4204 - return clone $this->mParserOptions;
4205 - }
4206 -
4207 - /**
4208 - * Get parser options suitable for rendering the primary article wikitext
4209 - * @param User $user
4210 - * @return ParserOptions
4211 - */
4212 - public function makeParserOptions( User $user ) {
4213 - $options = ParserOptions::newFromUser( $user );
4214 - $options->enableLimitReport(); // show inclusion/loop reports
4215 - $options->setTidy( true ); // fix bad HTML
4216 - return $options;
4217 - }
4218 -
4219 - /**
42201984 * Updates cascading protections
42211985 *
42221986 * @param $parserOutput ParserOutput object, or boolean false
42231987 **/
42241988 protected function doCascadeProtectionUpdates( $parserOutput ) {
4225 - if ( !$this->isCurrent() || wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
 1989+ if ( !$this->isCurrent() || wfReadOnly() || !$this->getTitle()->areRestrictionsCascading() ) {
42261990 return;
42271991 }
42281992
@@ -4233,7 +1997,7 @@
42341998 // are visible.
42351999
42362000 # Get templates from templatelinks
4237 - $id = $this->mTitle->getArticleID();
 2001+ $id = $this->getTitle()->getArticleID();
42382002
42392003 $tlTemplates = array();
42402004
@@ -4261,145 +2025,123 @@
42622026
42632027 if ( count( $templates_diff ) > 0 ) {
42642028 # Whee, link updates time.
4265 - $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
 2029+ $u = new LinksUpdate( $this->getTitle(), $parserOutput, false );
42662030 $u->doUpdate();
42672031 }
42682032 }
42692033
42702034 /**
4271 - * Update all the appropriate counts in the category table, given that
4272 - * we've added the categories $added and deleted the categories $deleted.
 2035+ * Sets the context this Article is executed in
42732036 *
4274 - * @param $added array The names of categories that were added
4275 - * @param $deleted array The names of categories that were deleted
 2037+ * @param $context RequestContext
 2038+ * @since 1.18
42762039 */
4277 - public function updateCategoryCounts( $added, $deleted ) {
4278 - $ns = $this->mTitle->getNamespace();
4279 - $dbw = wfGetDB( DB_MASTER );
 2040+ public function setContext( $context ) {
 2041+ $this->mContext = $context;
 2042+ }
42802043
4281 - # First make sure the rows exist. If one of the "deleted" ones didn't
4282 - # exist, we might legitimately not create it, but it's simpler to just
4283 - # create it and then give it a negative value, since the value is bogus
4284 - # anyway.
4285 - #
4286 - # Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE.
4287 - $insertCats = array_merge( $added, $deleted );
4288 - if ( !$insertCats ) {
4289 - # Okay, nothing to do
4290 - return;
 2044+ /**
 2045+ * Gets the context this Article is executed in
 2046+ *
 2047+ * @return RequestContext
 2048+ * @since 1.18
 2049+ */
 2050+ public function getContext() {
 2051+ if ( $this->mContext instanceof RequestContext ) {
 2052+ return $this->mContext;
 2053+ } else {
 2054+ wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
 2055+ return RequestContext::getMain();
42912056 }
 2057+ }
42922058
4293 - $insertRows = array();
4294 -
4295 - foreach ( $insertCats as $cat ) {
4296 - $insertRows[] = array(
4297 - 'cat_id' => $dbw->nextSequenceValue( 'category_cat_id_seq' ),
4298 - 'cat_title' => $cat
4299 - );
 2059+ /**
 2060+ * Use PHP's magic __get handler to handle accessing of
 2061+ * raw WikiPage fields for backwards compatibility.
 2062+ *
 2063+ * @param $fname String Field name
 2064+ */
 2065+ public function __get( $fname ) {
 2066+ if ( property_exists( $this->mPage, $fname ) ) {
 2067+ #wfWarn( "Access to raw $fname field " . __CLASS__ );
 2068+ return $this->mPage->$fname;
43002069 }
4301 - $dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' );
 2070+ trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
 2071+ }
43022072
4303 - $addFields = array( 'cat_pages = cat_pages + 1' );
4304 - $removeFields = array( 'cat_pages = cat_pages - 1' );
4305 -
4306 - if ( $ns == NS_CATEGORY ) {
4307 - $addFields[] = 'cat_subcats = cat_subcats + 1';
4308 - $removeFields[] = 'cat_subcats = cat_subcats - 1';
4309 - } elseif ( $ns == NS_FILE ) {
4310 - $addFields[] = 'cat_files = cat_files + 1';
4311 - $removeFields[] = 'cat_files = cat_files - 1';
 2073+ /**
 2074+ * Use PHP's magic __set handler to handle setting of
 2075+ * raw WikiPage fields for backwards compatibility.
 2076+ *
 2077+ * @param $fname String Field name
 2078+ * @param $fvalue mixed New value
 2079+ * @param $args Array Arguments to the method
 2080+ */
 2081+ public function __set( $fname, $fvalue ) {
 2082+ if ( property_exists( $this->mPage, $fname ) ) {
 2083+ #wfWarn( "Access to raw $fname field of " . __CLASS__ );
 2084+ $this->mPage->$fname = $fvalue;
 2085+ // Note: extensions may want to toss on new fields
 2086+ } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) {
 2087+ $this->mPage->$fname = $fvalue;
 2088+ } else {
 2089+ trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
43122090 }
4313 -
4314 - if ( $added ) {
4315 - $dbw->update(
4316 - 'category',
4317 - $addFields,
4318 - array( 'cat_title' => $added ),
4319 - __METHOD__
4320 - );
4321 - }
4322 -
4323 - if ( $deleted ) {
4324 - $dbw->update(
4325 - 'category',
4326 - $removeFields,
4327 - array( 'cat_title' => $deleted ),
4328 - __METHOD__
4329 - );
4330 - }
43312091 }
43322092
43332093 /**
4334 - * Lightweight method to get the parser output for a page, checking the parser cache
4335 - * and so on. Doesn't consider most of the stuff that Article::view is forced to
4336 - * consider, so it's not appropriate to use there.
 2094+ * Use PHP's magic __call handler to transform instance calls to
 2095+ * WikiPage functions for backwards compatibility.
43372096 *
4338 - * @since 1.16 (r52326) for LiquidThreads
4339 - *
4340 - * @param $oldid mixed integer Revision ID or null
4341 - * @return ParserOutput or false if the given revsion ID is not found
 2097+ * @param $fname String Name of called method
 2098+ * @param $args Array Arguments to the method
43422099 */
4343 - public function getParserOutput( $oldid = null ) {
4344 - global $wgEnableParserCache, $wgUser;
 2100+ public function __call( $fname, $args ) {
 2101+ if ( is_callable( array( $this->mPage, $fname ) ) ) {
 2102+ #wfWarn( "Call to " . __CLASS__ . "::$fname; please use WikiPage instead" );
 2103+ return call_user_func_array( array( $this->mPage, $fname ), $args );
 2104+ }
 2105+ trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR );
 2106+ }
43452107
4346 - // Should the parser cache be used?
4347 - $useParserCache = $wgEnableParserCache &&
4348 - $wgUser->getStubThreshold() == 0 &&
4349 - $this->exists() &&
4350 - $oldid === null;
 2108+ // ****** B/C functions to work-around PHP silliness with __call and references ****** //
 2109+ public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
 2110+ return $this->mPage->updateRestrictions( $limit, $reason, $cascade, $expiry );
 2111+ }
43512112
4352 - wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
 2113+ public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) {
 2114+ return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error );
 2115+ }
43532116
4354 - if ( $wgUser->getStubThreshold() ) {
4355 - wfIncrStats( 'pcache_miss_stub' );
4356 - }
 2117+ public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails ) {
 2118+ return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails );
 2119+ }
43572120
4358 - if ( $useParserCache ) {
4359 - $parserOutput = ParserCache::singleton()->get( $this, $this->getParserOptions() );
4360 - if ( $parserOutput !== false ) {
4361 - return $parserOutput;
4362 - }
4363 - }
 2121+ public function commitRollback( $fromP, $summary, $bot, &$resultDetails ) {
 2122+ return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails );
 2123+ }
43642124
4365 - // Cache miss; parse and output it.
4366 - if ( $oldid === null ) {
4367 - $text = $this->getRawText();
4368 - } else {
4369 - $rev = Revision::newFromTitle( $this->getTitle(), $oldid );
4370 - if ( $rev === null ) {
4371 - return false;
4372 - }
4373 - $text = $rev->getText();
4374 - }
 2125+ // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
 2126+ public static function selectFields() {
 2127+ return WikiPage::selectFields();
 2128+ }
43752129
4376 - return $this->getOutputFromWikitext( $text, $useParserCache );
 2130+ public static function onArticleCreate( $title ) {
 2131+ return WikiPage::onArticleCreate( $title );
43772132 }
43782133
4379 - /**
4380 - * Sets the context this Article is executed in
4381 - *
4382 - * @param $context RequestContext
4383 - * @since 1.18
4384 - */
4385 - public function setContext( $context ) {
4386 - $this->mContext = $context;
 2134+ public static function onArticleDelete( $title ) {
 2135+ return WikiPage::onArticleDelete( $title );
43872136 }
43882137
4389 - /**
4390 - * Gets the context this Article is executed in
4391 - *
4392 - * @return RequestContext
4393 - * @since 1.18
4394 - */
4395 - public function getContext() {
4396 - if ( $this->mContext instanceof RequestContext ) {
4397 - return $this->mContext;
4398 - } else {
4399 - wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
4400 - return RequestContext::getMain();
4401 - }
 2138+ public static function onArticleEdit( $title ) {
 2139+ return WikiPage::onArticleEdit( $title );
44022140 }
44032141
 2142+ public static function getAutosummary( $oldtext, $newtext, $flags ) {
 2143+ return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
 2144+ }
 2145+ // ******
44042146 }
44052147
44062148 class PoolWorkArticleView extends PoolCounterWork {
Index: trunk/phase3/includes/WikiFilePage.php
@@ -0,0 +1,135 @@
 2+<?php
 3+/**
 4+ * Special handling for file pages
 5+ *
 6+ * @ingroup Media
 7+ */
 8+class WikiFilePage extends WikiPage {
 9+ protected $mFile = false; // !< File object
 10+ protected $mRepo = null; // !<
 11+ protected $mFileLoaded = false; // !<
 12+ protected $mDupes = null; // !<
 13+
 14+ function __construct( $title ) {
 15+ parent::__construct( $title );
 16+ $this->mDupes = null;
 17+ $this->mRepo = null;
 18+ }
 19+
 20+ /**
 21+ * @param $file File:
 22+ * @return void
 23+ */
 24+ public function setFile( $file ) {
 25+ $this->mFile = $file;
 26+ $this->mFileLoaded = true;
 27+ }
 28+
 29+ protected function loadFile() {
 30+ if ( $this->mFileLoaded ) {
 31+ return true;
 32+ }
 33+ $this->mFileLoaded = true;
 34+
 35+ $this->mFile = false;
 36+ if ( !$this->mFile ) {
 37+ $this->mFile = wfFindFile( $this->mTitle );
 38+ if ( !$this->mFile ) {
 39+ $this->mFile = wfLocalFile( $this->mTitle ); // always a File
 40+ }
 41+ }
 42+ $this->mRepo = $this->mFile->getRepo();
 43+ }
 44+
 45+ public function getRedirectTarget() {
 46+ $this->loadFile();
 47+ if ( $this->mFile->isLocal() ) {
 48+ return parent::getRedirectTarget();
 49+ }
 50+ // Foreign image page
 51+ $from = $this->mFile->getRedirected();
 52+ $to = $this->mFile->getName();
 53+ if ( $from == $to ) {
 54+ return null;
 55+ }
 56+ return $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
 57+ }
 58+
 59+ public function followRedirect() {
 60+ $this->loadFile();
 61+ if ( $this->mFile->isLocal() ) {
 62+ return parent::followRedirect();
 63+ }
 64+ $from = $this->mFile->getRedirected();
 65+ $to = $this->mFile->getName();
 66+ if ( $from == $to ) {
 67+ return false;
 68+ }
 69+ return Title::makeTitle( NS_FILE, $to );
 70+ }
 71+
 72+ public function isRedirect( $text = false ) {
 73+ $this->loadFile();
 74+ if ( $this->mFile->isLocal() ) {
 75+ return parent::isRedirect( $text );
 76+ }
 77+
 78+ return (bool)$this->mFile->getRedirected();
 79+ }
 80+
 81+ public function isLocal() {
 82+ $this->loadFile();
 83+ return $this->mFile->isLocal();
 84+ }
 85+
 86+ public function getFile() {
 87+ $this->loadFile();
 88+ return $this->mFile;
 89+ }
 90+
 91+ public function getDuplicates() {
 92+ $this->loadFile();
 93+ if ( !is_null( $this->mDupes ) ) {
 94+ return $this->mDupes;
 95+ }
 96+ $hash = $this->mFile->getSha1();
 97+ if ( !( $hash ) ) {
 98+ return $this->mDupes = array();
 99+ }
 100+ $dupes = RepoGroup::singleton()->findBySha1( $hash );
 101+ // Remove duplicates with self and non matching file sizes
 102+ $self = $this->mFile->getRepoName() . ':' . $this->mFile->getName();
 103+ $size = $this->mFile->getSize();
 104+ foreach ( $dupes as $index => $file ) {
 105+ $key = $file->getRepoName() . ':' . $file->getName();
 106+ if ( $key == $self ) {
 107+ unset( $dupes[$index] );
 108+ }
 109+ if ( $file->getSize() != $size ) {
 110+ unset( $dupes[$index] );
 111+ }
 112+ }
 113+ $this->mDupes = $dupes;
 114+ return $this->mDupes;
 115+ }
 116+
 117+ /**
 118+ * Override handling of action=purge
 119+ */
 120+ public function doPurge() {
 121+ $this->loadFile();
 122+ if ( $this->mFile->exists() ) {
 123+ wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
 124+ $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
 125+ $update->doUpdate();
 126+ $this->mFile->upgradeRow();
 127+ $this->mFile->purgeCache();
 128+ } else {
 129+ wfDebug( 'ImagePage::doPurge no image for ' . $this->mFile->getName() . "; limiting purge to cache only\n" );
 130+ // even if the file supposedly doesn't exist, force any cached information
 131+ // to be updated (in case the cached information is wrong)
 132+ $this->mFile->purgeCache();
 133+ }
 134+ parent::doPurge();
 135+ }
 136+}
Property changes on: trunk/phase3/includes/WikiFilePage.php
___________________________________________________________________
Added: svn:eol-style
1137 + native
Added: svn:keywords
2138 + Author Date Id Revision
Index: trunk/phase3/includes/parser/ParserCache.php
@@ -181,9 +181,6 @@
182182 wfDebug( "ParserOutput key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
183183 $value = false;
184184 } else {
185 - if ( isset( $value->mTimestamp ) ) {
186 - $article->mTimestamp = $value->mTimestamp;
187 - }
188185 wfIncrStats( "pcache_hit" );
189186 }
190187
Index: trunk/phase3/includes/ImagePage.php
@@ -1,7 +1,6 @@
22 <?php
3 -
43 /**
5 - * Special handling for image description pages
 4+ * Class for viewing MediaWiki file description pages
65 *
76 * @ingroup Media
87 */
@@ -10,7 +9,6 @@
1110 /**
1211 * @var File
1312 */
14 - private $img;
1513 private $displayImg;
1614 /**
1715 * @var FileRepo
@@ -19,21 +17,30 @@
2018 private $fileLoaded;
2119
2220 var $mExtraDescription = false;
23 - var $dupes;
2421
25 - function __construct( $title ) {
26 - parent::__construct( $title );
27 - $this->dupes = null;
28 - $this->repo = null;
 22+ protected function newPage( Title $title ) {
 23+ // Overload mPage with a file-specific page
 24+ return new WikiFilePage( $title );
2925 }
3026
3127 /**
 28+ * Constructor from a page id
 29+ * @param $id Int article ID to load
 30+ */
 31+ public static function newFromID( $id ) {
 32+ $t = Title::newFromID( $id );
 33+ # @todo FIXME: Doesn't inherit right
 34+ return $t == null ? null : new self( $t );
 35+ # return $t == null ? null : new static( $t ); // PHP 5.3
 36+ }
 37+
 38+ /**
3239 * @param $file File:
3340 * @return void
3441 */
3542 public function setFile( $file ) {
 43+ $this->mPage->setFile( $file );
3644 $this->displayImg = $file;
37 - $this->img = $file;
3845 $this->fileLoaded = true;
3946 }
4047
@@ -43,18 +50,19 @@
4451 }
4552 $this->fileLoaded = true;
4653
47 - $this->displayImg = $this->img = false;
48 - wfRunHooks( 'ImagePageFindFile', array( $this, &$this->img, &$this->displayImg ) );
49 - if ( !$this->img ) {
50 - $this->img = wfFindFile( $this->mTitle );
51 - if ( !$this->img ) {
52 - $this->img = wfLocalFile( $this->mTitle );
 54+ $this->displayImg = $img = false;
 55+ wfRunHooks( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) );
 56+ if ( !$img ) { // not set by hook?
 57+ $img = wfFindFile( $this->getTitle() );
 58+ if ( !$img ) {
 59+ $img = wfLocalFile( $this->getTitle() );
5360 }
5461 }
55 - if ( !$this->displayImg ) {
56 - $this->displayImg = $this->img;
 62+ $this->mPage->setFile( $img );
 63+ if ( !$this->displayImg ) { // not set by hook?
 64+ $this->displayImg = $img;
5765 }
58 - $this->repo = $this->img->getRepo();
 66+ $this->repo = $img->getRepo();
5967 }
6068
6169 /**
@@ -73,14 +81,14 @@
7482 $diff = $wgRequest->getVal( 'diff' );
7583 $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
7684
77 - if ( $this->mTitle->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) ) {
 85+ if ( $this->getTitle()->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) ) {
7886 return parent::view();
7987 }
8088
8189 $this->loadFile();
8290
83 - if ( $this->mTitle->getNamespace() == NS_FILE && $this->img->getRedirected() ) {
84 - if ( $this->mTitle->getDBkey() == $this->img->getName() || isset( $diff ) ) {
 91+ if ( $this->getTitle()->getNamespace() == NS_FILE && $this->mPage->getFile()->getRedirected() ) {
 92+ if ( $this->getTitle()->getDBkey() == $this->mPage->getFile()->getName() || isset( $diff ) ) {
8593 // mTitle is the same as the redirect target so ask Article
8694 // to perform the redirect for us.
8795 $wgRequest->setVal( 'diffonly', 'true' );
@@ -88,10 +96,10 @@
8997 } else {
9098 // mTitle is not the same as the redirect target so it is
9199 // probably the redirect page itself. Fake the redirect symbol
92 - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
93 - $wgOut->addHTML( $this->viewRedirect( Title::makeTitle( NS_FILE, $this->img->getName() ),
 100+ $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
 101+ $wgOut->addHTML( $this->viewRedirect( Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ),
94102 /* $appendSubtitle */ true, /* $forceKnown */ true ) );
95 - $this->viewUpdates();
 103+ $this->mPage->viewUpdates();
96104 return;
97105 }
98106 }
@@ -115,10 +123,10 @@
116124 }
117125
118126 # No need to display noarticletext, we use our own message, output in openShowImage()
119 - if ( $this->getID() ) {
 127+ if ( $this->mPage->getID() ) {
120128 # When $wgBetterDirectionality is enabled, NS_FILE is in the user language,
121129 # but this section (the actual wikitext) should be in page content language
122 - $pageLang = $this->mTitle->getPageLanguage();
 130+ $pageLang = $this->getTitle()->getPageLanguage();
123131 $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
124132 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
125133 'class' => 'mw-content-'.$pageLang->getDir() ) ) );
@@ -127,8 +135,8 @@
128136 } else {
129137 # Just need to set the right headers
130138 $wgOut->setArticleFlag( true );
131 - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
132 - $this->viewUpdates();
 139+ $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
 140+ $this->mPage->viewUpdates();
133141 }
134142
135143 # Show shared description, if needed
@@ -175,82 +183,11 @@
176184 // always show the local local Filepage.css, bug 29277
177185 $wgOut->addModuleStyles( 'filepage' );
178186 }
179 -
180 - public function getRedirectTarget() {
181 - $this->loadFile();
182 - if ( $this->img->isLocal() ) {
183 - return parent::getRedirectTarget();
184 - }
185 - // Foreign image page
186 - $from = $this->img->getRedirected();
187 - $to = $this->img->getName();
188 - if ( $from == $to ) {
189 - return null;
190 - }
191 - return $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
192 - }
193187
194 - public function followRedirect() {
195 - $this->loadFile();
196 - if ( $this->img->isLocal() ) {
197 - return parent::followRedirect();
198 - }
199 - $from = $this->img->getRedirected();
200 - $to = $this->img->getName();
201 - if ( $from == $to ) {
202 - return false;
203 - }
204 - return Title::makeTitle( NS_FILE, $to );
205 - }
206 -
207 - public function isRedirect( $text = false ) {
208 - $this->loadFile();
209 - if ( $this->img->isLocal() ) {
210 - return parent::isRedirect( $text );
211 - }
212 -
213 - return (bool)$this->img->getRedirected();
214 - }
215 -
216 - public function isLocal() {
217 - $this->loadFile();
218 - return $this->img->isLocal();
219 - }
220 -
221 - public function getFile() {
222 - $this->loadFile();
223 - return $this->img;
224 - }
225 -
226188 public function getDisplayedFile() {
227189 $this->loadFile();
228190 return $this->displayImg;
229191 }
230 -
231 - public function getDuplicates() {
232 - $this->loadFile();
233 - if ( !is_null( $this->dupes ) ) {
234 - return $this->dupes;
235 - }
236 - $hash = $this->img->getSha1();
237 - if ( !( $hash ) ) {
238 - return $this->dupes = array();
239 - }
240 - $dupes = RepoGroup::singleton()->findBySha1( $hash );
241 - // Remove duplicates with self and non matching file sizes
242 - $self = $this->img->getRepoName() . ':' . $this->img->getName();
243 - $size = $this->img->getSize();
244 - foreach ( $dupes as $index => $file ) {
245 - $key = $file->getRepoName() . ':' . $file->getName();
246 - if ( $key == $self ) {
247 - unset( $dupes[$index] );
248 - }
249 - if ( $file->getSize() != $size ) {
250 - unset( $dupes[$index] );
251 - }
252 - }
253 - return $this->dupes = $dupes;
254 - }
255192
256193 /**
257194 * Create the TOC
@@ -309,7 +246,7 @@
310247 */
311248 public function getContent() {
312249 $this->loadFile();
313 - if ( $this->img && !$this->img->isLocal() && 0 == $this->getID() ) {
 250+ if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
314251 return '';
315252 }
316253 return parent::getContent();
@@ -422,13 +359,13 @@
423360 if ( $page > 1 ) {
424361 $label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false );
425362 $link = $sk->link(
426 - $this->mTitle,
 363+ $this->getTitle(),
427364 $label,
428365 array(),
429366 array( 'page' => $page - 1 ),
430367 array( 'known', 'noclasses' )
431368 );
432 - $thumb1 = $sk->makeThumbLinkObj( $this->mTitle, $this->displayImg, $link, $label, 'none',
 369+ $thumb1 = $sk->makeThumbLinkObj( $this->getTitle(), $this->displayImg, $link, $label, 'none',
433370 array( 'page' => $page - 1 ) );
434371 } else {
435372 $thumb1 = '';
@@ -437,13 +374,13 @@
438375 if ( $page < $count ) {
439376 $label = wfMsg( 'imgmultipagenext' );
440377 $link = $sk->link(
441 - $this->mTitle,
 378+ $this->getTitle(),
442379 $label,
443380 array(),
444381 array( 'page' => $page + 1 ),
445382 array( 'known', 'noclasses' )
446383 );
447 - $thumb2 = $sk->makeThumbLinkObj( $this->mTitle, $this->displayImg, $link, $label, 'none',
 384+ $thumb2 = $sk->makeThumbLinkObj( $this->getTitle(), $this->displayImg, $link, $label, 'none',
448385 array( 'page' => $page + 1 ) );
449386 } else {
450387 $thumb2 = '';
@@ -521,7 +458,7 @@
522459 $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
523460 $nofile = array(
524461 'filepage-nofile-link',
525 - $uploadTitle->getFullURL( array( 'wpDestFile' => $this->img->getName() ) )
 462+ $uploadTitle->getFullURL( array( 'wpDestFile' => $this->mPage->getFile()->getName() ) )
526463 );
527464 } else {
528465 $nofile = 'filepage-nofile';
@@ -569,16 +506,16 @@
570507
571508 $this->loadFile();
572509
573 - $descUrl = $this->img->getDescriptionUrl();
574 - $descText = $this->img->getDescriptionText();
 510+ $descUrl = $this->mPage->getFile()->getDescriptionUrl();
 511+ $descText = $this->mPage->getFile()->getDescriptionText();
575512
576513 /* Add canonical to head if there is no local page for this shared file */
577 - if( $descUrl && $this->getID() == 0 ) {
 514+ if( $descUrl && $this->mPage->getID() == 0 ) {
578515 $wgOut->addLink( array( 'rel' => 'canonical', 'href' => $descUrl ) );
579516 }
580517
581518 $wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
582 - $repo = $this->img->getRepo()->getDisplayName();
 519+ $repo = $this->mPage->getFile()->getRepo()->getDisplayName();
583520
584521 if ( $descUrl && $descText && wfMsgNoTrans( 'sharedupload-desc-here' ) !== '-' ) {
585522 $wgOut->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) );
@@ -597,7 +534,7 @@
598535 $this->loadFile();
599536 $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
600537 return $uploadTitle->getFullURL( array(
601 - 'wpDestFile' => $this->img->getName(),
 538+ 'wpDestFile' => $this->mPage->getFile()->getName(),
602539 'wpForReUpload' => 1
603540 ) );
604541 }
@@ -614,7 +551,7 @@
615552 }
616553
617554 $this->loadFile();
618 - if ( !$this->img->isLocal() ) {
 555+ if ( !$this->mPage->getFile()->isLocal() ) {
619556 return;
620557 }
621558
@@ -623,7 +560,7 @@
624561 $wgOut->addHTML( "<br /><ul>\n" );
625562
626563 # "Upload a new version of this file" link
627 - if ( UploadBase::userCanReUpload( $wgUser, $this->img->name ) ) {
 564+ if ( UploadBase::userCanReUpload( $wgUser, $this->mPage->getFile()->name ) ) {
628565 $ulink = $sk->makeExternalLink( $this->getUploadUrl(), wfMsg( 'uploadnewversion-linktext' ) );
629566 $wgOut->addHTML( "<li id=\"mw-imagepage-reupload-link\"><div class=\"plainlinks\">{$ulink}</div></li>\n" );
630567 }
@@ -631,7 +568,7 @@
632569 # External editing link
633570 if ( $wgUseExternalEditor ) {
634571 $elink = $sk->link(
635 - $this->mTitle,
 572+ $this->getTitle(),
636573 wfMsgHtml( 'edit-externally' ),
637574 array(),
638575 array(
@@ -665,11 +602,11 @@
666603 $wgOut->addHTML( $pager->getBody() );
667604 $wgOut->preventClickjacking( $pager->getPreventClickjacking() );
668605
669 - $this->img->resetHistory(); // free db resources
 606+ $this->mPage->getFile()->resetHistory(); // free db resources
670607
671608 # Exist check because we don't want to show this on pages where an image
672609 # doesn't exist along with the noimage message, that would suck. -ævar
673 - if ( $this->img->exists() ) {
 610+ if ( $this->mPage->getFile()->exists() ) {
674611 $this->uploadLinksBox();
675612 }
676613 }
@@ -691,7 +628,7 @@
692629
693630 $limit = 100;
694631
695 - $res = $this->queryImageLinks( $this->mTitle->getDbKey(), $limit + 1);
 632+ $res = $this->queryImageLinks( $this->getTitle()->getDbKey(), $limit + 1);
696633 $rows = array();
697634 $redirects = array();
698635 foreach ( $res as $row ) {
@@ -729,7 +666,7 @@
730667 // More links than the limit. Add a link to [[Special:Whatlinkshere]]
731668 $wgOut->addWikiMsg( 'linkstoimage-more',
732669 $wgLang->formatNum( $limit ),
733 - $this->mTitle->getPrefixedDBkey()
 670+ $this->getTitle()->getPrefixedDBkey()
734671 );
735672 }
736673
@@ -786,7 +723,7 @@
787724
788725 // Add a links to [[Special:Whatlinkshere]]
789726 if ( $count > $limit ) {
790 - $wgOut->addWikiMsg( 'morelinkstoimage', $this->mTitle->getPrefixedDBkey() );
 727+ $wgOut->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() );
791728 }
792729 $wgOut->addHTML( Html::closeElement( 'div' ) . "\n" );
793730 }
@@ -796,14 +733,14 @@
797734
798735 $this->loadFile();
799736
800 - $dupes = $this->getDuplicates();
 737+ $dupes = $this->mPage->getDuplicates();
801738 if ( count( $dupes ) == 0 ) {
802739 return;
803740 }
804741
805742 $wgOut->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
806743 $wgOut->addWikiMsg( 'duplicatesoffile',
807 - $wgLang->formatNum( count( $dupes ) ), $this->mTitle->getDBkey()
 744+ $wgLang->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey()
808745 );
809746 $wgOut->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
810747
@@ -833,19 +770,19 @@
834771 */
835772 public function delete() {
836773 global $wgUploadMaintenance;
837 - if ( $wgUploadMaintenance && $this->mTitle && $this->mTitle->getNamespace() == NS_FILE ) {
 774+ if ( $wgUploadMaintenance && $this->getTitle() && $this->getTitle()->getNamespace() == NS_FILE ) {
838775 global $wgOut;
839776 $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", array( 'filedelete-maintenance' ) );
840777 return;
841778 }
842779
843780 $this->loadFile();
844 - if ( !$this->img->exists() || !$this->img->isLocal() || $this->img->getRedirected() ) {
 781+ if ( !$this->mPage->getFile()->exists() || !$this->mPage->getFile()->isLocal() || $this->mPage->getFile()->getRedirected() ) {
845782 // Standard article deletion
846783 parent::delete();
847784 return;
848785 }
849 - $deleter = new FileDeleteForm( $this->img );
 786+ $deleter = new FileDeleteForm( $this->mPage->getFile() );
850787 $deleter->execute();
851788 }
852789
@@ -854,7 +791,7 @@
855792 */
856793 public function revert() {
857794 $this->loadFile();
858 - $reverter = new FileRevertForm( $this->img );
 795+ $reverter = new FileRevertForm( $this->mPage->getFile() );
859796 $reverter->execute();
860797 }
861798
@@ -863,17 +800,17 @@
864801 */
865802 public function doPurge() {
866803 $this->loadFile();
867 - if ( $this->img->exists() ) {
868 - wfDebug( 'ImagePage::doPurge purging ' . $this->img->getName() . "\n" );
869 - $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
 804+ if ( $this->mPage->getFile()->exists() ) {
 805+ wfDebug( 'ImagePage::doPurge purging ' . $this->mPage->getFile()->getName() . "\n" );
 806+ $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
870807 $update->doUpdate();
871 - $this->img->upgradeRow();
872 - $this->img->purgeCache();
 808+ $this->mPage->getFile()->upgradeRow();
 809+ $this->mPage->getFile()->purgeCache();
873810 } else {
874 - wfDebug( 'ImagePage::doPurge no image for ' . $this->img->getName() . "; limiting purge to cache only\n" );
 811+ wfDebug( 'ImagePage::doPurge no image for ' . $this->mPage->getFile()->getName() . "; limiting purge to cache only\n" );
875812 // even if the file supposedly doesn't exist, force any cached information
876813 // to be updated (in case the cached information is wrong)
877 - $this->img->purgeCache();
 814+ $this->mPage->getFile()->purgeCache();
878815 }
879816 parent::doPurge();
880817 }
Index: trunk/phase3/includes/actions/CreditsAction.php
@@ -83,7 +83,7 @@
8484 * @param $article Article object
8585 * @return String HTML
8686 */
87 - protected static function getAuthor( Article $article ) {
 87+ protected static function getAuthor( Page $article ) {
8888 global $wgLang;
8989
9090 $user = User::newFromId( $article->getUser() );
Index: trunk/phase3/includes/AutoLoader.php
@@ -159,6 +159,7 @@
160160 'MWNamespace' => 'includes/Namespace.php',
161161 'OldChangesList' => 'includes/ChangesList.php',
162162 'OutputPage' => 'includes/OutputPage.php',
 163+ 'Page' => 'includes/WikiPage.php',
163164 'PageHistory' => 'includes/HistoryPage.php',
164165 'PageHistoryPager' => 'includes/HistoryPage.php',
165166 'PageQueryPage' => 'includes/PageQueryPage.php',
@@ -233,10 +234,13 @@
234235 'WebRequest' => 'includes/WebRequest.php',
235236 'WebRequestUpload' => 'includes/WebRequest.php',
236237 'WebResponse' => 'includes/WebResponse.php',
 238+ 'WikiCategoryPage' => 'includes/WikiCategoryPage.php',
237239 'WikiError' => 'includes/WikiError.php',
238240 'WikiErrorMsg' => 'includes/WikiError.php',
239241 'WikiExporter' => 'includes/Export.php',
 242+ 'WikiFilePage' => 'includes/WikiFilePage.php',
240243 'WikiImporter' => 'includes/Import.php',
 244+ 'WikiPage' => 'includes/WikiPage.php',
241245 'WikiRevision' => 'includes/Import.php',
242246 'WikiMap' => 'includes/WikiMap.php',
243247 'WikiReference' => 'includes/WikiMap.php',
Index: trunk/phase3/includes/Wiki.php
@@ -426,7 +426,7 @@
427427 *
428428 * @param $article Article
429429 */
430 - private function performAction( Article $article ) {
 430+ private function performAction( Page $article ) {
431431 global $wgSquidMaxage, $wgUseExternalEditor;
432432
433433 wfProfileIn( __METHOD__ );
Index: trunk/phase3/includes/RawPage.php
@@ -23,7 +23,7 @@
2424 var $mSmaxage, $mMaxage;
2525 var $mContentType, $mExpandTemplates;
2626
27 - function __construct( Article $article, $request = false ) {
 27+ function __construct( Page $article, $request = false ) {
2828 global $wgRequest, $wgSquidMaxage, $wgJsMimeType, $wgGroupPermissions;
2929
3030 $allowedCTypes = array( 'text/x-wiki', $wgJsMimeType, 'text/css', 'application/x-zope-edit' );
Index: trunk/phase3/includes/WikiPage.php
@@ -0,0 +1,2440 @@
 2+<?php
 3+/**
 4+ * Abstract class for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
 5+ */
 6+abstract class Page {}
 7+
 8+/**
 9+ * Class representing a MediaWiki article and history.
 10+ *
 11+ * Some fields are public only for backwards-compatibility. Use accessors.
 12+ * In the past, this class was part of Article.php and everything was public.
 13+ *
 14+ * @TODO: dependency inject $wgUser as an argument to functions
 15+ *
 16+ * @internal documentation reviewed 15 Mar 2010
 17+ */
 18+class WikiPage extends Page {
 19+ /**@{{
 20+ * @protected
 21+ * Fields are public for backwards-compatibility. Use accessors.
 22+ * In the past, this class was part of Article.php and everything was public.
 23+ */
 24+ public $mTitle = null; // !< Title object
 25+ public $mCounter = -1; // !< Not loaded
 26+ public $mDataLoaded = false; // !<
 27+ public $mIsRedirect = false; // !<
 28+ public $mLatest = false; // !<
 29+ public $mPreparedEdit = false; // !<
 30+ public $mRedirectTarget = null; // !< Title object
 31+ public $mLastRevision = null; // !< Revision object
 32+ public $mTimestamp = ''; // !<
 33+ public $mTouched = '19700101000000'; // !<
 34+ /**@}}*/
 35+
 36+ /**
 37+ * @protected
 38+ * @var ParserOptions: ParserOptions object for $wgUser articles
 39+ */
 40+ public $mParserOptions;
 41+
 42+ /**
 43+ * Constructor and clear the article
 44+ * @param $title Title Reference to a Title object.
 45+ */
 46+ public function __construct( Title $title ) {
 47+ $this->mTitle = $title;
 48+ }
 49+
 50+ /**
 51+ * Constructor from a page id
 52+ * @param $id Int article ID to load
 53+ */
 54+ public static function newFromID( $id ) {
 55+ $t = Title::newFromID( $id );
 56+ # @todo FIXME: Doesn't inherit right
 57+ return $t == null ? null : new self( $t );
 58+ # return $t == null ? null : new static( $t ); // PHP 5.3
 59+ }
 60+
 61+ /**
 62+ * If this page is a redirect, get its target
 63+ *
 64+ * The target will be fetched from the redirect table if possible.
 65+ * If this page doesn't have an entry there, call insertRedirect()
 66+ * @return Title|mixed object, or null if this page is not a redirect
 67+ */
 68+ public function getRedirectTarget() {
 69+ if ( !$this->mTitle->isRedirect() ) {
 70+ return null;
 71+ }
 72+
 73+ if ( $this->mRedirectTarget !== null ) {
 74+ return $this->mRedirectTarget;
 75+ }
 76+
 77+ # Query the redirect table
 78+ $dbr = wfGetDB( DB_SLAVE );
 79+ $row = $dbr->selectRow( 'redirect',
 80+ array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
 81+ array( 'rd_from' => $this->getID() ),
 82+ __METHOD__
 83+ );
 84+
 85+ // rd_fragment and rd_interwiki were added later, populate them if empty
 86+ if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
 87+ return $this->mRedirectTarget = Title::makeTitle(
 88+ $row->rd_namespace, $row->rd_title,
 89+ $row->rd_fragment, $row->rd_interwiki );
 90+ }
 91+
 92+ # This page doesn't have an entry in the redirect table
 93+ return $this->mRedirectTarget = $this->insertRedirect();
 94+ }
 95+
 96+ /**
 97+ * Insert an entry for this page into the redirect table.
 98+ *
 99+ * Don't call this function directly unless you know what you're doing.
 100+ * @return Title object or null if not a redirect
 101+ */
 102+ public function insertRedirect() {
 103+ // recurse through to only get the final target
 104+ $retval = Title::newFromRedirectRecurse( $this->getRawText() );
 105+ if ( !$retval ) {
 106+ return null;
 107+ }
 108+ $this->insertRedirectEntry( $retval );
 109+ return $retval;
 110+ }
 111+
 112+ /**
 113+ * Insert or update the redirect table entry for this page to indicate
 114+ * it redirects to $rt .
 115+ * @param $rt Title redirect target
 116+ */
 117+ public function insertRedirectEntry( $rt ) {
 118+ $dbw = wfGetDB( DB_MASTER );
 119+ $dbw->replace( 'redirect', array( 'rd_from' ),
 120+ array(
 121+ 'rd_from' => $this->getID(),
 122+ 'rd_namespace' => $rt->getNamespace(),
 123+ 'rd_title' => $rt->getDBkey(),
 124+ 'rd_fragment' => $rt->getFragment(),
 125+ 'rd_interwiki' => $rt->getInterwiki(),
 126+ ),
 127+ __METHOD__
 128+ );
 129+ }
 130+
 131+ /**
 132+ * Get the Title object or URL this page redirects to
 133+ *
 134+ * @return mixed false, Title of in-wiki target, or string with URL
 135+ */
 136+ public function followRedirect() {
 137+ return $this->getRedirectURL( $this->getRedirectTarget() );
 138+ }
 139+
 140+ /**
 141+ * Get the Title object or URL to use for a redirect. We use Title
 142+ * objects for same-wiki, non-special redirects and URLs for everything
 143+ * else.
 144+ * @param $rt Title Redirect target
 145+ * @return mixed false, Title object of local target, or string with URL
 146+ */
 147+ public function getRedirectURL( $rt ) {
 148+ if ( $rt ) {
 149+ if ( $rt->getInterwiki() != '' ) {
 150+ if ( $rt->isLocal() ) {
 151+ // Offsite wikis need an HTTP redirect.
 152+ //
 153+ // This can be hard to reverse and may produce loops,
 154+ // so they may be disabled in the site configuration.
 155+ $source = $this->mTitle->getFullURL( 'redirect=no' );
 156+ return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
 157+ }
 158+ } else {
 159+ if ( $rt->getNamespace() == NS_SPECIAL ) {
 160+ // Gotta handle redirects to special pages differently:
 161+ // Fill the HTTP response "Location" header and ignore
 162+ // the rest of the page we're on.
 163+ //
 164+ // This can be hard to reverse, so they may be disabled.
 165+ if ( $rt->isSpecial( 'Userlogout' ) ) {
 166+ // rolleyes
 167+ } else {
 168+ return $rt->getFullURL();
 169+ }
 170+ }
 171+
 172+ return $rt;
 173+ }
 174+ }
 175+
 176+ // No or invalid redirect
 177+ return false;
 178+ }
 179+
 180+ /**
 181+ * Get the title object of the article
 182+ * @return Title object of this page
 183+ */
 184+ public function getTitle() {
 185+ return $this->mTitle;
 186+ }
 187+
 188+ /**
 189+ * Clear the object
 190+ */
 191+ public function clear() {
 192+ $this->mDataLoaded = false;
 193+
 194+ $this->mCounter = -1; # Not loaded
 195+ $this->mRedirectTarget = null; # Title object if set
 196+ $this->mLastRevision = null; # Latest revision
 197+ $this->mTimestamp = '';
 198+ $this->mTouched = '19700101000000';
 199+ $this->mIsRedirect = false;
 200+ $this->mLatest = false;
 201+ $this->mPreparedEdit = false;
 202+ }
 203+
 204+ /**
 205+ * Get the text that needs to be saved in order to undo all revisions
 206+ * between $undo and $undoafter. Revisions must belong to the same page,
 207+ * must exist and must not be deleted
 208+ * @param $undo Revision
 209+ * @param $undoafter Revision Must be an earlier revision than $undo
 210+ * @return mixed string on success, false on failure
 211+ */
 212+ public function getUndoText( Revision $undo, Revision $undoafter = null ) {
 213+ $cur_text = $this->getRawText();
 214+ if ( $cur_text === false ) {
 215+ return false; // no page
 216+ }
 217+ $undo_text = $undo->getText();
 218+ $undoafter_text = $undoafter->getText();
 219+
 220+ if ( $cur_text == $undo_text ) {
 221+ # No use doing a merge if it's just a straight revert.
 222+ return $undoafter_text;
 223+ }
 224+
 225+ $undone_text = '';
 226+
 227+ if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
 228+ return false;
 229+ }
 230+
 231+ return $undone_text;
 232+ }
 233+
 234+ /**
 235+ * Return the list of revision fields that should be selected to create
 236+ * a new page.
 237+ */
 238+ public static function selectFields() {
 239+ return array(
 240+ 'page_id',
 241+ 'page_namespace',
 242+ 'page_title',
 243+ 'page_restrictions',
 244+ 'page_counter',
 245+ 'page_is_redirect',
 246+ 'page_is_new',
 247+ 'page_random',
 248+ 'page_touched',
 249+ 'page_latest',
 250+ 'page_len',
 251+ );
 252+ }
 253+
 254+ /**
 255+ * Fetch a page record with the given conditions
 256+ * @param $dbr DatabaseBase object
 257+ * @param $conditions Array
 258+ * @return mixed Database result resource, or false on failure
 259+ */
 260+ protected function pageData( $dbr, $conditions ) {
 261+ $fields = self::selectFields();
 262+
 263+ wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
 264+
 265+ $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__ );
 266+
 267+ wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
 268+
 269+ return $row;
 270+ }
 271+
 272+ /**
 273+ * Fetch a page record matching the Title object's namespace and title
 274+ * using a sanitized title string
 275+ *
 276+ * @param $dbr DatabaseBase object
 277+ * @param $title Title object
 278+ * @return mixed Database result resource, or false on failure
 279+ */
 280+ protected function pageDataFromTitle( $dbr, $title ) {
 281+ return $this->pageData( $dbr, array(
 282+ 'page_namespace' => $title->getNamespace(),
 283+ 'page_title' => $title->getDBkey() ) );
 284+ }
 285+
 286+ /**
 287+ * Fetch a page record matching the requested ID
 288+ *
 289+ * @param $dbr DatabaseBase
 290+ * @param $id Integer
 291+ * @return mixed Database result resource, or false on failure
 292+ */
 293+ protected function pageDataFromId( $dbr, $id ) {
 294+ return $this->pageData( $dbr, array( 'page_id' => $id ) );
 295+ }
 296+
 297+ /**
 298+ * Set the general counter, title etc data loaded from
 299+ * some source.
 300+ *
 301+ * @param $data Object|String $res->fetchObject() object or the string "fromdb" to reload
 302+ */
 303+ public function loadPageData( $data = 'fromdb' ) {
 304+ if ( $data === 'fromdb' ) {
 305+ $dbr = wfGetDB( DB_SLAVE );
 306+ $data = $this->pageDataFromTitle( $dbr, $this->mTitle );
 307+ }
 308+
 309+ $lc = LinkCache::singleton();
 310+
 311+ if ( $data ) {
 312+ $lc->addGoodLinkObj( $data->page_id, $this->mTitle, $data->page_len, $data->page_is_redirect, $data->page_latest );
 313+
 314+ $this->mTitle->mArticleID = intval( $data->page_id );
 315+
 316+ # Old-fashioned restrictions
 317+ $this->mTitle->loadRestrictions( $data->page_restrictions );
 318+
 319+ $this->mCounter = intval( $data->page_counter );
 320+ $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
 321+ $this->mIsRedirect = intval( $data->page_is_redirect );
 322+ $this->mLatest = intval( $data->page_latest );
 323+ } else {
 324+ $lc->addBadLinkObj( $this->mTitle );
 325+ $this->mTitle->mArticleID = 0;
 326+ }
 327+
 328+ $this->mDataLoaded = true;
 329+ }
 330+
 331+ /**
 332+ * @return int Page ID
 333+ */
 334+ public function getID() {
 335+ return $this->mTitle->getArticleID();
 336+ }
 337+
 338+ /**
 339+ * @return bool Whether or not the page exists in the database
 340+ */
 341+ public function exists() {
 342+ return $this->getId() > 0;
 343+ }
 344+
 345+ /**
 346+ * Check if this page is something we're going to be showing
 347+ * some sort of sensible content for. If we return false, page
 348+ * views (plain action=view) will return an HTTP 404 response,
 349+ * so spiders and robots can know they're following a bad link.
 350+ *
 351+ * @return bool
 352+ */
 353+ public function hasViewableContent() {
 354+ return $this->exists() || $this->mTitle->isAlwaysKnown();
 355+ }
 356+
 357+ /**
 358+ * @return int The view count for the page
 359+ */
 360+ public function getCount() {
 361+ if ( -1 == $this->mCounter ) {
 362+ $id = $this->getID();
 363+
 364+ if ( $id == 0 ) {
 365+ $this->mCounter = 0;
 366+ } else {
 367+ $dbr = wfGetDB( DB_SLAVE );
 368+ $this->mCounter = $dbr->selectField( 'page',
 369+ 'page_counter',
 370+ array( 'page_id' => $id ),
 371+ __METHOD__
 372+ );
 373+ }
 374+ }
 375+
 376+ return $this->mCounter;
 377+ }
 378+
 379+ /**
 380+ * Determine whether a page would be suitable for being counted as an
 381+ * article in the site_stats table based on the title & its content
 382+ *
 383+ * @param $editInfo Object or false: object returned by prepareTextForEdit(),
 384+ * if false, the current database state will be used
 385+ * @return Boolean
 386+ */
 387+ public function isCountable( $editInfo = false ) {
 388+ global $wgArticleCountMethod;
 389+
 390+ if ( !$this->mTitle->isContentPage() ) {
 391+ return false;
 392+ }
 393+
 394+ $text = $editInfo ? $editInfo->pst : false;
 395+
 396+ if ( $this->isRedirect( $text ) ) {
 397+ return false;
 398+ }
 399+
 400+ switch ( $wgArticleCountMethod ) {
 401+ case 'any':
 402+ return true;
 403+ case 'comma':
 404+ if ( $text === false ) {
 405+ $text = $this->getRawText();
 406+ }
 407+ return strpos( $text, ',' ) !== false;
 408+ case 'link':
 409+ if ( $editInfo ) {
 410+ // ParserOutput::getLinks() is a 2D array of page links, so
 411+ // to be really correct we would need to recurse in the array
 412+ // but the main array should only have items in it if there are
 413+ // links.
 414+ return (bool)count( $editInfo->output->getLinks() );
 415+ } else {
 416+ return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
 417+ array( 'pl_from' => $this->getId() ), __METHOD__ );
 418+ }
 419+ }
 420+ }
 421+
 422+ /**
 423+ * Tests if the article text represents a redirect
 424+ *
 425+ * @param $text mixed string containing article contents, or boolean
 426+ * @return bool
 427+ */
 428+ public function isRedirect( $text = false ) {
 429+ if ( $text === false ) {
 430+ if ( !$this->mDataLoaded ) {
 431+ $this->loadPageData();
 432+ }
 433+
 434+ return (bool)$this->mIsRedirect;
 435+ } else {
 436+ return Title::newFromRedirect( $text ) !== null;
 437+ }
 438+ }
 439+
 440+ /**
 441+ * Loads everything except the text
 442+ * This isn't necessary for all uses, so it's only done if needed.
 443+ */
 444+ protected function loadLastEdit() {
 445+ if ( $this->mLastRevision !== null ) {
 446+ return; // already loaded
 447+ }
 448+
 449+ # New or non-existent articles have no user information
 450+ $id = $this->getID();
 451+ if ( 0 == $id ) {
 452+ return;
 453+ }
 454+
 455+ $revision = Revision::loadFromPageId( wfGetDB( DB_MASTER ), $id );
 456+ if ( $revision ) {
 457+ $this->setLastEdit( $revision );
 458+ }
 459+ }
 460+
 461+ /**
 462+ * Set the latest revision
 463+ */
 464+ protected function setLastEdit( Revision $revision ) {
 465+ $this->mLastRevision = $revision;
 466+ $this->mTimestamp = $revision->getTimestamp();
 467+ }
 468+
 469+ /**
 470+ * Get the latest revision
 471+ * @return Revision|false
 472+ */
 473+ public function getRevision() {
 474+ $this->loadLastEdit();
 475+ if ( $this->mLastRevision ) {
 476+ return $this->mLastRevision;
 477+ }
 478+ return false;
 479+ }
 480+
 481+ /**
 482+ * Get the text of the current revision. No side-effects...
 483+ *
 484+ * @param $audience Integer: one of:
 485+ * Revision::FOR_PUBLIC to be displayed to all users
 486+ * Revision::FOR_THIS_USER to be displayed to $wgUser
 487+ * Revision::RAW get the text regardless of permissions
 488+ * @return String|false The text of the current revision
 489+ */
 490+ public function getText( $audience = Revision::FOR_PUBLIC ) {
 491+ $this->loadLastEdit();
 492+ if ( $this->mLastRevision ) {
 493+ return $this->mLastRevision->getText( $audience );
 494+ }
 495+ return false;
 496+ }
 497+
 498+ /**
 499+ * Get the text of the current revision. No side-effects...
 500+ *
 501+ * @return String|false The text of the current revision
 502+ */
 503+ public function getRawText() {
 504+ $this->loadLastEdit();
 505+ if ( $this->mLastRevision ) {
 506+ return $this->mLastRevision->getRawText();
 507+ }
 508+ return false;
 509+ }
 510+
 511+ /**
 512+ * @return string MW timestamp of last article revision
 513+ */
 514+ public function getTimestamp() {
 515+ // Check if the field has been filled by ParserCache::get()
 516+ if ( !$this->mTimestamp ) {
 517+ $this->loadLastEdit();
 518+ }
 519+ return wfTimestamp( TS_MW, $this->mTimestamp );
 520+ }
 521+
 522+ /**
 523+ * Set the page timestamp (use only to avoid DB queries)
 524+ * @param $ts string MW timestamp of last article revision
 525+ * @return void
 526+ */
 527+ public function setTimestamp( $ts ) {
 528+ $this->mTimestamp = wfTimestamp( TS_MW, $ts );
 529+ }
 530+
 531+ /**
 532+ * @param $audience Integer: one of:
 533+ * Revision::FOR_PUBLIC to be displayed to all users
 534+ * Revision::FOR_THIS_USER to be displayed to $wgUser
 535+ * Revision::RAW get the text regardless of permissions
 536+ * @return int user ID for the user that made the last article revision
 537+ */
 538+ public function getUser( $audience = Revision::FOR_PUBLIC ) {
 539+ $this->loadLastEdit();
 540+ if ( $this->mLastRevision ) {
 541+ return $this->mLastRevision->getUser( $audience );
 542+ } else {
 543+ return -1;
 544+ }
 545+ }
 546+
 547+ /**
 548+ * @param $audience Integer: one of:
 549+ * Revision::FOR_PUBLIC to be displayed to all users
 550+ * Revision::FOR_THIS_USER to be displayed to $wgUser
 551+ * Revision::RAW get the text regardless of permissions
 552+ * @return string username of the user that made the last article revision
 553+ */
 554+ public function getUserText( $audience = Revision::FOR_PUBLIC ) {
 555+ $this->loadLastEdit();
 556+ if ( $this->mLastRevision ) {
 557+ return $this->mLastRevision->getUserText( $audience );
 558+ } else {
 559+ return '';
 560+ }
 561+ }
 562+
 563+ /**
 564+ * @param $audience Integer: one of:
 565+ * Revision::FOR_PUBLIC to be displayed to all users
 566+ * Revision::FOR_THIS_USER to be displayed to $wgUser
 567+ * Revision::RAW get the text regardless of permissions
 568+ * @return string Comment stored for the last article revision
 569+ */
 570+ public function getComment( $audience = Revision::FOR_PUBLIC ) {
 571+ $this->loadLastEdit();
 572+ if ( $this->mLastRevision ) {
 573+ return $this->mLastRevision->getComment( $audience );
 574+ } else {
 575+ return '';
 576+ }
 577+ }
 578+
 579+ /**
 580+ * Returns true if last revision was marked as "minor edit"
 581+ *
 582+ * @return boolean Minor edit indicator for the last article revision.
 583+ */
 584+ public function getMinorEdit() {
 585+ $this->loadLastEdit();
 586+ if ( $this->mLastRevision ) {
 587+ return $this->mLastRevision->isMinor();
 588+ } else {
 589+ return false;
 590+ }
 591+ }
 592+
 593+ /**
 594+ * Get a list of users who have edited this article, not including the user who made
 595+ * the most recent revision, which you can get from $article->getUser() if you want it
 596+ * @return UserArrayFromResult
 597+ */
 598+ public function getContributors() {
 599+ # @todo FIXME: This is expensive; cache this info somewhere.
 600+
 601+ $dbr = wfGetDB( DB_SLAVE );
 602+
 603+ if ( $dbr->implicitGroupby() ) {
 604+ $realNameField = 'user_real_name';
 605+ } else {
 606+ $realNameField = 'FIRST(user_real_name) AS user_real_name';
 607+ }
 608+
 609+ $tables = array( 'revision', 'user' );
 610+
 611+ $fields = array(
 612+ 'rev_user as user_id',
 613+ 'rev_user_text AS user_name',
 614+ $realNameField,
 615+ 'MAX(rev_timestamp) AS timestamp',
 616+ );
 617+
 618+ $conds = array( 'rev_page' => $this->getId() );
 619+
 620+ // The user who made the top revision gets credited as "this page was last edited by
 621+ // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
 622+ $user = $this->getUser();
 623+ if ( $user ) {
 624+ $conds[] = "rev_user != $user";
 625+ } else {
 626+ $conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
 627+ }
 628+
 629+ $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0"; // username hidden?
 630+
 631+ $jconds = array(
 632+ 'user' => array( 'LEFT JOIN', 'rev_user = user_id' ),
 633+ );
 634+
 635+ $options = array(
 636+ 'GROUP BY' => array( 'rev_user', 'rev_user_text' ),
 637+ 'ORDER BY' => 'timestamp DESC',
 638+ );
 639+
 640+ $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $jconds );
 641+ return new UserArrayFromResult( $res );
 642+ }
 643+
 644+ /**
 645+ * Should the parser cache be used?
 646+ *
 647+ * @return boolean
 648+ */
 649+ public function useParserCache( $oldid ) {
 650+ global $wgUser, $wgEnableParserCache;
 651+
 652+ return $wgEnableParserCache
 653+ && $wgUser->getStubThreshold() == 0
 654+ && $this->exists()
 655+ && empty( $oldid )
 656+ && !$this->mTitle->isCssOrJsPage()
 657+ && !$this->mTitle->isCssJsSubpage();
 658+ }
 659+
 660+ /**
 661+ * Perform the actions of a page purging
 662+ */
 663+ public function doPurge() {
 664+ global $wgUseSquid;
 665+
 666+ if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ){
 667+ return false;
 668+ }
 669+
 670+ // Invalidate the cache
 671+ $this->mTitle->invalidateCache();
 672+ $this->clear();
 673+
 674+ if ( $wgUseSquid ) {
 675+ // Commit the transaction before the purge is sent
 676+ $dbw = wfGetDB( DB_MASTER );
 677+ $dbw->commit();
 678+
 679+ // Send purge
 680+ $update = SquidUpdate::newSimplePurge( $this->mTitle );
 681+ $update->doUpdate();
 682+ }
 683+
 684+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
 685+ if ( $this->getID() == 0 ) {
 686+ $text = false;
 687+ } else {
 688+ $text = $this->getRawText();
 689+ }
 690+
 691+ MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
 692+ }
 693+ }
 694+
 695+ /**
 696+ * Insert a new empty page record for this article.
 697+ * This *must* be followed up by creating a revision
 698+ * and running $this->updateRevisionOn( ... );
 699+ * or else the record will be left in a funky state.
 700+ * Best if all done inside a transaction.
 701+ *
 702+ * @param $dbw DatabaseBase
 703+ * @return int The newly created page_id key, or false if the title already existed
 704+ * @private
 705+ */
 706+ public function insertOn( $dbw ) {
 707+ wfProfileIn( __METHOD__ );
 708+
 709+ $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
 710+ $dbw->insert( 'page', array(
 711+ 'page_id' => $page_id,
 712+ 'page_namespace' => $this->mTitle->getNamespace(),
 713+ 'page_title' => $this->mTitle->getDBkey(),
 714+ 'page_counter' => 0,
 715+ 'page_restrictions' => '',
 716+ 'page_is_redirect' => 0, # Will set this shortly...
 717+ 'page_is_new' => 1,
 718+ 'page_random' => wfRandom(),
 719+ 'page_touched' => $dbw->timestamp(),
 720+ 'page_latest' => 0, # Fill this in shortly...
 721+ 'page_len' => 0, # Fill this in shortly...
 722+ ), __METHOD__, 'IGNORE' );
 723+
 724+ $affected = $dbw->affectedRows();
 725+
 726+ if ( $affected ) {
 727+ $newid = $dbw->insertId();
 728+ $this->mTitle->resetArticleID( $newid );
 729+ }
 730+ wfProfileOut( __METHOD__ );
 731+
 732+ return $affected ? $newid : false;
 733+ }
 734+
 735+ /**
 736+ * Update the page record to point to a newly saved revision.
 737+ *
 738+ * @param $dbw DatabaseBase: object
 739+ * @param $revision Revision: For ID number, and text used to set
 740+ length and redirect status fields
 741+ * @param $lastRevision Integer: if given, will not overwrite the page field
 742+ * when different from the currently set value.
 743+ * Giving 0 indicates the new page flag should be set
 744+ * on.
 745+ * @param $lastRevIsRedirect Boolean: if given, will optimize adding and
 746+ * removing rows in redirect table.
 747+ * @return bool true on success, false on failure
 748+ * @private
 749+ */
 750+ public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
 751+ wfProfileIn( __METHOD__ );
 752+
 753+ $text = $revision->getText();
 754+ $rt = Title::newFromRedirectRecurse( $text );
 755+
 756+ $conditions = array( 'page_id' => $this->getId() );
 757+
 758+ if ( !is_null( $lastRevision ) ) {
 759+ # An extra check against threads stepping on each other
 760+ $conditions['page_latest'] = $lastRevision;
 761+ }
 762+
 763+ $dbw->update( 'page',
 764+ array( /* SET */
 765+ 'page_latest' => $revision->getId(),
 766+ 'page_touched' => $dbw->timestamp(),
 767+ 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
 768+ 'page_is_redirect' => $rt !== null ? 1 : 0,
 769+ 'page_len' => strlen( $text ),
 770+ ),
 771+ $conditions,
 772+ __METHOD__ );
 773+
 774+ $result = $dbw->affectedRows() != 0;
 775+ if ( $result ) {
 776+ $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
 777+ }
 778+
 779+ wfProfileOut( __METHOD__ );
 780+ return $result;
 781+ }
 782+
 783+ /**
 784+ * Add row to the redirect table if this is a redirect, remove otherwise.
 785+ *
 786+ * @param $dbw DatabaseBase
 787+ * @param $redirectTitle Title object pointing to the redirect target,
 788+ * or NULL if this is not a redirect
 789+ * @param $lastRevIsRedirect If given, will optimize adding and
 790+ * removing rows in redirect table.
 791+ * @return bool true on success, false on failure
 792+ * @private
 793+ */
 794+ public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
 795+ // Always update redirects (target link might have changed)
 796+ // Update/Insert if we don't know if the last revision was a redirect or not
 797+ // Delete if changing from redirect to non-redirect
 798+ $isRedirect = !is_null( $redirectTitle );
 799+
 800+ if ( !$isRedirect && !is_null( $lastRevIsRedirect ) && $lastRevIsRedirect === $isRedirect ) {
 801+ return true;
 802+ }
 803+
 804+ wfProfileIn( __METHOD__ );
 805+ if ( $isRedirect ) {
 806+ $this->insertRedirectEntry( $redirectTitle );
 807+ } else {
 808+ // This is not a redirect, remove row from redirect table
 809+ $where = array( 'rd_from' => $this->getId() );
 810+ $dbw->delete( 'redirect', $where, __METHOD__ );
 811+ }
 812+
 813+ if ( $this->getTitle()->getNamespace() == NS_FILE ) {
 814+ RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
 815+ }
 816+ wfProfileOut( __METHOD__ );
 817+
 818+ return ( $dbw->affectedRows() != 0 );
 819+ }
 820+
 821+ /**
 822+ * If the given revision is newer than the currently set page_latest,
 823+ * update the page record. Otherwise, do nothing.
 824+ *
 825+ * @param $dbw Database object
 826+ * @param $revision Revision object
 827+ * @return mixed
 828+ */
 829+ public function updateIfNewerOn( $dbw, $revision ) {
 830+ wfProfileIn( __METHOD__ );
 831+
 832+ $row = $dbw->selectRow(
 833+ array( 'revision', 'page' ),
 834+ array( 'rev_id', 'rev_timestamp', 'page_is_redirect' ),
 835+ array(
 836+ 'page_id' => $this->getId(),
 837+ 'page_latest=rev_id' ),
 838+ __METHOD__ );
 839+
 840+ if ( $row ) {
 841+ if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
 842+ wfProfileOut( __METHOD__ );
 843+ return false;
 844+ }
 845+ $prev = $row->rev_id;
 846+ $lastRevIsRedirect = (bool)$row->page_is_redirect;
 847+ } else {
 848+ # No or missing previous revision; mark the page as new
 849+ $prev = 0;
 850+ $lastRevIsRedirect = null;
 851+ }
 852+
 853+ $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
 854+
 855+ wfProfileOut( __METHOD__ );
 856+ return $ret;
 857+ }
 858+
 859+ /**
 860+ * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...)
 861+ * @param $text String: new text of the section
 862+ * @param $summary String: new section's subject, only if $section is 'new'
 863+ * @param $edittime String: revision timestamp or null to use the current revision
 864+ * @return string Complete article text, or null if error
 865+ */
 866+ public function replaceSection( $section, $text, $summary = '', $edittime = null ) {
 867+ wfProfileIn( __METHOD__ );
 868+
 869+ if ( strval( $section ) == '' ) {
 870+ // Whole-page edit; let the whole text through
 871+ } else {
 872+ if ( is_null( $edittime ) ) {
 873+ $rev = Revision::newFromTitle( $this->mTitle );
 874+ } else {
 875+ $dbw = wfGetDB( DB_MASTER );
 876+ $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
 877+ }
 878+
 879+ if ( !$rev ) {
 880+ wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
 881+ $this->getId() . "; section: $section; edittime: $edittime)\n" );
 882+ wfProfileOut( __METHOD__ );
 883+ return null;
 884+ }
 885+
 886+ $oldtext = $rev->getText();
 887+
 888+ if ( $section == 'new' ) {
 889+ # Inserting a new section
 890+ $subject = $summary ? wfMsgForContent( 'newsectionheaderdefaultlevel', $summary ) . "\n\n" : '';
 891+ $text = strlen( trim( $oldtext ) ) > 0
 892+ ? "{$oldtext}\n\n{$subject}{$text}"
 893+ : "{$subject}{$text}";
 894+ } else {
 895+ # Replacing an existing section; roll out the big guns
 896+ global $wgParser;
 897+
 898+ $text = $wgParser->replaceSection( $oldtext, $section, $text );
 899+ }
 900+ }
 901+
 902+ wfProfileOut( __METHOD__ );
 903+ return $text;
 904+ }
 905+
 906+ /**
 907+ * Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
 908+ * @param $flags Int
 909+ * @return Int updated $flags
 910+ */
 911+ function checkFlags( $flags ) {
 912+ if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
 913+ if ( $this->mTitle->getArticleID() ) {
 914+ $flags |= EDIT_UPDATE;
 915+ } else {
 916+ $flags |= EDIT_NEW;
 917+ }
 918+ }
 919+
 920+ return $flags;
 921+ }
 922+
 923+ /**
 924+ * Change an existing article or create a new article. Updates RC and all necessary caches,
 925+ * optionally via the deferred update array.
 926+ *
 927+ * $wgUser must be set before calling this function.
 928+ *
 929+ * @param $text String: new text
 930+ * @param $summary String: edit summary
 931+ * @param $flags Integer bitfield:
 932+ * EDIT_NEW
 933+ * Article is known or assumed to be non-existent, create a new one
 934+ * EDIT_UPDATE
 935+ * Article is known or assumed to be pre-existing, update it
 936+ * EDIT_MINOR
 937+ * Mark this edit minor, if the user is allowed to do so
 938+ * EDIT_SUPPRESS_RC
 939+ * Do not log the change in recentchanges
 940+ * EDIT_FORCE_BOT
 941+ * Mark the edit a "bot" edit regardless of user rights
 942+ * EDIT_DEFER_UPDATES
 943+ * Defer some of the updates until the end of index.php
 944+ * EDIT_AUTOSUMMARY
 945+ * Fill in blank summaries with generated text where possible
 946+ *
 947+ * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
 948+ * If EDIT_UPDATE is specified and the article doesn't exist, the function will an
 949+ * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
 950+ * edit-already-exists error will be returned. These two conditions are also possible with
 951+ * auto-detection due to MediaWiki's performance-optimised locking strategy.
 952+ *
 953+ * @param $baseRevId the revision ID this edit was based off, if any
 954+ * @param $user User (optional), $wgUser will be used if not passed
 955+ *
 956+ * @return Status object. Possible errors:
 957+ * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
 958+ * edit-gone-missing: In update mode, but the article didn't exist
 959+ * edit-conflict: In update mode, the article changed unexpectedly
 960+ * edit-no-change: Warning that the text was the same as before
 961+ * edit-already-exists: In creation mode, but the article already exists
 962+ *
 963+ * Extensions may define additional errors.
 964+ *
 965+ * $return->value will contain an associative array with members as follows:
 966+ * new: Boolean indicating if the function attempted to create a new article
 967+ * revision: The revision object for the inserted revision, or null
 968+ *
 969+ * Compatibility note: this function previously returned a boolean value indicating success/failure
 970+ */
 971+ public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
 972+ global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries;
 973+
 974+ # Low-level sanity check
 975+ if ( $this->mTitle->getText() === '' ) {
 976+ throw new MWException( 'Something is trying to edit an article with an empty title' );
 977+ }
 978+
 979+ wfProfileIn( __METHOD__ );
 980+
 981+ $user = is_null( $user ) ? $wgUser : $user;
 982+ $status = Status::newGood( array() );
 983+
 984+ # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already
 985+ $this->loadPageData();
 986+
 987+ $flags = $this->checkFlags( $flags );
 988+
 989+ if ( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text, &$summary,
 990+ $flags & EDIT_MINOR, null, null, &$flags, &$status ) ) )
 991+ {
 992+ wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
 993+
 994+ if ( $status->isOK() ) {
 995+ $status->fatal( 'edit-hook-aborted' );
 996+ }
 997+
 998+ wfProfileOut( __METHOD__ );
 999+ return $status;
 1000+ }
 1001+
 1002+ # Silently ignore EDIT_MINOR if not allowed
 1003+ $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
 1004+ $bot = $flags & EDIT_FORCE_BOT;
 1005+
 1006+ $oldtext = $this->getRawText(); // current revision
 1007+ $oldsize = strlen( $oldtext );
 1008+
 1009+ # Provide autosummaries if one is not provided and autosummaries are enabled.
 1010+ if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
 1011+ $summary = $this->getAutosummary( $oldtext, $text, $flags );
 1012+ }
 1013+
 1014+ $editInfo = $this->prepareTextForEdit( $text, null, $user );
 1015+ $text = $editInfo->pst;
 1016+ $newsize = strlen( $text );
 1017+
 1018+ $dbw = wfGetDB( DB_MASTER );
 1019+ $now = wfTimestampNow();
 1020+ $this->mTimestamp = $now;
 1021+
 1022+ if ( $flags & EDIT_UPDATE ) {
 1023+ # Update article, but only if changed.
 1024+ $status->value['new'] = false;
 1025+
 1026+ # Make sure the revision is either completely inserted or not inserted at all
 1027+ if ( !$wgDBtransactions ) {
 1028+ $userAbort = ignore_user_abort( true );
 1029+ }
 1030+
 1031+ $changed = ( strcmp( $text, $oldtext ) != 0 );
 1032+
 1033+ if ( $changed ) {
 1034+ if ( !$this->mLatest ) {
 1035+ # Article gone missing
 1036+ wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
 1037+ $status->fatal( 'edit-gone-missing' );
 1038+
 1039+ wfProfileOut( __METHOD__ );
 1040+ return $status;
 1041+ }
 1042+
 1043+ $revision = new Revision( array(
 1044+ 'page' => $this->getId(),
 1045+ 'comment' => $summary,
 1046+ 'minor_edit' => $isminor,
 1047+ 'text' => $text,
 1048+ 'parent_id' => $this->mLatest,
 1049+ 'user' => $user->getId(),
 1050+ 'user_text' => $user->getName(),
 1051+ 'timestamp' => $now
 1052+ ) );
 1053+
 1054+ $dbw->begin();
 1055+ $revisionId = $revision->insertOn( $dbw );
 1056+
 1057+ # Update page
 1058+ #
 1059+ # Note that we use $this->mLatest instead of fetching a value from the master DB
 1060+ # during the course of this function. This makes sure that EditPage can detect
 1061+ # edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
 1062+ # before this function is called. A previous function used a separate query, this
 1063+ # creates a window where concurrent edits can cause an ignored edit conflict.
 1064+ $ok = $this->updateRevisionOn( $dbw, $revision, $this->mLatest );
 1065+
 1066+ if ( !$ok ) {
 1067+ /* Belated edit conflict! Run away!! */
 1068+ $status->fatal( 'edit-conflict' );
 1069+
 1070+ # Delete the invalid revision if the DB is not transactional
 1071+ if ( !$wgDBtransactions ) {
 1072+ $dbw->delete( 'revision', array( 'rev_id' => $revisionId ), __METHOD__ );
 1073+ }
 1074+
 1075+ $revisionId = 0;
 1076+ $dbw->rollback();
 1077+ } else {
 1078+ global $wgUseRCPatrol;
 1079+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
 1080+ # Update recentchanges
 1081+ if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
 1082+ # Mark as patrolled if the user can do so
 1083+ $patrolled = $wgUseRCPatrol && !count(
 1084+ $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
 1085+ # Add RC row to the DB
 1086+ $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
 1087+ $this->mLatest, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
 1088+ $revisionId, $patrolled
 1089+ );
 1090+
 1091+ # Log auto-patrolled edits
 1092+ if ( $patrolled ) {
 1093+ PatrolLog::record( $rc, true );
 1094+ }
 1095+ }
 1096+ $user->incEditCount();
 1097+ $dbw->commit();
 1098+ }
 1099+ } else {
 1100+ $status->warning( 'edit-no-change' );
 1101+ $revision = null;
 1102+ // Keep the same revision ID, but do some updates on it
 1103+ $revisionId = $this->getLatest();
 1104+ // Update page_touched, this is usually implicit in the page update
 1105+ // Other cache updates are done in onArticleEdit()
 1106+ $this->mTitle->invalidateCache();
 1107+ }
 1108+
 1109+ if ( !$wgDBtransactions ) {
 1110+ ignore_user_abort( $userAbort );
 1111+ }
 1112+
 1113+ // Now that ignore_user_abort is restored, we can respond to fatal errors
 1114+ if ( !$status->isOK() ) {
 1115+ wfProfileOut( __METHOD__ );
 1116+ return $status;
 1117+ }
 1118+
 1119+ # Invalidate cache of this article and all pages using this article
 1120+ # as a template. Partly deferred.
 1121+ self::onArticleEdit( $this->mTitle );
 1122+ # Update links tables, site stats, etc.
 1123+ $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed, $user );
 1124+ } else {
 1125+ # Create new article
 1126+ $status->value['new'] = true;
 1127+
 1128+ $dbw->begin();
 1129+
 1130+ # Add the page record; stake our claim on this title!
 1131+ # This will return false if the article already exists
 1132+ $newid = $this->insertOn( $dbw );
 1133+
 1134+ if ( $newid === false ) {
 1135+ $dbw->rollback();
 1136+ $status->fatal( 'edit-already-exists' );
 1137+
 1138+ wfProfileOut( __METHOD__ );
 1139+ return $status;
 1140+ }
 1141+
 1142+ # Save the revision text...
 1143+ $revision = new Revision( array(
 1144+ 'page' => $newid,
 1145+ 'comment' => $summary,
 1146+ 'minor_edit' => $isminor,
 1147+ 'text' => $text,
 1148+ 'user' => $user->getId(),
 1149+ 'user_text' => $user->getName(),
 1150+ 'timestamp' => $now
 1151+ ) );
 1152+ $revisionId = $revision->insertOn( $dbw );
 1153+
 1154+ $this->mTitle->resetArticleID( $newid );
 1155+ # Update the LinkCache. Resetting the Title ArticleID means it will rely on having that already cached
 1156+ # @todo FIXME?
 1157+ LinkCache::singleton()->addGoodLinkObj( $newid, $this->mTitle, strlen( $text ), (bool)Title::newFromRedirect( $text ), $revisionId );
 1158+
 1159+ # Update the page record with revision data
 1160+ $this->updateRevisionOn( $dbw, $revision, 0 );
 1161+
 1162+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
 1163+
 1164+ # Update recentchanges
 1165+ if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
 1166+ global $wgUseRCPatrol, $wgUseNPPatrol;
 1167+
 1168+ # Mark as patrolled if the user can do so
 1169+ $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
 1170+ $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
 1171+ # Add RC row to the DB
 1172+ $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
 1173+ '', strlen( $text ), $revisionId, $patrolled );
 1174+
 1175+ # Log auto-patrolled edits
 1176+ if ( $patrolled ) {
 1177+ PatrolLog::record( $rc, true );
 1178+ }
 1179+ }
 1180+ $user->incEditCount();
 1181+ $dbw->commit();
 1182+
 1183+ # Update links, etc.
 1184+ $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, true, $user, true );
 1185+
 1186+ # Clear caches
 1187+ self::onArticleCreate( $this->mTitle );
 1188+
 1189+ wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
 1190+ $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
 1191+ }
 1192+
 1193+ # Do updates right now unless deferral was requested
 1194+ if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
 1195+ wfDoUpdates();
 1196+ }
 1197+
 1198+ // Return the new revision (or null) to the caller
 1199+ $status->value['revision'] = $revision;
 1200+
 1201+ wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
 1202+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
 1203+
 1204+ # Promote user to any groups they meet the criteria for
 1205+ $user->addAutopromoteOnceGroups( 'onEdit' );
 1206+
 1207+ wfProfileOut( __METHOD__ );
 1208+ return $status;
 1209+ }
 1210+
 1211+ /**
 1212+ * Update the article's restriction field, and leave a log entry.
 1213+ *
 1214+ * @param $limit Array: set of restriction keys
 1215+ * @param $reason String
 1216+ * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
 1217+ * @param $expiry Array: per restriction type expiration
 1218+ * @return bool true on success
 1219+ */
 1220+ public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
 1221+ global $wgUser, $wgContLang;
 1222+
 1223+ $restrictionTypes = $this->mTitle->getRestrictionTypes();
 1224+
 1225+ $id = $this->mTitle->getArticleID();
 1226+
 1227+ if ( $id <= 0 ) {
 1228+ wfDebug( "updateRestrictions failed: article id $id <= 0\n" );
 1229+ return false;
 1230+ }
 1231+
 1232+ if ( wfReadOnly() ) {
 1233+ wfDebug( "updateRestrictions failed: read-only\n" );
 1234+ return false;
 1235+ }
 1236+
 1237+ if ( !$this->mTitle->userCan( 'protect' ) ) {
 1238+ wfDebug( "updateRestrictions failed: insufficient permissions\n" );
 1239+ return false;
 1240+ }
 1241+
 1242+ if ( !$cascade ) {
 1243+ $cascade = false;
 1244+ }
 1245+
 1246+ // Take this opportunity to purge out expired restrictions
 1247+ Title::purgeExpiredRestrictions();
 1248+
 1249+ # @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
 1250+ # we expect a single selection, but the schema allows otherwise.
 1251+ $current = array();
 1252+ $updated = self::flattenRestrictions( $limit );
 1253+ $changed = false;
 1254+
 1255+ foreach ( $restrictionTypes as $action ) {
 1256+ if ( isset( $expiry[$action] ) ) {
 1257+ # Get current restrictions on $action
 1258+ $aLimits = $this->mTitle->getRestrictions( $action );
 1259+ $current[$action] = implode( '', $aLimits );
 1260+ # Are any actual restrictions being dealt with here?
 1261+ $aRChanged = count( $aLimits ) || !empty( $limit[$action] );
 1262+
 1263+ # If something changed, we need to log it. Checking $aRChanged
 1264+ # assures that "unprotecting" a page that is not protected does
 1265+ # not log just because the expiry was "changed".
 1266+ if ( $aRChanged && $this->mTitle->mRestrictionsExpiry[$action] != $expiry[$action] ) {
 1267+ $changed = true;
 1268+ }
 1269+ }
 1270+ }
 1271+
 1272+ $current = self::flattenRestrictions( $current );
 1273+
 1274+ $changed = ( $changed || $current != $updated );
 1275+ $changed = $changed || ( $updated && $this->mTitle->areRestrictionsCascading() != $cascade );
 1276+ $protect = ( $updated != '' );
 1277+
 1278+ # If nothing's changed, do nothing
 1279+ if ( $changed ) {
 1280+ if ( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser, $limit, $reason ) ) ) {
 1281+ $dbw = wfGetDB( DB_MASTER );
 1282+
 1283+ # Prepare a null revision to be added to the history
 1284+ $modified = $current != '' && $protect;
 1285+
 1286+ if ( $protect ) {
 1287+ $comment_type = $modified ? 'modifiedarticleprotection' : 'protectedarticle';
 1288+ } else {
 1289+ $comment_type = 'unprotectedarticle';
 1290+ }
 1291+
 1292+ $comment = $wgContLang->ucfirst( wfMsgForContent( $comment_type, $this->mTitle->getPrefixedText() ) );
 1293+
 1294+ # Only restrictions with the 'protect' right can cascade...
 1295+ # Otherwise, people who cannot normally protect can "protect" pages via transclusion
 1296+ $editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' );
 1297+
 1298+ # The schema allows multiple restrictions
 1299+ if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) ) {
 1300+ $cascade = false;
 1301+ }
 1302+
 1303+ $cascade_description = '';
 1304+
 1305+ if ( $cascade ) {
 1306+ $cascade_description = ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']';
 1307+ }
 1308+
 1309+ if ( $reason ) {
 1310+ $comment .= ": $reason";
 1311+ }
 1312+
 1313+ $editComment = $comment;
 1314+ $encodedExpiry = array();
 1315+ $protect_description = '';
 1316+ foreach ( $limit as $action => $restrictions ) {
 1317+ if ( !isset( $expiry[$action] ) )
 1318+ $expiry[$action] = $dbw->getInfinity();
 1319+
 1320+ $encodedExpiry[$action] = $dbw->encodeExpiry( $expiry[$action] );
 1321+ if ( $restrictions != '' ) {
 1322+ $protect_description .= "[$action=$restrictions] (";
 1323+ if ( $encodedExpiry[$action] != 'infinity' ) {
 1324+ $protect_description .= wfMsgForContent( 'protect-expiring',
 1325+ $wgContLang->timeanddate( $expiry[$action], false, false ) ,
 1326+ $wgContLang->date( $expiry[$action], false, false ) ,
 1327+ $wgContLang->time( $expiry[$action], false, false ) );
 1328+ } else {
 1329+ $protect_description .= wfMsgForContent( 'protect-expiry-indefinite' );
 1330+ }
 1331+
 1332+ $protect_description .= ') ';
 1333+ }
 1334+ }
 1335+ $protect_description = trim( $protect_description );
 1336+
 1337+ if ( $protect_description && $protect ) {
 1338+ $editComment .= " ($protect_description)";
 1339+ }
 1340+
 1341+ if ( $cascade ) {
 1342+ $editComment .= "$cascade_description";
 1343+ }
 1344+
 1345+ # Update restrictions table
 1346+ foreach ( $limit as $action => $restrictions ) {
 1347+ if ( $restrictions != '' ) {
 1348+ $dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ),
 1349+ array( 'pr_page' => $id,
 1350+ 'pr_type' => $action,
 1351+ 'pr_level' => $restrictions,
 1352+ 'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
 1353+ 'pr_expiry' => $encodedExpiry[$action]
 1354+ ),
 1355+ __METHOD__
 1356+ );
 1357+ } else {
 1358+ $dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
 1359+ 'pr_type' => $action ), __METHOD__ );
 1360+ }
 1361+ }
 1362+
 1363+ # Insert a null revision
 1364+ $nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true );
 1365+ $nullRevId = $nullRevision->insertOn( $dbw );
 1366+
 1367+ $latest = $this->getLatest();
 1368+ # Update page record
 1369+ $dbw->update( 'page',
 1370+ array( /* SET */
 1371+ 'page_touched' => $dbw->timestamp(),
 1372+ 'page_restrictions' => '',
 1373+ 'page_latest' => $nullRevId
 1374+ ), array( /* WHERE */
 1375+ 'page_id' => $id
 1376+ ), __METHOD__
 1377+ );
 1378+
 1379+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $wgUser ) );
 1380+ wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) );
 1381+
 1382+ # Update the protection log
 1383+ $log = new LogPage( 'protect' );
 1384+ if ( $protect ) {
 1385+ $params = array( $protect_description, $cascade ? 'cascade' : '' );
 1386+ $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason ), $params );
 1387+ } else {
 1388+ $log->addEntry( 'unprotect', $this->mTitle, $reason );
 1389+ }
 1390+ } # End hook
 1391+ } # End "changed" check
 1392+
 1393+ return true;
 1394+ }
 1395+
 1396+ /**
 1397+ * Take an array of page restrictions and flatten it to a string
 1398+ * suitable for insertion into the page_restrictions field.
 1399+ * @param $limit Array
 1400+ * @return String
 1401+ */
 1402+ protected static function flattenRestrictions( $limit ) {
 1403+ if ( !is_array( $limit ) ) {
 1404+ throw new MWException( 'WikiPage::flattenRestrictions given non-array restriction set' );
 1405+ }
 1406+
 1407+ $bits = array();
 1408+ ksort( $limit );
 1409+
 1410+ foreach ( $limit as $action => $restrictions ) {
 1411+ if ( $restrictions != '' ) {
 1412+ $bits[] = "$action=$restrictions";
 1413+ }
 1414+ }
 1415+
 1416+ return implode( ':', $bits );
 1417+ }
 1418+
 1419+ /**
 1420+ * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
 1421+ */
 1422+ public function isBigDeletion() {
 1423+ global $wgDeleteRevisionsLimit;
 1424+
 1425+ if ( $wgDeleteRevisionsLimit ) {
 1426+ $revCount = $this->estimateRevisionCount();
 1427+
 1428+ return $revCount > $wgDeleteRevisionsLimit;
 1429+ }
 1430+
 1431+ return false;
 1432+ }
 1433+
 1434+ /**
 1435+ * @return int approximate revision count
 1436+ */
 1437+ public function estimateRevisionCount() {
 1438+ $dbr = wfGetDB( DB_SLAVE );
 1439+
 1440+ // For an exact count...
 1441+ // return $dbr->selectField( 'revision', 'COUNT(*)',
 1442+ // array( 'rev_page' => $this->getId() ), __METHOD__ );
 1443+ return $dbr->estimateRowCount( 'revision', '*',
 1444+ array( 'rev_page' => $this->getId() ), __METHOD__ );
 1445+ }
 1446+
 1447+ /**
 1448+ * Get the last N authors
 1449+ * @param $num Integer: number of revisions to get
 1450+ * @param $revLatest String: the latest rev_id, selected from the master (optional)
 1451+ * @return array Array of authors, duplicates not removed
 1452+ */
 1453+ public function getLastNAuthors( $num, $revLatest = 0 ) {
 1454+ wfProfileIn( __METHOD__ );
 1455+ // First try the slave
 1456+ // If that doesn't have the latest revision, try the master
 1457+ $continue = 2;
 1458+ $db = wfGetDB( DB_SLAVE );
 1459+
 1460+ do {
 1461+ $res = $db->select( array( 'page', 'revision' ),
 1462+ array( 'rev_id', 'rev_user_text' ),
 1463+ array(
 1464+ 'page_namespace' => $this->mTitle->getNamespace(),
 1465+ 'page_title' => $this->mTitle->getDBkey(),
 1466+ 'rev_page = page_id'
 1467+ ), __METHOD__,
 1468+ array(
 1469+ 'ORDER BY' => 'rev_timestamp DESC',
 1470+ 'LIMIT' => $num
 1471+ )
 1472+ );
 1473+
 1474+ if ( !$res ) {
 1475+ wfProfileOut( __METHOD__ );
 1476+ return array();
 1477+ }
 1478+
 1479+ $row = $db->fetchObject( $res );
 1480+
 1481+ if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
 1482+ $db = wfGetDB( DB_MASTER );
 1483+ $continue--;
 1484+ } else {
 1485+ $continue = 0;
 1486+ }
 1487+ } while ( $continue );
 1488+
 1489+ $authors = array( $row->rev_user_text );
 1490+
 1491+ foreach ( $res as $row ) {
 1492+ $authors[] = $row->rev_user_text;
 1493+ }
 1494+
 1495+ wfProfileOut( __METHOD__ );
 1496+ return $authors;
 1497+ }
 1498+
 1499+ /**
 1500+ * Back-end article deletion
 1501+ * Deletes the article with database consistency, writes logs, purges caches
 1502+ *
 1503+ * @param $reason string delete reason for deletion log
 1504+ * @param suppress bitfield
 1505+ * Revision::DELETED_TEXT
 1506+ * Revision::DELETED_COMMENT
 1507+ * Revision::DELETED_USER
 1508+ * Revision::DELETED_RESTRICTED
 1509+ * @param $id int article ID
 1510+ * @param $commit boolean defaults to true, triggers transaction end
 1511+ * @return boolean true if successful
 1512+ */
 1513+ public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) {
 1514+ global $wgDeferredUpdateList, $wgUseTrackbacks;
 1515+ global $wgUser;
 1516+
 1517+ wfDebug( __METHOD__ . "\n" );
 1518+
 1519+ if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$wgUser, &$reason, &$error ) ) ) {
 1520+ return false;
 1521+ }
 1522+ $dbw = wfGetDB( DB_MASTER );
 1523+ $t = $this->mTitle->getDBkey();
 1524+ $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
 1525+
 1526+ if ( $t === '' || $id == 0 ) {
 1527+ return false;
 1528+ }
 1529+
 1530+ $u = new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 );
 1531+ array_push( $wgDeferredUpdateList, $u );
 1532+
 1533+ // Bitfields to further suppress the content
 1534+ if ( $suppress ) {
 1535+ $bitfield = 0;
 1536+ // This should be 15...
 1537+ $bitfield |= Revision::DELETED_TEXT;
 1538+ $bitfield |= Revision::DELETED_COMMENT;
 1539+ $bitfield |= Revision::DELETED_USER;
 1540+ $bitfield |= Revision::DELETED_RESTRICTED;
 1541+ } else {
 1542+ $bitfield = 'rev_deleted';
 1543+ }
 1544+
 1545+ $dbw->begin();
 1546+ // For now, shunt the revision data into the archive table.
 1547+ // Text is *not* removed from the text table; bulk storage
 1548+ // is left intact to avoid breaking block-compression or
 1549+ // immutable storage schemes.
 1550+ //
 1551+ // For backwards compatibility, note that some older archive
 1552+ // table entries will have ar_text and ar_flags fields still.
 1553+ //
 1554+ // In the future, we may keep revisions and mark them with
 1555+ // the rev_deleted field, which is reserved for this purpose.
 1556+ $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
 1557+ array(
 1558+ 'ar_namespace' => 'page_namespace',
 1559+ 'ar_title' => 'page_title',
 1560+ 'ar_comment' => 'rev_comment',
 1561+ 'ar_user' => 'rev_user',
 1562+ 'ar_user_text' => 'rev_user_text',
 1563+ 'ar_timestamp' => 'rev_timestamp',
 1564+ 'ar_minor_edit' => 'rev_minor_edit',
 1565+ 'ar_rev_id' => 'rev_id',
 1566+ 'ar_text_id' => 'rev_text_id',
 1567+ 'ar_text' => '\'\'', // Be explicit to appease
 1568+ 'ar_flags' => '\'\'', // MySQL's "strict mode"...
 1569+ 'ar_len' => 'rev_len',
 1570+ 'ar_page_id' => 'page_id',
 1571+ 'ar_deleted' => $bitfield
 1572+ ), array(
 1573+ 'page_id' => $id,
 1574+ 'page_id = rev_page'
 1575+ ), __METHOD__
 1576+ );
 1577+
 1578+ # Delete restrictions for it
 1579+ $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
 1580+
 1581+ # Now that it's safely backed up, delete it
 1582+ $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
 1583+ $ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy
 1584+
 1585+ if ( !$ok ) {
 1586+ $dbw->rollback();
 1587+ return false;
 1588+ }
 1589+
 1590+ # Fix category table counts
 1591+ $cats = array();
 1592+ $res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
 1593+
 1594+ foreach ( $res as $row ) {
 1595+ $cats [] = $row->cl_to;
 1596+ }
 1597+
 1598+ $this->updateCategoryCounts( array(), $cats );
 1599+
 1600+ # If using cascading deletes, we can skip some explicit deletes
 1601+ if ( !$dbw->cascadingDeletes() ) {
 1602+ $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
 1603+
 1604+ if ( $wgUseTrackbacks )
 1605+ $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
 1606+
 1607+ # Delete outgoing links
 1608+ $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
 1609+ $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
 1610+ $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
 1611+ $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
 1612+ $dbw->delete( 'externallinks', array( 'el_from' => $id ) );
 1613+ $dbw->delete( 'langlinks', array( 'll_from' => $id ) );
 1614+ $dbw->delete( 'iwlinks', array( 'iwl_from' => $id ) );
 1615+ $dbw->delete( 'redirect', array( 'rd_from' => $id ) );
 1616+ }
 1617+
 1618+ # If using cleanup triggers, we can skip some manual deletes
 1619+ if ( !$dbw->cleanupTriggers() ) {
 1620+ # Clean up recentchanges entries...
 1621+ $dbw->delete( 'recentchanges',
 1622+ array( 'rc_type != ' . RC_LOG,
 1623+ 'rc_namespace' => $this->mTitle->getNamespace(),
 1624+ 'rc_title' => $this->mTitle->getDBkey() ),
 1625+ __METHOD__ );
 1626+ $dbw->delete( 'recentchanges',
 1627+ array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
 1628+ __METHOD__ );
 1629+ }
 1630+
 1631+ # Clear caches
 1632+ self::onArticleDelete( $this->mTitle );
 1633+
 1634+ # Clear the cached article id so the interface doesn't act like we exist
 1635+ $this->mTitle->resetArticleID( 0 );
 1636+
 1637+ # Log the deletion, if the page was suppressed, log it at Oversight instead
 1638+ $logtype = $suppress ? 'suppress' : 'delete';
 1639+ $log = new LogPage( $logtype );
 1640+
 1641+ # Make sure logging got through
 1642+ $log->addEntry( 'delete', $this->mTitle, $reason, array() );
 1643+
 1644+ if ( $commit ) {
 1645+ $dbw->commit();
 1646+ }
 1647+
 1648+ wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$wgUser, $reason, $id ) );
 1649+ return true;
 1650+ }
 1651+
 1652+ /**
 1653+ * Roll back the most recent consecutive set of edits to a page
 1654+ * from the same user; fails if there are no eligible edits to
 1655+ * roll back to, e.g. user is the sole contributor. This function
 1656+ * performs permissions checks on $wgUser, then calls commitRollback()
 1657+ * to do the dirty work
 1658+ *
 1659+ * @param $fromP String: Name of the user whose edits to rollback.
 1660+ * @param $summary String: Custom summary. Set to default summary if empty.
 1661+ * @param $token String: Rollback token.
 1662+ * @param $bot Boolean: If true, mark all reverted edits as bot.
 1663+ *
 1664+ * @param $resultDetails Array: contains result-specific array of additional values
 1665+ * 'alreadyrolled' : 'current' (rev)
 1666+ * success : 'summary' (str), 'current' (rev), 'target' (rev)
 1667+ *
 1668+ * @return array of errors, each error formatted as
 1669+ * array(messagekey, param1, param2, ...).
 1670+ * On success, the array is empty. This array can also be passed to
 1671+ * OutputPage::showPermissionsErrorPage().
 1672+ */
 1673+ public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails ) {
 1674+ global $wgUser;
 1675+
 1676+ $resultDetails = null;
 1677+
 1678+ # Check permissions
 1679+ $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
 1680+ $rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $wgUser );
 1681+ $errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
 1682+
 1683+ if ( !$wgUser->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) ) {
 1684+ $errors[] = array( 'sessionfailure' );
 1685+ }
 1686+
 1687+ if ( $wgUser->pingLimiter( 'rollback' ) || $wgUser->pingLimiter() ) {
 1688+ $errors[] = array( 'actionthrottledtext' );
 1689+ }
 1690+
 1691+ # If there were errors, bail out now
 1692+ if ( !empty( $errors ) ) {
 1693+ return $errors;
 1694+ }
 1695+
 1696+ return $this->commitRollback( $fromP, $summary, $bot, $resultDetails );
 1697+ }
 1698+
 1699+ /**
 1700+ * Backend implementation of doRollback(), please refer there for parameter
 1701+ * and return value documentation
 1702+ *
 1703+ * NOTE: This function does NOT check ANY permissions, it just commits the
 1704+ * rollback to the DB Therefore, you should only call this function direct-
 1705+ * ly if you want to use custom permissions checks. If you don't, use
 1706+ * doRollback() instead.
 1707+ */
 1708+ public function commitRollback( $fromP, $summary, $bot, &$resultDetails ) {
 1709+ global $wgUseRCPatrol, $wgUser, $wgContLang;
 1710+
 1711+ $dbw = wfGetDB( DB_MASTER );
 1712+
 1713+ if ( wfReadOnly() ) {
 1714+ return array( array( 'readonlytext' ) );
 1715+ }
 1716+
 1717+ # Get the last editor
 1718+ $current = Revision::newFromTitle( $this->mTitle );
 1719+ if ( is_null( $current ) ) {
 1720+ # Something wrong... no page?
 1721+ return array( array( 'notanarticle' ) );
 1722+ }
 1723+
 1724+ $from = str_replace( '_', ' ', $fromP );
 1725+ # User name given should match up with the top revision.
 1726+ # If the user was deleted then $from should be empty.
 1727+ if ( $from != $current->getUserText() ) {
 1728+ $resultDetails = array( 'current' => $current );
 1729+ return array( array( 'alreadyrolled',
 1730+ htmlspecialchars( $this->mTitle->getPrefixedText() ),
 1731+ htmlspecialchars( $fromP ),
 1732+ htmlspecialchars( $current->getUserText() )
 1733+ ) );
 1734+ }
 1735+
 1736+ # Get the last edit not by this guy...
 1737+ # Note: these may not be public values
 1738+ $user = intval( $current->getRawUser() );
 1739+ $user_text = $dbw->addQuotes( $current->getRawUserText() );
 1740+ $s = $dbw->selectRow( 'revision',
 1741+ array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
 1742+ array( 'rev_page' => $current->getPage(),
 1743+ "rev_user != {$user} OR rev_user_text != {$user_text}"
 1744+ ), __METHOD__,
 1745+ array( 'USE INDEX' => 'page_timestamp',
 1746+ 'ORDER BY' => 'rev_timestamp DESC' )
 1747+ );
 1748+ if ( $s === false ) {
 1749+ # No one else ever edited this page
 1750+ return array( array( 'cantrollback' ) );
 1751+ } elseif ( $s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER ) {
 1752+ # Only admins can see this text
 1753+ return array( array( 'notvisiblerev' ) );
 1754+ }
 1755+
 1756+ $set = array();
 1757+ if ( $bot && $wgUser->isAllowed( 'markbotedits' ) ) {
 1758+ # Mark all reverted edits as bot
 1759+ $set['rc_bot'] = 1;
 1760+ }
 1761+
 1762+ if ( $wgUseRCPatrol ) {
 1763+ # Mark all reverted edits as patrolled
 1764+ $set['rc_patrolled'] = 1;
 1765+ }
 1766+
 1767+ if ( count( $set ) ) {
 1768+ $dbw->update( 'recentchanges', $set,
 1769+ array( /* WHERE */
 1770+ 'rc_cur_id' => $current->getPage(),
 1771+ 'rc_user_text' => $current->getUserText(),
 1772+ "rc_timestamp > '{$s->rev_timestamp}'",
 1773+ ), __METHOD__
 1774+ );
 1775+ }
 1776+
 1777+ # Generate the edit summary if necessary
 1778+ $target = Revision::newFromId( $s->rev_id );
 1779+ if ( empty( $summary ) ) {
 1780+ if ( $from == '' ) { // no public user name
 1781+ $summary = wfMsgForContent( 'revertpage-nouser' );
 1782+ } else {
 1783+ $summary = wfMsgForContent( 'revertpage' );
 1784+ }
 1785+ }
 1786+
 1787+ # Allow the custom summary to use the same args as the default message
 1788+ $args = array(
 1789+ $target->getUserText(), $from, $s->rev_id,
 1790+ $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
 1791+ $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
 1792+ );
 1793+ $summary = wfMsgReplaceArgs( $summary, $args );
 1794+
 1795+ # Save
 1796+ $flags = EDIT_UPDATE;
 1797+
 1798+ if ( $wgUser->isAllowed( 'minoredit' ) ) {
 1799+ $flags |= EDIT_MINOR;
 1800+ }
 1801+
 1802+ if ( $bot && ( $wgUser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
 1803+ $flags |= EDIT_FORCE_BOT;
 1804+ }
 1805+
 1806+ # Actually store the edit
 1807+ $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
 1808+ if ( !empty( $status->value['revision'] ) ) {
 1809+ $revId = $status->value['revision']->getId();
 1810+ } else {
 1811+ $revId = false;
 1812+ }
 1813+
 1814+ wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target, $current ) );
 1815+
 1816+ $resultDetails = array(
 1817+ 'summary' => $summary,
 1818+ 'current' => $current,
 1819+ 'target' => $target,
 1820+ 'newid' => $revId
 1821+ );
 1822+
 1823+ return array();
 1824+ }
 1825+
 1826+ /**
 1827+ * Do standard deferred updates after page view
 1828+ */
 1829+ public function viewUpdates() {
 1830+ global $wgDeferredUpdateList, $wgDisableCounters, $wgUser;
 1831+ if ( wfReadOnly() ) {
 1832+ return;
 1833+ }
 1834+
 1835+ # Don't update page view counters on views from bot users (bug 14044)
 1836+ if ( !$wgDisableCounters && !$wgUser->isAllowed( 'bot' ) && $this->getID() ) {
 1837+ $wgDeferredUpdateList[] = new ViewCountUpdate( $this->getID() );
 1838+ $wgDeferredUpdateList[] = new SiteStatsUpdate( 1, 0, 0 );
 1839+ }
 1840+
 1841+ # Update newtalk / watchlist notification status
 1842+ $wgUser->clearNotification( $this->mTitle );
 1843+ }
 1844+
 1845+ /**
 1846+ * Prepare text which is about to be saved.
 1847+ * Returns a stdclass with source, pst and output members
 1848+ */
 1849+ public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
 1850+ if ( $this->mPreparedEdit && $this->mPreparedEdit->newText == $text && $this->mPreparedEdit->revid == $revid ) {
 1851+ // Already prepared
 1852+ return $this->mPreparedEdit;
 1853+ }
 1854+
 1855+ global $wgParser;
 1856+
 1857+ if( $user === null ) {
 1858+ global $wgUser;
 1859+ $user = $wgUser;
 1860+ }
 1861+ $popts = ParserOptions::newFromUser( $user );
 1862+ wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
 1863+
 1864+ $edit = (object)array();
 1865+ $edit->revid = $revid;
 1866+ $edit->newText = $text;
 1867+ $edit->pst = $this->preSaveTransform( $text, $user, $popts );
 1868+ $edit->popts = $this->getParserOptions( true );
 1869+ $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
 1870+ $edit->oldText = $this->getRawText();
 1871+
 1872+ $this->mPreparedEdit = $edit;
 1873+
 1874+ return $edit;
 1875+ }
 1876+
 1877+ /**
 1878+ * Do standard deferred updates after page edit.
 1879+ * Update links tables, site stats, search index and message cache.
 1880+ * Purges pages that include this page if the text was changed here.
 1881+ * Every 100th edit, prune the recent changes table.
 1882+ *
 1883+ * @private
 1884+ * @param $text String: New text of the article
 1885+ * @param $summary String: Edit summary
 1886+ * @param $minoredit Boolean: Minor edit
 1887+ * @param $timestamp_of_pagechange String timestamp associated with the page change
 1888+ * @param $newid Integer: rev_id value of the new revision
 1889+ * @param $changed Boolean: Whether or not the content actually changed
 1890+ * @param $user User object: User doing the edit
 1891+ * @param $created Boolean: Whether the edit created the page
 1892+ */
 1893+ public function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange, $newid,
 1894+ $changed = true, User $user = null, $created = false )
 1895+ {
 1896+ global $wgDeferredUpdateList, $wgUser, $wgEnableParserCache;
 1897+
 1898+ wfProfileIn( __METHOD__ );
 1899+
 1900+ # Parse the text
 1901+ # Be careful not to double-PST: $text is usually already PST-ed once
 1902+ if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
 1903+ wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
 1904+ $editInfo = $this->prepareTextForEdit( $text, $newid, $user );
 1905+ } else {
 1906+ wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
 1907+ $editInfo = $this->mPreparedEdit;
 1908+ }
 1909+
 1910+ # Save it to the parser cache
 1911+ if ( $wgEnableParserCache ) {
 1912+ $parserCache = ParserCache::singleton();
 1913+ $parserCache->save( $editInfo->output, $this, $editInfo->popts );
 1914+ }
 1915+
 1916+ # Update the links tables
 1917+ $u = new LinksUpdate( $this->mTitle, $editInfo->output );
 1918+ $u->doUpdate();
 1919+
 1920+ wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $changed ) );
 1921+
 1922+ if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
 1923+ if ( 0 == mt_rand( 0, 99 ) ) {
 1924+ // Flush old entries from the `recentchanges` table; we do this on
 1925+ // random requests so as to avoid an increase in writes for no good reason
 1926+ global $wgRCMaxAge;
 1927+
 1928+ $dbw = wfGetDB( DB_MASTER );
 1929+ $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
 1930+ $dbw->delete(
 1931+ 'recentchanges',
 1932+ array( "rc_timestamp < '$cutoff'" ),
 1933+ __METHOD__
 1934+ );
 1935+ }
 1936+ }
 1937+
 1938+ $id = $this->getID();
 1939+ $title = $this->mTitle->getPrefixedDBkey();
 1940+ $shortTitle = $this->mTitle->getDBkey();
 1941+
 1942+ if ( 0 == $id ) {
 1943+ wfProfileOut( __METHOD__ );
 1944+ return;
 1945+ }
 1946+
 1947+ if ( !$changed ) {
 1948+ $good = 0;
 1949+ $total = 0;
 1950+ } elseif ( $created ) {
 1951+ $good = (int)$this->isCountable( $editInfo );
 1952+ $total = 1;
 1953+ } else {
 1954+ $good = (int)$this->isCountable( $editInfo ) - (int)$this->isCountable();
 1955+ $total = 0;
 1956+ }
 1957+
 1958+ $wgDeferredUpdateList[] = new SiteStatsUpdate( 0, 1, $good, $total );
 1959+ $wgDeferredUpdateList[] = new SearchUpdate( $id, $title, $text );
 1960+
 1961+ # If this is another user's talk page, update newtalk
 1962+ # Don't do this if $changed = false otherwise some idiot can null-edit a
 1963+ # load of user talk pages and piss people off, nor if it's a minor edit
 1964+ # by a properly-flagged bot.
 1965+ if ( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed
 1966+ && !( $minoredit && $wgUser->isAllowed( 'nominornewtalk' ) )
 1967+ ) {
 1968+ if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
 1969+ $other = User::newFromName( $shortTitle, false );
 1970+ if ( !$other ) {
 1971+ wfDebug( __METHOD__ . ": invalid username\n" );
 1972+ } elseif ( User::isIP( $shortTitle ) ) {
 1973+ // An anonymous user
 1974+ $other->setNewtalk( true );
 1975+ } elseif ( $other->isLoggedIn() ) {
 1976+ $other->setNewtalk( true );
 1977+ } else {
 1978+ wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
 1979+ }
 1980+ }
 1981+ }
 1982+
 1983+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
 1984+ MessageCache::singleton()->replace( $shortTitle, $text );
 1985+ }
 1986+
 1987+ wfProfileOut( __METHOD__ );
 1988+ }
 1989+
 1990+ /**
 1991+ * Perform article updates on a special page creation.
 1992+ *
 1993+ * @param $rev Revision object
 1994+ *
 1995+ * @todo This is a shitty interface function. Kill it and replace the
 1996+ * other shitty functions like editUpdates and such so it's not needed
 1997+ * anymore.
 1998+ */
 1999+ public function createUpdates( $rev ) {
 2000+ $this->editUpdates( $rev->getText(), $rev->getComment(),
 2001+ $rev->isMinor(), wfTimestamp(), $rev->getId(), true, null, true );
 2002+ }
 2003+
 2004+ /**
 2005+ * This function is called right before saving the wikitext,
 2006+ * so we can do things like signatures and links-in-context.
 2007+ *
 2008+ * @param $text String article contents
 2009+ * @param $user User object: user doing the edit, $wgUser will be used if
 2010+ * null is given
 2011+ * @param $popts ParserOptions object: parser options, default options for
 2012+ * the user loaded if null given
 2013+ * @return string article contents with altered wikitext markup (signatures
 2014+ * converted, {{subst:}}, templates, etc.)
 2015+ */
 2016+ public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
 2017+ global $wgParser;
 2018+
 2019+ if ( $user === null ) {
 2020+ global $wgUser;
 2021+ $user = $wgUser;
 2022+ }
 2023+
 2024+ if ( $popts === null ) {
 2025+ $popts = ParserOptions::newFromUser( $user );
 2026+ }
 2027+
 2028+ return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
 2029+ }
 2030+
 2031+ /**
 2032+ * Loads page_touched and returns a value indicating if it should be used
 2033+ * @return boolean true if not a redirect
 2034+ */
 2035+ public function checkTouched() {
 2036+ if ( !$this->mDataLoaded ) {
 2037+ $this->loadPageData();
 2038+ }
 2039+
 2040+ return !$this->mIsRedirect;
 2041+ }
 2042+
 2043+ /**
 2044+ * Get the page_touched field
 2045+ * @return string containing GMT timestamp
 2046+ */
 2047+ public function getTouched() {
 2048+ if ( !$this->mDataLoaded ) {
 2049+ $this->loadPageData();
 2050+ }
 2051+
 2052+ return $this->mTouched;
 2053+ }
 2054+
 2055+ /**
 2056+ * Get the page_latest field
 2057+ * @return integer rev_id of current revision
 2058+ */
 2059+ public function getLatest() {
 2060+ if ( !$this->mDataLoaded ) {
 2061+ $this->loadPageData();
 2062+ }
 2063+
 2064+ return (int)$this->mLatest;
 2065+ }
 2066+
 2067+ /**
 2068+ * Edit an article without doing all that other stuff
 2069+ * The article must already exist; link tables etc
 2070+ * are not updated, caches are not flushed.
 2071+ *
 2072+ * @param $text String: text submitted
 2073+ * @param $comment String: comment submitted
 2074+ * @param $minor Boolean: whereas it's a minor modification
 2075+ */
 2076+ public function quickEdit( $text, $comment = '', $minor = 0 ) {
 2077+ wfProfileIn( __METHOD__ );
 2078+
 2079+ $dbw = wfGetDB( DB_MASTER );
 2080+ $revision = new Revision( array(
 2081+ 'page' => $this->getId(),
 2082+ 'text' => $text,
 2083+ 'comment' => $comment,
 2084+ 'minor_edit' => $minor ? 1 : 0,
 2085+ ) );
 2086+ $revision->insertOn( $dbw );
 2087+ $this->updateRevisionOn( $dbw, $revision );
 2088+
 2089+ global $wgUser;
 2090+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $wgUser ) );
 2091+
 2092+ wfProfileOut( __METHOD__ );
 2093+ }
 2094+
 2095+ /**
 2096+ * The onArticle*() functions are supposed to be a kind of hooks
 2097+ * which should be called whenever any of the specified actions
 2098+ * are done.
 2099+ *
 2100+ * This is a good place to put code to clear caches, for instance.
 2101+ *
 2102+ * This is called on page move and undelete, as well as edit
 2103+ *
 2104+ * @param $title Title object
 2105+ */
 2106+ public static function onArticleCreate( $title ) {
 2107+ # Update existence markers on article/talk tabs...
 2108+ if ( $title->isTalkPage() ) {
 2109+ $other = $title->getSubjectPage();
 2110+ } else {
 2111+ $other = $title->getTalkPage();
 2112+ }
 2113+
 2114+ $other->invalidateCache();
 2115+ $other->purgeSquid();
 2116+
 2117+ $title->touchLinks();
 2118+ $title->purgeSquid();
 2119+ $title->deleteTitleProtection();
 2120+ }
 2121+
 2122+ /**
 2123+ * Clears caches when article is deleted
 2124+ *
 2125+ * @param $title Title
 2126+ */
 2127+ public static function onArticleDelete( $title ) {
 2128+ # Update existence markers on article/talk tabs...
 2129+ if ( $title->isTalkPage() ) {
 2130+ $other = $title->getSubjectPage();
 2131+ } else {
 2132+ $other = $title->getTalkPage();
 2133+ }
 2134+
 2135+ $other->invalidateCache();
 2136+ $other->purgeSquid();
 2137+
 2138+ $title->touchLinks();
 2139+ $title->purgeSquid();
 2140+
 2141+ # File cache
 2142+ HTMLFileCache::clearFileCache( $title );
 2143+
 2144+ # Messages
 2145+ if ( $title->getNamespace() == NS_MEDIAWIKI ) {
 2146+ MessageCache::singleton()->replace( $title->getDBkey(), false );
 2147+ }
 2148+
 2149+ # Images
 2150+ if ( $title->getNamespace() == NS_FILE ) {
 2151+ $update = new HTMLCacheUpdate( $title, 'imagelinks' );
 2152+ $update->doUpdate();
 2153+ }
 2154+
 2155+ # User talk pages
 2156+ if ( $title->getNamespace() == NS_USER_TALK ) {
 2157+ $user = User::newFromName( $title->getText(), false );
 2158+ $user->setNewtalk( false );
 2159+ }
 2160+
 2161+ # Image redirects
 2162+ RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
 2163+ }
 2164+
 2165+ /**
 2166+ * Purge caches on page update etc
 2167+ *
 2168+ * @param $title Title object
 2169+ * @todo: verify that $title is always a Title object (and never false or null), add Title hint to parameter $title
 2170+ */
 2171+ public static function onArticleEdit( $title ) {
 2172+ global $wgDeferredUpdateList;
 2173+
 2174+ // Invalidate caches of articles which include this page
 2175+ $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
 2176+
 2177+ // Invalidate the caches of all pages which redirect here
 2178+ $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
 2179+
 2180+ # Purge squid for this page only
 2181+ $title->purgeSquid();
 2182+
 2183+ # Clear file cache for this page only
 2184+ HTMLFileCache::clearFileCache( $title );
 2185+ }
 2186+
 2187+ /**#@-*/
 2188+
 2189+ /**
 2190+ * Return a list of templates used by this article.
 2191+ * Uses the templatelinks table
 2192+ *
 2193+ * @return Array of Title objects
 2194+ */
 2195+ public function getUsedTemplates() {
 2196+ $result = array();
 2197+ $id = $this->mTitle->getArticleID();
 2198+
 2199+ if ( $id == 0 ) {
 2200+ return array();
 2201+ }
 2202+
 2203+ $dbr = wfGetDB( DB_SLAVE );
 2204+ $res = $dbr->select( array( 'templatelinks' ),
 2205+ array( 'tl_namespace', 'tl_title' ),
 2206+ array( 'tl_from' => $id ),
 2207+ __METHOD__ );
 2208+
 2209+ if ( $res !== false ) {
 2210+ foreach ( $res as $row ) {
 2211+ $result[] = Title::makeTitle( $row->tl_namespace, $row->tl_title );
 2212+ }
 2213+ }
 2214+
 2215+ return $result;
 2216+ }
 2217+
 2218+ /**
 2219+ * Returns a list of hidden categories this page is a member of.
 2220+ * Uses the page_props and categorylinks tables.
 2221+ *
 2222+ * @return Array of Title objects
 2223+ */
 2224+ public function getHiddenCategories() {
 2225+ $result = array();
 2226+ $id = $this->mTitle->getArticleID();
 2227+
 2228+ if ( $id == 0 ) {
 2229+ return array();
 2230+ }
 2231+
 2232+ $dbr = wfGetDB( DB_SLAVE );
 2233+ $res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ),
 2234+ array( 'cl_to' ),
 2235+ array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
 2236+ 'page_namespace' => NS_CATEGORY, 'page_title=cl_to' ),
 2237+ __METHOD__ );
 2238+
 2239+ if ( $res !== false ) {
 2240+ foreach ( $res as $row ) {
 2241+ $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
 2242+ }
 2243+ }
 2244+
 2245+ return $result;
 2246+ }
 2247+
 2248+ /**
 2249+ * Return an applicable autosummary if one exists for the given edit.
 2250+ * @param $oldtext String: the previous text of the page.
 2251+ * @param $newtext String: The submitted text of the page.
 2252+ * @param $flags Int bitmask: a bitmask of flags submitted for the edit.
 2253+ * @return string An appropriate autosummary, or an empty string.
 2254+ */
 2255+ public static function getAutosummary( $oldtext, $newtext, $flags ) {
 2256+ global $wgContLang;
 2257+
 2258+ # Decide what kind of autosummary is needed.
 2259+
 2260+ # Redirect autosummaries
 2261+ $ot = Title::newFromRedirect( $oldtext );
 2262+ $rt = Title::newFromRedirect( $newtext );
 2263+
 2264+ if ( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) {
 2265+ return wfMsgForContent( 'autoredircomment', $rt->getFullText() );
 2266+ }
 2267+
 2268+ # New page autosummaries
 2269+ if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
 2270+ # If they're making a new article, give its text, truncated, in the summary.
 2271+
 2272+ $truncatedtext = $wgContLang->truncate(
 2273+ str_replace( "\n", ' ', $newtext ),
 2274+ max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) );
 2275+
 2276+ return wfMsgForContent( 'autosumm-new', $truncatedtext );
 2277+ }
 2278+
 2279+ # Blanking autosummaries
 2280+ if ( $oldtext != '' && $newtext == '' ) {
 2281+ return wfMsgForContent( 'autosumm-blank' );
 2282+ } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
 2283+ # Removing more than 90% of the article
 2284+
 2285+ $truncatedtext = $wgContLang->truncate(
 2286+ $newtext,
 2287+ max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) );
 2288+
 2289+ return wfMsgForContent( 'autosumm-replace', $truncatedtext );
 2290+ }
 2291+
 2292+ # If we reach this point, there's no applicable autosummary for our case, so our
 2293+ # autosummary is empty.
 2294+ return '';
 2295+ }
 2296+
 2297+ /**
 2298+ * Get parser options suitable for rendering the primary article wikitext
 2299+ * @param $canonical boolean Determines that the generated options must not depend on user preferences (see bug 14404)
 2300+ * @return mixed ParserOptions object or boolean false
 2301+ */
 2302+ public function getParserOptions( $canonical = false ) {
 2303+ global $wgUser, $wgLanguageCode;
 2304+
 2305+ if ( !$this->mParserOptions || $canonical ) {
 2306+ $user = !$canonical ? $wgUser : new User;
 2307+ $parserOptions = new ParserOptions( $user );
 2308+ $parserOptions->setTidy( true );
 2309+ $parserOptions->enableLimitReport();
 2310+
 2311+ if ( $canonical ) {
 2312+ $parserOptions->setUserLang( $wgLanguageCode ); # Must be set explicitely
 2313+ return $parserOptions;
 2314+ }
 2315+ $this->mParserOptions = $parserOptions;
 2316+ }
 2317+ // Clone to allow modifications of the return value without affecting cache
 2318+ return clone $this->mParserOptions;
 2319+ }
 2320+
 2321+ /**
 2322+ * Get parser options suitable for rendering the primary article wikitext
 2323+ * @param User $user
 2324+ * @return ParserOptions
 2325+ */
 2326+ public function makeParserOptions( User $user ) {
 2327+ $options = ParserOptions::newFromUser( $user );
 2328+ $options->enableLimitReport(); // show inclusion/loop reports
 2329+ $options->setTidy( true ); // fix bad HTML
 2330+ return $options;
 2331+ }
 2332+
 2333+ /**
 2334+ * Update all the appropriate counts in the category table, given that
 2335+ * we've added the categories $added and deleted the categories $deleted.
 2336+ *
 2337+ * @param $added array The names of categories that were added
 2338+ * @param $deleted array The names of categories that were deleted
 2339+ */
 2340+ public function updateCategoryCounts( $added, $deleted ) {
 2341+ $ns = $this->mTitle->getNamespace();
 2342+ $dbw = wfGetDB( DB_MASTER );
 2343+
 2344+ # First make sure the rows exist. If one of the "deleted" ones didn't
 2345+ # exist, we might legitimately not create it, but it's simpler to just
 2346+ # create it and then give it a negative value, since the value is bogus
 2347+ # anyway.
 2348+ #
 2349+ # Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE.
 2350+ $insertCats = array_merge( $added, $deleted );
 2351+ if ( !$insertCats ) {
 2352+ # Okay, nothing to do
 2353+ return;
 2354+ }
 2355+
 2356+ $insertRows = array();
 2357+
 2358+ foreach ( $insertCats as $cat ) {
 2359+ $insertRows[] = array(
 2360+ 'cat_id' => $dbw->nextSequenceValue( 'category_cat_id_seq' ),
 2361+ 'cat_title' => $cat
 2362+ );
 2363+ }
 2364+ $dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' );
 2365+
 2366+ $addFields = array( 'cat_pages = cat_pages + 1' );
 2367+ $removeFields = array( 'cat_pages = cat_pages - 1' );
 2368+
 2369+ if ( $ns == NS_CATEGORY ) {
 2370+ $addFields[] = 'cat_subcats = cat_subcats + 1';
 2371+ $removeFields[] = 'cat_subcats = cat_subcats - 1';
 2372+ } elseif ( $ns == NS_FILE ) {
 2373+ $addFields[] = 'cat_files = cat_files + 1';
 2374+ $removeFields[] = 'cat_files = cat_files - 1';
 2375+ }
 2376+
 2377+ if ( $added ) {
 2378+ $dbw->update(
 2379+ 'category',
 2380+ $addFields,
 2381+ array( 'cat_title' => $added ),
 2382+ __METHOD__
 2383+ );
 2384+ }
 2385+
 2386+ if ( $deleted ) {
 2387+ $dbw->update(
 2388+ 'category',
 2389+ $removeFields,
 2390+ array( 'cat_title' => $deleted ),
 2391+ __METHOD__
 2392+ );
 2393+ }
 2394+ }
 2395+
 2396+ /**
 2397+ * Lightweight method to get the parser output for a page, checking the parser cache
 2398+ * and so on. Doesn't consider most of the stuff that WikiPage::view is forced to
 2399+ * consider, so it's not appropriate to use there.
 2400+ *
 2401+ * @since 1.16 (r52326) for LiquidThreads
 2402+ *
 2403+ * @param $oldid mixed integer Revision ID or null
 2404+ * @return ParserOutput or false if the given revsion ID is not found
 2405+ */
 2406+ public function getParserOutput( $oldid = null ) {
 2407+ global $wgEnableParserCache, $wgUser;
 2408+
 2409+ // Should the parser cache be used?
 2410+ $useParserCache = $wgEnableParserCache &&
 2411+ $wgUser->getStubThreshold() == 0 &&
 2412+ $this->exists() &&
 2413+ $oldid === null;
 2414+
 2415+ wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
 2416+
 2417+ if ( $wgUser->getStubThreshold() ) {
 2418+ wfIncrStats( 'pcache_miss_stub' );
 2419+ }
 2420+
 2421+ if ( $useParserCache ) {
 2422+ $parserOutput = ParserCache::singleton()->get( $this, $this->getParserOptions() );
 2423+ if ( $parserOutput !== false ) {
 2424+ return $parserOutput;
 2425+ }
 2426+ }
 2427+
 2428+ // Cache miss; parse and output it.
 2429+ if ( $oldid === null ) {
 2430+ $text = $this->getRawText();
 2431+ } else {
 2432+ $rev = Revision::newFromTitle( $this->getTitle(), $oldid );
 2433+ if ( $rev === null ) {
 2434+ return false;
 2435+ }
 2436+ $text = $rev->getText();
 2437+ }
 2438+
 2439+ return $this->getOutputFromWikitext( $text, $useParserCache );
 2440+ }
 2441+}
Property changes on: trunk/phase3/includes/WikiPage.php
___________________________________________________________________
Added: svn:eol-style
12442 + native
Added: svn:keywords
22443 + Author Date Id Revision
Index: trunk/phase3/includes/Metadata.php
@@ -28,7 +28,7 @@
2929 * Constructor
3030 * @param $article Article object
3131 */
32 - public function __construct( Article $article ) {
 32+ public function __construct( Page $article ) {
3333 $this->mArticle = $article;
3434 }
3535
Index: trunk/phase3/includes/Action.php
@@ -73,7 +73,7 @@
7474 * @return Action|false|null false if the action is disabled, null
7575 * if it is not recognised
7676 */
77 - public final static function factory( $action, Article $page ) {
 77+ public final static function factory( $action, Page $page ) {
7878 $class = self::getClass( $action );
7979 if ( $class ) {
8080 $obj = new $class( $page );
@@ -159,9 +159,9 @@
160160 /**
161161 * Protected constructor: use Action::factory( $action, $page ) to actually build
162162 * these things in the real world
163 - * @param Article $page
 163+ * @param Page $page
164164 */
165 - protected function __construct( Article $page ) {
 165+ protected function __construct( Page $page ) {
166166 $this->page = $page;
167167 }
168168
Index: trunk/phase3/includes/WikiCategoryPage.php
@@ -0,0 +1,37 @@
 2+<?php
 3+/**
 4+ * Special handling for category pages
 5+ */
 6+class WikiCategoryPage extends WikiPage {
 7+ /**
 8+ * Constructor from a page id
 9+ * @param $id Int article ID to load
 10+ */
 11+ public static function newFromID( $id ) {
 12+ $t = Title::newFromID( $id );
 13+ # @todo FIXME: Doesn't inherit right
 14+ return $t == null ? null : new self( $t );
 15+ # return $t == null ? null : new static( $t ); // PHP 5.3
 16+ }
 17+
 18+ /**
 19+ * Don't return a 404 for categories in use.
 20+ * In use defined as: either the actual page exists
 21+ * or the category currently has members.
 22+ */
 23+ function hasViewableContent() {
 24+ if ( parent::hasViewableContent() ) {
 25+ return true;
 26+ } else {
 27+ $cat = Category::newFromTitle( $this->mTitle );
 28+ // If any of these are not 0, then has members
 29+ if ( $cat->getPageCount()
 30+ || $cat->getSubcatCount()
 31+ || $cat->getFileCount()
 32+ ) {
 33+ return true;
 34+ }
 35+ }
 36+ return false;
 37+ }
 38+}
Property changes on: trunk/phase3/includes/WikiCategoryPage.php
___________________________________________________________________
Added: svn:mergeinfo
139 Merged /branches/new-installer/phase3/includes/CategoryPage.php:r43664-66004
240 Merged /branches/wmf-deployment/includes/CategoryPage.php:r53381
341 Merged /branches/wmf/1.17wmf1/includes/CategoryPage.php:r83562
442 Merged /branches/REL1_15/phase3/includes/CategoryPage.php:r51646
543 Merged /branches/sqlite/includes/CategoryPage.php:r58211-58321
Added: svn:eol-style
644 + native
Added: svn:keywords
745 + Author Date Id Revision
Index: trunk/extensions/FlaggedRevs/dataclasses/FRUserCounters.php
@@ -159,11 +159,11 @@
160160 /**
161161 * Update users params array for a user on edit
162162 * @param &array $p user params
163 - * @param Article $article the article just edited
 163+ * @param Page $article the article just edited
164164 * @param string $summary edit summary
165165 * @return bool anything changed
166166 */
167 - public static function updateUserParams( array &$p, Article $article, $summary ) {
 167+ public static function updateUserParams( array &$p, Page $article, $summary ) {
168168 global $wgFlaggedRevsAutoconfirm, $wgFlaggedRevsAutopromote;
169169 # Update any special counters for non-null revisions
170170 $changed = false;
Index: trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevs.hooks.php
@@ -91,7 +91,7 @@
9292 * (b) Pages with stable versions that use this page will be purged
9393 * Note: pages with current versions that use this page should already be purged
9494 */
95 - public static function onArticleEditUpdates( Article $article, $editInfo ) {
 95+ public static function onArticleEditUpdates( Page $article, $editInfo ) {
9696 FlaggedRevs::stableVersionUpdates( $article->getTitle(), null, null, $editInfo );
9797 FlaggedRevs::extraHTMLCacheUpdate( $article->getTitle() );
9898 return true;
@@ -102,7 +102,7 @@
103103 * (b) Pages with stable versions that use this page will be purged
104104 * Note: pages with current versions that use this page should already be purged
105105 */
106 - public static function onArticleDelete( Article $article, $user, $reason, $id ) {
 106+ public static function onArticleDelete( Page $article, $user, $reason, $id ) {
107107 FlaggedRevs::clearTrackingRows( $id );
108108 FlaggedRevs::extraHTMLCacheUpdate( $article->getTitle() );
109109 return true;
@@ -343,7 +343,7 @@
344344 * Note: $article one of Article, ImagePage, Category page as appropriate.
345345 */
346346 public static function maybeMakeEditReviewed(
347 - Article $article, $rev, $baseRevId = false, $user = null
 347+ Page $article, $rev, $baseRevId = false, $user = null
348348 ) {
349349 global $wgRequest;
350350 # Edit must be non-null, to a reviewable page, with $user set
@@ -446,7 +446,7 @@
447447 // Review $rev if $editTimestamp matches the previous revision's timestamp.
448448 // Otherwise, review the revision that has $editTimestamp as its timestamp value.
449449 protected static function editCheckReview(
450 - Article $article, $rev, $user, $editTimestamp
 450+ Page $article, $rev, $user, $editTimestamp
451451 ) {
452452 $prevTimestamp = $flags = null;
453453 $prevRevId = $rev->getParentId(); // revision before $rev
@@ -516,7 +516,7 @@
517517 * Note: called after edit ops are finished
518518 */
519519 public static function maybeNullEditReview(
520 - Article $article, $user, $text, $s, $m, $a, $b, $flags, $rev, &$status, $baseId
 520+ Page $article, $user, $text, $s, $m, $a, $b, $flags, $rev, &$status, $baseId
521521 ) {
522522 global $wgRequest;
523523 # Revision must *be* null (null edit). We also need the user who made the edit.
@@ -642,7 +642,7 @@
643643 }
644644
645645 public static function incrementRollbacks(
646 - Article $article, $user, $goodRev, Revision $badRev
 646+ Page $article, $user, $goodRev, Revision $badRev
647647 ) {
648648 # Mark when a user reverts another user, but not self-reverts
649649 $badUserId = $badRev->getRawUser();
@@ -658,7 +658,7 @@
659659 }
660660
661661 public static function incrementReverts(
662 - Article $article, $rev, $baseRevId = false, $user = null
 662+ Page $article, $rev, $baseRevId = false, $user = null
663663 ) {
664664 global $wgRequest;
665665 # Was this an edit by an auto-sighter that undid another edit?
@@ -807,7 +807,7 @@
808808 * $wgFlaggedRevsAutopromote. This also handles user stats tallies.
809809 */
810810 public static function onArticleSaveComplete(
811 - Article $article, $user, $text, $summary, $m, $a, $b, &$f, $rev
 811+ Page $article, $user, $text, $summary, $m, $a, $b, &$f, $rev
812812 ) {
813813 global $wgFlaggedRevsAutopromote, $wgFlaggedRevsAutoconfirm;
814814 # Ignore NULL edits or edits by anon users
Index: trunk/extensions/FlaggedRevs/dataclasses/FlaggedRevs.class.php
@@ -654,11 +654,11 @@
655655 }
656656
657657 /**
658 - * @param Article $article
 658+ * @param Page $article
659659 * @param parserOutput $parserOut
660660 * Updates the stable-only cache dependency table
661661 */
662 - public static function updateStableOnlyDeps( Article $article, ParserOutput $stableOut ) {
 662+ public static function updateStableOnlyDeps( Page $article, ParserOutput $stableOut ) {
663663 wfProfileIn( __METHOD__ );
664664 if ( !wfReadOnly() ) {
665665 $frDepUpdate = new FRDependencyUpdate( $article->getTitle(), $stableOut );
@@ -749,11 +749,11 @@
750750 /**
751751 * Return memc value if not expired
752752 * @param object|false $data makeMemcObj() tuple
753 - * @param Article $article
 753+ * @param Page $article
754754 * @param $allowStale Use 'allowStale' to skip page_touched check
755755 * @return mixed
756756 */
757 - public static function getMemcValue( $data, Article $article, $allowStale = '' ) {
 757+ public static function getMemcValue( $data, Page $article, $allowStale = '' ) {
758758 if ( is_object( $data ) ) {
759759 if ( $allowStale === 'allowStale' || $data->time >= $article->getTouched() ) {
760760 return $data->value;
@@ -926,7 +926,7 @@
927927 * If no appropriate tags can be found, then the review will abort.
928928 */
929929 public static function autoReviewEdit(
930 - Article $article, $user, Revision $rev, array $flags = null, $auto = true
 930+ Page $article, $user, Revision $rev, array $flags = null, $auto = true
931931 ) {
932932 wfProfileIn( __METHOD__ );
933933 $title = $article->getTitle(); // convenience
Index: trunk/extensions/FlaggedRevs/dataclasses/FRInclusionCache.php
@@ -6,7 +6,7 @@
77 class FRInclusionCache {
88 /**
99 * Get template and image versions from parsing a revision
10 - * @param Article $article
 10+ * @param Page $article
1111 * @param Revision $rev
1212 * @param User $user
1313 * @param string $regen use 'regen' to force regeneration
@@ -15,7 +15,7 @@
1616 * fileSHA1Keys like ParserOutput->mImageTimeKeys
1717 */
1818 public static function getRevIncludes(
19 - Article $article, Revision $rev, User $user, $regen = ''
 19+ Page $article, Revision $rev, User $user, $regen = ''
2020 ) {
2121 global $wgParser, $wgMemc;
2222 wfProfileIn( __METHOD__ );
Index: trunk/extensions/FlaggedRevs/dataclasses/FlaggedPage.php
@@ -4,7 +4,7 @@
55 *
66 * FlaggedPage::getTitleInstance() is preferred over constructor calls
77 */
8 -class FlaggedPage extends Article {
 8+class FlaggedPage extends WikiPage {
99 /* Process cache variables */
1010 protected $stable = 0;
1111 protected $stableRev = null;
@@ -33,8 +33,8 @@
3434 * @param Article
3535 * @return FlaggedPage
3636 */
37 - public static function getArticleInstance( Article $article ) {
38 - return self::getTitleInstance( $article->mTitle );
 37+ public static function getArticleInstance( Page $article ) {
 38+ return self::getTitleInstance( $article->getTitle() );
3939 }
4040
4141 /**
@@ -382,7 +382,7 @@
383383 $row = $dbr->selectRow(
384384 array( 'page', 'flaggedpages', 'flaggedpage_config' ),
385385 array_merge(
386 - Article::selectFields(),
 386+ WikiPage::selectFields(),
387387 FlaggedPageConfig::selectFields(),
388388 array( 'fp_pending_since', 'fp_stable', 'fp_reviewed' ) ),
389389 $conditions,
Index: trunk/extensions/FlaggedRevs/presentation/FlaggedRevsUI.hooks.php
@@ -656,7 +656,7 @@
657657
658658 // Add selector of review "protection" options
659659 // Code stolen from Stabilization (which was stolen from ProtectionForm)
660 - public static function onProtectionForm( Article $article, &$output ) {
 660+ public static function onProtectionForm( Page $article, &$output ) {
661661 global $wgUser, $wgOut, $wgRequest, $wgLang;
662662 if ( !$article->exists() ) {
663663 return true; // nothing to do
@@ -793,7 +793,7 @@
794794 }
795795
796796 // Add stability log extract to protection form
797 - public static function insertStabilityLog( Article $article, OutputPage $out ) {
 797+ public static function insertStabilityLog( Page $article, OutputPage $out ) {
798798 if ( !$article->exists() ) {
799799 return true; // nothing to do
800800 } elseif ( !FlaggedRevs::inReviewNamespace( $article->getTitle() ) ) {
@@ -806,7 +806,7 @@
807807 }
808808
809809 // Update stability config from request
810 - public static function onProtectionSave( Article $article, &$errorMsg ) {
 810+ public static function onProtectionSave( Page $article, &$errorMsg ) {
811811 global $wgUser, $wgRequest;
812812 if ( !$article->exists() ) {
813813 return true; // simple custom levels set for action=protect
Index: trunk/extensions/FlaggedRevs/presentation/FlaggedRevsXML.php
@@ -409,7 +409,7 @@
410410 }
411411
412412 /*
413 - * @param Article $article
 413+ * @param Page $article
414414 * @return string
415415 * Creates a stability log excerpt
416416 */
Index: trunk/extensions/FlaggedRevs/presentation/FlaggedPageView.php
@@ -242,7 +242,7 @@
243243 return true;
244244 }
245245 # We may have nav links like "direction=prev&oldid=x"
246 - $revID = $this->article->getOldIDFromRequest();
 246+ $revID = $this->getOldIDFromRequest();
247247 $frev = FlaggedRevision::newFromTitle( $this->article->getTitle(), $revID );
248248 # Give a notice if this rev ID corresponds to a reviewed version...
249249 if ( $frev ) {
@@ -755,7 +755,7 @@
756756 return false; // nothing to do here
757757 }
758758 # Diff should only show for the draft
759 - $oldid = $this->article->getOldIDFromRequest();
 759+ $oldid = $this->getOldIDFromRequest();
760760 $latest = $this->article->getLatest();
761761 if ( $oldid && $oldid != $latest ) {
762762 return false; // not viewing the draft
@@ -1305,6 +1305,11 @@
13061306 return false;
13071307 }
13081308
 1309+ protected function getOldIDFromRequest() {
 1310+ $article = new Article( $this->article->getTitle() );
 1311+ return $article->getOldIDFromRequest();
 1312+ }
 1313+
13091314 /**
13101315 * Adds a notice saying that this revision is pending review
13111316 * @param FlaggedRevision $srev The stable version

Follow-up revisions

RevisionCommit summaryAuthorDate
r91159Expand on r91123 Article refactoring:...aaron07:05, 30 June 2011
r91162* Fixes for r91123:...aaron07:52, 30 June 2011
r91238Follow-up r91123:...aaron00:12, 1 July 2011
r92962It is stupid to test that a void method returns null. Add a comment explainin...platonides20:23, 23 July 2011
r93061Updated hooks docs per r91123. The UI methods are not part of the object anym...aaron16:44, 25 July 2011
r96460For r91123:...aaron17:55, 7 September 2011
r104799Fix Article class compatibility with MediaWiki 1.18 (r91123).juliano00:05, 1 December 2011

Comments

#Comment by 😂 (talk | contribs)   02:47, 30 June 2011

Splitting this into separate core and FR commits would make it much easier to review...

#Comment by Raymond (talk | contribs)   07:03, 30 June 2011

Seen on Translatewiki:

PHP Fatal error: Call to protected method WikiPage::pageDataFromId() from context 'Article' in /www/w/includes/Article.php on line 289
PHP Fatal error: Call to undefined method WikiPage::getOutputFromWikitext() in /www/w/includes/WikiPage.php on line 2438
#Comment by Aaron Schulz (talk | contribs)   17:23, 30 June 2011

Fixed in r91162.

#Comment by Happy-melon (talk | contribs)   08:42, 30 June 2011

(empty post for now to join the CC list)

#Comment by Jack Phoenix (talk | contribs)   14:17, 30 June 2011
--- trunk/phase3/includes/AutoLoader.php	2011/06/29 22:08:12	91122
+++ trunk/phase3/includes/AutoLoader.php	2011/06/29 22:09:51	91123
@@ -159,6 +159,7 @@
 	'MWNamespace' => 'includes/Namespace.php',
 	'OldChangesList' => 'includes/ChangesList.php',
 	'OutputPage' => 'includes/OutputPage.php',
+    'Page' =>  'includes/WikiPage.php',
 	'PageHistory' => 'includes/HistoryPage.php',
 	'PageHistoryPager' => 'includes/HistoryPage.php',
 	'PageQueryPage' => 'includes/PageQueryPage.php',
@@ -233,10 +234,13 @@
 	'WebRequest' => 'includes/WebRequest.php',
 	'WebRequestUpload' => 'includes/WebRequest.php',
 	'WebResponse' => 'includes/WebResponse.php',
+    'WikiCategoryPage' => 'includes/WikiCategoryPage.php',
 	'WikiError' => 'includes/WikiError.php',
 	'WikiErrorMsg' => 'includes/WikiError.php',
 	'WikiExporter' => 'includes/Export.php',
+    'WikiFilePage' =>  'includes/WikiFilePage.php',
 	'WikiImporter' => 'includes/Import.php',
+    'WikiPage' =>  'includes/WikiPage.php',
 	'WikiRevision' => 'includes/Import.php',
 	'WikiMap' => 'includes/WikiMap.php',
 	'WikiReference' => 'includes/WikiMap.php',

Should be indented with a tab character instead of four spaces.

#Comment by Aaron Schulz (talk | contribs)   17:23, 30 June 2011
#Comment by Platonides (talk | contribs)   21:54, 30 June 2011

Oh no, more magic __get()s and __call()s no!

Why can't Article just extend WikiPage?

#Comment by Aaron Schulz (talk | contribs)   21:56, 30 June 2011

ImagePage extends Article, which would extend WikiPage, but ImagePage should use WikiFilePage and not WikiPage, CategoryPage has a similar situation. I guess multiple-inheritance (non-PHP) would have been useful here.

#Comment by 😂 (talk | contribs)   21:58, 30 June 2011

5.4!

#Comment by Reedy (talk | contribs)   23:11, 3 July 2011

Caused bug 29699

#Comment by Aaron Schulz (talk | contribs)   22:38, 4 July 2011

Problem gone since r91418 (I could never reproduce it myself though)

#Comment by Nikerabbit (talk | contribs)   15:54, 25 July 2011

This changed first parameter of ArticleSaveComplete from Article to WikiPage. Change is undocumented and breaking for those who rely in methods existing only in Article.

#Comment by Aaron Schulz (talk | contribs)   20:45, 29 July 2011

Docs updated in r93061.

#Comment by Duplicatebug (talk | contribs)   18:00, 19 August 2011

You have copy many code from Article to WikiPage. Maybe change also the name of some method, like "onArticleDelete" to "onWikiPageDelete", to reflect that change.

Maybe also rename or duplicate the hooks to reflect also the new name and the new parameter.

#Comment by 😂 (talk | contribs)   18:07, 19 August 2011

Renaming hooks breaks people's extensions and causes grief.

#Comment by Catrope (talk | contribs)   10:36, 7 September 2011
+		$title = Title::makeTitle( NS_MAIN, 'somePage' );

Why are you constructing a title that is invalid on most wikis (those that have $wgCapitalLinks enabled)?

		} elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) {

What is this condition good for exactly? The code seems to allow people to create new properties except if they're called mContext or mPage, but property_exists() can't possibly return false for those, can it?

		// Check if the field has been filled by ParserCache::get()

This comment in WikiPage::getTimestamp() seems to be outdated since this revision moves the code filling $mTimestamp out of ParserCache::get() and into Article::view()

#Comment by Aaron Schulz (talk | contribs)   17:18, 7 September 2011

Do you mean property_exists() can't return true for them? How it handles privilege depends on the version of PHP (it changed fairly recently).

#Comment by Catrope (talk | contribs)   17:19, 7 September 2011

D'oh, they're protected. Of course.

#Comment by Catrope (talk | contribs)   12:17, 7 September 2011

Per Aaron's recommendation, I grepped the extensions dir for all hooks that he moved from Article to WikiPage (thus changing the type of the $this parameter), and found the following issues:

  • extensions/Checkpoint/Checkpoint.php:48 calls $article->fetchContent() but $article is now a WikiPage and doesn't have fetchContent()
  • extensions/PureWikiDeletion/PureWikiDeletion.hooks.php:59 calls $article->generateReason() on a WikiPage

Can be marked resolved once those two are fixed.

#Comment by Juliano (talk | contribs)   19:45, 30 November 2011

Doesn't this newFromID() method in Article, WikiPage, etc. break the hook "ArticleFromTitle"? The hook works like a object factory plugin, and is supposed to return a new Article object (perhaps a subclass of Article, like Image and Category pages) according to the title of the page. This new method newFromID() creates a new way to create objects that is not handled by "ArticleFromTitle" hook.

Now I'm seeing internal incompatibilities between situations where article objects are created through the "ArticleFromTitle" hook, and where article objects are created from this other way.

Status & tagging log