MediaWiki  REL1_23
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 
00087         $levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(), $wgUser );
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, $levels ) ) {
00136                 $this->mRestrictions[$action] = $val;
00137             }
00138         }
00139     }
00140 
00148     function getExpiry( $action ) {
00149         if ( $this->mExpirySelection[$action] == 'existing' ) {
00150             return $this->mExistingExpiry[$action];
00151         } elseif ( $this->mExpirySelection[$action] == 'othertime' ) {
00152             $value = $this->mExpiry[$action];
00153         } else {
00154             $value = $this->mExpirySelection[$action];
00155         }
00156         if ( $value == 'infinite' || $value == 'indefinite' || $value == 'infinity' ) {
00157             $time = wfGetDB( DB_SLAVE )->getInfinity();
00158         } else {
00159             $unix = strtotime( $value );
00160 
00161             if ( !$unix || $unix === -1 ) {
00162                 return false;
00163             }
00164 
00165             // @todo FIXME: Non-qualified absolute times are not in users specified timezone
00166             // and there isn't notice about it in the ui
00167             $time = wfTimestamp( TS_MW, $unix );
00168         }
00169         return $time;
00170     }
00171 
00175     function execute() {
00176         global $wgRequest, $wgOut;
00177 
00178         if ( MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) === array( '' ) ) {
00179             throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' );
00180         }
00181 
00182         if ( $wgRequest->wasPosted() ) {
00183             if ( $this->save() ) {
00184                 $q = $this->mArticle->isRedirect() ? 'redirect=no' : '';
00185                 $wgOut->redirect( $this->mTitle->getFullURL( $q ) );
00186             }
00187         } else {
00188             $this->show();
00189         }
00190     }
00191 
00197     function show( $err = null ) {
00198         global $wgOut;
00199 
00200         $wgOut->setRobotPolicy( 'noindex,nofollow' );
00201         $wgOut->addBacklinkSubtitle( $this->mTitle );
00202 
00203         if ( is_array( $err ) ) {
00204             $wgOut->wrapWikiMsg( "<p class='error'>\n$1\n</p>\n", $err );
00205         } elseif ( is_string( $err ) ) {
00206             $wgOut->addHTML( "<p class='error'>{$err}</p>\n" );
00207         }
00208 
00209         if ( $this->mTitle->getRestrictionTypes() === array() ) {
00210             // No restriction types available for the current title
00211             // this might happen if an extension alters the available types
00212             $wgOut->setPageTitle( wfMessage( 'protect-norestrictiontypes-title', $this->mTitle->getPrefixedText() ) );
00213             $wgOut->addWikiText( wfMessage( 'protect-norestrictiontypes-text' )->text() );
00214 
00215             // Show the log in case protection was possible once
00216             $this->showLogExtract( $wgOut );
00217             // return as there isn't anything else we can do
00218             return;
00219         }
00220 
00221         list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
00222         if ( $cascadeSources && count( $cascadeSources ) > 0 ) {
00223             $titles = '';
00224 
00225             foreach ( $cascadeSources as $title ) {
00226                 $titles .= '* [[:' . $title->getPrefixedText() . "]]\n";
00227             }
00228 
00229             $wgOut->wrapWikiMsg( "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles . "</div>", array( 'protect-cascadeon', count( $cascadeSources ) ) );
00230         }
00231 
00232         # Show an appropriate message if the user isn't allowed or able to change
00233         # the protection settings at this time
00234         if ( $this->disabled ) {
00235             $wgOut->setPageTitle( wfMessage( 'protect-title-notallowed', $this->mTitle->getPrefixedText() ) );
00236             $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) );
00237         } else {
00238             $wgOut->setPageTitle( wfMessage( 'protect-title', $this->mTitle->getPrefixedText() ) );
00239             $wgOut->addWikiMsg( 'protect-text',
00240                 wfEscapeWikiText( $this->mTitle->getPrefixedText() ) );
00241         }
00242 
00243         $wgOut->addHTML( $this->buildForm() );
00244         $this->showLogExtract( $wgOut );
00245     }
00246 
00252     function save() {
00253         global $wgRequest, $wgUser, $wgOut;
00254 
00255         # Permission check!
00256         if ( $this->disabled ) {
00257             $this->show();
00258             return false;
00259         }
00260 
00261         $token = $wgRequest->getVal( 'wpEditToken' );
00262         if ( !$wgUser->matchEditToken( $token, array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) ) {
00263             $this->show( array( 'sessionfailure' ) );
00264             return false;
00265         }
00266 
00267         # Create reason string. Use list and/or custom string.
00268         $reasonstr = $this->mReasonSelection;
00269         if ( $reasonstr != 'other' && $this->mReason != '' ) {
00270             // Entry from drop down menu + additional comment
00271             $reasonstr .= wfMessage( 'colon-separator' )->text() . $this->mReason;
00272         } elseif ( $reasonstr == 'other' ) {
00273             $reasonstr = $this->mReason;
00274         }
00275         $expiry = array();
00276         foreach ( $this->mApplicableTypes as $action ) {
00277             $expiry[$action] = $this->getExpiry( $action );
00278             if ( empty( $this->mRestrictions[$action] ) ) {
00279                 continue; // unprotected
00280             }
00281             if ( !$expiry[$action] ) {
00282                 $this->show( array( 'protect_expiry_invalid' ) );
00283                 return false;
00284             }
00285             if ( $expiry[$action] < wfTimestampNow() ) {
00286                 $this->show( array( 'protect_expiry_old' ) );
00287                 return false;
00288             }
00289         }
00290 
00291         $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
00292 
00293         $status = $this->mArticle->doUpdateRestrictions( $this->mRestrictions, $expiry, $this->mCascade, $reasonstr, $wgUser );
00294 
00295         if ( !$status->isOK() ) {
00296             $this->show( $wgOut->parseInline( $status->getWikiText() ) );
00297             return false;
00298         }
00299 
00306         $errorMsg = '';
00307         if ( !wfRunHooks( 'ProtectionForm::save', array( $this->mArticle, &$errorMsg, $reasonstr ) ) ) {
00308             if ( $errorMsg == '' ) {
00309                 $errorMsg = array( 'hookaborted' );
00310             }
00311         }
00312         if ( $errorMsg != '' ) {
00313             $this->show( $errorMsg );
00314             return false;
00315         }
00316 
00317         WatchAction::doWatchOrUnwatch( $wgRequest->getCheck( 'mwProtectWatch' ), $this->mTitle, $wgUser );
00318 
00319         return true;
00320     }
00321 
00327     function buildForm() {
00328         global $wgUser, $wgLang, $wgOut;
00329 
00330         $mProtectreasonother = Xml::label(
00331             wfMessage( 'protectcomment' )->text(),
00332             'wpProtectReasonSelection'
00333         );
00334         $mProtectreason = Xml::label(
00335             wfMessage( 'protect-otherreason' )->text(),
00336             'mwProtect-reason'
00337         );
00338 
00339         $out = '';
00340         if ( !$this->disabled ) {
00341             $wgOut->addModules( 'mediawiki.legacy.protect' );
00342             $out .= Xml::openElement( 'form', array( 'method' => 'post',
00343                 'action' => $this->mTitle->getLocalURL( 'action=protect' ),
00344                 'id' => 'mw-Protect-Form', 'onsubmit' => 'ProtectionForm.enableUnchainedInputs(true)' ) );
00345         }
00346 
00347         $out .= Xml::openElement( 'fieldset' ) .
00348             Xml::element( 'legend', null, wfMessage( 'protect-legend' )->text() ) .
00349             Xml::openElement( 'table', array( 'id' => 'mwProtectSet' ) ) .
00350             Xml::openElement( 'tbody' );
00351 
00352         // Not all languages have V_x <-> N_x relation
00353         foreach ( $this->mRestrictions as $action => $selected ) {
00354             // Messages:
00355             // restriction-edit, restriction-move, restriction-create, restriction-upload
00356             $msg = wfMessage( 'restriction-' . $action );
00357             $out .= "<tr><td>" .
00358             Xml::openElement( 'fieldset' ) .
00359             Xml::element( 'legend', null, $msg->exists() ? $msg->text() : $action ) .
00360             Xml::openElement( 'table', array( 'id' => "mw-protect-table-$action" ) ) .
00361                 "<tr><td>" . $this->buildSelector( $action, $selected ) . "</td></tr><tr><td>";
00362 
00363             $reasonDropDown = Xml::listDropDown( 'wpProtectReasonSelection',
00364                 wfMessage( 'protect-dropdown' )->inContentLanguage()->text(),
00365                 wfMessage( 'protect-otherreason-op' )->inContentLanguage()->text(),
00366                 $this->mReasonSelection,
00367                 'mwProtect-reason', 4 );
00368             $scExpiryOptions = wfMessage( 'protect-expiry-options' )->inContentLanguage()->text();
00369 
00370             $showProtectOptions = $scExpiryOptions !== '-' && !$this->disabled;
00371 
00372             $mProtectexpiry = Xml::label(
00373                 wfMessage( 'protectexpiry' )->text(),
00374                 "mwProtectExpirySelection-$action"
00375             );
00376             $mProtectother = Xml::label(
00377                 wfMessage( 'protect-othertime' )->text(),
00378                 "mwProtect-$action-expires"
00379             );
00380 
00381             $expiryFormOptions = '';
00382             if ( $this->mExistingExpiry[$action] && $this->mExistingExpiry[$action] != 'infinity' ) {
00383                 $timestamp = $wgLang->timeanddate( $this->mExistingExpiry[$action], true );
00384                 $d = $wgLang->date( $this->mExistingExpiry[$action], true );
00385                 $t = $wgLang->time( $this->mExistingExpiry[$action], true );
00386                 $expiryFormOptions .=
00387                     Xml::option(
00388                         wfMessage( 'protect-existing-expiry', $timestamp, $d, $t )->text(),
00389                         'existing',
00390                         $this->mExpirySelection[$action] == 'existing'
00391                     ) . "\n";
00392             }
00393 
00394             $expiryFormOptions .= Xml::option(
00395                 wfMessage( 'protect-othertime-op' )->text(),
00396                 "othertime"
00397             ) . "\n";
00398             foreach ( explode( ',', $scExpiryOptions ) as $option ) {
00399                 if ( strpos( $option, ":" ) === false ) {
00400                     $show = $value = $option;
00401                 } else {
00402                     list( $show, $value ) = explode( ":", $option );
00403                 }
00404                 $show = htmlspecialchars( $show );
00405                 $value = htmlspecialchars( $value );
00406                 $expiryFormOptions .= Xml::option( $show, $value, $this->mExpirySelection[$action] === $value ) . "\n";
00407             }
00408             # Add expiry dropdown
00409             if ( $showProtectOptions && !$this->disabled ) {
00410                 $out .= "
00411                     <table><tr>
00412                         <td class='mw-label'>
00413                             {$mProtectexpiry}
00414                         </td>
00415                         <td class='mw-input'>" .
00416                             Xml::tags( 'select',
00417                                 array(
00418                                     'id' => "mwProtectExpirySelection-$action",
00419                                     'name' => "wpProtectExpirySelection-$action",
00420                                     'onchange' => "ProtectionForm.updateExpiryList(this)",
00421                                     'tabindex' => '2' ) + $this->disabledAttrib,
00422                                 $expiryFormOptions ) .
00423                         "</td>
00424                     </tr></table>";
00425             }
00426             # Add custom expiry field
00427             $attribs = array( 'id' => "mwProtect-$action-expires",
00428                 'onkeyup' => 'ProtectionForm.updateExpiry(this)',
00429                 'onchange' => 'ProtectionForm.updateExpiry(this)' ) + $this->disabledAttrib;
00430             $out .= "<table><tr>
00431                     <td class='mw-label'>" .
00432                         $mProtectother .
00433                     '</td>
00434                     <td class="mw-input">' .
00435                         Xml::input( "mwProtect-expiry-$action", 50, $this->mExpiry[$action], $attribs ) .
00436                     '</td>
00437                 </tr></table>';
00438             $out .= "</td></tr>" .
00439             Xml::closeElement( 'table' ) .
00440             Xml::closeElement( 'fieldset' ) .
00441             "</td></tr>";
00442         }
00443         # Give extensions a chance to add items to the form
00444         wfRunHooks( 'ProtectionForm::buildForm', array( $this->mArticle, &$out ) );
00445 
00446         $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
00447 
00448         // JavaScript will add another row with a value-chaining checkbox
00449         if ( $this->mTitle->exists() ) {
00450             $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table2' ) ) .
00451                 Xml::openElement( 'tbody' );
00452             $out .= '<tr>
00453                     <td></td>
00454                     <td class="mw-input">' .
00455                         Xml::checkLabel(
00456                             wfMessage( 'protect-cascade' )->text(),
00457                             'mwProtect-cascade',
00458                             'mwProtect-cascade',
00459                             $this->mCascade, $this->disabledAttrib
00460                         ) .
00461                     "</td>
00462                 </tr>\n";
00463             $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
00464         }
00465 
00466         # Add manual and custom reason field/selects as well as submit
00467         if ( !$this->disabled ) {
00468             $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table3' ) ) .
00469                 Xml::openElement( 'tbody' );
00470             $out .= "
00471                 <tr>
00472                     <td class='mw-label'>
00473                         {$mProtectreasonother}
00474                     </td>
00475                     <td class='mw-input'>
00476                         {$reasonDropDown}
00477                     </td>
00478                 </tr>
00479                 <tr>
00480                     <td class='mw-label'>
00481                         {$mProtectreason}
00482                     </td>
00483                     <td class='mw-input'>" .
00484                         Xml::input( 'mwProtect-reason', 60, $this->mReason, array( 'type' => 'text',
00485                             'id' => 'mwProtect-reason', 'maxlength' => 180 ) ) .
00486                             // Limited maxlength as the database trims at 255 bytes and other texts
00487                             // chosen by dropdown menus on this page are also included in this database field.
00488                             // The byte limit of 180 bytes is enforced in javascript
00489                     "</td>
00490                 </tr>";
00491             # Disallow watching is user is not logged in
00492             if ( $wgUser->isLoggedIn() ) {
00493                 $out .= "
00494                 <tr>
00495                     <td></td>
00496                     <td class='mw-input'>" .
00497                         Xml::checkLabel( wfMessage( 'watchthis' )->text(),
00498                             'mwProtectWatch', 'mwProtectWatch',
00499                             $wgUser->isWatched( $this->mTitle ) || $wgUser->getOption( 'watchdefault' ) ) .
00500                     "</td>
00501                 </tr>";
00502             }
00503             $out .= "
00504                 <tr>
00505                     <td></td>
00506                     <td class='mw-submit'>" .
00507                         Xml::submitButton(
00508                             wfMessage( 'confirm' )->text(),
00509                             array( 'id' => 'mw-Protect-submit' )
00510                         ) .
00511                     "</td>
00512                 </tr>\n";
00513             $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
00514         }
00515         $out .= Xml::closeElement( 'fieldset' );
00516 
00517         if ( $wgUser->isAllowed( 'editinterface' ) ) {
00518             $title = Title::makeTitle( NS_MEDIAWIKI, 'Protect-dropdown' );
00519             $link = Linker::link(
00520                 $title,
00521                 wfMessage( 'protect-edit-reasonlist' )->escaped(),
00522                 array(),
00523                 array( 'action' => 'edit' )
00524             );
00525             $out .= '<p class="mw-protect-editreasons">' . $link . '</p>';
00526         }
00527 
00528         if ( !$this->disabled ) {
00529             $out .= Html::hidden( 'wpEditToken', $wgUser->getEditToken( array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) );
00530             $out .= Xml::closeElement( 'form' );
00531             $wgOut->addScript( $this->buildCleanupScript() );
00532         }
00533 
00534         return $out;
00535     }
00536 
00544     function buildSelector( $action, $selected ) {
00545         global $wgUser;
00546 
00547         // If the form is disabled, display all relevant levels. Otherwise,
00548         // just show the ones this user can use.
00549         $levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(),
00550             $this->disabled ? null : $wgUser
00551         );
00552 
00553         $id = 'mwProtect-level-' . $action;
00554         $attribs = array(
00555             'id' => $id,
00556             'name' => $id,
00557             'size' => count( $levels ),
00558             'onchange' => 'ProtectionForm.updateLevels(this)',
00559             ) + $this->disabledAttrib;
00560 
00561         $out = Xml::openElement( 'select', $attribs );
00562         foreach ( $levels as $key ) {
00563             $out .= Xml::option( $this->getOptionLabel( $key ), $key, $key == $selected );
00564         }
00565         $out .= Xml::closeElement( 'select' );
00566         return $out;
00567     }
00568 
00575     private function getOptionLabel( $permission ) {
00576         if ( $permission == '' ) {
00577             return wfMessage( 'protect-default' )->text();
00578         } else {
00579             // Messages: protect-level-autoconfirmed, protect-level-sysop
00580             $msg = wfMessage( "protect-level-{$permission}" );
00581             if ( $msg->exists() ) {
00582                 return $msg->text();
00583             }
00584             return wfMessage( 'protect-fallback', $permission )->text();
00585         }
00586     }
00587 
00588     function buildCleanupScript() {
00589         global $wgCascadingRestrictionLevels, $wgOut;
00590 
00591         $cascadeableLevels = $wgCascadingRestrictionLevels;
00592         $options = array(
00593             'tableId' => 'mwProtectSet',
00594             'labelText' => wfMessage( 'protect-unchain-permissions' )->plain(),
00595             'numTypes' => count( $this->mApplicableTypes ),
00596             'existingMatch' => count( array_unique( $this->mExistingExpiry ) ) === 1,
00597         );
00598 
00599         $wgOut->addJsConfigVars( 'wgCascadeableLevels', $cascadeableLevels );
00600         $script = Xml::encodeJsCall( 'ProtectionForm.init', array( $options ) );
00601         return Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) );
00602     }
00603 
00610     function showLogExtract( &$out ) {
00611         # Show relevant lines from the protection log:
00612         $protectLogPage = new LogPage( 'protect' );
00613         $out->addHTML( Xml::element( 'h2', null, $protectLogPage->getName()->text() ) );
00614         LogEventsList::showLogExtract( $out, 'protect', $this->mTitle );
00615         # Let extensions add other relevant log extracts
00616         wfRunHooks( 'ProtectionForm::showLogExtract', array( $this->mArticle, $out ) );
00617     }
00618 }