* Use local context to get messages
[lhc/web/wiklou.git] / includes / WikiPage.php
index ee6713e..6cad466 100644 (file)
@@ -13,6 +13,30 @@ abstract class Page {}
  * @internal documentation reviewed 15 Mar 2010
  */
 class WikiPage extends Page {
+       // doDeleteArticleReal() return values. Values less than zero indicate fatal errors,
+       // values greater than zero indicate that there were problems not resulting in page
+       // not being deleted
+
+       /**
+        * Delete operation aborted by hook
+        */
+       const DELETE_HOOK_ABORTED = -1;
+
+       /**
+        * Deletion successful
+        */
+       const DELETE_SUCCESS = 0;
+
+       /**
+        * Page not found
+        */
+       const DELETE_NO_PAGE = 1;
+
+       /**
+        * No revisions found to delete
+        */
+       const DELETE_NO_REVISIONS = 2;
+
        /**
         * @var Title
         */
@@ -24,7 +48,8 @@ class WikiPage extends Page {
        public $mDataLoaded = false;         // !< Boolean
        public $mIsRedirect = false;         // !< Boolean
        public $mLatest = false;             // !< Integer (false means "not loaded")
-       public $mPreparedEdit = false;           // !< Array
+       public $mPreparedEdit = false;       // !< Array
+       /**@}}*/
 
        /**
         * @var Title
@@ -36,9 +61,20 @@ class WikiPage extends Page {
         */
        protected $mLastRevision = null;
 
-       protected $mTimestamp = '';             // !< String
-       protected $mTouched = '19700101000000'; // !< String
-       /**@}}*/
+       /**
+        * @var string; timestamp of the current revision or empty string if not loaded
+        */
+       protected $mTimestamp = '';
+
+       /**
+        * @var string
+        */
+       protected $mTouched = '19700101000000';
+
+       /**
+        * @var int|null
+        */
+       protected $mCounter = null;
 
        /**
         * Constructor and clear the article
@@ -82,7 +118,7 @@ class WikiPage extends Page {
         *
         * @param $id Int article ID to load
         *
-        * @return WikiPage
+        * @return WikiPage|null
         */
        public static function newFromID( $id ) {
                $t = Title::newFromID( $id );
@@ -106,125 +142,6 @@ class WikiPage extends Page {
                return array();
        }
 
-       /**
-        * If this page is a redirect, get its target
-        *
-        * The target will be fetched from the redirect table if possible.
-        * If this page doesn't have an entry there, call insertRedirect()
-        * @return Title|mixed object, or null if this page is not a redirect
-        */
-       public function getRedirectTarget() {
-               if ( !$this->mTitle->isRedirect() ) {
-                       return null;
-               }
-
-               if ( $this->mRedirectTarget !== null ) {
-                       return $this->mRedirectTarget;
-               }
-
-               # Query the redirect table
-               $dbr = wfGetDB( DB_SLAVE );
-               $row = $dbr->selectRow( 'redirect',
-                       array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
-                       array( 'rd_from' => $this->getId() ),
-                       __METHOD__
-               );
-
-               // rd_fragment and rd_interwiki were added later, populate them if empty
-               if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
-                       return $this->mRedirectTarget = Title::makeTitle(
-                               $row->rd_namespace, $row->rd_title,
-                               $row->rd_fragment, $row->rd_interwiki );
-               }
-
-               # This page doesn't have an entry in the redirect table
-               return $this->mRedirectTarget = $this->insertRedirect();
-       }
-
-       /**
-        * Insert an entry for this page into the redirect table.
-        *
-        * Don't call this function directly unless you know what you're doing.
-        * @return Title object or null if not a redirect
-        */
-       public function insertRedirect() {
-               // recurse through to only get the final target
-               $retval = Title::newFromRedirectRecurse( $this->getRawText() );
-               if ( !$retval ) {
-                       return null;
-               }
-               $this->insertRedirectEntry( $retval );
-               return $retval;
-       }
-
-       /**
-        * Insert or update the redirect table entry for this page to indicate
-        * it redirects to $rt .
-        * @param $rt Title redirect target
-        */
-       public function insertRedirectEntry( $rt ) {
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->replace( 'redirect', array( 'rd_from' ),
-                       array(
-                               'rd_from'      => $this->getId(),
-                               'rd_namespace' => $rt->getNamespace(),
-                               'rd_title'     => $rt->getDBkey(),
-                               'rd_fragment'  => $rt->getFragment(),
-                               'rd_interwiki' => $rt->getInterwiki(),
-                       ),
-                       __METHOD__
-               );
-       }
-
-       /**
-        * Get the Title object or URL this page redirects to
-        *
-        * @return mixed false, Title of in-wiki target, or string with URL
-        */
-       public function followRedirect() {
-               return $this->getRedirectURL( $this->getRedirectTarget() );
-       }
-
-       /**
-        * Get the Title object or URL to use for a redirect. We use Title
-        * objects for same-wiki, non-special redirects and URLs for everything
-        * else.
-        * @param $rt Title Redirect target
-        * @return mixed false, Title object of local target, or string with URL
-        */
-       public function getRedirectURL( $rt ) {
-               if ( $rt ) {
-                       if ( $rt->getInterwiki() != '' ) {
-                               if ( $rt->isLocal() ) {
-                                       // Offsite wikis need an HTTP redirect.
-                                       //
-                                       // This can be hard to reverse and may produce loops,
-                                       // so they may be disabled in the site configuration.
-                                       $source = $this->mTitle->getFullURL( 'redirect=no' );
-                                       return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
-                               }
-                       } else {
-                               if ( $rt->isSpecialPage() ) {
-                                       // Gotta handle redirects to special pages differently:
-                                       // Fill the HTTP response "Location" header and ignore
-                                       // the rest of the page we're on.
-                                       //
-                                       // This can be hard to reverse, so they may be disabled.
-                                       if ( $rt->isSpecial( 'Userlogout' ) ) {
-                                               // rolleyes
-                                       } else {
-                                               return $rt->getFullURL();
-                                       }
-                               }
-
-                               return $rt;
-                       }
-               }
-
-               // No or invalid redirect
-               return false;
-       }
-
        /**
         * Get the title object of the article
         * @return Title object of this page
@@ -239,45 +156,16 @@ class WikiPage extends Page {
        public function clear() {
                $this->mDataLoaded = false;
 
+               $this->mCounter = null;
                $this->mRedirectTarget = null; # Title object if set
                $this->mLastRevision = null; # Latest revision
-               $this->mTimestamp = '';
                $this->mTouched = '19700101000000';
+               $this->mTimestamp = '';
                $this->mIsRedirect = false;
                $this->mLatest = false;
                $this->mPreparedEdit = false;
        }
 
-       /**
-        * Get the text that needs to be saved in order to undo all revisions
-        * between $undo and $undoafter. Revisions must belong to the same page,
-        * must exist and must not be deleted
-        * @param $undo Revision
-        * @param $undoafter Revision Must be an earlier revision than $undo
-        * @return mixed string on success, false on failure
-        */
-       public function getUndoText( Revision $undo, Revision $undoafter = null ) {
-               $cur_text = $this->getRawText();
-               if ( $cur_text === false ) {
-                       return false; // no page
-               }
-               $undo_text = $undo->getText();
-               $undoafter_text = $undoafter->getText();
-
-               if ( $cur_text == $undo_text ) {
-                       # No use doing a merge if it's just a straight revert.
-                       return $undoafter_text;
-               }
-
-               $undone_text = '';
-
-               if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
-                       return false;
-               }
-
-               return $undone_text;
-       }
-
        /**
         * Return the list of revision fields that should be selected to create
         * a new page.
@@ -378,6 +266,7 @@ class WikiPage extends Page {
                        # Old-fashioned restrictions
                        $this->mTitle->loadRestrictions( $data->page_restrictions );
 
+                       $this->mCounter     = intval( $data->page_counter );
                        $this->mTouched     = wfTimestamp( TS_MW, $data->page_touched );
                        $this->mIsRedirect  = intval( $data->page_is_redirect );
                        $this->mLatest      = intval( $data->page_latest );
@@ -417,55 +306,14 @@ class WikiPage extends Page {
        }
 
        /**
-        * Get the number of views of this page
-        *
         * @return int The view count for the page
         */
        public function getCount() {
-               return $this->mTitle->getCount();
-       }
-
-       /**
-        * Determine whether a page would be suitable for being counted as an
-        * article in the site_stats table based on the title & its content
-        *
-        * @param $editInfo Object or false: object returned by prepareTextForEdit(),
-        *        if false, the current database state will be used
-        * @return Boolean
-        */
-       public function isCountable( $editInfo = false ) {
-               global $wgArticleCountMethod;
-
-               if ( !$this->mTitle->isContentPage() ) {
-                       return false;
-               }
-
-               $text = $editInfo ? $editInfo->pst : false;
-
-               if ( $this->isRedirect( $text ) ) {
-                       return false;
+               if ( !$this->mDataLoaded ) {
+                       $this->loadPageData();
                }
 
-               switch ( $wgArticleCountMethod ) {
-               case 'any':
-                       return true;
-               case 'comma':
-                       if ( $text === false ) {
-                               $text = $this->getRawText();
-                       }
-                       return strpos( $text,  ',' ) !== false;
-               case 'link':
-                       if ( $editInfo ) {
-                               // ParserOutput::getLinks() is a 2D array of page links, so
-                               // to be really correct we would need to recurse in the array
-                               // but the main array should only have items in it if there are
-                               // links.
-                               return (bool)count( $editInfo->output->getLinks() );
-                       } else {
-                               return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
-                                       array( 'pl_from' => $this->getId() ), __METHOD__ );
-                       }
-               }
+               return $this->mCounter;
        }
 
        /**
@@ -566,7 +414,7 @@ class WikiPage extends Page {
         *      Revision::FOR_PUBLIC       to be displayed to all users
         *      Revision::FOR_THIS_USER    to be displayed to $wgUser
         *      Revision::RAW              get the text regardless of permissions
-        * @return String|false The text of the current revision
+        * @return String|bool The text of the current revision. False on failure
         */
        public function getText( $audience = Revision::FOR_PUBLIC ) {
                $this->loadLastEdit();
@@ -579,7 +427,7 @@ class WikiPage extends Page {
        /**
         * Get the text of the current revision. No side-effects...
         *
-        * @return String|false The text of the current revision
+        * @return String|bool The text of the current revision. False on failure
         */
        public function getRawText() {
                $this->loadLastEdit();
@@ -597,6 +445,7 @@ class WikiPage extends Page {
                if ( !$this->mTimestamp ) {
                        $this->loadLastEdit();
                }
+               
                return wfTimestamp( TS_MW, $this->mTimestamp );
        }
 
@@ -694,6 +543,171 @@ class WikiPage extends Page {
                $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60*15 );
        }
 
+       /**
+        * Determine whether a page would be suitable for being counted as an
+        * article in the site_stats table based on the title & its content
+        *
+        * @param $editInfo Object or false: object returned by prepareTextForEdit(),
+        *        if false, the current database state will be used
+        * @return Boolean
+        */
+       public function isCountable( $editInfo = false ) {
+               global $wgArticleCountMethod;
+
+               if ( !$this->mTitle->isContentPage() ) {
+                       return false;
+               }
+
+               $text = $editInfo ? $editInfo->pst : false;
+
+               if ( $this->isRedirect( $text ) ) {
+                       return false;
+               }
+
+               switch ( $wgArticleCountMethod ) {
+               case 'any':
+                       return true;
+               case 'comma':
+                       if ( $text === false ) {
+                               $text = $this->getRawText();
+                       }
+                       return strpos( $text,  ',' ) !== false;
+               case 'link':
+                       if ( $editInfo ) {
+                               // ParserOutput::getLinks() is a 2D array of page links, so
+                               // to be really correct we would need to recurse in the array
+                               // but the main array should only have items in it if there are
+                               // links.
+                               return (bool)count( $editInfo->output->getLinks() );
+                       } else {
+                               return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+                                       array( 'pl_from' => $this->getId() ), __METHOD__ );
+                       }
+               }
+       }
+
+       /**
+        * If this page is a redirect, get its target
+        *
+        * The target will be fetched from the redirect table if possible.
+        * If this page doesn't have an entry there, call insertRedirect()
+        * @return Title|mixed object, or null if this page is not a redirect
+        */
+       public function getRedirectTarget() {
+               if ( !$this->mTitle->isRedirect() ) {
+                       return null;
+               }
+
+               if ( $this->mRedirectTarget !== null ) {
+                       return $this->mRedirectTarget;
+               }
+
+               # Query the redirect table
+               $dbr = wfGetDB( DB_SLAVE );
+               $row = $dbr->selectRow( 'redirect',
+                       array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
+                       array( 'rd_from' => $this->getId() ),
+                       __METHOD__
+               );
+
+               // rd_fragment and rd_interwiki were added later, populate them if empty
+               if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
+                       return $this->mRedirectTarget = Title::makeTitle(
+                               $row->rd_namespace, $row->rd_title,
+                               $row->rd_fragment, $row->rd_interwiki );
+               }
+
+               # This page doesn't have an entry in the redirect table
+               return $this->mRedirectTarget = $this->insertRedirect();
+       }
+
+       /**
+        * Insert an entry for this page into the redirect table.
+        *
+        * Don't call this function directly unless you know what you're doing.
+        * @return Title object or null if not a redirect
+        */
+       public function insertRedirect() {
+               // recurse through to only get the final target
+               $retval = Title::newFromRedirectRecurse( $this->getRawText() );
+               if ( !$retval ) {
+                       return null;
+               }
+               $this->insertRedirectEntry( $retval );
+               return $retval;
+       }
+
+       /**
+        * Insert or update the redirect table entry for this page to indicate
+        * it redirects to $rt .
+        * @param $rt Title redirect target
+        */
+       public function insertRedirectEntry( $rt ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->replace( 'redirect', array( 'rd_from' ),
+                       array(
+                               'rd_from'      => $this->getId(),
+                               'rd_namespace' => $rt->getNamespace(),
+                               'rd_title'     => $rt->getDBkey(),
+                               'rd_fragment'  => $rt->getFragment(),
+                               'rd_interwiki' => $rt->getInterwiki(),
+                       ),
+                       __METHOD__
+               );
+       }
+
+       /**
+        * Get the Title object or URL this page redirects to
+        *
+        * @return mixed false, Title of in-wiki target, or string with URL
+        */
+       public function followRedirect() {
+               return $this->getRedirectURL( $this->getRedirectTarget() );
+       }
+
+       /**
+        * Get the Title object or URL to use for a redirect. We use Title
+        * objects for same-wiki, non-special redirects and URLs for everything
+        * else.
+        * @param $rt Title Redirect target
+        * @return mixed false, Title object of local target, or string with URL
+        */
+       public function getRedirectURL( $rt ) {
+               if ( !$rt ) {
+                       return false;
+               }
+
+               if ( $rt->isExternal() ) {
+                       if ( $rt->isLocal() ) {
+                               // Offsite wikis need an HTTP redirect.
+                               //
+                               // This can be hard to reverse and may produce loops,
+                               // so they may be disabled in the site configuration.
+                               $source = $this->mTitle->getFullURL( 'redirect=no' );
+                               return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
+                       } else {
+                               // External pages pages without "local" bit set are not valid
+                               // redirect targets
+                               return false;
+                       }
+               }
+
+               if ( $rt->isSpecialPage() ) {
+                       // Gotta handle redirects to special pages differently:
+                       // Fill the HTTP response "Location" header and ignore
+                       // the rest of the page we're on.
+                       //
+                       // Some pages are not valid targets
+                       if ( $rt->isValidRedirectTarget() ) {
+                               return $rt->getFullURL();
+                       } else {
+                               return false;
+                       }
+               }
+
+               return $rt;
+       }
+
        /**
         * Get a list of users who have edited this article, not including the user who made
         * the most recent revision, which you can get from $article->getUser() if you want it
@@ -875,6 +889,7 @@ class WikiPage extends Page {
 
        /**
         * Perform the actions of a page purging
+        * @return bool
         */
        public function doPurge() {
                global $wgUseSquid;
@@ -890,7 +905,7 @@ class WikiPage extends Page {
                if ( $wgUseSquid ) {
                        // Commit the transaction before the purge is sent
                        $dbw = wfGetDB( DB_MASTER );
-                       $dbw->commit();
+                       $dbw->commit( __METHOD__ );
 
                        // Send purge
                        $update = SquidUpdate::newSimplePurge( $this->mTitle );
@@ -1010,7 +1025,7 @@ class WikiPage extends Page {
         * @param $dbw DatabaseBase
         * @param $redirectTitle Title object pointing to the redirect target,
         *                       or NULL if this is not a redirect
-        * @param $lastRevIsRedirect If given, will optimize adding and
+        * @param $lastRevIsRedirect null|bool If given, will optimize adding and
         *                           removing rows in redirect table.
         * @return bool true on success, false on failure
         * @private
@@ -1021,7 +1036,7 @@ class WikiPage extends Page {
                // Delete if changing from redirect to non-redirect
                $isRedirect = !is_null( $redirectTitle );
 
-               if ( !$isRedirect && !is_null( $lastRevIsRedirect ) && $lastRevIsRedirect === $isRedirect ) {
+               if ( !$isRedirect && $lastRevIsRedirect === false ) {
                        return true;
                }
 
@@ -1046,7 +1061,7 @@ class WikiPage extends Page {
         * If the given revision is newer than the currently set page_latest,
         * update the page record. Otherwise, do nothing.
         *
-        * @param $dbw Database object
+        * @param $dbw DatabaseBase object
         * @param $revision Revision object
         * @return mixed
         */
@@ -1081,7 +1096,37 @@ class WikiPage extends Page {
        }
 
        /**
-        * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...)
+        * Get the text that needs to be saved in order to undo all revisions
+        * between $undo and $undoafter. Revisions must belong to the same page,
+        * must exist and must not be deleted
+        * @param $undo Revision
+        * @param $undoafter Revision Must be an earlier revision than $undo
+        * @return mixed string on success, false on failure
+        */
+       public function getUndoText( Revision $undo, Revision $undoafter = null ) {
+               $cur_text = $this->getRawText();
+               if ( $cur_text === false ) {
+                       return false; // no page
+               }
+               $undo_text = $undo->getText();
+               $undoafter_text = $undoafter->getText();
+
+               if ( $cur_text == $undo_text ) {
+                       # No use doing a merge if it's just a straight revert.
+                       return $undoafter_text;
+               }
+
+               $undone_text = '';
+
+               if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
+                       return false;
+               }
+
+               return $undone_text;
+       }
+
+       /**
+        * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
         * @param $text String: new text of the section
         * @param $sectionTitle String: new section's subject, only if $section is 'new'
         * @param $edittime String: revision timestamp or null to use the current revision
@@ -1098,6 +1143,7 @@ class WikiPage extends Page {
                                $oldtext = $this->getRawText();
                                if ( $oldtext === false ) {
                                        wfDebug( __METHOD__ . ": no page text\n" );
+                                       wfProfileOut( __METHOD__ );
                                        return null;
                                }
                        } else {
@@ -1179,7 +1225,7 @@ class WikiPage extends Page {
         * edit-already-exists error will be returned. These two conditions are also possible with
         * auto-detection due to MediaWiki's performance-optimised locking strategy.
         *
-        * @param $baseRevId the revision ID this edit was based off, if any
+        * @param $baseRevId int the revision ID this edit was based off, if any
         * @param $user User the user doing the edit
         *
         * @return Status object. Possible errors:
@@ -1255,6 +1301,15 @@ class WikiPage extends Page {
                        # Update article, but only if changed.
                        $status->value['new'] = false;
 
+                       if ( !$oldid ) {
+                               # Article gone missing
+                               wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
+                               $status->fatal( 'edit-gone-missing' );
+
+                               wfProfileOut( __METHOD__ );
+                               return $status;
+                       }
+
                        # Make sure the revision is either completely inserted or not inserted at all
                        if ( !$wgDBtransactions ) {
                                $userAbort = ignore_user_abort( true );
@@ -1274,16 +1329,7 @@ class WikiPage extends Page {
                        $changed = ( strcmp( $text, $oldtext ) != 0 );
 
                        if ( $changed ) {
-                               if ( !$this->mLatest ) {
-                                       # Article gone missing
-                                       wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
-                                       $status->fatal( 'edit-gone-missing' );
-
-                                       wfProfileOut( __METHOD__ );
-                                       return $status;
-                               }
-
-                               $dbw->begin();
+                               $dbw->begin( __METHOD__ );
                                $revisionId = $revision->insertOn( $dbw );
 
                                # Update page
@@ -1305,7 +1351,7 @@ class WikiPage extends Page {
                                        }
 
                                        $revisionId = 0;
-                                       $dbw->rollback();
+                                       $dbw->rollback( __METHOD__ );
                                } else {
                                        global $wgUseRCPatrol;
                                        wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
@@ -1322,12 +1368,16 @@ class WikiPage extends Page {
 
                                                # Log auto-patrolled edits
                                                if ( $patrolled ) {
-                                                       PatrolLog::record( $rc, true );
+                                                       PatrolLog::record( $rc, true, $user );
                                                }
                                        }
                                        $user->incEditCount();
-                                       $dbw->commit();
+                                       $dbw->commit( __METHOD__ );
                                }
+                       } else {
+                               // Bug 32948: revision ID must be set to page {{REVISIONID}} and
+                               // related variables correctly
+                               $revision->setId( $this->getLatest() );
                        }
 
                        if ( !$wgDBtransactions ) {
@@ -1347,8 +1397,6 @@ class WikiPage extends Page {
                        if ( !$changed ) {
                                $status->warning( 'edit-no-change' );
                                $revision = null;
-                               // Keep the same revision ID, but do some updates on it
-                               $revisionId = $this->getLatest();
                                // Update page_touched, this is usually implicit in the page update
                                // Other cache updates are done in onArticleEdit()
                                $this->mTitle->invalidateCache();
@@ -1357,14 +1405,14 @@ class WikiPage extends Page {
                        # Create new article
                        $status->value['new'] = true;
 
-                       $dbw->begin();
+                       $dbw->begin( __METHOD__ );
 
                        # Add the page record; stake our claim on this title!
                        # This will return false if the article already exists
                        $newid = $this->insertOn( $dbw );
 
                        if ( $newid === false ) {
-                               $dbw->rollback();
+                               $dbw->rollback( __METHOD__ );
                                $status->fatal( 'edit-already-exists' );
 
                                wfProfileOut( __METHOD__ );
@@ -1401,11 +1449,11 @@ class WikiPage extends Page {
 
                                # Log auto-patrolled edits
                                if ( $patrolled ) {
-                                       PatrolLog::record( $rc, true );
+                                       PatrolLog::record( $rc, true, $user );
                                }
                        }
                        $user->incEditCount();
-                       $dbw->commit();
+                       $dbw->commit( __METHOD__ );
 
                        # Update links, etc.
                        $this->doEditUpdates( $revision, $user, array( 'created' => true ) );
@@ -1452,6 +1500,7 @@ class WikiPage extends Page {
        /**
         * Prepare text which is about to be saved.
         * Returns a stdclass with source, pst and output members
+        * @return bool|object
         */
        public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
                global $wgParser, $wgContLang, $wgUser;
@@ -1867,23 +1916,46 @@ class WikiPage extends Page {
        }
 
        /**
-        * Back-end article deletion
+        * Same as doDeleteArticleReal(), but returns more detailed success/failure status
         * Deletes the article with database consistency, writes logs, purges caches
         *
         * @param $reason string delete reason for deletion log
-        * @param $suppress bitfield
+        * @param $suppress int bitfield
         *      Revision::DELETED_TEXT
         *      Revision::DELETED_COMMENT
         *      Revision::DELETED_USER
         *      Revision::DELETED_RESTRICTED
         * @param $id int article ID
         * @param $commit boolean defaults to true, triggers transaction end
-        * @param &$errors Array of errors to append to
-        * @param $user User The relevant user
+        * @param &$error Array of errors to append to
+        * @param $user User The deleting user
         * @return boolean true if successful
         */
        public function doDeleteArticle(
                $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+       ) {
+               return $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user )
+                       == WikiPage::DELETE_SUCCESS;
+       }
+
+       /**
+        * Back-end article deletion
+        * Deletes the article with database consistency, writes logs, purges caches
+        *
+        * @param $reason string delete reason for deletion log
+        * @param $suppress int bitfield
+        *      Revision::DELETED_TEXT
+        *      Revision::DELETED_COMMENT
+        *      Revision::DELETED_USER
+        *      Revision::DELETED_RESTRICTED
+        * @param $id int article ID
+        * @param $commit boolean defaults to true, triggers transaction end
+        * @param &$error Array of errors to append to
+        * @param $user User The deleting user
+        * @return int: One of WikiPage::DELETE_* constants
+        */
+       public function doDeleteArticleReal(
+               $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
        ) {
                global $wgUser;
                $user = is_null( $user ) ? $wgUser : $user;
@@ -1891,20 +1963,16 @@ class WikiPage extends Page {
                wfDebug( __METHOD__ . "\n" );
 
                if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error ) ) ) {
-                       return false;
+                       return WikiPage::DELETE_HOOK_ABORTED;
                }
                $dbw = wfGetDB( DB_MASTER );
                $t = $this->mTitle->getDBkey();
                $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
 
                if ( $t === '' || $id == 0 ) {
-                       return false;
+                       return WikiPage::DELETE_NO_PAGE;
                }
 
-               DeferredUpdates::addUpdate(
-                       new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 )
-               );
-
                // Bitfields to further suppress the content
                if ( $suppress ) {
                        $bitfield = 0;
@@ -1917,7 +1985,7 @@ class WikiPage extends Page {
                        $bitfield = 'rev_deleted';
                }
 
-               $dbw->begin();
+               $dbw->begin( __METHOD__ );
                // For now, shunt the revision data into the archive table.
                // Text is *not* removed from the text table; bulk storage
                // is left intact to avoid breaking block-compression or
@@ -1952,18 +2020,48 @@ class WikiPage extends Page {
                        ), __METHOD__
                );
 
-               # Delete restrictions for it
-               $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
-
                # Now that it's safely backed up, delete it
                $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
-               $ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy
+               $ok = ( $dbw->affectedRows() > 0 ); // getArticleID() uses slave, could be laggy
 
                if ( !$ok ) {
-                       $dbw->rollback();
-                       return false;
+                       $dbw->rollback( __METHOD__ );
+                       return WikiPage::DELETE_NO_REVISIONS;
                }
 
+               $this->doDeleteUpdates( $id );
+
+               # Log the deletion, if the page was suppressed, log it at Oversight instead
+               $logtype = $suppress ? 'suppress' : 'delete';
+
+               $logEntry = new ManualLogEntry( $logtype, 'delete' );
+               $logEntry->setPerformer( $user );
+               $logEntry->setTarget( $this->mTitle );
+               $logEntry->setComment( $reason );
+               $logid = $logEntry->insert();
+               $logEntry->publish( $logid );
+
+               if ( $commit ) {
+                       $dbw->commit( __METHOD__ );
+               }
+
+               wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
+               return WikiPage::DELETE_SUCCESS;
+       }
+
+       /**
+        * Do some database updates after deletion
+        *
+        * @param $id Int: page_id value of the page being deleted
+        */
+       public function doDeleteUpdates( $id ) {
+               DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               # Delete restrictions for it
+               $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
+
                # Fix category table counts
                $cats = array();
                $res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
@@ -2006,25 +2104,11 @@ class WikiPage extends Page {
                # Clear caches
                self::onArticleDelete( $this->mTitle );
 
+               # Reset this object
+               $this->clear();
+
                # Clear the cached article id so the interface doesn't act like we exist
                $this->mTitle->resetArticleID( 0 );
-
-               # Log the deletion, if the page was suppressed, log it at Oversight instead
-               $logtype = $suppress ? 'suppress' : 'delete';
-
-               $logEntry = new ManualLogEntry( $logtype, 'delete' );
-               $logEntry->setPerformer( $user );
-               $logEntry->setTarget( $this->mTitle );
-               $logEntry->setComment( $reason );
-               $logid = $logEntry->insert();
-               $logEntry->publish( $logid );
-
-               if ( $commit ) {
-                       $dbw->commit();
-               }
-
-               wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
-               return true;
        }
 
        /**
@@ -2091,6 +2175,7 @@ class WikiPage extends Page {
         *
         * @param $resultDetails Array: contains result-specific array of additional values
         * @param $guser User The user performing the rollback
+        * @return array
         */
        public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) {
                global $wgUseRCPatrol, $wgContLang;
@@ -2102,7 +2187,7 @@ class WikiPage extends Page {
                }
 
                # Get the last editor
-               $current = Revision::newFromTitle( $this->mTitle );
+               $current = $this->getRevision();
                if ( is_null( $current ) ) {
                        # Something wrong... no page?
                        return array( array( 'notanarticle' ) );
@@ -2191,7 +2276,7 @@ class WikiPage extends Page {
                }
 
                # Actually store the edit
-               $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
+               $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId(), $guser );
                if ( !empty( $status->value['revision'] ) ) {
                        $revId = $status->value['revision']->getId();
                } else {
@@ -2305,35 +2390,6 @@ class WikiPage extends Page {
 
        /**#@-*/
 
-       /**
-        * Return a list of templates used by this article.
-        * Uses the templatelinks table
-        *
-        * @return Array of Title objects
-        */
-       public function getUsedTemplates() {
-               $result = array();
-               $id = $this->mTitle->getArticleID();
-
-               if ( $id == 0 ) {
-                       return array();
-               }
-
-               $dbr = wfGetDB( DB_SLAVE );
-               $res = $dbr->select( array( 'templatelinks' ),
-                       array( 'tl_namespace', 'tl_title' ),
-                       array( 'tl_from' => $id ),
-                       __METHOD__ );
-
-               if ( $res !== false ) {
-                       foreach ( $res as $row ) {
-                               $result[] = Title::makeTitle( $row->tl_namespace, $row->tl_title );
-                       }
-               }
-
-               return $result;
-       }
-
        /**
         * Returns a list of hidden categories this page is a member of.
         * Uses the page_props and categorylinks tables.
@@ -2429,9 +2485,8 @@ class WikiPage extends Page {
        public function getAutoDeleteReason( &$hasHistory ) {
                global $wgContLang;
 
-               $dbw = wfGetDB( DB_MASTER );
                // Get the last revision
-               $rev = Revision::newFromTitle( $this->getTitle() );
+               $rev = $this->getRevision();
 
                if ( is_null( $rev ) ) {
                        return false;
@@ -2452,6 +2507,8 @@ class WikiPage extends Page {
                        }
                }
 
+               $dbw = wfGetDB( DB_MASTER );
+
                // Find out if there was only one contributor
                // Only scan the last 20 revisions
                $res = $dbw->select( 'revision', 'rev_user_text',
@@ -2627,6 +2684,17 @@ class WikiPage extends Page {
                }
        }
 
+       /**
+        * Return a list of templates used by this article.
+        * Uses the templatelinks table
+        *
+        * @deprecated in 1.19; use Title::getTemplateLinksFrom()
+        * @return Array of Title objects
+        */
+       public function getUsedTemplates() {
+               return $this->mTitle->getTemplateLinksFrom();
+       }
+
        /**
         * Perform article updates on a special page creation.
         *
@@ -2732,6 +2800,7 @@ class WikiPage extends Page {
 
        /**
         * @deprecated since 1.18
+        * @return bool
         */
        public function useParserCache( $oldid ) {
                wfDeprecated( __METHOD__, '1.18' );
@@ -2768,7 +2837,7 @@ class PoolWorkArticleView extends PoolCounterWork {
        private $text;
 
        /**
-        * @var ParserOutput|false
+        * @var ParserOutput|bool
         */
        private $parserOutput = false;
 
@@ -2778,7 +2847,7 @@ class PoolWorkArticleView extends PoolCounterWork {
        private $isDirty = false;
 
        /**
-        * @var Status|false
+        * @var Status|bool
         */
        private $error = false;
 
@@ -2822,7 +2891,7 @@ class PoolWorkArticleView extends PoolCounterWork {
        /**
         * Get a Status object in case of error or false otherwise
         *
-        * @return Status|false
+        * @return Status|bool
         */
        public function getError() {
                return $this->error;
@@ -2848,10 +2917,10 @@ class PoolWorkArticleView extends PoolCounterWork {
                        $text = $rev->getText();
                }
 
-               $time = - wfTime();
+               $time = - microtime( true );
                $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
                        $this->parserOptions, true, true, $this->revid );
-               $time += wfTime();
+               $time += microtime( true );
 
                # Timing hack
                if ( $time > 3 ) {
@@ -2912,6 +2981,7 @@ class PoolWorkArticleView extends PoolCounterWork {
 
        /**
         * @param $status Status
+        * @return bool
         */
        function error( $status ) {
                $this->error = $status;