From 46c5d3edad6b9f07aa49ca382912019f4081f7a0 Mon Sep 17 00:00:00 2001 From: Alexandre Emsenhuber Date: Sun, 18 Dec 2011 16:01:31 +0000 Subject: [PATCH] * Merged WikiPage::updateRestrictions() and Title::updateTitleProtection() into WikiPage::doUpdateRestrictions(); older methods still work for backward compatibility, but marked Title::updateTitleProtection() as deprecated and for removal in 1.20 since no extension calls it * Removed permissions check from WikiPage::doUpdateRestrictions() and left it for callers, resolves the todo from documentation * Inverted $expiry and $reason parameter between WikiPage::doUpdateRestrictions() and WikiPage::updateRestrictions() for more consistency; WikiPage::doUpdateRestrictions() also requires all parameters to be passed * WikiPage::doUpdateRestrictions() returns a Status object instead of bool for the older one; only possible error at the moment is a read-only database * Updated core calls to these functions * Made maintenance scripts using it simply protect all actions returned by Title::getRestrictionTypes() instead of hardcoded 'edit' and 'move' * This also means that protect.php can be used to protect a non-existing page for creation --- includes/Article.php | 12 ++ includes/Title.php | 67 ++------ includes/WikiPage.php | 313 ++++++++++++++++++++--------------- includes/api/ApiProtect.php | 16 +- maintenance/importImages.php | 46 ++--- maintenance/protect.php | 25 ++- 6 files changed, 250 insertions(+), 229 deletions(-) diff --git a/includes/Article.php b/includes/Article.php index a5084f1d6e..d32bec7f12 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -1782,6 +1782,18 @@ class Article extends Page { // ****** B/C functions to work-around PHP silliness with __call and references ****** // + /** + * @param $limit array + * @param $expiry array + * @param $cascade bool + * @param $reason string + * @param $user User + * @return Status + */ + public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) { + return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user ); + } + /** * @param $limit array * @param $reason string diff --git a/includes/Title.php b/includes/Title.php index 6dd79d24c7..1e312d4884 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -2219,66 +2219,24 @@ class Title { /** * Update the title protection status * + * @deprecated in 1.19; will be removed in 1.20. Use WikiPage::doUpdateRestrictions() instead. * @param $create_perm String Permission required for creation * @param $reason String Reason for protection * @param $expiry String Expiry timestamp * @return boolean true */ public function updateTitleProtection( $create_perm, $reason, $expiry ) { - global $wgUser, $wgContLang; - - if ( $create_perm == implode( ',', $this->getRestrictions( 'create' ) ) - && $expiry == $this->mRestrictionsExpiry['create'] ) { - // No change - return true; - } - - list ( $namespace, $title ) = array( $this->getNamespace(), $this->getDBkey() ); - - $dbw = wfGetDB( DB_MASTER ); - - $encodedExpiry = $dbw->encodeExpiry( $expiry ); + wfDeprecated( __METHOD__, '1.19' ); - $expiry_description = ''; - if ( $encodedExpiry != $dbw->getInfinity() ) { - $expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ), - $wgContLang->date( $expiry ) , $wgContLang->time( $expiry ) ) . ')'; - } else { - $expiry_description .= ' (' . wfMsgForContent( 'protect-expiry-indefinite' ) . ')'; - } - - # Update protection table - if ( $create_perm != '' ) { - $this->mTitleProtection = array( - 'pt_namespace' => $namespace, - 'pt_title' => $title, - 'pt_create_perm' => $create_perm, - 'pt_timestamp' => $dbw->encodeExpiry( wfTimestampNow() ), - 'pt_expiry' => $encodedExpiry, - 'pt_user' => $wgUser->getId(), - 'pt_reason' => $reason, - ); - $dbw->replace( 'protected_titles', array( array( 'pt_namespace', 'pt_title' ) ), - $this->mTitleProtection, __METHOD__ ); - } else { - $dbw->delete( 'protected_titles', array( 'pt_namespace' => $namespace, - 'pt_title' => $title ), __METHOD__ ); - $this->mTitleProtection = false; - } + global $wgUser; - # Update the protection log - if ( $dbw->affectedRows() ) { - $log = new LogPage( 'protect' ); + $imit = array( 'create' => $create_perm ); + $expiry = array( 'create' => $expiry ); - if ( $create_perm ) { - $params = array( "[create=$create_perm] $expiry_description", '' ); - $log->addEntry( ( isset( $this->mRestrictions['create'] ) && $this->mRestrictions['create'] ) ? 'modify' : 'protect', $this, trim( $reason ), $params ); - } else { - $log->addEntry( 'unprotect', $this, $reason ); - } - } + $page = WikiPage::factory( $this ); + $status = $page->doUpdateRestrictions( $limit, $expiry, false, $reason, $wgUser ); - return true; + return $status->isOK(); } /** @@ -2664,6 +2622,15 @@ class Title { } } + /** + * Flush the protection cache in this object and force reload from the database. + * This is used when updating protection from WikiPage::doUpdateRestrictions(). + */ + public function flushRestrictions() { + $this->mRestrictionsLoaded = false; + $this->mTitleProtection = null; + } + /** * Purge expired restrictions from the page_restrictions table */ diff --git a/includes/WikiPage.php b/includes/WikiPage.php index ac0ab02ff9..1e67cfc60b 100644 --- a/includes/WikiPage.php +++ b/includes/WikiPage.php @@ -1324,8 +1324,7 @@ class WikiPage extends Page { /** * Update the article's restriction field, and leave a log entry. - * - * @todo: seperate the business/permission stuff out from backend code + * This works for protection both existing and non-existing pages. * * @param $limit Array: set of restriction keys * @param $reason String @@ -1334,30 +1333,16 @@ class WikiPage extends Page { * @param $user User The user updating the restrictions * @return bool true on success */ - public function updateRestrictions( - $limit = array(), $reason = '', &$cascade = 0, $expiry = array(), User $user = null - ) { - global $wgUser, $wgContLang; - $user = is_null( $user ) ? $wgUser : $user; - - $restrictionTypes = $this->mTitle->getRestrictionTypes(); - - $id = $this->mTitle->getArticleID(); - - if ( $id <= 0 ) { - wfDebug( "updateRestrictions failed: article id $id <= 0\n" ); - return false; - } + public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) { + global $wgContLang; if ( wfReadOnly() ) { - wfDebug( "updateRestrictions failed: read-only\n" ); - return false; + return Status::newFatal( 'readonlytext', wfReadOnlyReason() ); } - if ( count( $this->mTitle->getUserPermissionsErrors( 'protect', $user ) ) ) { - wfDebug( "updateRestrictions failed: insufficient permissions\n" ); - return false; - } + $restrictionTypes = $this->mTitle->getRestrictionTypes(); + + $id = $this->mTitle->getArticleID(); if ( !$cascade ) { $cascade = false; @@ -1368,151 +1353,182 @@ class WikiPage extends Page { # @todo FIXME: Same limitations as described in ProtectionForm.php (line 37); # we expect a single selection, but the schema allows otherwise. - $current = array(); - $updated = self::flattenRestrictions( $limit ); + $isProtected = false; + $protect = false; $changed = false; + $dbw = wfGetDB( DB_MASTER ); + foreach ( $restrictionTypes as $action ) { - if ( isset( $expiry[$action] ) ) { - # Get current restrictions on $action - $aLimits = $this->mTitle->getRestrictions( $action ); - $current[$action] = implode( '', $aLimits ); - # Are any actual restrictions being dealt with here? - $aRChanged = count( $aLimits ) || !empty( $limit[$action] ); - - # If something changed, we need to log it. Checking $aRChanged - # assures that "unprotecting" a page that is not protected does - # not log just because the expiry was "changed". - if ( $aRChanged && - $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) - { + if ( !isset( $expiry[$action] ) ) { + $expiry[$action] = $dbw->getInfinity(); + } + if ( !isset( $limit[$action] ) ) { + $limit[$action] = ''; + } elseif ( $limit[$action] != '' ) { + $protect = true; + } + + # Get current restrictions on $action + $current = implode( '', $this->mTitle->getRestrictions( $action ) ); + if ( $current != '' ) { + $isProtected = true; + } + + if ( $limit[$action] != $current ) { + $changed = true; + } elseif ( $limit[$action] != '' ) { + # Only check expiry change if the action is actually being + # protected, since expiry does nothing on an not-protected + # action. + if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) { $changed = true; } } } - $current = self::flattenRestrictions( $current ); - - $changed = ( $changed || $current != $updated ); - $changed = $changed || ( $updated && $this->mTitle->areRestrictionsCascading() != $cascade ); - $protect = ( $updated != '' ); + if ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) { + $changed = true; + } # If nothing's changed, do nothing - if ( $changed ) { - if ( wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) { - $dbw = wfGetDB( DB_MASTER ); + if ( !$changed ) { + return Status::newGood(); + } - # Prepare a null revision to be added to the history - $modified = $current != '' && $protect; + if ( !$protect ) { # No protection at all means unprotection + $revCommentMsg = 'unprotectedarticle'; + $logAction = 'unprotect'; + } elseif ( $isProtected ) { + $revCommentMsg = 'modifiedarticleprotection'; + $logAction = 'modify'; + } else { + $revCommentMsg = 'protectedarticle'; + $logAction = 'protect'; + } - if ( $protect ) { - $comment_type = $modified ? 'modifiedarticleprotection' : 'protectedarticle'; + $encodedExpiry = array(); + $protectDescription = ''; + foreach ( $limit as $action => $restrictions ) { + $encodedExpiry[$action] = $dbw->encodeExpiry( $expiry[$action] ); + if ( $restrictions != '' ) { + $protectDescription .= $wgContLang->getDirMark() . "[$action=$restrictions] ("; + if ( $encodedExpiry[$action] != 'infinity' ) { + $protectDescription .= wfMsgForContent( 'protect-expiring', + $wgContLang->timeanddate( $expiry[$action], false, false ) , + $wgContLang->date( $expiry[$action], false, false ) , + $wgContLang->time( $expiry[$action], false, false ) ); } else { - $comment_type = 'unprotectedarticle'; + $protectDescription .= wfMsgForContent( 'protect-expiry-indefinite' ); } - $comment = $wgContLang->ucfirst( wfMsgForContent( $comment_type, $this->mTitle->getPrefixedText() ) ); - - # Only restrictions with the 'protect' right can cascade... - # Otherwise, people who cannot normally protect can "protect" pages via transclusion - $editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' ); - - # The schema allows multiple restrictions - if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) ) { - $cascade = false; - } - - $cascade_description = ''; + $protectDescription .= ') '; + } + } + $protectDescription = trim( $protectDescription ); - if ( $cascade ) { - $cascade_description = ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']'; - } + if ( $id ) { # Protection of existing page + if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) { + return Status::newGood(); + } - if ( $reason ) { - $comment .= ": $reason"; - } + # Only restrictions with the 'protect' right can cascade... + # Otherwise, people who cannot normally protect can "protect" pages via transclusion + $editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' ); - $editComment = $comment; - $encodedExpiry = array(); - $protect_description = ''; - foreach ( $limit as $action => $restrictions ) { - if ( !isset( $expiry[$action] ) ) - $expiry[$action] = $dbw->getInfinity(); - - $encodedExpiry[$action] = $dbw->encodeExpiry( $expiry[$action] ); - if ( $restrictions != '' ) { - $protect_description .= $wgContLang->getDirMark() . "[$action=$restrictions] ("; - if ( $encodedExpiry[$action] != 'infinity' ) { - $protect_description .= wfMsgForContent( 'protect-expiring', - $wgContLang->timeanddate( $expiry[$action], false, false ) , - $wgContLang->date( $expiry[$action], false, false ) , - $wgContLang->time( $expiry[$action], false, false ) ); - } else { - $protect_description .= wfMsgForContent( 'protect-expiry-indefinite' ); - } + # The schema allows multiple restrictions + if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) ) { + $cascade = false; + } - $protect_description .= ') '; - } + # Update restrictions table + foreach ( $limit as $action => $restrictions ) { + if ( $restrictions != '' ) { + $dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ), + array( 'pr_page' => $id, + 'pr_type' => $action, + 'pr_level' => $restrictions, + 'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0, + 'pr_expiry' => $encodedExpiry[$action] + ), + __METHOD__ + ); + } else { + $dbw->delete( 'page_restrictions', array( 'pr_page' => $id, + 'pr_type' => $action ), __METHOD__ ); } - $protect_description = trim( $protect_description ); + } - if ( $protect_description && $protect ) { - $editComment .= " ($protect_description)"; - } + # Prepare a null revision to be added to the history + $editComment = $wgContLang->ucfirst( wfMsgForContent( $revCommentMsg, $this->mTitle->getPrefixedText() ) ); + if ( $reason ) { + $editComment .= ": $reason"; + } + if ( $protectDescription ) { + $editComment .= " ($protectDescription)"; + } + if ( $cascade ) { + $editComment .= ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']'; + } - if ( $cascade ) { - $editComment .= "$cascade_description"; - } + # Insert a null revision + $nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true ); + $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__ + ); - # Update restrictions table - foreach ( $limit as $action => $restrictions ) { - if ( $restrictions != '' ) { - $dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ), - array( 'pr_page' => $id, - 'pr_type' => $action, - 'pr_level' => $restrictions, - 'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0, - 'pr_expiry' => $encodedExpiry[$action] - ), - __METHOD__ - ); - } else { - $dbw->delete( 'page_restrictions', array( 'pr_page' => $id, - 'pr_type' => $action ), __METHOD__ ); - } - } + wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) ); + wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) ); + } else { # Protection of non-existing page (also known as "title protection") + # Cascade protection is meaningless in this case + $cascade = false; - # Insert a null revision - $nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true ); - $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 + if ( $limit['create'] != '' ) { + $dbw->replace( 'protected_titles', + array( array( 'pt_namespace', 'pt_title' ) ), + array( + 'pt_namespace' => $this->mTitle->getNamespace(), + 'pt_title' => $this->mTitle->getDBkey(), + 'pt_create_perm' => $limit['create'], + 'pt_timestamp' => $dbw->encodeExpiry( wfTimestampNow() ), + 'pt_expiry' => $encodedExpiry['create'], + 'pt_user' => $user->getId(), + 'pt_reason' => $reason, + ), __METHOD__ + ); + } else { + $dbw->delete( 'protected_titles', + array( + 'pt_namespace' => $this->mTitle->getNamespace(), + 'pt_title' => $this->mTitle->getDBkey() ), __METHOD__ ); + } + } - wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) ); - wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) ); + $this->mTitle->flushRestrictions(); - # Update the protection log - $log = new LogPage( 'protect' ); - if ( $protect ) { - $params = array( $protect_description, $cascade ? 'cascade' : '' ); - $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason ), $params ); - } else { - $log->addEntry( 'unprotect', $this->mTitle, $reason ); - } - } # End hook - } # End "changed" check + if ( $logAction == 'unprotect' ) { + $logParams = array(); + } else { + $logParams = array( $protectDescription, $cascade ? 'cascade' : '' ); + } - return true; + # Update the protection log + $log = new LogPage( 'protect' ); + $log->addEntry( $logAction, $this->mTitle, trim( $reason ), $logParams ); + + return Status::newGood(); } /** @@ -2683,6 +2699,27 @@ class WikiPage extends Page { } } + /** + * Update the article's restriction field, and leave a log entry. + * + * @deprecated since 1.19 + * @param $limit Array: set of restriction keys + * @param $reason String + * @param &$cascade Integer. Set to false if cascading protection isn't allowed. + * @param $expiry Array: per restriction type expiration + * @param $user User The user updating the restrictions + * @return bool true on success + */ + public function updateRestrictions( + $limit = array(), $reason = '', &$cascade = 0, $expiry = array(), User $user = null + ) { + global $wgUser; + + $user = is_null( $user ) ? $wgUser : $user; + + return $this->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user )->isOK(); + } + /** * @deprecated since 1.18 */ diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php index 255c913475..fb225d86b6 100644 --- a/includes/api/ApiProtect.php +++ b/includes/api/ApiProtect.php @@ -106,16 +106,12 @@ class ApiProtect extends ApiBase { $watch = $params['watch'] ? 'watch' : $params['watchlist']; $this->setWatch( $watch, $titleObj ); - if ( $titleObj->exists() ) { - $pageObj = WikiPage::factory( $titleObj ); - $ok = $pageObj->updateRestrictions( $protections, $params['reason'], $cascade, $expiryarray ); - } else { - $ok = $titleObj->updateTitleProtection( $protections['create'], $params['reason'], $expiryarray['create'] ); - } - if ( !$ok ) { - // This is very weird. Maybe the article was deleted or the user was blocked/desysopped in the meantime? - // Just throw an unknown error in this case, as it's very likely to be a race condition - $this->dieUsageMsg( array() ); + $pageObj = WikiPage::factory( $titleObj ); + $status = $pageObj->doUpdateRestrictions( $protections, $expiryarray, $cascade, $params['reason'], $this->getUser() ); + + if ( !$status->isOK() ) { + $errors = $status->getErrorsArray(); + $this->dieUsageMsg( $errors[0] ); } $res = array( 'title' => $titleObj->getPrefixedText(), diff --git a/maintenance/importImages.php b/maintenance/importImages.php index 17e3f0dbf4..7e4d5e230d 100644 --- a/maintenance/importImages.php +++ b/maintenance/importImages.php @@ -231,44 +231,44 @@ if ( $count > 0 ) { } } - $doProtect = false; - $restrictions = array(); + if ( isset( $options['dry'] ) ) { + echo( "done.\n" ); + } elseif ( $image->recordUpload( $archive->value, $commentText, $license ) ) { + # We're done! + echo( "done.\n" ); - global $wgRestrictionLevels; + $doProtect = false; - $protectLevel = isset( $options['protect'] ) ? $options['protect'] : null; + global $wgRestrictionLevels; - if ( $protectLevel && in_array( $protectLevel, $wgRestrictionLevels ) ) { - $restrictions['move'] = $protectLevel; - $restrictions['edit'] = $protectLevel; + $protectLevel = isset( $options['protect'] ) ? $options['protect'] : null; + + if ( $protectLevel && in_array( $protectLevel, $wgRestrictionLevels ) ) { $doProtect = true; - } - if ( isset( $options['unprotect'] ) ) { - $restrictions['move'] = ''; - $restrictions['edit'] = ''; + } + if ( isset( $options['unprotect'] ) ) { + $protectLevel = ''; $doProtect = true; - } - + } - if ( isset( $options['dry'] ) ) { - echo( "done.\n" ); - } elseif ( $image->recordUpload( $archive->value, $commentText, $license ) ) { - # We're done! - echo( "done.\n" ); if ( $doProtect ) { # Protect the file - $article = new Article( $title ); echo "\nWaiting for slaves...\n"; // Wait for slaves. sleep( 2.0 ); # Why this sleep? wfWaitForSlaves(); echo( "\nSetting image restrictions ... " ); - if ( $article->updateRestrictions( $restrictions ) ) { - echo( "done.\n" ); - } else { - echo( "failed.\n" ); + + $cascade = false; + $restrictions = array(); + foreach( $title->getRestrictionTypes() as $type ) { + $restrictions[$type] = $protectLevel; } + + $page = WikiPage::factory( $title ); + $status = $page->doUpdateRestrictions( $restrictions, array(), $cascade, '', $user ); + echo( ( $status->isOK() ? 'done' : 'failed' ) . "\n" ); } } else { diff --git a/maintenance/protect.php b/maintenance/protect.php index c3043967d4..56958ea77e 100644 --- a/maintenance/protect.php +++ b/maintenance/protect.php @@ -28,8 +28,9 @@ class Protect extends Maintenance { $this->mDescription = "Protect or unprotect an article from the command line."; $this->addOption( 'unprotect', 'Removes protection' ); $this->addOption( 'semiprotect', 'Adds semi-protection' ); - $this->addOption( 'u', 'Username to protect with', false, true ); - $this->addOption( 'r', 'Reason for un/protection', false, true ); + $this->addOption( 'cascade', 'Add cascading protection' ); + $this->addOption( 'user', 'Username to protect with', false, true, 'u' ); + $this->addOption( 'reason', 'Reason for un/protection', false, true, 'r' ); $this->addArg( 'title', 'Title to protect', true ); } @@ -39,6 +40,8 @@ class Protect extends Maintenance { $userName = $this->getOption( 'u', 'Maintenance script' ); $reason = $this->getOption( 'r', '' ); + $cascade = $this->hasOption( 'cascade' ); + $protection = "sysop"; if ( $this->hasOption( 'semiprotect' ) ) { $protection = "autoconfirmed"; @@ -46,11 +49,11 @@ class Protect extends Maintenance { $protection = ""; } - $wgUser = User::newFromName( $userName ); - if ( !$wgUser ) { + $user = User::newFromName( $userName ); + if ( !$user ) { $this->error( "Invalid username", true ); } - + $restrictions = array( 'edit' => $protection, 'move' => $protection ); $t = Title::newFromText( $this->getArg() ); @@ -58,12 +61,18 @@ class Protect extends Maintenance { $this->error( "Invalid title", true ); } - $article = new Article( $t ); + $restrictions = array(); + foreach( $t->getRestrictionTypes() as $type ) { + $restrictions[$type] = $protection; + } # un/protect the article $this->output( "Updating protection status... " ); - $success = $article->updateRestrictions( $restrictions, $reason ); - if ( $success ) { + + $page = WikiPage::factory( $t ); + $status = $page->doUpdateRestrictions( $restrictions, array(), $cascade, $reason, $user ); + + if ( $status->isOK() ) { $this->output( "done\n" ); } else { $this->output( "failed\n" ); -- 2.20.1