Index: trunk/phase3/includes/actions/DeleteAction.php |
— | — | @@ -1,484 +0,0 @@ |
2 | | -<?php |
3 | | -/** |
4 | | - * Performs the delete action on a page |
5 | | - * |
6 | | - * This program is free software; you can redistribute it and/or modify |
7 | | - * it under the terms of the GNU General Public License as published by |
8 | | - * the Free Software Foundation; either version 2 of the License, or |
9 | | - * (at your option) any later version. |
10 | | - * |
11 | | - * This program is distributed in the hope that it will be useful, |
12 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | - * GNU General Public License for more details. |
15 | | - * |
16 | | - * You should have received a copy of the GNU General Public License |
17 | | - * along with this program; if not, write to the Free Software |
18 | | - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA |
19 | | - * |
20 | | - * @file |
21 | | - * @ingroup Actions |
22 | | - */ |
23 | | - |
24 | | -class DeleteAction extends Action { |
25 | | - |
26 | | - public function getName(){ |
27 | | - return 'delete'; |
28 | | - } |
29 | | - |
30 | | - public function getRestriction(){ |
31 | | - return 'delete'; |
32 | | - } |
33 | | - |
34 | | - protected function getDescription(){ |
35 | | - return wfMsg( 'delete-confirm', $this->getTitle()->getPrefixedText() ); |
36 | | - } |
37 | | - |
38 | | - /** |
39 | | - * Check that the deletion can be executed. In addition to checking the user permissions, |
40 | | - * check that the page is not too big and has not already been deleted. |
41 | | - * @throws ErrorPageError |
42 | | - * @see Action::checkCanExecute |
43 | | - * |
44 | | - * @param $user User |
45 | | - */ |
46 | | - protected function checkCanExecute( User $user ){ |
47 | | - |
48 | | - // Check that the article hasn't already been deleted |
49 | | - $dbw = wfGetDB( DB_MASTER ); |
50 | | - $conds = $this->getTitle()->pageCond(); |
51 | | - $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ ); |
52 | | - if ( $latest === false ) { |
53 | | - // Get the deletion log |
54 | | - $log = ''; |
55 | | - LogEventsList::showLogExtract( |
56 | | - $log, |
57 | | - 'delete', |
58 | | - $this->getTitle()->getPrefixedText() |
59 | | - ); |
60 | | - |
61 | | - $msg = new Message( 'cannotdelete' ); |
62 | | - $msg->params( $this->getTitle()->getPrefixedText() ); // This parameter is parsed |
63 | | - $msg->rawParams( $log ); // This is not |
64 | | - |
65 | | - throw new ErrorPageError( 'internalerror', $msg ); |
66 | | - } |
67 | | - |
68 | | - // Limit deletions of big pages |
69 | | - $bigHistory = $this->isBigDeletion(); |
70 | | - if ( $bigHistory && !$user->isAllowed( 'bigdelete' ) ) { |
71 | | - global $wgDeleteRevisionsLimit; |
72 | | - throw new ErrorPageError( |
73 | | - 'internalerror', |
74 | | - 'delete-toobig', |
75 | | - $this->getContext()->lang->formatNum( $wgDeleteRevisionsLimit ) |
76 | | - ); |
77 | | - } |
78 | | - |
79 | | - return parent::checkCanExecute( $user ); |
80 | | - } |
81 | | - |
82 | | - protected function getFormFields(){ |
83 | | - // TODO: add more useful things here? |
84 | | - $infoText = Html::rawElement( |
85 | | - 'strong', |
86 | | - array(), |
87 | | - Linker::link( $this->getTitle(), $this->getTitle()->getText() ) |
88 | | - ); |
89 | | - |
90 | | - $arr = array( |
91 | | - 'Page' => array( |
92 | | - 'type' => 'info', |
93 | | - 'raw' => true, |
94 | | - 'default' => $infoText, |
95 | | - ), |
96 | | - 'Reason' => array( |
97 | | - 'type' => 'selectandother', |
98 | | - 'label-message' => 'deletecomment', |
99 | | - 'options-message' => 'deletereason-dropdown', |
100 | | - 'size' => '60', |
101 | | - 'maxlength' => '255', |
102 | | - 'default' => self::getAutoReason( $this->page), |
103 | | - ), |
104 | | - ); |
105 | | - |
106 | | - if( $this->getUser()->isLoggedIn() ){ |
107 | | - $arr['Watch'] = array( |
108 | | - 'type' => 'check', |
109 | | - 'label-message' => 'watchthis', |
110 | | - 'default' => $this->getUser()->getBoolOption( 'watchdeletion' ) || $this->getTitle()->userIsWatching() |
111 | | - ); |
112 | | - } |
113 | | - |
114 | | - if( $this->getUser()->isAllowed( 'suppressrevision' ) ){ |
115 | | - $arr['Suppress'] = array( |
116 | | - 'type' => 'check', |
117 | | - 'label-message' => 'revdelete-suppress', |
118 | | - 'default' => false, |
119 | | - ); |
120 | | - } |
121 | | - |
122 | | - return $arr; |
123 | | - } |
124 | | - |
125 | | - /** |
126 | | - * Text to go at the top of the form, before the opening fieldset |
127 | | - * @see Action::preText() |
128 | | - * @return String |
129 | | - */ |
130 | | - protected function preText() { |
131 | | - |
132 | | - // If the page has a history, insert a warning |
133 | | - if ( $this->page->estimateRevisionCount() ) { |
134 | | - global $wgLang; |
135 | | - |
136 | | - $link = Linker::link( |
137 | | - $this->getTitle(), |
138 | | - wfMsgHtml( 'history' ), |
139 | | - array( 'rel' => 'archives' ), |
140 | | - array( 'action' => 'history' ) |
141 | | - ); |
142 | | - |
143 | | - return Html::rawElement( |
144 | | - 'strong', |
145 | | - array( 'class' => 'mw-delete-warning-revisions' ), |
146 | | - wfMessage( |
147 | | - 'historywarning', |
148 | | - $wgLang->formatNum( $this->page->estimateRevisionCount() ) |
149 | | - )->rawParams( $link )->parse() |
150 | | - ); |
151 | | - } |
152 | | - } |
153 | | - |
154 | | - /** |
155 | | - * Text to go at the bottom of the form, below the closing fieldset |
156 | | - * @see Action::postText() |
157 | | - * @return string |
158 | | - */ |
159 | | - protected function postText(){ |
160 | | - $s = ''; |
161 | | - LogEventsList::showLogExtract( |
162 | | - $s, |
163 | | - 'delete', |
164 | | - $this->getTitle()->getPrefixedText() |
165 | | - ); |
166 | | - return Html::element( 'h2', array(), LogPage::logName( 'delete' ) ) . $s; |
167 | | - } |
168 | | - |
169 | | - protected function alterForm( HTMLForm &$form ){ |
170 | | - $form->setWrapperLegend( wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ); |
171 | | - |
172 | | - if ( $this->getUser()->isAllowed( 'editinterface' ) ) { |
173 | | - $link = Linker::link( |
174 | | - Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' ), |
175 | | - wfMsgHtml( 'delete-edit-reasonlist' ), |
176 | | - array(), |
177 | | - array( 'action' => 'edit' ) |
178 | | - ); |
179 | | - $form->addHeaderText( '<p class="mw-delete-editreasons">' . $link . '</p>' ); |
180 | | - } |
181 | | - } |
182 | | - |
183 | | - /** |
184 | | - * Function called on form submission. Privilege checks and validation have already been |
185 | | - * completed by this point; we just need to jump out to the heavy-lifting function, |
186 | | - * which is implemented as a static method so it can be called from other places |
187 | | - * TODO: make those other places call $action->execute() properly |
188 | | - * @see Action::onSubmit() |
189 | | - * @param $data Array |
190 | | - * @return Array|Bool |
191 | | - */ |
192 | | - public function onSubmit( $data ){ |
193 | | - $status = self::doDeleteArticle( $this->page, $this->getContext(), $data, true ); |
194 | | - return $status; |
195 | | - } |
196 | | - |
197 | | - public function onSuccess(){ |
198 | | - // Watch or unwatch, if requested |
199 | | - if( $this->getRequest()->getCheck( 'wpWatch' ) && $this->getUser()->isLoggedIn() ) { |
200 | | - WatchAction::doWatch( $this->getTitle(), $this->getUser() ); |
201 | | - } elseif ( $this->getTitle()->userIsWatching() ) { |
202 | | - WatchAction::doUnwatch( $this->getTitle(), $this->getUser() ); |
203 | | - } |
204 | | - |
205 | | - $this->getOutput()->setPagetitle( wfMsg( 'actioncomplete' ) ); |
206 | | - $this->getOutput()->addWikiMsg( |
207 | | - 'deletedtext', |
208 | | - wfEscapeWikiText( $this->getTitle()->getPrefixedText() ), |
209 | | - '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]' |
210 | | - ); |
211 | | - $this->getOutput()->returnToMain( false ); |
212 | | - } |
213 | | - |
214 | | - /** |
215 | | - * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions |
216 | | - */ |
217 | | - protected function isBigDeletion() { |
218 | | - global $wgDeleteRevisionsLimit; |
219 | | - return $wgDeleteRevisionsLimit && $this->page->estimateRevisionCount() > $wgDeleteRevisionsLimit; |
220 | | - } |
221 | | - |
222 | | - /** |
223 | | - * Back-end article deletion |
224 | | - * Deletes the article with database consistency, writes logs, purges caches |
225 | | - * |
226 | | - * @param $commit boolean defaults to true, triggers transaction end |
227 | | - * @return Bool|Array true if successful, error array on failure |
228 | | - */ |
229 | | - public static function doDeleteArticle( Article $page, RequestContext $context, array $data, $commit = true ) { |
230 | | - global $wgDeferredUpdateList, $wgUseTrackbacks; |
231 | | - |
232 | | - wfDebug( __METHOD__ . "\n" ); |
233 | | - |
234 | | - // The normal syntax from HTMLSelectAndOtherField is for the reason to be in the form |
235 | | - // 'Reason' => array( <full reason>, <dropdown>, <custom> ), but it's reasonable for other |
236 | | - // functions to just pass 'Reason' => <reason> |
237 | | - $data['Reason'] = (array)$data['Reason']; |
238 | | - |
239 | | - $error = null; |
240 | | - if ( !wfRunHooks( 'ArticleDelete', array( &$page, $context->getUser(), &$data['Reason'][0], &$error ) ) ) { |
241 | | - return $error; |
242 | | - } |
243 | | - |
244 | | - $title = $page->getTitle(); |
245 | | - $id = $page->getID( Title::GAID_FOR_UPDATE ); |
246 | | - |
247 | | - if ( $title->getDBkey() === '' || $id == 0 ) { |
248 | | - return false; |
249 | | - } |
250 | | - |
251 | | - $updates = new SiteStatsUpdate( 0, 1, - (int)$page->isCountable(), -1 ); |
252 | | - array_push( $wgDeferredUpdateList, $updates ); |
253 | | - |
254 | | - // Bitfields to further suppress the content |
255 | | - if ( isset( $data['Suppress'] ) && $data['Suppress'] ) { |
256 | | - $bitfield = 0; |
257 | | - // This should be 15... |
258 | | - $bitfield |= Revision::DELETED_TEXT; |
259 | | - $bitfield |= Revision::DELETED_COMMENT; |
260 | | - $bitfield |= Revision::DELETED_USER; |
261 | | - $bitfield |= Revision::DELETED_RESTRICTED; |
262 | | - |
263 | | - $logtype = 'suppress'; |
264 | | - } else { |
265 | | - // Otherwise, leave it unchanged |
266 | | - $bitfield = 'rev_deleted'; |
267 | | - $logtype = 'delete'; |
268 | | - } |
269 | | - |
270 | | - $dbw = wfGetDB( DB_MASTER ); |
271 | | - $dbw->begin(); |
272 | | - // For now, shunt the revision data into the archive table. |
273 | | - // Text is *not* removed from the text table; bulk storage |
274 | | - // is left intact to avoid breaking block-compression or |
275 | | - // immutable storage schemes. |
276 | | - // |
277 | | - // For backwards compatibility, note that some older archive |
278 | | - // table entries will have ar_text and ar_flags fields still. |
279 | | - // |
280 | | - // In the future, we may keep revisions and mark them with |
281 | | - // the rev_deleted field, which is reserved for this purpose. |
282 | | - $dbw->insertSelect( |
283 | | - 'archive', |
284 | | - array( 'page', 'revision' ), |
285 | | - array( |
286 | | - 'ar_namespace' => 'page_namespace', |
287 | | - 'ar_title' => 'page_title', |
288 | | - 'ar_comment' => 'rev_comment', |
289 | | - 'ar_user' => 'rev_user', |
290 | | - 'ar_user_text' => 'rev_user_text', |
291 | | - 'ar_timestamp' => 'rev_timestamp', |
292 | | - 'ar_minor_edit' => 'rev_minor_edit', |
293 | | - 'ar_rev_id' => 'rev_id', |
294 | | - 'ar_text_id' => 'rev_text_id', |
295 | | - 'ar_text' => "''", // Be explicit to appease |
296 | | - 'ar_flags' => "''", // MySQL's "strict mode"... |
297 | | - 'ar_len' => 'rev_len', |
298 | | - 'ar_page_id' => 'page_id', |
299 | | - 'ar_deleted' => $bitfield |
300 | | - ), |
301 | | - array( |
302 | | - 'page_id' => $id, |
303 | | - 'page_id = rev_page' |
304 | | - ), |
305 | | - __METHOD__ |
306 | | - ); |
307 | | - |
308 | | - // Delete restrictions for it |
309 | | - $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ ); |
310 | | - |
311 | | - // Now that it's safely backed up, delete it |
312 | | - $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ ); |
313 | | - |
314 | | - // getArticleId() uses slave, could be laggy |
315 | | - if ( $dbw->affectedRows() == 0 ) { |
316 | | - $dbw->rollback(); |
317 | | - return false; |
318 | | - } |
319 | | - |
320 | | - // Fix category table counts |
321 | | - $res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ ); |
322 | | - $cats = array(); |
323 | | - foreach ( $res as $row ) { |
324 | | - $cats[] = $row->cl_to; |
325 | | - } |
326 | | - $page->updateCategoryCounts( array(), $cats ); |
327 | | - |
328 | | - // If using cascading deletes, we can skip some explicit deletes |
329 | | - if ( !$dbw->cascadingDeletes() ) { |
330 | | - $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ ); |
331 | | - |
332 | | - if ( $wgUseTrackbacks ){ |
333 | | - $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ ); |
334 | | - } |
335 | | - |
336 | | - // Delete outgoing links |
337 | | - $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) ); |
338 | | - $dbw->delete( 'imagelinks', array( 'il_from' => $id ) ); |
339 | | - $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) ); |
340 | | - $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) ); |
341 | | - $dbw->delete( 'externallinks', array( 'el_from' => $id ) ); |
342 | | - $dbw->delete( 'langlinks', array( 'll_from' => $id ) ); |
343 | | - $dbw->delete( 'redirect', array( 'rd_from' => $id ) ); |
344 | | - } |
345 | | - |
346 | | - // If using cleanup triggers, we can skip some manual deletes |
347 | | - if ( !$dbw->cleanupTriggers() ) { |
348 | | - // Clean up recentchanges entries... |
349 | | - $dbw->delete( 'recentchanges', |
350 | | - array( |
351 | | - 'rc_type != ' . RC_LOG, |
352 | | - 'rc_namespace' => $title->getNamespace(), |
353 | | - 'rc_title' => $title->getDBkey() ), |
354 | | - __METHOD__ |
355 | | - ); |
356 | | - $dbw->delete( |
357 | | - 'recentchanges', |
358 | | - array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ), |
359 | | - __METHOD__ |
360 | | - ); |
361 | | - } |
362 | | - |
363 | | - // Clear caches |
364 | | - // TODO: should this be in here or left in Article? |
365 | | - Article::onArticleDelete( $title ); |
366 | | - |
367 | | - // Clear the cached article id so the interface doesn't act like we exist |
368 | | - $title->resetArticleID( 0 ); |
369 | | - |
370 | | - // Log the deletion, if the page was suppressed, log it at Oversight instead |
371 | | - $log = new LogPage( $logtype ); |
372 | | - |
373 | | - // Make sure logging got through |
374 | | - $log->addEntry( 'delete', $title, $data['Reason'][0], array() ); |
375 | | - |
376 | | - if ( $commit ) { |
377 | | - $dbw->commit(); |
378 | | - } |
379 | | - |
380 | | - wfRunHooks( 'ArticleDeleteComplete', array( &$page, $context->getUser(), $data['Reason'][0], $id ) ); |
381 | | - return true; |
382 | | - } |
383 | | - |
384 | | - /** |
385 | | - * Auto-generates a deletion reason. Also sets $this->hasHistory if the page has old |
386 | | - * revisions. |
387 | | - * |
388 | | - * @return mixed String containing default reason or empty string, or boolean false |
389 | | - * if no revision was found |
390 | | - */ |
391 | | - public static function getAutoReason( Article $page ) { |
392 | | - global $wgContLang; |
393 | | - |
394 | | - $dbw = wfGetDB( DB_MASTER ); |
395 | | - // Get the last revision |
396 | | - $rev = Revision::newFromTitle( $page->getTitle() ); |
397 | | - |
398 | | - if ( is_null( $rev ) ) { |
399 | | - return false; |
400 | | - } |
401 | | - |
402 | | - // Get the article's contents |
403 | | - $contents = $rev->getText(); |
404 | | - $blank = false; |
405 | | - |
406 | | - // If the page is blank, use the text from the previous revision, |
407 | | - // which can only be blank if there's a move/import/protect dummy revision involved |
408 | | - if ( $contents == '' ) { |
409 | | - $prev = $rev->getPrevious(); |
410 | | - |
411 | | - if ( $prev ) { |
412 | | - $contents = $prev->getText(); |
413 | | - $blank = true; |
414 | | - } |
415 | | - } |
416 | | - |
417 | | - // Find out if there was only one contributor |
418 | | - // Only scan the last 20 revisions |
419 | | - $res = $dbw->select( 'revision', 'rev_user_text', |
420 | | - array( |
421 | | - 'rev_page' => $page->getID(), |
422 | | - $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' |
423 | | - ), |
424 | | - __METHOD__, |
425 | | - array( 'LIMIT' => 20 ) |
426 | | - ); |
427 | | - |
428 | | - if ( $res === false ) { |
429 | | - // This page has no revisions, which is very weird |
430 | | - return false; |
431 | | - } |
432 | | - |
433 | | - $row = $dbw->fetchObject( $res ); |
434 | | - |
435 | | - if ( $row ) { // $row is false if the only contributor is hidden |
436 | | - $onlyAuthor = $row->rev_user_text; |
437 | | - // Try to find a second contributor |
438 | | - foreach ( $res as $row ) { |
439 | | - if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999 |
440 | | - $onlyAuthor = false; |
441 | | - break; |
442 | | - } |
443 | | - } |
444 | | - } else { |
445 | | - $onlyAuthor = false; |
446 | | - } |
447 | | - |
448 | | - // Generate the summary with a '$1' placeholder |
449 | | - if ( $blank ) { |
450 | | - // The current revision is blank and the one before is also |
451 | | - // blank. It's just not our lucky day |
452 | | - $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text(); |
453 | | - } else { |
454 | | - if ( $onlyAuthor ) { |
455 | | - $reason = wfMessage( 'excontentauthor', '$1', $onlyAuthor )->inContentLanguage()->text(); |
456 | | - } else { |
457 | | - $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text(); |
458 | | - } |
459 | | - } |
460 | | - |
461 | | - if ( $reason == '-' ) { |
462 | | - // Allow these UI messages to be blanked out cleanly |
463 | | - return ''; |
464 | | - } |
465 | | - |
466 | | - // Replace newlines with spaces to prevent uglyness |
467 | | - $contents = preg_replace( "/[\n\r]/", ' ', $contents ); |
468 | | - // Calculate the maximum number of chars to get |
469 | | - // Max content length = max comment length - length of the comment (excl. $1) |
470 | | - $maxLength = 255 - ( strlen( $reason ) - 2 ); |
471 | | - $contents = $wgContLang->truncate( $contents, $maxLength ); |
472 | | - // Remove possible unfinished links |
473 | | - $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents ); |
474 | | - // Now replace the '$1' placeholder |
475 | | - $reason = str_replace( '$1', $contents, $reason ); |
476 | | - |
477 | | - return $reason; |
478 | | - } |
479 | | - |
480 | | - public function show() { |
481 | | - } |
482 | | - |
483 | | - public function execute(){ |
484 | | - } |
485 | | -} |