r50612 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r50611‎ | r50612 | r50613 >
Date:23:57, 14 May 2009
Author:soxred93
Status:deferred (Comments)
Tags:
Comment:
Branch update:
-class CheckUser is now class SpecialCheckUser, and moved to SpecialCheckUser.php
-check CheckUserApi now created, with barely anything in it.
-new CheckUser class created to serve as backend.

CheckUser is now backend, SpecialCheckUser and CheckUserApi are now frontend.
Modified paths:
  • /branches/new-checkuser/CheckUser.php (modified) (history)
  • /branches/new-checkuser/CheckUser_api.php (added) (history)
  • /branches/new-checkuser/CheckUser_body.php (modified) (history)
  • /branches/new-checkuser/SpecialCheckUser.php (added) (history)

Diff [purge]

Index: branches/new-checkuser/CheckUser_body.php
@@ -6,1282 +6,8 @@
77 }
88
99
10 -class CheckUser extends SpecialPage
 10+class CheckUser
1111 {
12 - function CheckUser() {
13 - global $wgUser;
14 - if ( $wgUser->isAllowed( 'checkuser' ) || !$wgUser->isAllowed( 'checkuser-log' ) ) {
15 - SpecialPage::SpecialPage('CheckUser', 'checkuser');
16 - } else {
17 - SpecialPage::SpecialPage('CheckUser', 'checkuser-log');
18 - }
19 - wfLoadExtensionMessages('CheckUser');
20 - }
21 -
22 - function execute( $subpage ) {
23 - global $wgRequest, $wgOut, $wgUser, $wgContLang;
24 -
25 - $this->setHeaders();
26 - $this->sk = $wgUser->getSkin();
27 -
28 - // This is horribly shitty.
29 - // Lacking formal aliases, it's tough to ensure we have compatibility.
30 - // Links may break, which sucks.
31 - // Language fallbacks will not always be properly utilized.
32 - $logMatches = array(
33 - wfMsgForContent( 'checkuser-log-subpage' ),
34 - 'Log'
35 - );
36 -
37 - foreach( $logMatches as $log ) {
38 - if ( str_replace( '_', ' ', $wgContLang->lc( $subpage ) )
39 - == str_replace( '_ ', ' ', $wgContLang->lc( $log ) ) ) {
40 - if( !$wgUser->isAllowed( 'checkuser-log' ) ) {
41 - $wgOut->permissionRequired( 'checkuser-log' );
42 - return;
43 - }
44 -
45 - $this->showLog();
46 - return;
47 - }
48 - }
49 -
50 - if( !$wgUser->isAllowed( 'checkuser' ) ) {
51 - if ( $wgUser->isAllowed( 'checkuser-log' ) ) {
52 - $wgOut->addWikiText( wfMsg( 'checkuser-summary' ) .
53 - "\n\n[[" . $this->getLogSubpageTitle()->getPrefixedText() .
54 - '|' . wfMsg( 'checkuser-showlog' ) . ']]'
55 - );
56 - return;
57 - }
58 -
59 - $wgOut->permissionRequired( 'checkuser' );
60 - return;
61 - }
62 -
63 - $user = $wgRequest->getText( 'user' ) ?
64 - $wgRequest->getText( 'user' ) : $wgRequest->getText( 'ip' );
65 - $user = trim($user);
66 - $reason = $wgRequest->getText( 'reason' );
67 - $blockreason = $wgRequest->getText( 'blockreason' );
68 - $checktype = $wgRequest->getVal( 'checktype' );
69 - $period = $wgRequest->getInt( 'period' );
70 - $users = $wgRequest->getArray( 'users' );
71 - $tag = $wgRequest->getBool('usetag') ? trim( $wgRequest->getVal( 'tag' ) ) : "";
72 - $talkTag = $wgRequest->getBool('usettag') ? trim( $wgRequest->getVal( 'talktag' ) ) : "";
73 -
74 - # An IPv4? An IPv6? CIDR included?
75 - if( IP::isIPAddress($user) ) {
76 - $ip = IP::sanitizeIP($user);
77 - $name = '';
78 - $xff = '';
79 - # An IPv4/IPv6 XFF string? CIDR included?
80 - } else if( preg_match('/^(.+)\/xff$/',$user,$m) && IP::isIPAddress($m[1]) ) {
81 - $ip = '';
82 - $name = '';
83 - $xff = IP::sanitizeIP($m[1]);
84 - # A user?
85 - } else {
86 - $ip = '';
87 - $name = $user;
88 - $xff = '';
89 - }
90 -
91 - $this->doForm( $user, $reason, $checktype, $ip, $xff, $name, $period );
92 - # Perform one of the various submit operations...
93 - if( $wgRequest->wasPosted() ) {
94 - if( $wgRequest->getVal('action') === 'block' ) {
95 - $this->doMassUserBlock( $users, $blockreason, $tag, $talkTag );
96 - } else if( $checktype=='subuserips' ) {
97 - $this->doUserIPsRequest( $name, $reason, $period );
98 - } else if( $xff && $checktype=='subipedits' ) {
99 - $this->doIPEditsRequest( $xff, true, $reason, $period );
100 - } else if( $checktype=='subipedits' ) {
101 - $this->doIPEditsRequest( $ip, false, $reason, $period );
102 - } else if( $xff && $checktype=='subipusers' ) {
103 - $this->doIPUsersRequest( $xff, true, $reason, $period, $tag, $talkTag );
104 - } else if( $checktype=='subipusers' ) {
105 - $this->doIPUsersRequest( $ip, false, $reason, $period, $tag, $talkTag );
106 - } else if( $checktype=='subuseredits' ) {
107 - $this->doUserEditsRequest( $user, $reason, $period );
108 - }
109 - }
110 - # Add CIDR calculation convenience form
111 - $this->addJsCIDRForm();
112 - $this->addStyles();
113 - }
114 -
115 - /**
116 - * As we use the same small set of messages in various methods and that
117 - * they are called often, we call them once and save them in $this->message
118 - */
119 - protected function preCacheMessages() {
120 - // Precache various messages
121 - if( !isset( $this->message ) ) {
122 - foreach( explode(' ', 'diff hist minoreditletter newpageletter blocklink log' ) as $msg ) {
123 - $this->message[$msg] = wfMsgExt( $msg, array( 'escape') );
124 - }
125 - }
126 - }
127 -
128 - public function getLogSubpageTitle() {
129 - if ( !isset( $this->logSubpageTitle ) ) {
130 - $this->logSubpageTitle = $this->getTitle( wfMsgForContent( 'checkuser-log-subpage' ) );
131 - }
132 - return $this->logSubpageTitle;
133 - }
134 -
135 - protected function doForm( $user, $reason, $checktype, $ip, $xff, $name, $period ) {
136 - global $wgOut, $wgUser;
137 - $action = $this->getTitle()->escapeLocalUrl();
138 - # Fill in requested type if it makes sense
139 - $encipusers = $encipedits = $encuserips = $encuseredits = 0;
140 - if( $checktype=='subipusers' && ( $ip || $xff ) )
141 - $encipusers = 1;
142 - else if( $checktype=='subipedits' && ( $ip || $xff ) )
143 - $encipedits = 1;
144 - else if( $checktype=='subuserips' && $name )
145 - $encuserips = 1;
146 - else if( $checktype=='subuseredits' && $name )
147 - $encuseredits = 1;
148 - # Defaults otherwise
149 - else if( $ip || $xff )
150 - $encipedits = 1;
151 - else
152 - $encuserips = 1;
153 - # Compile our nice form
154 - # User box length should fit things like "2001:0db8:85a3:08d3:1319:8a2e:0370:7344/100/xff"
155 - if( $wgUser->isAllowed( 'checkuser-log' ) ) {
156 - $wgOut->addWikiText( wfMsg( 'checkuser-summary' ) .
157 - "\n\n[[" . $this->getLogSubpageTitle()->getPrefixedText() .
158 - '|' . wfMsg( 'checkuser-showlog' ) . ']]'
159 - );
160 - }
161 - $form = "<form name='checkuserform' id='checkuserform' action=\"$action\" method='post'>";
162 - $form .= "<fieldset><legend>".wfMsgHtml( "checkuser-query" )."</legend>";
163 - $form .= "<table border='0' cellpadding='2'><tr>";
164 - $form .= "<td>".wfMsgHtml( "checkuser-target" ).":</td>";
165 - $form .= "<td>".Xml::input( 'user', 46, $user, array( 'id' => 'checktarget' ) );
166 - $form .= "&nbsp;".$this->getPeriodMenu( $period ) . "</td>";
167 - $form .= "</tr><tr>";
168 - $form .= "<td></td><td class='checkuserradios'><table border='0' cellpadding='3'><tr>";
169 - $form .= "<td>".Xml::radio( 'checktype', 'subuserips', $encuserips, array('id' => 'subuserips') );
170 - $form .= " ".Xml::label( wfMsgHtml("checkuser-ips"), 'subuserips' )."</td>";
171 - $form .= "<td>".Xml::radio( 'checktype', 'subipedits', $encipedits, array('id' => 'subipedits') );
172 - $form .= " ".Xml::label( wfMsgHtml("checkuser-edits"), 'subipedits' )."</td>";
173 - $form .= "<td>".Xml::radio( 'checktype', 'subipusers', $encipusers, array('id' => 'subipusers') );
174 - $form .= " ".Xml::label( wfMsgHtml("checkuser-users"), 'subipusers' )."</td>";
175 - $form .= "<td>".Xml::radio( 'checktype', 'subuseredits', $encuseredits, array('id' => 'subuseredits') );
176 - $form .= " ".Xml::label( wfMsgHtml("checkuser-account"), 'subuseredits' )."</td>";
177 - $form .= "</tr></table></td>";
178 - $form .= "</tr><tr>";
179 - $form .= "<td>".wfMsgHtml( "checkuser-reason" )."</td>";
180 - $form .= "<td>".Xml::input( 'reason', 46, $reason, array( 'maxlength' => '150', 'id' => 'checkreason' ) );
181 - $form .= "&nbsp; &nbsp;".Xml::submitButton( wfMsgHtml('checkuser-check'),
182 - array('id' => 'checkusersubmit','name' => 'checkusersubmit') )."</td>";
183 - $form .= "</tr></table></fieldset></form>";
184 - # Output form
185 - $wgOut->addHTML( $form );
186 - }
187 -
188 - /**
189 - * Add CSS/JS
190 - */
191 - protected function addStyles() {
192 - global $wgScriptPath, $wgCheckUserStyleVersion, $wgOut;
193 - $encJSFile = htmlspecialchars( "$wgScriptPath/extensions/CheckUser/checkuser.js?$wgCheckUserStyleVersion" );
194 - $wgOut->addScript( "<script type=\"text/javascript\" src=\"$encJSFile\"></script>" );
195 - }
196 -
197 - /**
198 - * Get a selector of time period options
199 - * @param int $selected, selected level
200 - */
201 - protected function getPeriodMenu( $selected=null ) {
202 - $s = "<label for='period'>" . wfMsgHtml('checkuser-period') . "</label>&nbsp;";
203 - $s .= Xml::openElement( 'select', array('name' => 'period','id' => 'period','style' => 'margin-top:.2em;') );
204 - $s .= Xml::option( wfMsg( "checkuser-week-1" ), 7, $selected===7 );
205 - $s .= Xml::option( wfMsg( "checkuser-week-2" ), 14, $selected===14 );
206 - $s .= Xml::option( wfMsg( "checkuser-month" ), 31, $selected===31 );
207 - $s .= Xml::option( wfMsg( "checkuser-all" ), 0, $selected===0 );
208 - $s .= Xml::closeElement('select')."\n";
209 - return $s;
210 - }
211 -
212 - /**
213 - * Make a quick JS form for admins to calculate block ranges
214 - */
215 - protected function addJsCIDRForm() {
216 - global $wgOut;
217 - $s = '<fieldset id="mw-checkuser-cidrform" style="display:none; clear:both;">'.
218 - '<legend>'.wfMsgHtml('checkuser-cidr-label').'</legend>';
219 - $s .= '<textarea id="mw-checkuser-iplist" rows="5" cols="50" onkeyup="updateCIDRresult()" onclick="updateCIDRresult()"></textarea><br/>';
220 - $s .= wfMsgHtml('checkuser-cidr-res') . '&nbsp;' .
221 - Xml::input( 'mw-checkuser-cidr-res',35,'',array('id'=>'mw-checkuser-cidr-res') ) .
222 - '&nbsp;<strong id="mw-checkuser-ipnote"></strong>';
223 - $s .= '</fieldset>';
224 - $wgOut->addHTML( $s );
225 - }
226 -
227 - /**
228 - * Block a list of selected users
229 - * @param array $users
230 - * @param string $reason
231 - * @param string $tag
232 - */
233 - protected function doMassUserBlock( $users, $reason = '', $tag = '', $talkTag = '' ) {
234 - global $wgOut, $wgUser, $wgCheckUserMaxBlocks, $wgLang;
235 - if( empty($users) || $wgUser->isBlocked(false) ) {
236 - $wgOut->addWikiText( wfMsgExt('checkuser-block-failure',array('parsemag')) );
237 - return;
238 - } else if( count($users) > $wgCheckUserMaxBlocks ) {
239 - $wgOut->addWikiText( wfMsgExt('checkuser-block-limit',array('parsemag')) );
240 - return;
241 - } else if( !$reason ) {
242 - $wgOut->addWikiText( wfMsgExt('checkuser-block-noreason',array('parsemag')) );
243 - return;
244 - }
245 - $safeUsers = IPBlockForm::doMassUserBlock( $users, $reason, $tag, $talkTag );
246 - if( !empty( $safeUsers ) ) {
247 - $n = count( $safeUsers );
248 - $ulist = $wgLang->listToText( $safeUsers );
249 - $wgOut->addWikiText( wfMsgExt( 'checkuser-block-success', 'parsemag', $ulist, $wgLang->formatNum( $n ) ) );
250 - } else {
251 - $wgOut->addWikiText( wfMsgExt( 'checkuser-block-failure', 'parsemag' ) );
252 - }
253 - }
254 -
255 - protected function noMatchesMessage( $userName ) {
256 - global $wgLang;
257 - $dbr = wfGetDB( DB_SLAVE );
258 - $user_id = User::idFromName($userName);
259 - if( $user_id ) {
260 - $revEdit = $dbr->selectField( 'revision',
261 - 'rev_timestamp',
262 - array( 'rev_user' => $user_id ),
263 - __METHOD__,
264 - array( 'ORDER BY' => 'rev_timestamp DESC')
265 - );
266 - } else {
267 - $revEdit = $dbr->selectField( 'revision',
268 - 'rev_timestamp',
269 - array( 'rev_user_text' => $userName ),
270 - __METHOD__,
271 - array( 'ORDER BY' => 'rev_timestamp DESC')
272 - );
273 - }
274 - $logEdit = 0;
275 - if( $user_id ) {
276 - $logEdit = $dbr->selectField( 'logging',
277 - 'log_timestamp',
278 - array( 'log_user' => $user_id ),
279 - __METHOD__,
280 - array( 'ORDER BY' => 'log_timestamp DESC')
281 - );
282 - }
283 - $lastEdit = max( $revEdit, $logEdit );
284 - if( $lastEdit ) {
285 - $lastEditDate = $wgLang->date( wfTimestamp(TS_MW,$lastEdit), true );
286 - $lastEditTime = $wgLang->time( wfTimestamp(TS_MW,$lastEdit), true );
287 - return wfMsgHtml( 'checkuser-nomatch-edits', $lastEditDate, $lastEditTime );
288 - }
289 - return wfMsgExt('checkuser-nomatch','parse');
290 - }
291 -
292 - /**
293 - * @param string $ip
294 - * @param bool $xfor
295 - * @param string $reason
296 - * Get all IPs used by a user
297 - * Shows first and last date and number of edits
298 - */
299 - protected function doUserIPsRequest( $user , $reason = '', $period = 0 ) {
300 - global $wgOut, $wgLang, $wgUser;
301 -
302 - $userTitle = Title::newFromText( $user, NS_USER );
303 - if( !is_null( $userTitle ) ) {
304 - // normalize the username
305 - $user = $userTitle->getText();
306 - }
307 - # IPs are passed in as a blank string
308 - if( !$user ) {
309 - $wgOut->addWikiMsg( 'nouserspecified' );
310 - return;
311 - }
312 - # Get ID, works better than text as user may have been renamed
313 - $user_id = User::idFromName($user);
314 -
315 - # If user is not IP or nonexistent
316 - if( !$user_id ) {
317 - $s = wfMsgExt('nosuchusershort',array('parse'),$user);
318 - $wgOut->addHTML( $s );
319 - return;
320 - }
321 -
322 - if( !$this->addLogEntry( 'userips', 'user', $user, $reason, $user_id ) ) {
323 - $wgOut->addHTML( '<p>'.wfMsgHtml('checkuser-log-fail').'</p>' );
324 - }
325 - $dbr = wfGetDB( DB_SLAVE );
326 - $time_conds = $this->getTimeConds( $period );
327 - # Ordering by the latest timestamp makes a small filesort on the IP list
328 - $cu_changes = $dbr->tableName( 'cu_changes' );
329 - $use_index = $dbr->useIndexClause( 'cuc_user_ip_time' );
330 - $sql = "SELECT cuc_ip,cuc_ip_hex, COUNT(*) AS count,
331 - MIN(cuc_timestamp) AS first, MAX(cuc_timestamp) AS last
332 - FROM $cu_changes $use_index WHERE cuc_user = $user_id AND $time_conds
333 - GROUP BY cuc_ip,cuc_ip_hex ORDER BY last DESC LIMIT 5001";
334 -
335 - $ret = $dbr->query( $sql, __METHOD__ );
336 - if( !$dbr->numRows( $ret ) ) {
337 - $s = $this->noMatchesMessage($user)."\n";
338 - } else {
339 - $blockip = SpecialPage::getTitleFor( 'blockip' );
340 - $ips_edits = array();
341 - $counter = 0;
342 - while( $row = $dbr->fetchObject($ret) ) {
343 - if( $counter >= 5000 ) {
344 - $wgOut->addHTML( wfMsgExt('checkuser-limited',array('parse')) );
345 - break;
346 - }
347 - $ips_edits[$row->cuc_ip] = $row->count;
348 - $ips_first[$row->cuc_ip] = $row->first;
349 - $ips_last[$row->cuc_ip] = $row->last;
350 - $ips_hex[$row->cuc_ip] = $row->cuc_ip_hex;
351 - ++$counter;
352 - }
353 - // Count pinging might take some time...make sure it is there
354 - wfSuppressWarnings();
355 - set_time_limit(60);
356 - wfRestoreWarnings();
357 -
358 - $logs = SpecialPage::getTitleFor( 'Log' );
359 - $blocklist = SpecialPage::getTitleFor( 'Ipblocklist' );
360 - $s = '<div id="checkuserresults"><ul>';
361 - foreach( $ips_edits as $ip => $edits ) {
362 - $s .= '<li>';
363 - $s .= '<a href="' .
364 - $this->getTitle()->escapeLocalURL( 'user='.urlencode($ip) . '&reason='.urlencode($reason) ) . '">' .
365 - htmlspecialchars($ip) . '</a>';
366 - $s .= ' (<a href="' . $blockip->escapeLocalURL( 'ip='.urlencode($ip) ).'">' .
367 - wfMsgHtml('blocklink') . '</a>)';
368 - if( $ips_first[$ip] == $ips_last[$ip] ) {
369 - $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$ips_first[$ip]), true ) . ') ';
370 - } else {
371 - $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$ips_first[$ip]), true ) .
372 - ' -- ' . $wgLang->timeanddate( wfTimestamp(TS_MW,$ips_last[$ip]), true ) . ') ';
373 - }
374 - $s .= ' <strong>[' . $edits . ']</strong>';
375 -
376 - # If we get some results, it helps to know if the IP in general
377 - # has a lot more edits, e.g. "tip of the iceberg"...
378 - $ipedits = $dbr->estimateRowCount( 'cu_changes', '*',
379 - array( 'cuc_ip_hex' => $ips_hex[$ip] ),
380 - __METHOD__ );
381 - # If small enough, get a more accurate count
382 - if( $ipedits <= 1000 ) {
383 - $ipedits = $dbr->selectField( 'cu_changes', 'COUNT(*)',
384 - array( 'cuc_ip_hex' => $ips_hex[$ip] ),
385 - __METHOD__ );
386 - }
387 - if( $ipedits > $ips_edits[$ip] ) {
388 - $s .= ' <i>(' . wfMsgHtml('checkuser-ipeditcount',$ipedits) . ')</i>';
389 - }
390 -
391 - # If this IP is blocked, give a link to the block log
392 - $block = new Block();
393 - $block->fromMaster( false ); // use slaves
394 - if( $block->load( $ip, 0 ) ) {
395 - if( IP::isIPAddress($block->mAddress) && strpos($block->mAddress,'/') ) {
396 - $userpage = Title::makeTitle( NS_USER, $block->mAddress );
397 - $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml('checkuser-blocked'),
398 - 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
399 - $s .= ' <strong>(' . $blocklog . ' - ' . $block->mAddress . ')</strong>';
400 - } else if( $block->mAuto ) {
401 - $blocklog = $this->sk->makeKnownLinkObj( $blocklist, wfMsgHtml('checkuser-blocked'),
402 - 'ip=' . urlencode( "#$block->mId" ) );
403 - $s .= ' <strong>(' . $blocklog . ')</strong>';
404 - } else {
405 - $userpage = Title::makeTitle( NS_USER, $ip );
406 - $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml('checkuser-blocked'),
407 - 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
408 - $s .= ' <strong>(' . $blocklog . ')</strong>';
409 - }
410 - }
411 - $s .= "<div style='margin-left:5%'>";
412 - $s .= "<small>" . wfMsgExt('checkuser-toollinks',array('parseinline'),urlencode($ip)) . "</small>";
413 - $s .= "</div>";
414 - $s .= "</li>\n";
415 - }
416 - $s .= '</ul></div>';
417 - }
418 - $wgOut->addHTML( $s );
419 - $dbr->freeResult( $ret );
420 - }
421 -
422 - /**
423 - * @param string $ip
424 - * @param bool $xfor
425 - * @param string $reason
426 - * Shows all edits in Recent Changes by this IP (or range) and who made them
427 - */
428 - protected function doIPEditsRequest( $ip, $xfor = false, $reason = '', $period = 0 ) {
429 - global $wgUser, $wgOut, $wgLang;
430 - $dbr = wfGetDB( DB_SLAVE );
431 - # Invalid IPs are passed in as a blank string
432 - $ip_conds = $this->getIpConds( $dbr, $ip, $xfor );
433 - if( !$ip || $ip_conds === false ) {
434 - $wgOut->addWikiMsg( 'badipaddress' );
435 - return;
436 - }
437 -
438 - $logType = 'ipedits';
439 - if( $xfor ) {
440 - $logType .= '-xff';
441 - }
442 - # Record check...
443 - if( !$this->addLogEntry( $logType, 'ip', $ip, $reason ) ) {
444 - $wgOut->addWikiMsg( 'checkuser-log-fail' );
445 - }
446 -
447 - $ip_conds = $dbr->makeList( $ip_conds, LIST_AND );
448 - $time_conds = $this->getTimeConds( $period );
449 - $cu_changes = $dbr->tableName( 'cu_changes' );
450 - # Ordered in descent by timestamp. Can cause large filesorts on range scans.
451 - # Check how many rows will need sorting ahead of time to see if this is too big.
452 - # Also, if we only show 5000, too many will be ignored as well.
453 - $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time';
454 - if( strpos($ip,'/') !== false ) {
455 - # Quick index check only OK if no time constraint
456 - if( $period ) {
457 - $rangecount = $dbr->selectField( 'cu_changes', 'COUNT(*)',
458 - array( $ip_conds, $time_conds ),
459 - __METHOD__,
460 - array( 'USE INDEX' => $index ) );
461 - } else {
462 - $rangecount = $dbr->estimateRowCount( 'cu_changes', '*',
463 - array( $ip_conds ),
464 - __METHOD__,
465 - array( 'USE INDEX' => $index ) );
466 - }
467 - // Sorting might take some time...make sure it is there
468 - wfSuppressWarnings();
469 - set_time_limit(60);
470 - wfRestoreWarnings();
471 - }
472 - $counter = 0;
473 - # See what is best to do after testing the waters...
474 - if( isset($rangecount) && $rangecount > 5000 ) {
475 - $use_index = $dbr->useIndexClause( $index );
476 - $sql = "SELECT cuc_ip_hex, COUNT(*) AS count,
477 - MIN(cuc_timestamp) AS first, MAX(cuc_timestamp) AS last
478 - FROM $cu_changes $use_index
479 - WHERE $ip_conds AND $time_conds
480 - GROUP BY cuc_ip_hex ORDER BY cuc_ip_hex LIMIT 5001";
481 - $ret = $dbr->query( $sql, __METHOD__ );
482 - # List out each IP that has edits
483 - $s = wfMsgExt('checkuser-too-many',array('parse'));
484 - $s .= '<ol>';
485 - while( $row = $ret->fetchObject() ) {
486 - if( $counter >= 5000 ) {
487 - $wgOut->addHTML( wfMsgExt('checkuser-limited',array('parse')) );
488 - break;
489 - }
490 - # Convert the IP hexes into normal form
491 - if( strpos($row->cuc_ip_hex,'v6-') !==false ) {
492 - $ip = substr( $row->cuc_ip_hex, 3 );
493 - $ip = IP::HextoOctet( $ip );
494 - } else {
495 - $ip = long2ip( wfBaseConvert($row->cuc_ip_hex, 16, 10, 8) );
496 - }
497 - $s .= '<li><a href="'.
498 - $this->getTitle()->escapeLocalURL( 'user='.urlencode($ip).'&reason='.urlencode($reason).'&checktype=subipusers' ) .
499 - '">'.$ip.'</a>';
500 - if( $row->first == $row->last ) {
501 - $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->first), true ) . ') ';
502 - } else {
503 - $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->first), true ) .
504 - ' -- ' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->last), true ) . ') ';
505 - }
506 - $s .= " [<strong>" . $row->count . "</strong>]</li>\n";
507 - ++$counter;
508 - }
509 - $s .= '</ol>';
510 - $dbr->freeResult( $ret );
511 -
512 - $wgOut->addHTML( $s );
513 - return;
514 - } else if( isset($rangecount) && !$rangecount ) {
515 - $s = $this->noMatchesMessage($ip)."\n";
516 - $wgOut->addHTML( $s );
517 - return;
518 - }
519 - # OK, do the real query...
520 - $use_index = $dbr->useIndexClause( $index );
521 - $sql = "SELECT cuc_namespace,cuc_title,cuc_user,cuc_user_text,cuc_comment,cuc_actiontext,
522 - cuc_timestamp,cuc_minor,cuc_page_id,cuc_type,cuc_this_oldid,cuc_last_oldid,cuc_ip,cuc_xff,cuc_agent
523 - FROM $cu_changes $use_index WHERE $ip_conds AND $time_conds ORDER BY cuc_timestamp DESC LIMIT 5001";
524 - $ret = $dbr->query( $sql, __METHOD__ );
525 -
526 - if( !$dbr->numRows( $ret ) ) {
527 - $s = $this->noMatchesMessage($ip)."\n";
528 - } else {
529 - # Cache common messages
530 - $this->preCacheMessages();
531 - # Try to optimize this query
532 - $lb = new LinkBatch;
533 - while( $row = $ret->fetchObject() ) {
534 - $userText = str_replace( ' ', '_', $row->cuc_user_text );
535 - $lb->add( $row->cuc_namespace, $row->cuc_title );
536 - $lb->add( NS_USER, $userText );
537 - $lb->add( NS_USER_TALK, $userText );
538 - }
539 - $lb->execute();
540 - $ret->seek( 0 );
541 - # List out the edits
542 - $s = '<div id="checkuserresults">';
543 - while( $row = $ret->fetchObject() ) {
544 - if( $counter >= 5000 ) {
545 - $wgOut->addHTML( wfMsgExt('checkuser-limited',array('parse')) );
546 - break;
547 - }
548 - $s .= $this->CUChangesLine( $row, $reason );
549 - ++$counter;
550 - }
551 - $s .= '</ul></div>';
552 - $dbr->freeResult( $ret );
553 - }
554 -
555 - $wgOut->addHTML( $s );
556 - }
557 -
558 - /**
559 - * @param string $nuser
560 - * @param string $reason
561 - * Shows all edits in Recent Changes by this user
562 - */
563 - protected function doUserEditsRequest( $user, $reason = '', $period = 0 ) {
564 - global $wgUser, $wgOut, $wgLang;
565 -
566 - $userTitle = Title::newFromText( $user, NS_USER );
567 - if( !is_null( $userTitle ) ) {
568 - // normalize the username
569 - $user = $userTitle->getText();
570 - }
571 - # IPs are passed in as a blank string
572 - if( !$user ) {
573 - $wgOut->addWikiMsg( 'nouserspecified' );
574 - return;
575 - }
576 - # Get ID, works better than text as user may have been renamed
577 - $user_id = User::idFromName($user);
578 -
579 - # If user is not IP or nonexistent
580 - if( !$user_id ) {
581 - $s = wfMsgExt('nosuchusershort',array('parse'),$user);
582 - $wgOut->addHTML( $s );
583 - return;
584 - }
585 -
586 - # Record check...
587 - if( !$this->addLogEntry( 'useredits', 'user', $user, $reason, $user_id ) ) {
588 - $wgOut->addHTML( '<p>'.wfMsgHtml('checkuser-log-fail').'</p>' );
589 - }
590 -
591 - $dbr = wfGetDB( DB_SLAVE );
592 - $user_cond = "cuc_user = '$user_id'";
593 - $time_conds = $this->getTimeConds( $period );
594 - $cu_changes = $dbr->tableName( 'cu_changes' );
595 - # Ordered in descent by timestamp. Causes large filesorts if there are many edits.
596 - # Check how many rows will need sorting ahead of time to see if this is too big.
597 - # If it is, sort by IP,time to avoid the filesort.
598 - if( $period ) {
599 - $count = $dbr->selectField( 'cu_changes', 'COUNT(*)',
600 - array( $user_cond, $time_conds ),
601 - __METHOD__,
602 - array( 'USE INDEX' => 'cuc_user_ip_time' ) );
603 - } else {
604 - $count = $dbr->estimateRowCount( 'cu_changes', '*',
605 - array( $user_cond, $time_conds ),
606 - __METHOD__,
607 - array( 'USE INDEX' => 'cuc_user_ip_time' ) );
608 - }
609 - # Cache common messages
610 - $this->preCacheMessages();
611 - # See what is best to do after testing the waters...
612 - if( $count > 5000 ) {
613 - $wgOut->addHTML( wfMsgExt('checkuser-limited',array('parse')) );
614 - $use_index = $dbr->useIndexClause( 'cuc_user_ip_time' );
615 - $sql = "SELECT * FROM $cu_changes $use_index
616 - WHERE $user_cond AND $time_conds
617 - ORDER BY cuc_ip ASC, cuc_timestamp DESC LIMIT 5000";
618 - $ret = $dbr->query( $sql, __METHOD__ );
619 - # Try to optimize this query
620 - $lb = new LinkBatch;
621 - while( $row = $ret->fetchObject() ) {
622 - $lb->add( $row->cuc_namespace, $row->cuc_title );
623 - }
624 - $lb->execute();
625 - $ret->seek( 0 );
626 - $s = '';
627 - while( $row = $ret->fetchObject() ) {
628 - if( !$ip = htmlspecialchars($row->cuc_ip) ) {
629 - continue;
630 - }
631 - if( !isset($lastIP) ) {
632 - $lastIP = $row->cuc_ip;
633 - $s .= "\n<h2>$ip</h2>\n<div class=\"special\">";
634 - } else if( $lastIP != $row->cuc_ip ) {
635 - $s .= "</ul></div>\n<h2>$ip</h2>\n<div class=\"special\">";
636 - $lastIP = $row->cuc_ip;
637 - unset($this->lastdate); // start over
638 - }
639 - $s .= $this->CUChangesLine( $row, $reason );
640 - }
641 - $s .= '</ul></div>';
642 - $dbr->freeResult( $ret );
643 -
644 - $wgOut->addHTML( $s );
645 - return;
646 - }
647 - // Sorting might take some time...make sure it is there
648 - wfSuppressWarnings();
649 - set_time_limit(60);
650 - wfRestoreWarnings();
651 - # OK, do the real query...
652 - $use_index = $dbr->useIndexClause( 'cuc_user_ip_time' );
653 - $sql = "SELECT * FROM $cu_changes $use_index
654 - WHERE $user_cond AND $time_conds ORDER BY cuc_timestamp DESC LIMIT 5000";
655 - $ret = $dbr->query( $sql, __METHOD__ );
656 -
657 - if( !$dbr->numRows( $ret ) ) {
658 - $s = $this->noMatchesMessage($user)."\n";
659 - } else {
660 - # Try to optimize this query
661 - $lb = new LinkBatch;
662 - while( $row = $ret->fetchObject() ) {
663 - $lb->add( $row->cuc_namespace, $row->cuc_title );
664 - }
665 - $lb->execute();
666 - $ret->seek( 0 );
667 - # List out the edits
668 - $s = '<div id="checkuserresults">';
669 - while( $row = $ret->fetchObject() ) {
670 - $s .= $this->CUChangesLine( $row, $reason );
671 - }
672 - $s .= '</ul></div>';
673 - $dbr->freeResult( $ret );
674 - }
675 -
676 - $wgOut->addHTML( $s );
677 - }
678 -
679 - /**
680 - * @param string $ip
681 - * @param bool $xfor
682 - * @param string $reason
683 - * @param int $period
684 - * @param string $tag
685 - * @param string $talkTag
686 - * Lists all users in recent changes who used an IP, newest to oldest down
687 - * Outputs usernames, latest and earliest found edit date, and count
688 - * List unique IPs used for each user in time order, list corresponding user agent
689 - */
690 - protected function doIPUsersRequest( $ip, $xfor = false, $reason = '', $period = 0, $tag='', $talkTag='' ) {
691 - global $wgUser, $wgOut, $wgLang;
692 - $dbr = wfGetDB( DB_SLAVE );
693 - # Invalid IPs are passed in as a blank string
694 - $ip_conds = $this->getIpConds( $dbr, $ip, $xfor );
695 - if( !$ip || $ip_conds === false ) {
696 - $wgOut->addWikiMsg( 'badipaddress' );
697 - return;
698 - }
699 -
700 - $logType = 'ipusers';
701 - if( $xfor ) {
702 - $logType .= '-xff';
703 - }
704 - # Log the check...
705 - if( !$this->addLogEntry( $logType, 'ip', $ip, $reason ) ) {
706 - $wgOut->addHTML( '<p>'.wfMsgHtml('checkuser-log-fail').'</p>' );
707 - }
708 -
709 - $ip_conds = $dbr->makeList( $ip_conds, LIST_AND );
710 - $time_conds = $this->getTimeConds( $period );
711 - $cu_changes = $dbr->tableName( 'cu_changes' );
712 - $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time';
713 - # Ordered in descent by timestamp. Can cause large filesorts on range scans.
714 - # Check how many rows will need sorting ahead of time to see if this is too big.
715 - if( strpos($ip,'/') !==false ) {
716 - # Quick index check only OK if no time constraint
717 - if( $period ) {
718 - $rangecount = $dbr->selectField( 'cu_changes', 'COUNT(*)',
719 - array( $ip_conds, $time_conds ),
720 - __METHOD__,
721 - array( 'USE INDEX' => $index ) );
722 - } else {
723 - $rangecount = $dbr->estimateRowCount( 'cu_changes', '*',
724 - array( $ip_conds ),
725 - __METHOD__,
726 - array( 'USE INDEX' => $index ) );
727 - }
728 - // Sorting might take some time...make sure it is there
729 - wfSuppressWarnings();
730 - set_time_limit(120);
731 - wfRestoreWarnings();
732 - }
733 - // Are there too many edits?
734 - if( isset($rangecount) && $rangecount > 10000 ) {
735 - $use_index = $dbr->useIndexClause( $index );
736 - $sql = "SELECT cuc_ip_hex, COUNT(*) AS count,
737 - MIN(cuc_timestamp) AS first, MAX(cuc_timestamp) AS last
738 - FROM $cu_changes $use_index WHERE $ip_conds AND $time_conds
739 - GROUP BY cuc_ip_hex ORDER BY cuc_ip_hex LIMIT 5001";
740 - $ret = $dbr->query( $sql, __METHOD__ );
741 - # List out each IP that has edits
742 - $s = '<h5>' . wfMsg('checkuser-too-many') . '</h5>';
743 - $s .= '<ol>';
744 - $counter = 0;
745 - while( $row = $ret->fetchObject() ) {
746 - if( $counter >= 5000 ) {
747 - $wgOut->addHTML( wfMsgExt('checkuser-limited',array('parse')) );
748 - break;
749 - }
750 - # Convert the IP hexes into normal form
751 - if( strpos($row->cuc_ip_hex,'v6-') !==false ) {
752 - $ip = substr( $row->cuc_ip_hex, 3 );
753 - $ip = IP::HextoOctet( $ip );
754 - } else {
755 - $ip = long2ip( wfBaseConvert($row->cuc_ip_hex, 16, 10, 8) );
756 - }
757 - $s .= '<li><a href="'.
758 - $this->getTitle()->escapeLocalURL( 'user='.urlencode($ip).'&reason='.urlencode($reason).'&checktype=subipusers' ) .
759 - '">'.$ip.'</a>';
760 - if( $row->first == $row->last ) {
761 - $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->first), true ) . ') ';
762 - } else {
763 - $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->first), true ) .
764 - ' -- ' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->last), true ) . ') ';
765 - }
766 - $s .= " [<strong>" . $row->count . "</strong>]</li>\n";
767 - ++$counter;
768 - }
769 - $s .= '</ol>';
770 - $dbr->freeResult( $ret );
771 -
772 - $wgOut->addHTML( $s );
773 - return;
774 - } else if( isset($rangecount) && !$rangecount ) {
775 - $s = $this->noMatchesMessage($ip)."\n";
776 - $wgOut->addHTML( $s );
777 - return;
778 - }
779 -
780 - global $wgMemc;
781 - # OK, do the real query...
782 - $use_index = $dbr->useIndexClause( $index );
783 - $sql = "SELECT cuc_user_text, cuc_timestamp, cuc_user, cuc_ip, cuc_agent, cuc_xff
784 - FROM $cu_changes $use_index WHERE $ip_conds AND $time_conds
785 - ORDER BY cuc_timestamp DESC LIMIT 10000";
786 - $ret = $dbr->query( $sql, __METHOD__ );
787 -
788 - $users_first = $users_last = $users_edits = $users_ids = array();
789 - if( !$dbr->numRows( $ret ) ) {
790 - $s = $this->noMatchesMessage($ip)."\n";
791 - } else {
792 - global $wgAuth;
793 - while( ($row = $dbr->fetchObject($ret) ) != false ) {
794 - if( !array_key_exists( $row->cuc_user_text, $users_edits ) ) {
795 - $users_last[$row->cuc_user_text] = $row->cuc_timestamp;
796 - $users_edits[$row->cuc_user_text] = 0;
797 - $users_ids[$row->cuc_user_text] = $row->cuc_user;
798 - $users_infosets[$row->cuc_user_text] = array();
799 - $users_agentsets[$row->cuc_user_text] = array();
800 - }
801 - $users_edits[$row->cuc_user_text] += 1;
802 - $users_first[$row->cuc_user_text] = $row->cuc_timestamp;
803 - # Treat blank or NULL xffs as empty strings
804 - $xff = empty($row->cuc_xff) ? null : $row->cuc_xff;
805 - $xff_ip_combo = array( $row->cuc_ip, $xff );
806 - # Add this IP/XFF combo for this username if it's not already there
807 - if( !in_array($xff_ip_combo,$users_infosets[$row->cuc_user_text]) ) {
808 - $users_infosets[$row->cuc_user_text][] = $xff_ip_combo;
809 - }
810 - # Add this agent string if it's not already there; 10 max.
811 - if( count($users_agentsets[$row->cuc_user_text]) < 10 ) {
812 - if( !in_array($row->cuc_agent,$users_agentsets[$row->cuc_user_text]) ) {
813 - $users_agentsets[$row->cuc_user_text][] = $row->cuc_agent;
814 - }
815 - }
816 - }
817 - $dbr->freeResult( $ret );
818 -
819 - $logs = SpecialPage::getTitleFor( 'Log' );
820 - $blocklist = SpecialPage::getTitleFor( 'Ipblocklist' );
821 -
822 - $action = $this->getTitle()->escapeLocalUrl( 'action=block' );
823 - $s = "<form name='checkuserblock' id='checkuserblock' action=\"$action\" method='post'>";
824 - $s .= '<div id="checkuserresults"><ul>';
825 - foreach( $users_edits as $name => $count ) {
826 - $s .= '<li>';
827 - $s .= Xml::check( 'users[]', false, array( 'value' => $name ) ) . '&nbsp;';
828 - # Load user object
829 - $user = User::newFromName( $name, false );
830 - # Add user tool links
831 - $s .= $this->sk->userLink( -1 , $name ) . $this->sk->userToolLinks( -1 , $name );
832 - # Add CheckUser link
833 - $s .= ' (<a href="' . $this->getTitle()->escapeLocalURL( 'user='.urlencode($name) .
834 - '&reason='.urlencode($reason) ) . '">' . wfMsgHtml('checkuser-check') . '</a>)';
835 - # Show edit time range
836 - if( $users_first[$name] == $users_last[$name] ) {
837 - $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$users_first[$name]), true ) . ') ';
838 - } else {
839 - $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$users_first[$name]), true ) .
840 - ' -- ' . $wgLang->timeanddate( wfTimestamp(TS_MW,$users_last[$name]), true ) . ') ';
841 - }
842 - # Total edit count
843 - $s .= ' [<strong>' . $count . '</strong>]<br />';
844 - # Check if this user or IP is blocked. If so, give a link to the block log...
845 - $block = new Block();
846 - $block->fromMaster( false ); // use slaves
847 - $ip = IP::isIPAddress( $name ) ? $name : '';
848 - $flags = array();
849 - if( $block->load( $ip, $users_ids[$name] ) ) {
850 - // Range blocked?
851 - if( IP::isIPAddress($block->mAddress) && strpos($block->mAddress,'/') ) {
852 - $userpage = Title::makeTitle( NS_USER, $block->mAddress );
853 - $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml('checkuser-blocked'),
854 - 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
855 - $flags[] = '<strong>(' . $blocklog . ' - ' . $block->mAddress . ')</strong>';
856 - // Auto blocked?
857 - } else if( $block->mAuto ) {
858 - $blocklog = $this->sk->makeKnownLinkObj( $blocklist,
859 - wfMsgHtml('checkuser-blocked'), 'ip=' . urlencode( "#{$block->mId}" ) );
860 - $flags[] = '<strong>(' . $blocklog . ')</strong>';
861 - } else {
862 - $userpage = Title::makeTitle( NS_USER, $name );
863 - $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml('checkuser-blocked'),
864 - 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
865 - $flags[] = '<strong>(' . $blocklog . ')</strong>';
866 - }
867 - // IP that is blocked on all wikis?
868 - } else if( $ip === $name && $user->isBlockedGlobally( $ip ) ) {
869 - $flags[] = '<strong>(' . wfMsgHtml('checkuser-gblocked') . ')</strong>';
870 - } else if( self::userWasBlocked( $name ) ) {
871 - $userpage = Title::makeTitle( NS_USER, $name );
872 - $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml('checkuser-wasblocked'),
873 - 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
874 - $flags[] = '<strong>(' . $blocklog . ')</strong>';
875 - }
876 - # Show if account is local only
877 - $authUser = $wgAuth->getUserInstance( $user );
878 - if( $user->getId() && $authUser->getId() === 0 ) {
879 - $flags[] = '<strong>(' . wfMsgHtml('checkuser-localonly') . ')</strong>';
880 - }
881 - # Check for extra user rights...
882 - if( $users_ids[$name] ) {
883 - $user = User::newFromId( $users_ids[$name] );
884 - if( $user->isLocked() ) {
885 - $flags[] = '<b>(' . wfMsgHtml('checkuser-locked') . ')</b>';
886 - }
887 - $list = array();
888 - foreach( $user->getGroups() as $group ) {
889 - $list[] = self::buildGroupLink( $group );
890 - }
891 - $groups = $wgLang->commaList( $list );
892 - if( $groups ) {
893 - $flags[] = '<i>(' . $groups . ')</i>';
894 - }
895 - }
896 - # Check how many accounts the user made recently?
897 - if( $ip ) {
898 - $key = wfMemcKey( 'acctcreate', 'ip', $ip );
899 - $count = intval( $wgMemc->get( $key ) );
900 - if( $count ) {
901 - $flags[] = '<strong>[' . wfMsgExt( 'checkuser-accounts', 'parsemag', $wgLang->formatNum( $count ) ) . ']</strong>';
902 - }
903 - }
904 - $s .= implode(' ',$flags);
905 - $s .= '<ol>';
906 - # List out each IP/XFF combo for this username
907 - for( $i = (count($users_infosets[$name]) - 1); $i >= 0; $i-- ) {
908 - $set = $users_infosets[$name][$i];
909 - # IP link
910 - $s .= '<li>';
911 - $s .= '<a href="'.$this->getTitle()->escapeLocalURL( 'user='.urlencode($set[0]) ).'">'.htmlspecialchars($set[0]).'</a>';
912 - # XFF string, link to /xff search
913 - if( $set[1] ) {
914 - # Flag our trusted proxies
915 - list($client,$trusted) = efGetClientIPfromXFF($set[1],$set[0]);
916 - $c = $trusted ? '#F0FFF0' : '#FFFFCC';
917 - $s .= '&nbsp;&nbsp;&nbsp;<span style="background-color: '.$c.'"><strong>XFF</strong>: ';
918 - $s .= $this->sk->makeKnownLinkObj( $this->getTitle(),
919 - htmlspecialchars( $set[1] ),
920 - "user=" . urlencode( $client ) . "/xff" )."</span>";
921 - }
922 - $s .= "</li>\n";
923 - }
924 - $s .= '</ol><br /><ol>';
925 - # List out each agent for this username
926 - for( $i = (count($users_agentsets[$name]) - 1); $i >= 0; $i-- ) {
927 - $agent = $users_agentsets[$name][$i];
928 - $s .= "<li><i>" . htmlspecialchars($agent) . "</i></li>\n";
929 - }
930 - $s .= '</ol>';
931 - $s .= '</li>';
932 - }
933 - $s .= "</ul></div>\n";
934 - if( $wgUser->isAllowed('block') && !$wgUser->isBlocked() ) {
935 - $s .= "<fieldset>\n";
936 - $s .= "<legend>" . wfMsgHtml('checkuser-massblock') . "</legend>\n";
937 - $s .= "<p>" . wfMsgExt('checkuser-massblock-text',array('parseinline')) . "</p>\n";
938 - $s .= '<table><tr>' .
939 - '<td>' . Xml::check( 'usetag', false, array('id' => 'usetag') ) . '</td>' .
940 - '<td>' . Xml::label( wfMsgHtml( "checkuser-blocktag" ), 'usetag' ) . '</td>' .
941 - '<td>' . Xml::input( 'tag', 46, $tag, array('id' => 'blocktag') ) . '</td>' .
942 - '</tr><tr>' .
943 - '<td>' . Xml::check( 'usettag', false, array('id' => 'usettag') ) . '</td>' .
944 - '<td>' . Xml::label( wfMsgHtml( "checkuser-blocktag-talk" ), 'usettag' ) . '</td>' .
945 - '<td>' . Xml::input( 'talktag', 46, $talkTag, array('id' => 'talktag') ).'</td>'.
946 - '</tr></table>';
947 - $s .= "<p>" . wfMsgHtml( "checkuser-reason" ) . '&nbsp;';
948 - $s .= Xml::input( 'blockreason', 46, '', array( 'maxlength' => '150', 'id' => 'blockreason' ) );
949 - $s .= '&nbsp;' . Xml::submitButton( wfMsgHtml('checkuser-massblock-commit'),
950 - array('id' => 'checkuserblocksubmit','name' => 'checkuserblock') ) . "</p>\n";
951 - $s .= "</fieldset>\n";
952 - }
953 - $s .= '</form>';
954 - }
955 -
956 - $wgOut->addHTML( $s );
957 - }
958 -
959 - /**
960 - * @param $row
961 - * @return a streamlined recent changes line with IP data
962 - */
963 - protected function CUChangesLine( $row, $reason ) {
964 - global $wgLang;
965 - # Add date headers
966 - $date = $wgLang->date( wfTimestamp(TS_MW,$row->cuc_timestamp), true, true );
967 - if( !isset($this->lastdate) ) {
968 - $this->lastdate = $date;
969 - $line = "\n<h4>$date</h4>\n<ul class=\"special\">";
970 - } else if( $date != $this->lastdate ) {
971 - $line = "</ul>\n<h4>$date</h4>\n<ul class=\"special\">";
972 - $this->lastdate = $date;
973 - } else {
974 - $line = '';
975 - }
976 - $line .= "<li>";
977 - # Create diff/hist/page links
978 - $line .= $this->getLinksFromRow( $row );
979 - # Show date
980 - $line .= ' . . ' . $wgLang->time( wfTimestamp(TS_MW,$row->cuc_timestamp), true, true ) . ' . . ';
981 - # Userlinks
982 - $line .= $this->sk->userLink( $row->cuc_user, $row->cuc_user_text );
983 - $line .= $this->sk->userToolLinks( $row->cuc_user, $row->cuc_user_text );
984 - # Action text, hackish ...
985 - if( $row->cuc_actiontext )
986 - $line .= ' ' . $this->sk->formatComment( $row->cuc_actiontext ) . ' ';
987 - # Comment
988 - $line .= $this->sk->commentBlock( $row->cuc_comment );
989 -
990 - $cuTitle = SpecialPage::getTitleFor( 'CheckUser' );
991 - $line .= '<br />&nbsp; &nbsp; &nbsp; &nbsp; <small>';
992 - # IP
993 - $line .= ' <strong>IP</strong>: '.$this->sk->makeKnownLinkObj( $cuTitle,
994 - htmlspecialchars( $row->cuc_ip ),
995 - "user=".urlencode( $row->cuc_ip ).'&reason='.urlencode($reason) );
996 - # XFF
997 - if( $row->cuc_xff !=null ) {
998 - # Flag our trusted proxies
999 - list($client,$trusted) = efGetClientIPfromXFF($row->cuc_xff,$row->cuc_ip);
1000 - $c = $trusted ? '#F0FFF0' : '#FFFFCC';
1001 - $line .= '&nbsp;&nbsp;&nbsp;<span class="mw-checkuser-xff" style="background-color: '.$c.'">'.
1002 - '<strong>XFF</strong>: ';
1003 - $line .= $this->sk->makeKnownLinkObj( $cuTitle,
1004 - htmlspecialchars( $row->cuc_xff ),
1005 - "user=".urlencode($client)."/xff&reason=".urlencode($reason) )."</span>";
1006 - }
1007 - # User agent
1008 - $line .= '&nbsp;&nbsp;&nbsp;<span class="mw-checkuser-agent" style="color:#888;">' .
1009 - htmlspecialchars( $row->cuc_agent )."</span>";
1010 -
1011 - $line .= "</small></li>\n";
1012 -
1013 - return $line;
1014 - }
1015 -
1016 - /**
1017 - * @param $row
1018 - * @create diff/hist/page link
1019 - */
1020 - protected function getLinksFromRow( $row ) {
1021 - // Log items (old format) and events to logs
1022 - if( $row->cuc_type == RC_LOG && $row->cuc_namespace == NS_SPECIAL ) {
1023 - list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $row->cuc_title );
1024 - $logname = LogPage::logName( $logtype );
1025 - $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
1026 - $links = '(' . $this->sk->makeKnownLinkObj( $title, $logname ) . ')';
1027 - // Log items
1028 - } elseif( $row->cuc_type == RC_LOG ) {
1029 - $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
1030 - $links = '(' . $this->sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), $this->message['log'],
1031 - wfArrayToCGI( array('page' => $title->getPrefixedText() ) ) ) . ')';
1032 - } else {
1033 - $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
1034 - # New pages
1035 - if( $row->cuc_type == RC_NEW ) {
1036 - $links = '(' . $this->message['diff'] . ') ';
1037 - } else {
1038 - # Diff link
1039 - $links = ' (' . $this->sk->makeKnownLinkObj( $title, $this->message['diff'],
1040 - wfArrayToCGI( array(
1041 - 'curid' => $row->cuc_page_id,
1042 - 'diff' => $row->cuc_this_oldid,
1043 - 'oldid' => $row->cuc_last_oldid ) ) ) . ') ';
1044 - }
1045 - # History link
1046 - $links .= ' (' . $this->sk->makeKnownLinkObj( $title, $this->message['hist'],
1047 - wfArrayToCGI( array(
1048 - 'curid' => $row->cuc_page_id,
1049 - 'action' => 'history' ) ) ) . ') . . ';
1050 - # Some basic flags
1051 - if( $row->cuc_type == RC_NEW )
1052 - $links .= '<span class="newpage">' . $this->message['newpageletter'] . '</span>';
1053 - if( $row->cuc_minor )
1054 - $links .= '<span class="minor">' . $this->message['minoreditletter'] . '</span>';
1055 - # Page link
1056 - $links .= ' ' . $this->sk->makeLinkObj( $title );
1057 - }
1058 - return $links;
1059 - }
1060 -
1061 - protected static function userWasBlocked( $name ) {
1062 - $userpage = Title::makeTitle( NS_USER, $name );
1063 - return wfGetDB( DB_SLAVE )->selectField( 'logging', '1',
1064 - array( 'log_type' => array('block','suppress'),
1065 - 'log_action' => 'block',
1066 - 'log_namespace' => $userpage->getNamespace(),
1067 - 'log_title' => $userpage->getDBKey() ),
1068 - __METHOD__,
1069 - array( 'USE INDEX' => 'page_time' ) );
1070 - }
1071 -
1072 - /**
1073 - * Format a link to a group description page
1074 - *
1075 - * @param string $group
1076 - * @return string
1077 - */
1078 - protected static function buildGroupLink( $group ) {
1079 - static $cache = array();
1080 - if( !isset( $cache[$group] ) )
1081 - $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) );
1082 - return $cache[$group];
1083 - }
1084 -
1085 - /**
1086 - * @param Database $db
1087 - * @param string $ip
1088 - * @param string $xfor
1089 - * @return mixed array/false conditions
1090 - */
1091 - protected function getIpConds( $db, $ip, $xfor = false ) {
1092 - $type = ( $xfor ) ? 'xff' : 'ip';
1093 - // IPv4 CIDR, 16-32 bits
1094 - if( preg_match( '#^(\d+\.\d+\.\d+\.\d+)/(\d+)$#', $ip, $matches ) ) {
1095 - if( $matches[2] < 16 || $matches[2] > 32 )
1096 - return false; // invalid
1097 - list( $start, $end ) = IP::parseRange( $ip );
1098 - return array( 'cuc_'.$type.'_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) );
1099 - } else if( preg_match( '#^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}/(\d+)$#', $ip, $matches ) ) {
1100 - // IPv6 CIDR, 96-128 bits
1101 - if( $matches[1] < 96 || $matches[1] > 128 )
1102 - return false; // invalid
1103 - list( $start, $end ) = IP::parseRange6( $ip );
1104 - return array( 'cuc_'.$type.'_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) );
1105 - } else if( preg_match( '#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#', $ip ) ) {
1106 - // 32 bit IPv4
1107 - $ip_hex = IP::toHex( $ip );
1108 - return array( 'cuc_'.$type.'_hex' => $ip_hex );
1109 - } else if( preg_match( '#^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}$#', $ip ) ) {
1110 - // 128 bit IPv6
1111 - $ip_hex = IP::toHex( $ip );
1112 - return array( 'cuc_'.$type.'_hex' => $ip_hex );
1113 - }
1114 - // throw away this query, incomplete IP, these don't get through the entry point anyway
1115 - return false; // invalid
1116 - }
1117 -
1118 - protected function getTimeConds( $period ) {
1119 - if( !$period ) {
1120 - return "1 = 1";
1121 - }
1122 - $dbr = wfGetDB( DB_SLAVE );
1123 - $cutoff_unixtime = time() - ($period * 24 * 3600);
1124 - $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400);
1125 - $cutoff = $dbr->addQuotes( $dbr->timestamp( $cutoff_unixtime ) );
1126 - return "cuc_timestamp > $cutoff";
1127 - }
1128 -
1129 - protected function showLog() {
1130 - global $wgRequest, $wgOut;
1131 - $type = $wgRequest->getVal( 'cuSearchType' );
1132 - $target = $wgRequest->getVal( 'cuSearch' );
1133 - $year = $wgRequest->getIntOrNull( 'year' );
1134 - $month = $wgRequest->getIntOrNull( 'month' );
1135 - $error = false;
1136 - $dbr = wfGetDB( DB_SLAVE );
1137 - $searchConds = false;
1138 -
1139 - $wgOut->setPageTitle( wfMsg( 'checkuser-log' ) );
1140 -
1141 - $wgOut->addHTML( $this->sk->makeKnownLinkObj( $this->getTitle(), wfMsgHtml( 'checkuser-log-return' ) ) );
1142 -
1143 - if ( $type === null ) {
1144 - $type = 'target';
1145 - } elseif ( $type == 'initiator' ) {
1146 - $user = User::newFromName( $target );
1147 - if ( !$user || !$user->getID() ) {
1148 - $error = 'checkuser-user-nonexistent';
1149 - } else {
1150 - $searchConds = array( 'cul_user' => $user->getID() );
1151 - }
1152 - } else /* target */ {
1153 - $type = 'target';
1154 - // Is it an IP?
1155 - list( $start, $end ) = IP::parseRange( $target );
1156 - if ( $start !== false ) {
1157 - if ( $start == $end ) {
1158 - $searchConds = array( 'cul_target_hex = ' . $dbr->addQuotes( $start ) . ' OR ' .
1159 - '(cul_range_end >= ' . $dbr->addQuotes( $start ) . ' AND ' .
1160 - 'cul_range_start <= ' . $dbr->addQuotes( $end ) . ')'
1161 - );
1162 - } else {
1163 - $searchConds = array(
1164 - '(cul_target_hex >= ' . $dbr->addQuotes( $start ) . ' AND ' .
1165 - 'cul_target_hex <= ' . $dbr->addQuotes( $end ) . ') OR ' .
1166 - '(cul_range_end >= ' . $dbr->addQuotes( $start ) . ' AND ' .
1167 - 'cul_range_start <= ' . $dbr->addQuotes( $end ) . ')'
1168 - );
1169 - }
1170 - } else {
1171 - // Is it a user?
1172 - $user = User::newFromName( $target );
1173 - if ( $user && $user->getID() ) {
1174 - $searchConds = array(
1175 - 'cul_type' => 'userips',
1176 - 'cul_target_id' => $user->getID(),
1177 - );
1178 - } else if ( $target ) {
1179 - $error = 'checkuser-user-nonexistent';
1180 - }
1181 - }
1182 - }
1183 -
1184 - $searchTypes = array( 'initiator', 'target' );
1185 - $select = "<select name=\"cuSearchType\" style='margin-top:.2em;'>\n";
1186 - foreach ( $searchTypes as $searchType ) {
1187 - if ( $type == $searchType ) {
1188 - $checked = 'selected="selected"';
1189 - } else {
1190 - $checked = '';
1191 - }
1192 - $caption = wfMsgHtml( 'checkuser-search-' . $searchType );
1193 - $select .= "<option value=\"$searchType\" $checked>$caption</option>\n";
1194 - }
1195 - $select .= "</select>";
1196 -
1197 - $encTarget = htmlspecialchars( $target );
1198 - $msgSearch = wfMsgHtml( 'checkuser-search' );
1199 - $input = "<input type=\"text\" name=\"cuSearch\" value=\"$encTarget\" size=\"40\"/>";
1200 - $msgSearchForm = wfMsgHtml( 'checkuser-search-form', $select, $input );
1201 - $formAction = $this->getLogSubpageTitle()->escapeLocalURL();
1202 - $msgSearchSubmit = '&nbsp;&nbsp;' . wfMsgHtml( 'checkuser-search-submit' ) . '&nbsp;&nbsp;';
1203 -
1204 - $s = "<form method='get' action=\"$formAction\">\n" .
1205 - "<fieldset><legend>$msgSearch</legend>\n" .
1206 - "<p>$msgSearchForm</p>\n" .
1207 - "<p>" . $this->getDateMenu( $year, $month ) . "&nbsp;&nbsp;&nbsp;\n" .
1208 - "<input type=\"submit\" name=\"cuSearchSubmit\" value=\"$msgSearchSubmit\"/></p>\n" .
1209 - "</fieldset></form>\n";
1210 - $wgOut->addHTML( $s );
1211 -
1212 - if ( $error !== false ) {
1213 - $wgOut->addWikiText( '<div class="errorbox">' . wfMsg( $error ) . '</div>' );
1214 - return;
1215 - }
1216 -
1217 - $pager = new CheckUserLogPager( $this, $searchConds, $year, $month );
1218 - $wgOut->addHTML(
1219 - $pager->getNavigationBar() .
1220 - $pager->getBody() .
1221 - $pager->getNavigationBar() );
1222 - }
1223 -
1224 - /**
1225 - * @return string Formatted HTML
1226 - * @param int $year
1227 - * @param int $month
1228 - */
1229 - protected function getDateMenu( $year, $month ) {
1230 - # Offset overrides year/month selection
1231 - if( $month && $month !== -1 ) {
1232 - $encMonth = intval( $month );
1233 - } else {
1234 - $encMonth = '';
1235 - }
1236 - if ( $year ) {
1237 - $encYear = intval( $year );
1238 - } else if( $encMonth ) {
1239 - $thisMonth = intval( gmdate( 'n' ) );
1240 - $thisYear = intval( gmdate( 'Y' ) );
1241 - if( intval($encMonth) > $thisMonth ) {
1242 - $thisYear--;
1243 - }
1244 - $encYear = $thisYear;
1245 - } else {
1246 - $encYear = '';
1247 - }
1248 - return Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
1249 - Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) .
1250 - ' '.
1251 - Xml::label( wfMsg( 'month' ), 'month' ) . ' '.
1252 - Xml::monthSelector( $encMonth, -1 );
1253 - }
1254 -
1255 - protected function addLogEntry( $logType, $targetType, $target, $reason, $targetID = 0 ) {
1256 - global $wgUser;
1257 -
1258 - if ( $targetType == 'ip' ) {
1259 - list( $rangeStart, $rangeEnd ) = IP::parseRange( $target );
1260 - $targetHex = $rangeStart;
1261 - if ( $rangeStart == $rangeEnd ) {
1262 - $rangeStart = $rangeEnd = '';
1263 - }
1264 - } else {
1265 - $targetHex = $rangeStart = $rangeEnd = '';
1266 - }
1267 -
1268 - $dbw = wfGetDB( DB_MASTER );
1269 - $cul_id = $dbw->nextSequenceValue( 'cu_log_cul_id_seq' );
1270 - $dbw->insert( 'cu_log',
1271 - array(
1272 - 'cul_id' => $cul_id,
1273 - 'cul_timestamp' => $dbw->timestamp(),
1274 - 'cul_user' => $wgUser->getID(),
1275 - 'cul_user_text' => $wgUser->getName(),
1276 - 'cul_reason' => $reason,
1277 - 'cul_type' => $logType,
1278 - 'cul_target_id' => $targetID,
1279 - 'cul_target_text' => $target,
1280 - 'cul_target_hex' => $targetHex,
1281 - 'cul_range_start' => $rangeStart,
1282 - 'cul_range_end' => $rangeEnd,
1283 - ), __METHOD__ );
1284 - return true;
1285 - }
128612 }
128713
128814 class CheckUserLogPager extends ReverseChronologicalPager {
Index: branches/new-checkuser/CheckUser_api.php
@@ -0,0 +1,148 @@
 2+<?php
 3+
 4+/*
 5+ * Created on Jan 14, 2009
 6+ *
 7+ * Copyright (C) 2009 Soxred93 soxred93 [-at-] gee mail [-dot-] com,
 8+ *
 9+ * This program is free software; you can redistribute it and/or modify
 10+ * it under the terms of the GNU General Public License as published by
 11+ * the Free Software Foundation; either version 2 of the License, or
 12+ * (at your option) any later version.
 13+ *
 14+ * This program is distributed in the hope that it will be useful,
 15+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 17+ * GNU General Public License for more details.
 18+ *
 19+ * You should have received a copy of the GNU General Public License along
 20+ * with this program; if not, write to the Free Software Foundation, Inc.,
 21+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 22+ * http://www.gnu.org/copyleft/gpl.html
 23+ */
 24+
 25+if (!defined('MEDIAWIKI')) {
 26+ // Eclipse helper - will be ignored in production
 27+ require_once ('ApiBase.php');
 28+}
 29+
 30+class CheckUserApi extends ApiBase {
 31+
 32+ public function execute() {
 33+ global $wgUser;
 34+ $this->getMain()->requestWriteMode();
 35+ $params = $this->extractRequestParams();
 36+
 37+ if(is_null($params['user']))
 38+ $this->dieUsageMsg(array('missingparam', 'user'));
 39+ if(is_null($params['type']))
 40+ $this->dieUsageMsg(array('missingparam', 'type'));
 41+ if(is_null($params['duration']))
 42+ $this->dieUsageMsg(array('missingparam', 'duration'));
 43+ if(!isset($params['reason'])) {
 44+ $reason = '';
 45+ }
 46+ else {
 47+ $reason = $params['reason'];
 48+ }
 49+ if(!$wgUser->isAllowed('checkuser'))
 50+ $this->dieUsageMsg(array('cantcheckuser'));
 51+
 52+ $user = $params['user'];
 53+ $checktype = $params['type'];
 54+ $period = $params['duration'];
 55+
 56+ # An IPv4?
 57+ if( preg_match( '#^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(/\d{1,2}|)$#', $user ) ) {
 58+ $ip = $user;
 59+ $name = '';
 60+ $xff = '';
 61+ # An IPv6?
 62+ } else if( preg_match( '#^[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4})+(/\d{1,3}|)$#', $user ) ) {
 63+ $ip = IP::sanitizeIP($user);
 64+ $name = '';
 65+ $xff = '';
 66+ # An IPv4 XFF string?
 67+ } else if( preg_match( '#^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(/\d{1,2}|)/xff$#', $user, $matches ) ) {
 68+ list( $junk, $xffip, $xffbit) = $matches;
 69+ $ip = '';
 70+ $name = '';
 71+ $xff = $xffip . $xffbit;
 72+ # An IPv6 XFF string?
 73+ } else if( preg_match( '#^([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4})+)(/\d{1,3}|)/xff$#', $user, $matches ) ) {
 74+ list( $junk, $xffip, $xffbit ) = $matches;
 75+ $ip = '';
 76+ $name = '';
 77+ $xff = IP::sanitizeIP( $xffip ) . $xffbit;
 78+ # A user?
 79+ } else {
 80+ $ip = '';
 81+ $name = $user;
 82+ $xff = '';
 83+ }
 84+
 85+ if( $checktype=='subuserips' ) {
 86+ $res = $this->doUserIPsRequest( $name, $reason, $period );
 87+ } else if( $xff && $checktype=='subipedits' ) {
 88+ $res = $this->doIPEditsRequest( $xff, true, $reason, $period );
 89+ } else if( $checktype=='subipedits' ) {
 90+ $res = $this->doIPEditsRequest( $ip, false, $reason, $period );
 91+ } else if( $xff && $checktype=='subipusers' ) {
 92+ $res = $this->doIPUsersRequest( $xff, true, $reason, $period );
 93+ } else if( $checktype=='subipusers' ) {
 94+ $res = $this->doIPUsersRequest( $ip, false, $reason, $period );
 95+ } else if( $checktype=='subuseredits' ) {
 96+ $res = $this->doUserEditsRequest( $user, $reason, $period );
 97+ }
 98+
 99+ if( !is_null( $res ) ) {
 100+ $this->getResult()->setIndexedTagName($res, 'cu');
 101+ $this->getResult()->addValue(null, $this->getModuleName(), $res);
 102+ }
 103+ }
 104+
 105+ public function __construct($main, $action) {
 106+ parent :: __construct($main, $action);
 107+ ApiBase::$messageMap['cantcheckuser'] = array('code' => 'cantcheckuser', 'info' => "You dont have permission to run a checkuser");
 108+ ApiBase::$messageMap['checkuserlogfail'] = array('code' => 'checkuserlogfail', 'info' => "Inserting a log entry failed");
 109+ ApiBase::$messageMap['nomatch'] = array('code' => 'nomatch', 'info' => "No matches found");
 110+ ApiBase::$messageMap['nomatchedit'] = array('code' => 'nomatch', 'info' => "No matches found. Last edit was on $1 at $2");
 111+ }
 112+
 113+ public function mustBePosted() { return true; }
 114+
 115+ public function getAllowedParams() {
 116+ return array (
 117+ 'user' => null,
 118+ 'type' => null,
 119+ 'duration' => null,
 120+ 'reason' => null,
 121+ );
 122+ }
 123+
 124+ public function getParamDescription() {
 125+ return array (
 126+ 'user' => 'The user (or IP) you want to check',
 127+ 'type' => 'The type of check you want to make (subuserips, subipedits, subipusers, or subuseredits)',
 128+ 'duration' => 'How far back you want to check',
 129+ 'reason' => 'The reason for checking',
 130+ );
 131+ }
 132+
 133+ public function getDescription() {
 134+ return array (
 135+ 'Run a CheckUser on a username or IP address'
 136+ );
 137+ }
 138+
 139+ protected function getExamples() {
 140+ return array(
 141+ 'api.php?action=checkuser&user=127.0.0.1/xff&type=subipedits&duration=all',
 142+ 'api.php?action=checkuser&user=Example&type=subuserips&duration=2_weeks',
 143+ );
 144+ }
 145+
 146+ public function getVersion() {
 147+ return __CLASS__ . ': $Id: CheckUser_api.php 45575 2009-01-14 22:50:32Z soxred93 $';
 148+ }
 149+}
\ No newline at end of file
Property changes on: branches/new-checkuser/CheckUser_api.php
___________________________________________________________________
Name: svn:eol-style
1150 + native
Index: branches/new-checkuser/CheckUser.php
@@ -8,8 +8,8 @@
99
1010 # Internationalisation file
1111 $dir = dirname(__FILE__) . '/';
12 -$wgExtensionMessagesFiles['CheckUser'] = $dir . 'CheckUser.i18n.php';
13 -$wgExtensionAliasesFiles['CheckUser'] = $dir . 'CheckUser.alias.php';
 12+$wgExtensionMessagesFiles['SpecialCheckUser'] = $dir . 'CheckUser.i18n.php';
 13+$wgExtensionAliasesFiles['SpecialCheckUser'] = $dir . 'CheckUser.alias.php';
1414
1515 $wgExtensionCredits['specialpage'][] = array(
1616 'path' => __FILE__,
@@ -46,6 +46,14 @@
4747 $wgHooks['LoadExtensionSchemaUpdates'][] = 'efCheckUserSchemaUpdates';
4848 $wgHooks['ContributionsToolLinks'][] = 'efLoadCheckUserLink';
4949
 50+# Setup classes
 51+$wgSpecialPages['CheckUser'] = 'SpecialCheckUser';
 52+$wgSpecialPageGroups['CheckUser'] = 'users';
 53+$wgAutoloadClasses['CheckUser'] = dirname(__FILE__) . '/CheckUser_body.php';
 54+$wgAutoloadClasses['SpecialCheckUser'] = dirname(__FILE__) . '/SpecialCheckUser.php';
 55+$wgAutoloadClasses['CheckUserApi'] = dirname(__FILE__) . '/CheckUser_api.php';
 56+$wgAPIModules['checkuser'] = 'CheckUserApi';
 57+
5058 /**
5159 * Hook function for RecentChange_save
5260 * Saves user data into the cu_changes table
@@ -348,11 +356,7 @@
349357 return true;
350358 }
351359
352 -$wgSpecialPages['CheckUser'] = 'CheckUser';
353 -$wgSpecialPageGroups['CheckUser'] = 'users';
354 -$wgAutoloadClasses['CheckUser'] = dirname(__FILE__) . '/CheckUser_body.php';
355360
356 -
357361 function efLoadCheckUserLink( $id, $nt, &$links ) {
358362 global $wgUser;
359363 if( $wgUser->isAllowed( 'checkuser' ) ) {
Index: branches/new-checkuser/SpecialCheckUser.php
@@ -0,0 +1,1285 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ echo "CheckUser extension\n";
 6+ exit( 1 );
 7+}
 8+
 9+
 10+class SpecialCheckUser extends SpecialPage
 11+{
 12+ function SpecialCheckUser() {
 13+ global $wgUser;
 14+ if ( $wgUser->isAllowed( 'checkuser' ) || !$wgUser->isAllowed( 'checkuser-log' ) ) {
 15+ SpecialPage::SpecialPage('CheckUser', 'checkuser');
 16+ } else {
 17+ SpecialPage::SpecialPage('CheckUser', 'checkuser-log');
 18+ }
 19+ wfLoadExtensionMessages('SpecialCheckUser');
 20+ }
 21+
 22+ function execute( $subpage ) {
 23+ global $wgRequest, $wgOut, $wgUser, $wgContLang;
 24+
 25+ $this->setHeaders();
 26+ $this->sk = $wgUser->getSkin();
 27+
 28+ // This is horribly shitty.
 29+ // Lacking formal aliases, it's tough to ensure we have compatibility.
 30+ // Links may break, which sucks.
 31+ // Language fallbacks will not always be properly utilized.
 32+ $logMatches = array(
 33+ wfMsgForContent( 'checkuser-log-subpage' ),
 34+ 'Log'
 35+ );
 36+
 37+ foreach( $logMatches as $log ) {
 38+ if ( str_replace( '_', ' ', $wgContLang->lc( $subpage ) )
 39+ == str_replace( '_ ', ' ', $wgContLang->lc( $log ) ) ) {
 40+ if( !$wgUser->isAllowed( 'checkuser-log' ) ) {
 41+ $wgOut->permissionRequired( 'checkuser-log' );
 42+ return;
 43+ }
 44+
 45+ $this->showLog();
 46+ return;
 47+ }
 48+ }
 49+
 50+ if( !$wgUser->isAllowed( 'checkuser' ) ) {
 51+ if ( $wgUser->isAllowed( 'checkuser-log' ) ) {
 52+ $wgOut->addWikiText( wfMsg( 'checkuser-summary' ) .
 53+ "\n\n[[" . $this->getLogSubpageTitle()->getPrefixedText() .
 54+ '|' . wfMsg( 'checkuser-showlog' ) . ']]'
 55+ );
 56+ return;
 57+ }
 58+
 59+ $wgOut->permissionRequired( 'checkuser' );
 60+ return;
 61+ }
 62+
 63+ $user = $wgRequest->getText( 'user' ) ?
 64+ $wgRequest->getText( 'user' ) : $wgRequest->getText( 'ip' );
 65+ $user = trim($user);
 66+ $reason = $wgRequest->getText( 'reason' );
 67+ $blockreason = $wgRequest->getText( 'blockreason' );
 68+ $checktype = $wgRequest->getVal( 'checktype' );
 69+ $period = $wgRequest->getInt( 'period' );
 70+ $users = $wgRequest->getArray( 'users' );
 71+ $tag = $wgRequest->getBool('usetag') ? trim( $wgRequest->getVal( 'tag' ) ) : "";
 72+ $talkTag = $wgRequest->getBool('usettag') ? trim( $wgRequest->getVal( 'talktag' ) ) : "";
 73+
 74+ # An IPv4? An IPv6? CIDR included?
 75+ if( IP::isIPAddress($user) ) {
 76+ $ip = IP::sanitizeIP($user);
 77+ $name = '';
 78+ $xff = '';
 79+ # An IPv4/IPv6 XFF string? CIDR included?
 80+ } else if( preg_match('/^(.+)\/xff$/',$user,$m) && IP::isIPAddress($m[1]) ) {
 81+ $ip = '';
 82+ $name = '';
 83+ $xff = IP::sanitizeIP($m[1]);
 84+ # A user?
 85+ } else {
 86+ $ip = '';
 87+ $name = $user;
 88+ $xff = '';
 89+ }
 90+
 91+ $this->doForm( $user, $reason, $checktype, $ip, $xff, $name, $period );
 92+ # Perform one of the various submit operations...
 93+ if( $wgRequest->wasPosted() ) {
 94+ if( $wgRequest->getVal('action') === 'block' ) {
 95+ $this->doMassUserBlock( $users, $blockreason, $tag, $talkTag );
 96+ } else if( $checktype=='subuserips' ) {
 97+ $this->doUserIPsRequest( $name, $reason, $period );
 98+ } else if( $xff && $checktype=='subipedits' ) {
 99+ $this->doIPEditsRequest( $xff, true, $reason, $period );
 100+ } else if( $checktype=='subipedits' ) {
 101+ $this->doIPEditsRequest( $ip, false, $reason, $period );
 102+ } else if( $xff && $checktype=='subipusers' ) {
 103+ $this->doIPUsersRequest( $xff, true, $reason, $period, $tag, $talkTag );
 104+ } else if( $checktype=='subipusers' ) {
 105+ $this->doIPUsersRequest( $ip, false, $reason, $period, $tag, $talkTag );
 106+ } else if( $checktype=='subuseredits' ) {
 107+ $this->doUserEditsRequest( $user, $reason, $period );
 108+ }
 109+ }
 110+ # Add CIDR calculation convenience form
 111+ $this->addJsCIDRForm();
 112+ $this->addStyles();
 113+ }
 114+
 115+ /**
 116+ * As we use the same small set of messages in various methods and that
 117+ * they are called often, we call them once and save them in $this->message
 118+ */
 119+ protected function preCacheMessages() {
 120+ // Precache various messages
 121+ if( !isset( $this->message ) ) {
 122+ foreach( explode(' ', 'diff hist minoreditletter newpageletter blocklink log' ) as $msg ) {
 123+ $this->message[$msg] = wfMsgExt( $msg, array( 'escape') );
 124+ }
 125+ }
 126+ }
 127+
 128+ public function getLogSubpageTitle() {
 129+ if ( !isset( $this->logSubpageTitle ) ) {
 130+ $this->logSubpageTitle = $this->getTitle( wfMsgForContent( 'checkuser-log-subpage' ) );
 131+ }
 132+ return $this->logSubpageTitle;
 133+ }
 134+
 135+ protected function doForm( $user, $reason, $checktype, $ip, $xff, $name, $period ) {
 136+ global $wgOut, $wgUser;
 137+ $action = $this->getTitle()->escapeLocalUrl();
 138+ # Fill in requested type if it makes sense
 139+ $encipusers = $encipedits = $encuserips = $encuseredits = 0;
 140+ if( $checktype=='subipusers' && ( $ip || $xff ) )
 141+ $encipusers = 1;
 142+ else if( $checktype=='subipedits' && ( $ip || $xff ) )
 143+ $encipedits = 1;
 144+ else if( $checktype=='subuserips' && $name )
 145+ $encuserips = 1;
 146+ else if( $checktype=='subuseredits' && $name )
 147+ $encuseredits = 1;
 148+ # Defaults otherwise
 149+ else if( $ip || $xff )
 150+ $encipedits = 1;
 151+ else
 152+ $encuserips = 1;
 153+ # Compile our nice form
 154+ # User box length should fit things like "2001:0db8:85a3:08d3:1319:8a2e:0370:7344/100/xff"
 155+ if( $wgUser->isAllowed( 'checkuser-log' ) ) {
 156+ $wgOut->addWikiText( wfMsg( 'checkuser-summary' ) .
 157+ "\n\n[[" . $this->getLogSubpageTitle()->getPrefixedText() .
 158+ '|' . wfMsg( 'checkuser-showlog' ) . ']]'
 159+ );
 160+ }
 161+ $form = "<form name='checkuserform' id='checkuserform' action=\"$action\" method='post'>";
 162+ $form .= "<fieldset><legend>".wfMsgHtml( "checkuser-query" )."</legend>";
 163+ $form .= "<table border='0' cellpadding='2'><tr>";
 164+ $form .= "<td>".wfMsgHtml( "checkuser-target" ).":</td>";
 165+ $form .= "<td>".Xml::input( 'user', 46, $user, array( 'id' => 'checktarget' ) );
 166+ $form .= "&nbsp;".$this->getPeriodMenu( $period ) . "</td>";
 167+ $form .= "</tr><tr>";
 168+ $form .= "<td></td><td class='checkuserradios'><table border='0' cellpadding='3'><tr>";
 169+ $form .= "<td>".Xml::radio( 'checktype', 'subuserips', $encuserips, array('id' => 'subuserips') );
 170+ $form .= " ".Xml::label( wfMsgHtml("checkuser-ips"), 'subuserips' )."</td>";
 171+ $form .= "<td>".Xml::radio( 'checktype', 'subipedits', $encipedits, array('id' => 'subipedits') );
 172+ $form .= " ".Xml::label( wfMsgHtml("checkuser-edits"), 'subipedits' )."</td>";
 173+ $form .= "<td>".Xml::radio( 'checktype', 'subipusers', $encipusers, array('id' => 'subipusers') );
 174+ $form .= " ".Xml::label( wfMsgHtml("checkuser-users"), 'subipusers' )."</td>";
 175+ $form .= "<td>".Xml::radio( 'checktype', 'subuseredits', $encuseredits, array('id' => 'subuseredits') );
 176+ $form .= " ".Xml::label( wfMsgHtml("checkuser-account"), 'subuseredits' )."</td>";
 177+ $form .= "</tr></table></td>";
 178+ $form .= "</tr><tr>";
 179+ $form .= "<td>".wfMsgHtml( "checkuser-reason" )."</td>";
 180+ $form .= "<td>".Xml::input( 'reason', 46, $reason, array( 'maxlength' => '150', 'id' => 'checkreason' ) );
 181+ $form .= "&nbsp; &nbsp;".Xml::submitButton( wfMsgHtml('checkuser-check'),
 182+ array('id' => 'checkusersubmit','name' => 'checkusersubmit') )."</td>";
 183+ $form .= "</tr></table></fieldset></form>";
 184+ # Output form
 185+ $wgOut->addHTML( $form );
 186+ }
 187+
 188+ /**
 189+ * Add CSS/JS
 190+ */
 191+ protected function addStyles() {
 192+ global $wgScriptPath, $wgCheckUserStyleVersion, $wgOut;
 193+ $encJSFile = htmlspecialchars( "$wgScriptPath/extensions/CheckUser/checkuser.js?$wgCheckUserStyleVersion" );
 194+ $wgOut->addScript( "<script type=\"text/javascript\" src=\"$encJSFile\"></script>" );
 195+ }
 196+
 197+ /**
 198+ * Get a selector of time period options
 199+ * @param int $selected, selected level
 200+ */
 201+ protected function getPeriodMenu( $selected=null ) {
 202+ $s = "<label for='period'>" . wfMsgHtml('checkuser-period') . "</label>&nbsp;";
 203+ $s .= Xml::openElement( 'select', array('name' => 'period','id' => 'period','style' => 'margin-top:.2em;') );
 204+ $s .= Xml::option( wfMsg( "checkuser-week-1" ), 7, $selected===7 );
 205+ $s .= Xml::option( wfMsg( "checkuser-week-2" ), 14, $selected===14 );
 206+ $s .= Xml::option( wfMsg( "checkuser-month" ), 31, $selected===31 );
 207+ $s .= Xml::option( wfMsg( "checkuser-all" ), 0, $selected===0 );
 208+ $s .= Xml::closeElement('select')."\n";
 209+ return $s;
 210+ }
 211+
 212+ /**
 213+ * Make a quick JS form for admins to calculate block ranges
 214+ */
 215+ protected function addJsCIDRForm() {
 216+ global $wgOut;
 217+ $s = '<fieldset id="mw-checkuser-cidrform" style="display:none; clear:both;">'.
 218+ '<legend>'.wfMsgHtml('checkuser-cidr-label').'</legend>';
 219+ $s .= '<textarea id="mw-checkuser-iplist" rows="5" cols="50" onkeyup="updateCIDRresult()" onclick="updateCIDRresult()"></textarea><br/>';
 220+ $s .= wfMsgHtml('checkuser-cidr-res') . '&nbsp;' .
 221+ Xml::input( 'mw-checkuser-cidr-res',35,'',array('id'=>'mw-checkuser-cidr-res') ) .
 222+ '&nbsp;<strong id="mw-checkuser-ipnote"></strong>';
 223+ $s .= '</fieldset>';
 224+ $wgOut->addHTML( $s );
 225+ }
 226+
 227+ /**
 228+ * Block a list of selected users
 229+ * @param array $users
 230+ * @param string $reason
 231+ * @param string $tag
 232+ */
 233+ protected function doMassUserBlock( $users, $reason = '', $tag = '', $talkTag = '' ) {
 234+ global $wgOut, $wgUser, $wgCheckUserMaxBlocks, $wgLang;
 235+ if( empty($users) || $wgUser->isBlocked(false) ) {
 236+ $wgOut->addWikiText( wfMsgExt('checkuser-block-failure',array('parsemag')) );
 237+ return;
 238+ } else if( count($users) > $wgCheckUserMaxBlocks ) {
 239+ $wgOut->addWikiText( wfMsgExt('checkuser-block-limit',array('parsemag')) );
 240+ return;
 241+ } else if( !$reason ) {
 242+ $wgOut->addWikiText( wfMsgExt('checkuser-block-noreason',array('parsemag')) );
 243+ return;
 244+ }
 245+ $safeUsers = IPBlockForm::doMassUserBlock( $users, $reason, $tag, $talkTag );
 246+ if( !empty( $safeUsers ) ) {
 247+ $n = count( $safeUsers );
 248+ $ulist = $wgLang->listToText( $safeUsers );
 249+ $wgOut->addWikiText( wfMsgExt( 'checkuser-block-success', 'parsemag', $ulist, $wgLang->formatNum( $n ) ) );
 250+ } else {
 251+ $wgOut->addWikiText( wfMsgExt( 'checkuser-block-failure', 'parsemag' ) );
 252+ }
 253+ }
 254+
 255+ protected function noMatchesMessage( $userName ) {
 256+ global $wgLang;
 257+ $dbr = wfGetDB( DB_SLAVE );
 258+ $user_id = User::idFromName($userName);
 259+ if( $user_id ) {
 260+ $revEdit = $dbr->selectField( 'revision',
 261+ 'rev_timestamp',
 262+ array( 'rev_user' => $user_id ),
 263+ __METHOD__,
 264+ array( 'ORDER BY' => 'rev_timestamp DESC')
 265+ );
 266+ } else {
 267+ $revEdit = $dbr->selectField( 'revision',
 268+ 'rev_timestamp',
 269+ array( 'rev_user_text' => $userName ),
 270+ __METHOD__,
 271+ array( 'ORDER BY' => 'rev_timestamp DESC')
 272+ );
 273+ }
 274+ $logEdit = 0;
 275+ if( $user_id ) {
 276+ $logEdit = $dbr->selectField( 'logging',
 277+ 'log_timestamp',
 278+ array( 'log_user' => $user_id ),
 279+ __METHOD__,
 280+ array( 'ORDER BY' => 'log_timestamp DESC')
 281+ );
 282+ }
 283+ $lastEdit = max( $revEdit, $logEdit );
 284+ if( $lastEdit ) {
 285+ $lastEditDate = $wgLang->date( wfTimestamp(TS_MW,$lastEdit), true );
 286+ $lastEditTime = $wgLang->time( wfTimestamp(TS_MW,$lastEdit), true );
 287+ return wfMsgHtml( 'checkuser-nomatch-edits', $lastEditDate, $lastEditTime );
 288+ }
 289+ return wfMsgExt('checkuser-nomatch','parse');
 290+ }
 291+
 292+ /**
 293+ * @param string $ip
 294+ * @param bool $xfor
 295+ * @param string $reason
 296+ * Get all IPs used by a user
 297+ * Shows first and last date and number of edits
 298+ */
 299+ protected function doUserIPsRequest( $user , $reason = '', $period = 0 ) {
 300+ global $wgOut, $wgLang, $wgUser;
 301+
 302+ $userTitle = Title::newFromText( $user, NS_USER );
 303+ if( !is_null( $userTitle ) ) {
 304+ // normalize the username
 305+ $user = $userTitle->getText();
 306+ }
 307+ # IPs are passed in as a blank string
 308+ if( !$user ) {
 309+ $wgOut->addWikiMsg( 'nouserspecified' );
 310+ return;
 311+ }
 312+ # Get ID, works better than text as user may have been renamed
 313+ $user_id = User::idFromName($user);
 314+
 315+ # If user is not IP or nonexistent
 316+ if( !$user_id ) {
 317+ $s = wfMsgExt('nosuchusershort',array('parse'),$user);
 318+ $wgOut->addHTML( $s );
 319+ return;
 320+ }
 321+
 322+ if( !$this->addLogEntry( 'userips', 'user', $user, $reason, $user_id ) ) {
 323+ $wgOut->addHTML( '<p>'.wfMsgHtml('checkuser-log-fail').'</p>' );
 324+ }
 325+ $dbr = wfGetDB( DB_SLAVE );
 326+ $time_conds = $this->getTimeConds( $period );
 327+ # Ordering by the latest timestamp makes a small filesort on the IP list
 328+ $cu_changes = $dbr->tableName( 'cu_changes' );
 329+ $use_index = $dbr->useIndexClause( 'cuc_user_ip_time' );
 330+ $sql = "SELECT cuc_ip,cuc_ip_hex, COUNT(*) AS count,
 331+ MIN(cuc_timestamp) AS first, MAX(cuc_timestamp) AS last
 332+ FROM $cu_changes $use_index WHERE cuc_user = $user_id AND $time_conds
 333+ GROUP BY cuc_ip,cuc_ip_hex ORDER BY last DESC LIMIT 5001";
 334+
 335+ $ret = $dbr->query( $sql, __METHOD__ );
 336+ if( !$dbr->numRows( $ret ) ) {
 337+ $s = $this->noMatchesMessage($user)."\n";
 338+ } else {
 339+ $blockip = SpecialPage::getTitleFor( 'blockip' );
 340+ $ips_edits = array();
 341+ $counter = 0;
 342+ while( $row = $dbr->fetchObject($ret) ) {
 343+ if( $counter >= 5000 ) {
 344+ $wgOut->addHTML( wfMsgExt('checkuser-limited',array('parse')) );
 345+ break;
 346+ }
 347+ $ips_edits[$row->cuc_ip] = $row->count;
 348+ $ips_first[$row->cuc_ip] = $row->first;
 349+ $ips_last[$row->cuc_ip] = $row->last;
 350+ $ips_hex[$row->cuc_ip] = $row->cuc_ip_hex;
 351+ ++$counter;
 352+ }
 353+ // Count pinging might take some time...make sure it is there
 354+ wfSuppressWarnings();
 355+ set_time_limit(60);
 356+ wfRestoreWarnings();
 357+
 358+ $logs = SpecialPage::getTitleFor( 'Log' );
 359+ $blocklist = SpecialPage::getTitleFor( 'Ipblocklist' );
 360+ $s = '<div id="checkuserresults"><ul>';
 361+ foreach( $ips_edits as $ip => $edits ) {
 362+ $s .= '<li>';
 363+ $s .= '<a href="' .
 364+ $this->getTitle()->escapeLocalURL( 'user='.urlencode($ip) . '&reason='.urlencode($reason) ) . '">' .
 365+ htmlspecialchars($ip) . '</a>';
 366+ $s .= ' (<a href="' . $blockip->escapeLocalURL( 'ip='.urlencode($ip) ).'">' .
 367+ wfMsgHtml('blocklink') . '</a>)';
 368+ if( $ips_first[$ip] == $ips_last[$ip] ) {
 369+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$ips_first[$ip]), true ) . ') ';
 370+ } else {
 371+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$ips_first[$ip]), true ) .
 372+ ' -- ' . $wgLang->timeanddate( wfTimestamp(TS_MW,$ips_last[$ip]), true ) . ') ';
 373+ }
 374+ $s .= ' <strong>[' . $edits . ']</strong>';
 375+
 376+ # If we get some results, it helps to know if the IP in general
 377+ # has a lot more edits, e.g. "tip of the iceberg"...
 378+ $ipedits = $dbr->estimateRowCount( 'cu_changes', '*',
 379+ array( 'cuc_ip_hex' => $ips_hex[$ip] ),
 380+ __METHOD__ );
 381+ # If small enough, get a more accurate count
 382+ if( $ipedits <= 1000 ) {
 383+ $ipedits = $dbr->selectField( 'cu_changes', 'COUNT(*)',
 384+ array( 'cuc_ip_hex' => $ips_hex[$ip] ),
 385+ __METHOD__ );
 386+ }
 387+ if( $ipedits > $ips_edits[$ip] ) {
 388+ $s .= ' <i>(' . wfMsgHtml('checkuser-ipeditcount',$ipedits) . ')</i>';
 389+ }
 390+
 391+ # If this IP is blocked, give a link to the block log
 392+ $block = new Block();
 393+ $block->fromMaster( false ); // use slaves
 394+ if( $block->load( $ip, 0 ) ) {
 395+ if( IP::isIPAddress($block->mAddress) && strpos($block->mAddress,'/') ) {
 396+ $userpage = Title::makeTitle( NS_USER, $block->mAddress );
 397+ $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml('checkuser-blocked'),
 398+ 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
 399+ $s .= ' <strong>(' . $blocklog . ' - ' . $block->mAddress . ')</strong>';
 400+ } else if( $block->mAuto ) {
 401+ $blocklog = $this->sk->makeKnownLinkObj( $blocklist, wfMsgHtml('checkuser-blocked'),
 402+ 'ip=' . urlencode( "#$block->mId" ) );
 403+ $s .= ' <strong>(' . $blocklog . ')</strong>';
 404+ } else {
 405+ $userpage = Title::makeTitle( NS_USER, $ip );
 406+ $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml('checkuser-blocked'),
 407+ 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
 408+ $s .= ' <strong>(' . $blocklog . ')</strong>';
 409+ }
 410+ }
 411+ $s .= "<div style='margin-left:5%'>";
 412+ $s .= "<small>" . wfMsgExt('checkuser-toollinks',array('parseinline'),urlencode($ip)) . "</small>";
 413+ $s .= "</div>";
 414+ $s .= "</li>\n";
 415+ }
 416+ $s .= '</ul></div>';
 417+ }
 418+ $wgOut->addHTML( $s );
 419+ $dbr->freeResult( $ret );
 420+ }
 421+
 422+ /**
 423+ * @param string $ip
 424+ * @param bool $xfor
 425+ * @param string $reason
 426+ * Shows all edits in Recent Changes by this IP (or range) and who made them
 427+ */
 428+ protected function doIPEditsRequest( $ip, $xfor = false, $reason = '', $period = 0 ) {
 429+ global $wgUser, $wgOut, $wgLang;
 430+ $dbr = wfGetDB( DB_SLAVE );
 431+ # Invalid IPs are passed in as a blank string
 432+ $ip_conds = $this->getIpConds( $dbr, $ip, $xfor );
 433+ if( !$ip || $ip_conds === false ) {
 434+ $wgOut->addWikiMsg( 'badipaddress' );
 435+ return;
 436+ }
 437+
 438+ $logType = 'ipedits';
 439+ if( $xfor ) {
 440+ $logType .= '-xff';
 441+ }
 442+ # Record check...
 443+ if( !$this->addLogEntry( $logType, 'ip', $ip, $reason ) ) {
 444+ $wgOut->addWikiMsg( 'checkuser-log-fail' );
 445+ }
 446+
 447+ $ip_conds = $dbr->makeList( $ip_conds, LIST_AND );
 448+ $time_conds = $this->getTimeConds( $period );
 449+ $cu_changes = $dbr->tableName( 'cu_changes' );
 450+ # Ordered in descent by timestamp. Can cause large filesorts on range scans.
 451+ # Check how many rows will need sorting ahead of time to see if this is too big.
 452+ # Also, if we only show 5000, too many will be ignored as well.
 453+ $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time';
 454+ if( strpos($ip,'/') !== false ) {
 455+ # Quick index check only OK if no time constraint
 456+ if( $period ) {
 457+ $rangecount = $dbr->selectField( 'cu_changes', 'COUNT(*)',
 458+ array( $ip_conds, $time_conds ),
 459+ __METHOD__,
 460+ array( 'USE INDEX' => $index ) );
 461+ } else {
 462+ $rangecount = $dbr->estimateRowCount( 'cu_changes', '*',
 463+ array( $ip_conds ),
 464+ __METHOD__,
 465+ array( 'USE INDEX' => $index ) );
 466+ }
 467+ // Sorting might take some time...make sure it is there
 468+ wfSuppressWarnings();
 469+ set_time_limit(60);
 470+ wfRestoreWarnings();
 471+ }
 472+ $counter = 0;
 473+ # See what is best to do after testing the waters...
 474+ if( isset($rangecount) && $rangecount > 5000 ) {
 475+ $use_index = $dbr->useIndexClause( $index );
 476+ $sql = "SELECT cuc_ip_hex, COUNT(*) AS count,
 477+ MIN(cuc_timestamp) AS first, MAX(cuc_timestamp) AS last
 478+ FROM $cu_changes $use_index
 479+ WHERE $ip_conds AND $time_conds
 480+ GROUP BY cuc_ip_hex ORDER BY cuc_ip_hex LIMIT 5001";
 481+ $ret = $dbr->query( $sql, __METHOD__ );
 482+ # List out each IP that has edits
 483+ $s = wfMsgExt('checkuser-too-many',array('parse'));
 484+ $s .= '<ol>';
 485+ while( $row = $ret->fetchObject() ) {
 486+ if( $counter >= 5000 ) {
 487+ $wgOut->addHTML( wfMsgExt('checkuser-limited',array('parse')) );
 488+ break;
 489+ }
 490+ # Convert the IP hexes into normal form
 491+ if( strpos($row->cuc_ip_hex,'v6-') !==false ) {
 492+ $ip = substr( $row->cuc_ip_hex, 3 );
 493+ $ip = IP::HextoOctet( $ip );
 494+ } else {
 495+ $ip = long2ip( wfBaseConvert($row->cuc_ip_hex, 16, 10, 8) );
 496+ }
 497+ $s .= '<li><a href="'.
 498+ $this->getTitle()->escapeLocalURL( 'user='.urlencode($ip).'&reason='.urlencode($reason).'&checktype=subipusers' ) .
 499+ '">'.$ip.'</a>';
 500+ if( $row->first == $row->last ) {
 501+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->first), true ) . ') ';
 502+ } else {
 503+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->first), true ) .
 504+ ' -- ' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->last), true ) . ') ';
 505+ }
 506+ $s .= " [<strong>" . $row->count . "</strong>]</li>\n";
 507+ ++$counter;
 508+ }
 509+ $s .= '</ol>';
 510+ $dbr->freeResult( $ret );
 511+
 512+ $wgOut->addHTML( $s );
 513+ return;
 514+ } else if( isset($rangecount) && !$rangecount ) {
 515+ $s = $this->noMatchesMessage($ip)."\n";
 516+ $wgOut->addHTML( $s );
 517+ return;
 518+ }
 519+ # OK, do the real query...
 520+ $use_index = $dbr->useIndexClause( $index );
 521+ $sql = "SELECT cuc_namespace,cuc_title,cuc_user,cuc_user_text,cuc_comment,cuc_actiontext,
 522+ cuc_timestamp,cuc_minor,cuc_page_id,cuc_type,cuc_this_oldid,cuc_last_oldid,cuc_ip,cuc_xff,cuc_agent
 523+ FROM $cu_changes $use_index WHERE $ip_conds AND $time_conds ORDER BY cuc_timestamp DESC LIMIT 5001";
 524+ $ret = $dbr->query( $sql, __METHOD__ );
 525+
 526+ if( !$dbr->numRows( $ret ) ) {
 527+ $s = $this->noMatchesMessage($ip)."\n";
 528+ } else {
 529+ # Cache common messages
 530+ $this->preCacheMessages();
 531+ # Try to optimize this query
 532+ $lb = new LinkBatch;
 533+ while( $row = $ret->fetchObject() ) {
 534+ $userText = str_replace( ' ', '_', $row->cuc_user_text );
 535+ $lb->add( $row->cuc_namespace, $row->cuc_title );
 536+ $lb->add( NS_USER, $userText );
 537+ $lb->add( NS_USER_TALK, $userText );
 538+ }
 539+ $lb->execute();
 540+ $ret->seek( 0 );
 541+ # List out the edits
 542+ $s = '<div id="checkuserresults">';
 543+ while( $row = $ret->fetchObject() ) {
 544+ if( $counter >= 5000 ) {
 545+ $wgOut->addHTML( wfMsgExt('checkuser-limited',array('parse')) );
 546+ break;
 547+ }
 548+ $s .= $this->CUChangesLine( $row, $reason );
 549+ ++$counter;
 550+ }
 551+ $s .= '</ul></div>';
 552+ $dbr->freeResult( $ret );
 553+ }
 554+
 555+ $wgOut->addHTML( $s );
 556+ }
 557+
 558+ /**
 559+ * @param string $nuser
 560+ * @param string $reason
 561+ * Shows all edits in Recent Changes by this user
 562+ */
 563+ protected function doUserEditsRequest( $user, $reason = '', $period = 0 ) {
 564+ global $wgUser, $wgOut, $wgLang;
 565+
 566+ $userTitle = Title::newFromText( $user, NS_USER );
 567+ if( !is_null( $userTitle ) ) {
 568+ // normalize the username
 569+ $user = $userTitle->getText();
 570+ }
 571+ # IPs are passed in as a blank string
 572+ if( !$user ) {
 573+ $wgOut->addWikiMsg( 'nouserspecified' );
 574+ return;
 575+ }
 576+ # Get ID, works better than text as user may have been renamed
 577+ $user_id = User::idFromName($user);
 578+
 579+ # If user is not IP or nonexistent
 580+ if( !$user_id ) {
 581+ $s = wfMsgExt('nosuchusershort',array('parse'),$user);
 582+ $wgOut->addHTML( $s );
 583+ return;
 584+ }
 585+
 586+ # Record check...
 587+ if( !$this->addLogEntry( 'useredits', 'user', $user, $reason, $user_id ) ) {
 588+ $wgOut->addHTML( '<p>'.wfMsgHtml('checkuser-log-fail').'</p>' );
 589+ }
 590+
 591+ $dbr = wfGetDB( DB_SLAVE );
 592+ $user_cond = "cuc_user = '$user_id'";
 593+ $time_conds = $this->getTimeConds( $period );
 594+ $cu_changes = $dbr->tableName( 'cu_changes' );
 595+ # Ordered in descent by timestamp. Causes large filesorts if there are many edits.
 596+ # Check how many rows will need sorting ahead of time to see if this is too big.
 597+ # If it is, sort by IP,time to avoid the filesort.
 598+ if( $period ) {
 599+ $count = $dbr->selectField( 'cu_changes', 'COUNT(*)',
 600+ array( $user_cond, $time_conds ),
 601+ __METHOD__,
 602+ array( 'USE INDEX' => 'cuc_user_ip_time' ) );
 603+ } else {
 604+ $count = $dbr->estimateRowCount( 'cu_changes', '*',
 605+ array( $user_cond, $time_conds ),
 606+ __METHOD__,
 607+ array( 'USE INDEX' => 'cuc_user_ip_time' ) );
 608+ }
 609+ # Cache common messages
 610+ $this->preCacheMessages();
 611+ # See what is best to do after testing the waters...
 612+ if( $count > 5000 ) {
 613+ $wgOut->addHTML( wfMsgExt('checkuser-limited',array('parse')) );
 614+ $use_index = $dbr->useIndexClause( 'cuc_user_ip_time' );
 615+ $sql = "SELECT * FROM $cu_changes $use_index
 616+ WHERE $user_cond AND $time_conds
 617+ ORDER BY cuc_ip ASC, cuc_timestamp DESC LIMIT 5000";
 618+ $ret = $dbr->query( $sql, __METHOD__ );
 619+ # Try to optimize this query
 620+ $lb = new LinkBatch;
 621+ while( $row = $ret->fetchObject() ) {
 622+ $lb->add( $row->cuc_namespace, $row->cuc_title );
 623+ }
 624+ $lb->execute();
 625+ $ret->seek( 0 );
 626+ $s = '';
 627+ while( $row = $ret->fetchObject() ) {
 628+ if( !$ip = htmlspecialchars($row->cuc_ip) ) {
 629+ continue;
 630+ }
 631+ if( !isset($lastIP) ) {
 632+ $lastIP = $row->cuc_ip;
 633+ $s .= "\n<h2>$ip</h2>\n<div class=\"special\">";
 634+ } else if( $lastIP != $row->cuc_ip ) {
 635+ $s .= "</ul></div>\n<h2>$ip</h2>\n<div class=\"special\">";
 636+ $lastIP = $row->cuc_ip;
 637+ unset($this->lastdate); // start over
 638+ }
 639+ $s .= $this->CUChangesLine( $row, $reason );
 640+ }
 641+ $s .= '</ul></div>';
 642+ $dbr->freeResult( $ret );
 643+
 644+ $wgOut->addHTML( $s );
 645+ return;
 646+ }
 647+ // Sorting might take some time...make sure it is there
 648+ wfSuppressWarnings();
 649+ set_time_limit(60);
 650+ wfRestoreWarnings();
 651+ # OK, do the real query...
 652+ $use_index = $dbr->useIndexClause( 'cuc_user_ip_time' );
 653+ $sql = "SELECT * FROM $cu_changes $use_index
 654+ WHERE $user_cond AND $time_conds ORDER BY cuc_timestamp DESC LIMIT 5000";
 655+ $ret = $dbr->query( $sql, __METHOD__ );
 656+
 657+ if( !$dbr->numRows( $ret ) ) {
 658+ $s = $this->noMatchesMessage($user)."\n";
 659+ } else {
 660+ # Try to optimize this query
 661+ $lb = new LinkBatch;
 662+ while( $row = $ret->fetchObject() ) {
 663+ $lb->add( $row->cuc_namespace, $row->cuc_title );
 664+ }
 665+ $lb->execute();
 666+ $ret->seek( 0 );
 667+ # List out the edits
 668+ $s = '<div id="checkuserresults">';
 669+ while( $row = $ret->fetchObject() ) {
 670+ $s .= $this->CUChangesLine( $row, $reason );
 671+ }
 672+ $s .= '</ul></div>';
 673+ $dbr->freeResult( $ret );
 674+ }
 675+
 676+ $wgOut->addHTML( $s );
 677+ }
 678+
 679+ /**
 680+ * @param string $ip
 681+ * @param bool $xfor
 682+ * @param string $reason
 683+ * @param int $period
 684+ * @param string $tag
 685+ * @param string $talkTag
 686+ * Lists all users in recent changes who used an IP, newest to oldest down
 687+ * Outputs usernames, latest and earliest found edit date, and count
 688+ * List unique IPs used for each user in time order, list corresponding user agent
 689+ */
 690+ protected function doIPUsersRequest( $ip, $xfor = false, $reason = '', $period = 0, $tag='', $talkTag='' ) {
 691+ global $wgUser, $wgOut, $wgLang;
 692+ $dbr = wfGetDB( DB_SLAVE );
 693+ # Invalid IPs are passed in as a blank string
 694+ $ip_conds = $this->getIpConds( $dbr, $ip, $xfor );
 695+ if( !$ip || $ip_conds === false ) {
 696+ $wgOut->addWikiMsg( 'badipaddress' );
 697+ return;
 698+ }
 699+
 700+ $logType = 'ipusers';
 701+ if( $xfor ) {
 702+ $logType .= '-xff';
 703+ }
 704+ # Log the check...
 705+ if( !$this->addLogEntry( $logType, 'ip', $ip, $reason ) ) {
 706+ $wgOut->addHTML( '<p>'.wfMsgHtml('checkuser-log-fail').'</p>' );
 707+ }
 708+
 709+ $ip_conds = $dbr->makeList( $ip_conds, LIST_AND );
 710+ $time_conds = $this->getTimeConds( $period );
 711+ $cu_changes = $dbr->tableName( 'cu_changes' );
 712+ $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time';
 713+ # Ordered in descent by timestamp. Can cause large filesorts on range scans.
 714+ # Check how many rows will need sorting ahead of time to see if this is too big.
 715+ if( strpos($ip,'/') !==false ) {
 716+ # Quick index check only OK if no time constraint
 717+ if( $period ) {
 718+ $rangecount = $dbr->selectField( 'cu_changes', 'COUNT(*)',
 719+ array( $ip_conds, $time_conds ),
 720+ __METHOD__,
 721+ array( 'USE INDEX' => $index ) );
 722+ } else {
 723+ $rangecount = $dbr->estimateRowCount( 'cu_changes', '*',
 724+ array( $ip_conds ),
 725+ __METHOD__,
 726+ array( 'USE INDEX' => $index ) );
 727+ }
 728+ // Sorting might take some time...make sure it is there
 729+ wfSuppressWarnings();
 730+ set_time_limit(120);
 731+ wfRestoreWarnings();
 732+ }
 733+ // Are there too many edits?
 734+ if( isset($rangecount) && $rangecount > 10000 ) {
 735+ $use_index = $dbr->useIndexClause( $index );
 736+ $sql = "SELECT cuc_ip_hex, COUNT(*) AS count,
 737+ MIN(cuc_timestamp) AS first, MAX(cuc_timestamp) AS last
 738+ FROM $cu_changes $use_index WHERE $ip_conds AND $time_conds
 739+ GROUP BY cuc_ip_hex ORDER BY cuc_ip_hex LIMIT 5001";
 740+ $ret = $dbr->query( $sql, __METHOD__ );
 741+ # List out each IP that has edits
 742+ $s = '<h5>' . wfMsg('checkuser-too-many') . '</h5>';
 743+ $s .= '<ol>';
 744+ $counter = 0;
 745+ while( $row = $ret->fetchObject() ) {
 746+ if( $counter >= 5000 ) {
 747+ $wgOut->addHTML( wfMsgExt('checkuser-limited',array('parse')) );
 748+ break;
 749+ }
 750+ # Convert the IP hexes into normal form
 751+ if( strpos($row->cuc_ip_hex,'v6-') !==false ) {
 752+ $ip = substr( $row->cuc_ip_hex, 3 );
 753+ $ip = IP::HextoOctet( $ip );
 754+ } else {
 755+ $ip = long2ip( wfBaseConvert($row->cuc_ip_hex, 16, 10, 8) );
 756+ }
 757+ $s .= '<li><a href="'.
 758+ $this->getTitle()->escapeLocalURL( 'user='.urlencode($ip).'&reason='.urlencode($reason).'&checktype=subipusers' ) .
 759+ '">'.$ip.'</a>';
 760+ if( $row->first == $row->last ) {
 761+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->first), true ) . ') ';
 762+ } else {
 763+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->first), true ) .
 764+ ' -- ' . $wgLang->timeanddate( wfTimestamp(TS_MW,$row->last), true ) . ') ';
 765+ }
 766+ $s .= " [<strong>" . $row->count . "</strong>]</li>\n";
 767+ ++$counter;
 768+ }
 769+ $s .= '</ol>';
 770+ $dbr->freeResult( $ret );
 771+
 772+ $wgOut->addHTML( $s );
 773+ return;
 774+ } else if( isset($rangecount) && !$rangecount ) {
 775+ $s = $this->noMatchesMessage($ip)."\n";
 776+ $wgOut->addHTML( $s );
 777+ return;
 778+ }
 779+
 780+ global $wgMemc;
 781+ # OK, do the real query...
 782+ $use_index = $dbr->useIndexClause( $index );
 783+ $sql = "SELECT cuc_user_text, cuc_timestamp, cuc_user, cuc_ip, cuc_agent, cuc_xff
 784+ FROM $cu_changes $use_index WHERE $ip_conds AND $time_conds
 785+ ORDER BY cuc_timestamp DESC LIMIT 10000";
 786+ $ret = $dbr->query( $sql, __METHOD__ );
 787+
 788+ $users_first = $users_last = $users_edits = $users_ids = array();
 789+ if( !$dbr->numRows( $ret ) ) {
 790+ $s = $this->noMatchesMessage($ip)."\n";
 791+ } else {
 792+ global $wgAuth;
 793+ while( ($row = $dbr->fetchObject($ret) ) != false ) {
 794+ if( !array_key_exists( $row->cuc_user_text, $users_edits ) ) {
 795+ $users_last[$row->cuc_user_text] = $row->cuc_timestamp;
 796+ $users_edits[$row->cuc_user_text] = 0;
 797+ $users_ids[$row->cuc_user_text] = $row->cuc_user;
 798+ $users_infosets[$row->cuc_user_text] = array();
 799+ $users_agentsets[$row->cuc_user_text] = array();
 800+ }
 801+ $users_edits[$row->cuc_user_text] += 1;
 802+ $users_first[$row->cuc_user_text] = $row->cuc_timestamp;
 803+ # Treat blank or NULL xffs as empty strings
 804+ $xff = empty($row->cuc_xff) ? null : $row->cuc_xff;
 805+ $xff_ip_combo = array( $row->cuc_ip, $xff );
 806+ # Add this IP/XFF combo for this username if it's not already there
 807+ if( !in_array($xff_ip_combo,$users_infosets[$row->cuc_user_text]) ) {
 808+ $users_infosets[$row->cuc_user_text][] = $xff_ip_combo;
 809+ }
 810+ # Add this agent string if it's not already there; 10 max.
 811+ if( count($users_agentsets[$row->cuc_user_text]) < 10 ) {
 812+ if( !in_array($row->cuc_agent,$users_agentsets[$row->cuc_user_text]) ) {
 813+ $users_agentsets[$row->cuc_user_text][] = $row->cuc_agent;
 814+ }
 815+ }
 816+ }
 817+ $dbr->freeResult( $ret );
 818+
 819+ $logs = SpecialPage::getTitleFor( 'Log' );
 820+ $blocklist = SpecialPage::getTitleFor( 'Ipblocklist' );
 821+
 822+ $action = $this->getTitle()->escapeLocalUrl( 'action=block' );
 823+ $s = "<form name='checkuserblock' id='checkuserblock' action=\"$action\" method='post'>";
 824+ $s .= '<div id="checkuserresults"><ul>';
 825+ foreach( $users_edits as $name => $count ) {
 826+ $s .= '<li>';
 827+ $s .= Xml::check( 'users[]', false, array( 'value' => $name ) ) . '&nbsp;';
 828+ # Load user object
 829+ $user = User::newFromName( $name, false );
 830+ # Add user tool links
 831+ $s .= $this->sk->userLink( -1 , $name ) . $this->sk->userToolLinks( -1 , $name );
 832+ # Add CheckUser link
 833+ $s .= ' (<a href="' . $this->getTitle()->escapeLocalURL( 'user='.urlencode($name) .
 834+ '&reason='.urlencode($reason) ) . '">' . wfMsgHtml('checkuser-check') . '</a>)';
 835+ # Show edit time range
 836+ if( $users_first[$name] == $users_last[$name] ) {
 837+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$users_first[$name]), true ) . ') ';
 838+ } else {
 839+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp(TS_MW,$users_first[$name]), true ) .
 840+ ' -- ' . $wgLang->timeanddate( wfTimestamp(TS_MW,$users_last[$name]), true ) . ') ';
 841+ }
 842+ # Total edit count
 843+ $s .= ' [<strong>' . $count . '</strong>]<br />';
 844+ # Check if this user or IP is blocked. If so, give a link to the block log...
 845+ $block = new Block();
 846+ $block->fromMaster( false ); // use slaves
 847+ $ip = IP::isIPAddress( $name ) ? $name : '';
 848+ $flags = array();
 849+ if( $block->load( $ip, $users_ids[$name] ) ) {
 850+ // Range blocked?
 851+ if( IP::isIPAddress($block->mAddress) && strpos($block->mAddress,'/') ) {
 852+ $userpage = Title::makeTitle( NS_USER, $block->mAddress );
 853+ $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml('checkuser-blocked'),
 854+ 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
 855+ $flags[] = '<strong>(' . $blocklog . ' - ' . $block->mAddress . ')</strong>';
 856+ // Auto blocked?
 857+ } else if( $block->mAuto ) {
 858+ $blocklog = $this->sk->makeKnownLinkObj( $blocklist,
 859+ wfMsgHtml('checkuser-blocked'), 'ip=' . urlencode( "#{$block->mId}" ) );
 860+ $flags[] = '<strong>(' . $blocklog . ')</strong>';
 861+ } else {
 862+ $userpage = Title::makeTitle( NS_USER, $name );
 863+ $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml('checkuser-blocked'),
 864+ 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
 865+ $flags[] = '<strong>(' . $blocklog . ')</strong>';
 866+ }
 867+ // IP that is blocked on all wikis?
 868+ } else if( $ip === $name && $user->isBlockedGlobally( $ip ) ) {
 869+ $flags[] = '<strong>(' . wfMsgHtml('checkuser-gblocked') . ')</strong>';
 870+ } else if( self::userWasBlocked( $name ) ) {
 871+ $userpage = Title::makeTitle( NS_USER, $name );
 872+ $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml('checkuser-wasblocked'),
 873+ 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
 874+ $flags[] = '<strong>(' . $blocklog . ')</strong>';
 875+ }
 876+ # Show if account is local only
 877+ $authUser = $wgAuth->getUserInstance( $user );
 878+ if( $user->getId() && $authUser->getId() === 0 ) {
 879+ $flags[] = '<strong>(' . wfMsgHtml('checkuser-localonly') . ')</strong>';
 880+ }
 881+ # Check for extra user rights...
 882+ if( $users_ids[$name] ) {
 883+ $user = User::newFromId( $users_ids[$name] );
 884+ if( $user->isLocked() ) {
 885+ $flags[] = '<b>(' . wfMsgHtml('checkuser-locked') . ')</b>';
 886+ }
 887+ $list = array();
 888+ foreach( $user->getGroups() as $group ) {
 889+ $list[] = self::buildGroupLink( $group );
 890+ }
 891+ $groups = $wgLang->commaList( $list );
 892+ if( $groups ) {
 893+ $flags[] = '<i>(' . $groups . ')</i>';
 894+ }
 895+ }
 896+ # Check how many accounts the user made recently?
 897+ if( $ip ) {
 898+ $key = wfMemcKey( 'acctcreate', 'ip', $ip );
 899+ $count = intval( $wgMemc->get( $key ) );
 900+ if( $count ) {
 901+ $flags[] = '<strong>[' . wfMsgExt( 'checkuser-accounts', 'parsemag', $wgLang->formatNum( $count ) ) . ']</strong>';
 902+ }
 903+ }
 904+ $s .= implode(' ',$flags);
 905+ $s .= '<ol>';
 906+ # List out each IP/XFF combo for this username
 907+ for( $i = (count($users_infosets[$name]) - 1); $i >= 0; $i-- ) {
 908+ $set = $users_infosets[$name][$i];
 909+ # IP link
 910+ $s .= '<li>';
 911+ $s .= '<a href="'.$this->getTitle()->escapeLocalURL( 'user='.urlencode($set[0]) ).'">'.htmlspecialchars($set[0]).'</a>';
 912+ # XFF string, link to /xff search
 913+ if( $set[1] ) {
 914+ # Flag our trusted proxies
 915+ list($client,$trusted) = efGetClientIPfromXFF($set[1],$set[0]);
 916+ $c = $trusted ? '#F0FFF0' : '#FFFFCC';
 917+ $s .= '&nbsp;&nbsp;&nbsp;<span style="background-color: '.$c.'"><strong>XFF</strong>: ';
 918+ $s .= $this->sk->makeKnownLinkObj( $this->getTitle(),
 919+ htmlspecialchars( $set[1] ),
 920+ "user=" . urlencode( $client ) . "/xff" )."</span>";
 921+ }
 922+ $s .= "</li>\n";
 923+ }
 924+ $s .= '</ol><br /><ol>';
 925+ # List out each agent for this username
 926+ for( $i = (count($users_agentsets[$name]) - 1); $i >= 0; $i-- ) {
 927+ $agent = $users_agentsets[$name][$i];
 928+ $s .= "<li><i>" . htmlspecialchars($agent) . "</i></li>\n";
 929+ }
 930+ $s .= '</ol>';
 931+ $s .= '</li>';
 932+ }
 933+ $s .= "</ul></div>\n";
 934+ if( $wgUser->isAllowed('block') && !$wgUser->isBlocked() ) {
 935+ $s .= "<fieldset>\n";
 936+ $s .= "<legend>" . wfMsgHtml('checkuser-massblock') . "</legend>\n";
 937+ $s .= "<p>" . wfMsgExt('checkuser-massblock-text',array('parseinline')) . "</p>\n";
 938+ $s .= '<table><tr>' .
 939+ '<td>' . Xml::check( 'usetag', false, array('id' => 'usetag') ) . '</td>' .
 940+ '<td>' . Xml::label( wfMsgHtml( "checkuser-blocktag" ), 'usetag' ) . '</td>' .
 941+ '<td>' . Xml::input( 'tag', 46, $tag, array('id' => 'blocktag') ) . '</td>' .
 942+ '</tr><tr>' .
 943+ '<td>' . Xml::check( 'usettag', false, array('id' => 'usettag') ) . '</td>' .
 944+ '<td>' . Xml::label( wfMsgHtml( "checkuser-blocktag-talk" ), 'usettag' ) . '</td>' .
 945+ '<td>' . Xml::input( 'talktag', 46, $talkTag, array('id' => 'talktag') ).'</td>'.
 946+ '</tr></table>';
 947+ $s .= "<p>" . wfMsgHtml( "checkuser-reason" ) . '&nbsp;';
 948+ $s .= Xml::input( 'blockreason', 46, '', array( 'maxlength' => '150', 'id' => 'blockreason' ) );
 949+ $s .= '&nbsp;' . Xml::submitButton( wfMsgHtml('checkuser-massblock-commit'),
 950+ array('id' => 'checkuserblocksubmit','name' => 'checkuserblock') ) . "</p>\n";
 951+ $s .= "</fieldset>\n";
 952+ }
 953+ $s .= '</form>';
 954+ }
 955+
 956+ $wgOut->addHTML( $s );
 957+ }
 958+
 959+ /**
 960+ * @param $row
 961+ * @return a streamlined recent changes line with IP data
 962+ */
 963+ protected function CUChangesLine( $row, $reason ) {
 964+ global $wgLang;
 965+ # Add date headers
 966+ $date = $wgLang->date( wfTimestamp(TS_MW,$row->cuc_timestamp), true, true );
 967+ if( !isset($this->lastdate) ) {
 968+ $this->lastdate = $date;
 969+ $line = "\n<h4>$date</h4>\n<ul class=\"special\">";
 970+ } else if( $date != $this->lastdate ) {
 971+ $line = "</ul>\n<h4>$date</h4>\n<ul class=\"special\">";
 972+ $this->lastdate = $date;
 973+ } else {
 974+ $line = '';
 975+ }
 976+ $line .= "<li>";
 977+ # Create diff/hist/page links
 978+ $line .= $this->getLinksFromRow( $row );
 979+ # Show date
 980+ $line .= ' . . ' . $wgLang->time( wfTimestamp(TS_MW,$row->cuc_timestamp), true, true ) . ' . . ';
 981+ # Userlinks
 982+ $line .= $this->sk->userLink( $row->cuc_user, $row->cuc_user_text );
 983+ $line .= $this->sk->userToolLinks( $row->cuc_user, $row->cuc_user_text );
 984+ # Action text, hackish ...
 985+ if( $row->cuc_actiontext )
 986+ $line .= ' ' . $this->sk->formatComment( $row->cuc_actiontext ) . ' ';
 987+ # Comment
 988+ $line .= $this->sk->commentBlock( $row->cuc_comment );
 989+
 990+ $cuTitle = SpecialPage::getTitleFor( 'CheckUser' );
 991+ $line .= '<br />&nbsp; &nbsp; &nbsp; &nbsp; <small>';
 992+ # IP
 993+ $line .= ' <strong>IP</strong>: '.$this->sk->makeKnownLinkObj( $cuTitle,
 994+ htmlspecialchars( $row->cuc_ip ),
 995+ "user=".urlencode( $row->cuc_ip ).'&reason='.urlencode($reason) );
 996+ # XFF
 997+ if( $row->cuc_xff !=null ) {
 998+ # Flag our trusted proxies
 999+ list($client,$trusted) = efGetClientIPfromXFF($row->cuc_xff,$row->cuc_ip);
 1000+ $c = $trusted ? '#F0FFF0' : '#FFFFCC';
 1001+ $line .= '&nbsp;&nbsp;&nbsp;<span class="mw-checkuser-xff" style="background-color: '.$c.'">'.
 1002+ '<strong>XFF</strong>: ';
 1003+ $line .= $this->sk->makeKnownLinkObj( $cuTitle,
 1004+ htmlspecialchars( $row->cuc_xff ),
 1005+ "user=".urlencode($client)."/xff&reason=".urlencode($reason) )."</span>";
 1006+ }
 1007+ # User agent
 1008+ $line .= '&nbsp;&nbsp;&nbsp;<span class="mw-checkuser-agent" style="color:#888;">' .
 1009+ htmlspecialchars( $row->cuc_agent )."</span>";
 1010+
 1011+ $line .= "</small></li>\n";
 1012+
 1013+ return $line;
 1014+ }
 1015+
 1016+ /**
 1017+ * @param $row
 1018+ * @create diff/hist/page link
 1019+ */
 1020+ protected function getLinksFromRow( $row ) {
 1021+ // Log items (old format) and events to logs
 1022+ if( $row->cuc_type == RC_LOG && $row->cuc_namespace == NS_SPECIAL ) {
 1023+ list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $row->cuc_title );
 1024+ $logname = LogPage::logName( $logtype );
 1025+ $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
 1026+ $links = '(' . $this->sk->makeKnownLinkObj( $title, $logname ) . ')';
 1027+ // Log items
 1028+ } elseif( $row->cuc_type == RC_LOG ) {
 1029+ $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
 1030+ $links = '(' . $this->sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), $this->message['log'],
 1031+ wfArrayToCGI( array('page' => $title->getPrefixedText() ) ) ) . ')';
 1032+ } else {
 1033+ $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
 1034+ # New pages
 1035+ if( $row->cuc_type == RC_NEW ) {
 1036+ $links = '(' . $this->message['diff'] . ') ';
 1037+ } else {
 1038+ # Diff link
 1039+ $links = ' (' . $this->sk->makeKnownLinkObj( $title, $this->message['diff'],
 1040+ wfArrayToCGI( array(
 1041+ 'curid' => $row->cuc_page_id,
 1042+ 'diff' => $row->cuc_this_oldid,
 1043+ 'oldid' => $row->cuc_last_oldid ) ) ) . ') ';
 1044+ }
 1045+ # History link
 1046+ $links .= ' (' . $this->sk->makeKnownLinkObj( $title, $this->message['hist'],
 1047+ wfArrayToCGI( array(
 1048+ 'curid' => $row->cuc_page_id,
 1049+ 'action' => 'history' ) ) ) . ') . . ';
 1050+ # Some basic flags
 1051+ if( $row->cuc_type == RC_NEW )
 1052+ $links .= '<span class="newpage">' . $this->message['newpageletter'] . '</span>';
 1053+ if( $row->cuc_minor )
 1054+ $links .= '<span class="minor">' . $this->message['minoreditletter'] . '</span>';
 1055+ # Page link
 1056+ $links .= ' ' . $this->sk->makeLinkObj( $title );
 1057+ }
 1058+ return $links;
 1059+ }
 1060+
 1061+ protected static function userWasBlocked( $name ) {
 1062+ $userpage = Title::makeTitle( NS_USER, $name );
 1063+ return wfGetDB( DB_SLAVE )->selectField( 'logging', '1',
 1064+ array( 'log_type' => array('block','suppress'),
 1065+ 'log_action' => 'block',
 1066+ 'log_namespace' => $userpage->getNamespace(),
 1067+ 'log_title' => $userpage->getDBKey() ),
 1068+ __METHOD__,
 1069+ array( 'USE INDEX' => 'page_time' ) );
 1070+ }
 1071+
 1072+ /**
 1073+ * Format a link to a group description page
 1074+ *
 1075+ * @param string $group
 1076+ * @return string
 1077+ */
 1078+ protected static function buildGroupLink( $group ) {
 1079+ static $cache = array();
 1080+ if( !isset( $cache[$group] ) )
 1081+ $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) );
 1082+ return $cache[$group];
 1083+ }
 1084+
 1085+ /**
 1086+ * @param Database $db
 1087+ * @param string $ip
 1088+ * @param string $xfor
 1089+ * @return mixed array/false conditions
 1090+ */
 1091+ protected function getIpConds( $db, $ip, $xfor = false ) {
 1092+ $type = ( $xfor ) ? 'xff' : 'ip';
 1093+ // IPv4 CIDR, 16-32 bits
 1094+ if( preg_match( '#^(\d+\.\d+\.\d+\.\d+)/(\d+)$#', $ip, $matches ) ) {
 1095+ if( $matches[2] < 16 || $matches[2] > 32 )
 1096+ return false; // invalid
 1097+ list( $start, $end ) = IP::parseRange( $ip );
 1098+ return array( 'cuc_'.$type.'_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) );
 1099+ } else if( preg_match( '#^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}/(\d+)$#', $ip, $matches ) ) {
 1100+ // IPv6 CIDR, 96-128 bits
 1101+ if( $matches[1] < 96 || $matches[1] > 128 )
 1102+ return false; // invalid
 1103+ list( $start, $end ) = IP::parseRange6( $ip );
 1104+ return array( 'cuc_'.$type.'_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) );
 1105+ } else if( preg_match( '#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#', $ip ) ) {
 1106+ // 32 bit IPv4
 1107+ $ip_hex = IP::toHex( $ip );
 1108+ return array( 'cuc_'.$type.'_hex' => $ip_hex );
 1109+ } else if( preg_match( '#^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}$#', $ip ) ) {
 1110+ // 128 bit IPv6
 1111+ $ip_hex = IP::toHex( $ip );
 1112+ return array( 'cuc_'.$type.'_hex' => $ip_hex );
 1113+ }
 1114+ // throw away this query, incomplete IP, these don't get through the entry point anyway
 1115+ return false; // invalid
 1116+ }
 1117+
 1118+ protected function getTimeConds( $period ) {
 1119+ if( !$period ) {
 1120+ return "1 = 1";
 1121+ }
 1122+ $dbr = wfGetDB( DB_SLAVE );
 1123+ $cutoff_unixtime = time() - ($period * 24 * 3600);
 1124+ $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400);
 1125+ $cutoff = $dbr->addQuotes( $dbr->timestamp( $cutoff_unixtime ) );
 1126+ return "cuc_timestamp > $cutoff";
 1127+ }
 1128+
 1129+ protected function showLog() {
 1130+ global $wgRequest, $wgOut;
 1131+ $type = $wgRequest->getVal( 'cuSearchType' );
 1132+ $target = $wgRequest->getVal( 'cuSearch' );
 1133+ $year = $wgRequest->getIntOrNull( 'year' );
 1134+ $month = $wgRequest->getIntOrNull( 'month' );
 1135+ $error = false;
 1136+ $dbr = wfGetDB( DB_SLAVE );
 1137+ $searchConds = false;
 1138+
 1139+ $wgOut->setPageTitle( wfMsg( 'checkuser-log' ) );
 1140+
 1141+ $wgOut->addHTML( $this->sk->makeKnownLinkObj( $this->getTitle(), wfMsgHtml( 'checkuser-log-return' ) ) );
 1142+
 1143+ if ( $type === null ) {
 1144+ $type = 'target';
 1145+ } elseif ( $type == 'initiator' ) {
 1146+ $user = User::newFromName( $target );
 1147+ if ( !$user || !$user->getID() ) {
 1148+ $error = 'checkuser-user-nonexistent';
 1149+ } else {
 1150+ $searchConds = array( 'cul_user' => $user->getID() );
 1151+ }
 1152+ } else /* target */ {
 1153+ $type = 'target';
 1154+ // Is it an IP?
 1155+ list( $start, $end ) = IP::parseRange( $target );
 1156+ if ( $start !== false ) {
 1157+ if ( $start == $end ) {
 1158+ $searchConds = array( 'cul_target_hex = ' . $dbr->addQuotes( $start ) . ' OR ' .
 1159+ '(cul_range_end >= ' . $dbr->addQuotes( $start ) . ' AND ' .
 1160+ 'cul_range_start <= ' . $dbr->addQuotes( $end ) . ')'
 1161+ );
 1162+ } else {
 1163+ $searchConds = array(
 1164+ '(cul_target_hex >= ' . $dbr->addQuotes( $start ) . ' AND ' .
 1165+ 'cul_target_hex <= ' . $dbr->addQuotes( $end ) . ') OR ' .
 1166+ '(cul_range_end >= ' . $dbr->addQuotes( $start ) . ' AND ' .
 1167+ 'cul_range_start <= ' . $dbr->addQuotes( $end ) . ')'
 1168+ );
 1169+ }
 1170+ } else {
 1171+ // Is it a user?
 1172+ $user = User::newFromName( $target );
 1173+ if ( $user && $user->getID() ) {
 1174+ $searchConds = array(
 1175+ 'cul_type' => 'userips',
 1176+ 'cul_target_id' => $user->getID(),
 1177+ );
 1178+ } else if ( $target ) {
 1179+ $error = 'checkuser-user-nonexistent';
 1180+ }
 1181+ }
 1182+ }
 1183+
 1184+ $searchTypes = array( 'initiator', 'target' );
 1185+ $select = "<select name=\"cuSearchType\" style='margin-top:.2em;'>\n";
 1186+ foreach ( $searchTypes as $searchType ) {
 1187+ if ( $type == $searchType ) {
 1188+ $checked = 'selected="selected"';
 1189+ } else {
 1190+ $checked = '';
 1191+ }
 1192+ $caption = wfMsgHtml( 'checkuser-search-' . $searchType );
 1193+ $select .= "<option value=\"$searchType\" $checked>$caption</option>\n";
 1194+ }
 1195+ $select .= "</select>";
 1196+
 1197+ $encTarget = htmlspecialchars( $target );
 1198+ $msgSearch = wfMsgHtml( 'checkuser-search' );
 1199+ $input = "<input type=\"text\" name=\"cuSearch\" value=\"$encTarget\" size=\"40\"/>";
 1200+ $msgSearchForm = wfMsgHtml( 'checkuser-search-form', $select, $input );
 1201+ $formAction = $this->getLogSubpageTitle()->escapeLocalURL();
 1202+ $msgSearchSubmit = '&nbsp;&nbsp;' . wfMsgHtml( 'checkuser-search-submit' ) . '&nbsp;&nbsp;';
 1203+
 1204+ $s = "<form method='get' action=\"$formAction\">\n" .
 1205+ "<fieldset><legend>$msgSearch</legend>\n" .
 1206+ "<p>$msgSearchForm</p>\n" .
 1207+ "<p>" . $this->getDateMenu( $year, $month ) . "&nbsp;&nbsp;&nbsp;\n" .
 1208+ "<input type=\"submit\" name=\"cuSearchSubmit\" value=\"$msgSearchSubmit\"/></p>\n" .
 1209+ "</fieldset></form>\n";
 1210+ $wgOut->addHTML( $s );
 1211+
 1212+ if ( $error !== false ) {
 1213+ $wgOut->addWikiText( '<div class="errorbox">' . wfMsg( $error ) . '</div>' );
 1214+ return;
 1215+ }
 1216+
 1217+ $pager = new CheckUserLogPager( $this, $searchConds, $year, $month );
 1218+ $wgOut->addHTML(
 1219+ $pager->getNavigationBar() .
 1220+ $pager->getBody() .
 1221+ $pager->getNavigationBar() );
 1222+ }
 1223+
 1224+ /**
 1225+ * @return string Formatted HTML
 1226+ * @param int $year
 1227+ * @param int $month
 1228+ */
 1229+ protected function getDateMenu( $year, $month ) {
 1230+ # Offset overrides year/month selection
 1231+ if( $month && $month !== -1 ) {
 1232+ $encMonth = intval( $month );
 1233+ } else {
 1234+ $encMonth = '';
 1235+ }
 1236+ if ( $year ) {
 1237+ $encYear = intval( $year );
 1238+ } else if( $encMonth ) {
 1239+ $thisMonth = intval( gmdate( 'n' ) );
 1240+ $thisYear = intval( gmdate( 'Y' ) );
 1241+ if( intval($encMonth) > $thisMonth ) {
 1242+ $thisYear--;
 1243+ }
 1244+ $encYear = $thisYear;
 1245+ } else {
 1246+ $encYear = '';
 1247+ }
 1248+ return Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
 1249+ Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) .
 1250+ ' '.
 1251+ Xml::label( wfMsg( 'month' ), 'month' ) . ' '.
 1252+ Xml::monthSelector( $encMonth, -1 );
 1253+ }
 1254+
 1255+ protected function addLogEntry( $logType, $targetType, $target, $reason, $targetID = 0 ) {
 1256+ global $wgUser;
 1257+
 1258+ if ( $targetType == 'ip' ) {
 1259+ list( $rangeStart, $rangeEnd ) = IP::parseRange( $target );
 1260+ $targetHex = $rangeStart;
 1261+ if ( $rangeStart == $rangeEnd ) {
 1262+ $rangeStart = $rangeEnd = '';
 1263+ }
 1264+ } else {
 1265+ $targetHex = $rangeStart = $rangeEnd = '';
 1266+ }
 1267+
 1268+ $dbw = wfGetDB( DB_MASTER );
 1269+ $cul_id = $dbw->nextSequenceValue( 'cu_log_cul_id_seq' );
 1270+ $dbw->insert( 'cu_log',
 1271+ array(
 1272+ 'cul_id' => $cul_id,
 1273+ 'cul_timestamp' => $dbw->timestamp(),
 1274+ 'cul_user' => $wgUser->getID(),
 1275+ 'cul_user_text' => $wgUser->getName(),
 1276+ 'cul_reason' => $reason,
 1277+ 'cul_type' => $logType,
 1278+ 'cul_target_id' => $targetID,
 1279+ 'cul_target_text' => $target,
 1280+ 'cul_target_hex' => $targetHex,
 1281+ 'cul_range_start' => $rangeStart,
 1282+ 'cul_range_end' => $rangeEnd,
 1283+ ), __METHOD__ );
 1284+ return true;
 1285+ }
 1286+}
\ No newline at end of file
Property changes on: branches/new-checkuser/SpecialCheckUser.php
___________________________________________________________________
Name: svn:eol-style
11287 + native

Comments

#Comment by Bryan (talk | contribs)   18:30, 15 May 2009

A couple of notes:

  • Database functions and interface functions are still mixed up. Try to avoid using wfMsg* and db related functions in the same function whenever you are trying to split of fore and background logic.
  • The preg_match calls in the API appear to be also used in the SpecialPage frontend and could thus be split into a separate function.
  • There is no reason to use p.q.r.s/xff when you want to check for xff in the api; add a boolean xff parameter.
  • An error message should be thrown when type is invalid. I believe there are standard API functions for this.
#Comment by Soxred93 (talk | contribs)   21:19, 17 May 2009

Bryan, that's because I didn't do anything besides move some functions around to better filenames.

#Comment by Catrope (talk | contribs)   19:30, 18 May 2009

Comments on the API module as requested on IRC:

  • The type parameter should use something like
    'type' => array('subuserips', 'subipedits', 'subipusers', 'subuseredits'),
    
    That way internal parameter handling code takes care of validation.
  • The input format for the duration parameter should be documented; my gut says that a timestamp parameter could probably be used, though:
    'duration' => array(ApiBase::PARAM_TYPE => 'timestamp'),
    
    That also accepts stuff like "3 weeks ago".
  • Like Bryan said, XFF should be a boolean field rather than being appended to the IP:
    'xff' => false,
    
    With that done, you should use the regular User functions to handle the user parameter.
  • $this->doUserIPsRequest() and friends are missing, looks like this is skeleton code.

Status & tagging log