Clean up old title on move before reset article id
[lhc/web/wiklou.git] / includes / WikiPage.php
index ce26fb9..1965982 100644 (file)
@@ -23,7 +23,8 @@
 /**
  * Abstract class for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
  */
-interface Page {}
+interface Page {
+}
 
 /**
  * Class representing a MediaWiki article and history.
@@ -541,6 +542,7 @@ class WikiPage implements Page, IDBAccessObject {
                $db = wfGetDB( DB_SLAVE );
                $revSelectFields = Revision::selectFields();
 
+               $row = null;
                while ( $continue ) {
                        $row = $db->selectRow(
                                array( 'page', 'revision' ),
@@ -1023,8 +1025,8 @@ class WikiPage implements Page, IDBAccessObject {
 
        /**
         * Get the last N authors
-        * @param $num Integer: number of revisions to get
-        * @param string $revLatest the latest rev_id, selected from the master (optional)
+        * @param int $num Number of revisions to get
+        * @param int|string $revLatest the latest rev_id, selected from the master (optional)
         * @return array Array of authors, duplicates not removed
         */
        public function getLastNAuthors( $num, $revLatest = 0 ) {
@@ -1095,8 +1097,8 @@ class WikiPage implements Page, IDBAccessObject {
         * The parser cache will be used if possible.
         *
         * @since 1.19
-        * @param $parserOptions ParserOptions to use for the parse operation
-        * @param $oldid Revision ID to get the text from, passing null or 0 will
+        * @param ParserOptions $parserOptions ParserOptions to use for the parse operation
+        * @param null|int $oldid Revision ID to get the text from, passing null or 0 will
         *               get the current revision (default value)
         *
         * @return ParserOutput or false if the revision was not found
@@ -1622,7 +1624,7 @@ class WikiPage implements Page, IDBAccessObject {
         * 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 bool|\the $baseRevId the revision ID this edit was based off, if any
+        * @param bool|int $baseRevId the revision ID this edit was based off, if any
         * @param $user User the user doing the edit
         * @param $serialisation_format String: format for storing the content in the database
         *
@@ -1710,6 +1712,10 @@ class WikiPage implements Page, IDBAccessObject {
 
                $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
                $serialized = $editInfo->pst;
+
+               /**
+                * @var Content $content
+                */
                $content = $editInfo->pstContent;
                $newsize = $content->getSize();
 
@@ -1979,16 +1985,18 @@ class WikiPage implements Page, IDBAccessObject {
         * Prepare content which is about to be saved.
         * Returns a stdclass with source, pst and output members
         *
-        * @param \Content $content
-        * @param null $revid
-        * @param null|\User $user
-        * @param null $serialization_format
+        * @param Content $content
+        * @param int|null $revid
+        * @param User|null $user
+        * @param string|null $serialization_format
         *
         * @return bool|object
         *
         * @since 1.21
         */
-       public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) {
+       public function prepareContentForEdit( Content $content, $revid = null, User $user = null,
+               $serialization_format = null
+       ) {
                global $wgContLang, $wgUser;
                $user = is_null( $user ) ? $wgUser : $user;
                //XXX: check $user->getId() here???
@@ -2174,7 +2182,7 @@ class WikiPage implements Page, IDBAccessObject {
                ContentHandler::deprecated( __METHOD__, "1.21" );
 
                $content = ContentHandler::makeContent( $text, $this->getTitle() );
-               return $this->doQuickEditContent( $content, $user, $comment, $minor );
+               $this->doQuickEditContent( $content, $user, $comment, $minor );
        }
 
        /**
@@ -2182,13 +2190,15 @@ class WikiPage implements Page, IDBAccessObject {
         * The article must already exist; link tables etc
         * are not updated, caches are not flushed.
         *
-        * @param $content Content: content submitted
-        * @param $user User The relevant user
+        * @param Content $content Content submitted
+        * @param User $user The relevant user
         * @param string $comment comment submitted
-        * @param $serialisation_format String: format for storing the content in the database
-        * @param $minor Boolean: whereas it's a minor modification
+        * @param string $serialisation_format Format for storing the content in the database
+        * @param bool $minor Whereas it's a minor modification
         */
-       public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = 0, $serialisation_format = null ) {
+       public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
+               $serialisation_format = null
+       ) {
                wfProfileIn( __METHOD__ );
 
                $serialized = $content->serialize( $serialisation_format );
@@ -2215,14 +2225,14 @@ class WikiPage implements Page, IDBAccessObject {
         * This works for protection both existing and non-existing pages.
         *
         * @param array $limit set of restriction keys
-        * @param $reason String
-        * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
         * @param array $expiry per restriction type expiration
-        * @param $user User The user updating the restrictions
+        * @param int &$cascade Set to false if cascading protection isn't allowed.
+        * @param string $reason
+        * @param User $user The user updating the restrictions
         * @return Status
         */
        public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
-               global $wgContLang, $wgCascadingRestrictionLevels;
+               global $wgCascadingRestrictionLevels;
 
                if ( wfReadOnly() ) {
                        return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
@@ -2295,51 +2305,6 @@ class WikiPage implements Page, IDBAccessObject {
                        $logAction = 'protect';
                }
 
-               $encodedExpiry = array();
-               $protectDescription = '';
-               # Some bots may parse IRC lines, which are generated from log entries which contain plain
-               # protect description text. Keep them in old format to avoid breaking compatibility.
-               # TODO: Fix protection log to store structured description and format it on-the-fly.
-               $protectDescriptionLog = '';
-               foreach ( $limit as $action => $restrictions ) {
-                       $encodedExpiry[$action] = $dbw->encodeExpiry( $expiry[$action] );
-                       if ( $restrictions != '' ) {
-                               $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] (";
-                               # $action is one of $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ).
-                               # All possible message keys are listed here for easier grepping:
-                               # * restriction-create
-                               # * restriction-edit
-                               # * restriction-move
-                               # * restriction-upload
-                               $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
-                               # $restrictions is one of $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ),
-                               # with '' filtered out. All possible message keys are listed below:
-                               # * protect-level-autoconfirmed
-                               # * protect-level-sysop
-                               $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
-                               if ( $encodedExpiry[$action] != 'infinity' ) {
-                                       $expiryText = wfMessage(
-                                               'protect-expiring',
-                                               $wgContLang->timeanddate( $expiry[$action], false, false ),
-                                               $wgContLang->date( $expiry[$action], false, false ),
-                                               $wgContLang->time( $expiry[$action], false, false )
-                                       )->inContentLanguage()->text();
-                               } else {
-                                       $expiryText = wfMessage( 'protect-expiry-indefinite' )
-                                               ->inContentLanguage()->text();
-                               }
-
-                               if ( $protectDescription !== '' ) {
-                                       $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
-                               }
-                               $protectDescription .= wfMessage( 'protect-summary-desc' )
-                                       ->params( $actionText, $restrictionsText, $expiryText )
-                                       ->inContentLanguage()->text();
-                               $protectDescriptionLog .= $expiryText . ') ';
-                       }
-               }
-               $protectDescriptionLog = trim( $protectDescriptionLog );
-
                if ( $id ) { // Protection of existing page
                        if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
                                return Status::newGood();
@@ -2367,28 +2332,9 @@ class WikiPage implements Page, IDBAccessObject {
                                $cascade = false;
                        }
 
-                       // Prepare a null revision to be added to the history
-                       $editComment = $wgContLang->ucfirst(
-                               wfMessage(
-                                       $revCommentMsg,
-                                       $this->mTitle->getPrefixedText()
-                               )->inContentLanguage()->text()
-                       );
-                       if ( $reason ) {
-                               $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
-                       }
-                       if ( $protectDescription ) {
-                               $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
-                               $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )->inContentLanguage()->text();
-                       }
-                       if ( $cascade ) {
-                               $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
-                               $editComment .= wfMessage( 'brackets' )->params(
-                                       wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
-                               )->inContentLanguage()->text();
-                       }
-
-                       $nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true );
+                       // insert null revision to identify the page protection change as edit summary
+                       $latest = $this->getLatest();
+                       $nullRevision = $this->insertProtectNullRevision( $revCommentMsg, $limit, $expiry, $cascade, $reason );
                        if ( $nullRevision === null ) {
                                return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
                        }
@@ -2401,7 +2347,7 @@ class WikiPage implements Page, IDBAccessObject {
                                                        'pr_type' => $action,
                                                        'pr_level' => $restrictions,
                                                        'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
-                                                       'pr_expiry' => $encodedExpiry[$action]
+                                                       'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
                                                ),
                                                __METHOD__
                                        );
@@ -2411,19 +2357,12 @@ class WikiPage implements Page, IDBAccessObject {
                                }
                        }
 
-                       // Insert a null revision
-                       $nullRevId = $nullRevision->insertOn( $dbw );
-
-                       $latest = $this->getLatest();
-                       // Update page record
-                       $dbw->update( 'page',
-                               array( /* SET */
-                                       'page_touched' => $dbw->timestamp(),
-                                       'page_restrictions' => '',
-                                       'page_latest' => $nullRevId
-                               ), array( /* WHERE */
-                                       'page_id' => $id
-                               ), __METHOD__
+                       // Clear out legacy restriction fields
+                       $dbw->update(
+                               'page',
+                               array( 'page_restrictions' => '' ),
+                               array( 'page_id' => $id ),
+                               __METHOD__
                        );
 
                        wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
@@ -2440,7 +2379,7 @@ class WikiPage implements Page, IDBAccessObject {
                                                'pt_title' => $this->mTitle->getDBkey(),
                                                'pt_create_perm' => $limit['create'],
                                                'pt_timestamp' => $dbw->encodeExpiry( wfTimestampNow() ),
-                                               'pt_expiry' => $encodedExpiry['create'],
+                                               'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
                                                'pt_user' => $user->getId(),
                                                'pt_reason' => $reason,
                                        ), __METHOD__
@@ -2459,18 +2398,150 @@ class WikiPage implements Page, IDBAccessObject {
                InfoAction::invalidateCache( $this->mTitle );
 
                if ( $logAction == 'unprotect' ) {
-                       $logParams = array();
+                       $params = array();
                } else {
-                       $logParams = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
+                       $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
+                       $params = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
                }
 
                // Update the protection log
                $log = new LogPage( 'protect' );
-               $log->addEntry( $logAction, $this->mTitle, trim( $reason ), $logParams, $user );
+               $log->addEntry( $logAction, $this->mTitle, trim( $reason ), $params, $user );
 
                return Status::newGood();
        }
 
+       /**
+        * Insert a new null revision for this page.
+        *
+        * @param string $revCommentMsg comment message key for the revision
+        * @param array $limit set of restriction keys
+        * @param array $expiry per restriction type expiration
+        * @param int $cascade Set to false if cascading protection isn't allowed.
+        * @param string $reason
+        * @return Revision|null on error
+        */
+       public function insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason ) {
+               global $wgContLang;
+               $dbw = wfGetDB( DB_MASTER );
+
+               // Prepare a null revision to be added to the history
+               $editComment = $wgContLang->ucfirst(
+                       wfMessage(
+                               $revCommentMsg,
+                               $this->mTitle->getPrefixedText()
+                       )->inContentLanguage()->text()
+               );
+               if ( $reason ) {
+                       $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
+               }
+               $protectDescription = $this->protectDescription( $limit, $expiry );
+               if ( $protectDescription ) {
+                       $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+                       $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )->inContentLanguage()->text();
+               }
+               if ( $cascade ) {
+                       $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+                       $editComment .= wfMessage( 'brackets' )->params(
+                               wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
+                       )->inContentLanguage()->text();
+               }
+
+               $nullRev = Revision::newNullRevision( $dbw, $this->getId(), $editComment, true );
+               if ( $nullRev ) {
+                       $nullRev->insertOn( $dbw );
+
+                       // Update page record and touch page
+                       $oldLatest = $nullRev->getParentId();
+                       $this->updateRevisionOn( $dbw, $nullRev, $oldLatest );
+               }
+
+               return $nullRev;
+       }
+
+       /**
+        * @param string $expiry 14-char timestamp or "infinity", or false if the input was invalid
+        * @return string
+        */
+       protected function formatExpiry( $expiry ) {
+               global $wgContLang;
+               $dbr = wfGetDB( DB_SLAVE );
+
+               $encodedExpiry = $dbr->encodeExpiry( $expiry );
+               if ( $encodedExpiry != 'infinity' ) {
+                       return wfMessage(
+                               'protect-expiring',
+                               $wgContLang->timeanddate( $expiry, false, false ),
+                               $wgContLang->date( $expiry, false, false ),
+                               $wgContLang->time( $expiry, false, false )
+                       )->inContentLanguage()->text();
+               } else {
+                       return wfMessage( 'protect-expiry-indefinite' )
+                               ->inContentLanguage()->text();
+               }
+       }
+
+       /**
+        * Builds the description to serve as comment for the edit.
+        *
+        * @param array $limit set of restriction keys
+        * @param array $expiry per restriction type expiration
+        * @return string
+        */
+       public function protectDescription( array $limit, array $expiry ) {
+               $protectDescription = '';
+
+               foreach ( array_filter( $limit ) as $action => $restrictions ) {
+                       # $action is one of $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ).
+                       # All possible message keys are listed here for easier grepping:
+                       # * restriction-create
+                       # * restriction-edit
+                       # * restriction-move
+                       # * restriction-upload
+                       $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
+                       # $restrictions is one of $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ),
+                       # with '' filtered out. All possible message keys are listed below:
+                       # * protect-level-autoconfirmed
+                       # * protect-level-sysop
+                       $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
+
+                       $expiryText = $this->formatExpiry( $expiry[$action] );
+
+                       if ( $protectDescription !== '' ) {
+                               $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+                       }
+                       $protectDescription .= wfMessage( 'protect-summary-desc' )
+                               ->params( $actionText, $restrictionsText, $expiryText )
+                               ->inContentLanguage()->text();
+               }
+
+               return $protectDescription;
+       }
+
+       /**
+        * Builds the description to serve as comment for the log entry.
+        *
+        * Some bots may parse IRC lines, which are generated from log entries which contain plain
+        * protect description text. Keep them in old format to avoid breaking compatibility.
+        * TODO: Fix protection log to store structured description and format it on-the-fly.
+        *
+        * @param array $limit set of restriction keys
+        * @param array $expiry per restriction type expiration
+        * @return string
+        */
+       public function protectDescriptionLog( array $limit, array $expiry ) {
+               global $wgContLang;
+
+               $protectDescriptionLog = '';
+
+               foreach ( array_filter( $limit ) as $action => $restrictions ) {
+                       $expiryText = $this->formatExpiry( $expiry[$action] );
+                       $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] ($expiryText)";
+               }
+
+               return trim( $protectDescriptionLog );
+       }
+
        /**
         * Take an array of page restrictions and flatten it to a string
         * suitable for insertion into the page_restrictions field.
@@ -2486,10 +2557,8 @@ class WikiPage implements Page, IDBAccessObject {
                $bits = array();
                ksort( $limit );
 
-               foreach ( $limit as $action => $restrictions ) {
-                       if ( $restrictions != '' ) {
-                               $bits[] = "$action=$restrictions";
-                       }
+               foreach ( array_filter( $limit ) as $action => $restrictions ) {
+                       $bits[] = "$action=$restrictions";
                }
 
                return implode( ':', $bits );
@@ -2637,6 +2706,10 @@ class WikiPage implements Page, IDBAccessObject {
                        return $status;
                }
 
+               if ( !$dbw->cascadingDeletes() ) {
+                       $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
+               }
+
                $this->doDeleteUpdates( $id, $content );
 
                // Log the deletion, if the page was suppressed, log it at Oversight instead
@@ -3294,7 +3367,7 @@ class WikiPage implements Page, IDBAccessObject {
        public function viewUpdates() {
                wfDeprecated( __METHOD__, '1.18' );
                global $wgUser;
-               return $this->doViewUpdates( $wgUser );
+               $this->doViewUpdates( $wgUser );
        }
 
        /**
@@ -3379,7 +3452,7 @@ class PoolWorkArticleView extends PoolCounterWork {
        /**
         * Constructor
         *
-        * @param $page Page
+        * @param $page Page|WikiPage
         * @param $revid Integer: ID of the revision being parsed
         * @param $useParserCache Boolean: whether to use the parser cache
         * @param $parserOptions parserOptions to use for the parse operation
@@ -3459,6 +3532,9 @@ class PoolWorkArticleView extends PoolCounterWork {
                        return false;
                }
 
+               // Reduce effects of race conditions for slow parses (bug 46014)
+               $cacheTime = wfTimestampNow();
+
                $time = - microtime( true );
                $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
                $time += microtime( true );
@@ -3470,7 +3546,8 @@ class PoolWorkArticleView extends PoolCounterWork {
                }
 
                if ( $this->cacheable && $this->parserOutput->isCacheable() ) {
-                       ParserCache::singleton()->save( $this->parserOutput, $this->page, $this->parserOptions );
+                       ParserCache::singleton()->save(
+                               $this->parserOutput, $this->page, $this->parserOptions, $cacheTime );
                }
 
                // Make sure file cache is not used on uncacheable content.