From 34d22f6edc19de43263a3f4ce9502cab7d270585 Mon Sep 17 00:00:00 2001 From: Andrew Garrett Date: Tue, 11 Dec 2007 09:51:56 +0000 Subject: [PATCH] * (bug 2919) Allow the protection of non-existent pages using the regular protection interface. * WARNING: This revision requires a schema change, which is included in maintenance/archives/patch-protected_titles.sql, and can be applied in the normal manner (update.php). --- RELEASE-NOTES | 1 + includes/Article.php | 1 + includes/ProtectionForm.php | 17 ++- includes/SkinTemplate.php | 49 +++++-- includes/Title.php | 137 ++++++++++++++++-- languages/messages/MessagesEn.php | 2 + .../archives/patch-protected_titles.sql | 13 ++ maintenance/tables.sql | 14 ++ maintenance/updaters.inc | 1 + 9 files changed, 199 insertions(+), 36 deletions(-) create mode 100644 maintenance/archives/patch-protected_titles.sql diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 37b44c58af..6c11ef2d7a 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -94,6 +94,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN * (bug 11657) Support for Thai solar calendar * (bug 943) RSS feed for Recentchangeslinked * Introduced AbortMove hook +* (bug 2919) Protection of nonexistent pages with regular protection interface. === Bug fixes in 1.12 === diff --git a/includes/Article.php b/includes/Article.php index f89ceac93c..a6a1d01914 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -2898,6 +2898,7 @@ class Article { $title->touchLinks(); $title->purgeSquid(); + $title->deleteTitleProtection(); } static function onArticleDelete( $title ) { diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php index 3bfd03347d..b042bb7cc9 100644 --- a/includes/ProtectionForm.php +++ b/includes/ProtectionForm.php @@ -29,17 +29,19 @@ class ProtectionForm { var $mCascade = false; var $mExpiry = null; var $mPermErrors = array(); + var $mApplicableTypes = array(); function __construct( &$article ) { global $wgRequest, $wgUser; global $wgRestrictionTypes, $wgRestrictionLevels; $this->mArticle =& $article; $this->mTitle =& $article->mTitle; + $this->mApplicableTypes = $this->mTitle->exists() ? $wgRestrictionTypes : array('create'); if( $this->mTitle ) { $this->mTitle->loadRestrictions(); - foreach( $wgRestrictionTypes as $action ) { + foreach( $this->mApplicableTypes as $action ) { // Fixme: this form currently requires individual selections, // but the db allows multiples separated by commas. $this->mRestrictions[$action] = implode( '', $this->mTitle->getRestrictions( $action ) ); @@ -67,7 +69,7 @@ class ProtectionForm { $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' ); $this->mExpiry = $wgRequest->getText( 'mwProtect-expiry' ); - foreach( $wgRestrictionTypes as $action ) { + foreach( $this->mApplicableTypes as $action ) { $val = $wgRequest->getVal( "mwProtect-level-$action" ); if( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) { $this->mRestrictions[$action] = $val; @@ -95,7 +97,6 @@ class ProtectionForm { $wgOut->setRobotpolicy( 'noindex,nofollow' ); if( is_null( $this->mTitle ) || - !$this->mTitle->exists() || $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { $wgOut->showFatalError( wfMsg( 'badarticleerror' ) ); return; @@ -185,7 +186,12 @@ class ProtectionForm { !(isset($wgGroupPermissions[$edit_restriction]['protect']) && $wgGroupPermissions[$edit_restriction]['protect'] ) ) $this->mCascade = false; - $ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $this->mReason, $this->mCascade, $expiry ); + if ($this->mTitle->exists()) { + $ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $this->mReason, $this->mCascade, $expiry ); + } else { + $ok = $this->mTitle->updateTitleProtection( $this->mRestrictions['create'], $this->mReason, $expiry ); + } + if( !$ok ) { throw new FatalError( "Unknown error at restriction save time." ); } @@ -222,6 +228,7 @@ class ProtectionForm { $out .= ""; $out .= ""; $out .= "\n"; + foreach( $this->mRestrictions as $action => $required ) { /* Not all languages have V_x <-> N_x relation */ $out .= "\n"; @@ -244,7 +251,7 @@ class ProtectionForm { $out .= "\n"; global $wgEnableCascadingProtection; - if( $wgEnableCascadingProtection ) + if( $wgEnableCascadingProtection && $this->mTitle->exists() ) $out .= '\n"; $out .= $this->buildExpiryInput(); diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php index 2e6c8938d6..c0e2007454 100644 --- a/includes/SkinTemplate.php +++ b/includes/SkinTemplate.php @@ -718,6 +718,22 @@ class SkinTemplate extends Skin { 'href' => $this->mTitle->getLocalUrl( 'action=history') ); + if($wgUser->isAllowed('delete')){ + $content_actions['delete'] = array( + 'class' => ($action == 'delete') ? 'selected' : false, + 'text' => wfMsg('delete'), + 'href' => $this->mTitle->getLocalUrl( 'action=delete' ) + ); + } + if ( $this->mTitle->quickUserCan( 'move' ) ) { + $moveTitle = SpecialPage::getTitleFor( 'Movepage', $this->thispage ); + $content_actions['move'] = array( + 'class' => $this->mTitle->isSpecial( 'Movepage' ) ? 'selected' : false, + 'text' => wfMsg('move'), + 'href' => $moveTitle->getLocalUrl() + ); + } + if ( $this->mTitle->getNamespace() !== NS_MEDIAWIKI && $wgUser->isAllowed( 'protect' ) ) { if(!$this->mTitle->isProtected()){ $content_actions['protect'] = array( @@ -734,21 +750,6 @@ class SkinTemplate extends Skin { ); } } - if($wgUser->isAllowed('delete')){ - $content_actions['delete'] = array( - 'class' => ($action == 'delete') ? 'selected' : false, - 'text' => wfMsg('delete'), - 'href' => $this->mTitle->getLocalUrl( 'action=delete' ) - ); - } - if ( $this->mTitle->quickUserCan( 'move' ) ) { - $moveTitle = SpecialPage::getTitleFor( 'Movepage', $this->thispage ); - $content_actions['move'] = array( - 'class' => $this->mTitle->isSpecial( 'Movepage' ) ? 'selected' : false, - 'text' => wfMsg('move'), - 'href' => $moveTitle->getLocalUrl() - ); - } } else { //article doesn't exist or is deleted if( $wgUser->isAllowed( 'delete' ) ) { @@ -762,7 +763,25 @@ class SkinTemplate extends Skin { ); } } + + if ( $this->mTitle->getNamespace() !== NS_MEDIAWIKI && $wgUser->isAllowed( 'protect' ) ) { + if(!is_array($this->mTitle->getTitleProtection())){ + $content_actions['protect'] = array( + 'class' => ($action == 'protect') ? 'selected' : false, + 'text' => wfMsg('protect'), + 'href' => $this->mTitle->getLocalUrl( 'action=protect' ) + ); + + } else { + $content_actions['unprotect'] = array( + 'class' => ($action == 'unprotect') ? 'selected' : false, + 'text' => wfMsg('unprotect'), + 'href' => $this->mTitle->getLocalUrl( 'action=unprotect' ) + ); + } + } } + wfProfileOut( "$fname-live" ); if( $this->loggedin ) { diff --git a/includes/Title.php b/includes/Title.php index cf8e295ad2..eebe4f97bc 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -1195,6 +1195,22 @@ class Title { } } + + if ($action == 'create') { + $title_protection = $this->getTitleProtection(); + + if (is_array($title_protection)) { + extract($title_protection); + + if ($pt_create_perm == 'sysop') + $pt_create_perm = 'protect'; + + if ($pt_create_perm == '' || !$user->isAllowed($pt_create_perm)) { + $errors[] = array ( 'titleprotected', User::whoIs($pt_by), $pt_reason ); + } + } + } + if( $action == 'create' ) { if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) || ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) { @@ -1235,6 +1251,77 @@ class Title { return $errors; } + /** + * Is this title subject to title protection? + * @return array An associative array representing any existent title protection. + */ + public function getTitleProtection() { + $dbr = wfGetDB( DB_SLAVE ); + + $res = $dbr->select( 'protected_titles', '*', + array ('pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBKey()) ); + + if ($row = $dbr->fetchRow( $res )) { + return $row; + } else { + return false; + } + } + + public function updateTitleProtection( $create_perm, $reason, $expiry ) { + global $wgGroupPermissions,$wgUser,$wgContLang; + + if ($create_perm == implode(',',$this->getRestrictions('create')) + && $expiry == $this->mRestrictionsExpiry) { + // No change + return true; + } + + list ($namespace, $title) = array( $this->getNamespace(), $this->getDBKey() ); + + $dbw = wfGetDB( DB_MASTER ); + + $encodedExpiry = Block::encodeExpiry($expiry, $dbw ); + + $expiry_description = ''; + if ( $encodedExpiry != 'infinity' ) { + $expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ) ).')'; + } + + # Update protection table + if ($create_perm != '' ) { + $dbw->replace( 'protected_titles', array(array('pt_namespace', 'pt_title')), + array( 'pt_namespace' => $namespace, 'pt_title' => $title + , 'pt_create_perm' => $create_perm + , 'pt_timestamp' => Block::encodeExpiry(wfTimestampNow(), $dbw) + , 'pt_expiry' => $encodedExpiry + , 'pt_by' => $wgUser->getId(), 'pt_reason' => $reason ), __METHOD__ ); + } else { + $dbw->delete( 'protected_titles', array( 'pt_namespace' => $namespace, + 'pt_title' => $title ), __METHOD__ ); + } + # Update the protection log + $log = new LogPage( 'protect' ); + + if( $create_perm ) { + $log->addEntry( $this->mRestrictions['create'] ? 'modify' : 'protect', $this, trim( $reason . " [create=$create_perm] $expiry_description" ) ); + } else { + $log->addEntry( 'unprotect', $this, $reason ); + } + + return true; + } + + /** + * Remove any title protection (due to page existing + */ + public function deleteTitleProtection() { + $dbw = wfGetDB( DB_MASTER ); + + $dbw->delete( 'protected_titles', + array ('pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBKey()), __METHOD__ ); + } + /** * Can $wgUser edit this page? * @return boolean @@ -1612,12 +1699,32 @@ class Title { public function loadRestrictions( $oldFashionedRestrictions = NULL ) { if( !$this->mRestrictionsLoaded ) { - $dbr = wfGetDB( DB_SLAVE ); + if ($this->exists()) { + $dbr = wfGetDB( DB_SLAVE ); + + $res = $dbr->select( 'page_restrictions', '*', + array ( 'pr_page' => $this->getArticleId() ), __METHOD__ ); + + $this->loadRestrictionsFromRow( $res, $oldFashionedRestrictions ); + } else { + $title_protection = $this->getTitleProtection(); - $res = $dbr->select( 'page_restrictions', '*', - array ( 'pr_page' => $this->getArticleId() ), __METHOD__ ); + if (is_array($title_protection)) { + extract($title_protection); - $this->loadRestrictionsFromRow( $res, $oldFashionedRestrictions ); + $now = wfTimestampNow(); + $expiry = Block::decodeExpiry($pt_expiry); + + if (!$expiry || $expiry > $now) { + // Apply the restrictions + $this->mRestrictionsExpiry = $expiry; + $this->mRestrictions['create'] = explode(',', trim($pt_create_perm) ); + } else { // Get rid of the old restrictions + Title::purgeExpiredRestrictions(); + } + } + $this->mRestrictionsLoaded = true; + } } } @@ -1629,6 +1736,10 @@ class Title { $dbw->delete( 'page_restrictions', array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ ); + + $dbw->delete( 'protected_titles', + array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), + __METHOD__ ); } /** @@ -1638,16 +1749,12 @@ class Title { * @return array the array of groups allowed to edit this article */ public function getRestrictions( $action ) { - if( $this->exists() ) { - if( !$this->mRestrictionsLoaded ) { - $this->loadRestrictions(); - } - return isset( $this->mRestrictions[$action] ) - ? $this->mRestrictions[$action] - : array(); - } else { - return array(); + if( !$this->mRestrictionsLoaded ) { + $this->loadRestrictions(); } + return isset( $this->mRestrictions[$action] ) + ? $this->mRestrictions[$action] + : array(); } /** @@ -2794,6 +2901,4 @@ class Title { return Namespace::isContent( $this->getNamespace() ); } -} - - +} \ No newline at end of file diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 474d66e172..fb2c679bd1 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -803,6 +803,7 @@ $2', 'namespaceprotected' => "You do not have permission to edit pages in the '''$1''' namespace.", 'customcssjsprotected' => "You do not have permission to edit this page, because it contains another user's personal settings.", 'ns-specialprotected' => 'Pages in the {{ns:special}} namespace cannot be edited.', +'titleprotected' => 'This title has been protected from creation by [[User:$1|$1]]. The reason given is $2.', # Login and logout pages 'logouttitle' => 'User logout', @@ -1956,6 +1957,7 @@ Here are the current settings for the page $1:', # Restrictions (nouns) 'restriction-edit' => 'Edit', 'restriction-move' => 'Move', +'restriction-create' => 'Create', # Restriction levels 'restriction-level-sysop' => 'full protected', diff --git a/maintenance/archives/patch-protected_titles.sql b/maintenance/archives/patch-protected_titles.sql new file mode 100644 index 0000000000..da1df179b9 --- /dev/null +++ b/maintenance/archives/patch-protected_titles.sql @@ -0,0 +1,13 @@ +-- Protected titles - nonexistent pages that have been protected +CREATE TABLE `/*$wgDBPrefix*/protected_titles` ( + `pt_namespace` int(11) NOT NULL, + `pt_title` varchar(255) NOT NULL, + `pt_by` int(10) unsigned NOT NULL, + `pt_reason` tinyblob, + `pt_timestamp` binary(14) NOT NULL, + `pt_expiry` varbinary(14) NOT NULL default '', + `pt_create_perm` varbinary(60) NOT NULL, + PRIMARY KEY (`pt_namespace`,`pt_title`), + KEY `pt_by` (`pt_by`), + KEY `pt_timestamp` (`pt_timestamp`) +) /*$wgDBTableOptions*/ \ No newline at end of file diff --git a/maintenance/tables.sql b/maintenance/tables.sql index 72a779a912..adc22e2cfa 100644 --- a/maintenance/tables.sql +++ b/maintenance/tables.sql @@ -1168,4 +1168,18 @@ CREATE TABLE /*$wgDBprefix*/page_restrictions ( KEY pr_cascade (pr_cascade) ) /*$wgDBTableOptions*/; +-- Protected titles - nonexistent pages that have been protected +CREATE TABLE `/*$wgDBPrefix*/protected_titles` ( + `pt_namespace` int(11) NOT NULL, + `pt_title` varchar(255) NOT NULL, + `pt_by` int(10) unsigned NOT NULL, + `pt_reason` tinyblob, + `pt_timestamp` binary(14) NOT NULL, + `pt_expiry` varbinary(14) NOT NULL default '', + `pt_create_perm` varbinary(60) NOT NULL, + PRIMARY KEY (`pt_namespace`,`pt_title`), + KEY `pt_by` (`pt_by`), + KEY `pt_timestamp` (`pt_timestamp`) +) /*$wgDBTableOptions*/ + -- vim: sw=2 sts=2 et diff --git a/maintenance/updaters.inc b/maintenance/updaters.inc index 9069d6415a..97267a09bf 100644 --- a/maintenance/updaters.inc +++ b/maintenance/updaters.inc @@ -38,6 +38,7 @@ $wgNewTables = array( array( 'filearchive', 'patch-filearchive.sql' ), array( 'querycachetwo', 'patch-querycachetwo.sql' ), array( 'redirect', 'patch-redirect.sql' ), + array( 'protected_titles', 'patch-protected_titles.sql' ), ); $wgNewFields = array( -- 2.20.1
" . wfMsgHtml( 'restriction-' . $action ) . "
' . $this->buildCascadeInput() . "