From a35fcb0bed5f8600b4330039b6772fa4c7a49cd0 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 22 Dec 2005 05:41:06 +0000 Subject: [PATCH] * (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 --- RELEASE-NOTES | 4 + includes/Article.php | 187 ++++---------- includes/DefaultSettings.php | 40 +++ includes/ProtectionForm.php | 244 ++++++++++++++++++ includes/SpecialLog.php | 5 +- includes/User.php | 32 ++- languages/Language.php | 11 + .../archives/patch-user_registration.sql | 9 + maintenance/mysql5/tables.sql | 4 + maintenance/tables.sql | 4 + maintenance/updaters.inc | 1 + skins/common/protect.js | 126 +++++++++ 12 files changed, 526 insertions(+), 141 deletions(-) create mode 100644 includes/ProtectionForm.php create mode 100644 maintenance/archives/patch-user_registration.sql create mode 100644 skins/common/protect.js diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 253c2dd09b..837d51a925 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -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 === diff --git a/includes/Article.php b/includes/Article.php index 1a348018d0..d8f6068b6c 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -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( " -
- - - - - " ); - if($moveonly != '') { - $wgOut->AddHTML( " - - - - " ); - } - $wgOut->addHTML( " - - - - -
- - - -
- - - -
  - -
- -
" ); - - $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 ); } /* diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index cb2afbfc2d..dcd9c5178f 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -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 index 0000000000..c0c666ade9 --- /dev/null +++ b/includes/ProtectionForm.php @@ -0,0 +1,244 @@ + + * 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 .= ""; + $out .= ""; + $out .= "\n"; + foreach( $this->mRestrictions as $action => $required ) { + $out .= "\n"; + } + $out .= "\n"; + $out .= "\n"; + foreach( $this->mRestrictions as $action => $selected ) { + $out .= "\n"; + } + $out .= "\n"; + + // JavaScript will add another row with a value-chaining checkbox + + $out .= "\n"; + $out .= "
" . wfMsgHtml( $action ) . "
\n"; + $out .= $this->buildSelector( $action, $selected ); + $out .= "
\n"; + + if( !$this->disabled ) { + $out .= "\n"; + $out .= "\n"; + $out .= "\n"; + $out .= "\n"; + $out .= "\n"; + $out .= "
" . $this->buildReasonInput() . "
" . $this->buildSubmit() . "
\n"; + $out .= "\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 .= "\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' ) ) . + '' . + 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 ''; + } + + function buildCleanupScript() { + return ''; + } + + /** + * @param OutputPage $out + * @access private + */ + function showLogExtract( &$out ) { + # Show relevant lines from the deletion log: + $out->addHTML( "

" . htmlspecialchars( LogPage::logName( 'protect' ) ) . "

\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 diff --git a/includes/SpecialLog.php b/includes/SpecialLog.php index 65de257e33..f41269f661 100644 --- a/includes/SpecialLog.php +++ b/includes/SpecialLog.php @@ -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\n"; + $out->addHTML( $html ); + } else { + $out->addWikiText( wfMsg( 'logempty' ) ); } $result->free(); - $out->addHTML( $html ); } /** diff --git a/includes/User.php b/includes/User.php index ad81edd802..17fef75a75 100644 --- a/includes/User.php +++ b/includes/User.php @@ -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' ) ); } - + } ?> diff --git a/languages/Language.php b/languages/Language.php index bf522d5d0b..a6558596b8 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -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 index 0000000000..65fd99df35 --- /dev/null +++ b/maintenance/archives/patch-user_registration.sql @@ -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; diff --git a/maintenance/mysql5/tables.sql b/maintenance/mysql5/tables.sql index 29b47157b0..025514b7b1 100644 --- a/maintenance/mysql5/tables.sql +++ b/maintenance/mysql5/tables.sql @@ -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) diff --git a/maintenance/tables.sql b/maintenance/tables.sql index 0d3911efd1..f6c1c20cb1 100644 --- a/maintenance/tables.sql +++ b/maintenance/tables.sql @@ -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), diff --git a/maintenance/updaters.inc b/maintenance/updaters.inc index 8c669e2ef6..ab950d0aeb 100644 --- a/maintenance/updaters.inc +++ b/maintenance/updaters.inc @@ -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 index 0000000000..a144e5ebfe --- /dev/null +++ b/skins/common/protect.js @@ -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"; + } + }); +} -- 2.20.1