* (bug 1735) Revamped protection interface
authorBrion Vibber <brion@users.mediawiki.org>
Thu, 22 Dec 2005 05:41:06 +0000 (05:41 +0000)
committerBrion Vibber <brion@users.mediawiki.org>
Thu, 22 Dec 2005 05:41:06 +0000 (05:41 +0000)
* (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

12 files changed:
RELEASE-NOTES
includes/Article.php
includes/DefaultSettings.php
includes/ProtectionForm.php [new file with mode: 0644]
includes/SpecialLog.php
includes/User.php
languages/Language.php
maintenance/archives/patch-user_registration.sql [new file with mode: 0644]
maintenance/mysql5/tables.sql
maintenance/tables.sql
maintenance/updaters.inc
skins/common/protect.js [new file with mode: 0644]

index 253c2dd..837d51a 100644 (file)
@@ -341,6 +341,10 @@ fully support the editing toolbar, but was found to be too confusing.
 * (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 1a34801..d8f6068 100644 (file)
@@ -1582,164 +1582,91 @@ class Article {
        }
 
        /**
-        * 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;
-               }
-
-               // bug 2261
-               if ( $this->mTitle->isProtected() && $limit == 'sysop' ) {
-                       $this->view();
-                       return;
+               if ( !$wgUser->isAllowed( 'protect' ) ) {
+                       return false;
                }
 
-               if ( wfReadOnly() ) {
-                       $wgOut->readOnlyPage();
-                       return;
+               if( wfReadOnly() ) {
+                       return false;
                }
 
                $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
-        */
-       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 );
-               }
-
-               $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> " );
-               }
-               $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 );
-       }
-
+       
        /**
-        * Unprotect the pages
+        * 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 unprotect() {
-               // bug 2261
-               if ( $this->mTitle->isProtected() ) {
-                       return $this->protect( '' );
-               } else {
-                       $this->view();
-                       return;
+       function flattenRestrictions( $limit ) {
+               if( !is_array( $limit ) ) {
+                       wfDebugDieBacktrace( 'Article::flattenRestrictions given non-array restriction set' );
+               }
+               $bits = array();
+               foreach( $limit as $action => $restrictions ) {
+                       if( $restrictions != '' ) {
+                               $bits[] = "$action=$restrictions";
+                       }
                }
+               return implode( ':', $bits );
        }
 
        /*
index cb2afbf..dcd9c51 100644 (file)
@@ -772,12 +772,14 @@ $wgWhitelistRead = false;
  */
 $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' ]['upload']          = true;
 $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']['upload']          = true;
 $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,6 +826,35 @@ $wgGroupPermissions['bureaucrat']['userrights'] = true;
 # $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
 #
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
new file mode 100644 (file)
index 0000000..c0c666a
--- /dev/null
@@ -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
index 65de257..f41269f 100644 (file)
@@ -275,7 +275,6 @@ class LogViewer {
 
        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 @@ class LogViewer {
                                $html .= $this->logLine( $s );
                        }
                        $html .= "\n</ul>\n";
+                       $out->addHTML( $html );
+               } else {
+                       $out->addWikiText( wfMsg( 'logempty' ) );
                }
                $result->free();
-               $out->addHTML( $html );
        }
 
        /**
index ad81edd..17fef75 100644 (file)
@@ -14,7 +14,7 @@ require_once( 'WatchedItem.php' );
 define( 'USER_TOKEN_LENGTH', 32 );
 
 # Serialized record version
-define( 'MW_USER_VERSION', 2 );
+define( 'MW_USER_VERSION', 3 );
 
 /**
  *
@@ -36,6 +36,7 @@ class User {
        var $mHash;
        var $mGroups;
        var $mVersion; // serialized version
+       var $mRegistration;
 
        /** Construct using User:loadDefaults() */
        function User() {
@@ -107,7 +108,7 @@ class User {
                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 @@ class User {
                        $this->mTouched = '0'; # Allow any pages to be cached
                }
 
+               $this->mRegistration = wfTimestamp( TS_MW );
+               
                wfProfileOut( $fname );
        }
 
@@ -651,7 +654,7 @@ class User {
                } 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 @@ class User {
                $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 @@ class User {
                        $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 @@ class User {
                        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 @@ class User {
                                '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 @@ class User {
         * @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 @@ class User {
                global $wgGroupPermissions;
                return array_diff(
                        array_keys( $wgGroupPermissions ),
-                       array( '*', 'user' ) );
+                       array( '*', 'user', 'autoconfirmed' ) );
        }
-
+       
 }
 
 ?>
index bf522d5..a655859 100644 (file)
@@ -1208,6 +1208,8 @@ about books you are looking for.",
 '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)',
@@ -1376,6 +1378,15 @@ See [[Project:Protected page]] for more information.",
 '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',
diff --git a/maintenance/archives/patch-user_registration.sql b/maintenance/archives/patch-user_registration.sql
new file mode 100644 (file)
index 0000000..65fd99d
--- /dev/null
@@ -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;
index 29b4715..025514b 100644 (file)
@@ -119,6 +119,10 @@ CREATE TABLE /*$wgDBprefix*/user (
   -- 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 0d3911e..f6c1c20 100644 (file)
@@ -105,6 +105,10 @@ CREATE TABLE /*$wgDBprefix*/user (
   
   -- 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 8c669e2..ab950d0 100644 (file)
@@ -39,6 +39,7 @@ $wgNewFields = array(
        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' ),
diff --git a/skins/common/protect.js b/skins/common/protect.js
new file mode 100644 (file)
index 0000000..a144e5e
--- /dev/null
@@ -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";
+               }
+       });
+}