MediaWiki  REL1_22
ProtectionForm.php
Go to the documentation of this file.
00001 <?php
00029 class ProtectionForm {
00031     var $mRestrictions = array();
00032 
00034     var $mReason = '';
00035 
00037     var $mReasonSelection = '';
00038 
00040     var $mCascade = false;
00041 
00043     var $mExpiry = array();
00044 
00049     var $mExpirySelection = array();
00050 
00052     var $mPermErrors = array();
00053 
00055     var $mApplicableTypes = array();
00056 
00058     var $mExistingExpiry = array();
00059 
00060     function __construct( Page $article ) {
00061         global $wgUser;
00062         // Set instance variables.
00063         $this->mArticle = $article;
00064         $this->mTitle = $article->getTitle();
00065         $this->mApplicableTypes = $this->mTitle->getRestrictionTypes();
00066 
00067         // Check if the form should be disabled.
00068         // If it is, the form will be available in read-only to show levels.
00069         $this->mPermErrors = $this->mTitle->getUserPermissionsErrors( 'protect', $wgUser );
00070         if ( wfReadOnly() ) {
00071             $this->mPermErrors[] = array( 'readonlytext', wfReadOnlyReason() );
00072         }
00073         $this->disabled = $this->mPermErrors != array();
00074         $this->disabledAttrib = $this->disabled
00075             ? array( 'disabled' => 'disabled' )
00076             : array();
00077 
00078         $this->loadData();
00079     }
00080 
00084     function loadData() {
00085         global $wgRequest, $wgUser;
00086         global $wgRestrictionLevels;
00087 
00088         $this->mCascade = $this->mTitle->areRestrictionsCascading();
00089 
00090         $this->mReason = $wgRequest->getText( 'mwProtect-reason' );
00091         $this->mReasonSelection = $wgRequest->getText( 'wpProtectReasonSelection' );
00092         $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade', $this->mCascade );
00093 
00094         foreach ( $this->mApplicableTypes as $action ) {
00095             // @todo FIXME: This form currently requires individual selections,
00096             // but the db allows multiples separated by commas.
00097 
00098             // Pull the actual restriction from the DB
00099             $this->mRestrictions[$action] = implode( '', $this->mTitle->getRestrictions( $action ) );
00100 
00101             if ( !$this->mRestrictions[$action] ) {
00102                 // No existing expiry
00103                 $existingExpiry = '';
00104             } else {
00105                 $existingExpiry = $this->mTitle->getRestrictionExpiry( $action );
00106             }
00107             $this->mExistingExpiry[$action] = $existingExpiry;
00108 
00109             $requestExpiry = $wgRequest->getText( "mwProtect-expiry-$action" );
00110             $requestExpirySelection = $wgRequest->getVal( "wpProtectExpirySelection-$action" );
00111 
00112             if ( $requestExpiry ) {
00113                 // Custom expiry takes precedence
00114                 $this->mExpiry[$action] = $requestExpiry;
00115                 $this->mExpirySelection[$action] = 'othertime';
00116             } elseif ( $requestExpirySelection ) {
00117                 // Expiry selected from list
00118                 $this->mExpiry[$action] = '';
00119                 $this->mExpirySelection[$action] = $requestExpirySelection;
00120             } elseif ( $existingExpiry == 'infinity' ) {
00121                 // Existing expiry is infinite, use "infinite" in drop-down
00122                 $this->mExpiry[$action] = '';
00123                 $this->mExpirySelection[$action] = 'infinite';
00124             } elseif ( $existingExpiry ) {
00125                 // Use existing expiry in its own list item
00126                 $this->mExpiry[$action] = '';
00127                 $this->mExpirySelection[$action] = $existingExpiry;
00128             } else {
00129                 // Final default: infinite
00130                 $this->mExpiry[$action] = '';
00131                 $this->mExpirySelection[$action] = 'infinite';
00132             }
00133 
00134             $val = $wgRequest->getVal( "mwProtect-level-$action" );
00135             if ( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
00136                 // Prevent users from setting levels that they cannot later unset
00137                 if ( $val == 'sysop' ) {
00138                     // Special case, rewrite sysop to editprotected
00139                     if ( !$wgUser->isAllowed( 'editprotected' ) ) {
00140                         continue;
00141                     }
00142                 } elseif ( $val == 'autoconfirmed' ) {
00143                     // Special case, rewrite autoconfirmed to editsemiprotected
00144                     if ( !$wgUser->isAllowed( 'editsemiprotected' ) ) {
00145                         continue;
00146                     }
00147                 } elseif ( !$wgUser->isAllowed( $val ) ) {
00148                     continue;
00149                 }
00150                 $this->mRestrictions[$action] = $val;
00151             }
00152         }
00153     }
00154 
00162     function getExpiry( $action ) {
00163         if ( $this->mExpirySelection[$action] == 'existing' ) {
00164             return $this->mExistingExpiry[$action];
00165         } elseif ( $this->mExpirySelection[$action] == 'othertime' ) {
00166             $value = $this->mExpiry[$action];
00167         } else {
00168             $value = $this->mExpirySelection[$action];
00169         }
00170         if ( $value == 'infinite' || $value == 'indefinite' || $value == 'infinity' ) {
00171             $time = wfGetDB( DB_SLAVE )->getInfinity();
00172         } else {
00173             $unix = strtotime( $value );
00174 
00175             if ( !$unix || $unix === -1 ) {
00176                 return false;
00177             }
00178 
00179             // @todo FIXME: Non-qualified absolute times are not in users specified timezone
00180             // and there isn't notice about it in the ui
00181             $time = wfTimestamp( TS_MW, $unix );
00182         }
00183         return $time;
00184     }
00185 
00189     function execute() {
00190         global $wgRequest, $wgOut;
00191 
00192         if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
00193             throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' );
00194         }
00195 
00196         if ( $wgRequest->wasPosted() ) {
00197             if ( $this->save() ) {
00198                 $q = $this->mArticle->isRedirect() ? 'redirect=no' : '';
00199                 $wgOut->redirect( $this->mTitle->getFullURL( $q ) );
00200             }
00201         } else {
00202             $this->show();
00203         }
00204     }
00205 
00211     function show( $err = null ) {
00212         global $wgOut;
00213 
00214         $wgOut->setRobotPolicy( 'noindex,nofollow' );
00215         $wgOut->addBacklinkSubtitle( $this->mTitle );
00216 
00217         if ( is_array( $err ) ) {
00218             $wgOut->wrapWikiMsg( "<p class='error'>\n$1\n</p>\n", $err );
00219         } elseif ( is_string( $err ) ) {
00220             $wgOut->addHTML( "<p class='error'>{$err}</p>\n" );
00221         }
00222 
00223         if ( $this->mTitle->getRestrictionTypes() === array() ) {
00224             // No restriction types available for the current title
00225             // this might happen if an extension alters the available types
00226             $wgOut->setPageTitle( wfMessage( 'protect-norestrictiontypes-title', $this->mTitle->getPrefixedText() ) );
00227             $wgOut->addWikiText( wfMessage( 'protect-norestrictiontypes-text' )->text() );
00228 
00229             // Show the log in case protection was possible once
00230             $this->showLogExtract( $wgOut );
00231             // return as there isn't anything else we can do
00232             return;
00233         }
00234 
00235         list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
00236         if ( $cascadeSources && count( $cascadeSources ) > 0 ) {
00237             $titles = '';
00238 
00239             foreach ( $cascadeSources as $title ) {
00240                 $titles .= '* [[:' . $title->getPrefixedText() . "]]\n";
00241             }
00242 
00243             $wgOut->wrapWikiMsg( "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles . "</div>", array( 'protect-cascadeon', count( $cascadeSources ) ) );
00244         }
00245 
00246         # Show an appropriate message if the user isn't allowed or able to change
00247         # the protection settings at this time
00248         if ( $this->disabled ) {
00249             $wgOut->setPageTitle( wfMessage( 'protect-title-notallowed', $this->mTitle->getPrefixedText() ) );
00250             $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) );
00251         } else {
00252             $wgOut->setPageTitle( wfMessage( 'protect-title', $this->mTitle->getPrefixedText() ) );
00253             $wgOut->addWikiMsg( 'protect-text',
00254                 wfEscapeWikiText( $this->mTitle->getPrefixedText() ) );
00255         }
00256 
00257         $wgOut->addHTML( $this->buildForm() );
00258         $this->showLogExtract( $wgOut );
00259     }
00260 
00266     function save() {
00267         global $wgRequest, $wgUser, $wgOut;
00268 
00269         # Permission check!
00270         if ( $this->disabled ) {
00271             $this->show();
00272             return false;
00273         }
00274 
00275         $token = $wgRequest->getVal( 'wpEditToken' );
00276         if ( !$wgUser->matchEditToken( $token, array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) ) {
00277             $this->show( array( 'sessionfailure' ) );
00278             return false;
00279         }
00280 
00281         # Create reason string. Use list and/or custom string.
00282         $reasonstr = $this->mReasonSelection;
00283         if ( $reasonstr != 'other' && $this->mReason != '' ) {
00284             // Entry from drop down menu + additional comment
00285             $reasonstr .= wfMessage( 'colon-separator' )->text() . $this->mReason;
00286         } elseif ( $reasonstr == 'other' ) {
00287             $reasonstr = $this->mReason;
00288         }
00289         $expiry = array();
00290         foreach ( $this->mApplicableTypes as $action ) {
00291             $expiry[$action] = $this->getExpiry( $action );
00292             if ( empty( $this->mRestrictions[$action] ) ) {
00293                 continue; // unprotected
00294             }
00295             if ( !$expiry[$action] ) {
00296                 $this->show( array( 'protect_expiry_invalid' ) );
00297                 return false;
00298             }
00299             if ( $expiry[$action] < wfTimestampNow() ) {
00300                 $this->show( array( 'protect_expiry_old' ) );
00301                 return false;
00302             }
00303         }
00304 
00305         $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
00306 
00307         $status = $this->mArticle->doUpdateRestrictions( $this->mRestrictions, $expiry, $this->mCascade, $reasonstr, $wgUser );
00308 
00309         if ( !$status->isOK() ) {
00310             $this->show( $wgOut->parseInline( $status->getWikiText() ) );
00311             return false;
00312         }
00313 
00320         $errorMsg = '';
00321         if ( !wfRunHooks( 'ProtectionForm::save', array( $this->mArticle, &$errorMsg, $reasonstr ) ) ) {
00322             if ( $errorMsg == '' ) {
00323                 $errorMsg = array( 'hookaborted' );
00324             }
00325         }
00326         if ( $errorMsg != '' ) {
00327             $this->show( $errorMsg );
00328             return false;
00329         }
00330 
00331         WatchAction::doWatchOrUnwatch( $wgRequest->getCheck( 'mwProtectWatch' ), $this->mTitle, $wgUser );
00332 
00333         return true;
00334     }
00335 
00341     function buildForm() {
00342         global $wgUser, $wgLang, $wgOut;
00343 
00344         $mProtectreasonother = Xml::label(
00345             wfMessage( 'protectcomment' )->text(),
00346             'wpProtectReasonSelection'
00347         );
00348         $mProtectreason = Xml::label(
00349             wfMessage( 'protect-otherreason' )->text(),
00350             'mwProtect-reason'
00351         );
00352 
00353         $out = '';
00354         if ( !$this->disabled ) {
00355             $wgOut->addModules( 'mediawiki.legacy.protect' );
00356             $out .= Xml::openElement( 'form', array( 'method' => 'post',
00357                 'action' => $this->mTitle->getLocalURL( 'action=protect' ),
00358                 'id' => 'mw-Protect-Form', 'onsubmit' => 'ProtectionForm.enableUnchainedInputs(true)' ) );
00359         }
00360 
00361         $out .= Xml::openElement( 'fieldset' ) .
00362             Xml::element( 'legend', null, wfMessage( 'protect-legend' )->text() ) .
00363             Xml::openElement( 'table', array( 'id' => 'mwProtectSet' ) ) .
00364             Xml::openElement( 'tbody' );
00365 
00366         // Not all languages have V_x <-> N_x relation
00367         foreach ( $this->mRestrictions as $action => $selected ) {
00368             // Messages:
00369             // restriction-edit, restriction-move, restriction-create, restriction-upload
00370             $msg = wfMessage( 'restriction-' . $action );
00371             $out .= "<tr><td>" .
00372             Xml::openElement( 'fieldset' ) .
00373             Xml::element( 'legend', null, $msg->exists() ? $msg->text() : $action ) .
00374             Xml::openElement( 'table', array( 'id' => "mw-protect-table-$action" ) ) .
00375                 "<tr><td>" . $this->buildSelector( $action, $selected ) . "</td></tr><tr><td>";
00376 
00377             $reasonDropDown = Xml::listDropDown( 'wpProtectReasonSelection',
00378                 wfMessage( 'protect-dropdown' )->inContentLanguage()->text(),
00379                 wfMessage( 'protect-otherreason-op' )->inContentLanguage()->text(),
00380                 $this->mReasonSelection,
00381                 'mwProtect-reason', 4 );
00382             $scExpiryOptions = wfMessage( 'protect-expiry-options' )->inContentLanguage()->text();
00383 
00384             $showProtectOptions = $scExpiryOptions !== '-' && !$this->disabled;
00385 
00386             $mProtectexpiry = Xml::label(
00387                 wfMessage( 'protectexpiry' )->text(),
00388                 "mwProtectExpirySelection-$action"
00389             );
00390             $mProtectother = Xml::label(
00391                 wfMessage( 'protect-othertime' )->text(),
00392                 "mwProtect-$action-expires"
00393             );
00394 
00395             $expiryFormOptions = '';
00396             if ( $this->mExistingExpiry[$action] && $this->mExistingExpiry[$action] != 'infinity' ) {
00397                 $timestamp = $wgLang->timeanddate( $this->mExistingExpiry[$action], true );
00398                 $d = $wgLang->date( $this->mExistingExpiry[$action], true );
00399                 $t = $wgLang->time( $this->mExistingExpiry[$action], true );
00400                 $expiryFormOptions .=
00401                     Xml::option(
00402                         wfMessage( 'protect-existing-expiry', $timestamp, $d, $t )->text(),
00403                         'existing',
00404                         $this->mExpirySelection[$action] == 'existing'
00405                     ) . "\n";
00406             }
00407 
00408             $expiryFormOptions .= Xml::option(
00409                 wfMessage( 'protect-othertime-op' )->text(),
00410                 "othertime"
00411             ) . "\n";
00412             foreach ( explode( ',', $scExpiryOptions ) as $option ) {
00413                 if ( strpos( $option, ":" ) === false ) {
00414                     $show = $value = $option;
00415                 } else {
00416                     list( $show, $value ) = explode( ":", $option );
00417                 }
00418                 $show = htmlspecialchars( $show );
00419                 $value = htmlspecialchars( $value );
00420                 $expiryFormOptions .= Xml::option( $show, $value, $this->mExpirySelection[$action] === $value ) . "\n";
00421             }
00422             # Add expiry dropdown
00423             if ( $showProtectOptions && !$this->disabled ) {
00424                 $out .= "
00425                     <table><tr>
00426                         <td class='mw-label'>
00427                             {$mProtectexpiry}
00428                         </td>
00429                         <td class='mw-input'>" .
00430                             Xml::tags( 'select',
00431                                 array(
00432                                     'id' => "mwProtectExpirySelection-$action",
00433                                     'name' => "wpProtectExpirySelection-$action",
00434                                     'onchange' => "ProtectionForm.updateExpiryList(this)",
00435                                     'tabindex' => '2' ) + $this->disabledAttrib,
00436                                 $expiryFormOptions ) .
00437                         "</td>
00438                     </tr></table>";
00439             }
00440             # Add custom expiry field
00441             $attribs = array( 'id' => "mwProtect-$action-expires",
00442                 'onkeyup' => 'ProtectionForm.updateExpiry(this)',
00443                 'onchange' => 'ProtectionForm.updateExpiry(this)' ) + $this->disabledAttrib;
00444             $out .= "<table><tr>
00445                     <td class='mw-label'>" .
00446                         $mProtectother .
00447                     '</td>
00448                     <td class="mw-input">' .
00449                         Xml::input( "mwProtect-expiry-$action", 50, $this->mExpiry[$action], $attribs ) .
00450                     '</td>
00451                 </tr></table>';
00452             $out .= "</td></tr>" .
00453             Xml::closeElement( 'table' ) .
00454             Xml::closeElement( 'fieldset' ) .
00455             "</td></tr>";
00456         }
00457         # Give extensions a chance to add items to the form
00458         wfRunHooks( 'ProtectionForm::buildForm', array( $this->mArticle, &$out ) );
00459 
00460         $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
00461 
00462         // JavaScript will add another row with a value-chaining checkbox
00463         if ( $this->mTitle->exists() ) {
00464             $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table2' ) ) .
00465                 Xml::openElement( 'tbody' );
00466             $out .= '<tr>
00467                     <td></td>
00468                     <td class="mw-input">' .
00469                         Xml::checkLabel(
00470                             wfMessage( 'protect-cascade' )->text(),
00471                             'mwProtect-cascade',
00472                             'mwProtect-cascade',
00473                             $this->mCascade, $this->disabledAttrib
00474                         ) .
00475                     "</td>
00476                 </tr>\n";
00477             $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
00478         }
00479 
00480         # Add manual and custom reason field/selects as well as submit
00481         if ( !$this->disabled ) {
00482             $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table3' ) ) .
00483                 Xml::openElement( 'tbody' );
00484             $out .= "
00485                 <tr>
00486                     <td class='mw-label'>
00487                         {$mProtectreasonother}
00488                     </td>
00489                     <td class='mw-input'>
00490                         {$reasonDropDown}
00491                     </td>
00492                 </tr>
00493                 <tr>
00494                     <td class='mw-label'>
00495                         {$mProtectreason}
00496                     </td>
00497                     <td class='mw-input'>" .
00498                         Xml::input( 'mwProtect-reason', 60, $this->mReason, array( 'type' => 'text',
00499                             'id' => 'mwProtect-reason', 'maxlength' => 180 ) ) .
00500                             // Limited maxlength as the database trims at 255 bytes and other texts
00501                             // chosen by dropdown menus on this page are also included in this database field.
00502                             // The byte limit of 180 bytes is enforced in javascript
00503                     "</td>
00504                 </tr>";
00505             # Disallow watching is user is not logged in
00506             if ( $wgUser->isLoggedIn() ) {
00507                 $out .= "
00508                 <tr>
00509                     <td></td>
00510                     <td class='mw-input'>" .
00511                         Xml::checkLabel( wfMessage( 'watchthis' )->text(),
00512                             'mwProtectWatch', 'mwProtectWatch',
00513                             $wgUser->isWatched( $this->mTitle ) || $wgUser->getOption( 'watchdefault' ) ) .
00514                     "</td>
00515                 </tr>";
00516             }
00517             $out .= "
00518                 <tr>
00519                     <td></td>
00520                     <td class='mw-submit'>" .
00521                         Xml::submitButton(
00522                             wfMessage( 'confirm' )->text(),
00523                             array( 'id' => 'mw-Protect-submit' )
00524                         ) .
00525                     "</td>
00526                 </tr>\n";
00527             $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
00528         }
00529         $out .= Xml::closeElement( 'fieldset' );
00530 
00531         if ( $wgUser->isAllowed( 'editinterface' ) ) {
00532             $title = Title::makeTitle( NS_MEDIAWIKI, 'Protect-dropdown' );
00533             $link = Linker::link(
00534                 $title,
00535                 wfMessage( 'protect-edit-reasonlist' )->escaped(),
00536                 array(),
00537                 array( 'action' => 'edit' )
00538             );
00539             $out .= '<p class="mw-protect-editreasons">' . $link . '</p>';
00540         }
00541 
00542         if ( !$this->disabled ) {
00543             $out .= Html::hidden( 'wpEditToken', $wgUser->getEditToken( array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) );
00544             $out .= Xml::closeElement( 'form' );
00545             $wgOut->addScript( $this->buildCleanupScript() );
00546         }
00547 
00548         return $out;
00549     }
00550 
00558     function buildSelector( $action, $selected ) {
00559         global $wgRestrictionLevels, $wgUser;
00560 
00561         $levels = array();
00562         foreach ( $wgRestrictionLevels as $key ) {
00563             //don't let them choose levels above their own (aka so they can still unprotect and edit the page). but only when the form isn't disabled
00564             if ( $key == 'sysop' ) {
00565                 //special case, rewrite sysop to editprotected
00566                 if ( !$wgUser->isAllowed( 'editprotected' ) && !$this->disabled ) {
00567                     continue;
00568                 }
00569             } elseif ( $key == 'autoconfirmed' ) {
00570                 //special case, rewrite autoconfirmed to editsemiprotected
00571                 if ( !$wgUser->isAllowed( 'editsemiprotected' ) && !$this->disabled ) {
00572                     continue;
00573                 }
00574             } else {
00575                 if ( !$wgUser->isAllowed( $key ) && !$this->disabled ) {
00576                     continue;
00577                 }
00578             }
00579             $levels[] = $key;
00580         }
00581 
00582         $id = 'mwProtect-level-' . $action;
00583         $attribs = array(
00584             'id' => $id,
00585             'name' => $id,
00586             'size' => count( $levels ),
00587             'onchange' => 'ProtectionForm.updateLevels(this)',
00588             ) + $this->disabledAttrib;
00589 
00590         $out = Xml::openElement( 'select', $attribs );
00591         foreach ( $levels as $key ) {
00592             $out .= Xml::option( $this->getOptionLabel( $key ), $key, $key == $selected );
00593         }
00594         $out .= Xml::closeElement( 'select' );
00595         return $out;
00596     }
00597 
00604     private function getOptionLabel( $permission ) {
00605         if ( $permission == '' ) {
00606             return wfMessage( 'protect-default' )->text();
00607         } else {
00608             // Messages: protect-level-autoconfirmed, protect-level-sysop
00609             $msg = wfMessage( "protect-level-{$permission}" );
00610             if ( $msg->exists() ) {
00611                 return $msg->text();
00612             }
00613             return wfMessage( 'protect-fallback', $permission )->text();
00614         }
00615     }
00616 
00617     function buildCleanupScript() {
00618         global $wgCascadingRestrictionLevels, $wgOut;
00619 
00620         $cascadeableLevels = $wgCascadingRestrictionLevels;
00621         $options = array(
00622             'tableId' => 'mwProtectSet',
00623             'labelText' => wfMessage( 'protect-unchain-permissions' )->plain(),
00624             'numTypes' => count( $this->mApplicableTypes ),
00625             'existingMatch' => count( array_unique( $this->mExistingExpiry ) ) === 1,
00626         );
00627 
00628         $wgOut->addJsConfigVars( 'wgCascadeableLevels', $cascadeableLevels );
00629         $script = Xml::encodeJsCall( 'ProtectionForm.init', array( $options ) );
00630         return Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) );
00631     }
00632 
00639     function showLogExtract( &$out ) {
00640         # Show relevant lines from the protection log:
00641         $protectLogPage = new LogPage( 'protect' );
00642         $out->addHTML( Xml::element( 'h2', null, $protectLogPage->getName()->text() ) );
00643         LogEventsList::showLogExtract( $out, 'protect', $this->mTitle );
00644         # Let extensions add other relevant log extracts
00645         wfRunHooks( 'ProtectionForm::showLogExtract', array( $this->mArticle, $out ) );
00646     }
00647 }