MediaWiki REL1_34
SpecialContributions.php
Go to the documentation of this file.
1<?php
26
33 protected $opts;
34
35 public function __construct() {
36 parent::__construct( 'Contributions' );
37 }
38
39 public function execute( $par ) {
40 $this->setHeaders();
41 $this->outputHeader();
42 $out = $this->getOutput();
43 // Modules required for viewing the list of contributions (also when included on other pages)
44 $out->addModuleStyles( [
45 'jquery.makeCollapsible.styles',
46 'mediawiki.interface.helpers.styles',
47 'mediawiki.special',
48 'mediawiki.special.changeslist',
49 ] );
50 $out->addModules( [
51 'mediawiki.special.recentchanges',
52 // Certain skins e.g. Minerva might have disabled this module.
53 'mediawiki.page.ready'
54 ] );
55 $this->addHelpLink( 'Help:User contributions' );
56
57 $this->opts = [];
58 $request = $this->getRequest();
59
60 $target = $par ?? $request->getVal( 'target' );
61
62 $this->opts['deletedOnly'] = $request->getBool( 'deletedOnly' );
63
64 if ( !strlen( $target ) ) {
65 if ( !$this->including() ) {
66 $out->addHTML( $this->getForm( $this->opts ) );
67 }
68
69 return;
70 }
71
72 $user = $this->getUser();
73
74 $this->opts['limit'] = $request->getInt( 'limit', $user->getOption( 'rclimit' ) );
75 $this->opts['target'] = $target;
76 $this->opts['topOnly'] = $request->getBool( 'topOnly' );
77 $this->opts['newOnly'] = $request->getBool( 'newOnly' );
78 $this->opts['hideMinor'] = $request->getBool( 'hideMinor' );
79
80 $id = 0;
81 if ( ExternalUserNames::isExternal( $target ) ) {
82 $userObj = User::newFromName( $target, false );
83 if ( !$userObj ) {
84 $out->addHTML( $this->getForm( $this->opts ) );
85 return;
86 }
87
88 $out->addSubtitle( $this->contributionsSub( $userObj ) );
89 $out->setHTMLTitle( $this->msg(
90 'pagetitle',
91 $this->msg( 'contributions-title', $target )->plain()
92 )->inContentLanguage() );
93 } else {
94 $nt = Title::makeTitleSafe( NS_USER, $target );
95 if ( !$nt ) {
96 $out->addHTML( $this->getForm( $this->opts ) );
97 return;
98 }
99 $userObj = User::newFromName( $nt->getText(), false );
100 if ( !$userObj ) {
101 $out->addHTML( $this->getForm( $this->opts ) );
102 return;
103 }
104 $id = $userObj->getId();
105
106 $target = $nt->getText();
107 $out->addSubtitle( $this->contributionsSub( $userObj ) );
108 $out->setHTMLTitle( $this->msg(
109 'pagetitle',
110 $this->msg( 'contributions-title', $target )->plain()
111 )->inContentLanguage() );
112
113 # For IP ranges, we want the contributionsSub, but not the skin-dependent
114 # links under 'Tools', which may include irrelevant links like 'Logs'.
115 if ( !IP::isValidRange( $target ) ) {
116 $this->getSkin()->setRelevantUser( $userObj );
117 }
118 }
119
120 $ns = $request->getVal( 'namespace', null );
121 if ( $ns !== null && $ns !== '' && $ns !== 'all' ) {
122 $this->opts['namespace'] = intval( $ns );
123 } else {
124 $this->opts['namespace'] = '';
125 }
126
127 // Backwards compatibility: Before using OOUI form the old HTML form had
128 // fields for nsInvert and associated. These have now been replaced with the
129 // wpFilters query string parameters. These are retained to keep old URIs working.
130 $this->opts['associated'] = $request->getBool( 'associated' );
131 $this->opts['nsInvert'] = (bool)$request->getVal( 'nsInvert' );
132 $nsFilters = $request->getArray( 'wpfilters', null );
133 if ( $nsFilters !== null ) {
134 $this->opts['associated'] = in_array( 'associated', $nsFilters );
135 $this->opts['nsInvert'] = in_array( 'nsInvert', $nsFilters );
136 }
137
138 $this->opts['tagfilter'] = (string)$request->getVal( 'tagfilter' );
139
140 // Allows reverts to have the bot flag in recent changes. It is just here to
141 // be passed in the form at the top of the page
142 if ( MediaWikiServices::getInstance()
143 ->getPermissionManager()
144 ->userHasRight( $user, 'markbotedits' ) && $request->getBool( 'bot' )
145 ) {
146 $this->opts['bot'] = '1';
147 }
148
149 $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev';
150 # Offset overrides year/month selection
151 if ( !$skip ) {
152 $this->opts['year'] = $request->getVal( 'year' );
153 $this->opts['month'] = $request->getVal( 'month' );
154
155 $this->opts['start'] = $request->getVal( 'start' );
156 $this->opts['end'] = $request->getVal( 'end' );
157 }
158 $this->opts = ContribsPager::processDateFilter( $this->opts );
159
160 if ( $this->opts['namespace'] < NS_MAIN ) {
161 $this->getOutput()->wrapWikiMsg(
162 "<div class=\"mw-negative-namespace-not-supported error\">\n\$1\n</div>",
163 [ 'negative-namespace-not-supported' ]
164 );
165 $out->addHTML( $this->getForm( $this->opts ) );
166 return;
167 }
168
169 $feedType = $request->getVal( 'feed' );
170
171 $feedParams = [
172 'action' => 'feedcontributions',
173 'user' => $target,
174 ];
175 if ( $this->opts['topOnly'] ) {
176 $feedParams['toponly'] = true;
177 }
178 if ( $this->opts['newOnly'] ) {
179 $feedParams['newonly'] = true;
180 }
181 if ( $this->opts['hideMinor'] ) {
182 $feedParams['hideminor'] = true;
183 }
184 if ( $this->opts['deletedOnly'] ) {
185 $feedParams['deletedonly'] = true;
186 }
187 if ( $this->opts['tagfilter'] !== '' ) {
188 $feedParams['tagfilter'] = $this->opts['tagfilter'];
189 }
190 if ( $this->opts['namespace'] !== '' ) {
191 $feedParams['namespace'] = $this->opts['namespace'];
192 }
193 // Don't use year and month for the feed URL, but pass them on if
194 // we redirect to API (if $feedType is specified)
195 if ( $feedType && $this->opts['year'] !== null ) {
196 $feedParams['year'] = $this->opts['year'];
197 }
198 if ( $feedType && $this->opts['month'] !== null ) {
199 $feedParams['month'] = $this->opts['month'];
200 }
201
202 if ( $feedType ) {
203 // Maintain some level of backwards compatibility
204 // If people request feeds using the old parameters, redirect to API
205 $feedParams['feedformat'] = $feedType;
206 $url = wfAppendQuery( wfScript( 'api' ), $feedParams );
207
208 $out->redirect( $url, '301' );
209
210 return;
211 }
212
213 // Add RSS/atom links
214 $this->addFeedLinks( $feedParams );
215
216 if ( Hooks::run( 'SpecialContributionsBeforeMainOutput', [ $id, $userObj, $this ] ) ) {
217 $pager = new ContribsPager( $this->getContext(), [
218 'target' => $target,
219 'namespace' => $this->opts['namespace'],
220 'tagfilter' => $this->opts['tagfilter'],
221 'start' => $this->opts['start'],
222 'end' => $this->opts['end'],
223 'deletedOnly' => $this->opts['deletedOnly'],
224 'topOnly' => $this->opts['topOnly'],
225 'newOnly' => $this->opts['newOnly'],
226 'hideMinor' => $this->opts['hideMinor'],
227 'nsInvert' => $this->opts['nsInvert'],
228 'associated' => $this->opts['associated'],
229 ], $this->getLinkRenderer() );
230 if ( !$this->including() ) {
231 $out->addHTML( $this->getForm( $this->opts ) );
232 }
233
234 if ( IP::isValidRange( $target ) && !$pager->isQueryableRange( $target ) ) {
235 // Valid range, but outside CIDR limit.
236 $limits = $this->getConfig()->get( 'RangeContributionsCIDRLimit' );
237 $limit = $limits[ IP::isIPv4( $target ) ? 'IPv4' : 'IPv6' ];
238 $out->addWikiMsg( 'sp-contributions-outofrange', $limit );
239 } elseif ( !$pager->getNumRows() ) {
240 $out->addWikiMsg( 'nocontribs', $target );
241 } else {
242 // @todo We just want a wiki ID here, not a "DB domain", but
243 // current status of MediaWiki conflates the two. See T235955.
244 $poolKey = WikiMap::getCurrentWikiDbDomain() . ':SpecialContributions:';
245 if ( $this->getUser()->isAnon() ) {
246 $poolKey .= 'a:' . $this->getUser()->getName();
247 } else {
248 $poolKey .= 'u:' . $this->getUser()->getId();
249 }
250 $work = new PoolCounterWorkViaCallback( 'SpecialContributions', $poolKey, [
251 'doWork' => function () use ( $pager, $out ) {
252 # Show a message about replica DB lag, if applicable
253 $lag = $pager->getDatabase()->getSessionLagStatus()['lag'];
254 if ( $lag > 0 ) {
255 $out->showLagWarning( $lag );
256 }
257
258 $output = $pager->getBody();
259 if ( !$this->including() ) {
260 $output = $pager->getNavigationBar() .
261 $output .
262 $pager->getNavigationBar();
263 }
264 $out->addHTML( $output );
265 },
266 'error' => function () use ( $out ) {
267 $msg = $this->getUser()->isAnon()
268 ? 'sp-contributions-concurrency-ip'
269 : 'sp-contributions-concurrency-user';
270 $out->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>", $msg );
271 }
272 ] );
273 $work->execute();
274 }
275
276 $out->preventClickjacking( $pager->getPreventClickjacking() );
277
278 # Show the appropriate "footer" message - WHOIS tools, etc.
279 if ( IP::isValidRange( $target ) ) {
280 $message = 'sp-contributions-footer-anon-range';
281 } elseif ( IP::isIPAddress( $target ) ) {
282 $message = 'sp-contributions-footer-anon';
283 } elseif ( $userObj->isAnon() ) {
284 // No message for non-existing users
285 $message = '';
286 } else {
287 $message = 'sp-contributions-footer';
288 }
289
290 if ( $message && !$this->including() && !$this->msg( $message, $target )->isDisabled() ) {
291 $out->wrapWikiMsg(
292 "<div class='mw-contributions-footer'>\n$1\n</div>",
293 [ $message, $target ] );
294 }
295 }
296 }
297
305 protected function contributionsSub( $userObj ) {
306 if ( $userObj->isAnon() ) {
307 // Show a warning message that the user being searched for doesn't exist.
308 // User::isIP returns true for IP address and usemod IPs like '123.123.123.xxx',
309 // but returns false for IP ranges. We don't want to suggest either of these are
310 // valid usernames which we would with the 'contributions-userdoesnotexist' message.
311 if ( !User::isIP( $userObj->getName() ) && !$userObj->isIPRange() ) {
312 $this->getOutput()->wrapWikiMsg(
313 "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
314 [
315 'contributions-userdoesnotexist',
316 wfEscapeWikiText( $userObj->getName() ),
317 ]
318 );
319 if ( !$this->including() ) {
320 $this->getOutput()->setStatusCode( 404 );
321 }
322 }
323 $user = htmlspecialchars( $userObj->getName() );
324 } else {
325 $user = $this->getLinkRenderer()->makeLink( $userObj->getUserPage(), $userObj->getName() );
326 }
327 $nt = $userObj->getUserPage();
328 $talk = $userObj->getTalkPage();
329 $links = '';
330 if ( $talk ) {
331 $tools = self::getUserLinks( $this, $userObj );
332 $links = Html::openElement( 'span', [ 'class' => 'mw-changeslist-links' ] );
333 foreach ( $tools as $tool ) {
334 $links .= Html::rawElement( 'span', [], $tool ) . ' ';
335 }
336 $links = trim( $links ) . Html::closeElement( 'span' );
337
338 // Show a note if the user is blocked and display the last block log entry.
339 // Do not expose the autoblocks, since that may lead to a leak of accounts' IPs,
340 // and also this will display a totally irrelevant log entry as a current block.
341 if ( !$this->including() ) {
342 // For IP ranges you must give DatabaseBlock::newFromTarget the CIDR string
343 // and not a user object.
344 if ( $userObj->isIPRange() ) {
345 $block = DatabaseBlock::newFromTarget( $userObj->getName(), $userObj->getName() );
346 } else {
347 $block = DatabaseBlock::newFromTarget( $userObj, $userObj );
348 }
349
350 if ( !is_null( $block ) && $block->getType() != DatabaseBlock::TYPE_AUTO ) {
351 if ( $block->getType() == DatabaseBlock::TYPE_RANGE ) {
352 $nt = MediaWikiServices::getInstance()->getNamespaceInfo()->
353 getCanonicalName( NS_USER ) . ':' . $block->getTarget();
354 }
355
356 $out = $this->getOutput(); // showLogExtract() wants first parameter by reference
358 $out,
359 'block',
360 $nt,
361 '',
362 [
363 'lim' => 1,
364 'showIfEmpty' => false,
365 'msgKey' => [
366 $userObj->isAnon() ?
367 'sp-contributions-blocked-notice-anon' :
368 'sp-contributions-blocked-notice',
369 $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
370 ],
371 'offset' => '' # don't use WebRequest parameter offset
372 ]
373 );
374 }
375 }
376 }
377
378 return Html::rawElement( 'div', [ 'class' => 'mw-contributions-user-tools' ],
379 $this->msg( 'contributions-subtitle' )->rawParams( $user )->params( $userObj->getName() )
380 . ' ' . $links
381 );
382 }
383
392 public static function getUserLinks( SpecialPage $sp, User $target ) {
393 $id = $target->getId();
394 $username = $target->getName();
395 $userpage = $target->getUserPage();
396 $talkpage = $target->getTalkPage();
397 $isIP = IP::isValid( $username );
398 $isRange = IP::isValidRange( $username );
399
400 $linkRenderer = $sp->getLinkRenderer();
401
402 $tools = [];
403 # No talk pages for IP ranges.
404 if ( !$isRange ) {
405 $tools['user-talk'] = $linkRenderer->makeLink(
406 $talkpage,
407 $sp->msg( 'sp-contributions-talk' )->text()
408 );
409 }
410
411 # Block / Change block / Unblock links
412 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
413 if ( $permissionManager->userHasRight( $sp->getUser(), 'block' ) ) {
414 if ( $target->getBlock() && $target->getBlock()->getType() != DatabaseBlock::TYPE_AUTO ) {
415 $tools['block'] = $linkRenderer->makeKnownLink( # Change block link
416 SpecialPage::getTitleFor( 'Block', $username ),
417 $sp->msg( 'change-blocklink' )->text()
418 );
419 $tools['unblock'] = $linkRenderer->makeKnownLink( # Unblock link
420 SpecialPage::getTitleFor( 'Unblock', $username ),
421 $sp->msg( 'unblocklink' )->text()
422 );
423 } else { # User is not blocked
424 $tools['block'] = $linkRenderer->makeKnownLink( # Block link
425 SpecialPage::getTitleFor( 'Block', $username ),
426 $sp->msg( 'blocklink' )->text()
427 );
428 }
429 }
430
431 # Block log link
432 $tools['log-block'] = $linkRenderer->makeKnownLink(
433 SpecialPage::getTitleFor( 'Log', 'block' ),
434 $sp->msg( 'sp-contributions-blocklog' )->text(),
435 [],
436 [ 'page' => $userpage->getPrefixedText() ]
437 );
438
439 # Suppression log link (T61120)
440 if ( $permissionManager->userHasRight( $sp->getUser(), 'suppressionlog' ) ) {
441 $tools['log-suppression'] = $linkRenderer->makeKnownLink(
442 SpecialPage::getTitleFor( 'Log', 'suppress' ),
443 $sp->msg( 'sp-contributions-suppresslog', $username )->text(),
444 [],
445 [ 'offender' => $username ]
446 );
447 }
448
449 # Don't show some links for IP ranges
450 if ( !$isRange ) {
451 # Uploads: hide if IPs cannot upload (T220674)
452 if ( !$isIP || $permissionManager->userHasRight( $target, 'upload' ) ) {
453 $tools['uploads'] = $linkRenderer->makeKnownLink(
454 SpecialPage::getTitleFor( 'Listfiles', $username ),
455 $sp->msg( 'sp-contributions-uploads' )->text()
456 );
457 }
458
459 # Other logs link
460 # Todo: T146628
461 $tools['logs'] = $linkRenderer->makeKnownLink(
462 SpecialPage::getTitleFor( 'Log', $username ),
463 $sp->msg( 'sp-contributions-logs' )->text()
464 );
465
466 # Add link to deleted user contributions for priviledged users
467 # Todo: T183457
468 if ( $permissionManager->userHasRight( $sp->getUser(), 'deletedhistory' ) ) {
469 $tools['deletedcontribs'] = $linkRenderer->makeKnownLink(
470 SpecialPage::getTitleFor( 'DeletedContributions', $username ),
471 $sp->msg( 'sp-contributions-deleted', $username )->text()
472 );
473 }
474 }
475
476 # Add a link to change user rights for privileged users
477 $userrightsPage = new UserrightsPage();
478 $userrightsPage->setContext( $sp->getContext() );
479 if ( $userrightsPage->userCanChangeRights( $target ) ) {
480 $tools['userrights'] = $linkRenderer->makeKnownLink(
481 SpecialPage::getTitleFor( 'Userrights', $username ),
482 $sp->msg( 'sp-contributions-userrights', $username )->text()
483 );
484 }
485
486 Hooks::run( 'ContributionsToolLinks', [ $id, $userpage, &$tools, $sp ] );
487
488 return $tools;
489 }
490
497 protected function getForm( array $pagerOptions ) {
498 $this->opts['title'] = $this->getPageTitle()->getPrefixedText();
499 // Modules required only for the form
500 $this->getOutput()->addModules( [
501 'mediawiki.userSuggest',
502 'mediawiki.special.contributions',
503 ] );
504 $this->getOutput()->addModuleStyles( 'mediawiki.widgets.DateInputWidget.styles' );
505 $this->getOutput()->enableOOUI();
506 $fields = [];
507
508 # Add hidden params for tracking except for parameters in $skipParameters
509 $skipParameters = [
510 'namespace',
511 'nsInvert',
512 'deletedOnly',
513 'target',
514 'year',
515 'month',
516 'start',
517 'end',
518 'topOnly',
519 'newOnly',
520 'hideMinor',
521 'associated',
522 'tagfilter'
523 ];
524
525 foreach ( $this->opts as $name => $value ) {
526 if ( in_array( $name, $skipParameters ) ) {
527 continue;
528 }
529
530 $fields[$name] = [
531 'name' => $name,
532 'type' => 'hidden',
533 'default' => $value,
534 ];
535 }
536
537 $target = $this->opts['target'] ?? null;
538 $fields['target'] = [
539 'type' => 'text',
540 'cssclass' => 'mw-autocomplete-user mw-ui-input-inline mw-input',
541 'default' => $target ?
542 str_replace( '_', ' ', $target ) : '' ,
543 'label' => $this->msg( 'sp-contributions-username' )->text(),
544 'name' => 'target',
545 'id' => 'mw-target-user-or-ip',
546 'size' => 40,
547 'autofocus' => !$target,
548 'section' => 'contribs-top',
549 ];
550
551 $ns = $this->opts['namespace'] ?? null;
552 $fields['namespace'] = [
553 'type' => 'namespaceselect',
554 'label' => $this->msg( 'namespace' )->text(),
555 'name' => 'namespace',
556 'cssclass' => 'namespaceselector',
557 'default' => $ns,
558 'id' => 'namespace',
559 'section' => 'contribs-top',
560 ];
561 $request = $this->getRequest();
562 $nsFilters = $request->getArray( 'wpfilters' );
563 $fields['nsFilters'] = [
564 'class' => 'HTMLMultiSelectField',
565 'label' => '',
566 'name' => 'wpfilters',
567 'flatlist' => true,
568 // Only shown when namespaces are selected.
569 'cssclass' => $ns === '' ?
570 'contribs-ns-filters mw-input-with-label mw-input-hidden' :
571 'contribs-ns-filters mw-input-with-label',
572 // `contribs-ns-filters` class allows these fields to be toggled on/off by JavaScript.
573 // See resources/src/mediawiki.special.recentchanges.js
574 'infusable' => true,
575 'options-messages' => [
576 'invert' => 'nsInvert',
577 'namespace_association' => 'associated',
578 ],
579 'default' => $nsFilters,
580 'section' => 'contribs-top',
581 ];
582 $fields['tagfilter'] = [
583 'type' => 'tagfilter',
584 'cssclass' => 'mw-tagfilter-input',
585 'id' => 'tagfilter',
586 'label-message' => [ 'tag-filter', 'parse' ],
587 'name' => 'tagfilter',
588 'size' => 20,
589 'section' => 'contribs-top',
590 ];
591
592 if ( MediaWikiServices::getInstance()
594 ->userHasRight( $this->getUser(), 'deletedhistory' )
595 ) {
596 $fields['deletedOnly'] = [
597 'type' => 'check',
598 'id' => 'mw-show-deleted-only',
599 'label' => $this->msg( 'history-show-deleted' )->text(),
600 'name' => 'deletedOnly',
601 'section' => 'contribs-top',
602 ];
603 }
604
605 $fields['topOnly'] = [
606 'type' => 'check',
607 'id' => 'mw-show-top-only',
608 'label' => $this->msg( 'sp-contributions-toponly' )->text(),
609 'name' => 'topOnly',
610 'section' => 'contribs-top',
611 ];
612 $fields['newOnly'] = [
613 'type' => 'check',
614 'id' => 'mw-show-new-only',
615 'label' => $this->msg( 'sp-contributions-newonly' )->text(),
616 'name' => 'newOnly',
617 'section' => 'contribs-top',
618 ];
619 $fields['hideMinor'] = [
620 'type' => 'check',
621 'cssclass' => 'mw-hide-minor-edits',
622 'id' => 'mw-show-new-only',
623 'label' => $this->msg( 'sp-contributions-hideminor' )->text(),
624 'name' => 'hideMinor',
625 'section' => 'contribs-top',
626 ];
627
628 // Allow additions at this point to the filters.
629 $rawFilters = [];
630 Hooks::run(
631 'SpecialContributions::getForm::filters',
632 [ $this, &$rawFilters ]
633 );
634 foreach ( $rawFilters as $filter ) {
635 // Backwards compatibility support for previous hook function signature.
636 if ( is_string( $filter ) ) {
637 $fields[] = [
638 'type' => 'info',
639 'default' => $filter,
640 'raw' => true,
641 'section' => 'contribs-top',
642 ];
644 __METHOD__ .
645 ' returning string[]',
646 '1.33'
647 );
648 } else {
649 // Preferred append method.
650 $fields[] = $filter;
651 }
652 }
653
654 $fields['start'] = [
655 'type' => 'date',
656 'default' => '',
657 'id' => 'mw-date-start',
658 'label' => $this->msg( 'date-range-from' )->text(),
659 'name' => 'start',
660 'section' => 'contribs-date',
661 ];
662 $fields['end'] = [
663 'type' => 'date',
664 'default' => '',
665 'id' => 'mw-date-end',
666 'label' => $this->msg( 'date-range-to' )->text(),
667 'name' => 'end',
668 'section' => 'contribs-date',
669 ];
670
671 $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
672 $htmlForm
673 ->setMethod( 'get' )
674 // When offset is defined, the user is paging through results
675 // so we hide the form by default to allow users to focus on browsing
676 // rather than defining search parameters
677 ->setCollapsibleOptions(
678 ( $pagerOptions['target'] ?? null ) ||
679 ( $pagerOptions['start'] ?? null ) ||
680 ( $pagerOptions['end'] ?? null )
681 )
682 ->setAction( wfScript() )
683 ->setSubmitText( $this->msg( 'sp-contributions-submit' )->text() )
684 ->setWrapperLegend( $this->msg( 'sp-contributions-search' )->text() );
685
686 $explain = $this->msg( 'sp-contributions-explain' );
687 if ( !$explain->isBlank() ) {
688 $htmlForm->addFooterText( "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>" );
689 }
690
691 $htmlForm->loadData();
692
693 return $htmlForm->getHTML( false );
694 }
695
704 public function prefixSearchSubpages( $search, $limit, $offset ) {
705 $user = User::newFromName( $search );
706 if ( !$user ) {
707 // No prefix suggestion for invalid user
708 return [];
709 }
710 // Autocomplete subpage as user list - public to allow caching
711 return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
712 }
713
714 protected function getGroupName() {
715 return 'users';
716 }
717}
getPermissionManager()
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
static processDateFilter(array $opts)
Set up date filter options, given request data.
static isExternal( $username)
Tells whether the username is external or not.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition Hooks.php:200
A collection of public static functions to play with IP address and IP ranges.
Definition IP.php:67
Shortcut to construct an includable special page.
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
MediaWikiServices is the service locator for the application scope of MediaWiki.
Convenience class for dealing with PoolCounters using callbacks.
execute( $skipcache=false)
Get the result of the work (whatever it is), or the result of the error() function.
Special:Contributions, show user contributions in a paged list.
static getUserLinks(SpecialPage $sp, User $target)
Links to different places.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
getForm(array $pagerOptions)
Generates the namespace selector form with hidden attributes.
execute( $par)
Default execute method Checks user permissions.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
contributionsSub( $userObj)
Generates the subheading with links.
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.
getSkin()
Shortcut to get the skin being used for this instance.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
addFeedLinks( $params)
Adds RSS/atom links.
getContext()
Gets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
including( $x=null)
Whether the special page is being evaluated via transclusion.
MediaWiki Linker LinkRenderer null $linkRenderer
static search( $audience, $search, $limit, $offset=0)
Do a prefix search of user names and return a list of matching user names.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:518
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:933
Special page to allow managing user group membership.
const NS_USER
Definition Defines.php:71
const NS_MAIN
Definition Defines.php:69
$filter