Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F2294
checkuser.patch
Public
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Authored By
•
bzimport
Nov 21 2014, 8:48 PM
2014-11-21 20:48:55 (UTC+0)
Size
35 KB
Referenced Files
None
Subscribers
None
checkuser.patch
View Options
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 .= " ".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/> <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
Details
Attached
Mime Type
text/x-diff
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1870
Default Alt Text
checkuser.patch (35 KB)
Attached To
Mode
T5281: Have CheckUser search deleted edits
Attached
Detach File
Event Timeline
Log In to Comment