PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/includes/ProtectionForm.php

https://bitbucket.org/brunodefraine/mediawiki
PHP | 614 lines | 416 code | 70 blank | 128 comment | 91 complexity | 04f7e2143b21bcbf64213873361c92b3 MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, LGPL-3.0
  1. <?php
  2. /**
  3. * Page protection
  4. *
  5. * Copyright Š 2005 Brion Vibber <brion@pobox.com>
  6. * http://www.mediawiki.org/
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License along
  19. * with this program; if not, write to the Free Software Foundation, Inc.,
  20. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21. * http://www.gnu.org/copyleft/gpl.html
  22. *
  23. * @file
  24. */
  25. /**
  26. * Handles the page protection UI and backend
  27. */
  28. class ProtectionForm {
  29. /** A map of action to restriction level, from request or default */
  30. var $mRestrictions = array();
  31. /** The custom/additional protection reason */
  32. var $mReason = '';
  33. /** The reason selected from the list, blank for other/additional */
  34. var $mReasonSelection = '';
  35. /** True if the restrictions are cascading, from request or existing protection */
  36. var $mCascade = false;
  37. /** Map of action to "other" expiry time. Used in preference to mExpirySelection. */
  38. var $mExpiry = array();
  39. /**
  40. * Map of action to value selected in expiry drop-down list.
  41. * Will be set to 'othertime' whenever mExpiry is set.
  42. */
  43. var $mExpirySelection = array();
  44. /** Permissions errors for the protect action */
  45. var $mPermErrors = array();
  46. /** Types (i.e. actions) for which levels can be selected */
  47. var $mApplicableTypes = array();
  48. /** Map of action to the expiry time of the existing protection */
  49. var $mExistingExpiry = array();
  50. function __construct( Page $article ) {
  51. global $wgUser;
  52. // Set instance variables.
  53. $this->mArticle = $article;
  54. $this->mTitle = $article->getTitle();
  55. $this->mApplicableTypes = $this->mTitle->getRestrictionTypes();
  56. // Check if the form should be disabled.
  57. // If it is, the form will be available in read-only to show levels.
  58. $this->mPermErrors = $this->mTitle->getUserPermissionsErrors( 'protect', $wgUser );
  59. if ( wfReadOnly() ) {
  60. $this->mPermErrors[] = array( 'readonlytext', wfReadOnlyReason() );
  61. }
  62. $this->disabled = $this->mPermErrors != array();
  63. $this->disabledAttrib = $this->disabled
  64. ? array( 'disabled' => 'disabled' )
  65. : array();
  66. $this->loadData();
  67. }
  68. /**
  69. * Loads the current state of protection into the object.
  70. */
  71. function loadData() {
  72. global $wgRequest, $wgUser;
  73. global $wgRestrictionLevels;
  74. $this->mCascade = $this->mTitle->areRestrictionsCascading();
  75. $this->mReason = $wgRequest->getText( 'mwProtect-reason' );
  76. $this->mReasonSelection = $wgRequest->getText( 'wpProtectReasonSelection' );
  77. $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade', $this->mCascade );
  78. foreach( $this->mApplicableTypes as $action ) {
  79. // @todo FIXME: This form currently requires individual selections,
  80. // but the db allows multiples separated by commas.
  81. // Pull the actual restriction from the DB
  82. $this->mRestrictions[$action] = implode( '', $this->mTitle->getRestrictions( $action ) );
  83. if ( !$this->mRestrictions[$action] ) {
  84. // No existing expiry
  85. $existingExpiry = '';
  86. } else {
  87. $existingExpiry = $this->mTitle->getRestrictionExpiry( $action );
  88. }
  89. $this->mExistingExpiry[$action] = $existingExpiry;
  90. $requestExpiry = $wgRequest->getText( "mwProtect-expiry-$action" );
  91. $requestExpirySelection = $wgRequest->getVal( "wpProtectExpirySelection-$action" );
  92. if ( $requestExpiry ) {
  93. // Custom expiry takes precedence
  94. $this->mExpiry[$action] = $requestExpiry;
  95. $this->mExpirySelection[$action] = 'othertime';
  96. } elseif ( $requestExpirySelection ) {
  97. // Expiry selected from list
  98. $this->mExpiry[$action] = '';
  99. $this->mExpirySelection[$action] = $requestExpirySelection;
  100. } elseif ( $existingExpiry == 'infinity' ) {
  101. // Existing expiry is infinite, use "infinite" in drop-down
  102. $this->mExpiry[$action] = '';
  103. $this->mExpirySelection[$action] = 'infinite';
  104. } elseif ( $existingExpiry ) {
  105. // Use existing expiry in its own list item
  106. $this->mExpiry[$action] = '';
  107. $this->mExpirySelection[$action] = $existingExpiry;
  108. } else {
  109. // Final default: infinite
  110. $this->mExpiry[$action] = '';
  111. $this->mExpirySelection[$action] = 'infinite';
  112. }
  113. $val = $wgRequest->getVal( "mwProtect-level-$action" );
  114. if( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
  115. // Prevent users from setting levels that they cannot later unset
  116. if( $val == 'sysop' ) {
  117. // Special case, rewrite sysop to either protect and editprotected
  118. if( !$wgUser->isAllowedAny( 'protect', 'editprotected' ) )
  119. continue;
  120. } else {
  121. if( !$wgUser->isAllowed($val) )
  122. continue;
  123. }
  124. $this->mRestrictions[$action] = $val;
  125. }
  126. }
  127. }
  128. /**
  129. * Get the expiry time for a given action, by combining the relevant inputs.
  130. *
  131. * @param $action string
  132. *
  133. * @return string 14-char timestamp or "infinity", or false if the input was invalid
  134. */
  135. function getExpiry( $action ) {
  136. if ( $this->mExpirySelection[$action] == 'existing' ) {
  137. return $this->mExistingExpiry[$action];
  138. } elseif ( $this->mExpirySelection[$action] == 'othertime' ) {
  139. $value = $this->mExpiry[$action];
  140. } else {
  141. $value = $this->mExpirySelection[$action];
  142. }
  143. if ( $value == 'infinite' || $value == 'indefinite' || $value == 'infinity' ) {
  144. $time = wfGetDB( DB_SLAVE )->getInfinity();
  145. } else {
  146. $unix = strtotime( $value );
  147. if ( !$unix || $unix === -1 ) {
  148. return false;
  149. }
  150. // @todo FIXME: Non-qualified absolute times are not in users specified timezone
  151. // and there isn't notice about it in the ui
  152. $time = wfTimestamp( TS_MW, $unix );
  153. }
  154. return $time;
  155. }
  156. /**
  157. * Main entry point for action=protect and action=unprotect
  158. */
  159. function execute() {
  160. global $wgRequest, $wgOut;
  161. if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
  162. throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' );
  163. }
  164. if( $wgRequest->wasPosted() ) {
  165. if( $this->save() ) {
  166. $q = $this->mArticle->isRedirect() ? 'redirect=no' : '';
  167. $wgOut->redirect( $this->mTitle->getFullUrl( $q ) );
  168. }
  169. } else {
  170. $this->show();
  171. }
  172. }
  173. /**
  174. * Show the input form with optional error message
  175. *
  176. * @param $err String: error message or null if there's no error
  177. */
  178. function show( $err = null ) {
  179. global $wgOut;
  180. $wgOut->setRobotPolicy( 'noindex,nofollow' );
  181. if ( is_array( $err ) ) {
  182. $wgOut->wrapWikiMsg( "<p class='error'>\n$1\n</p>\n", $err );
  183. } elseif ( is_string( $err ) ) {
  184. $wgOut->addHTML( "<p class='error'>{$err}</p>\n" );
  185. }
  186. list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
  187. if ( $cascadeSources && count($cascadeSources) > 0 ) {
  188. $titles = '';
  189. foreach ( $cascadeSources as $title ) {
  190. $titles .= '* [[:' . $title->getPrefixedText() . "]]\n";
  191. }
  192. $wgOut->wrapWikiMsg( "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles . "</div>", array( 'protect-cascadeon', count($cascadeSources) ) );
  193. }
  194. # Show an appropriate message if the user isn't allowed or able to change
  195. # the protection settings at this time
  196. if ( $this->disabled ) {
  197. $wgOut->setPageTitle( wfMessage( 'protect-title-notallowed', $this->mTitle->getPrefixedText() ) );
  198. $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) );
  199. } else {
  200. $wgOut->setPageTitle( wfMessage( 'protect-title', $this->mTitle->getPrefixedText() ) );
  201. $wgOut->addWikiMsg( 'protect-text',
  202. wfEscapeWikiText( $this->mTitle->getPrefixedText() ) );
  203. }
  204. $wgOut->addBacklinkSubtitle( $this->mTitle );
  205. $wgOut->addHTML( $this->buildForm() );
  206. $this->showLogExtract( $wgOut );
  207. }
  208. /**
  209. * Save submitted protection form
  210. *
  211. * @return Boolean: success
  212. */
  213. function save() {
  214. global $wgRequest, $wgUser, $wgOut;
  215. # Permission check!
  216. if ( $this->disabled ) {
  217. $this->show();
  218. return false;
  219. }
  220. $token = $wgRequest->getVal( 'wpEditToken' );
  221. if ( !$wgUser->matchEditToken( $token, array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) ) {
  222. $this->show( array( 'sessionfailure' ) );
  223. return false;
  224. }
  225. # Create reason string. Use list and/or custom string.
  226. $reasonstr = $this->mReasonSelection;
  227. if ( $reasonstr != 'other' && $this->mReason != '' ) {
  228. // Entry from drop down menu + additional comment
  229. $reasonstr .= wfMsgForContent( 'colon-separator' ) . $this->mReason;
  230. } elseif ( $reasonstr == 'other' ) {
  231. $reasonstr = $this->mReason;
  232. }
  233. $expiry = array();
  234. foreach( $this->mApplicableTypes as $action ) {
  235. $expiry[$action] = $this->getExpiry( $action );
  236. if( empty($this->mRestrictions[$action]) )
  237. continue; // unprotected
  238. if ( !$expiry[$action] ) {
  239. $this->show( array( 'protect_expiry_invalid' ) );
  240. return false;
  241. }
  242. if ( $expiry[$action] < wfTimestampNow() ) {
  243. $this->show( array( 'protect_expiry_old' ) );
  244. return false;
  245. }
  246. }
  247. # They shouldn't be able to do this anyway, but just to make sure, ensure that cascading restrictions aren't being applied
  248. # to a semi-protected page.
  249. global $wgGroupPermissions;
  250. $edit_restriction = isset( $this->mRestrictions['edit'] ) ? $this->mRestrictions['edit'] : '';
  251. $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
  252. if ($this->mCascade && ($edit_restriction != 'protect') &&
  253. !(isset($wgGroupPermissions[$edit_restriction]['protect']) && $wgGroupPermissions[$edit_restriction]['protect'] ) )
  254. $this->mCascade = false;
  255. $status = $this->mArticle->doUpdateRestrictions( $this->mRestrictions, $expiry, $this->mCascade, $reasonstr, $wgUser );
  256. if ( !$status->isOK() ) {
  257. $this->show( $wgOut->parseInline( $status->getWikiText() ) );
  258. return false;
  259. }
  260. /**
  261. * Give extensions a change to handle added form items
  262. *
  263. * @since 1.19 you can (and you should) return false to abort saving;
  264. * you can also return an array of message name and its parameters
  265. */
  266. $errorMsg = '';
  267. if( !wfRunHooks( 'ProtectionForm::save', array( $this->mArticle, &$errorMsg ) ) ) {
  268. if ( $errorMsg == '' ) {
  269. $errorMsg = array( 'hookaborted' );
  270. }
  271. }
  272. if( $errorMsg != '' ) {
  273. $this->show( $errorMsg );
  274. return false;
  275. }
  276. if ( $wgRequest->getCheck( 'mwProtectWatch' ) && $wgUser->isLoggedIn() ) {
  277. WatchAction::doWatch( $this->mTitle, $wgUser );
  278. } elseif ( $this->mTitle->userIsWatching() ) {
  279. WatchAction::doUnwatch( $this->mTitle, $wgUser );
  280. }
  281. return true;
  282. }
  283. /**
  284. * Build the input form
  285. *
  286. * @return String: HTML form
  287. */
  288. function buildForm() {
  289. global $wgUser, $wgLang, $wgOut;
  290. $mProtectreasonother = Xml::label( wfMsg( 'protectcomment' ), 'wpProtectReasonSelection' );
  291. $mProtectreason = Xml::label( wfMsg( 'protect-otherreason' ), 'mwProtect-reason' );
  292. $out = '';
  293. if( !$this->disabled ) {
  294. $wgOut->addModules( 'mediawiki.legacy.protect' );
  295. $out .= Xml::openElement( 'form', array( 'method' => 'post',
  296. 'action' => $this->mTitle->getLocalUrl( 'action=protect' ),
  297. 'id' => 'mw-Protect-Form', 'onsubmit' => 'ProtectionForm.enableUnchainedInputs(true)' ) );
  298. }
  299. $out .= Xml::openElement( 'fieldset' ) .
  300. Xml::element( 'legend', null, wfMsg( 'protect-legend' ) ) .
  301. Xml::openElement( 'table', array( 'id' => 'mwProtectSet' ) ) .
  302. Xml::openElement( 'tbody' );
  303. foreach( $this->mRestrictions as $action => $selected ) {
  304. /* Not all languages have V_x <-> N_x relation */
  305. $msg = wfMessage( 'restriction-' . $action );
  306. $out .= "<tr><td>".
  307. Xml::openElement( 'fieldset' ) .
  308. Xml::element( 'legend', null, $msg->exists() ? $msg->text() : $action ) .
  309. Xml::openElement( 'table', array( 'id' => "mw-protect-table-$action" ) ) .
  310. "<tr><td>" . $this->buildSelector( $action, $selected ) . "</td></tr><tr><td>";
  311. $reasonDropDown = Xml::listDropDown( 'wpProtectReasonSelection',
  312. wfMsgForContent( 'protect-dropdown' ),
  313. wfMsgForContent( 'protect-otherreason-op' ),
  314. $this->mReasonSelection,
  315. 'mwProtect-reason', 4 );
  316. $scExpiryOptions = wfMsgForContent( 'protect-expiry-options' );
  317. $showProtectOptions = ($scExpiryOptions !== '-' && !$this->disabled);
  318. $mProtectexpiry = Xml::label( wfMsg( 'protectexpiry' ), "mwProtectExpirySelection-$action" );
  319. $mProtectother = Xml::label( wfMsg( 'protect-othertime' ), "mwProtect-$action-expires" );
  320. $expiryFormOptions = '';
  321. if ( $this->mExistingExpiry[$action] && $this->mExistingExpiry[$action] != 'infinity' ) {
  322. $timestamp = $wgLang->timeanddate( $this->mExistingExpiry[$action], true );
  323. $d = $wgLang->date( $this->mExistingExpiry[$action], true );
  324. $t = $wgLang->time( $this->mExistingExpiry[$action], true );
  325. $expiryFormOptions .=
  326. Xml::option(
  327. wfMsg( 'protect-existing-expiry', $timestamp, $d, $t ),
  328. 'existing',
  329. $this->mExpirySelection[$action] == 'existing'
  330. ) . "\n";
  331. }
  332. $expiryFormOptions .= Xml::option( wfMsg( 'protect-othertime-op' ), "othertime" ) . "\n";
  333. foreach( explode(',', $scExpiryOptions) as $option ) {
  334. if ( strpos($option, ":") === false ) {
  335. $show = $value = $option;
  336. } else {
  337. list($show, $value) = explode(":", $option);
  338. }
  339. $show = htmlspecialchars($show);
  340. $value = htmlspecialchars($value);
  341. $expiryFormOptions .= Xml::option( $show, $value, $this->mExpirySelection[$action] === $value ) . "\n";
  342. }
  343. # Add expiry dropdown
  344. if( $showProtectOptions && !$this->disabled ) {
  345. $out .= "
  346. <table><tr>
  347. <td class='mw-label'>
  348. {$mProtectexpiry}
  349. </td>
  350. <td class='mw-input'>" .
  351. Xml::tags( 'select',
  352. array(
  353. 'id' => "mwProtectExpirySelection-$action",
  354. 'name' => "wpProtectExpirySelection-$action",
  355. 'onchange' => "ProtectionForm.updateExpiryList(this)",
  356. 'tabindex' => '2' ) + $this->disabledAttrib,
  357. $expiryFormOptions ) .
  358. "</td>
  359. </tr></table>";
  360. }
  361. # Add custom expiry field
  362. $attribs = array( 'id' => "mwProtect-$action-expires",
  363. 'onkeyup' => 'ProtectionForm.updateExpiry(this)',
  364. 'onchange' => 'ProtectionForm.updateExpiry(this)' ) + $this->disabledAttrib;
  365. $out .= "<table><tr>
  366. <td class='mw-label'>" .
  367. $mProtectother .
  368. '</td>
  369. <td class="mw-input">' .
  370. Xml::input( "mwProtect-expiry-$action", 50, $this->mExpiry[$action], $attribs ) .
  371. '</td>
  372. </tr></table>';
  373. $out .= "</td></tr>" .
  374. Xml::closeElement( 'table' ) .
  375. Xml::closeElement( 'fieldset' ) .
  376. "</td></tr>";
  377. }
  378. # Give extensions a chance to add items to the form
  379. wfRunHooks( 'ProtectionForm::buildForm', array($this->mArticle,&$out) );
  380. $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
  381. // JavaScript will add another row with a value-chaining checkbox
  382. if( $this->mTitle->exists() ) {
  383. $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table2' ) ) .
  384. Xml::openElement( 'tbody' );
  385. $out .= '<tr>
  386. <td></td>
  387. <td class="mw-input">' .
  388. Xml::checkLabel( wfMsg( 'protect-cascade' ), 'mwProtect-cascade', 'mwProtect-cascade',
  389. $this->mCascade, $this->disabledAttrib ) .
  390. "</td>
  391. </tr>\n";
  392. $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
  393. }
  394. # Add manual and custom reason field/selects as well as submit
  395. if( !$this->disabled ) {
  396. $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table3' ) ) .
  397. Xml::openElement( 'tbody' );
  398. $out .= "
  399. <tr>
  400. <td class='mw-label'>
  401. {$mProtectreasonother}
  402. </td>
  403. <td class='mw-input'>
  404. {$reasonDropDown}
  405. </td>
  406. </tr>
  407. <tr>
  408. <td class='mw-label'>
  409. {$mProtectreason}
  410. </td>
  411. <td class='mw-input'>" .
  412. Xml::input( 'mwProtect-reason', 60, $this->mReason, array( 'type' => 'text',
  413. 'id' => 'mwProtect-reason', 'maxlength' => 180 ) ) .
  414. // Limited maxlength as the database trims at 255 bytes and other texts
  415. // chosen by dropdown menus on this page are also included in this database field.
  416. // The byte limit of 180 bytes is enforced in javascript
  417. "</td>
  418. </tr>";
  419. # Disallow watching is user is not logged in
  420. if( $wgUser->isLoggedIn() ) {
  421. $out .= "
  422. <tr>
  423. <td></td>
  424. <td class='mw-input'>" .
  425. Xml::checkLabel( wfMsg( 'watchthis' ),
  426. 'mwProtectWatch', 'mwProtectWatch',
  427. $this->mTitle->userIsWatching() || $wgUser->getOption( 'watchdefault' ) ) .
  428. "</td>
  429. </tr>";
  430. }
  431. $out .= "
  432. <tr>
  433. <td></td>
  434. <td class='mw-submit'>" .
  435. Xml::submitButton( wfMsg( 'confirm' ), array( 'id' => 'mw-Protect-submit' ) ) .
  436. "</td>
  437. </tr>\n";
  438. $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
  439. }
  440. $out .= Xml::closeElement( 'fieldset' );
  441. if ( $wgUser->isAllowed( 'editinterface' ) ) {
  442. $title = Title::makeTitle( NS_MEDIAWIKI, 'Protect-dropdown' );
  443. $link = Linker::link(
  444. $title,
  445. wfMsgHtml( 'protect-edit-reasonlist' ),
  446. array(),
  447. array( 'action' => 'edit' )
  448. );
  449. $out .= '<p class="mw-protect-editreasons">' . $link . '</p>';
  450. }
  451. if ( !$this->disabled ) {
  452. $out .= Html::hidden( 'wpEditToken', $wgUser->getEditToken( array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) );
  453. $out .= Xml::closeElement( 'form' );
  454. $wgOut->addScript( $this->buildCleanupScript() );
  455. }
  456. return $out;
  457. }
  458. /**
  459. * Build protection level selector
  460. *
  461. * @param $action String: action to protect
  462. * @param $selected String: current protection level
  463. * @return String: HTML fragment
  464. */
  465. function buildSelector( $action, $selected ) {
  466. global $wgRestrictionLevels, $wgUser;
  467. $levels = array();
  468. foreach( $wgRestrictionLevels as $key ) {
  469. //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
  470. if( $key == 'sysop' ) {
  471. //special case, rewrite sysop to protect and editprotected
  472. if( !$wgUser->isAllowedAny( 'protect', 'editprotected' ) && !$this->disabled )
  473. continue;
  474. } else {
  475. if( !$wgUser->isAllowed($key) && !$this->disabled )
  476. continue;
  477. }
  478. $levels[] = $key;
  479. }
  480. $id = 'mwProtect-level-' . $action;
  481. $attribs = array(
  482. 'id' => $id,
  483. 'name' => $id,
  484. 'size' => count( $levels ),
  485. 'onchange' => 'ProtectionForm.updateLevels(this)',
  486. ) + $this->disabledAttrib;
  487. $out = Xml::openElement( 'select', $attribs );
  488. foreach( $levels as $key ) {
  489. $out .= Xml::option( $this->getOptionLabel( $key ), $key, $key == $selected );
  490. }
  491. $out .= Xml::closeElement( 'select' );
  492. return $out;
  493. }
  494. /**
  495. * Prepare the label for a protection selector option
  496. *
  497. * @param $permission String: permission required
  498. * @return String
  499. */
  500. private function getOptionLabel( $permission ) {
  501. if( $permission == '' ) {
  502. return wfMsg( 'protect-default' );
  503. } else {
  504. $msg = wfMessage( "protect-level-{$permission}" );
  505. if( $msg->exists() ) {
  506. return $msg->text();
  507. }
  508. return wfMsg( 'protect-fallback', $permission );
  509. }
  510. }
  511. function buildCleanupScript() {
  512. global $wgRestrictionLevels, $wgGroupPermissions, $wgOut;
  513. $cascadeableLevels = array();
  514. foreach( $wgRestrictionLevels as $key ) {
  515. if ( ( isset( $wgGroupPermissions[$key]['protect'] ) && $wgGroupPermissions[$key]['protect'] )
  516. || $key == 'protect'
  517. ) {
  518. $cascadeableLevels[] = $key;
  519. }
  520. }
  521. $options = array(
  522. 'tableId' => 'mwProtectSet',
  523. 'labelText' => wfMessage( 'protect-unchain-permissions' )->plain(),
  524. 'numTypes' => count( $this->mApplicableTypes ),
  525. 'existingMatch' => count( array_unique( $this->mExistingExpiry ) ) === 1,
  526. );
  527. $wgOut->addJsConfigVars( 'wgCascadeableLevels', $cascadeableLevels );
  528. $script = Xml::encodeJsCall( 'ProtectionForm.init', array( $options ) );
  529. return Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) );
  530. }
  531. /**
  532. * Show protection long extracts for this page
  533. *
  534. * @param $out OutputPage
  535. * @access private
  536. */
  537. function showLogExtract( &$out ) {
  538. # Show relevant lines from the protection log:
  539. $out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'protect' ) ) );
  540. LogEventsList::showLogExtract( $out, 'protect', $this->mTitle );
  541. # Let extensions add other relevant log extracts
  542. wfRunHooks( 'ProtectionForm::showLogExtract', array($this->mArticle,$out) );
  543. }
  544. }