* (bug 2919) Allow the protection of non-existent pages using the regular protection...
authorAndrew Garrett <werdna@users.mediawiki.org>
Tue, 11 Dec 2007 09:51:56 +0000 (09:51 +0000)
committerAndrew Garrett <werdna@users.mediawiki.org>
Tue, 11 Dec 2007 09:51:56 +0000 (09:51 +0000)
* 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
includes/Article.php
includes/ProtectionForm.php
includes/SkinTemplate.php
includes/Title.php
languages/messages/MessagesEn.php
maintenance/archives/patch-protected_titles.sql [new file with mode: 0644]
maintenance/tables.sql
maintenance/updaters.inc

index 37b44c5..6c11ef2 100644 (file)
@@ -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 ===
 
index f89ceac..a6a1d01 100644 (file)
@@ -2898,6 +2898,7 @@ class Article {
 
                $title->touchLinks();
                $title->purgeSquid();
+               $title->deleteTitleProtection();
        }
 
        static function onArticleDelete( $title ) {
index 3bfd033..b042bb7 100644 (file)
@@ -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 .= "<table id='mwProtectSet'>";
                $out .= "<tbody>";
                $out .= "<tr>\n";
+
                foreach( $this->mRestrictions as $action => $required ) {
                        /* Not all languages have V_x <-> N_x relation */
                        $out .= "<th>" . wfMsgHtml( 'restriction-' . $action ) . "</th>\n";
@@ -244,7 +251,7 @@ class ProtectionForm {
                $out .= "<tbody>\n";
 
                global $wgEnableCascadingProtection;
-               if( $wgEnableCascadingProtection )
+               if( $wgEnableCascadingProtection && $this->mTitle->exists() )
                        $out .= '<tr><td></td><td>' . $this->buildCascadeInput() . "</td></tr>\n";
 
                $out .= $this->buildExpiryInput();
index 2e6c893..c0e2007 100644 (file)
@@ -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 ) {
index cf8e295..eebe4f9 100644 (file)
@@ -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
index 474d66e..fb2c679 100644 (file)
@@ -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 <i>$2</i>.',
 
 # Login and logout pages
 'logouttitle'                => 'User logout',
@@ -1956,6 +1957,7 @@ Here are the current settings for the page <strong>$1</strong>:',
 # 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 (file)
index 0000000..da1df17
--- /dev/null
@@ -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
index 72a779a..adc22e2 100644 (file)
@@ -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
index 9069d64..97267a0 100644 (file)
@@ -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(