r12207 - Code Review

From MediaWiki.org

Jump to: navigation, search
Repository:MediaWiki
Revision:r12206 | r12207 (on ViewVC) | r12208 >
Date:05:41, 22 December 2005
Author:vibber
Status:new
Tags:
Comment:* (bug 1735) Revamped protection interface
* (bug 675) Add page protection level for unregistered/new accounts
* User::isNewbie now uses the registration date and $wgAutoconfirmAge
* Log views show message when no matches
Modified paths:

Diff [purge]

Index: trunk/phase3/maintenance/archives/patch-user_registration.sql
===================================================================
--- trunk/phase3/maintenance/archives/patch-user_registration.sql	(revision 0)
+++ trunk/phase3/maintenance/archives/patch-user_registration.sql	(revision 12207)
@@ -0,0 +1,9 @@
+--
+-- New user field for tracking registration time
+-- 2005-12-21
+--
+
+ALTER TABLE /*$wgDBprefix*/user
+  -- Timestamp of account registration.
+  -- Accounts predating this schema addition may contain NULL.
+  ADD user_registration CHAR(14) BINARY;

Property changes on: trunk/phase3/maintenance/archives/patch-user_registration.sql
___________________________________________________________________
Name: svn:eol-style
   + native
Name: svn:keywords
   + Author Date Id Revision

Index: trunk/phase3/maintenance/mysql5/tables.sql
===================================================================
--- trunk/phase3/maintenance/mysql5/tables.sql	(revision 12206)
+++ trunk/phase3/maintenance/mysql5/tables.sql	(revision 12207)
@@ -119,6 +119,10 @@
   -- Expiration date for the user_email_token
   user_email_token_expires CHAR(14) BINARY,
 
+  -- Timestamp of account registration.
+  -- Accounts predating this schema addition may contain NULL.
+  user_registration CHAR(14) BINARY,
+
   PRIMARY KEY user_id (user_id),
   UNIQUE INDEX user_name (user_name),
   INDEX (user_email_token)
Index: trunk/phase3/maintenance/updaters.inc
===================================================================
--- trunk/phase3/maintenance/updaters.inc	(revision 12206)
+++ trunk/phase3/maintenance/updaters.inc	(revision 12207)
@@ -39,6 +39,7 @@
 	array( 'user',          'user_real_name',   'patch-user-realname.sql' ),
 	array( 'user',          'user_token',       'patch-user_token.sql' ),
 	array( 'user',          'user_email_token', 'patch-user_email_token.sql' ),
+	array( 'user',          'user_registration','patch-user_registration.sql' ),
  	array( 'logging',       'log_params',       'patch-log_params.sql' ),
  	array( 'archive',       'ar_rev_id',        'patch-archive-rev_id.sql' ),
  	array( 'archive',       'ar_text_id',       'patch-archive-text_id.sql' ),
Index: trunk/phase3/maintenance/tables.sql
===================================================================
--- trunk/phase3/maintenance/tables.sql	(revision 12206)
+++ trunk/phase3/maintenance/tables.sql	(revision 12207)
@@ -105,6 +105,10 @@
   
   -- Expiration date for the user_email_token
   user_email_token_expires CHAR(14) BINARY,
+  
+  -- Timestamp of account registration.
+  -- Accounts predating this schema addition may contain NULL.
+  user_registration CHAR(14) BINARY,
 
   PRIMARY KEY user_id (user_id),
   UNIQUE INDEX user_name (user_name),
Index: trunk/phase3/skins/common/protect.js
===================================================================
--- trunk/phase3/skins/common/protect.js	(revision 0)
+++ trunk/phase3/skins/common/protect.js	(revision 12207)
@@ -0,0 +1,126 @@
+function protectInitialize(tableId, labelText) {
+	if (document.createTextNode) {
+		var box = document.getElementById(tableId);
+		if (!box)
+			return false;
+		
+		var tbody = box.getElementsByTagName('tbody')[0];
+		var row = document.createElement('tr');
+		tbody.appendChild(row);
+		
+		row.appendChild(document.createElement('td'));
+		var col2 = document.createElement('td');
+		row.appendChild(col2);
+		
+		var check = document.createElement('input');
+		check.id = "mwProtectUnchained";
+		check.type = "checkbox";
+		check.onclick = protectChainUpdate;
+		col2.appendChild(check);
+		
+		var label = document.createElement('label');
+		label.setAttribute("for", "mwProtectUnchained");
+		label.appendChild(document.createTextNode(labelText));
+		col2.appendChild(label);
+		
+		if (protectAllMatch()) {
+			check.checked = false;
+			protectEnable(false);
+		} else {
+			check.checked = true;
+			protectEnable(true);
+		}
+		
+		return true;
+	}
+	return false;
+}
+
+function protectLevelsUpdate(source) {
+	if (!protectUnchained()) {
+		protectUpdateAll(source.selectedIndex);
+	}
+}
+
+function protectChainUpdate() {
+	if (protectUnchained()) {
+		protectEnable(true);
+	} else {
+		protectChain();
+		protectEnable(false);
+	}
+}
+
+
+function protectAllMatch() {
+	var values = new Array();
+	protectForSelectors(function(set) {
+		values[values.length] = set.selectedIndex;
+	});
+	for (var i = 1; i < values.length; i++) {
+		if (values[i] != values[0]) {
+			return false;
+		}
+	}
+	return true;
+}
+
+function protectUnchained() {
+	var unchain = document.getElementById("mwProtectUnchained");
+	if (!unchain) {
+		alert("This shouldn't happen");
+		return false;
+	}
+	return unchain.checked;
+}
+
+function protectChain() {
+	// Find the highest-protected action and bump them all to this level
+	var maxIndex = -1;
+	protectForSelectors(function(set) {
+		if (set.selectedIndex > maxIndex) {
+			maxIndex = set.selectedIndex;
+		}
+	});
+	protectUpdateAll(maxIndex);
+}
+
+function protectUpdateAll(index) {
+	protectForSelectors(function(set) {
+		if (set.selectedIndex != index) {
+			set.selectedIndex = index;
+		}
+	});
+}
+
+function protectForSelectors(func) {
+	var selectors = protectSelectors();
+	for (var i = 0; i < selectors.length; i++) {
+		func(selectors[i]);
+	}
+}
+
+function protectSelectors() {
+	var all = document.getElementsByTagName("select");
+	var ours = new Array();
+	for (var i = 0; i < all.length; i++) {
+		var set = all[i];
+		if (set.id.match(/^mwProtect-level-/)) {
+			ours[ours.length] = set;
+		}
+	}
+	return ours;
+}
+
+function protectEnable(val) {
+	// fixme
+	var first = true;
+	protectForSelectors(function(set) {
+		if (first) {
+			first = false;
+		} else {
+			set.disabled = !val;
+			set.style.visible = val ? "visible" : "hidden";
+		}
+	});
+}

Property changes on: trunk/phase3/skins/common/protect.js
___________________________________________________________________
Name: svn:eol-style
   + native
Name: svn:keywords
   + Author Date Id Revision

Index: trunk/phase3/includes/ProtectionForm.php
===================================================================
--- trunk/phase3/includes/ProtectionForm.php	(revision 0)
+++ trunk/phase3/includes/ProtectionForm.php	(revision 12207)
@@ -0,0 +1,244 @@
+<?php
+/**
+ * Copyright (C) 2005 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or 
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @package MediaWiki
+ * @subpackage SpecialPage
+ */
+
+class ProtectionForm {
+	var $mRestrictions = array();
+	var $mReason = '';
+	
+	function ProtectionForm( &$article ) {
+		global $wgRequest, $wgUser;
+		global $wgRestrictionTypes, $wgRestrictionLevels;
+		$this->mArticle =& $article;
+		$this->mTitle =& $article->mTitle;
+		
+		if( $this->mTitle ) {
+			foreach( $wgRestrictionTypes 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 ) );
+			}
+		}
+		
+		// The form will be available in read-only to show levels.
+		$this->disabled = !$wgUser->isAllowed( 'protect' ) || wfReadOnly();
+		$this->disabledAttrib = $this->disabled
+			? array( 'disabled' => 'disabled' )
+			: array();
+		
+		if( $wgRequest->wasPosted() ) {
+			$this->mReason = $wgRequest->getText( 'mwProtect-reason' );
+			foreach( $wgRestrictionTypes as $action ) {
+				$val = $wgRequest->getVal( "mwProtect-level-$action" );
+				if( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
+					$this->mRestrictions[$action] = $val;
+				}
+			}
+		}
+	}
+	
+	function show() {
+		global $wgOut;
+		
+		$wgOut->setRobotpolicy( 'noindex,nofollow' );
+
+		if( is_null( $this->mTitle ) ||
+			!$this->mTitle->exists() ||
+			$this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+			$wgOut->fatalError( wfMsg( 'badarticleerror' ) );
+			return;
+		}
+		
+		if( $this->save() ) {
+			$wgOut->redirect( $this->mTitle->getFullUrl() );
+			return;
+		}
+		
+		$wgOut->setPageTitle( wfMsg( 'confirmprotect' ) );
+		$wgOut->setSubtitle( wfMsg( 'protectsub', $this->mTitle->getPrefixedText() ) );
+		
+		$wgOut->addWikiText(
+			wfMsg( $this->disabled ? "protect-viewtext" : "protect-text",
+				$this->mTitle->getPrefixedText() ) );
+		
+		$wgOut->addHTML( $this->buildForm() );
+		
+		$this->showLogExtract( $wgOut );
+	}
+	
+	function save() {
+		global $wgRequest, $wgUser;
+		if( !$wgRequest->wasPosted() ) {
+			return false;
+		}
+		
+		if( $this->disabled ) {
+			return false;
+		}
+		
+		$token = $wgRequest->getVal( 'wpEditToken' );
+		if( !$wgUser->matchEditToken( $token ) ) {
+			$wgOut->fatalError( wfMsg( 'sessionfailure' ) );
+			return false;
+		}
+		
+		$ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $this->mReason );
+		if( !$ok ) {
+			$wgOut->fatalError( "Unknown error at restriction save time." );
+		}
+		return $ok;
+	}
+	
+	function buildForm() {
+		global $wgUser;
+		
+		$out = '';
+		if( !$this->disabled ) {
+			$out .= $this->buildScript();
+			// The submission needs to reenable the move permission selector
+			// if it's in locked mode, or some browsers won't submit the data.
+			$out .= wfOpenElement( 'form', array(
+				'action' => $this->mTitle->getLocalUrl( 'action=protect' ),
+				'method' => 'post',
+				'onsubmit' => 'protectEnable(true)' ) );
+
+			$out .= wfElement( 'input', array(
+				'type' => 'hidden',
+				'name' => 'wpEditToken',
+				'value' => $wgUser->editToken() ) );
+		}
+		
+		$out .= "<table id='mwProtectSet'>";
+		$out .= "<tbody>";
+		$out .= "<tr>\n";
+		foreach( $this->mRestrictions as $action => $required ) {
+			$out .= "<th>" . wfMsgHtml( $action ) . "</th>\n";
+		}
+		$out .= "</tr>\n";
+		$out .= "<tr>\n";
+		foreach( $this->mRestrictions as $action => $selected ) {
+			$out .= "<td>\n";
+			$out .= $this->buildSelector( $action, $selected );
+			$out .= "</td>\n";
+		}
+		$out .= "</tr>\n";
+		
+		// JavaScript will add another row with a value-chaining checkbox
+		
+		$out .= "</tbody>\n";
+		$out .= "</table>\n";
+		
+		if( !$this->disabled ) {
+			$out .= "<table>\n";
+			$out .= "<tbody>\n";
+			$out .= "<tr><td>" . $this->buildReasonInput() . "</td></tr>\n";
+			$out .= "<tr><td></td><td>" . $this->buildSubmit() . "</td></tr>\n";
+			$out .= "</tbody>\n";
+			$out .= "</table>\n";
+			$out .= "</form>\n";
+			$out .= $this->buildCleanupScript();
+		}
+		
+		return $out;
+	}
+	
+	function buildSelector( $action, $selected ) {
+		global $wgRestrictionLevels;
+		$id = 'mwProtect-level-' . $action;
+		$attribs = array(
+			'id' => $id,
+			'name' => $id,
+			'size' => count( $wgRestrictionLevels ),
+			'onchange' => 'protectLevelsUpdate(this)',
+			) + $this->disabledAttrib;
+		
+		$out = wfOpenElement( 'select', $attribs );
+		foreach( $wgRestrictionLevels as $key ) {
+			$out .= $this->buildOption( $key, $selected );
+		}
+		$out .= "</select>\n";
+		return $out;
+	}
+	
+	function buildOption( $key, $selected ) {
+		$text = ( $key == '' )
+			? wfMsg( 'protect-default' )
+			: wfMsg( "protect-level-$key" );
+		$selectedAttrib = ($selected == $key)
+			? array( 'selected' => 'selected' )
+			: array();
+		return wfElement( 'option',
+			array( 'value' => $key ) + $selectedAttrib,
+			$text );
+	}
+	
+	function buildReasonInput() {
+		$id = 'mwProtect-reason';
+		return wfElement( 'label', array(
+				'id' => "$id-label",
+				'for' => $id ),
+				wfMsg( 'protectcomment' ) ) .
+			'</td><td>' .
+			wfElement( 'input', array(
+				'size' => 60,
+				'name' => $id,
+				'id' => $id ) );
+	}
+	
+	function buildSubmit() {
+		return wfElement( 'input', array(
+			'type' => 'submit',
+			'value' => wfMsg( 'confirm' ) ) );
+	}
+	
+	function buildScript() {
+		global $wgStylePath;
+		return '<script type="text/javascript" src="' .
+			htmlspecialchars( $wgStylePath . "/common/protect.js" ) .
+			'"></script>';
+	}
+	
+	function buildCleanupScript() {
+		return '<script type="text/javascript">protectInitialize("mwProtectSet","' .
+			wfEscapeJsString( wfMsg( 'protect-unchain' ) ) . '")</script>';
+	}
+	
+	/**
+	 * @param OutputPage $out
+	 * @access private
+	 */
+	function showLogExtract( &$out ) {
+		# Show relevant lines from the deletion log:
+		$out->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'protect' ) ) . "</h2>\n" );
+		require_once( 'SpecialLog.php' );
+		$logViewer = new LogViewer(
+			new LogReader(
+				new FauxRequest(
+					array( 'page' => $this->mTitle->getPrefixedText(),
+					       'type' => 'protect' ) ) ) );
+		$logViewer->showList( $out );
+	}
+}
+
+
+?>
\ No newline at end of file

Property changes on: trunk/phase3/includes/ProtectionForm.php
___________________________________________________________________
Name: svn:eol-style
   + native
Name: svn:keywords
   + Author Date Id Revision

Index: trunk/phase3/includes/User.php
===================================================================
--- trunk/phase3/includes/User.php	(revision 12206)
+++ trunk/phase3/includes/User.php	(revision 12207)
@@ -14,7 +14,7 @@
 define( 'USER_TOKEN_LENGTH', 32 );
 
 # Serialized record version
-define( 'MW_USER_VERSION', 2 );
+define( 'MW_USER_VERSION', 3 );
 
 /**
  *
@@ -36,6 +36,7 @@
 	var $mHash;
 	var $mGroups;
 	var $mVersion; // serialized version
+	var $mRegistration;
 
 	/** Construct using User:loadDefaults() */
 	function User()	{
@@ -107,7 +108,7 @@
 		return array( 'mId', 'mName', 'mPassword', 'mEmail', 'mNewtalk',
 			'mEmailAuthenticated', 'mRights', 'mOptions', 'mDataLoaded',
 			'mNewpassword', 'mBlockedby', 'mBlockreason', 'mTouched',
-			'mToken', 'mRealName', 'mHash', 'mGroups' );
+			'mToken', 'mRealName', 'mHash', 'mGroups', 'mRegistration' );
 	}
 
 	/**
@@ -321,6 +322,8 @@
 			$this->mTouched = '0'; # Allow any pages to be cached
 		}
 
+		$this->mRegistration = wfTimestamp( TS_MW );
+		
 		wfProfileOut( $fname );
 	}
 
@@ -651,7 +654,7 @@
 		} else {
 			wfDebug( "User::loadFromSession() got from cache!\n" );
 		}
-
+		
 		if ( isset( $_SESSION['wsToken'] ) ) {
 			$passwordCorrect = $_SESSION['wsToken'] == $user->mToken;
 		} else if ( isset( $_COOKIE["{$wgDBname}Token"] ) ) {
@@ -699,7 +702,7 @@
 		$dbr =& wfGetDB( DB_SLAVE );
 		$s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
 		  'user_email_authenticated',
-		  'user_real_name','user_options','user_touched', 'user_token' ),
+		  'user_real_name','user_options','user_touched', 'user_token', 'user_registration' ),
 		  array( 'user_id' => $this->mId ), $fname );
 
 		if ( $s !== false ) {
@@ -712,6 +715,7 @@
 			$this->decodeOptions( $s->user_options );
 			$this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
 			$this->mToken = $s->user_token;
+			$this->mRegistration = wfTimestamp( TS_MW, $s->user_registration );
 
 			$res = $dbr->select( 'user_groups',
 				array( 'ug_group' ),
@@ -721,7 +725,15 @@
 			while( $row = $dbr->fetchObject( $res ) ) {
 				$this->mGroups[] = $row->ug_group;
 			}
-			$effectiveGroups = array_merge( array( '*', 'user' ), $this->mGroups );
+			$implicitGroups = array( '*', 'user' );
+			
+			global $wgAutoConfirmAge;
+			$accountAge = time() - wfTimestampOrNull( TS_UNIX, $this->mRegistration );
+			if( $accountAge >= $wgAutoConfirmAge ) {
+				$implicitGroups[] = 'autoconfirmed';
+			}
+			
+			$effectiveGroups = array_merge( $implicitGroups, $this->mGroups );
 			$this->mRights = $this->getGroupPermissions( $effectiveGroups );
 		}
 
@@ -1392,7 +1404,8 @@
 				'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
 				'user_real_name' => $this->mRealName,
 				'user_options' => $this->encodeOptions(),
-				'user_token' => $this->mToken
+				'user_token' => $this->mToken,
+				'user_registration' => $dbw->timestamp( $this->mRegistration ),
 			), $fname
 		);
 		$this->mId = $dbw->insertId();
@@ -1526,7 +1539,8 @@
 	 * @return bool True if it is a newbie.
 	 */
 	function isNewbie() {
-		return $this->isAnon() || $this->mId > User::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
+		return !$this->isAllowed( 'autoconfirmed' );
+		//return $this->isAnon() || $this->mId > User::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
 	}
 
 	/**
@@ -1811,9 +1825,9 @@
 		global $wgGroupPermissions;
 		return array_diff(
 			array_keys( $wgGroupPermissions ),
-			array( '*', 'user' ) );
+			array( '*', 'user', 'autoconfirmed' ) );
 	}
-
+	
 }
 
 ?>
Index: trunk/phase3/includes/Article.php
===================================================================
--- trunk/phase3/includes/Article.php	(revision 12206)
+++ trunk/phase3/includes/Article.php	(revision 12207)
@@ -1582,166 +1582,93 @@
 	}
 
 	/**
-	 * protect a page
+	 * action=protect handler
 	 */
-	function protect( $limit = 'sysop' ) {
+	function protect() {
+		require_once 'ProtectionForm.php';
+		$form = new ProtectionForm( $this );
+		$form->show();
+	}
+	
+	/**
+	 * action=unprotect handler (alias)
+	 */
+	function unprotect() {
+		$this->protect();
+	}
+	
+	/**
+	 * Update the article's restriction field, and leave a log entry.
+	 *
+	 * @param array $limit set of restriction keys
+	 * @param string $reason
+	 * @return bool true on success
+	 */
+	function updateRestrictions( $limit = array(), $reason = '' ) {
 		global $wgUser, $wgOut, $wgRequest;
 
-		if ( ! $wgUser->isAllowed('protect') ) {
-			$wgOut->sysopRequired();
-			return;
+		if ( !$wgUser->isAllowed( 'protect' ) ) {
+			return false;
 		}
 
-		// bug 2261
-		if ( $this->mTitle->isProtected() && $limit == 'sysop' ) {
-			$this->view();
-			return;
+		if( wfReadOnly() ) {
+			return false;
 		}
 
-		if ( wfReadOnly() ) {
-			$wgOut->readOnlyPage();
-			return;
-		}
-
 		$id = $this->mTitle->getArticleID();
 		if ( 0 == $id ) {
-			$wgOut->fatalError( wfMsg( 'badarticleerror' ) );
-			return;
+			return false;
 		}
 
-		$confirm = $wgRequest->wasPosted() &&
-			$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
-		$moveonly = $wgRequest->getBool( 'wpMoveOnly' );
-		$reason = $wgRequest->getText( 'wpReasonProtect' );
+		$flat = Article::flattenRestrictions( $limit );
+		$protecting = ($flat != '');
+		
+		if( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser,
+			$limit, $reason ) ) ) {
 
-		if ( $confirm ) {
 			$dbw =& wfGetDB( DB_MASTER );
 			$dbw->update( 'page',
 				array( /* SET */
 					'page_touched' => $dbw->timestamp(),
-					'page_restrictions' => (string)$limit
+					'page_restrictions' => $flat
 				), array( /* WHERE */
 					'page_id' => $id
 				), 'Article::protect'
 			);
 
-			$restrictions = "move=" . $limit;
-			if( !$moveonly ) {
-				$restrictions .= ":edit=" . $limit;
-			}
-			if (wfRunHooks('ArticleProtect', array(&$this, &$wgUser, $limit == 'sysop', $reason, $moveonly))) {
+			wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser,
+				$limit, $reason ) );
 
-				$dbw =& wfGetDB( DB_MASTER );
-				$dbw->update( 'page',
-							  array( /* SET */
-									 'page_touched' => $dbw->timestamp(),
-									 'page_restrictions' => $restrictions
-									 ), array( /* WHERE */
-											   'page_id' => $id
-											   ), 'Article::protect'
-							  );
-
-				wfRunHooks('ArticleProtectComplete', array(&$this, &$wgUser, $limit == 'sysop', $reason, $moveonly));
-
-				$log = new LogPage( 'protect' );
-				if ( $limit === '' ) {
-					$log->addEntry( 'unprotect', $this->mTitle, $reason );
-				} else {
-					$log->addEntry( 'protect', $this->mTitle, $reason );
-				}
-				$wgOut->redirect( $this->mTitle->getFullURL() );
+			$log = new LogPage( 'protect' );
+			if( $protecting ) {
+				$log->addEntry( 'protect', $this->mTitle, trim( $reason . " [$flat]" ) );
+			} else {
+				$log->addEntry( 'unprotect', $this->mTitle, $reason );
 			}
-			return;
-		} else {
-			return $this->confirmProtect( '', '', $limit );
 		}
+		return true;
 	}
-
+	
 	/**
-	 * Output protection confirmation dialog
+	 * Take an array of page restrictions and flatten it to a string
+	 * suitable for insertion into the page_restrictions field.
+	 * @param array $limit
+	 * @return string
+	 * @access private
 	 */
-	function confirmProtect( $par, $reason, $limit = 'sysop'  ) {
-		global $wgOut, $wgUser;
-
-		wfDebug( "Article::confirmProtect\n" );
-
-		$sub = htmlspecialchars( $this->mTitle->getPrefixedText() );
-		$wgOut->setRobotpolicy( 'noindex,nofollow' );
-
-		$check = '';
-		$protcom = '';
-		$moveonly = '';
-
-		if ( $limit === '' ) {
-			$wgOut->setPageTitle( wfMsg( 'confirmunprotect' ) );
-			$wgOut->setSubtitle( wfMsg( 'unprotectsub', $sub ) );
-			$wgOut->addWikiText( wfMsg( 'confirmunprotecttext' ) );
-			$protcom = htmlspecialchars( wfMsg( 'unprotectcomment' ) );
-			$formaction = $this->mTitle->escapeLocalURL( 'action=unprotect' . $par );
-		} else {
-			$wgOut->setPageTitle( wfMsg( 'confirmprotect' ) );
-			$wgOut->setSubtitle( wfMsg( 'protectsub', $sub ) );
-			$wgOut->addWikiText( wfMsg( 'confirmprotecttext' ) );
-			$moveonly = wfMsg( 'protectmoveonly' ) ; // add it using addWikiText to prevent xss. bug:3991
-			$protcom = htmlspecialchars( wfMsg( 'protectcomment' ) );
-			$formaction = $this->mTitle->escapeLocalURL( 'action=protect' . $par );
+	function flattenRestrictions( $limit ) {
+		if( !is_array( $limit ) ) {
+			wfDebugDieBacktrace( 'Article::flattenRestrictions given non-array restriction set' );
 		}
-
-		$confirm = htmlspecialchars( wfMsg( 'protectpage' ) );
-		$token = htmlspecialchars( $wgUser->editToken() );
-
-		$wgOut->addHTML( "
-<form id='protectconfirm' method='post' action=\"{$formaction}\">
-	<table border='0'>
-		<tr>
-			<td align='right'>
-				<label for='wpReasonProtect'>{$protcom}:</label>
-			</td>
-			<td align='left'>
-				<input type='text' size='60' name='wpReasonProtect' id='wpReasonProtect' value=\"" . htmlspecialchars( $reason ) . "\" />
-			</td>
-		</tr>" );
-		if($moveonly != '') {
-			$wgOut->AddHTML( "
-		<tr>
-			<td align='right'>
-				<input type='checkbox' name='wpMoveOnly' value='1' id='wpMoveOnly' />
-			</td>
-			<td align='left'>
-				<label for='wpMoveOnly'> ");
-			$wgOut->addWikiText( $moveonly ); // bug 3991
-			$wgOut->addHTML( "
-				</label>
-			</td>
-		</tr> " );
+		$bits = array();
+		foreach( $limit as $action => $restrictions ) {
+			if( $restrictions != '' ) {
+				$bits[] = "$action=$restrictions";
+			}
 		}
-		$wgOut->addHTML( "
-		<tr>
-			<td>&nbsp;</td>
-			<td>
-				<input type='submit' name='wpConfirmProtectB' value=\"{$confirm}\" />
-			</td>
-		</tr>
-	</table>
-	<input type='hidden' name='wpEditToken' value=\"{$token}\" />
-</form>" );
-
-		$wgOut->returnToMain( false );
+		return implode( ':', $bits );
 	}
 
-	/**
-	 * Unprotect the pages
-	 */
-	function unprotect() {
-		// bug 2261
-		if ( $this->mTitle->isProtected() ) {
-			return $this->protect( '' );
-		} else {
-			$this->view();
-			return;
-		}
-	}
-
 	/*
 	 * UI entry point for page deletion
 	 */
Index: trunk/phase3/includes/SpecialLog.php
===================================================================
--- trunk/phase3/includes/SpecialLog.php	(revision 12206)
+++ trunk/phase3/includes/SpecialLog.php	(revision 12207)
@@ -275,7 +275,6 @@
 
 	function doShowList( &$out, $result ) {
 		// Rewind result pointer and go through it again, making the HTML
-		$html='';
 		if ($this->numResults > 0) {
 			$html = "\n<ul>\n";
 			$result->seek( 0 );
@@ -283,9 +282,11 @@
 				$html .= $this->logLine( $s );
 			}
 			$html .= "\n</ul>\n";
+			$out->addHTML( $html );
+		} else {
+			$out->addWikiText( wfMsg( 'logempty' ) );
 		}
 		$result->free();
-		$out->addHTML( $html );
 	}
 
 	/**
Index: trunk/phase3/includes/DefaultSettings.php
===================================================================
--- trunk/phase3/includes/DefaultSettings.php	(revision 12206)
+++ trunk/phase3/includes/DefaultSettings.php	(revision 12207)
@@ -772,12 +772,14 @@
  */
 $wgGroupPermissions = array();
 
+// Implicit group for all visitors
 $wgGroupPermissions['*'    ]['createaccount']   = true;
 $wgGroupPermissions['*'    ]['read']            = true;
 $wgGroupPermissions['*'    ]['edit']            = true;
 $wgGroupPermissions['*'    ]['createpage']      = true;
 $wgGroupPermissions['*'    ]['createtalk']      = true;
 
+// Implicit group for all logged-in accounts
 $wgGroupPermissions['user' ]['move']            = true;
 $wgGroupPermissions['user' ]['read']            = true;
 $wgGroupPermissions['user' ]['edit']            = true;
@@ -787,8 +789,15 @@
 $wgGroupPermissions['user' ]['reupload']        = true;
 $wgGroupPermissions['user' ]['reupload-shared'] = true;
 
+// Implicit group for accounts that pass $wgAutoConfirmAge
+$wgGroupPermissions['autoconfirmed']['autoconfirmed'] = true;
+
+// Users with bot privilege can have their edits hidden
+// from various log pages by default
 $wgGroupPermissions['bot'  ]['bot']             = true;
+$wgGroupPermissions['bot'  ]['autoconfirmed']   = true;
 
+// Most extra permission abilities go to this group
 $wgGroupPermissions['sysop']['block']           = true;
 $wgGroupPermissions['sysop']['createaccount']   = true;
 $wgGroupPermissions['sysop']['delete']          = true;
@@ -803,7 +812,9 @@
 $wgGroupPermissions['sysop']['reupload']        = true;
 $wgGroupPermissions['sysop']['reupload-shared'] = true;
 $wgGroupPermissions['sysop']['unwatchedpages']	= true;
+$wgGroupPermissions['sysop']['autoconfirmed']   = true;
 
+// Permission to change users' group assignments
 $wgGroupPermissions['bureaucrat']['userrights'] = true;
 
 /**
@@ -815,7 +826,36 @@
 # $wgGroupPermissions['developer']['siteadmin'] = true;
 
 
+/**
+ * Set of available actions that can be restricted via Special:Protect
+ * You probably shouldn't change this.
+ */
+$wgRestrictionTypes = array( 'edit', 'move' );
 
+/**
+ * Set of permission keys that can be selected via Special:Protect.
+ * 'autoconfirm' allows all registerd users if $wgAutoConfirmAge is 0.
+ */
+$wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' );
+
+
+/**
+ * Number of seconds an account is required to age before
+ * it's given the implicit 'autoconfirm' group membership.
+ * This can be used to limit privileges of new accounts.
+ *
+ * Accounts created by earlier versions of the software
+ * may not have a recorded creation date, and will always
+ * be considered to pass the age test.
+ *
+ * When left at 0, all registered accounts will pass.
+ */
+$wgAutoConfirmAge = 0;
+//$wgAutoConfirmAge = 600;     // ten minutes
+//$wgAutoConfirmAge = 3600*24; // one day
+
+
+
 # Proxy scanner settings
 #
 
Index: trunk/phase3/RELEASE-NOTES
===================================================================
--- trunk/phase3/RELEASE-NOTES	(revision 12206)
+++ trunk/phase3/RELEASE-NOTES	(revision 12207)
@@ -341,6 +341,10 @@
 * (bug 3424) Update page_touched for category members on category page creation
 * (bug 4108, 4336) Remove trailing whitespace from various messages, which
   mucks up message updating to create dupe entries
+* (bug 1735) Revamped protection interface
+* (bug 675) Add page protection level for unregistered/new accounts
+* User::isNewbie now uses the registration date and $wgAutoconfirmAge
+* Log views show message when no matches
 
 
 === Caveats ===
Index: trunk/phase3/languages/Language.php
===================================================================
--- trunk/phase3/languages/Language.php	(revision 12206)
+++ trunk/phase3/languages/Language.php	(revision 12207)
@@ -1208,7 +1208,9 @@
 'log'		=> 'Logs',
 'alllogstext'	=> 'Combined display of upload, deletion, protection, blocking, and sysop logs.
 You can narrow down the view by selecting a log type, the user name, or the affected page.',
+'logempty' => 'No matching items in log.',
 
+
 # Special:Allpages
 'nextpage'          => 'Next page ($1)',
 'allpagesfrom'		=> 'Display pages starting at:',
@@ -1376,7 +1378,16 @@
 'confirmunprotecttext' => 'Do you really want to unprotect this page?',
 'confirmunprotect' => 'Confirm unprotection',
 'unprotectcomment' => 'Reason for unprotecting',
+'protect-unchain' => 'Unlock move permissions',
+'protect-text' => 'You may view and change the protection level here for the page [[$1]].
+Please be sure you are following the [[Project:Protected page|project guidelines]].',
+'protect-viewtext' => 'Your account does not have permission to change
+page protection levels. Here are the current settings for the page [[$1]]:',
+'protect-default' => '(default)',
+'protect-level-autoconfirmed' => 'Block unregistered users',
+'protect-level-sysop' => 'Sysops only',
 
+
 # Undelete
 'undelete' => 'View deleted pages',
 'undeletepage' => 'View and restore deleted pages',
Views
Toolbox