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