SpecialRevisionDelete.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. <?php
  2. /**
  3. * Implements Special:Revisiondelete
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup SpecialPage
  22. */
  23. use MediaWiki\MediaWikiServices;
  24. use MediaWiki\Revision\RevisionRecord;
  25. use MediaWiki\Permissions\PermissionManager;
  26. /**
  27. * Special page allowing users with the appropriate permissions to view
  28. * and hide revisions. Log items can also be hidden.
  29. *
  30. * @ingroup SpecialPage
  31. */
  32. class SpecialRevisionDelete extends UnlistedSpecialPage {
  33. /** @var bool Was the DB modified in this request */
  34. protected $wasSaved = false;
  35. /** @var bool True if the submit button was clicked, and the form was posted */
  36. private $submitClicked;
  37. /** @var array Target ID list */
  38. private $ids;
  39. /** @var string Archive name, for reviewing deleted files */
  40. private $archiveName;
  41. /** @var string Edit token for securing image views against XSS */
  42. private $token;
  43. /** @var Title Title object for target parameter */
  44. private $targetObj;
  45. /** @var string Deletion type, may be revision, archive, oldimage, filearchive, logging. */
  46. private $typeName;
  47. /** @var array Array of checkbox specs (message, name, deletion bits) */
  48. private $checks;
  49. /** @var array UI Labels about the current type */
  50. private $typeLabels;
  51. /** @var RevDelList RevDelList object, storing the list of items to be deleted/undeleted */
  52. private $revDelList;
  53. /** @var bool Whether user is allowed to perform the action */
  54. private $mIsAllowed;
  55. /** @var string */
  56. private $otherReason;
  57. /** @var PermissionManager */
  58. private $permissionManager;
  59. /**
  60. * UI labels for each type.
  61. */
  62. private static $UILabels = [
  63. 'revision' => [
  64. 'check-label' => 'revdelete-hide-text',
  65. 'success' => 'revdelete-success',
  66. 'failure' => 'revdelete-failure',
  67. 'text' => 'revdelete-text-text',
  68. 'selected' => 'revdelete-selected-text',
  69. ],
  70. 'archive' => [
  71. 'check-label' => 'revdelete-hide-text',
  72. 'success' => 'revdelete-success',
  73. 'failure' => 'revdelete-failure',
  74. 'text' => 'revdelete-text-text',
  75. 'selected' => 'revdelete-selected-text',
  76. ],
  77. 'oldimage' => [
  78. 'check-label' => 'revdelete-hide-image',
  79. 'success' => 'revdelete-success',
  80. 'failure' => 'revdelete-failure',
  81. 'text' => 'revdelete-text-file',
  82. 'selected' => 'revdelete-selected-file',
  83. ],
  84. 'filearchive' => [
  85. 'check-label' => 'revdelete-hide-image',
  86. 'success' => 'revdelete-success',
  87. 'failure' => 'revdelete-failure',
  88. 'text' => 'revdelete-text-file',
  89. 'selected' => 'revdelete-selected-file',
  90. ],
  91. 'logging' => [
  92. 'check-label' => 'revdelete-hide-name',
  93. 'success' => 'logdelete-success',
  94. 'failure' => 'logdelete-failure',
  95. 'text' => 'logdelete-text',
  96. 'selected' => 'logdelete-selected',
  97. ],
  98. ];
  99. /**
  100. * @inheritDoc
  101. *
  102. * @param PermissionManager $permissionManager
  103. */
  104. public function __construct( PermissionManager $permissionManager ) {
  105. parent::__construct( 'Revisiondelete', 'deleterevision' );
  106. $this->permissionManager = $permissionManager;
  107. }
  108. public function doesWrites() {
  109. return true;
  110. }
  111. public function execute( $par ) {
  112. $this->useTransactionalTimeLimit();
  113. $this->checkPermissions();
  114. $this->checkReadOnly();
  115. $output = $this->getOutput();
  116. $user = $this->getUser();
  117. $this->setHeaders();
  118. $this->outputHeader();
  119. $request = $this->getRequest();
  120. $this->submitClicked = $request->wasPosted() && $request->getBool( 'wpSubmit' );
  121. # Handle our many different possible input types.
  122. $ids = $request->getVal( 'ids' );
  123. if ( !is_null( $ids ) ) {
  124. # Allow CSV, for backwards compatibility, or a single ID for show/hide links
  125. $this->ids = explode( ',', $ids );
  126. } else {
  127. # Array input
  128. $this->ids = array_keys( $request->getArray( 'ids', [] ) );
  129. }
  130. // $this->ids = array_map( 'intval', $this->ids );
  131. $this->ids = array_unique( array_filter( $this->ids ) );
  132. $this->typeName = $request->getVal( 'type' );
  133. $this->targetObj = Title::newFromText( $request->getText( 'target' ) );
  134. # For reviewing deleted files...
  135. $this->archiveName = $request->getVal( 'file' );
  136. $this->token = $request->getVal( 'token' );
  137. if ( $this->archiveName && $this->targetObj ) {
  138. $this->tryShowFile( $this->archiveName );
  139. return;
  140. }
  141. $this->typeName = RevisionDeleter::getCanonicalTypeName( $this->typeName );
  142. # No targets?
  143. if ( !$this->typeName || count( $this->ids ) == 0 ) {
  144. throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
  145. }
  146. # Allow the list type to adjust the passed target
  147. $this->targetObj = RevisionDeleter::suggestTarget(
  148. $this->typeName,
  149. $this->targetObj,
  150. $this->ids
  151. );
  152. # We need a target page!
  153. if ( $this->targetObj === null ) {
  154. $output->addWikiMsg( 'undelete-header' );
  155. return;
  156. }
  157. // Check blocks
  158. if ( $this->permissionManager->isBlockedFrom( $user, $this->targetObj ) ) {
  159. throw new UserBlockedError( $user->getBlock() );
  160. }
  161. $this->typeLabels = self::$UILabels[$this->typeName];
  162. $list = $this->getList();
  163. $list->reset();
  164. $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
  165. $this->mIsAllowed = $permissionManager->userHasRight( $user,
  166. RevisionDeleter::getRestriction( $this->typeName ) );
  167. $canViewSuppressedOnly = $permissionManager->userHasRight( $user, 'viewsuppressed' ) &&
  168. !$permissionManager->userHasRight( $user, 'suppressrevision' );
  169. $pageIsSuppressed = $list->areAnySuppressed();
  170. $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
  171. $this->otherReason = $request->getVal( 'wpReason' );
  172. # Give a link to the logs/hist for this page
  173. $this->showConvenienceLinks();
  174. # Initialise checkboxes
  175. $this->checks = [
  176. # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
  177. [ $this->typeLabels['check-label'], 'wpHidePrimary',
  178. RevisionDeleter::getRevdelConstant( $this->typeName )
  179. ],
  180. [ 'revdelete-hide-comment', 'wpHideComment', RevisionRecord::DELETED_COMMENT ],
  181. [ 'revdelete-hide-user', 'wpHideUser', RevisionRecord::DELETED_USER ]
  182. ];
  183. if ( $permissionManager->userHasRight( $user, 'suppressrevision' ) ) {
  184. $this->checks[] = [ 'revdelete-hide-restricted',
  185. 'wpHideRestricted', RevisionRecord::DELETED_RESTRICTED ];
  186. }
  187. # Either submit or create our form
  188. if ( $this->mIsAllowed && $this->submitClicked ) {
  189. $this->submit();
  190. } else {
  191. $this->showForm();
  192. }
  193. if ( $permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
  194. $qc = $this->getLogQueryCond();
  195. # Show relevant lines from the deletion log
  196. $deleteLogPage = new LogPage( 'delete' );
  197. $output->addHTML( "<h2>" . $deleteLogPage->getName()->escaped() . "</h2>\n" );
  198. LogEventsList::showLogExtract(
  199. $output,
  200. 'delete',
  201. $this->targetObj,
  202. '', /* user */
  203. [ 'lim' => 25, 'conds' => $qc, 'useMaster' => $this->wasSaved ]
  204. );
  205. }
  206. # Show relevant lines from the suppression log
  207. if ( $permissionManager->userHasRight( $user, 'suppressionlog' ) ) {
  208. $suppressLogPage = new LogPage( 'suppress' );
  209. $output->addHTML( "<h2>" . $suppressLogPage->getName()->escaped() . "</h2>\n" );
  210. LogEventsList::showLogExtract(
  211. $output,
  212. 'suppress',
  213. $this->targetObj,
  214. '',
  215. [ 'lim' => 25, 'conds' => $qc, 'useMaster' => $this->wasSaved ]
  216. );
  217. }
  218. }
  219. /**
  220. * Show some useful links in the subtitle
  221. */
  222. protected function showConvenienceLinks() {
  223. $linkRenderer = $this->getLinkRenderer();
  224. # Give a link to the logs/hist for this page
  225. if ( $this->targetObj ) {
  226. // Also set header tabs to be for the target.
  227. $this->getSkin()->setRelevantTitle( $this->targetObj );
  228. $links = [];
  229. $links[] = $linkRenderer->makeKnownLink(
  230. SpecialPage::getTitleFor( 'Log' ),
  231. $this->msg( 'viewpagelogs' )->text(),
  232. [],
  233. [ 'page' => $this->targetObj->getPrefixedText() ]
  234. );
  235. if ( !$this->targetObj->isSpecialPage() ) {
  236. # Give a link to the page history
  237. $links[] = $linkRenderer->makeKnownLink(
  238. $this->targetObj,
  239. $this->msg( 'pagehist' )->text(),
  240. [],
  241. [ 'action' => 'history' ]
  242. );
  243. # Link to deleted edits
  244. if ( MediaWikiServices::getInstance()
  245. ->getPermissionManager()
  246. ->userHasRight( $this->getUser(), 'undelete' )
  247. ) {
  248. $undelete = SpecialPage::getTitleFor( 'Undelete' );
  249. $links[] = $linkRenderer->makeKnownLink(
  250. $undelete,
  251. $this->msg( 'deletedhist' )->text(),
  252. [],
  253. [ 'target' => $this->targetObj->getPrefixedDBkey() ]
  254. );
  255. }
  256. }
  257. # Logs themselves don't have histories or archived revisions
  258. $this->getOutput()->addSubtitle( $this->getLanguage()->pipeList( $links ) );
  259. }
  260. }
  261. /**
  262. * Get the condition used for fetching log snippets
  263. * @return array
  264. */
  265. protected function getLogQueryCond() {
  266. $conds = [];
  267. // Revision delete logs for these item
  268. $conds['log_type'] = [ 'delete', 'suppress' ];
  269. $conds['log_action'] = $this->getList()->getLogAction();
  270. $conds['ls_field'] = RevisionDeleter::getRelationType( $this->typeName );
  271. $conds['ls_value'] = $this->ids;
  272. return $conds;
  273. }
  274. /**
  275. * Show a deleted file version requested by the visitor.
  276. * @todo Mostly copied from Special:Undelete. Refactor.
  277. * @param string $archiveName
  278. * @throws MWException
  279. * @throws PermissionsError
  280. */
  281. protected function tryShowFile( $archiveName ) {
  282. $repo = RepoGroup::singleton()->getLocalRepo();
  283. $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
  284. $oimage->load();
  285. // Check if user is allowed to see this file
  286. if ( !$oimage->exists() ) {
  287. $this->getOutput()->addWikiMsg( 'revdelete-no-file' );
  288. return;
  289. }
  290. $user = $this->getUser();
  291. if ( !$oimage->userCan( File::DELETED_FILE, $user ) ) {
  292. if ( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
  293. throw new PermissionsError( 'suppressrevision' );
  294. } else {
  295. throw new PermissionsError( 'deletedtext' );
  296. }
  297. }
  298. if ( !$user->matchEditToken( $this->token, $archiveName ) ) {
  299. $lang = $this->getLanguage();
  300. $this->getOutput()->addWikiMsg( 'revdelete-show-file-confirm',
  301. $this->targetObj->getText(),
  302. $lang->userDate( $oimage->getTimestamp(), $user ),
  303. $lang->userTime( $oimage->getTimestamp(), $user ) );
  304. $this->getOutput()->addHTML(
  305. Xml::openElement( 'form', [
  306. 'method' => 'POST',
  307. 'action' => $this->getPageTitle()->getLocalURL( [
  308. 'target' => $this->targetObj->getPrefixedDBkey(),
  309. 'file' => $archiveName,
  310. 'token' => $user->getEditToken( $archiveName ),
  311. ] )
  312. ]
  313. ) .
  314. Xml::submitButton( $this->msg( 'revdelete-show-file-submit' )->text() ) .
  315. '</form>'
  316. );
  317. return;
  318. }
  319. $this->getOutput()->disable();
  320. # We mustn't allow the output to be CDN cached, otherwise
  321. # if an admin previews a deleted image, and it's cached, then
  322. # a user without appropriate permissions can toddle off and
  323. # nab the image, and CDN will serve it
  324. $this->getRequest()->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
  325. $this->getRequest()->response()->header(
  326. 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
  327. );
  328. $this->getRequest()->response()->header( 'Pragma: no-cache' );
  329. $key = $oimage->getStorageKey();
  330. $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
  331. $repo->streamFileWithStatus( $path );
  332. }
  333. /**
  334. * Get the list object for this request
  335. * @return RevDelList
  336. */
  337. protected function getList() {
  338. if ( is_null( $this->revDelList ) ) {
  339. $this->revDelList = RevisionDeleter::createList(
  340. $this->typeName, $this->getContext(), $this->targetObj, $this->ids
  341. );
  342. }
  343. return $this->revDelList;
  344. }
  345. /**
  346. * Show a list of items that we will operate on, and show a form with checkboxes
  347. * which will allow the user to choose new visibility settings.
  348. */
  349. protected function showForm() {
  350. $userAllowed = true;
  351. // Messages: revdelete-selected-text, revdelete-selected-file, logdelete-selected
  352. $out = $this->getOutput();
  353. $out->wrapWikiMsg( "<strong>$1</strong>", [ $this->typeLabels['selected'],
  354. $this->getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ] );
  355. $this->addHelpLink( 'Help:RevisionDelete' );
  356. $out->addHTML( "<ul>" );
  357. $numRevisions = 0;
  358. // Live revisions...
  359. $list = $this->getList();
  360. for ( $list->reset(); $list->current(); $list->next() ) {
  361. $item = $list->current();
  362. if ( !$item->canView() ) {
  363. if ( !$this->submitClicked ) {
  364. throw new PermissionsError( 'suppressrevision' );
  365. }
  366. $userAllowed = false;
  367. }
  368. $numRevisions++;
  369. $out->addHTML( $item->getHTML() );
  370. }
  371. if ( !$numRevisions ) {
  372. throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
  373. }
  374. $out->addHTML( "</ul>" );
  375. // Explanation text
  376. $this->addUsageText();
  377. // Normal sysops can always see what they did, but can't always change it
  378. if ( !$userAllowed ) {
  379. return;
  380. }
  381. // Show form if the user can submit
  382. if ( $this->mIsAllowed ) {
  383. $out->addModules( [ 'mediawiki.special.revisionDelete' ] );
  384. $out->addModuleStyles( [ 'mediawiki.special',
  385. 'mediawiki.interface.helpers.styles' ] );
  386. $form = Xml::openElement( 'form', [ 'method' => 'post',
  387. 'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ),
  388. 'id' => 'mw-revdel-form-revisions' ] ) .
  389. Xml::fieldset( $this->msg( 'revdelete-legend' )->text() ) .
  390. $this->buildCheckBoxes() .
  391. Xml::openElement( 'table' ) .
  392. "<tr>\n" .
  393. '<td class="mw-label">' .
  394. Xml::label( $this->msg( 'revdelete-log' )->text(), 'wpRevDeleteReasonList' ) .
  395. '</td>' .
  396. '<td class="mw-input">' .
  397. Xml::listDropDown( 'wpRevDeleteReasonList',
  398. $this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->text(),
  399. $this->msg( 'revdelete-reasonotherlist' )->inContentLanguage()->text(),
  400. $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ), 'wpReasonDropDown'
  401. ) .
  402. '</td>' .
  403. "</tr><tr>\n" .
  404. '<td class="mw-label">' .
  405. Xml::label( $this->msg( 'revdelete-otherreason' )->text(), 'wpReason' ) .
  406. '</td>' .
  407. '<td class="mw-input">' .
  408. Xml::input( 'wpReason', 60, $this->otherReason, [
  409. 'id' => 'wpReason',
  410. // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
  411. // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
  412. // Unicode codepoints.
  413. // "- 155" is to leave room for the 'wpRevDeleteReasonList' value.
  414. 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT - 155,
  415. ] ) .
  416. '</td>' .
  417. "</tr><tr>\n" .
  418. '<td></td>' .
  419. '<td class="mw-submit">' .
  420. Xml::submitButton( $this->msg( 'revdelete-submit', $numRevisions )->text(),
  421. [ 'name' => 'wpSubmit' ] ) .
  422. '</td>' .
  423. "</tr>\n" .
  424. Xml::closeElement( 'table' ) .
  425. Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) .
  426. Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
  427. Html::hidden( 'type', $this->typeName ) .
  428. Html::hidden( 'ids', implode( ',', $this->ids ) ) .
  429. Xml::closeElement( 'fieldset' ) . "\n" .
  430. Xml::closeElement( 'form' ) . "\n";
  431. // Show link to edit the dropdown reasons
  432. if ( MediaWikiServices::getInstance()
  433. ->getPermissionManager()
  434. ->userHasRight( $this->getUser(), 'editinterface' )
  435. ) {
  436. $link = $this->getLinkRenderer()->makeKnownLink(
  437. $this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->getTitle(),
  438. $this->msg( 'revdelete-edit-reasonlist' )->text(),
  439. [],
  440. [ 'action' => 'edit' ]
  441. );
  442. $form .= Xml::tags( 'p', [ 'class' => 'mw-revdel-editreasons' ], $link ) . "\n";
  443. }
  444. } else {
  445. $form = '';
  446. }
  447. $out->addHTML( $form );
  448. }
  449. /**
  450. * Show some introductory text
  451. * @todo FIXME: Wikimedia-specific policy text
  452. */
  453. protected function addUsageText() {
  454. // Messages: revdelete-text-text, revdelete-text-file, logdelete-text
  455. $this->getOutput()->wrapWikiMsg(
  456. "<strong>$1</strong>\n$2", $this->typeLabels['text'],
  457. 'revdelete-text-others'
  458. );
  459. if ( MediaWikiServices::getInstance()
  460. ->getPermissionManager()
  461. ->userHasRight( $this->getUser(), 'suppressrevision' )
  462. ) {
  463. $this->getOutput()->addWikiMsg( 'revdelete-suppress-text' );
  464. }
  465. if ( $this->mIsAllowed ) {
  466. $this->getOutput()->addWikiMsg( 'revdelete-confirm' );
  467. }
  468. }
  469. /**
  470. * @return string HTML
  471. */
  472. protected function buildCheckBoxes() {
  473. $html = '<table>';
  474. // If there is just one item, use checkboxes
  475. $list = $this->getList();
  476. if ( $list->length() == 1 ) {
  477. $list->reset();
  478. $bitfield = $list->current()->getBits(); // existing field
  479. if ( $this->submitClicked ) {
  480. $bitfield = RevisionDeleter::extractBitfield( $this->extractBitParams(), $bitfield );
  481. }
  482. foreach ( $this->checks as $item ) {
  483. // Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name,
  484. // revdelete-hide-comment, revdelete-hide-user, revdelete-hide-restricted
  485. list( $message, $name, $field ) = $item;
  486. $innerHTML = Xml::checkLabel(
  487. $this->msg( $message )->text(),
  488. $name,
  489. $name,
  490. $bitfield & $field
  491. );
  492. if ( $field == RevisionRecord::DELETED_RESTRICTED ) {
  493. $innerHTML = "<b>$innerHTML</b>";
  494. }
  495. $line = Xml::tags( 'td', [ 'class' => 'mw-input' ], $innerHTML );
  496. $html .= "<tr>$line</tr>\n";
  497. }
  498. } else {
  499. // Otherwise, use tri-state radios
  500. $html .= '<tr>';
  501. $html .= '<th class="mw-revdel-checkbox">'
  502. . $this->msg( 'revdelete-radio-same' )->escaped() . '</th>';
  503. $html .= '<th class="mw-revdel-checkbox">'
  504. . $this->msg( 'revdelete-radio-unset' )->escaped() . '</th>';
  505. $html .= '<th class="mw-revdel-checkbox">'
  506. . $this->msg( 'revdelete-radio-set' )->escaped() . '</th>';
  507. $html .= "<th></th></tr>\n";
  508. foreach ( $this->checks as $item ) {
  509. // Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name,
  510. // revdelete-hide-comment, revdelete-hide-user, revdelete-hide-restricted
  511. list( $message, $name, $field ) = $item;
  512. // If there are several items, use third state by default...
  513. if ( $this->submitClicked ) {
  514. $selected = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
  515. } else {
  516. $selected = -1; // use existing field
  517. }
  518. $line = '<td class="mw-revdel-checkbox">' . Xml::radio( $name, -1, $selected == -1 ) . '</td>';
  519. $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 0, $selected == 0 ) . '</td>';
  520. $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 1, $selected == 1 ) . '</td>';
  521. $label = $this->msg( $message )->escaped();
  522. if ( $field == RevisionRecord::DELETED_RESTRICTED ) {
  523. $label = "<b>$label</b>";
  524. }
  525. $line .= "<td>$label</td>";
  526. $html .= "<tr>$line</tr>\n";
  527. }
  528. }
  529. $html .= '</table>';
  530. return $html;
  531. }
  532. /**
  533. * UI entry point for form submission.
  534. * @throws PermissionsError
  535. * @return bool
  536. */
  537. protected function submit() {
  538. # Check edit token on submission
  539. $token = $this->getRequest()->getVal( 'wpEditToken' );
  540. if ( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
  541. $this->getOutput()->addWikiMsg( 'sessionfailure' );
  542. return false;
  543. }
  544. $bitParams = $this->extractBitParams();
  545. // from dropdown
  546. $listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' );
  547. $comment = $listReason;
  548. if ( $comment === 'other' ) {
  549. $comment = $this->otherReason;
  550. } elseif ( $this->otherReason !== '' ) {
  551. // Entry from drop down menu + additional comment
  552. $comment .= $this->msg( 'colon-separator' )->inContentLanguage()->text()
  553. . $this->otherReason;
  554. }
  555. # Can the user set this field?
  556. if ( $bitParams[RevisionRecord::DELETED_RESTRICTED] == 1
  557. && !MediaWikiServices::getInstance()
  558. ->getPermissionManager()
  559. ->userHasRight( $this->getUser(), 'suppressrevision' )
  560. ) {
  561. throw new PermissionsError( 'suppressrevision' );
  562. }
  563. # If the save went through, go to success message...
  564. $status = $this->save( $bitParams, $comment );
  565. if ( $status->isGood() ) {
  566. $this->success();
  567. return true;
  568. } else {
  569. # ...otherwise, bounce back to form...
  570. $this->failure( $status );
  571. }
  572. return false;
  573. }
  574. /**
  575. * Report that the submit operation succeeded
  576. */
  577. protected function success() {
  578. // Messages: revdelete-success, logdelete-success
  579. $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
  580. $this->getOutput()->wrapWikiMsg(
  581. "<div class=\"successbox\">\n$1\n</div>",
  582. $this->typeLabels['success']
  583. );
  584. $this->wasSaved = true;
  585. $this->revDelList->reloadFromMaster();
  586. $this->showForm();
  587. }
  588. /**
  589. * Report that the submit operation failed
  590. * @param Status $status
  591. */
  592. protected function failure( $status ) {
  593. // Messages: revdelete-failure, logdelete-failure
  594. $this->getOutput()->setPageTitle( $this->msg( 'actionfailed' ) );
  595. $this->getOutput()->wrapWikiTextAsInterface(
  596. 'errorbox',
  597. $status->getWikiText( $this->typeLabels['failure'], false, $this->getLanguage() )
  598. );
  599. $this->showForm();
  600. }
  601. /**
  602. * Put together an array that contains -1, 0, or the *_deleted const for each bit
  603. *
  604. * @return array
  605. */
  606. protected function extractBitParams() {
  607. $bitfield = [];
  608. foreach ( $this->checks as $item ) {
  609. list( /* message */, $name, $field ) = $item;
  610. $val = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
  611. if ( $val < -1 || $val > 1 ) {
  612. $val = -1; // -1 for existing value
  613. }
  614. $bitfield[$field] = $val;
  615. }
  616. if ( !isset( $bitfield[RevisionRecord::DELETED_RESTRICTED] ) ) {
  617. $bitfield[RevisionRecord::DELETED_RESTRICTED] = 0;
  618. }
  619. return $bitfield;
  620. }
  621. /**
  622. * Do the write operations. Simple wrapper for RevDel*List::setVisibility().
  623. * @param array $bitPars ExtractBitParams() bitfield array
  624. * @param string $reason
  625. * @return Status
  626. */
  627. protected function save( array $bitPars, $reason ) {
  628. return $this->getList()->setVisibility(
  629. [ 'value' => $bitPars, 'comment' => $reason ]
  630. );
  631. }
  632. protected function getGroupName() {
  633. return 'pagetools';
  634. }
  635. }