This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Implements Special:Contributions |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup SpecialPage |
||
22 | */ |
||
23 | |||
24 | /** |
||
25 | * Special:Contributions, show user contributions in a paged list |
||
26 | * |
||
27 | * @ingroup SpecialPage |
||
28 | */ |
||
29 | class SpecialContributions extends IncludableSpecialPage { |
||
30 | protected $opts; |
||
31 | |||
32 | public function __construct() { |
||
33 | parent::__construct( 'Contributions' ); |
||
34 | } |
||
35 | |||
36 | public function execute( $par ) { |
||
37 | $this->setHeaders(); |
||
38 | $this->outputHeader(); |
||
39 | $out = $this->getOutput(); |
||
40 | $out->addModuleStyles( [ |
||
41 | 'mediawiki.special', |
||
42 | 'mediawiki.special.changeslist', |
||
43 | ] ); |
||
44 | $this->addHelpLink( 'Help:User contributions' ); |
||
45 | |||
46 | $this->opts = []; |
||
47 | $request = $this->getRequest(); |
||
48 | |||
49 | if ( $par !== null ) { |
||
50 | $target = $par; |
||
51 | } else { |
||
52 | $target = $request->getVal( 'target' ); |
||
53 | } |
||
54 | |||
55 | if ( $request->getVal( 'contribs' ) == 'newbie' || $par === 'newbies' ) { |
||
56 | $target = 'newbies'; |
||
57 | $this->opts['contribs'] = 'newbie'; |
||
58 | } else { |
||
59 | $this->opts['contribs'] = 'user'; |
||
60 | } |
||
61 | |||
62 | $this->opts['deletedOnly'] = $request->getBool( 'deletedOnly' ); |
||
63 | |||
64 | if ( !strlen( $target ) ) { |
||
65 | if ( !$this->including() ) { |
||
66 | $out->addHTML( $this->getForm() ); |
||
67 | } |
||
68 | |||
69 | return; |
||
70 | } |
||
71 | |||
72 | $user = $this->getUser(); |
||
73 | |||
74 | $this->opts['limit'] = $request->getInt( 'limit', $user->getOption( 'rclimit' ) ); |
||
75 | $this->opts['target'] = $target; |
||
76 | $this->opts['topOnly'] = $request->getBool( 'topOnly' ); |
||
77 | $this->opts['newOnly'] = $request->getBool( 'newOnly' ); |
||
78 | $this->opts['hideMinor'] = $request->getBool( 'hideMinor' ); |
||
79 | |||
80 | $nt = Title::makeTitleSafe( NS_USER, $target ); |
||
81 | if ( !$nt ) { |
||
82 | $out->addHTML( $this->getForm() ); |
||
83 | |||
84 | return; |
||
85 | } |
||
86 | $userObj = User::newFromName( $nt->getText(), false ); |
||
87 | if ( !$userObj ) { |
||
88 | $out->addHTML( $this->getForm() ); |
||
89 | |||
90 | return; |
||
91 | } |
||
92 | $id = $userObj->getId(); |
||
93 | |||
94 | if ( $this->opts['contribs'] != 'newbie' ) { |
||
95 | $target = $nt->getText(); |
||
96 | $out->addSubtitle( $this->contributionsSub( $userObj ) ); |
||
97 | $out->setHTMLTitle( $this->msg( |
||
98 | 'pagetitle', |
||
99 | $this->msg( 'contributions-title', $target )->plain() |
||
100 | )->inContentLanguage() ); |
||
101 | $this->getSkin()->setRelevantUser( $userObj ); |
||
102 | } else { |
||
103 | $out->addSubtitle( $this->msg( 'sp-contributions-newbies-sub' ) ); |
||
104 | $out->setHTMLTitle( $this->msg( |
||
105 | 'pagetitle', |
||
106 | $this->msg( 'sp-contributions-newbies-title' )->plain() |
||
107 | )->inContentLanguage() ); |
||
108 | } |
||
109 | |||
110 | $ns = $request->getVal( 'namespace', null ); |
||
111 | if ( $ns !== null && $ns !== '' ) { |
||
112 | $this->opts['namespace'] = intval( $ns ); |
||
113 | } else { |
||
114 | $this->opts['namespace'] = ''; |
||
115 | } |
||
116 | |||
117 | $this->opts['associated'] = $request->getBool( 'associated' ); |
||
118 | $this->opts['nsInvert'] = (bool)$request->getVal( 'nsInvert' ); |
||
119 | $this->opts['tagfilter'] = (string)$request->getVal( 'tagfilter' ); |
||
120 | |||
121 | // Allows reverts to have the bot flag in recent changes. It is just here to |
||
122 | // be passed in the form at the top of the page |
||
123 | if ( $user->isAllowed( 'markbotedits' ) && $request->getBool( 'bot' ) ) { |
||
124 | $this->opts['bot'] = '1'; |
||
125 | } |
||
126 | |||
127 | $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev'; |
||
128 | # Offset overrides year/month selection |
||
129 | if ( $skip ) { |
||
130 | $this->opts['year'] = ''; |
||
131 | $this->opts['month'] = ''; |
||
132 | } else { |
||
133 | $this->opts['year'] = $request->getIntOrNull( 'year' ); |
||
134 | $this->opts['month'] = $request->getIntOrNull( 'month' ); |
||
135 | } |
||
136 | |||
137 | $feedType = $request->getVal( 'feed' ); |
||
138 | |||
139 | $feedParams = [ |
||
140 | 'action' => 'feedcontributions', |
||
141 | 'user' => $target, |
||
142 | ]; |
||
143 | if ( $this->opts['topOnly'] ) { |
||
144 | $feedParams['toponly'] = true; |
||
145 | } |
||
146 | if ( $this->opts['newOnly'] ) { |
||
147 | $feedParams['newonly'] = true; |
||
148 | } |
||
149 | if ( $this->opts['hideMinor'] ) { |
||
150 | $feedParams['hideminor'] = true; |
||
151 | } |
||
152 | if ( $this->opts['deletedOnly'] ) { |
||
153 | $feedParams['deletedonly'] = true; |
||
154 | } |
||
155 | if ( $this->opts['tagfilter'] !== '' ) { |
||
156 | $feedParams['tagfilter'] = $this->opts['tagfilter']; |
||
157 | } |
||
158 | if ( $this->opts['namespace'] !== '' ) { |
||
159 | $feedParams['namespace'] = $this->opts['namespace']; |
||
160 | } |
||
161 | // Don't use year and month for the feed URL, but pass them on if |
||
162 | // we redirect to API (if $feedType is specified) |
||
163 | View Code Duplication | if ( $feedType && $this->opts['year'] !== null ) { |
|
0 ignored issues
–
show
|
|||
164 | $feedParams['year'] = $this->opts['year']; |
||
165 | } |
||
166 | View Code Duplication | if ( $feedType && $this->opts['month'] !== null ) { |
|
0 ignored issues
–
show
The expression
$feedType of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
167 | $feedParams['month'] = $this->opts['month']; |
||
168 | } |
||
169 | |||
170 | if ( $feedType ) { |
||
0 ignored issues
–
show
The expression
$feedType of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
171 | // Maintain some level of backwards compatibility |
||
172 | // If people request feeds using the old parameters, redirect to API |
||
173 | $feedParams['feedformat'] = $feedType; |
||
174 | $url = wfAppendQuery( wfScript( 'api' ), $feedParams ); |
||
175 | |||
176 | $out->redirect( $url, '301' ); |
||
177 | |||
178 | return; |
||
179 | } |
||
180 | |||
181 | // Add RSS/atom links |
||
182 | $this->addFeedLinks( $feedParams ); |
||
183 | |||
184 | if ( Hooks::run( 'SpecialContributionsBeforeMainOutput', [ $id, $userObj, $this ] ) ) { |
||
185 | if ( !$this->including() ) { |
||
186 | $out->addHTML( $this->getForm() ); |
||
187 | } |
||
188 | $pager = new ContribsPager( $this->getContext(), [ |
||
189 | 'target' => $target, |
||
190 | 'contribs' => $this->opts['contribs'], |
||
191 | 'namespace' => $this->opts['namespace'], |
||
192 | 'tagfilter' => $this->opts['tagfilter'], |
||
193 | 'year' => $this->opts['year'], |
||
194 | 'month' => $this->opts['month'], |
||
195 | 'deletedOnly' => $this->opts['deletedOnly'], |
||
196 | 'topOnly' => $this->opts['topOnly'], |
||
197 | 'newOnly' => $this->opts['newOnly'], |
||
198 | 'hideMinor' => $this->opts['hideMinor'], |
||
199 | 'nsInvert' => $this->opts['nsInvert'], |
||
200 | 'associated' => $this->opts['associated'], |
||
201 | ] ); |
||
202 | |||
203 | if ( !$pager->getNumRows() ) { |
||
204 | $out->addWikiMsg( 'nocontribs', $target ); |
||
205 | } else { |
||
206 | # Show a message about replica DB lag, if applicable |
||
207 | $lag = wfGetLB()->safeGetLag( $pager->getDatabase() ); |
||
0 ignored issues
–
show
The function
wfGetLB() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancer() or MediaWikiServices::getDBLoadBalancerFactory() instead.
This function has been deprecated. The supplier of the file has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead. ![]() It seems like
$pager->getDatabase() can be null ; however, safeGetLag() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
208 | if ( $lag > 0 ) { |
||
209 | $out->showLagWarning( $lag ); |
||
0 ignored issues
–
show
It seems like
$lag defined by wfGetLB()->safeGetLag($pager->getDatabase()) on line 207 can also be of type boolean ; however, OutputPage::showLagWarning() does only seem to accept integer , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
210 | } |
||
211 | |||
212 | $output = $pager->getBody(); |
||
213 | if ( !$this->including() ) { |
||
214 | $output = '<p>' . $pager->getNavigationBar() . '</p>' . |
||
215 | $output . |
||
216 | '<p>' . $pager->getNavigationBar() . '</p>'; |
||
217 | } |
||
218 | $out->addHTML( $output ); |
||
219 | } |
||
220 | $out->preventClickjacking( $pager->getPreventClickjacking() ); |
||
221 | |||
222 | # Show the appropriate "footer" message - WHOIS tools, etc. |
||
223 | if ( $this->opts['contribs'] == 'newbie' ) { |
||
224 | $message = 'sp-contributions-footer-newbies'; |
||
225 | } elseif ( IP::isIPAddress( $target ) ) { |
||
226 | $message = 'sp-contributions-footer-anon'; |
||
227 | } elseif ( $userObj->isAnon() ) { |
||
228 | // No message for non-existing users |
||
229 | $message = ''; |
||
230 | } else { |
||
231 | $message = 'sp-contributions-footer'; |
||
232 | } |
||
233 | |||
234 | if ( $message ) { |
||
235 | if ( !$this->including() ) { |
||
236 | if ( !$this->msg( $message, $target )->isDisabled() ) { |
||
237 | $out->wrapWikiMsg( |
||
238 | "<div class='mw-contributions-footer'>\n$1\n</div>", |
||
239 | [ $message, $target ] ); |
||
240 | } |
||
241 | } |
||
242 | } |
||
243 | } |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Generates the subheading with links |
||
248 | * @param User $userObj User object for the target |
||
249 | * @return string Appropriately-escaped HTML to be output literally |
||
250 | * @todo FIXME: Almost the same as getSubTitle in SpecialDeletedContributions.php. |
||
251 | * Could be combined. |
||
252 | */ |
||
253 | protected function contributionsSub( $userObj ) { |
||
254 | if ( $userObj->isAnon() ) { |
||
255 | // Show a warning message that the user being searched for doesn't exists |
||
256 | if ( !User::isIP( $userObj->getName() ) ) { |
||
257 | $this->getOutput()->wrapWikiMsg( |
||
258 | "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>", |
||
259 | [ |
||
260 | 'contributions-userdoesnotexist', |
||
261 | wfEscapeWikiText( $userObj->getName() ), |
||
262 | ] |
||
263 | ); |
||
264 | if ( !$this->including() ) { |
||
265 | $this->getOutput()->setStatusCode( 404 ); |
||
266 | } |
||
267 | } |
||
268 | $user = htmlspecialchars( $userObj->getName() ); |
||
269 | } else { |
||
270 | $user = $this->getLinkRenderer()->makeLink( $userObj->getUserPage(), $userObj->getName() ); |
||
271 | } |
||
272 | $nt = $userObj->getUserPage(); |
||
273 | $talk = $userObj->getTalkPage(); |
||
274 | $links = ''; |
||
275 | if ( $talk ) { |
||
276 | $tools = self::getUserLinks( $this, $userObj ); |
||
277 | $links = $this->getLanguage()->pipeList( $tools ); |
||
278 | |||
279 | // Show a note if the user is blocked and display the last block log entry. |
||
280 | // Do not expose the autoblocks, since that may lead to a leak of accounts' IPs, |
||
281 | // and also this will display a totally irrelevant log entry as a current block. |
||
282 | if ( !$this->including() ) { |
||
283 | $block = Block::newFromTarget( $userObj, $userObj ); |
||
284 | View Code Duplication | if ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { |
|
285 | if ( $block->getType() == Block::TYPE_RANGE ) { |
||
286 | $nt = MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(); |
||
287 | } |
||
288 | |||
289 | $out = $this->getOutput(); // showLogExtract() wants first parameter by reference |
||
290 | LogEventsList::showLogExtract( |
||
291 | $out, |
||
292 | 'block', |
||
293 | $nt, |
||
294 | '', |
||
295 | [ |
||
296 | 'lim' => 1, |
||
297 | 'showIfEmpty' => false, |
||
298 | 'msgKey' => [ |
||
299 | $userObj->isAnon() ? |
||
300 | 'sp-contributions-blocked-notice-anon' : |
||
301 | 'sp-contributions-blocked-notice', |
||
302 | $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice' |
||
303 | ], |
||
304 | 'offset' => '' # don't use WebRequest parameter offset |
||
305 | ] |
||
306 | ); |
||
307 | } |
||
308 | } |
||
309 | } |
||
310 | |||
311 | return $this->msg( 'contribsub2' )->rawParams( $user, $links )->params( $userObj->getName() ); |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Links to different places. |
||
316 | * |
||
317 | * @note This function is also called in DeletedContributionsPage |
||
318 | * @param SpecialPage $sp SpecialPage instance, for context |
||
319 | * @param User $target Target user object |
||
320 | * @return array |
||
321 | */ |
||
322 | public static function getUserLinks( SpecialPage $sp, User $target ) { |
||
323 | |||
324 | $id = $target->getId(); |
||
325 | $username = $target->getName(); |
||
326 | $userpage = $target->getUserPage(); |
||
327 | $talkpage = $target->getTalkPage(); |
||
328 | |||
329 | $linkRenderer = $sp->getLinkRenderer(); |
||
330 | $tools['user-talk'] = $linkRenderer->makeLink( |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
$tools was never initialized. Although not strictly required by PHP, it is generally a good practice to add $tools = array(); before regardless.
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code. Let’s take a look at an example: foreach ($collection as $item) {
$myArray['foo'] = $item->getFoo();
if ($item->hasBar()) {
$myArray['bar'] = $item->getBar();
}
// do something with $myArray
}
As you can see in this example, the array This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop. ![]() |
|||
331 | $talkpage, |
||
332 | $sp->msg( 'sp-contributions-talk' )->text() |
||
333 | ); |
||
334 | |||
335 | if ( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) { |
||
336 | if ( $sp->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links |
||
337 | if ( $target->isBlocked() && $target->getBlock()->getType() != Block::TYPE_AUTO ) { |
||
338 | $tools['block'] = $linkRenderer->makeKnownLink( # Change block link |
||
339 | SpecialPage::getTitleFor( 'Block', $username ), |
||
340 | $sp->msg( 'change-blocklink' )->text() |
||
341 | ); |
||
342 | $tools['unblock'] = $linkRenderer->makeKnownLink( # Unblock link |
||
343 | SpecialPage::getTitleFor( 'Unblock', $username ), |
||
344 | $sp->msg( 'unblocklink' )->text() |
||
345 | ); |
||
346 | } else { # User is not blocked |
||
347 | $tools['block'] = $linkRenderer->makeKnownLink( # Block link |
||
348 | SpecialPage::getTitleFor( 'Block', $username ), |
||
349 | $sp->msg( 'blocklink' )->text() |
||
350 | ); |
||
351 | } |
||
352 | } |
||
353 | |||
354 | # Block log link |
||
355 | $tools['log-block'] = $linkRenderer->makeKnownLink( |
||
356 | SpecialPage::getTitleFor( 'Log', 'block' ), |
||
357 | $sp->msg( 'sp-contributions-blocklog' )->text(), |
||
358 | [], |
||
359 | [ 'page' => $userpage->getPrefixedText() ] |
||
360 | ); |
||
361 | |||
362 | # Suppression log link (bug 59120) |
||
363 | View Code Duplication | if ( $sp->getUser()->isAllowed( 'suppressionlog' ) ) { |
|
364 | $tools['log-suppression'] = $linkRenderer->makeKnownLink( |
||
365 | SpecialPage::getTitleFor( 'Log', 'suppress' ), |
||
366 | $sp->msg( 'sp-contributions-suppresslog', $username )->text(), |
||
367 | [], |
||
368 | [ 'offender' => $username ] |
||
369 | ); |
||
370 | } |
||
371 | } |
||
372 | # Uploads |
||
373 | $tools['uploads'] = $linkRenderer->makeKnownLink( |
||
374 | SpecialPage::getTitleFor( 'Listfiles', $username ), |
||
375 | $sp->msg( 'sp-contributions-uploads' )->text() |
||
376 | ); |
||
377 | |||
378 | # Other logs link |
||
379 | $tools['logs'] = $linkRenderer->makeKnownLink( |
||
380 | SpecialPage::getTitleFor( 'Log', $username ), |
||
381 | $sp->msg( 'sp-contributions-logs' )->text() |
||
382 | ); |
||
383 | |||
384 | # Add link to deleted user contributions for priviledged users |
||
385 | View Code Duplication | if ( $sp->getUser()->isAllowed( 'deletedhistory' ) ) { |
|
386 | $tools['deletedcontribs'] = $linkRenderer->makeKnownLink( |
||
387 | SpecialPage::getTitleFor( 'DeletedContributions', $username ), |
||
388 | $sp->msg( 'sp-contributions-deleted', $username )->text() |
||
389 | ); |
||
390 | } |
||
391 | |||
392 | # Add a link to change user rights for privileged users |
||
393 | $userrightsPage = new UserrightsPage(); |
||
394 | $userrightsPage->setContext( $sp->getContext() ); |
||
395 | if ( $userrightsPage->userCanChangeRights( $target ) ) { |
||
396 | $tools['userrights'] = $linkRenderer->makeKnownLink( |
||
397 | SpecialPage::getTitleFor( 'Userrights', $username ), |
||
398 | $sp->msg( 'sp-contributions-userrights' )->text() |
||
399 | ); |
||
400 | } |
||
401 | |||
402 | Hooks::run( 'ContributionsToolLinks', [ $id, $userpage, &$tools, $sp ] ); |
||
403 | |||
404 | return $tools; |
||
405 | } |
||
406 | |||
407 | /** |
||
408 | * Generates the namespace selector form with hidden attributes. |
||
409 | * @return string HTML fragment |
||
410 | */ |
||
411 | protected function getForm() { |
||
412 | $this->opts['title'] = $this->getPageTitle()->getPrefixedText(); |
||
413 | if ( !isset( $this->opts['target'] ) ) { |
||
414 | $this->opts['target'] = ''; |
||
415 | } else { |
||
416 | $this->opts['target'] = str_replace( '_', ' ', $this->opts['target'] ); |
||
417 | } |
||
418 | |||
419 | if ( !isset( $this->opts['namespace'] ) ) { |
||
420 | $this->opts['namespace'] = ''; |
||
421 | } |
||
422 | |||
423 | if ( !isset( $this->opts['nsInvert'] ) ) { |
||
424 | $this->opts['nsInvert'] = ''; |
||
425 | } |
||
426 | |||
427 | if ( !isset( $this->opts['associated'] ) ) { |
||
428 | $this->opts['associated'] = false; |
||
429 | } |
||
430 | |||
431 | if ( !isset( $this->opts['contribs'] ) ) { |
||
432 | $this->opts['contribs'] = 'user'; |
||
433 | } |
||
434 | |||
435 | if ( !isset( $this->opts['year'] ) ) { |
||
436 | $this->opts['year'] = ''; |
||
437 | } |
||
438 | |||
439 | if ( !isset( $this->opts['month'] ) ) { |
||
440 | $this->opts['month'] = ''; |
||
441 | } |
||
442 | |||
443 | if ( $this->opts['contribs'] == 'newbie' ) { |
||
444 | $this->opts['target'] = ''; |
||
445 | } |
||
446 | |||
447 | if ( !isset( $this->opts['tagfilter'] ) ) { |
||
448 | $this->opts['tagfilter'] = ''; |
||
449 | } |
||
450 | |||
451 | if ( !isset( $this->opts['topOnly'] ) ) { |
||
452 | $this->opts['topOnly'] = false; |
||
453 | } |
||
454 | |||
455 | if ( !isset( $this->opts['newOnly'] ) ) { |
||
456 | $this->opts['newOnly'] = false; |
||
457 | } |
||
458 | |||
459 | if ( !isset( $this->opts['hideMinor'] ) ) { |
||
460 | $this->opts['hideMinor'] = false; |
||
461 | } |
||
462 | |||
463 | $form = Html::openElement( |
||
464 | 'form', |
||
465 | [ |
||
466 | 'method' => 'get', |
||
467 | 'action' => wfScript(), |
||
468 | 'class' => 'mw-contributions-form' |
||
469 | ] |
||
470 | ); |
||
471 | |||
472 | # Add hidden params for tracking except for parameters in $skipParameters |
||
473 | $skipParameters = [ |
||
474 | 'namespace', |
||
475 | 'nsInvert', |
||
476 | 'deletedOnly', |
||
477 | 'target', |
||
478 | 'contribs', |
||
479 | 'year', |
||
480 | 'month', |
||
481 | 'topOnly', |
||
482 | 'newOnly', |
||
483 | 'hideMinor', |
||
484 | 'associated', |
||
485 | 'tagfilter' |
||
486 | ]; |
||
487 | |||
488 | foreach ( $this->opts as $name => $value ) { |
||
489 | if ( in_array( $name, $skipParameters ) ) { |
||
490 | continue; |
||
491 | } |
||
492 | $form .= "\t" . Html::hidden( $name, $value ) . "\n"; |
||
493 | } |
||
494 | |||
495 | $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] ); |
||
496 | |||
497 | if ( $tagFilter ) { |
||
498 | $filterSelection = Html::rawElement( |
||
499 | 'div', |
||
500 | [], |
||
501 | implode( ' ', $tagFilter ) |
||
502 | ); |
||
503 | } else { |
||
504 | $filterSelection = Html::rawElement( 'div', [], '' ); |
||
505 | } |
||
506 | |||
507 | $this->getOutput()->addModules( 'mediawiki.userSuggest' ); |
||
508 | |||
509 | $labelNewbies = Xml::radioLabel( |
||
510 | $this->msg( 'sp-contributions-newbies' )->text(), |
||
511 | 'contribs', |
||
512 | 'newbie', |
||
513 | 'newbie', |
||
514 | $this->opts['contribs'] == 'newbie', |
||
515 | [ 'class' => 'mw-input' ] |
||
516 | ); |
||
517 | $labelUsername = Xml::radioLabel( |
||
518 | $this->msg( 'sp-contributions-username' )->text(), |
||
519 | 'contribs', |
||
520 | 'user', |
||
521 | 'user', |
||
522 | $this->opts['contribs'] == 'user', |
||
523 | [ 'class' => 'mw-input' ] |
||
524 | ); |
||
525 | $input = Html::input( |
||
526 | 'target', |
||
527 | $this->opts['target'], |
||
528 | 'text', |
||
529 | [ |
||
530 | 'size' => '40', |
||
531 | 'required' => '', |
||
532 | 'class' => [ |
||
533 | 'mw-input', |
||
534 | 'mw-ui-input-inline', |
||
535 | 'mw-autocomplete-user', // used by mediawiki.userSuggest |
||
536 | ], |
||
537 | ] + ( |
||
538 | // Only autofocus if target hasn't been specified or in non-newbies mode |
||
539 | ( $this->opts['contribs'] === 'newbie' || $this->opts['target'] ) |
||
540 | ? [] : [ 'autofocus' => true ] |
||
541 | ) |
||
542 | ); |
||
543 | |||
544 | $targetSelection = Html::rawElement( |
||
545 | 'div', |
||
546 | [], |
||
547 | $labelNewbies . '<br>' . $labelUsername . ' ' . $input . ' ' |
||
548 | ); |
||
549 | |||
550 | $namespaceSelection = Xml::tags( |
||
551 | 'div', |
||
552 | [], |
||
553 | Xml::label( |
||
554 | $this->msg( 'namespace' )->text(), |
||
555 | 'namespace', |
||
556 | '' |
||
557 | ) . ' ' . |
||
558 | Html::namespaceSelector( |
||
559 | [ 'selected' => $this->opts['namespace'], 'all' => '' ], |
||
560 | [ |
||
561 | 'name' => 'namespace', |
||
562 | 'id' => 'namespace', |
||
563 | 'class' => 'namespaceselector', |
||
564 | ] |
||
565 | ) . ' ' . |
||
566 | Html::rawElement( |
||
567 | 'span', |
||
568 | [ 'class' => 'mw-input-with-label' ], |
||
569 | Xml::checkLabel( |
||
570 | $this->msg( 'invert' )->text(), |
||
571 | 'nsInvert', |
||
572 | 'nsInvert', |
||
573 | $this->opts['nsInvert'], |
||
574 | [ |
||
575 | 'title' => $this->msg( 'tooltip-invert' )->text(), |
||
576 | 'class' => 'mw-input' |
||
577 | ] |
||
578 | ) . ' ' |
||
579 | ) . |
||
580 | Html::rawElement( 'span', [ 'class' => 'mw-input-with-label' ], |
||
581 | Xml::checkLabel( |
||
582 | $this->msg( 'namespace_association' )->text(), |
||
583 | 'associated', |
||
584 | 'associated', |
||
585 | $this->opts['associated'], |
||
586 | [ |
||
587 | 'title' => $this->msg( 'tooltip-namespace_association' )->text(), |
||
588 | 'class' => 'mw-input' |
||
589 | ] |
||
590 | ) . ' ' |
||
591 | ) |
||
592 | ); |
||
593 | |||
594 | $filters = []; |
||
595 | |||
596 | if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) { |
||
597 | $filters[] = Html::rawElement( |
||
598 | 'span', |
||
599 | [ 'class' => 'mw-input-with-label' ], |
||
600 | Xml::checkLabel( |
||
601 | $this->msg( 'history-show-deleted' )->text(), |
||
602 | 'deletedOnly', |
||
603 | 'mw-show-deleted-only', |
||
604 | $this->opts['deletedOnly'], |
||
605 | [ 'class' => 'mw-input' ] |
||
606 | ) |
||
607 | ); |
||
608 | } |
||
609 | |||
610 | $filters[] = Html::rawElement( |
||
611 | 'span', |
||
612 | [ 'class' => 'mw-input-with-label' ], |
||
613 | Xml::checkLabel( |
||
614 | $this->msg( 'sp-contributions-toponly' )->text(), |
||
615 | 'topOnly', |
||
616 | 'mw-show-top-only', |
||
617 | $this->opts['topOnly'], |
||
618 | [ 'class' => 'mw-input' ] |
||
619 | ) |
||
620 | ); |
||
621 | $filters[] = Html::rawElement( |
||
622 | 'span', |
||
623 | [ 'class' => 'mw-input-with-label' ], |
||
624 | Xml::checkLabel( |
||
625 | $this->msg( 'sp-contributions-newonly' )->text(), |
||
626 | 'newOnly', |
||
627 | 'mw-show-new-only', |
||
628 | $this->opts['newOnly'], |
||
629 | [ 'class' => 'mw-input' ] |
||
630 | ) |
||
631 | ); |
||
632 | $filters[] = Html::rawElement( |
||
633 | 'span', |
||
634 | [ 'class' => 'mw-input-with-label' ], |
||
635 | Xml::checkLabel( |
||
636 | $this->msg( 'sp-contributions-hideminor' )->text(), |
||
637 | 'hideMinor', |
||
638 | 'mw-hide-minor-edits', |
||
639 | $this->opts['hideMinor'], |
||
640 | [ 'class' => 'mw-input' ] |
||
641 | ) |
||
642 | ); |
||
643 | |||
644 | Hooks::run( |
||
645 | 'SpecialContributions::getForm::filters', |
||
646 | [ $this, &$filters ] |
||
647 | ); |
||
648 | |||
649 | $extraOptions = Html::rawElement( |
||
650 | 'div', |
||
651 | [], |
||
652 | implode( '', $filters ) |
||
653 | ); |
||
654 | |||
655 | $dateSelectionAndSubmit = Xml::tags( 'div', [], |
||
656 | Xml::dateMenu( |
||
657 | $this->opts['year'] === '' ? MWTimestamp::getInstance()->format( 'Y' ) : $this->opts['year'], |
||
658 | $this->opts['month'] |
||
659 | ) . ' ' . |
||
660 | Html::submitButton( |
||
661 | $this->msg( 'sp-contributions-submit' )->text(), |
||
662 | [ 'class' => 'mw-submit' ], [ 'mw-ui-progressive' ] |
||
663 | ) |
||
664 | ); |
||
665 | |||
666 | $form .= Xml::fieldset( |
||
667 | $this->msg( 'sp-contributions-search' )->text(), |
||
668 | $targetSelection . |
||
669 | $namespaceSelection . |
||
670 | $filterSelection . |
||
671 | $extraOptions . |
||
672 | $dateSelectionAndSubmit, |
||
673 | [ 'class' => 'mw-contributions-table' ] |
||
674 | ); |
||
675 | |||
676 | $explain = $this->msg( 'sp-contributions-explain' ); |
||
677 | if ( !$explain->isBlank() ) { |
||
678 | $form .= "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>"; |
||
679 | } |
||
680 | |||
681 | $form .= Xml::closeElement( 'form' ); |
||
682 | |||
683 | return $form; |
||
684 | } |
||
685 | |||
686 | /** |
||
687 | * Return an array of subpages beginning with $search that this special page will accept. |
||
688 | * |
||
689 | * @param string $search Prefix to search for |
||
690 | * @param int $limit Maximum number of results to return (usually 10) |
||
691 | * @param int $offset Number of results to skip (usually 0) |
||
692 | * @return string[] Matching subpages |
||
693 | */ |
||
694 | View Code Duplication | public function prefixSearchSubpages( $search, $limit, $offset ) { |
|
695 | $user = User::newFromName( $search ); |
||
696 | if ( !$user ) { |
||
697 | // No prefix suggestion for invalid user |
||
698 | return []; |
||
699 | } |
||
700 | // Autocomplete subpage as user list - public to allow caching |
||
701 | return UserNamePrefixSearch::search( 'public', $search, $limit, $offset ); |
||
702 | } |
||
703 | |||
704 | protected function getGroupName() { |
||
705 | return 'users'; |
||
706 | } |
||
707 | } |
||
708 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: