X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FWikiPage.php;h=1965982f1e9cb270b4c4017ba0330d593f493fab;hb=bc8f309bdad5f1f8b95fe14d8fd3bb126594c325;hp=ce26fb9c9ba27c54ab7dd8c086c6201734b22754;hpb=2b8b4884ec508d425b5556ca7d1842cb41ab3490;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/WikiPage.php b/includes/WikiPage.php index ce26fb9c9b..1965982f1e 100644 --- a/includes/WikiPage.php +++ b/includes/WikiPage.php @@ -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.