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