Page MenuHomePhabricator

checkuser.patch

Authored By
bzimport
Nov 21 2014, 8:48 PM
Size
35 KB
Referenced Files
None
Subscribers
None

checkuser.patch

Index: CheckUser.i18n.php
===================================================================
--- CheckUser.i18n.php (revision 20304)
+++ CheckUser.i18n.php (working copy)
@@ -8,10 +8,28 @@
$wgCheckUserMessages = array();
$wgCheckUserMessages['en'] = array(
+ 'checkuser-summary' => 'This tool scans recent changes to retrieve the IPs used by a user or show the edit/user data for an IP.
+ Users and edits can be retrieved with an XFF IP by appending the IP with "/xff". IPv4 (CIDR 16-32) and IPv6 (CIDR 64-128) are supported.
+ Use this in accordance with policy. ',
+ 'checkuser-logcase' => 'The log search is case sensitive.',
'checkuser' => 'Check user',
'group-checkuser' => 'Check users',
'group-checkuser-member' => 'Check user',
'grouppage-checkuser' => '{{ns:project}}:Check user',
+ 'checkuser-reason' => 'Reason',
+ 'checkuser-showlog' => 'Show log',
+ 'checkuser-log' => 'Checkuser log',
+ 'checkuser-query' => 'Query recent changes',
+ 'checkuser-target' => 'User or IP',
+ 'checkuser-users' => 'Get users',
+ 'checkuser-edits' => 'Get edits from IP',
+ 'checkuser-ips' => 'Get IPs',
+ 'checkuser-search' => 'Search',
+ 'checkuser-empty' => 'The log contains no items.',
+ 'checkuser-nomatch' => 'No matches found.',
+ 'checkuser-check' => 'Check',
+ 'checkuser-log-fail' => 'Unable to add log entry',
+ 'checkuser-nolog' => 'No log file found.'
);
$wgCheckUserMessages['ca'] = array(
'checkuser' => 'Comprova l\'usuari',
@@ -37,11 +55,36 @@
'group-checkuser-member' => 'Osoitepaljastimen käyttäjä',
'grouppage-checkuser' => '{{ns:project}}:Osoitepaljastin',
);
+$wgCheckUserMessages['es'] = array(
+ 'checkuser' => 'Verificador del usuarios',
+ 'group-checkuser' => 'Verificadors del usuarios',
+ 'group-checkuser-member' => 'Verificador del usuarios',
+ 'grouppage-checkuser' => '{{ns:project}}:verificador del usuarios',
+);
$wgCheckUserMessages['fr'] = array(
- 'checkuser' => 'Vérificateur d’utilisateur',
- 'group-checkuser' => 'Vérificateurs d’utilisateur',
- 'group-checkuser-member' => 'Vérificateur d’utilisateur',
- 'grouppage-checkuser' => '{{ns:project}}:Vérificateur d’utilisateur',
+ 'checkuser-summary' => 'Cet outil balaye les changements récents pour rechercher l\'IPS employé par un utilisateur,
+ montrer tous édite par un IP, ou énumère les utilisateurs qui ont employé les IPs. Les utilisateur et modifications peut
+ être trouvé avec une IP XFF si il finit avec « /xff ». IPv4 (CIDR 16-32) et IPv6(CIDR 64-128) sont soutenus.
+ Employer ceci selon les chaînes de policy.',
+ 'checkuser-logcase' => 'La recherche de notation est cas sensible.',
+ 'checkuser' => 'Vérificateur d\'utilisateur',
+ 'group-checkuser' => 'Vérificateurs d\'utilisateur',
+ 'group-checkuser-member' => 'Vérificateur d\'utilisateur',
+ 'grouppage-checkuser' => '{{ns:projet}}:Vérificateur d\'utilisateur',
+ 'checkuser-reason' => 'Expanation ',
+ 'checkuser-showlog' => 'Montrer la notation',
+ 'checkuser-log' => 'Notation de Vérificateur d\'utilisateur',
+ 'checkuser-query' => 'Recherche par les changements récents',
+ 'checkuser-target' => 'Username ou IP',
+ 'checkuser-users' => 'Obtenir les users',
+ 'checkuser-edits' => 'Obtenir les modifications de l\'IP',
+ 'checkuser-ips' => 'Obtenir les IPs',
+ 'checkuser-search' => 'Recherche',
+ 'checkuser-empty' => 'La notation ne contient aucun article',
+ 'checkuser-nomatch' => 'Rien n\'a trouvé.',
+ 'checkuser-check' => 'Recherche',
+ 'checkuser-log-fail' => 'Incapable d\'ajouter l\'entrée de notation.',
+ 'checkuser-nolog' => 'Aucun dossier de notation trouvé.'
);
$wgCheckUserMessages['he'] = array(
'checkuser' => 'בדיקת משתמש',
Index: CheckUser.php
===================================================================
--- CheckUser.php (revision 20304)
+++ CheckUser.php (working copy)
@@ -14,6 +14,70 @@
$wgCheckUserLog = '/home/wikipedia/logs/checkuser.log';
+# How long to keep CU data?
+$wgCUDMaxAge = 3 * 30 * 24 * 3600;
+
+#Recent changes data hook
+$wgHooks['RecentChange_save'][] = 'efUpdateCheckUserData';
+
+ /**
+ * Hook function for RecentChange_save
+ * Saves user data into the cu_changes table
+ */
+ function efUpdateCheckUserData( $rc ) {
+ $dbw = wfGetDB( DB_MASTER );
+ extract( $rc->mAttribs );
+
+ // Convert all IPs to IPv6 if needed
+ $ip = wfGetIP();
+
+ $xff = wfGetForwardedFor();
+ $xff_ip = wfGetLastIPfromXFF( $xff );
+
+ $agent = wfGetAgent();
+
+ $rc_actiontext='';
+ if ( $rc_type==RC_LOG ) {
+ $title = Title::newFromText( $rc_title, $rc_namespace );
+ $rc_actiontext = LogPage::actionText( $rc_log_type, $rc_log_action, $title, NULL, LogPage::extractParams($rc_params), false, false, true );
+ }
+
+ $dbw->insert( 'cu_changes',
+ array(
+ 'cuc_namespace' => $rc_namespace,
+ 'cuc_title' => $rc_title,
+ 'cuc_minor' => $rc_minor,
+ 'cuc_user' => $rc_user,
+ 'cuc_user_text' => $rc_user_text,
+ 'cuc_actiontext' => $rc_actiontext,
+ 'cuc_comment' => $rc_comment,
+ 'cuc_page_id' => $rc_cur_id,
+ 'cuc_this_oldid' => $rc_this_oldid,
+ 'cuc_last_oldid' => $rc_last_oldid,
+ 'cuc_type' => $rc_type,
+ 'cuc_timestamp' => $rc_timestamp,
+ 'cuc_ip' => $ip,
+ 'cuc_ip_hex' => ($ip) ? IP::toHex( $ip ) : null,
+ 'cuc_xff' => $xff,
+ 'cuc_xff_hex' => ($xff_ip) ? IP::toHex( $xff_ip ) : null,
+ 'cuc_agent' => $agent,
+ ), __METHOD__
+ );
+
+ #Every 1000th edit, prune the checkuser changes table.
+ wfSeedRandom();
+ if ( 0 == mt_rand( 0, 999 ) ) {
+ # Periodically flush old entries from the recentchanges table.
+ global $wgCUDMaxAge;
+
+ $dbw =& wfGetDB( DB_MASTER );
+ $cutoff = $dbw->timestamp( time() - $wgCUDMaxAge );
+ $recentchanges = $dbw->tableName( 'cu_changes' );
+ $sql = "DELETE FROM $recentchanges WHERE cuc_timestamp < '{$cutoff}'";
+ $dbw->query( $sql );
+ }
+ }
+
if ( !function_exists( 'extAddSpecialPage' ) ) {
require( dirname(__FILE__) . '/../ExtensionFunctions.php' );
}
Index: checkuser.sql
===================================================================
--- checkuser.sql (revision 0)
+++ checkuser.sql (revision 0)
@@ -0,0 +1,90 @@
+-- Tables for the CheckUser extension
+-- vim: autoindent syn=mysql sts=2 sw=2
+-- Replace /*$wgDBprefix*/ with the proper prefix
+
+CREATE TABLE /*$wgDBprefix*/cu_changes (
+ -- Primary key
+ cuc_id INTEGER NOT NULL AUTO_INCREMENT,
+
+ -- When pages are renamed, their RC entries do _not_ change.
+ cuc_namespace int NOT NULL default '0',
+ cuc_title varchar(255) binary NOT NULL default '',
+
+ -- user.user_id
+ cuc_user INTEGER NOT NULL DEFAULT 0,
+ cuc_user_text VARCHAR(255) NOT NULL DEFAULT '',
+
+ -- Edit summary
+ cuc_actiontext varchar(255) binary NOT NULL default '',
+ cuc_comment varchar(255) binary NOT NULL default '',
+ cuc_minor bool NOT NULL default '0',
+
+ -- Key to page_id (was cur_id prior to 1.5).
+ -- This will keep links working after moves while
+ -- retaining the at-the-time name in the changes list.
+ cuc_page_id int(10) unsigned NOT NULL default '0',
+
+ -- rev_id of the given revision
+ cuc_this_oldid int(10) unsigned NOT NULL default '0',
+
+ -- rev_id of the prior revision, for generating diff links.
+ cuc_last_oldid int(10) unsigned NOT NULL default '0',
+
+ -- Edit/new/log
+ cuc_type tinyint(3) unsigned NOT NULL default '0',
+
+ -- Event timestamp
+ cuc_timestamp CHAR(14) NOT NULL default '',
+
+ -- IP address, visible
+ cuc_ip VARCHAR(255) NULL default '',
+
+ -- IP address as hexidecimal
+ cuc_ip_hex VARCHAR(255) default NULL,
+
+ -- XFF header, visible, all data
+ cuc_xff VARCHAR(255) BINARY NULL default '',
+
+ -- XFF header, last IP, as hexidecimal
+ cuc_xff_hex VARCHAR(255) default NULL,
+
+ -- User agent
+ cuc_agent VARCHAR(255) BINARY default NULL,
+
+ PRIMARY KEY cuc_id (cuc_id),
+ INDEX (cuc_ip_hex),
+ INDEX (cuc_user),
+ INDEX (cuc_xff_hex),
+ INDEX (cuc_timestamp)
+) TYPE=InnoDB;
+
+-- Copy important parts of recentchanges into checkuser data table
+INSERT INTO /*$wgDBprefix*/cu_changes (
+ cuc_timestamp,
+ cuc_user,
+ cuc_user_text,
+ cuc_namespace,
+ cuc_title,
+ cuc_comment,
+ cuc_minor,
+ cuc_page_id,
+ cuc_this_oldid,
+ cuc_last_oldid,
+ cuc_type,
+ cuc_ip,
+ cuc_ip_hex)
+ SELECT
+ rc_timestamp,
+ rc_user,
+ rc_user_text,
+ rc_namespace,
+ rc_title,
+ rc_comment,
+ rc_minor,
+ rc_cur_id,
+ rc_this_oldid,
+ rc_last_oldid,
+ rc_type,
+ rc_ip,
+ HEX(INET_ATON(rc_ip))
+ FROM /*$wgDBprefix*/recentchanges;
\ No newline at end of file
Index: CheckUser_body.php
===================================================================
--- CheckUser_body.php (revision 20304)
+++ CheckUser_body.php (working copy)
@@ -8,7 +8,7 @@
# Add messages
global $wgMessageCache, $wgCheckUserMessages;
foreach( $wgCheckUserMessages as $language => $messages ) {
- $wgMessageCache->addMessages( $messages, $language );
+ $wgMessageCache->addMessages( $messages, $language );
}
class CheckUser extends SpecialPage
@@ -24,297 +24,409 @@
$wgOut->permissionRequired( 'checkuser' );
return;
}
-
+
$this->setHeaders();
- $ip = $wgRequest->getText( 'ip' );
$user = $wgRequest->getText( 'user' );
$reason = $wgRequest->getText( 'reason' );
- $subipusers = $wgRequest->getBool( 'subipusers' );
- $subipedits = $wgRequest->getBool( 'subipedits' );
- $subuser = $wgRequest->getBool( 'subuser' );
- #enter fix hack
- $suball = $wgRequest->getBool( 'suball' );
-
- $this->doTop( $ip, $user, $reason);
- if ( $ip && $subipedits ) {
- $this->doIPEditsRequest( $ip, $reason);
- } else if ( $ip && $subipusers ) {
- $this->doIPUsersRequest( $ip , $reason );
- } else if ( $user && $subuser ) {
- $this->doUserRequest( $user , $reason );
- } else if ( !$user && $ip && $suball ) {
- $this->doIPEditsRequest( $ip, $reason );
- } else if ( $user && !$ip && $suball ) {
- $this->doUserRequest( $user, $reason );
+ $checktype = $wgRequest->getVal( 'checktype' );
+
+ $subipedits=0; $subipusers=0; $subuserips=0;
+ if( $checktype=='subipedits' ) {
+ $subipedits=1;
+ } else if( $checktype=='subipusers' ) {
+ $subipusers=1;
+ } else if( $checktype=='subuserips' ) {
+ $subuserips=1;
+ }
+ # An IPv4?
+ if ( preg_match('#^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(/\d{1,2}|)$#', $user) ) {
+ $ip = $user; $name = ''; $xff = '';
+ } else if ( preg_match('#^[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4})+(/\d{1,3}|)$#', $user) ) {
+ # An IPv6?
+ $ip = IP::sanitizeIP($user); $name = ''; $xff = '';
+ } else if ( preg_match('#^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(/\d{1,2}|)/xff$#', $user, $matches) ) {
+ # An IPv4 XFF string?
+ list( $junk, $xffip, $xffbit) = $matches;
+ $ip = ''; $name = ''; $xff = $xffip . $xffbit;
+ } else if ( preg_match('#^([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4})+)(/\d{1,3}|)$#', $user, $matches) ) {
+ # An IPv6 XFF string?
+ list( $junk, $xffip, $xffbit) = $matches;
+ $ip = ''; $name = ''; $xff = IP::sanitizeIP($xffip) . $xffbit;
} else {
- $this->showLog();
+ # A user?
+ $ip = ''; $name = $user; $xff = '';
}
+
+ $this->doTop( $user, $reason, $subipedits, $subipusers, $subuserips, $ip, $xff, $name);
+ $this->showLog( $user );
+
+ if ( $subuserips ) {
+ $this->doUserIPsRequest( $name, $reason );
+ } else if ( $xff && $subipedits ) {
+ $this->doIPEditsRequest( $xff, true, $reason);
+ } else if ( $subipedits ) {
+ $this->doIPEditsRequest( $ip, false, $reason);
+ } else if ( $xff && $subipusers ) {
+ $this->doIPUsersRequest( $xff, true, $reason );
+ } else if ( $subipusers ) {
+ $this->doIPUsersRequest( $ip, false, $reason );
+ }
}
- function doTop( $ip, $user, $reason ) {
+ function doTop( $user, $reason, $subipedits, $subipusers, $subuserips, $ip, $xff, $name ) {
global $wgOut, $wgTitle;
$action = $wgTitle->escapeLocalUrl();
- $encIp = htmlspecialchars( $ip );
$encUser = htmlspecialchars( $user );
$encReason = htmlspecialchars( $reason );
-
- #$wgOut->addHTML( wfMsg('checkuser-summary') );
- $wgOut->addHTML( <<<EOT
-<form name="checkuser" action="$action" method="post">
-<table border='0' cellpadding='5'><tr>
- <td>Reason:</td>
- <td><table border='0' cellpadding='0'><tr>
- <td><input type="text" name="reason" value="$encReason" maxlength='150' size='40' /></td>
- </tr></table></td><td></td>
- <td><input type="submit" name="suball" value="OK" style="visibility: hidden;"/></td>
-</tr><tr>
- <td>User:</td>
- <td><table border='0' cellpadding='0'>
- <tr><td><input type="text" name="user" value="$encUser" width="50" /></td></tr>
- <tr><td><input type="submit" name="subuser" value="Get IPs" /></td></tr>
- </table></td><td>IP:</td>
- <td><table border='0' cellpadding='0'>
- <tr><td><input type="text" name="ip" value="$encIp" width="50"/></td></tr>
- <tr><td><input type="submit" name="subipedits" value="Get edits" style="font-weight: bold;"/><input type="submit" name="subipusers" value="Get users" /></td></tr>
- </table></td>
-</tr></table></form><hr />
-EOT
- );
+ # Fill in requested type if it makes sense
+ $encipusers=0; $encipedits=0; $encuserips=0;
+ if ($subipusers && ($ip || $xff))
+ $encipusers = 1;
+ else if ($subipedits && ($ip || $xff))
+ $encipedits = 1;
+ else if ($subuserips && $name)
+ $encuserips = 1;
+ # Defaults otherwise
+ else if ($ip || $xff)
+ $encipedits = 1;
+ else if ($name)
+ $encuserips = 1;
+ # Compile our nice form
+ # User box length should fit things like "2001:0db8:85a3:08d3:1319:8a2e:0370:7344/100/xff"
+ $wgOut->addWikiText( wfMsgHtml('checkuser-summary') );
+ $form = "<form name='checkuser' action='$action' method='post'>";
+ $form .= "<fieldset><legend>".wfMsgHtml("checkuser-query")."</legend>";
+ $form .= "<table border='0' cellpadding='5'><tr>";
+ $form .= "<td>".wfMsgHtml("checkuser-target").":</td>";
+ $form .= "<td>".Xml::input('user', 46, $encUser, array( 'id' => 'checktarget' ) )."</td>";
+ $form .= "</tr><tr>";
+ $form .= "<td></td><td class='checkuserradios'><table border='0' cellpadding='3'><tr>";
+ $form .= "<td>".Xml::radio( 'checktype', 'subuserips', $encuserips ).wfMsgHtml("checkuser-ips")."</td>";
+ $form .= "<td>".Xml::radio( 'checktype', 'subipedits', $encipedits ).wfMsgHtml("checkuser-edits")."</td>";
+ $form .= "<td>".Xml::radio( 'checktype', 'subipusers', $encipusers ).wfMsgHtml("checkuser-users")."</td>";
+ $form .= "</tr></table></td>";
+ $form .= "</tr><tr>";
+ $form .= "<td>".wfMsgHtml("checkuser-reason").":</td>";
+ $form .= "<td>".Xml::input('reason', 46, $encReason, array( 'maxlength' => '150', 'id' => 'checkreason') );
+ $form .= "&nbsp; &nbsp;".Xml::submitButton( wfMsgHtml( 'checkuser-check' ) )."</td>";
+ $form .= "</tr></table></fieldset></form>";
+ # Output form
+ $wgOut->addHTML($form);
}
#shows all edits in Recent Changes by this IP (or range) and who made them
- function doIPEditsRequest( $ip, $reason = '') {
+ function doIPEditsRequest( $ip, $xfor = false, $reason = '') {
global $wgUser, $wgOut, $wgLang, $wgTitle, $wgDBname;
$fname = 'CheckUser::doIPEditsRequest';
+ #invalid IPs are passed in as a blank string
+ if (!$ip) {
+ $s = wfMsgHtml('badipaddress');
+ $wgOut->addHTML( $s );
+ return;
+ }
+
+ $xnote = ($xfor) ? ' XFF' : '';
if ( !$this->addLogEntry( time() , $wgUser->getName() ,
- 'got edits for' , htmlspecialchars( $ip ) , $wgDBname , $reason))
+ "got edits for$xnote" , htmlspecialchars( $ip ) , $wgDBname , $reason))
{
- $wgOut->addHTML( '<p>Unable to add log entry</p>' );
+ $wgOut->addHTML( '<p>'.wfMsgHtml('checkuser-log-fail').'</p>' );
}
- $dbr =& wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'recentchanges', array( '*' ), $this->getIpConds( $dbr, $ip ), $fname,
- array( 'ORDER BY' => 'rc_timestamp DESC' ) );
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $ip_conds = $this->getIpConds( $dbr, $ip, $xfor );
+ $res = $dbr->select( 'cu_changes', '*', $ip_conds, $fname,
+ array( 'ORDER BY' => 'cuc_timestamp DESC' ) );
- $counter = 1;
if ( !$dbr->numRows( $res ) ) {
- $s = "No results\n";
+ $s = wfMsgHtml("checkuser-nomatch")."\n";
} else {
global $IP;
- require_once( $IP.'/includes/RecentChange.php' );
- require_once( $IP.'/includes/ChangesList.php' );
+ require_once( $IP.'/includes/LogPage.php' );
+ $this->skin = $wgUser->getSkin();
+ $this->preCacheMessages();
- if ( in_array( 'newfromuser', array_map( 'strtolower', get_class_methods( 'ChangesList' ) ) ) ) {
- // MW >= 1.6
- $list = ChangesList::newFromUser( $wgUser );
- } else {
- // MW < 1.6
- $sk =& $wgUser->getSkin();
- $list = new ChangesList( $sk );
- }
- $s = $list->beginRecentChangesList();
- $ip_bloc3 = $this->parse17_23CIDR( $ip );
+ $s = '';
while ( ($row = $dbr->fetchObject( $res ) ) != false ) {
- #hackish culling of 16 CIDR results to avoid messier SQL query
- if ( $ip_bloc3==null || $this->isIn17_23CIDR( $row->rc_ip, $ip_bloc3 )) {
- $rc = RecentChange::newFromRow( $row );
- $rc->counter = $counter++;
- $s .= $list->recentChangesLine( $rc, false );
- }
+ $s .= $this->CUChangesLine( $row );
}
- $s .= $list->endRecentChangesList();
+ $s .= '</ul>';
}
- #check if anything left after culling
- if ( $counter===1 ) $s = "No results.\n";
+
$wgOut->addHTML( $s );
$dbr->freeResult( $res );
}
+
+ /**
+ * As we use the same small set of messages in various methods and that
+ * they are called often, we call them once and save them in $this->message
+ */
+ function preCacheMessages() {
+ // Precache various messages
+ if( !isset( $this->message ) ) {
+ foreach( explode(' ', 'diff hist minoreditletter newpageletter blocklink log' ) as $msg ) {
+ $this->message[$msg] = wfMsgExt( $msg, array( 'escape') );
+ }
+ }
+ }
+
+ /**
+ * @param $row
+ * @return a streamlined recent changes line with IP data
+ */
+ function CUChangesLine( $row ) {
+ global $wgLang;
+
+ # Add date headers
+ $date = $wgLang->date( $row->cuc_timestamp, true, true );
+ if ( !isset($this->lastdate) ) {
+ $this->lastdate = $date;
+ $line = "\n<h4>$date</h4>\n<ul class=\"special\">";
+ } else if( $date != $this->lastdate ) {
+ $line = "</ul>\n<h4>$date</h4>\n<ul class=\"special\">";
+ $this->lastdate = $date;
+ } else {
+ $line='';
+ }
+ $line .= "<li>";
+ # Create diff/hist/page links
+ $line .= $this->getLinkFromRow( $row );
+ # Show date
+ $line .= ' . . ' . $wgLang->time( $row->cuc_timestamp, true, true ) . ' . . ';
+ # Userlinks
+ $line .= $this->skin->userLink( $row->cuc_user, $row->cuc_user_text );
+ $line .= $this->skin->userToolLinks( $row->cuc_user, $row->cuc_user_text );
+ # Action text, hackish ...
+ if ( $row->cuc_actiontext )
+ $line .= ' ' . $this->skin->formatComment( $row->cuc_actiontext ) . ' ';
+ # Comment
+ $line .= $this->skin->commentBlock( $row->cuc_comment );
+ $line .= '<br/>&nbsp; &nbsp; &nbsp; &nbsp; <small>';
+ # IP
+ $line .= ' <strong>IP</strong>: '.htmlspecialchars($row->cuc_ip);
+ # XFF
+ if ( $row->cuc_xff !=null)
+ $line .= ' <strong>XFF</strong>: '.htmlspecialchars($row->cuc_xff);
+ $line .= "</small></li>\n";
+
+ return $line;
+ }
-#Lists all users in Recent Changes who used an IP
+ /**
+ * @param $row
+ * @create diff/hist/page link
+ */
+ function getLinkFromRow( $row ) {
+ if ( $row->cuc_type == RC_LOG && $row->cuc_namespace == NS_SPECIAL ) {
+ //Log items (old format) and events to logs
+ list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $row->cuc_title );
+ $logname = LogPage::logName( $logtype );
+ $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
+ $links = '(' . $this->skin->makeKnownLinkObj( $title, $logname ) . ')';
+ } elseif ( $row->cuc_type == RC_LOG ) {
+ //Log items
+ $specialTitle = SpecialPage::getTitleFor( 'Log' );
+ $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
+ $links = '(' . $this->skin->makeKnownLinkObj( $specialTitle, $this->message['log'],
+ wfArrayToCGI( array('page' => $title->getPrefixedText() ) ) ) . ')';
+ } elseif ( !is_null( $row->cuc_this_oldid ) ) {
+ //Everything else
+ $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
+ #new pages
+ if ( $row->cuc_type == RC_NEW ) {
+ $links = '(' . $this->message['diff'] . ') ';
+ } else {
+ #diff link
+ $links = ' (' . $this->skin->makeKnownLinkObj( $title, $this->message['diff'],
+ wfArrayToCGI( array(
+ 'curid' => $row->cuc_page_id,
+ 'diff' => $row->cuc_this_oldid,
+ 'oldid' => $row->cuc_last_oldid ) ) ) . ') ';
+ }
+ #history link
+ $links .= ' (' . $this->skin->makeKnownLinkObj( $title, $this->message['hist'],
+ wfArrayToCGI( array(
+ 'curid' => $row->cuc_page_id,
+ 'action' => 'history' ) ) ) . ') . . ';
+ #some basic flags
+ if ( $row->cuc_type == RC_NEW )
+ $links .= '<span class="newpage">' . $this->message['newpageletter'] . '</span>';
+ if ( $row->cuc_minor )
+ $links .= '<span class="minor">' . $this->message['minoreditletter'] . '</span>';
+ #page link
+ $links .= ' ' . $this->skin->makeLinkObj( $title );
+ } else {
+ $links = '';
+ }
+ return $links;
+ }
+
+#Lists all users in recent changes who used an IP, newest to oldest down
#Outputs usernames, latest and earliest found edit date, and count
-#Ordered by most recent edit, from newest to oldest down
- function doIPUsersRequest( $ip, $reason = '' ) {
+#List unique IPs used for each user in time order, list corresponding user agent
+ function doIPUsersRequest( $ip, $xfor = false, $reason = '' ) {
global $wgUser, $wgOut, $wgLang, $wgTitle, $wgDBname;
$fname = 'CheckUser::doIPUsersRequest';
+ #invalid IPs are passed in as a blank string
+ if (!$ip) {
+ $s = wfMsgHtml('badipaddress');
+ $wgOut->addHTML( $s );
+ return;
+ }
+
+ $xnote = ($xfor) ? ' XFF' : '';
if ( !$this->addLogEntry( time(), $wgUser->getName() ,
- 'got users for' , htmlspecialchars( $ip ) , $wgDBname , $reason))
+ "got users for$xnote" , htmlspecialchars( $ip ) , $wgDBname , $reason))
{
- $wgOut->addHTML( '<p>Unable to add log entry</p>' );
+ $wgOut->addHTML( '<p>'.wfMsgHtml('checkuser-log-fail').'</p>' );
}
- $users_id=Array(); $users_first=Array(); $users_last=Array(); $users_edits=Array();
+ $users_first=Array(); $users_last=Array(); $users_edits=Array(); $users_ips_agents=Array();
- $dbr =& wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'recentchanges', array( 'rc_user_text' , 'rc_user' , 'rc_timestamp', 'rc_ip'),
- $this->getIpConds( $dbr, $ip ), $fname, array( 'ORDER BY' => 'rc_timestamp DESC' ) );
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $ip_conds = $this->getIpConds( $dbr, $ip, $xfor );
+ $res = $dbr->select( 'cu_changes', array( 'cuc_user_text', 'cuc_timestamp', 'cuc_ip', 'cuc_agent', 'cuc_xff' ),
+ $ip_conds, $fname, array( 'ORDER BY' => 'cuc_timestamp DESC' ) );
if ( !$dbr->numRows( $res ) ) {
- $s = "No results.\n";
+ $s = wfMsgHtml("checkuser-nomatch")."\n";
} else {
- $s = '';
- $ip_bloc3 = $this->parse17_23CIDR( $ip );
while ( ($row = $dbr->fetchObject( $res ) ) != false ) {
- #hackish culling of 16 CIDR results to avoid messier SQL query
- if ( $ip_bloc3==null || $this->isIn17_23CIDR( $row->rc_ip, $ip_bloc3 )) {
- if ( !array_key_exists( $row->rc_user_text, $users_edits ) ) {
- $users_first[$row->rc_user_text]=$row->rc_timestamp;
- $users_edits[$row->rc_user_text]=0;
- $users_id[$row->rc_user_text]=$row->rc_user;
- }
- $users_edits[$row->rc_user_text]+=1;
- $users_last[$row->rc_user_text]=$row->rc_timestamp;
+ if ( !array_key_exists( $row->cuc_user_text, $users_edits ) ) {
+ $users_last[$row->cuc_user_text]=$row->cuc_timestamp;
+ $users_edits[$row->cuc_user_text]=0;
+ $users_ips_info[$row->cuc_user_text]='';
}
+ $users_edits[$row->cuc_user_text]+=1;
+ $users_first[$row->cuc_user_text]=$row->cuc_timestamp;
+ $ip_xff = ( $row->cuc_xff ) ? htmlspecialchars("$row->cuc_ip <b>XFF</b>: $row->cuc_xff") : htmlspecialchars($row->cuc_ip);
+ if ( strpos( $users_ips_info[$row->cuc_user_text], $ip_xff ) === false ) {
+ $users_ips_info[$row->cuc_user_text]="<li>$ip_xff <br/><i>$row->cuc_agent</i></li>\n".$users_ips_info[$row->cuc_user_text];
+ }
}
$links = new Linker();
- #check if anything left after culling
- if ( count( $users_edits ) ==0 ) $s = "No results.\n";
+ $s = '<ul>';
foreach ( $users_edits as $name=>$count ) {
- $links->skin = $wgUser->getSkin();
- #use checkip for IPs
- if ( $users_id[$name]==0 ) $checktype='ip=';
- else $checktype='user=';
- #hack, ALWAYS show contribs links
- $toollinks = $links->skin->userToolLinks( -1 , $name );
- $s .= '<li><a href="' . $wgTitle->escapeLocalURL( $checktype . urlencode( $name ) ) . '">' . htmlspecialchars( $name ) . '</a> ' .$toollinks .
- ' (' . wfmsg('histlast') . ': ' . $wgLang->timeanddate( $users_first[$name] ) . ') ' . ' (' . wfmsg('histfirst') . ': ' . $wgLang->timeanddate( $users_last[$name] ) . ') ' .
- ' [<strong>' . $count . '</strong>]' . '</li>';
+ $links->skin = $wgUser->getSkin();
+ #hack, ALWAYS show contribs links
+ $toollinks = $links->skin->userToolLinks( -1 , $name );
+ $s .= '<li><a href="' . $wgTitle->escapeLocalURL( 'user=' . urlencode( $name ) ) . '">' . htmlspecialchars( $name ) . '</a> ' .
+ $toollinks . ' (' . $wgLang->timeanddate( $users_first[$name] ) . ' -- ' . $wgLang->timeanddate( $users_last[$name] ) . ') ' .
+ ' [<strong>' . $count . '</strong>]<ol>' . $users_ips_info[$name] . '</ol></li>';
}
+ $s .= '</ul>';
}
- $wgOut->addHTML( '<ul>' );
$wgOut->addHTML( $s );
- $wgOut->addHTML( '</ul>' );
$dbr->freeResult( $res );
}
/**
- * Since we have stuff stored in text format, this only works easily
- * for some simple cases, such as /16 and /24.
* @param Database $db
* @param string $ip
+ * @param string $xfor
* @return array conditions
*/
- function getIpConds( $db, $ip ) {
- // haaaack
- if( preg_match( '#^(\d+)\.(\d+)\.(\d+)\.(\d+)/(\d+)$#', $ip, $matches ) ) {
- list( $junk, $a, $b, $c, $d, $bits ) = $matches;
- if( $bits == 32 ) {
- $match = "$a.$b.$c.$d";
- } elseif( $bits == 24 ) {
- $match = "$a.$b.$c.%";
- } elseif( $bits >= 16 && $bits < 24) {
- //results culled after query
- $match = "$a.$b.%";
- } else {
- // Other sizes not supported. /8 is too big
- $match = $ip;
- }
- return array( 'rc_ip LIKE ' . $db->addQuotes( $match ) );
+ function getIpConds( $db, $ip, $xfor = false ) {
+ $type = ( $xfor ) ? 'xff' : 'ip';
+ // IPv4 CIDR, 16-32 bits
+ if( preg_match( '#^(\d+\.\d+\.\d+\.\d+)/(\d+)$#', $ip, $matches ) ) {
+ if ( $matches[2] < 16 || $matches[2] > 32 )
+ return array( 'cuc_'.$type.'_hex' => -1 );
+ list( $start, $end ) = IP::parseRange( $ip );
+ return array( 'cuc_'.$type.'_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) );
+ } 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 ) ) {
+ // IPv6 CIDR, 64-128 bits
+ if ( $matches[2] < 64 || $matches[2] > 128 )
+ return array( 'cuc_'.$type.'_hex' => -1 );
+ list( $start, $end ) = IP::parseRange6( $ip );
+ return array( 'cuc_'.$type.'_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) );
+ } else if( preg_match( '#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#', $ip ) ) {
+ // 32 bit IPv4
+ $ip_hex = IP::toHex( $ip );
+ return array( 'cuc_'.$type.'_hex' => $ip_hex );
+ } 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 ) ) {
+ // 128 bit IPv6
+ $ip_hex = IP::toHex( $ip );
+ return array( 'cuc_'.$type.'_hex' => $ip_hex );
} else {
- return array( 'rc_ip' => $ip );
+ // throw away this query, incomplete IP, these don't get through the entry point anyway
+ return array( 'cuc_'.$type.'_hex' => -1 );
}
}
-
- /**
- * Function to convert the third bloc of a 17-23 CIDR range into binary
- * Returns null if the range is not 17-23
- */
- function parse17_23CIDR( $ip ) {
- // haaaack
- if( preg_match( '#^\d+\.\d+\.(\d+)\.\d+/(\d+)$#', $ip, $matches ) ) {
- list( $junk, $a, $bits ) = $matches;
- #for 17-23 queries, return blocs 3-4 in binary
- if( $bits > 16 && $bits < 24 ) {
- #Invalid IPs can return wrong results
- if ( $a > 256 ) {
- $a = 256;
- }
- $a_bin = base_convert( $a, 10, 2 );
- #convert has no starting zeroes
- $zeroes = 8-strlen($a_bin);
- for ($i=0; $i<$zeroes; ++$i) {
- $a_bin = "0$a_bin";
- }
- $r_bin = substr($a_bin, 0 , $bits - 16);
- return $r_bin;
- } else {
- return null;
- }
- } else {
- return null;
- }
- }
-
- /**
- * Function to see if a given IP is in a 17-23 CIDR range
- * Assumes that IP is already in /16 range!!!
- * @param given IP $ip
- * @param third bloc in binary $bloc3_bin
- */
- function isIn17_23CIDR( $ip, $bloc3_bin ) {
- // haaaack
- if( preg_match( '#^\d+\.\d+\.(\d+)#', $ip, $matches ) ) {
- list( $junk, $a ) = $matches;
- $a_bin = base_convert($a, 10, 2);
- #convert has no starting zeroes
- $zeroes = 8-strlen($a_bin);
- for ($i=0; $i < $zeroes; ++$i)
- $a_bin = "0$a_bin";
- if( strpos( $a_bin , $bloc3_bin )===0 ) {
- return true;
- } else {
- return false;
- }
- } else {
- return false;
- }
- }
- function doUserRequest( $user , $reason = '') {
+#Get all IPs used by a user
+#Shows first and last date and number of edits
+ function doUserIPsRequest( $user , $reason = '') {
global $wgOut, $wgTitle, $wgLang, $wgUser, $wgDBname;
- $fname = 'CheckUser::doUserRequest';
+ $fname = 'CheckUser::doUserIPsRequest';
$userTitle = Title::newFromText( $user, NS_USER );
if( !is_null( $userTitle ) ) {
// normalize the username
$user = $userTitle->getText();
}
+ #IPs are passed in as a blank string
+ if ( !$user) {
+ $s = wfMsgHtml('nouserspecified');
+ $wgOut->addHTML( $s );
+ return;
+ }
+ #get ID, works better than text as user may have been renamed
+ $user_id = User::idFromName($user);
+
+ #if user is not IP or nonexistant
+ if ( !$user_id ) {
+ $s = wfMsgHtml('nosuchusershort', $user);
+ $wgOut->addHTML( $s );
+ return;
+ }
if ( !$this->addLogEntry( time() , $wgUser->getName() ,
- 'got IPs for' , htmlspecialchars( $user ) , $wgDBname , $reason) )
+ "got IPs for" , htmlspecialchars( $user ) , $wgDBname , $reason) )
{
- $wgOut->addHTML( '<p>Unable to add log entry</p>' );
+ $wgOut->addHTML( '<p>'.wfMsgHtml('checkuser-log-fail').'</p>' );
}
-
- $dbr =& wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'recentchanges', array( 'DISTINCT rc_ip' ), array( 'rc_user_text' => $user ), $fname );
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'cu_changes', array( 'cuc_ip', 'cuc_timestamp' ), array( 'cuc_user' => $user_id ), $fname,
+ array( 'ORDER BY' => 'cuc_timestamp DESC' ) );
if ( !$dbr->numRows( $res ) ) {
- $s = "No results.\n";
+ $s = wfMsgHtml("checkuser-nomatch")."\n";
} else {
- $s = '<ul>';
+ $ips_edits=array();
while ( ($row = $dbr->fetchObject( $res ) ) != false ) {
- $s .= '<li><a href="' . $wgTitle->escapeLocalURL( 'ip=' . urlencode( $row->rc_ip ) ) . '">' .
- htmlspecialchars( $row->rc_ip ) . '</a></li>';
+ if ( !array_key_exists( $row->cuc_ip, $ips_edits ) ) {
+ $ips_edits[$row->cuc_ip]=0;
+ $ips_last[$row->cuc_ip]=$row->cuc_timestamp;
+ }
+ $ips_edits[$row->cuc_ip]+=1;
+ $ips_first[$row->cuc_ip]=$row->cuc_timestamp;
}
+ $s = '<ul>';
+ foreach ( $ips_edits as $ip => $edits ) {
+ $s .= '<li><a href="' . $wgTitle->escapeLocalURL( 'user=' . urlencode( $ip ) ) . '">' . $ip . '</a>' .
+ ' (' . $wgLang->timeanddate( $ips_first[$ip] ) . ' -- ' . $wgLang->timeanddate( $ips_last[$ip] ) . ') ' .
+ ' <strong>[' . $edits . ']</strong>' . '</li>';
+ }
$s .= '</ul>';
}
$wgOut->addHTML( $s );
}
- function showLog() {
- global $wgCheckUserLog;
+ function showLog( $user ) {
+ global $wgCheckUserLog, $wgOut;
if( $wgCheckUserLog === false || !file_exists( $wgCheckUserLog ) ) {
- global $wgOut;
# No log
- $wgOut->addHTML("<p>No log file.</p>");
+ $wgOut->addHTML("<p>".wfMsgHtml("checkuser-nolog")."</p>");
return;
} else {
- global $wgRequest, $wgOut, $wgScript;
+ global $wgRequest, $wgScript;
if( $wgRequest->getVal( 'log' ) == 1 ) {
$logsearch = $wgRequest->getText( 'logsearch' );
@@ -326,34 +438,35 @@
$CUtitle = Title::makeTitle( NS_SPECIAL, 'CheckUser' );
$title = $CUtitle->getNsText() . ':' . 'CheckUser';
$encLogSearch = htmlspecialchars( $logsearch );
+ $encUser = htmlspecialchars( $user );
$scroller = wfViewPrevNext( $offset, $limit, $CUtitle,
- 'log=1&logsearch=' . urlencode($logsearch),
+ 'log=1&logsearch=' . urlencode($logsearch) . '&user=' . urlencode($user),
count($log) <= $limit);
#If not filtered empty
if ( $log ) {
if (count($log) > $limit) array_pop($log);
$output = implode( "\n", $log );
+ } else {
+ $output = "<p>".wfMsgHtml('checkuser-nomatch')."</p>";
}
- else
- $output = "<p>No matches found.</p>";
- $wgOut->addHTML("<br></br>
+ $wgOut->addHTML("<fieldset><legend>".wfMsgHtml('checkuser-log').":</legend>
<form name='checkuserlog' action='$wgScript' method='get'>
<input type='hidden' name='title' value='$title' /><input type='hidden' name='log' value='1' />
- <table border='0' cellpadding='1'><tr>
- <td>Search:</td>
- <td><input type='text' name='logsearch' size='15' maxlength='50' value='$encLogSearch' /></td>
- <td><input type='submit' value='Go' /></td>
- </tr></table></form><br></br>");
- $wgOut->addHTML( "$scroller\n<ul>$output</ul>\n$scroller\n" );
+ <input type='hidden' name='user' value='$encUser' /><table border='0' cellpadding='1'><tr>
+ <td>".wfMsgHtml('checkuser-search').":</td>
+ <td><input type='text' name='logsearch' size='20' maxlength='50' value='$encLogSearch' /></td>
+ <td><input type='submit' value='".wfMsgHtml('go')."' /></td>
+ </tr></table><p>".wfMsgHtml('checkuser-logcase')."</p></form>");
+ $wgOut->addHTML( "$scroller\n<ul>$output</ul>\n$scroller\n</fieldset>" );
} else {
- $wgOut->addHTML( "<p>The log contains no items.</p>" );
+ $wgOut->addHTML( "<p>".wfMsgHtml('checkuser-empty')."</p>" );
}
} else {
# Hide the log, show a link
global $wgTitle, $wgUser;
$skin = $wgUser->getSkin();
- $link = $skin->makeKnownLinkObj( $wgTitle, 'Show log', 'log=1' );
+ $link = $skin->makeKnownLinkObj( $wgTitle, wfMsgHtml('checkuser-showlog'), 'log=1&user=' . urlencode($user) );
$wgOut->addHTML( "<p>$link</p>" );
}
}
@@ -428,7 +541,7 @@
break;
}
} while( $filePosition > 0 );
-
+ #leftover
if ($logsearch)
$srchind = strpos($leftover, $logsearch);
if ( !$logsearch || (3 < $srchind && $srchind < (strlen($parts[$i]) - 5)) ) {
@@ -457,7 +570,7 @@
if ( $reason ) $reason=' ("' . $reason . '")';
else $reason = "";
- $date = date( 'H:i, j F Y', $timestamp );
+ $date=date("H:i, j F Y",$timestamp);
if ( !fwrite( $f, "<li>$date, $checker $autsum $target on $db$reason</li>\n" ) ) {
return false;
}

File Metadata

Mime Type
text/x-diff
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1870
Default Alt Text
checkuser.patch (35 KB)

Event Timeline