Page MenuHomePhabricator

User summary on IP checks
Closed, ResolvedPublic

Description

Author: kelly.lynn.martin

Description:
When doing an IP check, it would be nice if there was a summary listing (top or
bottom) that listed all the users who edited from that IP. Right now I have to
read through the listing and it's easy to miss one, especially on high traffic IPs.


Version: unspecified
Severity: enhancement

Details

Reference
bz4627

Revisions and Commits

Event Timeline

bzimport raised the priority of this task from to Medium.Nov 21 2014, 9:02 PM
bzimport added a project: CheckUser.
bzimport set Reference to bz4627.
bzimport added a subscriber: Unknown Object (MLST).

Seconded. It's very easy to miss on a busy listing or on an IP range check.

A patch to add the list of users that used an IP and an autoblock checker

See [http://www.mediawiki.org/wiki/User:Voice_of_All/CheckUser2.0].

I am working on adding anything useful and not too expensive that CheckUser
could use. I'd appretiate any suggestions.

attachment CheckUser_body.php ignored as obsolete

*** Bug 5979 has been marked as a duplicate of this bug. ***

Comment on attachment 2806
A patch to add the list of users that used an IP and an autoblock checker

<?php

if ( !defined( 'MEDIAWIKI' ) ) {

echo "CheckUser extension\n";
exit( 1 );

}

Add messages

global $wgMessageCache, $wgCheckUserMessages;
foreach( $wgCheckUserMessages as $key => $value ) {
$wgMessageCache->addMessages( $wgCheckUserMessages[$key], $key );
}

class CheckUser extends SpecialPage
{
function CheckUser() {

		SpecialPage::SpecialPage('CheckUser', 'checkuser');

}

function execute( $par ) {

		global $wgRequest, $wgOut, $wgTitle, $wgUser;

		if( !$wgUser->isAllowed( 'checkuser' ) ) {
			$wgOut->permissionRequired( 'checkuser' );
			return;
		}

		$this->setHeaders();

		$ip = $wgRequest->getText( 'ip' );
		$user = $wgRequest->getText( 'user' );
		$autoblock = $wgRequest->getText( 'autoblock' );
		$subip = $wgRequest->getBool( 'subip' );
		$subuser = $wgRequest->getBool( 'subuser' );
		$subautoblock = $wgRequest->getBool( 'subautoblock' );
		$wpNamesOnly = $wgRequest->getBool( 'wpNamesOnly' );

		$this->doTop( $ip, $user, $autoblock);
		if ( $subip ) {
			$this->doIPRequest( $ip, $wpNamesOnly);
		} else if ( $subuser ) {
			$this->doUserRequest( $user );
		} else if ( $subautoblock ) {
			$this->doAutoblockRequest( $autoblock );
		} else {
			$this->showLog();
		}

}

function doTop( $ip, $user, $autoblock ) {

		global $wgOut, $wgTitle;

		$action = $wgTitle->escapeLocalUrl();
		$encIp = htmlspecialchars( $ip );
		$encUser = htmlspecialchars( $user );
		$encAutoblock = htmlspecialchars( $autoblock );

		$wgOut->addHTML( wfmsg('checkuserheader'));
		$wgOut->addHTML( <<<EOT

<table border=0 cellpadding=5>
<form name="checkuser" action="$action" method=post>
<tr><td>
IP address:
</td><td>
<input type="text" name="ip" value="$encIp" width=50 /> <input
type="submit" name="subip" value="OK" />
<br><input name="wpNamesOnly" type="checkbox" value="1"
id="wpNamesOnly" tabindex="4" /><label for="wpNamesOnly">List usernames
only</label>
</td></tr>
</form>

<form name="checkuser" action="$action" method=post>
<tr><td>
Username:
</td><td>
<input type="text" name="user" value="$encUser" width=50 /> <input
type="submit" name="subuser" value="OK" />
</td></tr>

<form name="checkuser" action="$action" method=post>
<tr><td>
Autoblock#:
</td><td>
<input type="text" name="autoblock" value="$encAutoblock" width=50 />
<input type="submit" name="subautoblock" value="OK" />
</td></tr>
</form>
</table>
EOT

		);

}

function doAutoblockRequest( $autoblock ) {

		global $wgUser, $wgOut, $wgLang, $wgTitle, $wgDBname;
		$fname = 'CheckUser::doAutoblockRequest';

		if ( !$this->addLogEntry( $wgLang->timeanddate(

wfTimestampNow() ) . ' ' .

		  $wgUser->getName() . ' got IP for autoblock #' .

htmlspecialchars( $autoblock ) . ' on ' . $wgDBname ))

		{
			$wgOut->addHTML( '<p>Unable to add log entry</p>' );
		}

		$dbr =& wfGetDB( DB_SLAVE );
		$res = $dbr->select( 'ipblocks', array( 'ipb_address' ), array(

'ipb_id' => $autoblock , 'ipb_auto' => 1), $fname );

		if (!$dbr->numRows( $res )) {
			$s = "No results, the block may have expired\n";
		} else {
		    global $IP;
			require_once( $IP.'/includes/RecentChange.php' );
			require_once( $IP.'/includes/ChangesList.php' );
		    $row = $dbr->fetchObject($res);
		    $s = '<ul>';
		    $s .= '<li><a href="' . $wgTitle->escapeLocalURL( 'user=' .

urlencode( $row->ipb_address ) ) . '">' .

					htmlspecialchars( $row->ipb_address ) .

'</a></li>';

			$s .= '</ul>';
		}
		$wgOut->addHTML( $s );
		$dbr->freeResult( $res );

}

function doIPRequest( $ip, $wpNamesOnly) {

		global $wgUser, $wgOut, $wgLang, $wgTitle, $wgDBname;
		$fname = 'CheckUser::doIPRequest';
		if ($wpNamesOnly) $type='users';
		else $type='edits';

		if ( !$this->addLogEntry( $wgLang->timeanddate(

wfTimestampNow() ) . ' ' .

		  $wgUser->getName() . ' got ' . $type . ' for ' .

htmlspecialchars( $ip ) . ' on ' . $wgDBname ))

		{
			$wgOut->addHTML( '<p>Unable to add log entry</p>' );
		}

		$dbr =& wfGetDB( DB_SLAVE );
		if (!$wpNamesOnly) {
		$res = $dbr->select( 'recentchanges', array( '*' ),

$this->getIpConds( $dbr, $ip ), $fname,

			array( 'ORDER BY' => 'rc_timestamp DESC' ) );
		} else { $res = $dbr->select( 'recentchanges', array(

'rc_user_text' ), $this->getIpConds( $dbr, $ip ), $fname,

			array( 'ORDER BY' => 'rc_timestamp DESC' ) );
		}

		if ( !$dbr->numRows( $res ) ) {
			$s =  "No results\n";
		} else if ( !$wpNamesOnly ) {
			global $IP;
			require_once( $IP.'/includes/RecentChange.php' );
			require_once( $IP.'/includes/ChangesList.php' );

			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();
			$counter = 1;
			while ( ($row = $dbr->fetchObject( $res ) ) != false )

{

				$rc = RecentChange::newFromRow( $row );
				$rc->counter = $counter++;
				$s .= $list->recentChangesLine( $rc, false );
			}
			$s .= $list->endRecentChangesList();
		} else {
		$uniqueUsers = array();
		$u_count = 0;
		$wgOut->addHTML( '<ul>' );
			while ( ($row = $dbr->fetchObject( $res ) ) != false )

{

			    if (!in_array( $row->rc_user_text, $uniqueUsers ))

{

			    $uniqueUsers[$u_count]=$row->rc_user_text;
			    $u_count++;
				$s .= '<li><a href="' .

$wgTitle->escapeLocalURL( 'user=' . urlencode( $row->rc_user_text ) ) . '">' .

					htmlspecialchars( $row->rc_user_text )

. '</a></li>';

				}
			}
		}
		$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
  • @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 ) {
				$match = "$a.$b.%";
			} else {
				// Other sizes not supported. /8 is too big
				$match = $ip;
			}
			return array( 'rc_ip LIKE ' . $db->addQuotes( $match )

);

		} else {
			return array( 'rc_ip' => $ip );
		}

}

function doUserRequest( $user ) {

		global $wgOut, $wgTitle, $wgLang, $wgUser, $wgDBname;
		$fname = 'CheckUser::doUserRequest';

		$userTitle = Title::newFromText( $user, NS_USER );
		if( !is_null( $userTitle ) ) {
			// normalize the username
			$user = $userTitle->getText();
		}

		if ( !$this->addLogEntry( $wgLang->timeanddate(

wfTimestampNow() ) . ' ' .

		  $wgUser->getName() . ' got IPs for ' . htmlspecialchars(

$user ) . ' on ' . $wgDBname ) )

		{
			$wgOut->addHTML( '<p>Unable to add log entry</p>' );
		}

		$dbr =& wfGetDB( DB_SLAVE );
		$res = $dbr->select( 'recentchanges', array( 'DISTINCT rc_ip'

), array( 'rc_user_text' => $user ), $fname );

		if ( !$dbr->numRows( $res ) ) {
			$s =  "No results\n";
		} else {
			$s = '<ul>';
			while ( ($row = $dbr->fetchObject( $res ) ) != false )

{

				$s .= '<li><a href="' .

$wgTitle->escapeLocalURL( 'ip=' . urlencode( $row->rc_ip ) ) . '">' .

					htmlspecialchars( $row->rc_ip ) .

'</a></li>';

			}
			$s .= '</ul>';
		}
		$wgOut->addHTML( $s );

}

function showLog() {

		global $wgCheckUserLog;

		if( $wgCheckUserLog === false || !file_exists( $wgCheckUserLog

) ) {

  1. No log
			return;
		} else {
			global $wgRequest, $wgOut;

			if( $wgRequest->getVal( 'log' ) == 1 ) {
				# Show the log
				list( $limit, $offset ) = wfCheckLimits();
				$log = $this->tail( $wgCheckUserLog, $limit,

$offset );

				if( !!$log ) {

					$scroller = wfViewPrevNext( $offset,

$limit,

						Title::makeTitle( NS_SPECIAL,

'CheckUser' ),

						'log=1',
						count( $log ) < $limit );

					$output = implode( "\n", $log );
					$wgOut->addHTML(

"$scroller\n<ul>$output</ul>\n$scroller\n" );

				} else {
					$wgOut->addHTML( "<p>The log contains

no items.</p>" );

				}
			} else {
				# Hide the log, show a link
				global $wgTitle, $wgUser;
				$skin = $wgUser->getSkin();
				$link = $skin->makeKnownLinkObj( $wgTitle,

'Show log', 'log=1' );

				$wgOut->addHTML( "<p>$link</p>" );
			}
		}

}

function tail( $filename, $limit, $offset ) {

		//wfSuppressWarnings();
		$file = fopen( $filename, "rt" );
		//wfRestoreWarnings();

		if( $file === false ) {
			return false;
		}

		$filePosition = filesize( $filename );
		if( $filePosition == 0 ) {
			return array();
		}

		$lines = array();
		$bufSize = 1024;
		$lineCount = 0;
		$total = $offset + $limit;
		$leftover = '';
		do {
			if( $filePosition < $bufSize ) {
				$bufSize = $filePosition;
			}
			$filePosition -= $bufSize;
			fseek( $file, $filePosition );
			$buffer = fread( $file, $bufSize );

			$parts = explode( "\n", $buffer );
			$num = count( $parts );

			if( $num > 0 ) {
				if( $lineCount++ > $offset ) {
					$lines[] = $parts[$num - 1] .

$leftover;

					if( $lineCount > $total ) {
						return $lines;
					}
				}
			}
			for( $i = $num - 2; $i > 0; $i-- ) {
				if( $lineCount++ > $offset ) {
					$lines[] = $parts[$i];
					if( $lineCount > $total ) {
						fclose( $file );
						return $lines;
					}
				}
			}
			if( $num > 1 ) {
				$leftover = $parts[0];
			} else {
				$leftover = '';
				break;
			}
		} while( $filePosition > 0 );

		if( $lineCount++ > $offset ) {
			$lines[] = $leftover;
		}
		fclose( $file );
		return $lines;

}

function addLogEntry( $entry ) {

		global $wgUser, $wgCheckUserLog;
		if ( $wgCheckUserLog === false ) {
			// No log required, this is not an error
			return true;
		}

		$f = fopen( $wgCheckUserLog, 'a' );
		if ( !$f ) {
			return false;
		}
		if ( !fwrite( $f, "<li>$entry</li>\n" ) ) {
			return false;
		}
		fclose( $f );
		return true;

}
}
?>

Comment on attachment 2806
A patch to add the list of users that used an IP and an autoblock checker

<?php

if ( !defined( 'MEDIAWIKI' ) ) {

echo "CheckUser extension\n";
exit( 1 );

}

Add messages

global $wgMessageCache, $wgCheckUserMessages;
foreach( $wgCheckUserMessages as $key => $value ) {
$wgMessageCache->addMessages( $wgCheckUserMessages[$key], $key );
}

class CheckUser extends SpecialPage
{
function CheckUser() {

		SpecialPage::SpecialPage('CheckUser', 'checkuser');

}

function execute( $par ) {

		global $wgRequest, $wgOut, $wgTitle, $wgUser;

		if( !$wgUser->isAllowed( 'checkuser' ) ) {
			$wgOut->permissionRequired( 'checkuser' );
			return;
		}

		$this->setHeaders();

		$ip = $wgRequest->getText( 'ip' );
		$user = $wgRequest->getText( 'user' );
		$autoblock = $wgRequest->getText( 'autoblock' );
		$subip = $wgRequest->getBool( 'subip' );
		$subuser = $wgRequest->getBool( 'subuser' );
		$subautoblock = $wgRequest->getBool( 'subautoblock' );
		$wpNamesOnly = $wgRequest->getBool( 'wpNamesOnly' );

		$this->doTop( $ip, $user, $autoblock);
		if ( $subip ) {
			$this->doIPRequest( $ip, $wpNamesOnly);
		} else if ( $subuser ) {
			$this->doUserRequest( $user );
		} else if ( $subautoblock ) {
			$this->doAutoblockRequest( $autoblock );
		} else {
			$this->showLog();
		}

}

function doTop( $ip, $user, $autoblock ) {

		global $wgOut, $wgTitle;

		$action = $wgTitle->escapeLocalUrl();
		$encIp = htmlspecialchars( $ip );
		$encUser = htmlspecialchars( $user );
		$encAutoblock = htmlspecialchars( $autoblock );

		$wgOut->addHTML( wfmsg('checkuserheader'));
		$wgOut->addHTML( <<<EOT

<table border=0 cellpadding=5>
<form name="checkuser" action="$action" method=post>
<tr><td>
IP address:
</td><td>
<input type="text" name="ip" value="$encIp" width=50 /> <input
type="submit" name="subip" value="OK" />
<br><input name="wpNamesOnly" type="checkbox" value="1"
id="wpNamesOnly" tabindex="4" /><label for="wpNamesOnly">List usernames
only</label>
</td></tr>
</form>

<form name="checkuser" action="$action" method=post>
<tr><td>
Username:
</td><td>
<input type="text" name="user" value="$encUser" width=50 /> <input
type="submit" name="subuser" value="OK" />
</td></tr>

<form name="checkuser" action="$action" method=post>
<tr><td>
Autoblock#:
</td><td>
<input type="text" name="autoblock" value="$encAutoblock" width=50 />
<input type="submit" name="subautoblock" value="OK" />
</td></tr>
</form>
</table>
EOT

		);

}

function doAutoblockRequest( $autoblock ) {

		global $wgUser, $wgOut, $wgLang, $wgTitle, $wgDBname;
		$fname = 'CheckUser::doAutoblockRequest';

		if ( !$this->addLogEntry( $wgLang->timeanddate(

wfTimestampNow() ) . ' ' .

		  $wgUser->getName() . ' got IP for autoblock #' .

htmlspecialchars( $autoblock ) . ' on ' . $wgDBname ))

		{
			$wgOut->addHTML( '<p>Unable to add log entry</p>' );
		}

		$dbr =& wfGetDB( DB_SLAVE );
		$res = $dbr->select( 'ipblocks', array( 'ipb_address' ), array(

'ipb_id' => $autoblock , 'ipb_auto' => 1), $fname );

		if (!$dbr->numRows( $res )) {
			$s = "No results, the block may have expired\n";
		} else {
		    global $IP;
			require_once( $IP.'/includes/RecentChange.php' );
			require_once( $IP.'/includes/ChangesList.php' );
		    $row = $dbr->fetchObject($res);
		    $s = '<ul>';
		    $s .= '<li><a href="' . $wgTitle->escapeLocalURL( 'user=' .

urlencode( $row->ipb_address ) ) . '">' .

					htmlspecialchars( $row->ipb_address ) .

'</a></li>';

			$s .= '</ul>';
		}
		$wgOut->addHTML( $s );
		$dbr->freeResult( $res );

}

function doIPRequest( $ip, $wpNamesOnly) {

		global $wgUser, $wgOut, $wgLang, $wgTitle, $wgDBname;
		$fname = 'CheckUser::doIPRequest';
		if ($wpNamesOnly) $type='users';
		else $type='edits';

		if ( !$this->addLogEntry( $wgLang->timeanddate(

wfTimestampNow() ) . ' ' .

		  $wgUser->getName() . ' got ' . $type . ' for ' .

htmlspecialchars( $ip ) . ' on ' . $wgDBname ))

		{
			$wgOut->addHTML( '<p>Unable to add log entry</p>' );
		}

		$dbr =& wfGetDB( DB_SLAVE );
		if (!$wpNamesOnly) {
		$res = $dbr->select( 'recentchanges', array( '*' ),

$this->getIpConds( $dbr, $ip ), $fname,

			array( 'ORDER BY' => 'rc_timestamp DESC' ) );
		} else { $res = $dbr->select( 'recentchanges', array(

'rc_user_text' ), $this->getIpConds( $dbr, $ip ), $fname,

			array( 'ORDER BY' => 'rc_timestamp DESC' ) );
		}

		if ( !$dbr->numRows( $res ) ) {
			$s =  "No results\n";
		} else if ( !$wpNamesOnly ) {
			global $IP;
			require_once( $IP.'/includes/RecentChange.php' );
			require_once( $IP.'/includes/ChangesList.php' );

			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();
			$counter = 1;
			while ( ($row = $dbr->fetchObject( $res ) ) != false )

{

				$rc = RecentChange::newFromRow( $row );
				$rc->counter = $counter++;
				$s .= $list->recentChangesLine( $rc, false );
			}
			$s .= $list->endRecentChangesList();
		} else {
		$uniqueUsers = array();
		$u_count = 0;
		$wgOut->addHTML( '<ul>' );
			while ( ($row = $dbr->fetchObject( $res ) ) != false )

{

			    if (!in_array( $row->rc_user_text, $uniqueUsers ))

{

			    $uniqueUsers[$u_count]=$row->rc_user_text;
			    $u_count++;
				$s .= '<li><a href="' .

$wgTitle->escapeLocalURL( 'user=' . urlencode( $row->rc_user_text ) ) . '">' .

					htmlspecialchars( $row->rc_user_text )

. '</a></li>';

				}
			}
		}
		$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
  • @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 ) {
				$match = "$a.$b.%";
			} else {
				// Other sizes not supported. /8 is too big
				$match = $ip;
			}
			return array( 'rc_ip LIKE ' . $db->addQuotes( $match )

);

		} else {
			return array( 'rc_ip' => $ip );
		}

}

function doUserRequest( $user ) {

		global $wgOut, $wgTitle, $wgLang, $wgUser, $wgDBname;
		$fname = 'CheckUser::doUserRequest';

		$userTitle = Title::newFromText( $user, NS_USER );
		if( !is_null( $userTitle ) ) {
			// normalize the username
			$user = $userTitle->getText();
		}

		if ( !$this->addLogEntry( $wgLang->timeanddate(

wfTimestampNow() ) . ' ' .

		  $wgUser->getName() . ' got IPs for ' . htmlspecialchars(

$user ) . ' on ' . $wgDBname ) )

		{
			$wgOut->addHTML( '<p>Unable to add log entry</p>' );
		}

		$dbr =& wfGetDB( DB_SLAVE );
		$res = $dbr->select( 'recentchanges', array( 'DISTINCT rc_ip'

), array( 'rc_user_text' => $user ), $fname );

		if ( !$dbr->numRows( $res ) ) {
			$s =  "No results\n";
		} else {
			$s = '<ul>';
			while ( ($row = $dbr->fetchObject( $res ) ) != false )

{

				$s .= '<li><a href="' .

$wgTitle->escapeLocalURL( 'ip=' . urlencode( $row->rc_ip ) ) . '">' .

					htmlspecialchars( $row->rc_ip ) .

'</a></li>';

			}
			$s .= '</ul>';
		}
		$wgOut->addHTML( $s );

}

function showLog() {

		global $wgCheckUserLog;

		if( $wgCheckUserLog === false || !file_exists( $wgCheckUserLog

) ) {

  1. No log
			return;
		} else {
			global $wgRequest, $wgOut;

			if( $wgRequest->getVal( 'log' ) == 1 ) {
				# Show the log
				list( $limit, $offset ) = wfCheckLimits();
				$log = $this->tail( $wgCheckUserLog, $limit,

$offset );

				if( !!$log ) {

					$scroller = wfViewPrevNext( $offset,

$limit,

						Title::makeTitle( NS_SPECIAL,

'CheckUser' ),

						'log=1',
						count( $log ) < $limit );

					$output = implode( "\n", $log );
					$wgOut->addHTML(

"$scroller\n<ul>$output</ul>\n$scroller\n" );

				} else {
					$wgOut->addHTML( "<p>The log contains

no items.</p>" );

				}
			} else {
				# Hide the log, show a link
				global $wgTitle, $wgUser;
				$skin = $wgUser->getSkin();
				$link = $skin->makeKnownLinkObj( $wgTitle,

'Show log', 'log=1' );

				$wgOut->addHTML( "<p>$link</p>" );
			}
		}

}

function tail( $filename, $limit, $offset ) {

		//wfSuppressWarnings();
		$file = fopen( $filename, "rt" );
		//wfRestoreWarnings();

		if( $file === false ) {
			return false;
		}

		$filePosition = filesize( $filename );
		if( $filePosition == 0 ) {
			return array();
		}

		$lines = array();
		$bufSize = 1024;
		$lineCount = 0;
		$total = $offset + $limit;
		$leftover = '';
		do {
			if( $filePosition < $bufSize ) {
				$bufSize = $filePosition;
			}
			$filePosition -= $bufSize;
			fseek( $file, $filePosition );
			$buffer = fread( $file, $bufSize );

			$parts = explode( "\n", $buffer );
			$num = count( $parts );

			if( $num > 0 ) {
				if( $lineCount++ > $offset ) {
					$lines[] = $parts[$num - 1] .

$leftover;

					if( $lineCount > $total ) {
						return $lines;
					}
				}
			}
			for( $i = $num - 2; $i > 0; $i-- ) {
				if( $lineCount++ > $offset ) {
					$lines[] = $parts[$i];
					if( $lineCount > $total ) {
						fclose( $file );
						return $lines;
					}
				}
			}
			if( $num > 1 ) {
				$leftover = $parts[0];
			} else {
				$leftover = '';
				break;
			}
		} while( $filePosition > 0 );

		if( $lineCount++ > $offset ) {
			$lines[] = $leftover;
		}
		fclose( $file );
		return $lines;

}

function addLogEntry( $entry ) {

		global $wgUser, $wgCheckUserLog;
		if ( $wgCheckUserLog === false ) {
			// No log required, this is not an error
			return true;
		}

		$f = fopen( $wgCheckUserLog, 'a' );
		if ( !$f ) {
			return false;
		}
		if ( !fwrite( $f, "<li>$entry</li>\n" ) ) {
			return false;
		}
		fclose( $f );
		return true;

}
}
?>

Comment on attachment 2806
A patch to add the list of users that used an IP and an autoblock checker

<?php

if ( !defined( 'MEDIAWIKI' ) ) {

echo "CheckUser extension\n";
exit( 1 );

}

Add messages

global $wgMessageCache, $wgCheckUserMessages;
foreach( $wgCheckUserMessages as $key => $value ) {
$wgMessageCache->addMessages( $wgCheckUserMessages[$key], $key );
}

class CheckUser extends SpecialPage
{
function CheckUser() {

		SpecialPage::SpecialPage('CheckUser', 'checkuser');

}

function execute( $par ) {

		global $wgRequest, $wgOut, $wgTitle, $wgUser;

		if( !$wgUser->isAllowed( 'checkuser' ) ) {
			$wgOut->permissionRequired( 'checkuser' );
			return;
		}

		$this->setHeaders();

		$ip = $wgRequest->getText( 'ip' );
		$user = $wgRequest->getText( 'user' );
		$autoblock = $wgRequest->getText( 'autoblock' );
		$subip = $wgRequest->getBool( 'subip' );
		$subuser = $wgRequest->getBool( 'subuser' );
		$subautoblock = $wgRequest->getBool( 'subautoblock' );
		$wpNamesOnly = $wgRequest->getBool( 'wpNamesOnly' );

		$this->doTop( $ip, $user, $autoblock);
		if ( $subip ) {
			$this->doIPRequest( $ip, $wpNamesOnly);
		} else if ( $subuser ) {
			$this->doUserRequest( $user );
		} else if ( $subautoblock ) {
			$this->doAutoblockRequest( $autoblock );
		} else {
			$this->showLog();
		}

}

function doTop( $ip, $user, $autoblock ) {

		global $wgOut, $wgTitle;

		$action = $wgTitle->escapeLocalUrl();
		$encIp = htmlspecialchars( $ip );
		$encUser = htmlspecialchars( $user );
		$encAutoblock = htmlspecialchars( $autoblock );

		$wgOut->addHTML( wfmsg('checkuserheader'));
		$wgOut->addHTML( <<<EOT

<table border=0 cellpadding=5>
<form name="checkuser" action="$action" method=post>
<tr><td>
IP address:
</td><td>
<input type="text" name="ip" value="$encIp" width=50 /> <input
type="submit" name="subip" value="OK" />
<br><input name="wpNamesOnly" type="checkbox" value="1"
id="wpNamesOnly" tabindex="4" /><label for="wpNamesOnly">List usernames
only</label>
</td></tr>
</form>

<form name="checkuser" action="$action" method=post>
<tr><td>
Username:
</td><td>
<input type="text" name="user" value="$encUser" width=50 /> <input
type="submit" name="subuser" value="OK" />
</td></tr>

<form name="checkuser" action="$action" method=post>
<tr><td>
Autoblock#:
</td><td>
<input type="text" name="autoblock" value="$encAutoblock" width=50 />
<input type="submit" name="subautoblock" value="OK" />
</td></tr>
</form>
</table>
EOT

		);

}

function doAutoblockRequest( $autoblock ) {

		global $wgUser, $wgOut, $wgLang, $wgTitle, $wgDBname;
		$fname = 'CheckUser::doAutoblockRequest';

		if ( !$this->addLogEntry( $wgLang->timeanddate(

wfTimestampNow() ) . ' ' .

		  $wgUser->getName() . ' got IP for autoblock #' .

htmlspecialchars( $autoblock ) . ' on ' . $wgDBname ))

		{
			$wgOut->addHTML( '<p>Unable to add log entry</p>' );
		}

		$dbr =& wfGetDB( DB_SLAVE );
		$res = $dbr->select( 'ipblocks', array( 'ipb_address' ), array(

'ipb_id' => $autoblock , 'ipb_auto' => 1), $fname );

		if (!$dbr->numRows( $res )) {
			$s = "No results, the block may have expired\n";
		} else {
		    global $IP;
			require_once( $IP.'/includes/RecentChange.php' );
			require_once( $IP.'/includes/ChangesList.php' );
		    $row = $dbr->fetchObject($res);
		    $s = '<ul>';
		    $s .= '<li><a href="' . $wgTitle->escapeLocalURL( 'user=' .

urlencode( $row->ipb_address ) ) . '">' .

					htmlspecialchars( $row->ipb_address ) .

'</a></li>';

			$s .= '</ul>';
		}
		$wgOut->addHTML( $s );
		$dbr->freeResult( $res );

}

function doIPRequest( $ip, $wpNamesOnly) {

		global $wgUser, $wgOut, $wgLang, $wgTitle, $wgDBname;
		$fname = 'CheckUser::doIPRequest';
		if ($wpNamesOnly) $type='users';
		else $type='edits';

		if ( !$this->addLogEntry( $wgLang->timeanddate(

wfTimestampNow() ) . ' ' .

		  $wgUser->getName() . ' got ' . $type . ' for ' .

htmlspecialchars( $ip ) . ' on ' . $wgDBname ))

		{
			$wgOut->addHTML( '<p>Unable to add log entry</p>' );
		}

		$dbr =& wfGetDB( DB_SLAVE );
		if (!$wpNamesOnly) {
		$res = $dbr->select( 'recentchanges', array( '*' ),

$this->getIpConds( $dbr, $ip ), $fname,

			array( 'ORDER BY' => 'rc_timestamp DESC' ) );
		} else { $res = $dbr->select( 'recentchanges', array(

'rc_user_text' ), $this->getIpConds( $dbr, $ip ), $fname,

			array( 'ORDER BY' => 'rc_timestamp DESC' ) );
		}

		if ( !$dbr->numRows( $res ) ) {
			$s =  "No results\n";
		} else if ( !$wpNamesOnly ) {
			global $IP;
			require_once( $IP.'/includes/RecentChange.php' );
			require_once( $IP.'/includes/ChangesList.php' );

			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();
			$counter = 1;
			while ( ($row = $dbr->fetchObject( $res ) ) != false )

{

				$rc = RecentChange::newFromRow( $row );
				$rc->counter = $counter++;
				$s .= $list->recentChangesLine( $rc, false );
			}
			$s .= $list->endRecentChangesList();
		} else {
		$uniqueUsers = array();
		$u_count = 0;
		$wgOut->addHTML( '<ul>' );
			while ( ($row = $dbr->fetchObject( $res ) ) != false )

{

			    if (!in_array( $row->rc_user_text, $uniqueUsers ))

{

			    $uniqueUsers[$u_count]=$row->rc_user_text;
			    $u_count++;
				$s .= '<li><a href="' .

$wgTitle->escapeLocalURL( 'user=' . urlencode( $row->rc_user_text ) ) . '">' .

					htmlspecialchars( $row->rc_user_text )

. '</a></li>';

				}
			}
		}
		$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
  • @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 ) {
				$match = "$a.$b.%";
			} else {
				// Other sizes not supported. /8 is too big
				$match = $ip;
			}
			return array( 'rc_ip LIKE ' . $db->addQuotes( $match )

);

		} else {
			return array( 'rc_ip' => $ip );
		}

}

function doUserRequest( $user ) {

		global $wgOut, $wgTitle, $wgLang, $wgUser, $wgDBname;
		$fname = 'CheckUser::doUserRequest';

		$userTitle = Title::newFromText( $user, NS_USER );
		if( !is_null( $userTitle ) ) {
			// normalize the username
			$user = $userTitle->getText();
		}

		if ( !$this->addLogEntry( $wgLang->timeanddate(

wfTimestampNow() ) . ' ' .

		  $wgUser->getName() . ' got IPs for ' . htmlspecialchars(

$user ) . ' on ' . $wgDBname ) )

		{
			$wgOut->addHTML( '<p>Unable to add log entry</p>' );
		}

		$dbr =& wfGetDB( DB_SLAVE );
		$res = $dbr->select( 'recentchanges', array( 'DISTINCT rc_ip'

), array( 'rc_user_text' => $user ), $fname );

		if ( !$dbr->numRows( $res ) ) {
			$s =  "No results\n";
		} else {
			$s = '<ul>';
			while ( ($row = $dbr->fetchObject( $res ) ) != false )

{

				$s .= '<li><a href="' .

$wgTitle->escapeLocalURL( 'ip=' . urlencode( $row->rc_ip ) ) . '">' .

					htmlspecialchars( $row->rc_ip ) .

'</a></li>';

			}
			$s .= '</ul>';
		}
		$wgOut->addHTML( $s );

}

function showLog() {

		global $wgCheckUserLog;

		if( $wgCheckUserLog === false || !file_exists( $wgCheckUserLog

) ) {

  1. No log
			return;
		} else {
			global $wgRequest, $wgOut;

			if( $wgRequest->getVal( 'log' ) == 1 ) {
				# Show the log
				list( $limit, $offset ) = wfCheckLimits();
				$log = $this->tail( $wgCheckUserLog, $limit,

$offset );

				if( !!$log ) {

					$scroller = wfViewPrevNext( $offset,

$limit,

						Title::makeTitle( NS_SPECIAL,

'CheckUser' ),

						'log=1',
						count( $log ) < $limit );

					$output = implode( "\n", $log );
					$wgOut->addHTML(

"$scroller\n<ul>$output</ul>\n$scroller\n" );

				} else {
					$wgOut->addHTML( "<p>The log contains

no items.</p>" );

				}
			} else {
				# Hide the log, show a link
				global $wgTitle, $wgUser;
				$skin = $wgUser->getSkin();
				$link = $skin->makeKnownLinkObj( $wgTitle,

'Show log', 'log=1' );

				$wgOut->addHTML( "<p>$link</p>" );
			}
		}

}

function tail( $filename, $limit, $offset ) {

		//wfSuppressWarnings();
		$file = fopen( $filename, "rt" );
		//wfRestoreWarnings();

		if( $file === false ) {
			return false;
		}

		$filePosition = filesize( $filename );
		if( $filePosition == 0 ) {
			return array();
		}

		$lines = array();
		$bufSize = 1024;
		$lineCount = 0;
		$total = $offset + $limit;
		$leftover = '';
		do {
			if( $filePosition < $bufSize ) {
				$bufSize = $filePosition;
			}
			$filePosition -= $bufSize;
			fseek( $file, $filePosition );
			$buffer = fread( $file, $bufSize );

			$parts = explode( "\n", $buffer );
			$num = count( $parts );

			if( $num > 0 ) {
				if( $lineCount++ > $offset ) {
					$lines[] = $parts[$num - 1] .

$leftover;

					if( $lineCount > $total ) {
						return $lines;
					}
				}
			}
			for( $i = $num - 2; $i > 0; $i-- ) {
				if( $lineCount++ > $offset ) {
					$lines[] = $parts[$i];
					if( $lineCount > $total ) {
						fclose( $file );
						return $lines;
					}
				}
			}
			if( $num > 1 ) {
				$leftover = $parts[0];
			} else {
				$leftover = '';
				break;
			}
		} while( $filePosition > 0 );

		if( $lineCount++ > $offset ) {
			$lines[] = $leftover;
		}
		fclose( $file );
		return $lines;

}

function addLogEntry( $entry ) {

		global $wgUser, $wgCheckUserLog;
		if ( $wgCheckUserLog === false ) {
			// No log required, this is not an error
			return true;
		}

		$f = fopen( $wgCheckUserLog, 'a' );
		if ( !$f ) {
			return false;
		}
		if ( !fwrite( $f, "<li>$entry</li>\n" ) ) {
			return false;
		}
		fclose( $f );
		return true;

}
}
?>

Gah! I thought that was updating the attachment, sorry. I'd like if someone
could delete these extras.

update; a bit shorter, log wording change

Update

attachment CheckUser_body.php ignored as obsolete

Autoblock IPs listed with link to checkip

attachment CheckUser_body.php ignored as obsolete

"list users only" option, replaced a useless extra option with a reason box

"list users only" option, replaced a useless extra option with a reason box

attachment CheckUser_body.php ignored as obsolete

robchur wrote:

Comment on attachment 2812
"list users only" option, replaced a useless extra option with a reason box

As with the other, not a true patch...

Comment on attachment 2812
"list users only" option, replaced a useless extra option with a reason box

"Patch" is obsolete. See better patch at bug 5044.

epriestley added a commit: Unknown Object (Diffusion Commit).Mar 4 2015, 8:16 AM