MediaWiki  REL1_24
ProtectionForm.php
Go to the documentation of this file.
00001 <?php
00029 class ProtectionForm {
00031     protected $mRestrictions = array();
00032 
00034     protected $mReason = '';
00035 
00037     protected $mReasonSelection = '';
00038 
00040     protected $mCascade = false;
00041 
00043     protected $mExpiry = array();
00044 
00049     protected $mExpirySelection = array();
00050 
00052     protected $mPermErrors = array();
00053 
00055     protected $mApplicableTypes = array();
00056 
00058     protected $mExistingExpiry = array();
00059 
00061     private $mContext;
00062 
00063     function __construct( Article $article ) {
00064         // Set instance variables.
00065         $this->mArticle = $article;
00066         $this->mTitle = $article->getTitle();
00067         $this->mApplicableTypes = $this->mTitle->getRestrictionTypes();
00068         $this->mContext = $article->getContext();
00069 
00070         // Check if the form should be disabled.
00071         // If it is, the form will be available in read-only to show levels.
00072         $this->mPermErrors = $this->mTitle->getUserPermissionsErrors(
00073             'protect', $this->mContext->getUser()
00074         );
00075         if ( wfReadOnly() ) {
00076             $this->mPermErrors[] = array( 'readonlytext', wfReadOnlyReason() );
00077         }
00078         $this->disabled = $this->mPermErrors != array();
00079         $this->disabledAttrib = $this->disabled
00080             ? array( 'disabled' => 'disabled' )
00081             : array();
00082 
00083         $this->loadData();
00084     }
00085 
00089     function loadData() {
00090         $levels = MWNamespace::getRestrictionLevels(
00091             $this->mTitle->getNamespace(), $this->mContext->getUser()
00092         );
00093         $this->mCascade = $this->mTitle->areRestrictionsCascading();
00094 
00095         $request = $this->mContext->getRequest();
00096         $this->mReason = $request->getText( 'mwProtect-reason' );
00097         $this->mReasonSelection = $request->getText( 'wpProtectReasonSelection' );
00098         $this->mCascade = $request->getBool( 'mwProtect-cascade', $this->mCascade );
00099 
00100         foreach ( $this->mApplicableTypes as $action ) {
00101             // @todo FIXME: This form currently requires individual selections,
00102             // but the db allows multiples separated by commas.
00103 
00104             // Pull the actual restriction from the DB
00105             $this->mRestrictions[$action] = implode( '', $this->mTitle->getRestrictions( $action ) );
00106 
00107             if ( !$this->mRestrictions[$action] ) {
00108                 // No existing expiry
00109                 $existingExpiry = '';
00110             } else {
00111                 $existingExpiry = $this->mTitle->getRestrictionExpiry( $action );
00112             }
00113             $this->mExistingExpiry[$action] = $existingExpiry;
00114 
00115             $requestExpiry = $request->getText( "mwProtect-expiry-$action" );
00116             $requestExpirySelection = $request->getVal( "wpProtectExpirySelection-$action" );
00117 
00118             if ( $requestExpiry ) {
00119                 // Custom expiry takes precedence
00120                 $this->mExpiry[$action] = $requestExpiry;
00121                 $this->mExpirySelection[$action] = 'othertime';
00122             } elseif ( $requestExpirySelection ) {
00123                 // Expiry selected from list
00124                 $this->mExpiry[$action] = '';
00125                 $this->mExpirySelection[$action] = $requestExpirySelection;
00126             } elseif ( $existingExpiry ) {
00127                 // Use existing expiry in its own list item
00128                 $this->mExpiry[$action] = '';
00129                 $this->mExpirySelection[$action] = $existingExpiry;
00130             } else {
00131                 // Catches 'infinity' - Existing expiry is infinite, use "infinite" in drop-down
00132                 // Final default: infinite
00133                 $this->mExpiry[$action] = '';
00134                 $this->mExpirySelection[$action] = 'infinite';
00135             }
00136 
00137             $val = $request->getVal( "mwProtect-level-$action" );
00138             if ( isset( $val ) && in_array( $val, $levels ) ) {
00139                 $this->mRestrictions[$action] = $val;
00140             }
00141         }
00142     }
00143 
00151     function getExpiry( $action ) {
00152         if ( $this->mExpirySelection[$action] == 'existing' ) {
00153             return $this->mExistingExpiry[$action];
00154         } elseif ( $this->mExpirySelection[$action] == 'othertime' ) {
00155             $value = $this->mExpiry[$action];
00156         } else {
00157             $value = $this->mExpirySelection[$action];
00158         }
00159         if ( $value == 'infinite' || $value == 'indefinite' || $value == 'infinity' ) {
00160             $time = wfGetDB( DB_SLAVE )->getInfinity();
00161         } else {
00162             $unix = strtotime( $value );
00163 
00164             if ( !$unix || $unix === -1 ) {
00165                 return false;
00166             }
00167 
00168             // @todo FIXME: Non-qualified absolute times are not in users specified timezone
00169             // and there isn't notice about it in the ui
00170             $time = wfTimestamp( TS_MW, $unix );
00171         }
00172         return $time;
00173     }
00174 
00178     function execute() {
00179         if ( MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) === array( '' ) ) {
00180             throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' );
00181         }
00182 
00183         if ( $this->mContext->getRequest()->wasPosted() ) {
00184             if ( $this->save() ) {
00185                 $q = $this->mArticle->isRedirect() ? 'redirect=no' : '';
00186                 $this->mContext->getOutput()->redirect( $this->mTitle->getFullURL( $q ) );
00187             }
00188         } else {
00189             $this->show();
00190         }
00191     }
00192 
00198     function show( $err = null ) {
00199         $out = $this->mContext->getOutput();
00200         $out->setRobotPolicy( 'noindex,nofollow' );
00201         $out->addBacklinkSubtitle( $this->mTitle );
00202 
00203         if ( is_array( $err ) ) {
00204             $out->wrapWikiMsg( "<p class='error'>\n$1\n</p>\n", $err );
00205         } elseif ( is_string( $err ) ) {
00206             $out->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             $out->setPageTitle( wfMessage(
00213                 'protect-norestrictiontypes-title',
00214                 $this->mTitle->getPrefixedText()
00215             ) );
00216             $out->addWikiText( wfMessage( 'protect-norestrictiontypes-text' )->text() );
00217 
00218             // Show the log in case protection was possible once
00219             $this->showLogExtract( $out );
00220             // return as there isn't anything else we can do
00221             return;
00222         }
00223 
00224         list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
00225         if ( $cascadeSources && count( $cascadeSources ) > 0 ) {
00226             $titles = '';
00227 
00228             foreach ( $cascadeSources as $title ) {
00229                 $titles .= '* [[:' . $title->getPrefixedText() . "]]\n";
00230             }
00231 
00233             $out->wrapWikiMsg(
00234                 "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles . "</div>",
00235                 array( 'protect-cascadeon', count( $cascadeSources ) )
00236             );
00237         }
00238 
00239         # Show an appropriate message if the user isn't allowed or able to change
00240         # the protection settings at this time
00241         if ( $this->disabled ) {
00242             $out->setPageTitle(
00243                 wfMessage( 'protect-title-notallowed',
00244                     $this->mTitle->getPrefixedText() )
00245             );
00246             $out->addWikiText( $out->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) );
00247         } else {
00248             $out->setPageTitle( wfMessage( 'protect-title', $this->mTitle->getPrefixedText() ) );
00249             $out->addWikiMsg( 'protect-text',
00250                 wfEscapeWikiText( $this->mTitle->getPrefixedText() ) );
00251         }
00252 
00253         $out->addHTML( $this->buildForm() );
00254         $this->showLogExtract( $out );
00255     }
00256 
00262     function save() {
00263         # Permission check!
00264         if ( $this->disabled ) {
00265             $this->show();
00266             return false;
00267         }
00268 
00269         $request = $this->mContext->getRequest();
00270         $user = $this->mContext->getUser();
00271         $out = $this->mContext->getOutput();
00272         $token = $request->getVal( 'wpEditToken' );
00273         if ( !$user->matchEditToken( $token, array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) ) {
00274             $this->show( array( 'sessionfailure' ) );
00275             return false;
00276         }
00277 
00278         # Create reason string. Use list and/or custom string.
00279         $reasonstr = $this->mReasonSelection;
00280         if ( $reasonstr != 'other' && $this->mReason != '' ) {
00281             // Entry from drop down menu + additional comment
00282             $reasonstr .= wfMessage( 'colon-separator' )->text() . $this->mReason;
00283         } elseif ( $reasonstr == 'other' ) {
00284             $reasonstr = $this->mReason;
00285         }
00286         $expiry = array();
00287         foreach ( $this->mApplicableTypes as $action ) {
00288             $expiry[$action] = $this->getExpiry( $action );
00289             if ( empty( $this->mRestrictions[$action] ) ) {
00290                 continue; // unprotected
00291             }
00292             if ( !$expiry[$action] ) {
00293                 $this->show( array( 'protect_expiry_invalid' ) );
00294                 return false;
00295             }
00296             if ( $expiry[$action] < wfTimestampNow() ) {
00297                 $this->show( array( 'protect_expiry_old' ) );
00298                 return false;
00299             }
00300         }
00301 
00302         $this->mCascade = $request->getBool( 'mwProtect-cascade' );
00303 
00304         $status = $this->mArticle->doUpdateRestrictions(
00305             $this->mRestrictions,
00306             $expiry,
00307             $this->mCascade,
00308             $reasonstr,
00309             $user
00310         );
00311 
00312         if ( !$status->isOK() ) {
00313             $this->show( $out->parseInline( $status->getWikiText() ) );
00314             return false;
00315         }
00316 
00323         $errorMsg = '';
00324         if ( !wfRunHooks( 'ProtectionForm::save', array( $this->mArticle, &$errorMsg, $reasonstr ) ) ) {
00325             if ( $errorMsg == '' ) {
00326                 $errorMsg = array( 'hookaborted' );
00327             }
00328         }
00329         if ( $errorMsg != '' ) {
00330             $this->show( $errorMsg );
00331             return false;
00332         }
00333 
00334         WatchAction::doWatchOrUnwatch( $request->getCheck( 'mwProtectWatch' ), $this->mTitle, $user );
00335 
00336         return true;
00337     }
00338 
00344     function buildForm() {
00345         $user = $this->mContext->getUser();
00346         $output = $this->mContext->getOutput();
00347         $lang = $this->mContext->getLanguage();
00348         $cascadingRestrictionLevels = $this->mContext->getConfig()->get( 'CascadingRestrictionLevels' );
00349         $out = '';
00350         if ( !$this->disabled ) {
00351             $output->addModules( 'mediawiki.legacy.protect' );
00352             $output->addJsConfigVars( 'wgCascadeableLevels', $cascadingRestrictionLevels );
00353             $out .= Xml::openElement( 'form', array( 'method' => 'post',
00354                 'action' => $this->mTitle->getLocalURL( 'action=protect' ),
00355                 'id' => 'mw-Protect-Form' ) );
00356         }
00357 
00358         $out .= Xml::openElement( 'fieldset' ) .
00359             Xml::element( 'legend', null, wfMessage( 'protect-legend' )->text() ) .
00360             Xml::openElement( 'table', array( 'id' => 'mwProtectSet' ) ) .
00361             Xml::openElement( 'tbody' );
00362 
00363         $scExpiryOptions = wfMessage( 'protect-expiry-options' )->inContentLanguage()->text();
00364         $showProtectOptions = $scExpiryOptions !== '-' && !$this->disabled;
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             $mProtectexpiry = Xml::label(
00378                 wfMessage( 'protectexpiry' )->text(),
00379                 "mwProtectExpirySelection-$action"
00380             );
00381             $mProtectother = Xml::label(
00382                 wfMessage( 'protect-othertime' )->text(),
00383                 "mwProtect-$action-expires"
00384             );
00385 
00386             $expiryFormOptions = '';
00387             if ( $this->mExistingExpiry[$action] ) {
00388                 if ( $this->mExistingExpiry[$action] == 'infinity' ) {
00389                     $existingExpiryMessage = wfMessage( 'protect-existing-expiry-infinity' );
00390                 } else {
00391                     $timestamp = $lang->timeanddate( $this->mExistingExpiry[$action], true );
00392                     $d = $lang->date( $this->mExistingExpiry[$action], true );
00393                     $t = $lang->time( $this->mExistingExpiry[$action], true );
00394                     $existingExpiryMessage = wfMessage( 'protect-existing-expiry', $timestamp, $d, $t );
00395                 }
00396                 $expiryFormOptions .=
00397                     Xml::option(
00398                         $existingExpiryMessage->text(),
00399                         'existing',
00400                         $this->mExpirySelection[$action] == 'existing'
00401                     ) . "\n";
00402             }
00403 
00404             $expiryFormOptions .= Xml::option(
00405                 wfMessage( 'protect-othertime-op' )->text(),
00406                 "othertime"
00407             ) . "\n";
00408             foreach ( explode( ',', $scExpiryOptions ) as $option ) {
00409                 if ( strpos( $option, ":" ) === false ) {
00410                     $show = $value = $option;
00411                 } else {
00412                     list( $show, $value ) = explode( ":", $option );
00413                 }
00414                 $show = htmlspecialchars( $show );
00415                 $value = htmlspecialchars( $value );
00416                 $expiryFormOptions .= Xml::option(
00417                     $show,
00418                     $value,
00419                     $this->mExpirySelection[$action] === $value
00420                 ) . "\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                                     'tabindex' => '2' ) + $this->disabledAttrib,
00435                                 $expiryFormOptions ) .
00436                         "</td>
00437                     </tr></table>";
00438             }
00439             # Add custom expiry field
00440             $attribs = array( 'id' => "mwProtect-$action-expires" ) + $this->disabledAttrib;
00441             $out .= "<table><tr>
00442                     <td class='mw-label'>" .
00443                         $mProtectother .
00444                     '</td>
00445                     <td class="mw-input">' .
00446                         Xml::input( "mwProtect-expiry-$action", 50, $this->mExpiry[$action], $attribs ) .
00447                     '</td>
00448                 </tr></table>';
00449             $out .= "</td></tr>" .
00450             Xml::closeElement( 'table' ) .
00451             Xml::closeElement( 'fieldset' ) .
00452             "</td></tr>";
00453         }
00454         # Give extensions a chance to add items to the form
00455         wfRunHooks( 'ProtectionForm::buildForm', array( $this->mArticle, &$out ) );
00456 
00457         $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
00458 
00459         // JavaScript will add another row with a value-chaining checkbox
00460         if ( $this->mTitle->exists() ) {
00461             $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table2' ) ) .
00462                 Xml::openElement( 'tbody' );
00463             $out .= '<tr>
00464                     <td></td>
00465                     <td class="mw-input">' .
00466                         Xml::checkLabel(
00467                             wfMessage( 'protect-cascade' )->text(),
00468                             'mwProtect-cascade',
00469                             'mwProtect-cascade',
00470                             $this->mCascade, $this->disabledAttrib
00471                         ) .
00472                     "</td>
00473                 </tr>\n";
00474             $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
00475         }
00476 
00477         # Add manual and custom reason field/selects as well as submit
00478         if ( !$this->disabled ) {
00479             $mProtectreasonother = Xml::label(
00480                 wfMessage( 'protectcomment' )->text(),
00481                 'wpProtectReasonSelection'
00482             );
00483 
00484             $mProtectreason = Xml::label(
00485                 wfMessage( 'protect-otherreason' )->text(),
00486                 'mwProtect-reason'
00487             );
00488 
00489             $reasonDropDown = Xml::listDropDown( 'wpProtectReasonSelection',
00490                 wfMessage( 'protect-dropdown' )->inContentLanguage()->text(),
00491                 wfMessage( 'protect-otherreason-op' )->inContentLanguage()->text(),
00492                 $this->mReasonSelection,
00493                 'mwProtect-reason', 4 );
00494 
00495             $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table3' ) ) .
00496                 Xml::openElement( 'tbody' );
00497             $out .= "
00498                 <tr>
00499                     <td class='mw-label'>
00500                         {$mProtectreasonother}
00501                     </td>
00502                     <td class='mw-input'>
00503                         {$reasonDropDown}
00504                     </td>
00505                 </tr>
00506                 <tr>
00507                     <td class='mw-label'>
00508                         {$mProtectreason}
00509                     </td>
00510                     <td class='mw-input'>" .
00511                         Xml::input( 'mwProtect-reason', 60, $this->mReason, array( 'type' => 'text',
00512                             'id' => 'mwProtect-reason', 'maxlength' => 180 ) ) .
00513                             // Limited maxlength as the database trims at 255 bytes and other texts
00514                             // chosen by dropdown menus on this page are also included in this database field.
00515                             // The byte limit of 180 bytes is enforced in javascript
00516                     "</td>
00517                 </tr>";
00518             # Disallow watching is user is not logged in
00519             if ( $user->isLoggedIn() ) {
00520                 $out .= "
00521                 <tr>
00522                     <td></td>
00523                     <td class='mw-input'>" .
00524                         Xml::checkLabel( wfMessage( 'watchthis' )->text(),
00525                             'mwProtectWatch', 'mwProtectWatch',
00526                             $user->isWatched( $this->mTitle ) || $user->getOption( 'watchdefault' ) ) .
00527                     "</td>
00528                 </tr>";
00529             }
00530             $out .= "
00531                 <tr>
00532                     <td></td>
00533                     <td class='mw-submit'>" .
00534                         Xml::submitButton(
00535                             wfMessage( 'confirm' )->text(),
00536                             array( 'id' => 'mw-Protect-submit' )
00537                         ) .
00538                     "</td>
00539                 </tr>\n";
00540             $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
00541         }
00542         $out .= Xml::closeElement( 'fieldset' );
00543 
00544         if ( $user->isAllowed( 'editinterface' ) ) {
00545             $title = Title::makeTitle( NS_MEDIAWIKI, 'Protect-dropdown' );
00546             $link = Linker::link(
00547                 $title,
00548                 wfMessage( 'protect-edit-reasonlist' )->escaped(),
00549                 array(),
00550                 array( 'action' => 'edit' )
00551             );
00552             $out .= '<p class="mw-protect-editreasons">' . $link . '</p>';
00553         }
00554 
00555         if ( !$this->disabled ) {
00556             $out .= Html::hidden(
00557                 'wpEditToken',
00558                 $user->getEditToken( array( 'protect', $this->mTitle->getPrefixedDBkey() ) )
00559             );
00560             $out .= Xml::closeElement( 'form' );
00561         }
00562 
00563         return $out;
00564     }
00565 
00573     function buildSelector( $action, $selected ) {
00574         // If the form is disabled, display all relevant levels. Otherwise,
00575         // just show the ones this user can use.
00576         $levels = MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace(),
00577             $this->disabled ? null : $this->mContext->getUser()
00578         );
00579 
00580         $id = 'mwProtect-level-' . $action;
00581         $attribs = array(
00582             'id' => $id,
00583             'name' => $id,
00584             'size' => count( $levels ),
00585         ) + $this->disabledAttrib;
00586 
00587         $out = Xml::openElement( 'select', $attribs );
00588         foreach ( $levels as $key ) {
00589             $out .= Xml::option( $this->getOptionLabel( $key ), $key, $key == $selected );
00590         }
00591         $out .= Xml::closeElement( 'select' );
00592         return $out;
00593     }
00594 
00601     private function getOptionLabel( $permission ) {
00602         if ( $permission == '' ) {
00603             return wfMessage( 'protect-default' )->text();
00604         } else {
00605             // Messages: protect-level-autoconfirmed, protect-level-sysop
00606             $msg = wfMessage( "protect-level-{$permission}" );
00607             if ( $msg->exists() ) {
00608                 return $msg->text();
00609             }
00610             return wfMessage( 'protect-fallback', $permission )->text();
00611         }
00612     }
00613 
00620     function showLogExtract( &$out ) {
00621         # Show relevant lines from the protection log:
00622         $protectLogPage = new LogPage( 'protect' );
00623         $out->addHTML( Xml::element( 'h2', null, $protectLogPage->getName()->text() ) );
00624         LogEventsList::showLogExtract( $out, 'protect', $this->mTitle );
00625         # Let extensions add other relevant log extracts
00626         wfRunHooks( 'ProtectionForm::showLogExtract', array( $this->mArticle, $out ) );
00627     }
00628 }