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()
144  ->getPermissionManager()
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 ( $this->getHookRunner()->onSpecialContributionsBeforeMainOutput(
218  $id, $userObj, $this )
219  ) {
220  $pager = new ContribsPager(
221  $this->getContext(), [
222  'target' => $target,
223  'namespace' => $this->opts['namespace'],
224  'tagfilter' => $this->opts['tagfilter'],
225  'start' => $this->opts['start'],
226  'end' => $this->opts['end'],
227  'deletedOnly' => $this->opts['deletedOnly'],
228  'topOnly' => $this->opts['topOnly'],
229  'newOnly' => $this->opts['newOnly'],
230  'hideMinor' => $this->opts['hideMinor'],
231  'nsInvert' => $this->opts['nsInvert'],
232  'associated' => $this->opts['associated'],
233  ],
234  $this->getLinkRenderer(),
235  MediaWikiServices::getInstance()->getLinkBatchFactory()
236  );
237  if ( !$this->including() ) {
238  $out->addHTML( $this->getForm( $this->opts ) );
239  }
240 
241  if ( IPUtils::isValidRange( $target ) && !$pager->isQueryableRange( $target ) ) {
242  // Valid range, but outside CIDR limit.
243  $limits = $this->getConfig()->get( 'RangeContributionsCIDRLimit' );
244  $limit = $limits[ IPUtils::isIPv4( $target ) ? 'IPv4' : 'IPv6' ];
245  $out->addWikiMsg( 'sp-contributions-outofrange', $limit );
246  } elseif ( !$pager->getNumRows() ) {
247  $out->addWikiMsg( 'nocontribs', $target );
248  } else {
249  // @todo We just want a wiki ID here, not a "DB domain", but
250  // current status of MediaWiki conflates the two. See T235955.
251  $poolKey = WikiMap::getCurrentWikiDbDomain() . ':SpecialContributions:';
252  if ( $this->getUser()->isAnon() ) {
253  $poolKey .= 'a:' . $this->getUser()->getName();
254  } else {
255  $poolKey .= 'u:' . $this->getUser()->getId();
256  }
257  $work = new PoolCounterWorkViaCallback( 'SpecialContributions', $poolKey, [
258  'doWork' => function () use ( $pager, $out ) {
259  # Show a message about replica DB lag, if applicable
260  $lag = $pager->getDatabase()->getSessionLagStatus()['lag'];
261  if ( $lag > 0 ) {
262  $out->showLagWarning( $lag );
263  }
264 
265  $output = $pager->getBody();
266  if ( !$this->including() ) {
267  $output = $pager->getNavigationBar() .
268  $output .
269  $pager->getNavigationBar();
270  }
271  $out->addHTML( $output );
272  },
273  'error' => function () use ( $out ) {
274  $msg = $this->getUser()->isAnon()
275  ? 'sp-contributions-concurrency-ip'
276  : 'sp-contributions-concurrency-user';
277  $out->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>", $msg );
278  }
279  ] );
280  $work->execute();
281  }
282 
283  $out->preventClickjacking( $pager->getPreventClickjacking() );
284 
285  # Show the appropriate "footer" message - WHOIS tools, etc.
286  if ( IPUtils::isValidRange( $target ) ) {
287  $message = 'sp-contributions-footer-anon-range';
288  } elseif ( IPUtils::isIPAddress( $target ) ) {
289  $message = 'sp-contributions-footer-anon';
290  } elseif ( $userObj->isAnon() ) {
291  // No message for non-existing users
292  $message = '';
293  } else {
294  $message = 'sp-contributions-footer';
295  }
296 
297  if ( $message && !$this->including() && !$this->msg( $message, $target )->isDisabled() ) {
298  $out->wrapWikiMsg(
299  "<div class='mw-contributions-footer'>\n$1\n</div>",
300  [ $message, $target ] );
301  }
302  }
303  }
304 
312  protected function contributionsSub( $userObj ) {
313  if ( $userObj->isAnon() ) {
314  // Show a warning message that the user being searched for doesn't exist.
315  // User::isIP returns true for IP address and usemod IPs like '123.123.123.xxx',
316  // but returns false for IP ranges. We don't want to suggest either of these are
317  // valid usernames which we would with the 'contributions-userdoesnotexist' message.
318  if ( !User::isIP( $userObj->getName() ) && !$userObj->isIPRange() ) {
319  $this->getOutput()->wrapWikiMsg(
320  "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
321  [
322  'contributions-userdoesnotexist',
323  wfEscapeWikiText( $userObj->getName() ),
324  ]
325  );
326  if ( !$this->including() ) {
327  $this->getOutput()->setStatusCode( 404 );
328  }
329  }
330  $user = htmlspecialchars( $userObj->getName() );
331  } else {
332  $user = $this->getLinkRenderer()->makeLink( $userObj->getUserPage(), $userObj->getName() );
333  }
334  $nt = $userObj->getUserPage();
335  $talk = $userObj->getTalkPage();
336  $links = '';
337  if ( $talk ) {
338  $tools = self::getUserLinks( $this, $userObj );
339  $links = Html::openElement( 'span', [ 'class' => 'mw-changeslist-links' ] );
340  foreach ( $tools as $tool ) {
341  $links .= Html::rawElement( 'span', [], $tool ) . ' ';
342  }
343  $links = trim( $links ) . Html::closeElement( 'span' );
344 
345  // Show a note if the user is blocked and display the last block log entry.
346  // Do not expose the autoblocks, since that may lead to a leak of accounts' IPs,
347  // and also this will display a totally irrelevant log entry as a current block.
348  if ( !$this->including() ) {
349  // For IP ranges you must give DatabaseBlock::newFromTarget the CIDR string
350  // and not a user object.
351  if ( $userObj->isIPRange() ) {
352  $block = DatabaseBlock::newFromTarget( $userObj->getName(), $userObj->getName() );
353  } else {
354  $block = DatabaseBlock::newFromTarget( $userObj, $userObj );
355  }
356 
357  if ( $block !== null && $block->getType() != DatabaseBlock::TYPE_AUTO ) {
358  if ( $block->getType() == DatabaseBlock::TYPE_RANGE ) {
359  $nt = MediaWikiServices::getInstance()->getNamespaceInfo()->
360  getCanonicalName( NS_USER ) . ':' . $block->getTarget();
361  }
362 
363  $out = $this->getOutput(); // showLogExtract() wants first parameter by reference
364  if ( $userObj->isAnon() ) {
365  $msgKey = $block->isSitewide() ?
366  'sp-contributions-blocked-notice-anon' :
367  'sp-contributions-blocked-notice-anon-partial';
368  } else {
369  $msgKey = $block->isSitewide() ?
370  'sp-contributions-blocked-notice' :
371  'sp-contributions-blocked-notice-partial';
372  }
373  // Allow local styling overrides for different types of block
374  $class = $block->isSitewide() ?
375  'mw-contributions-blocked-notice' :
376  'mw-contributions-blocked-notice-partial';
378  $out,
379  'block',
380  $nt,
381  '',
382  [
383  'lim' => 1,
384  'showIfEmpty' => false,
385  'msgKey' => [
386  $msgKey,
387  $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
388  ],
389  'offset' => '', # don't use WebRequest parameter offset
390  'wrap' => Html::rawElement(
391  'div',
392  [ 'class' => $class ],
393  '$1'
394  ),
395  ]
396  );
397  }
398  }
399  }
400 
401  return Html::rawElement( 'div', [ 'class' => 'mw-contributions-user-tools' ],
402  $this->msg( 'contributions-subtitle' )->rawParams( $user )->params( $userObj->getName() )
403  . ' ' . $links
404  );
405  }
406 
415  public static function getUserLinks( SpecialPage $sp, User $target ) {
416  $id = $target->getId();
417  $username = $target->getName();
418  $userpage = $target->getUserPage();
419  $talkpage = $target->getTalkPage();
420  $isIP = IPUtils::isValid( $username );
421  $isRange = IPUtils::isValidRange( $username );
422 
423  $linkRenderer = $sp->getLinkRenderer();
424 
425  $tools = [];
426  # No talk pages for IP ranges.
427  if ( !$isRange ) {
428  $tools['user-talk'] = $linkRenderer->makeLink(
429  $talkpage,
430  $sp->msg( 'sp-contributions-talk' )->text()
431  );
432  }
433 
434  # Block / Change block / Unblock links
435  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
436  if ( $permissionManager->userHasRight( $sp->getUser(), 'block' ) ) {
437  if ( $target->getBlock() && $target->getBlock()->getType() != DatabaseBlock::TYPE_AUTO ) {
438  $tools['block'] = $linkRenderer->makeKnownLink( # Change block link
439  SpecialPage::getTitleFor( 'Block', $username ),
440  $sp->msg( 'change-blocklink' )->text()
441  );
442  $tools['unblock'] = $linkRenderer->makeKnownLink( # Unblock link
443  SpecialPage::getTitleFor( 'Unblock', $username ),
444  $sp->msg( 'unblocklink' )->text()
445  );
446  } else { # User is not blocked
447  $tools['block'] = $linkRenderer->makeKnownLink( # Block link
448  SpecialPage::getTitleFor( 'Block', $username ),
449  $sp->msg( 'blocklink' )->text()
450  );
451  }
452  }
453 
454  # Block log link
455  $tools['log-block'] = $linkRenderer->makeKnownLink(
456  SpecialPage::getTitleFor( 'Log', 'block' ),
457  $sp->msg( 'sp-contributions-blocklog' )->text(),
458  [],
459  [ 'page' => $userpage->getPrefixedText() ]
460  );
461 
462  # Suppression log link (T61120)
463  if ( $permissionManager->userHasRight( $sp->getUser(), 'suppressionlog' ) ) {
464  $tools['log-suppression'] = $linkRenderer->makeKnownLink(
465  SpecialPage::getTitleFor( 'Log', 'suppress' ),
466  $sp->msg( 'sp-contributions-suppresslog', $username )->text(),
467  [],
468  [ 'offender' => $username ]
469  );
470  }
471 
472  # Don't show some links for IP ranges
473  if ( !$isRange ) {
474  # Uploads: hide if IPs cannot upload (T220674)
475  if ( !$isIP || $permissionManager->userHasRight( $target, 'upload' ) ) {
476  $tools['uploads'] = $linkRenderer->makeKnownLink(
477  SpecialPage::getTitleFor( 'Listfiles', $username ),
478  $sp->msg( 'sp-contributions-uploads' )->text()
479  );
480  }
481 
482  # Other logs link
483  # Todo: T146628
484  $tools['logs'] = $linkRenderer->makeKnownLink(
485  SpecialPage::getTitleFor( 'Log', $username ),
486  $sp->msg( 'sp-contributions-logs' )->text()
487  );
488 
489  # Add link to deleted user contributions for priviledged users
490  # Todo: T183457
491  if ( $permissionManager->userHasRight( $sp->getUser(), 'deletedhistory' ) ) {
492  $tools['deletedcontribs'] = $linkRenderer->makeKnownLink(
493  SpecialPage::getTitleFor( 'DeletedContributions', $username ),
494  $sp->msg( 'sp-contributions-deleted', $username )->text()
495  );
496  }
497  }
498 
499  # Add a link to change user rights for privileged users
500  $userrightsPage = new UserrightsPage();
501  $userrightsPage->setContext( $sp->getContext() );
502  if ( $userrightsPage->userCanChangeRights( $target ) ) {
503  $tools['userrights'] = $linkRenderer->makeKnownLink(
504  SpecialPage::getTitleFor( 'Userrights', $username ),
505  $sp->msg( 'sp-contributions-userrights', $username )->text()
506  );
507  }
508 
509  Hooks::runner()->onContributionsToolLinks( $id, $userpage, $tools, $sp );
510 
511  return $tools;
512  }
513 
520  protected function getForm( array $pagerOptions ) {
521  $this->opts['title'] = $this->getPageTitle()->getPrefixedText();
522  // Modules required only for the form
523  $this->getOutput()->addModules( [
524  'mediawiki.special.contributions',
525  ] );
526  $this->getOutput()->addModuleStyles( 'mediawiki.widgets.DateInputWidget.styles' );
527  $this->getOutput()->enableOOUI();
528  $fields = [];
529 
530  # Add hidden params for tracking except for parameters in $skipParameters
531  $skipParameters = [
532  'namespace',
533  'nsInvert',
534  'deletedOnly',
535  'target',
536  'year',
537  'month',
538  'start',
539  'end',
540  'topOnly',
541  'newOnly',
542  'hideMinor',
543  'associated',
544  'tagfilter'
545  ];
546 
547  foreach ( $this->opts as $name => $value ) {
548  if ( in_array( $name, $skipParameters ) ) {
549  continue;
550  }
551 
552  $fields[$name] = [
553  'name' => $name,
554  'type' => 'hidden',
555  'default' => $value,
556  ];
557  }
558 
559  $target = $this->opts['target'] ?? null;
560  $fields['target'] = [
561  'type' => 'user',
562  'default' => $target ?
563  str_replace( '_', ' ', $target ) : '' ,
564  'label' => $this->msg( 'sp-contributions-username' )->text(),
565  'name' => 'target',
566  'id' => 'mw-target-user-or-ip',
567  'size' => 40,
568  'autofocus' => !$target,
569  'section' => 'contribs-top',
570  ];
571 
572  $ns = $this->opts['namespace'] ?? null;
573  $fields['namespace'] = [
574  'type' => 'namespaceselect',
575  'label' => $this->msg( 'namespace' )->text(),
576  'name' => 'namespace',
577  'cssclass' => 'namespaceselector',
578  'default' => $ns,
579  'id' => 'namespace',
580  'section' => 'contribs-top',
581  ];
582  $request = $this->getRequest();
583  $nsFilters = $request->getArray( 'wpfilters' );
584  $fields['nsFilters'] = [
585  'class' => HTMLMultiSelectField::class,
586  'label' => '',
587  'name' => 'wpfilters',
588  'flatlist' => true,
589  // Only shown when namespaces are selected.
590  'cssclass' => $ns === '' ?
591  'contribs-ns-filters mw-input-with-label mw-input-hidden' :
592  'contribs-ns-filters mw-input-with-label',
593  // `contribs-ns-filters` class allows these fields to be toggled on/off by JavaScript.
594  // See resources/src/mediawiki.special.recentchanges.js
595  'infusable' => true,
596  'options-messages' => [
597  'invert' => 'nsInvert',
598  'namespace_association' => 'associated',
599  ],
600  'default' => $nsFilters,
601  'section' => 'contribs-top',
602  ];
603  $fields['tagfilter'] = [
604  'type' => 'tagfilter',
605  'cssclass' => 'mw-tagfilter-input',
606  'id' => 'tagfilter',
607  'label-message' => [ 'tag-filter', 'parse' ],
608  'name' => 'tagfilter',
609  'size' => 20,
610  'section' => 'contribs-top',
611  ];
612 
613  if ( MediaWikiServices::getInstance()
615  ->userHasRight( $this->getUser(), 'deletedhistory' )
616  ) {
617  $fields['deletedOnly'] = [
618  'type' => 'check',
619  'id' => 'mw-show-deleted-only',
620  'label' => $this->msg( 'history-show-deleted' )->text(),
621  'name' => 'deletedOnly',
622  'section' => 'contribs-top',
623  ];
624  }
625 
626  $fields['topOnly'] = [
627  'type' => 'check',
628  'id' => 'mw-show-top-only',
629  'label' => $this->msg( 'sp-contributions-toponly' )->text(),
630  'name' => 'topOnly',
631  'section' => 'contribs-top',
632  ];
633  $fields['newOnly'] = [
634  'type' => 'check',
635  'id' => 'mw-show-new-only',
636  'label' => $this->msg( 'sp-contributions-newonly' )->text(),
637  'name' => 'newOnly',
638  'section' => 'contribs-top',
639  ];
640  $fields['hideMinor'] = [
641  'type' => 'check',
642  'cssclass' => 'mw-hide-minor-edits',
643  'id' => 'mw-show-new-only',
644  'label' => $this->msg( 'sp-contributions-hideminor' )->text(),
645  'name' => 'hideMinor',
646  'section' => 'contribs-top',
647  ];
648 
649  // Allow additions at this point to the filters.
650  $rawFilters = [];
651  $this->getHookRunner()->onSpecialContributions__getForm__filters(
652  $this, $rawFilters );
653  foreach ( $rawFilters as $filter ) {
654  // Backwards compatibility support for previous hook function signature.
655  if ( is_string( $filter ) ) {
656  $fields[] = [
657  'type' => 'info',
658  'default' => $filter,
659  'raw' => true,
660  'section' => 'contribs-top',
661  ];
662  wfDeprecated(
663  'A SpecialContributions::getForm::filters hook handler returned ' .
664  'an array of strings, this is deprecated since MediaWiki 1.33',
665  '1.33', false, false
666  );
667  } else {
668  // Preferred append method.
669  $fields[] = $filter;
670  }
671  }
672 
673  $fields['start'] = [
674  'type' => 'date',
675  'default' => '',
676  'id' => 'mw-date-start',
677  'label' => $this->msg( 'date-range-from' )->text(),
678  'name' => 'start',
679  'section' => 'contribs-date',
680  ];
681  $fields['end'] = [
682  'type' => 'date',
683  'default' => '',
684  'id' => 'mw-date-end',
685  'label' => $this->msg( 'date-range-to' )->text(),
686  'name' => 'end',
687  'section' => 'contribs-date',
688  ];
689 
690  $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
691  $htmlForm
692  ->setMethod( 'get' )
693  // When offset is defined, the user is paging through results
694  // so we hide the form by default to allow users to focus on browsing
695  // rather than defining search parameters
696  ->setCollapsibleOptions(
697  ( $pagerOptions['target'] ?? null ) ||
698  ( $pagerOptions['start'] ?? null ) ||
699  ( $pagerOptions['end'] ?? null )
700  )
701  ->setAction( wfScript() )
702  ->setSubmitText( $this->msg( 'sp-contributions-submit' )->text() )
703  ->setWrapperLegend( $this->msg( 'sp-contributions-search' )->text() );
704 
705  $explain = $this->msg( 'sp-contributions-explain' );
706  if ( !$explain->isBlank() ) {
707  $htmlForm->addFooterText( "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>" );
708  }
709 
710  $htmlForm->loadData();
711 
712  return $htmlForm->getHTML( false );
713  }
714 
723  public function prefixSearchSubpages( $search, $limit, $offset ) {
724  $user = User::newFromName( $search );
725  if ( !$user ) {
726  // No prefix suggestion for invalid user
727  return [];
728  }
729  // Autocomplete subpage as user list - public to allow caching
730  return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
731  }
732 
733  protected function getGroupName() {
734  return 'users';
735  }
736 }
SpecialContributions\prefixSearchSubpages
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
Definition: SpecialContributions.php:723
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:697
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:827
WikiMap\getCurrentWikiDbDomain
static getCurrentWikiDbDomain()
Definition: WikiMap.php:293
SpecialContributions\getUserLinks
static getUserLinks(SpecialPage $sp, User $target)
Links to different places.
Definition: SpecialContributions.php:415
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:743
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:163
if
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:85
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_deprecated.php:50
IP
Pre-librarized class name for IPUtils.
Definition: IP.php:80
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:545
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:92
SpecialPage\getSkin
getSkin()
Shortcut to get the skin being used for this instance.
Definition: SpecialPage.php:763
IncludableSpecialPage
Shortcut to construct an includable special page.
Definition: IncludableSpecialPage.php:29
PoolCounterWorkViaCallback
Convenience class for dealing with PoolCounters using callbacks.
Definition: PoolCounterWorkViaCallback.php:31
ContribsPager\processDateFilter
static processDateFilter(array $opts)
Set up date filter options, given request data.
Definition: ContribsPager.php:825
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:438
NS_MAIN
const NS_MAIN
Definition: Defines.php:69
Html\closeElement
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:315
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:50
UserrightsPage
Special page to allow managing user group membership.
Definition: SpecialUserrights.php:32
SpecialPage\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: SpecialPage.php:863
SpecialPage\getHookRunner
getHookRunner()
Definition: SpecialPage.php:1010
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:793
SpecialPage\addFeedLinks
addFeedLinks( $params)
Adds RSS/atom links.
Definition: SpecialPage.php:845
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1027
wfScript
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Definition: GlobalFunctions.php:2542
getPermissionManager
getPermissionManager()
SpecialContributions\getForm
getForm(array $pagerOptions)
Generates the namespace selector form with hidden attributes.
Definition: SpecialContributions.php:520
PoolCounterWork\execute
execute( $skipcache=false)
Get the result of the work (whatever it is), or the result of the error() function.
Definition: PoolCounterWork.php:127
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:931
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!...
Definition: SpecialPage.php:571
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:753
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:609
SpecialPage\getContext
getContext()
Gets the context this SpecialPage is executed in.
Definition: SpecialPage.php:717
ContribsPager
Pager for Special:Contributions.
Definition: ContribsPager.php:35
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:617
SpecialContributions\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialContributions.php:733
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:733
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1494
SpecialContributions\$opts
$opts
Definition: SpecialContributions.php:34
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:944
Html\openElement
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:251
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
NS_USER
const NS_USER
Definition: Defines.php:71
SpecialPage\$linkRenderer
MediaWiki Linker LinkRenderer null $linkRenderer
Definition: SpecialPage.php:71
SpecialContributions\contributionsSub
contributionsSub( $userObj)
Generates the subheading with links.
Definition: SpecialContributions.php:312
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:315
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:662
SpecialPage\including
including( $x=null)
Whether the special page is being evaluated via transclusion.
Definition: SpecialPage.php:251
SpecialContributions
Special:Contributions, show user contributions in a paged list.
Definition: SpecialContributions.php:33