MediaWiki REL1_34
SpecialNuke.php
Go to the documentation of this file.
1<?php
2
3class SpecialNuke extends SpecialPage {
4
5 public function __construct() {
6 parent::__construct( 'Nuke', 'nuke' );
7 }
8
9 public function doesWrites() {
10 return true;
11 }
12
16 public function execute( $par ) {
17 $this->setHeaders();
18 $this->checkPermissions();
19 $this->checkReadOnly();
20 $this->outputHeader();
21 $this->addHelpLink( 'Extension:Nuke' );
22
23 $currentUser = $this->getUser();
24 if ( $currentUser->isBlocked() ) {
25 $block = $currentUser->getBlock();
26 throw new UserBlockedError( $block );
27 }
28
29 $req = $this->getRequest();
30 $target = trim( $req->getText( 'target', $par ) );
31
32 // Normalise name
33 if ( $target !== '' ) {
34 $user = User::newFromName( $target );
35 if ( $user ) {
36 $target = $user->getName();
37 }
38 }
39
40 $msg = $target === '' ?
41 $this->msg( 'nuke-multiplepeople' )->inContentLanguage()->text() :
42 $this->msg( 'nuke-defaultreason', $target )->
43 inContentLanguage()->text();
44 $reason = $req->getText( 'wpReason', $msg );
45
46 $limit = $req->getInt( 'limit', 500 );
47 $namespace = $req->getVal( 'namespace' );
48 $namespace = ctype_digit( $namespace ) ? (int)$namespace : null;
49
50 if ( $req->wasPosted()
51 && $currentUser->matchEditToken( $req->getVal( 'wpEditToken' ) )
52 ) {
53 if ( $req->getVal( 'action' ) === 'delete' ) {
54 $pages = $req->getArray( 'pages' );
55
56 if ( $pages ) {
57 $this->doDelete( $pages, $reason );
58
59 return;
60 }
61 } elseif ( $req->getVal( 'action' ) === 'submit' ) {
62 $this->listForm( $target, $reason, $limit, $namespace );
63 } else {
64 $this->promptForm();
65 }
66 } elseif ( $target === '' ) {
67 $this->promptForm();
68 } else {
69 $this->listForm( $target, $reason, $limit, $namespace );
70 }
71 }
72
78 protected function promptForm( $userName = '' ) {
79 $out = $this->getOutput();
80
81 $out->addWikiMsg( 'nuke-tools' );
82
83 $formDescriptor = [
84 'nuke-target' => [
85 'id' => 'nuke-target',
86 'default' => $userName,
87 'label' => $this->msg( 'nuke-userorip' )->text(),
88 'type' => 'user',
89 'name' => 'target',
90 'autofocus' => true
91 ],
92 'nuke-pattern' => [
93 'id' => 'nuke-pattern',
94 'label' => $this->msg( 'nuke-pattern' )->text(),
95 'maxLength' => 40,
96 'type' => 'text',
97 'name' => 'pattern'
98 ],
99 'namespace' => [
100 'id' => 'nuke-namespace',
101 'type' => 'namespaceselect',
102 'label' => $this->msg( 'nuke-namespace' )->text(),
103 'all' => 'all',
104 'name' => 'namespace'
105 ],
106 'limit' => [
107 'id' => 'nuke-limit',
108 'maxLength' => 7,
109 'default' => 500,
110 'label' => $this->msg( 'nuke-maxpages' )->text(),
111 'type' => 'int',
112 'name' => 'limit'
113 ]
114 ];
115
116 HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
117 ->setName( 'massdelete' )
118 ->setFormIdentifier( 'massdelete' )
119 ->setWrapperLegendMsg( 'nuke' )
120 ->setSubmitTextMsg( 'nuke-submit-user' )
121 ->setSubmitName( 'nuke-submit-user' )
122 ->setAction( $this->getPageTitle()->getLocalURL( 'action=submit' ) )
123 ->setMethod( 'post' )
124 ->addHiddenField( 'wpEditToken', $this->getUser()->getEditToken() )
125 ->prepareForm()
126 ->displayForm( false );
127 }
128
137 protected function listForm( $username, $reason, $limit, $namespace = null ) {
138 $out = $this->getOutput();
139
140 $pages = $this->getNewPages( $username, $limit, $namespace );
141
142 if ( count( $pages ) === 0 ) {
143 if ( $username === '' ) {
144 $out->addWikiMsg( 'nuke-nopages-global' );
145 } else {
146 $out->addWikiMsg( 'nuke-nopages', $username );
147 }
148
149 $this->promptForm( $username );
150
151 return;
152 }
153
154 $out->addModules( 'ext.nuke.confirm' );
155
156 if ( $username === '' ) {
157 $out->addWikiMsg( 'nuke-list-multiple' );
158 } else {
159 $out->addWikiMsg( 'nuke-list', $username );
160 }
161
162 $nuke = $this->getPageTitle();
163
164 $out->addHTML(
165 Xml::openElement( 'form', [
166 'action' => $nuke->getLocalURL( 'action=delete' ),
167 'method' => 'post',
168 'name' => 'nukelist' ]
169 ) .
170 Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) .
171 Xml::tags( 'p',
172 null,
173 Xml::inputLabel(
174 $this->msg( 'deletecomment' )->text(), 'wpReason', 'wpReason', 70, $reason
175 )
176 )
177 );
178
179 // Select: All, None, Invert
180 $listToggle = new ListToggle( $this->getOutput() );
181 $selectLinks = $listToggle->getHTML();
182
183 $out->addHTML(
184 $selectLinks .
185 '<ul>'
186 );
187
188 $wordSeparator = $this->msg( 'word-separator' )->escaped();
189 $commaSeparator = $this->msg( 'comma-separator' )->escaped();
190
192 foreach ( $pages as $info ) {
196 list( $title, $userName ) = $info;
197
198 $image = $title->inNamespace( NS_FILE ) ? wfLocalFile( $title ) : false;
199 $thumb = $image && $image->exists() ?
200 $image->transform( [ 'width' => 120, 'height' => 120 ], 0 ) :
201 false;
202
203 $userNameText = $userName ?
204 $this->msg( 'nuke-editby', $userName )->parse() . $commaSeparator :
205 '';
206 $changesLink = $linkRenderer->makeKnownLink(
207 $title,
208 $this->msg( 'nuke-viewchanges' )->text(),
209 [],
210 [ 'action' => 'history' ]
211 );
212 $out->addHTML( '<li>' .
213 Xml::check(
214 'pages[]',
215 true,
216 [ 'value' => $title->getPrefixedDBkey() ]
217 ) . '&#160;' .
218 ( $thumb ? $thumb->toHtml( [ 'desc-link' => true ] ) : '' ) .
219 $linkRenderer->makeKnownLink( $title ) . $wordSeparator .
220 $this->msg( 'parentheses' )->rawParams( $userNameText . $changesLink )->escaped() .
221 "</li>\n" );
222 }
223
224 $out->addHTML(
225 "</ul>\n" .
226 Xml::submitButton( $this->msg( 'nuke-submit-delete' )->text() ) .
227 '</form>'
228 );
229 }
230
240 protected function getNewPages( $username, $limit, $namespace = null ) {
242
243 $what = [
244 'rc_namespace',
245 'rc_title',
246 'rc_timestamp',
247 ];
248
249 $where = [ "(rc_new = 1) OR (rc_log_type = 'upload' AND rc_log_action = 'upload')" ];
250
251 if ( class_exists( ActorMigration::class ) ) {
252 if ( $username === '' ) {
253 $actorQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
254 $what['rc_user_text'] = $actorQuery['fields']['rc_user_text'];
255 } else {
256 $actorQuery = ActorMigration::newMigration()
257 ->getWhere( $dbr, 'rc_user', User::newFromName( $username, false ) );
258 $where[] = $actorQuery['conds'];
259 }
260 } else {
261 $actorQuery = [ 'tables' => [], 'joins' => [] ];
262 if ( $username === '' ) {
263 $what[] = 'rc_user_text';
264 } else {
265 $where['rc_user_text'] = $username;
266 }
267 }
268
269 if ( $namespace !== null ) {
270 $where['rc_namespace'] = $namespace;
271 }
272
273 $pattern = $this->getRequest()->getText( 'pattern' );
274 if ( !is_null( $pattern ) && trim( $pattern ) !== '' ) {
275 // $pattern is a SQL pattern supporting wildcards, so buildLike
276 // will not work.
277 $where[] = 'rc_title LIKE ' . $dbr->addQuotes( $pattern );
278 }
279 $group = implode( ', ', $what );
280
281 $result = $dbr->select(
282 [ 'recentchanges' ] + $actorQuery['tables'],
283 $what,
284 $where,
285 __METHOD__,
286 [
287 'ORDER BY' => 'rc_timestamp DESC',
288 'GROUP BY' => $group,
289 'LIMIT' => $limit
290 ],
291 $actorQuery['joins']
292 );
293
294 $pages = [];
295
296 foreach ( $result as $row ) {
297 $pages[] = [
298 Title::makeTitle( $row->rc_namespace, $row->rc_title ),
299 $username === '' ? $row->rc_user_text : false
300 ];
301 }
302
303 // Allows other extensions to provide pages to be nuked that don't use
304 // the recentchanges table the way mediawiki-core does
305 Hooks::run( 'NukeGetNewPages', [ $username, $pattern, $namespace, $limit, &$pages ] );
306
307 // Re-enforcing the limit *after* the hook because other extensions
308 // may add and/or remove pages. We need to make sure we don't end up
309 // with more pages than $limit.
310 if ( count( $pages ) > $limit ) {
311 $pages = array_slice( $pages, 0, $limit );
312 }
313
314 return $pages;
315 }
316
324 protected function doDelete( array $pages, $reason ) {
325 $res = [];
326
327 foreach ( $pages as $page ) {
328 $title = Title::newFromText( $page );
329
330 $deletionResult = false;
331 if ( !Hooks::run( 'NukeDeletePage', [ $title, $reason, &$deletionResult ] ) ) {
332 if ( $deletionResult ) {
333 $res[] = $this->msg( 'nuke-deleted', $title->getPrefixedText() )->parse();
334 } else {
335 $res[] = $this->msg( 'nuke-not-deleted', $title->getPrefixedText() )->parse();
336 }
337 continue;
338 }
339
340 $file = $title->getNamespace() === NS_FILE ? wfLocalFile( $title ) : false;
341 $permission_errors = $title->getUserPermissionsErrors( 'delete', $this->getUser() );
342
343 if ( $permission_errors !== [] ) {
344 throw new PermissionsError( 'delete', $permission_errors );
345 }
346
347 if ( $file ) {
348 $oldimage = null; // Must be passed by reference
349 $ok = FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, false )->isOK();
350 } else {
351 $article = new Article( $title, 0 );
352 $ok = $article->doDeleteArticle( $reason );
353 }
354
355 if ( $ok ) {
356 $res[] = $this->msg( 'nuke-deleted', $title->getPrefixedText() )->parse();
357 } else {
358 $res[] = $this->msg( 'nuke-not-deleted', $title->getPrefixedText() )->parse();
359 }
360 }
361
362 $this->getOutput()->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $res ) . "</li>\n</ul>\n" );
363 $this->getOutput()->addWikiMsg( 'nuke-delete-more' );
364 }
365
374 public function prefixSearchSubpages( $search, $limit, $offset ) {
375 $user = User::newFromName( $search );
376 if ( !$user ) {
377 // No prefix suggestion for invalid user
378 return [];
379 }
380 // Autocomplete subpage as user list - public to allow caching
381 return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
382 }
383
384 protected function getGroupName() {
385 return 'pagetools';
386 }
387}
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfLocalFile( $title)
Get an object referring to a locally registered file.
Class for viewing MediaWiki article and history.
Definition Article.php:38
static doDelete(&$title, &$file, &$oldimage, $reason, $suppress, User $user=null, $tags=[])
Really delete the file.
Class for generating clickable toggle links for a list of checkboxes.
Show an error when a user tries to do something they do not have the necessary permissions for.
execute( $par)
doDelete(array $pages, $reason)
Does the actual deletion of the pages.
promptForm( $userName='')
Prompt for a username or IP address.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
getNewPages( $username, $limit, $namespace=null)
Gets a list of new pages by the specified user or everyone when none is specified.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
listForm( $username, $reason, $limit, $namespace=null)
Display list of pages to delete.
doesWrites()
Indicates whether this special page may perform database writes.
Parent class for all special pages.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
getContext()
Gets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getRequest()
Get the WebRequest being used for this instance.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getPageTitle( $subpage=false)
Get a self-referential title object.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
MediaWiki Linker LinkRenderer null $linkRenderer
Show an error when the user tries to do something whilst blocked.
static search( $audience, $search, $limit, $offset=0)
Do a prefix search of user names and return a list of matching user names.
const NS_FILE
Definition Defines.php:75
const DB_REPLICA
Definition defines.php:25
return true
Definition router.php:94
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42