MediaWiki REL1_35
SpecialContributions.php
Go to the documentation of this file.
1<?php
26use 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 ( User::isIP( $target ) || $userObj->isRegistered() )
118 ) {
119 // Don't add non-existent users, because hidden users
120 // that we add here will be removed later to pretend
121 // that they don't exist, and if users that actually don't
122 // exist are added here and then not removed, it exposes
123 // which users exist and are hidden vs. which actually don't
124 // exist. But, do set the relevant user for single IPs.
125 $this->getSkin()->setRelevantUser( $userObj );
126 }
127 }
128
129 $ns = $request->getVal( 'namespace', null );
130 if ( $ns !== null && $ns !== '' && $ns !== 'all' ) {
131 $this->opts['namespace'] = intval( $ns );
132 } else {
133 $this->opts['namespace'] = '';
134 }
135
136 // Backwards compatibility: Before using OOUI form the old HTML form had
137 // fields for nsInvert and associated. These have now been replaced with the
138 // wpFilters query string parameters. These are retained to keep old URIs working.
139 $this->opts['associated'] = $request->getBool( 'associated' );
140 $this->opts['nsInvert'] = (bool)$request->getVal( 'nsInvert' );
141 $nsFilters = $request->getArray( 'wpfilters', null );
142 if ( $nsFilters !== null ) {
143 $this->opts['associated'] = in_array( 'associated', $nsFilters );
144 $this->opts['nsInvert'] = in_array( 'nsInvert', $nsFilters );
145 }
146
147 $this->opts['tagfilter'] = (string)$request->getVal( 'tagfilter' );
148
149 // Allows reverts to have the bot flag in recent changes. It is just here to
150 // be passed in the form at the top of the page
151 if ( MediaWikiServices::getInstance()
152 ->getPermissionManager()
153 ->userHasRight( $user, 'markbotedits' ) && $request->getBool( 'bot' )
154 ) {
155 $this->opts['bot'] = '1';
156 }
157
158 $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev';
159 # Offset overrides year/month selection
160 if ( !$skip ) {
161 $this->opts['year'] = $request->getVal( 'year' );
162 $this->opts['month'] = $request->getVal( 'month' );
163
164 $this->opts['start'] = $request->getVal( 'start' );
165 $this->opts['end'] = $request->getVal( 'end' );
166 }
167 $this->opts = ContribsPager::processDateFilter( $this->opts );
168
169 if ( $this->opts['namespace'] !== '' && $this->opts['namespace'] < NS_MAIN ) {
170 $this->getOutput()->wrapWikiMsg(
171 "<div class=\"mw-negative-namespace-not-supported error\">\n\$1\n</div>",
172 [ 'negative-namespace-not-supported' ]
173 );
174 $out->addHTML( $this->getForm( $this->opts ) );
175 return;
176 }
177
178 $feedType = $request->getVal( 'feed' );
179
180 $feedParams = [
181 'action' => 'feedcontributions',
182 'user' => $target,
183 ];
184 if ( $this->opts['topOnly'] ) {
185 $feedParams['toponly'] = true;
186 }
187 if ( $this->opts['newOnly'] ) {
188 $feedParams['newonly'] = true;
189 }
190 if ( $this->opts['hideMinor'] ) {
191 $feedParams['hideminor'] = true;
192 }
193 if ( $this->opts['deletedOnly'] ) {
194 $feedParams['deletedonly'] = true;
195 }
196 if ( $this->opts['tagfilter'] !== '' ) {
197 $feedParams['tagfilter'] = $this->opts['tagfilter'];
198 }
199 if ( $this->opts['namespace'] !== '' ) {
200 $feedParams['namespace'] = $this->opts['namespace'];
201 }
202 // Don't use year and month for the feed URL, but pass them on if
203 // we redirect to API (if $feedType is specified)
204 if ( $feedType && $this->opts['year'] !== null ) {
205 $feedParams['year'] = $this->opts['year'];
206 }
207 if ( $feedType && $this->opts['month'] !== null ) {
208 $feedParams['month'] = $this->opts['month'];
209 }
210
211 if ( $feedType ) {
212 // Maintain some level of backwards compatibility
213 // If people request feeds using the old parameters, redirect to API
214 $feedParams['feedformat'] = $feedType;
215 $url = wfAppendQuery( wfScript( 'api' ), $feedParams );
216
217 $out->redirect( $url, '301' );
218
219 return;
220 }
221
222 // Add RSS/atom links
223 $this->addFeedLinks( $feedParams );
224
225 if ( $this->getHookRunner()->onSpecialContributionsBeforeMainOutput(
226 $id, $userObj, $this )
227 ) {
228 $pager = new ContribsPager( $this->getContext(), [
229 'target' => $target,
230 'namespace' => $this->opts['namespace'],
231 'tagfilter' => $this->opts['tagfilter'],
232 'start' => $this->opts['start'],
233 'end' => $this->opts['end'],
234 'deletedOnly' => $this->opts['deletedOnly'],
235 'topOnly' => $this->opts['topOnly'],
236 'newOnly' => $this->opts['newOnly'],
237 'hideMinor' => $this->opts['hideMinor'],
238 'nsInvert' => $this->opts['nsInvert'],
239 'associated' => $this->opts['associated'],
240 ], $this->getLinkRenderer() );
241 if ( !$this->including() ) {
242 $out->addHTML( $this->getForm( $this->opts ) );
243 }
244
245 if ( IPUtils::isValidRange( $target ) && !$pager->isQueryableRange( $target ) ) {
246 // Valid range, but outside CIDR limit.
247 $limits = $this->getConfig()->get( 'RangeContributionsCIDRLimit' );
248 $limit = $limits[ IPUtils::isIPv4( $target ) ? 'IPv4' : 'IPv6' ];
249 $out->addWikiMsg( 'sp-contributions-outofrange', $limit );
250 } else {
251 // @todo We just want a wiki ID here, not a "DB domain", but
252 // current status of MediaWiki conflates the two. See T235955.
253 $poolKey = WikiMap::getCurrentWikiDbDomain() . ':SpecialContributions:';
254 if ( $this->getUser()->isAnon() ) {
255 $poolKey .= 'a:' . $this->getUser()->getName();
256 } else {
257 $poolKey .= 'u:' . $this->getUser()->getId();
258 }
259 $work = new PoolCounterWorkViaCallback( 'SpecialContributions', $poolKey, [
260 'doWork' => function () use ( $pager, $out, $target ) {
261 if ( !$pager->getNumRows() ) {
262 $out->addWikiMsg( 'nocontribs', $target );
263 } else {
264 # Show a message about replica DB lag, if applicable
265 $lag = $pager->getDatabase()->getSessionLagStatus()['lag'];
266 if ( $lag > 0 ) {
267 $out->showLagWarning( $lag );
268 }
269
270 $output = $pager->getBody();
271 if ( !$this->including() ) {
272 $output = $pager->getNavigationBar() .
273 $output .
274 $pager->getNavigationBar();
275 }
276 $out->addHTML( $output );
277 }
278 },
279 'error' => function () use ( $out ) {
280 $msg = $this->getUser()->isAnon()
281 ? 'sp-contributions-concurrency-ip'
282 : 'sp-contributions-concurrency-user';
283 $out->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>", $msg );
284 }
285 ] );
286 $work->execute();
287 }
288
289 $out->preventClickjacking( $pager->getPreventClickjacking() );
290
291 # Show the appropriate "footer" message - WHOIS tools, etc.
292 if ( IPUtils::isValidRange( $target ) ) {
293 $message = 'sp-contributions-footer-anon-range';
294 } elseif ( IPUtils::isIPAddress( $target ) ) {
295 $message = 'sp-contributions-footer-anon';
296 } elseif ( $userObj->isAnon() ) {
297 // No message for non-existing users
298 $message = '';
299 } elseif ( $userObj->isHidden() &&
300 !MediaWikiServices::getInstance()->getPermissionManager()
301 ->userHasRight( $this->getUser(), 'hideuser' )
302 ) {
303 // User is registered, but make sure that the viewer can see them, to avoid
304 // having different behavior for missing and hidden users; see T120883
305 $message = '';
306 } else {
307 // Not hidden, or hidden but the viewer can still see it
308 $message = 'sp-contributions-footer';
309 }
310
311 if ( $message && !$this->including() && !$this->msg( $message, $target )->isDisabled() ) {
312 $out->wrapWikiMsg(
313 "<div class='mw-contributions-footer'>\n$1\n</div>",
314 [ $message, $target ] );
315 }
316 }
317 }
318
326 protected function contributionsSub( $userObj ) {
327 $isAnon = $userObj->isAnon();
328 if ( !$isAnon && $userObj->isHidden() &&
329 !MediaWikiServices::getInstance()->getPermissionManager()
330 ->userHasRight( $this->getUser(), 'hideuser' )
331 ) {
332 // T120883 if the user is hidden and the viewer cannot see hidden
333 // users, pretend like it does not exist at all.
334 $isAnon = true;
335 }
336
337 if ( $isAnon ) {
338 // Show a warning message that the user being searched for doesn't exist.
339 // User::isIP returns true for IP address and usemod IPs like '123.123.123.xxx',
340 // but returns false for IP ranges. We don't want to suggest either of these are
341 // valid usernames which we would with the 'contributions-userdoesnotexist' message.
342 if ( !User::isIP( $userObj->getName() ) && !$userObj->isIPRange() ) {
343 $this->getOutput()->wrapWikiMsg(
344 "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
345 [
346 'contributions-userdoesnotexist',
347 wfEscapeWikiText( $userObj->getName() ),
348 ]
349 );
350 if ( !$this->including() ) {
351 $this->getOutput()->setStatusCode( 404 );
352 }
353 }
354 $user = htmlspecialchars( $userObj->getName() );
355 } else {
356 $user = $this->getLinkRenderer()->makeLink( $userObj->getUserPage(), $userObj->getName() );
357 }
358 $nt = $userObj->getUserPage();
359 $talk = $userObj->getTalkPage();
360 $links = '';
361 if ( $talk ) {
362 $tools = self::getUserLinks( $this, $userObj );
363 $links = Html::openElement( 'span', [ 'class' => 'mw-changeslist-links' ] );
364 foreach ( $tools as $tool ) {
365 $links .= Html::rawElement( 'span', [], $tool ) . ' ';
366 }
367 $links = trim( $links ) . Html::closeElement( 'span' );
368
369 // Show a note if the user is blocked and display the last block log entry.
370 // Do not expose the autoblocks, since that may lead to a leak of accounts' IPs,
371 // and also this will display a totally irrelevant log entry as a current block.
372 if ( !$this->including() ) {
373 // For IP ranges you must give DatabaseBlock::newFromTarget the CIDR string
374 // and not a user object.
375 if ( $userObj->isIPRange() ) {
376 $block = DatabaseBlock::newFromTarget( $userObj->getName(), $userObj->getName() );
377 } else {
378 $block = DatabaseBlock::newFromTarget( $userObj, $userObj );
379 }
380
381 if ( $block !== null && $block->getType() != DatabaseBlock::TYPE_AUTO ) {
382 if ( $block->getType() == DatabaseBlock::TYPE_RANGE ) {
383 $nt = MediaWikiServices::getInstance()->getNamespaceInfo()->
384 getCanonicalName( NS_USER ) . ':' . $block->getTarget();
385 }
386
387 $out = $this->getOutput(); // showLogExtract() wants first parameter by reference
388 if ( $userObj->isAnon() ) {
389 $msgKey = $block->isSitewide() ?
390 'sp-contributions-blocked-notice-anon' :
391 'sp-contributions-blocked-notice-anon-partial';
392 } else {
393 $msgKey = $block->isSitewide() ?
394 'sp-contributions-blocked-notice' :
395 'sp-contributions-blocked-notice-partial';
396 }
397 // Allow local styling overrides for different types of block
398 $class = $block->isSitewide() ?
399 'mw-contributions-blocked-notice' :
400 'mw-contributions-blocked-notice-partial';
401 LogEventsList::showLogExtract(
402 $out,
403 'block',
404 $nt,
405 '',
406 [
407 'lim' => 1,
408 'showIfEmpty' => false,
409 'msgKey' => [
410 $msgKey,
411 $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
412 ],
413 'offset' => '', # don't use WebRequest parameter offset
414 'wrap' => Html::rawElement(
415 'div',
416 [ 'class' => $class ],
417 '$1'
418 ),
419 ]
420 );
421 }
422 }
423 }
424
425 return Html::rawElement( 'div', [ 'class' => 'mw-contributions-user-tools' ],
426 $this->msg( 'contributions-subtitle' )->rawParams( $user )->params( $userObj->getName() )
427 . ' ' . $links
428 );
429 }
430
439 public static function getUserLinks( SpecialPage $sp, User $target ) {
440 $id = $target->getId();
441 $username = $target->getName();
442 $userpage = $target->getUserPage();
443 $talkpage = $target->getTalkPage();
444 $isIP = IPUtils::isValid( $username );
445 $isRange = IPUtils::isValidRange( $username );
446
447 $linkRenderer = $sp->getLinkRenderer();
448
449 $tools = [];
450 # No talk pages for IP ranges.
451 if ( !$isRange ) {
452 $tools['user-talk'] = $linkRenderer->makeLink(
453 $talkpage,
454 $sp->msg( 'sp-contributions-talk' )->text()
455 );
456 }
457
458 # Block / Change block / Unblock links
459 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
460 if ( $permissionManager->userHasRight( $sp->getUser(), 'block' ) ) {
461 if ( $target->getBlock() && $target->getBlock()->getType() != DatabaseBlock::TYPE_AUTO ) {
462 $tools['block'] = $linkRenderer->makeKnownLink( # Change block link
463 SpecialPage::getTitleFor( 'Block', $username ),
464 $sp->msg( 'change-blocklink' )->text()
465 );
466 $tools['unblock'] = $linkRenderer->makeKnownLink( # Unblock link
467 SpecialPage::getTitleFor( 'Unblock', $username ),
468 $sp->msg( 'unblocklink' )->text()
469 );
470 } else { # User is not blocked
471 $tools['block'] = $linkRenderer->makeKnownLink( # Block link
472 SpecialPage::getTitleFor( 'Block', $username ),
473 $sp->msg( 'blocklink' )->text()
474 );
475 }
476 }
477
478 # Block log link
479 $tools['log-block'] = $linkRenderer->makeKnownLink(
480 SpecialPage::getTitleFor( 'Log', 'block' ),
481 $sp->msg( 'sp-contributions-blocklog' )->text(),
482 [],
483 [ 'page' => $userpage->getPrefixedText() ]
484 );
485
486 # Suppression log link (T61120)
487 if ( $permissionManager->userHasRight( $sp->getUser(), 'suppressionlog' ) ) {
488 $tools['log-suppression'] = $linkRenderer->makeKnownLink(
489 SpecialPage::getTitleFor( 'Log', 'suppress' ),
490 $sp->msg( 'sp-contributions-suppresslog', $username )->text(),
491 [],
492 [ 'offender' => $username ]
493 );
494 }
495
496 # Don't show some links for IP ranges
497 if ( !$isRange ) {
498 # Uploads: hide if IPs cannot upload (T220674)
499 if ( !$isIP || $permissionManager->userHasRight( $target, 'upload' ) ) {
500 $tools['uploads'] = $linkRenderer->makeKnownLink(
501 SpecialPage::getTitleFor( 'Listfiles', $username ),
502 $sp->msg( 'sp-contributions-uploads' )->text()
503 );
504 }
505
506 # Other logs link
507 # Todo: T146628
508 $tools['logs'] = $linkRenderer->makeKnownLink(
509 SpecialPage::getTitleFor( 'Log', $username ),
510 $sp->msg( 'sp-contributions-logs' )->text()
511 );
512
513 # Add link to deleted user contributions for priviledged users
514 # Todo: T183457
515 if ( $permissionManager->userHasRight( $sp->getUser(), 'deletedhistory' ) ) {
516 $tools['deletedcontribs'] = $linkRenderer->makeKnownLink(
517 SpecialPage::getTitleFor( 'DeletedContributions', $username ),
518 $sp->msg( 'sp-contributions-deleted', $username )->text()
519 );
520 }
521 }
522
523 # Add a link to change user rights for privileged users
524 $userrightsPage = new UserrightsPage();
525 $userrightsPage->setContext( $sp->getContext() );
526 if ( $userrightsPage->userCanChangeRights( $target ) ) {
527 $tools['userrights'] = $linkRenderer->makeKnownLink(
528 SpecialPage::getTitleFor( 'Userrights', $username ),
529 $sp->msg( 'sp-contributions-userrights', $username )->text()
530 );
531 }
532
533 Hooks::runner()->onContributionsToolLinks( $id, $userpage, $tools, $sp );
534
535 return $tools;
536 }
537
544 protected function getForm( array $pagerOptions ) {
545 $this->opts['title'] = $this->getPageTitle()->getPrefixedText();
546 // Modules required only for the form
547 $this->getOutput()->addModules( [
548 'mediawiki.special.contributions',
549 ] );
550 $this->getOutput()->addModuleStyles( 'mediawiki.widgets.DateInputWidget.styles' );
551 $this->getOutput()->enableOOUI();
552 $fields = [];
553
554 # Add hidden params for tracking except for parameters in $skipParameters
555 $skipParameters = [
556 'namespace',
557 'nsInvert',
558 'deletedOnly',
559 'target',
560 'year',
561 'month',
562 'start',
563 'end',
564 'topOnly',
565 'newOnly',
566 'hideMinor',
567 'associated',
568 'tagfilter'
569 ];
570
571 foreach ( $this->opts as $name => $value ) {
572 if ( in_array( $name, $skipParameters ) ) {
573 continue;
574 }
575
576 $fields[$name] = [
577 'name' => $name,
578 'type' => 'hidden',
579 'default' => $value,
580 ];
581 }
582
583 $target = $this->opts['target'] ?? null;
584 $fields['target'] = [
585 'type' => 'user',
586 'default' => $target ?
587 str_replace( '_', ' ', $target ) : '' ,
588 'label' => $this->msg( 'sp-contributions-username' )->text(),
589 'name' => 'target',
590 'id' => 'mw-target-user-or-ip',
591 'size' => 40,
592 'autofocus' => !$target,
593 'section' => 'contribs-top',
594 ];
595
596 $ns = $this->opts['namespace'] ?? null;
597 $fields['namespace'] = [
598 'type' => 'namespaceselect',
599 'label' => $this->msg( 'namespace' )->text(),
600 'name' => 'namespace',
601 'cssclass' => 'namespaceselector',
602 'default' => $ns,
603 'id' => 'namespace',
604 'section' => 'contribs-top',
605 ];
606 $request = $this->getRequest();
607 $nsFilters = $request->getArray( 'wpfilters' );
608 $fields['nsFilters'] = [
609 'class' => 'HTMLMultiSelectField',
610 'label' => '',
611 'name' => 'wpfilters',
612 'flatlist' => true,
613 // Only shown when namespaces are selected.
614 'cssclass' => $ns === '' ?
615 'contribs-ns-filters mw-input-with-label mw-input-hidden' :
616 'contribs-ns-filters mw-input-with-label',
617 // `contribs-ns-filters` class allows these fields to be toggled on/off by JavaScript.
618 // See resources/src/mediawiki.special.recentchanges.js
619 'infusable' => true,
620 'options-messages' => [
621 'invert' => 'nsInvert',
622 'namespace_association' => 'associated',
623 ],
624 'default' => $nsFilters,
625 'section' => 'contribs-top',
626 ];
627 $fields['tagfilter'] = [
628 'type' => 'tagfilter',
629 'cssclass' => 'mw-tagfilter-input',
630 'id' => 'tagfilter',
631 'label-message' => [ 'tag-filter', 'parse' ],
632 'name' => 'tagfilter',
633 'size' => 20,
634 'section' => 'contribs-top',
635 ];
636
637 if ( MediaWikiServices::getInstance()
639 ->userHasRight( $this->getUser(), 'deletedhistory' )
640 ) {
641 $fields['deletedOnly'] = [
642 'type' => 'check',
643 'id' => 'mw-show-deleted-only',
644 'label' => $this->msg( 'history-show-deleted' )->text(),
645 'name' => 'deletedOnly',
646 'section' => 'contribs-top',
647 ];
648 }
649
650 $fields['topOnly'] = [
651 'type' => 'check',
652 'id' => 'mw-show-top-only',
653 'label' => $this->msg( 'sp-contributions-toponly' )->text(),
654 'name' => 'topOnly',
655 'section' => 'contribs-top',
656 ];
657 $fields['newOnly'] = [
658 'type' => 'check',
659 'id' => 'mw-show-new-only',
660 'label' => $this->msg( 'sp-contributions-newonly' )->text(),
661 'name' => 'newOnly',
662 'section' => 'contribs-top',
663 ];
664 $fields['hideMinor'] = [
665 'type' => 'check',
666 'cssclass' => 'mw-hide-minor-edits',
667 'id' => 'mw-show-new-only',
668 'label' => $this->msg( 'sp-contributions-hideminor' )->text(),
669 'name' => 'hideMinor',
670 'section' => 'contribs-top',
671 ];
672
673 // Allow additions at this point to the filters.
674 $rawFilters = [];
675 $this->getHookRunner()->onSpecialContributions__getForm__filters(
676 $this, $rawFilters );
677 foreach ( $rawFilters as $filter ) {
678 // Backwards compatibility support for previous hook function signature.
679 if ( is_string( $filter ) ) {
680 $fields[] = [
681 'type' => 'info',
682 'default' => $filter,
683 'raw' => true,
684 'section' => 'contribs-top',
685 ];
687 'A SpecialContributions::getForm::filters hook handler returned ' .
688 'an array of strings, this is deprecated since MediaWiki 1.33',
689 '1.33', false, false
690 );
691 } else {
692 // Preferred append method.
693 $fields[] = $filter;
694 }
695 }
696
697 $fields['start'] = [
698 'type' => 'date',
699 'default' => '',
700 'id' => 'mw-date-start',
701 'label' => $this->msg( 'date-range-from' )->text(),
702 'name' => 'start',
703 'section' => 'contribs-date',
704 ];
705 $fields['end'] = [
706 'type' => 'date',
707 'default' => '',
708 'id' => 'mw-date-end',
709 'label' => $this->msg( 'date-range-to' )->text(),
710 'name' => 'end',
711 'section' => 'contribs-date',
712 ];
713
714 $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
715 $htmlForm
716 ->setMethod( 'get' )
717 // When offset is defined, the user is paging through results
718 // so we hide the form by default to allow users to focus on browsing
719 // rather than defining search parameters
720 ->setCollapsibleOptions(
721 ( $pagerOptions['target'] ?? null ) ||
722 ( $pagerOptions['start'] ?? null ) ||
723 ( $pagerOptions['end'] ?? null )
724 )
725 ->setAction( wfScript() )
726 ->setSubmitText( $this->msg( 'sp-contributions-submit' )->text() )
727 ->setWrapperLegend( $this->msg( 'sp-contributions-search' )->text() );
728
729 $explain = $this->msg( 'sp-contributions-explain' );
730 if ( !$explain->isBlank() ) {
731 $htmlForm->addFooterText( "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>" );
732 }
733
734 $htmlForm->loadData();
735
736 return $htmlForm->getHTML( false );
737 }
738
747 public function prefixSearchSubpages( $search, $limit, $offset ) {
748 $user = User::newFromName( $search );
749 if ( !$user ) {
750 // No prefix suggestion for invalid user
751 return [];
752 }
753 // Autocomplete subpage as user list - public to allow caching
754 return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
755 }
756
757 protected function getGroupName() {
758 return 'users';
759 }
760}
getPermissionManager()
getUser()
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)
Logs a warning that $function is deprecated.
getContext()
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition Setup.php:85
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition Hooks.php:172
Pre-librarized class name for IPUtils.
Definition IP.php:80
Shortcut to construct an includable special page.
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.
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.
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.
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.
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.
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:541
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:951
Special page to allow managing user group membership.
const NS_USER
Definition Defines.php:72
const NS_MAIN
Definition Defines.php:70