* (bug 15551) Deletion log excerpt is now shown whenever a user vists a
deleted page, even if they are unable to edit it.
* Added Wantedfiles special pages, allowing users to find image links with no image.
+* (bug 12650) It is now possible to set different expiration times for different
+ restriction types on the protection form.
=== Bug fixes in 1.14 ===
* @param string $reason
* @return bool true on success
*/
- function updateRestrictions( $limit = array(), $reason = '', $cascade = 0, $expiry = null ) {
+ function updateRestrictions( $limit = array(), $reason = '', $cascade = 0, $expiry = array() ) {
global $wgUser, $wgRestrictionTypes, $wgContLang;
$id = $this->mTitle->getArticleID();
# FIXME: Same limitations as described in ProtectionForm.php (line 37);
# we expect a single selection, but the schema allows otherwise.
$current = array();
- foreach( $wgRestrictionTypes as $action )
+ $updated = Article::flattenRestrictions( $limit );
+ $changed = false;
+ foreach( $wgRestrictionTypes as $action ) {
$current[$action] = implode( '', $this->mTitle->getRestrictions( $action ) );
+ $changed = ($changed || ($this->mTitle->mRestrictionsExpiry[$action] != $expiry[$action]) );
+ }
$current = Article::flattenRestrictions( $current );
- $updated = Article::flattenRestrictions( $limit );
- $changed = ( $current != $updated );
+ $changed = ($changed || ( $current != $updated ) );
$changed = $changed || ($updated && $this->mTitle->areRestrictionsCascading() != $cascade);
- $changed = $changed || ($updated && $this->mTitle->mRestrictionsExpiry != $expiry);
$protect = ( $updated != '' );
# If nothing's changed, do nothing
if( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser, $limit, $reason ) ) ) {
$dbw = wfGetDB( DB_MASTER );
-
- $encodedExpiry = Block::encodeExpiry($expiry, $dbw );
-
- $expiry_description = '';
- if( $encodedExpiry != 'infinity' ) {
- $expiry_description = ' (' . wfMsgForContent( 'protect-expiring',
- $wgContLang->timeanddate( $expiry, false, false ) ).')';
- }
# Prepare a null revision to be added to the history
$modified = $current != '' && $protect;
break;
}
}
-
$cascade_description = '';
if( $cascade ) {
$cascade_description = ' ['.wfMsgForContent('protect-summary-cascade').']';
$comment .= ": $reason";
$editComment = $comment;
- if( $protect )
- $editComment .= " [$updated]";
- if( $expiry_description && $protect )
- $editComment .= "$expiry_description";
+ $encodedExpiry = array();
+ $protect_description = '';
+ foreach( $limit as $action => $restrictions ) {
+ $encodedExpiry[$action] = Block::encodeExpiry($expiry[$action], $dbw );
+ if ($restrictions != '') {
+ $protect_description .= "[$action=$restrictions] (";
+ if( $encodedExpiry[$action] != 'infinity' ) {
+ $protect_description .= wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry[$action], false, false ) );
+ } else {
+ $protect_description .= wfMsgForContent( 'protect-expiry-indefinite' );
+ }
+ $protect_description .= ') ';
+ }
+ }
+
+ if( $protect_description && $protect )
+ $editComment .= "($protect_description)";
if( $cascade )
$editComment .= "$cascade_description";
# Update restrictions table
if ($restrictions != '' ) {
$dbw->replace( 'page_restrictions', array(array('pr_page', 'pr_type')),
array( 'pr_page' => $id, 'pr_type' => $action
- , 'pr_level' => $restrictions, 'pr_cascade' => $cascade ? 1 : 0
- , 'pr_expiry' => $encodedExpiry ), __METHOD__ );
+ , 'pr_level' => $restrictions, 'pr_cascade' => $cascade && $action == 'edit' ? 1 : 0
+ , 'pr_expiry' => $encodedExpiry[$action] ), __METHOD__ );
} else {
$dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
'pr_type' => $action ), __METHOD__ );
# Update the protection log
$log = new LogPage( 'protect' );
if( $protect ) {
- $params = array($updated,$encodedExpiry,$cascade ? 'cascade' : '');
+ $params = array($protect_description,$cascade ? 'cascade' : '');
$log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason), $params );
} else {
$log->addEntry( 'unprotect', $this->mTitle, $reason );
* to ensure that client-side caches don't keep obsolete copies of global
* styles.
*/
-$wgStyleVersion = '176';
+$wgStyleVersion = '177';
# Server-side caching:
}
$params[2] = isset( $params[2] ) ?
self::formatBlockFlags( $params[2], is_null( $skin ) ) : '';
- } else if ( $type == 'protect' && count($params) == 4 ) {
- $details .= " [{$params[1]}]"; // the restrictions
- if( $params[2] != 'infinity' ) {
- $details .= ' (' . wfMsgForContent( 'protect-expiring',
- $wgContLang->timeanddate( $params[2], false, false ) ).')';
- }
- if( $params[3] ) {
+ } else if ( $type == 'protect' && count($params) == 3 ) {
+ $details .= " {$params[1]}"; // restrictions and expiries
+ if( $params[2] ) {
$details .= ' ['.wfMsg('protect-summary-cascade').']';
}
}
class ProtectionForm {
var $mRestrictions = array();
var $mReason = '';
+ var $mReasonList = '';
var $mCascade = false;
- var $mExpiry = null;
+ var $mExpiry =array();
+ var $mExpiryList = array();
var $mPermErrors = array();
var $mApplicableTypes = array();
// Fixme: this form currently requires individual selections,
// but the db allows multiples separated by commas.
$this->mRestrictions[$action] = implode( '', $this->mTitle->getRestrictions( $action ) );
+
+ if ( $this->mTitle->mRestrictionsExpiry[$action] == 'infinity' ) {
+ $this->mExpiry[$action] = 'infinite';
+ } else if ( strlen($this->mTitle->mRestrictionsExpiry[$action]) == 0 ) {
+ $this->mExpiry[$action] = '';
+ } else {
+ // FIXME: this format is not user friendly
+ $this->mExpiry[$action] = wfTimestamp( TS_ISO_8601, $this->mTitle->mRestrictionsExpiry[$action] );
+ }
}
-
$this->mCascade = $this->mTitle->areRestrictionsCascading();
-
- if ( $this->mTitle->mRestrictionsExpiry == 'infinity' ) {
- $this->mExpiry = 'infinite';
- } else if ( strlen($this->mTitle->mRestrictionsExpiry) == 0 ) {
- $this->mExpiry = '';
- } else {
- // FIXME: this format is not user friendly
- $this->mExpiry = wfTimestamp( TS_ISO_8601, $this->mTitle->mRestrictionsExpiry );
- }
}
// The form will be available in read-only to show levels.
$this->mReason = $wgRequest->getText( 'mwProtect-reason' );
$this->mReasonList = $wgRequest->getText( 'wpProtectReasonList' );
$this->mCascade = $wgRequest->getBool( 'mwProtect-cascade', $this->mCascade );
- // Let dropdown have 'infinite' for unprotected pages
- if( !($expiry = $wgRequest->getText( 'mwProtect-expiry' )) && $this->mExpiry != 'infinite' ) {
- $expiry = $this->mExpiry;
- }
- $this->mExpiry = $expiry;
- $this->mExpiryList = $wgRequest->getText( 'wpProtectExpiryList', $this->mExpiry ? '' : 'infinite' );
-
+
foreach( $this->mApplicableTypes as $action ) {
+ // Let dropdown have 'infinite' for unprotected pages
+ if( !($expiry[$action] = $wgRequest->getText( "mwProtect-expiry-$action" )) && $this->mExpiry[$action] != 'infinite' ) {
+ $expiry[$action] = $this->mExpiry[$action];
+ }
+ $this->mExpiry[$action] = $expiry[$action];
+ $this->mExpiryList[$action] = $wgRequest->getText( "wpProtectExpiryList-$action", $this->mExpiry[$action] ? '' : 'infinite' );
+
$val = $wgRequest->getVal( "mwProtect-level-$action" );
if( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
// Prevent users from setting levels that they cannot later unset
} elseif ( $reasonstr == 'other' ) {
$reasonstr = $this->mReason;
}
- # Custom expiry takes precedence
- if ( strlen( $this->mExpiry ) == 0 ) {
- $this->mExpiry = strlen($this->mExpiryList) ? $this->mExpiryList : 'infinite';
- }
-
- if ( $this->mExpiry == 'infinite' || $this->mExpiry == 'indefinite' ) {
- $expiry = Block::infinity();
- } else {
- # Convert GNU-style date, on error returns -1 for PHP <5.1 and false for PHP >=5.1
- $expiry = strtotime( $this->mExpiry );
-
- if ( $expiry < 0 || $expiry === false ) {
- $this->show( wfMsg( 'protect_expiry_invalid' ) );
- return false;
+ $expiry = array();
+ foreach( $this->mApplicableTypes as $action ) {
+ # Custom expiry takes precedence
+ if ( strlen( $wgRequest->getText( "mwProtect-expiry-$action" ) ) == 0 ) {
+ $this->mExpiry[$action] = strlen($wgRequest->getText( "wpProtectExpiryList-$action")) ? $wgRequest->getText( "wpProtectExpiryList-$action") : 'infinite';
+ } else {
+ $this->mExpiry[$action] = $wgRequest->getText( "mwProtect-expiry-$action" );
}
+ if ( $this->mExpiry[$action] == 'infinite' || $this->mExpiry[$action] == 'indefinite' ) {
+ $expiry[$action] = Block::infinity();
+ } else {
+ # Convert GNU-style date, on error returns -1 for PHP <5.1 and false for PHP >=5.1
+ $expiry[$action] = strtotime( $this->mExpiry[$action] );
+
+ if ( $expiry[$action] < 0 || $expiry[$action] === false ) {
+ $this->show( wfMsg( 'protect_expiry_invalid' ) );
+ return false;
+ }
- // Fixme: non-qualified absolute times are not in users specified timezone
- // and there isn't notice about it in the ui
- $expiry = wfTimestamp( TS_MW, $expiry );
+ // Fixme: non-qualified absolute times are not in users specified timezone
+ // and there isn't notice about it in the ui
+ $expiry[$action] = wfTimestamp( TS_MW, $expiry[$action] );
- if ( $expiry < wfTimestampNow() ) {
- $this->show( wfMsg( 'protect_expiry_old' ) );
- return false;
+ if ( $expiry[$action] < wfTimestampNow() ) {
+ $this->show( wfMsg( 'protect_expiry_old' ) );
+ return false;
+ }
}
}
-
# They shouldn't be able to do this anyway, but just to make sure, ensure that cascading restrictions aren't being applied
# to a semi-protected page.
global $wgGroupPermissions;
if ($this->mTitle->exists()) {
$ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $reasonstr, $this->mCascade, $expiry );
} else {
- $ok = $this->mTitle->updateTitleProtection( $this->mRestrictions['create'], $reasonstr, $expiry );
+ $ok = $this->mTitle->updateTitleProtection( $this->mRestrictions['create'], $reasonstr, $expiry['create'] );
}
if( !$ok ) {
function buildForm() {
global $wgUser;
- $mProtectexpiry = Xml::label( wfMsg( 'protectexpiry' ), 'mwProtectExpiryList' );
- $mProtectother = Xml::label( wfMsg( 'protect-othertime' ), 'expires' );
$mProtectreasonother = Xml::label( wfMsg( 'protectcomment' ), 'wpProtectReasonList' );
$mProtectreason = Xml::label( wfMsg( 'protect-otherreason' ), 'mwProtect-reason' );
$out .= Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'protect-legend' ) ) .
Xml::openElement( 'table', array( 'id' => 'mwProtectSet' ) ) .
- Xml::openElement( 'tbody' ) .
- "<tr>\n";
+ Xml::openElement( 'tbody' );
- foreach( $this->mRestrictions as $action => $required ) {
+ foreach( $this->mRestrictions as $action => $selected ) {
/* Not all languages have V_x <-> N_x relation */
$msg = wfMsg( 'restriction-' . $action );
if( wfEmptyMsg( 'restriction-' . $action, $msg ) ) {
$label = Xml::element( 'label',
array( 'for' => "mwProtect-level-$action" ),
$msg );
- $out .= "<th>$label</th>";
- }
- $out .= "</tr>
- <tr>\n";
- foreach( $this->mRestrictions as $action => $selected ) {
- $out .= "<td>" .
- $this->buildSelector( $action, $selected ) .
- "</td>";
- }
- $out .= "</tr>\n";
-
- $scExpiryOptions = wfMsgForContent( 'ipboptions' ); // FIXME: use its own message
-
- $showProtectOptions = ($scExpiryOptions !== '-' && !$this->disabled);
- if( !$showProtectOptions )
- $mProtectother = $mProtectexpiry;
-
- $expiryFormOptions = Xml::option( wfMsg( 'protect-othertime-op' ), 'wpProtectExpiryList' );
- foreach( explode(',', $scExpiryOptions) as $option ) {
- if ( strpos($option, ":") === false ) $option = "$option:$option";
- list($show, $value) = explode(":", $option);
- $show = htmlspecialchars($show);
- $value = htmlspecialchars($value);
- $expiryFormOptions .= Xml::option( $show, $value, $this->mExpiryList === $value ? true : false ) . "\n";
+ $out .= "<tr><th>$label</th></tr>";
+ $out .= "<tr><td>" .
+ $this->buildSelector( $action, $selected ) .
+ "</td></tr>";
+ $scExpiryOptions = wfMsgForContent( 'ipboptions' ); // FIXME: use its own message
+
+ $showProtectOptions = ($scExpiryOptions !== '-' && !$this->disabled);
+
+ $mProtectexpiry = Xml::label( wfMsg( 'protectexpiry' ), "mwProtectExpiryList-$action" );
+ $mProtectother = Xml::label( wfMsg( 'protect-othertime' ), "mwProtect-$action-expires" );
+ $expiryFormOptions = Xml::option( wfMsg( 'protect-othertime-op' ), "wpProtectExpiryList-$action" );
+ foreach( explode(',', $scExpiryOptions) as $option ) {
+ if ( strpos($option, ":") === false ) $option = "$option:$option";
+ list($show, $value) = explode(":", $option);
+ $show = htmlspecialchars($show);
+ $value = htmlspecialchars($value);
+ $expiryFormOptions .= Xml::option( $show, $value, $this->mExpiryList[$action] === $value ? true : false ) . "\n";
+ }
+ # Add expiry dropdown
+ if( $showProtectOptions && !$this->disabled ) {
+ $out .= "
+ <tr>
+ <td class='mw-label'>
+ {$mProtectexpiry}
+ </td>
+ <td class='mw-input'>" .
+ Xml::tags( 'select',
+ array(
+ 'id' => "mwProtectExpiryList-$action",
+ 'name' => "wpProtectExpiryList-$action",
+ 'onchange' => "protectExpiryListUpdate(this)",
+ 'tabindex' => '2' ) + $this->disabledAttrib,
+ $expiryFormOptions ) .
+ "</td>
+ </tr>";
+ }
+ # Add custom expiry field
+ $attribs = array( 'id' => "mwProtect-$action-expires", 'onkeyup' => 'protectExpiryUpdate(this)' ) + $this->disabledAttrib;
+ $out .= "<tr>
+ <td class='mw-label'>" .
+ $mProtectother .
+ '</td>
+ <td class="mw-input">' .
+ Xml::input( "mwProtect-expiry-$action", 60, $this->mExpiry[$action], $attribs ) .
+ '</td>
+ </tr>';
}
-
$reasonDropDown = Xml::listDropDown( 'wpProtectReasonList',
wfMsgForContent( 'protect-dropdown' ),
wfMsgForContent( 'protect-otherreason-op' ), '', 'mwProtect-reason', 4 );
"</td>
</tr>\n";
}
- # Add expiry dropdown
- if( $showProtectOptions && !$this->disabled ) {
- $out .= "
- <tr>
- <td class='mw-label'>
- {$mProtectexpiry}
- </td>
- <td class='mw-input'>" .
- Xml::tags( 'select',
- array(
- 'id' => 'mwProtectExpiryList',
- 'name' => 'wpProtectExpiryList',
- 'onchange' => "document.getElementById('expires').value='';",
- 'tabindex' => '2' ) + $this->disabledAttrib,
- $expiryFormOptions ) .
- "</td>
- </tr>";
- }
- # Add custom expiry field
- $attribs = array( 'id' => 'expires' ) + $this->disabledAttrib;
- $out .= "<tr>
- <td class='mw-label'>" .
- $mProtectother .
- '</td>
- <td class="mw-input">' .
- Xml::input( 'mwProtect-expiry', 60, $this->mExpiry, $attribs ) .
- '</td>
- </tr>';
# Add manual and custom reason field/selects
if( !$this->disabled ) {
$out .= "
var $mRestrictions = array(); ///< Array of groups allowed to edit this article
var $mOldRestrictions = false;
var $mCascadeRestriction; ///< Cascade restrictions on this page to included templates and images?
- var $mRestrictionsExpiry; ///< When do the restrictions on this page expire?
+ var $mRestrictionsExpiry = array(); ///< When do the restrictions on this page expire?
var $mHasCascadingRestrictions; ///< Are cascading restrictions in effect on this page?
var $mCascadeSources; ///< Where are the cascading restrictions coming from on this page?
var $mRestrictionsLoaded = false; ///< Boolean for initialisation on demand
global $wgUser,$wgContLang;
if ($create_perm == implode(',',$this->getRestrictions('create'))
- && $expiry == $this->mRestrictionsExpiry) {
+ && $expiry == $this->mRestrictionsExpiry['create']) {
// No change
return true;
}
if ( $encodedExpiry != 'infinity' ) {
$expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ) ).')';
}
-
+ else {
+ $expiry_description .= ' (' . wfMsgForContent( 'protect-expiry-indefinite' ).')';
+ }
+
# Update protection table
if ($create_perm != '' ) {
$dbw->replace( 'protected_titles', array(array('pt_namespace', 'pt_title')),
} else {
$this->mHasCascadingRestrictions = $sources;
}
-
return array( $sources, $pagerestrictions );
}
foreach( $wgRestrictionTypes as $type ){
$this->mRestrictions[$type] = array();
+ $this->mRestrictionsExpiry[$type] = Block::decodeExpiry('');
}
$this->mCascadeRestriction = false;
- $this->mRestrictionsExpiry = Block::decodeExpiry('');
# Backwards-compatibility: also load the restrictions from the page record (old format).
// Only apply the restrictions if they haven't expired!
if ( !$expiry || $expiry > $now ) {
- $this->mRestrictionsExpiry = $expiry;
+ $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
$this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
$this->mCascadeRestriction |= $row->pr_cascade;
if (!$expiry || $expiry > $now) {
// Apply the restrictions
- $this->mRestrictionsExpiry = $expiry;
+ $this->mRestrictionsExpiry['create'] = $expiry;
$this->mRestrictions['create'] = explode(',', trim($pt_create_perm) );
} else { // Get rid of the old restrictions
Title::purgeExpiredRestrictions();
}
} else {
- $this->mRestrictionsExpiry = Block::decodeExpiry('');
+ $this->mRestrictionsExpiry['create'] = Block::decodeExpiry('');
}
$this->mRestrictionsLoaded = true;
}
$error = $ot->moveTo( $nt, true, $this->reason );
if ( $error !== true ) {
+ if (isset($error[0][0]) && $error[0][0] = 'cascadeprotected') {
+ $wgOut->showPermissionsErrorPage($error, 'move');
+ return;
+ }
# FIXME: showForm() should handle multiple errors
call_user_func_array(array($this, 'showForm'), $error[0]);
return;
'protect-level-sysop' => 'Sysops only',
'protect-summary-cascade' => 'cascading',
'protect-expiring' => 'expires $1 (UTC)',
+'protect-expiry-indefinite' => 'indefinite',
'protect-cascade' => 'Protect pages included in this page (cascading protection)',
'protect-cantedit' => 'You cannot change the protection levels of this page, because you do not have permission to edit it.',
'protect-othertime' => 'Other time:',
setCascadeCheckbox();
}
+/**
+ * When protection levels are locked together, update the
+ * expiries when one changes
+ *
+ * @param Element source expiry input that changed
+ */
+
+function protectExpiryUpdate(source) {
+ if( !protectUnchained() ) {
+ var expiry = source.value;
+ expiryForInputs(function(set) {
+ set.value = expiry;
+ });
+ }
+}
+
+/**
+ * When protection levels are locked together, update the
+ * expiry lists when one changes and clear the custom inputs
+ *
+ * @param Element source expiry selector that changed
+ */
+function protectExpiryListUpdate(source) {
+ if( !protectUnchained() ) {
+ var expiry = source.value;
+ expiryListForInputs(function(set) {
+ set.value = expiry;
+ });
+ expiryForInputs(function(set) {
+ set.value = '';
+ });
+ }
+}
+
/**
* Update chain status and enable/disable various bits of the UI
* when the user changes the "unlock move permissions" checkbox
}
/**
- * Enable/disable protection selectors
+ * Apply a callback to each expiry input
+ *
+ * @param callable func Callback function
+ */
+function expiryForInputs(func) {
+ var inputs = expiryInputs();
+ for (var i = 0; i < inputs.length; i++) {
+ func(inputs[i]);
+ }
+}
+
+/**
+ * Get a list of all expiry inputs on the page
+ *
+ * @return Array
+ */
+function expiryInputs() {
+ var all = document.getElementsByTagName("input");
+ var ours = new Array();
+ for (var i = 0; i < all.length; i++) {
+ var set = all[i];
+ if (set.name.match(/^mwProtect-expiry-/)) {
+ ours[ours.length] = set;
+ }
+ }
+ return ours;
+}
+
+/**
+ * Apply a callback to each expiry selector list
+ * @param callable func Callback function
+ */
+function expiryListForInputs(func) {
+ var inputs = expiryListInputs();
+ for (var i = 0; i < inputs.length; i++) {
+ func(inputs[i]);
+ }
+}
+
+/**
+ * Get a list of all expiry selector lists on the page
+ *
+ * @return Array
+ */
+function expiryListInputs() {
+ 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(/^mwProtectExpiryList-/)) {
+ ours[ours.length] = set;
+ }
+ }
+ return ours;
+}
+
+/**
+ * Enable/disable protection selectors and expiry inputs
*
* @param boolean val Enable?
*/
set.style.visible = val ? "visible" : "hidden";
}
});
+ first = true;
+ expiryForInputs(function(set) {
+ if (first) {
+ first = false;
+ } else {
+ set.disabled = !val;
+ set.style.visible = val ? "visible" : "hidden";
+ }
+ });
+ first = true;
+ expiryListForInputs(function(set) {
+ if (first) {
+ first = false;
+ } else {
+ set.disabled = !val;
+ set.style.visible = val ? "visible" : "hidden";
+ }
+ });
}