summaryrefslogtreecommitdiff
path: root/extensions/CheckUser/CheckUser_body.php
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2010-09-16 21:49:19 +0200
committerPierre Schmitz <pierre@archlinux.de>2010-09-16 21:49:19 +0200
commit91da036ca57d692fba4faad573aa4bca1435eda7 (patch)
tree97d4e9b74fc15dd9ff6cac4e023db04843375dbc /extensions/CheckUser/CheckUser_body.php
parentbda2edfb2799e73eaab83b775eda8afbe8bcb15d (diff)
Add CheckUser extension
* revision 73143 * url: http://www.mediawiki.org/wiki/Extension:CheckUser
Diffstat (limited to 'extensions/CheckUser/CheckUser_body.php')
-rw-r--r--extensions/CheckUser/CheckUser_body.php1432
1 files changed, 1432 insertions, 0 deletions
diff --git a/extensions/CheckUser/CheckUser_body.php b/extensions/CheckUser/CheckUser_body.php
new file mode 100644
index 00000000..ee9bd7d5
--- /dev/null
+++ b/extensions/CheckUser/CheckUser_body.php
@@ -0,0 +1,1432 @@
+<?php
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ echo "CheckUser extension\n";
+ exit( 1 );
+}
+
+class CheckUser extends SpecialPage {
+ /**
+ * Constructor -- set up the new special page
+ */
+ public function __construct() {
+ global $wgUser;
+ if ( $wgUser->isAllowed( 'checkuser' ) || !$wgUser->isAllowed( 'checkuser-log' ) ) {
+ parent::__construct( 'CheckUser', 'checkuser' );
+ } else {
+ parent::__construct( 'CheckUser', 'checkuser-log' );
+ }
+
+ $this->sk = $wgUser->getSkin();
+
+ wfLoadExtensionMessages( 'CheckUser' );
+ }
+
+ public function execute( $subpage ) {
+ global $wgRequest, $wgOut, $wgUser, $wgContLang;
+
+ $this->setHeaders();
+ $this->sk = $wgUser->getSkin();
+
+ // This is horribly shitty.
+ // Lacking formal aliases, it's tough to ensure we have compatibility.
+ // Links may break, which sucks.
+ // Language fallbacks will not always be properly utilized.
+ $logMatches = array(
+ wfMsgForContent( 'checkuser-log-subpage' ),
+ 'Log'
+ );
+
+ foreach ( $logMatches as $log ) {
+ if ( str_replace( '_', ' ', $wgContLang->lc( $subpage ) )
+ == str_replace( '_ ', ' ', $wgContLang->lc( $log ) ) ) {
+ if ( !$wgUser->isAllowed( 'checkuser-log' ) ) {
+ $wgOut->permissionRequired( 'checkuser-log' );
+ return;
+ }
+
+ $this->showLog();
+ return;
+ }
+ }
+
+ if ( !$wgUser->isAllowed( 'checkuser' ) ) {
+ if ( $wgUser->isAllowed( 'checkuser-log' ) ) {
+ $wgOut->addWikiText( wfMsg( 'checkuser-summary' ) .
+ "\n\n[[" . $this->getLogSubpageTitle()->getPrefixedText() .
+ '|' . wfMsg( 'checkuser-showlog' ) . ']]'
+ );
+ return;
+ }
+
+ $wgOut->permissionRequired( 'checkuser' );
+ return;
+ }
+
+ $user = $wgRequest->getText( 'user' ) ?
+ $wgRequest->getText( 'user' ) : $wgRequest->getText( 'ip' );
+ $user = trim( $user );
+ $reason = $wgRequest->getText( 'reason' );
+ $blockreason = $wgRequest->getText( 'blockreason' );
+ $checktype = $wgRequest->getVal( 'checktype' );
+ $period = $wgRequest->getInt( 'period' );
+ $users = $wgRequest->getArray( 'users' );
+ $tag = $wgRequest->getBool( 'usetag' ) ? trim( $wgRequest->getVal( 'tag' ) ) : '';
+ $talkTag = $wgRequest->getBool( 'usettag' ) ? trim( $wgRequest->getVal( 'talktag' ) ) : '';
+
+ # An IPv4? An IPv6? CIDR included?
+ if ( IP::isIPAddress( $user ) ) {
+ $ip = IP::sanitizeIP( $user );
+ $name = '';
+ $xff = '';
+ # An IPv4/IPv6 XFF string? CIDR included?
+ } elseif ( preg_match( '/^(.+)\/xff$/', $user, $m ) && IP::isIPAddress( $m[1] ) ) {
+ $ip = '';
+ $name = '';
+ $xff = IP::sanitizeIP( $m[1] );
+ # A user?
+ } else {
+ $ip = '';
+ $name = $user;
+ $xff = '';
+ }
+
+ $this->doForm( $user, $reason, $checktype, $ip, $xff, $name, $period );
+ # Perform one of the various submit operations...
+ if ( $wgRequest->wasPosted() ) {
+ if ( $wgRequest->getVal( 'action' ) === 'block' ) {
+ $this->doMassUserBlock( $users, $blockreason, $tag, $talkTag );
+ } elseif ( !$this->checkReason( $reason ) ) {
+ $wgOut->addWikiMsg( 'checkuser-noreason' );
+ } elseif ( $checktype == 'subuserips' ) {
+ $this->doUserIPsRequest( $name, $reason, $period );
+ } elseif ( $xff && $checktype == 'subipedits' ) {
+ $this->doIPEditsRequest( $xff, true, $reason, $period );
+ } elseif ( $checktype == 'subipedits' ) {
+ $this->doIPEditsRequest( $ip, false, $reason, $period );
+ } elseif ( $xff && $checktype == 'subipusers' ) {
+ $this->doIPUsersRequest( $xff, true, $reason, $period, $tag, $talkTag );
+ } elseif ( $checktype == 'subipusers' ) {
+ $this->doIPUsersRequest( $ip, false, $reason, $period, $tag, $talkTag );
+ } elseif ( $checktype == 'subuseredits' ) {
+ $this->doUserEditsRequest( $user, $reason, $period );
+ }
+ }
+ # Add CIDR calculation convenience form
+ $this->addJsCIDRForm();
+ $this->addStyles();
+ }
+
+ /**
+ * 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
+ */
+ protected 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' ) );
+ }
+ }
+ }
+
+ public function getLogSubpageTitle() {
+ if ( !isset( $this->logSubpageTitle ) ) {
+ $this->logSubpageTitle = $this->getTitle( wfMsgForContent( 'checkuser-log-subpage' ) );
+ }
+ return $this->logSubpageTitle;
+ }
+
+ protected function doForm( $user, $reason, $checktype, $ip, $xff, $name, $period ) {
+ global $wgOut, $wgUser;
+ $action = $this->getTitle()->escapeLocalUrl();
+ # Fill in requested type if it makes sense
+ $encipusers = $encipedits = $encuserips = $encuseredits = 0;
+ if ( $checktype == 'subipusers' && ( $ip || $xff ) ) {
+ $encipusers = 1;
+ } elseif ( $checktype == 'subipedits' && ( $ip || $xff ) ) {
+ $encipedits = 1;
+ } elseif ( $checktype == 'subuserips' && $name ) {
+ $encuserips = 1;
+ } elseif ( $checktype == 'subuseredits' && $name ) {
+ $encuseredits = 1;
+ # Defaults otherwise
+ } elseif ( $ip || $xff ) {
+ $encipedits = 1;
+ } else {
+ $encuserips = 1;
+ }
+ # Compile our nice form
+ # User box length should fit things like "2001:0db8:85a3:08d3:1319:8a2e:0370:7344/100/xff"
+ if ( $wgUser->isAllowed( 'checkuser-log' ) ) {
+ $wgOut->addWikiText( wfMsg( 'checkuser-summary' ) .
+ "\n\n[[" . $this->getLogSubpageTitle()->getPrefixedText() .
+ '|' . wfMsg( 'checkuser-showlog' ) . ']]'
+ );
+ }
+ // FIXME: use more Xml/Html methods
+ $form = "<form name='checkuserform' id='checkuserform' action=\"$action\" method='post'>";
+ $form .= '<fieldset><legend>' . wfMsgHtml( 'checkuser-query' ) . '</legend>';
+ $form .= '<table border="0" cellpadding="2"><tr>';
+ $form .= '<td>' . wfMsgHtml( 'checkuser-target' ) . '</td>';
+ $form .= '<td>' . Xml::input( 'user', 46, $user, array( 'id' => 'checktarget' ) );
+ $form .= '&#160;' . $this->getPeriodMenu( $period ) . '</td>';
+ $form .= '</tr><tr>';
+ $form .= '<td></td><td class="checkuserradios"><table border="0" cellpadding="3"><tr>';
+ $form .= '<td>' . Xml::radio( 'checktype', 'subuserips', $encuserips, array( 'id' => 'subuserips' ) );
+ $form .= ' ' . Xml::label( wfMsg( 'checkuser-ips' ), 'subuserips' ) . '</td>';
+ $form .= '<td>' . Xml::radio( 'checktype', 'subipedits', $encipedits, array( 'id' => 'subipedits' ) );
+ $form .= ' ' . Xml::label( wfMsg( 'checkuser-edits' ), 'subipedits' ) . '</td>';
+ $form .= '<td>' . Xml::radio( 'checktype', 'subipusers', $encipusers, array( 'id' => 'subipusers' ) );
+ $form .= ' ' . Xml::label( wfMsg( 'checkuser-users' ), 'subipusers' ) . '</td>';
+ $form .= '<td>' . Xml::radio( 'checktype', 'subuseredits', $encuseredits, array( 'id' => 'subuseredits' ) );
+ $form .= ' ' . Xml::label( wfMsg( 'checkuser-account' ), 'subuseredits' ) . '</td>';
+ $form .= '</tr></table></td>';
+ $form .= '</tr><tr>';
+ $form .= '<td>' . wfMsgHtml( 'checkuser-reason' ) . '</td>';
+ $form .= '<td>' . Xml::input( 'reason', 46, $reason, array( 'maxlength' => '150', 'id' => 'checkreason' ) );
+ $form .= '&#160; &#160;' . Xml::submitButton( wfMsg( 'checkuser-check' ),
+ array( 'id' => 'checkusersubmit', 'name' => 'checkusersubmit' ) ) . '</td>';
+ $form .= '</tr></table></fieldset></form>';
+ # Output form
+ $wgOut->addHTML( $form );
+ }
+
+ /**
+ * Add CSS/JS
+ */
+ protected function addStyles() {
+ global $wgScriptPath, $wgCheckUserStyleVersion, $wgOut;
+ // FIXME, use Html::
+ $encJSFile = htmlspecialchars( "$wgScriptPath/extensions/CheckUser/checkuser.js?$wgCheckUserStyleVersion" );
+ $wgOut->addScript( "<script type=\"text/javascript\" src=\"$encJSFile\"></script>" );
+ }
+
+ /**
+ * Get a selector of time period options
+ * @param int $selected, selected level
+ */
+ protected function getPeriodMenu( $selected = null ) {
+ $s = '<label for="period">' . wfMsgHtml( 'checkuser-period' ) . '</label>&#160;';
+ $s .= Xml::openElement( 'select', array( 'name' => 'period', 'id' => 'period', 'style' => 'margin-top:.2em;' ) );
+ $s .= Xml::option( wfMsg( 'checkuser-week-1' ), 7, $selected === 7 );
+ $s .= Xml::option( wfMsg( 'checkuser-week-2' ), 14, $selected === 14 );
+ $s .= Xml::option( wfMsg( 'checkuser-month' ), 31, $selected === 31 );
+ $s .= Xml::option( wfMsg( 'checkuser-all' ), 0, $selected === 0 );
+ $s .= Xml::closeElement( 'select' ) . "\n";
+ return $s;
+ }
+
+ /**
+ * Make a quick JS form for admins to calculate block ranges
+ */
+ protected function addJsCIDRForm() {
+ global $wgOut;
+ $s = '<fieldset id="mw-checkuser-cidrform" style="display:none; clear:both;">' .
+ '<legend>' . wfMsgHtml( 'checkuser-cidr-label' ) . '</legend>';
+ $s .= '<textarea id="mw-checkuser-iplist" rows="5" cols="50" onkeyup="updateCIDRresult()" onclick="updateCIDRresult()"></textarea><br />';
+ $s .= wfMsgHtml( 'checkuser-cidr-res' ) . '&#160;' .
+ Xml::input( 'mw-checkuser-cidr-res', 35, '', array( 'id' => 'mw-checkuser-cidr-res' ) ) .
+ '&#160;<strong id="mw-checkuser-ipnote"></strong>';
+ $s .= '</fieldset>';
+ $wgOut->addHTML( $s );
+ }
+
+ /**
+ * FIXME: documentation incomplete
+ * Block a list of selected users
+ * @param array $users
+ * @param string $reason
+ * @param string $tag
+ */
+ protected function doMassUserBlock( $users, $reason = '', $tag = '', $talkTag = '' ) {
+ global $wgOut, $wgUser, $wgCheckUserMaxBlocks, $wgLang;
+ if ( empty( $users ) || $wgUser->isBlocked( false ) ) {
+ $wgOut->addWikiMsg( 'checkuser-block-failure' );
+ return;
+ } elseif ( count( $users ) > $wgCheckUserMaxBlocks ) {
+ $wgOut->addWikiMsg( 'checkuser-block-limit' );
+ return;
+ } elseif ( !$reason ) {
+ $wgOut->addWikiMsg( 'checkuser-block-noreason' );
+ return;
+ }
+ $safeUsers = IPBlockForm::doMassUserBlock( $users, $reason, $tag, $talkTag );
+ if ( !empty( $safeUsers ) ) {
+ $n = count( $safeUsers );
+ $ulist = $wgLang->listToText( $safeUsers );
+ $wgOut->addWikiMsg( 'checkuser-block-success', $ulist, $wgLang->formatNum( $n ) );
+ } else {
+ $wgOut->addWikiMsg( 'checkuser-block-failure' );
+ }
+ }
+
+ protected function noMatchesMessage( $userName ) {
+ global $wgLang;
+ $dbr = wfGetDB( DB_SLAVE );
+ $user_id = User::idFromName( $userName );
+ if ( $user_id ) {
+ $revEdit = $dbr->selectField( 'revision',
+ 'rev_timestamp',
+ array( 'rev_user' => $user_id ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_timestamp DESC' )
+ );
+ } else {
+ $revEdit = $dbr->selectField( 'revision',
+ 'rev_timestamp',
+ array( 'rev_user_text' => $userName ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_timestamp DESC' )
+ );
+ }
+ $logEdit = 0;
+ if ( $user_id ) {
+ $logEdit = $dbr->selectField( 'logging',
+ 'log_timestamp',
+ array( 'log_user' => $user_id ),
+ __METHOD__,
+ array( 'ORDER BY' => 'log_timestamp DESC' )
+ );
+ }
+ $lastEdit = max( $revEdit, $logEdit );
+ if ( $lastEdit ) {
+ $lastEditDate = $wgLang->date( wfTimestamp( TS_MW, $lastEdit ), true );
+ $lastEditTime = $wgLang->time( wfTimestamp( TS_MW, $lastEdit ), true );
+ // FIXME: don't pass around parsed messages
+ return wfMsgExt( 'checkuser-nomatch-edits', 'parse', $lastEditDate, $lastEditTime );
+ }
+ return wfMsgExt( 'checkuser-nomatch', 'parse' );
+ }
+
+ protected function checkReason( $reason ) {
+ global $wgCheckUserForceSummary;
+ return ( !$wgCheckUserForceSummary || strlen( $reason ) );
+ }
+
+ /**
+ * FIXME: documentation out of date
+ * @param string $ip <???
+ * @param bool $xfor <???
+ * @param string $reason
+ * Get all IPs used by a user
+ * Shows first and last date and number of edits
+ */
+ protected function doUserIPsRequest( $user , $reason = '', $period = 0 ) {
+ global $wgOut, $wgLang;
+
+ $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 ) {
+ $wgOut->addWikiMsg( 'nouserspecified' );
+ return;
+ }
+ # Get ID, works better than text as user may have been renamed
+ $user_id = User::idFromName( $user );
+
+ # If user is not IP or nonexistent
+ if ( !$user_id ) {
+ // FIXME: addWikiMsg
+ $s = wfMsgExt( 'nosuchusershort', array( 'parse' ), $user );
+ $wgOut->addHTML( $s );
+ return;
+ }
+
+ # Record check...
+ if ( !$this->addLogEntry( 'userips', 'user', $user, $reason, $user_id ) ) {
+ // FIXME: addWikiMsg
+ $wgOut->addHTML( '<p>' . wfMsgHtml( 'checkuser-log-fail' ) . '</p>' );
+ }
+ $dbr = wfGetDB( DB_SLAVE );
+ $time_conds = $this->getTimeConds( $period );
+ # Ordering by the latest timestamp makes a small filesort on the IP list
+ $cu_changes = $dbr->tableName( 'cu_changes' );
+ $use_index = $dbr->useIndexClause( 'cuc_user_ip_time' );
+ $sql = "SELECT cuc_ip,cuc_ip_hex, COUNT(*) AS count,
+ MIN(cuc_timestamp) AS first, MAX(cuc_timestamp) AS last
+ FROM $cu_changes $use_index WHERE cuc_user = $user_id AND $time_conds
+ GROUP BY cuc_ip,cuc_ip_hex ORDER BY last DESC LIMIT 5001";
+
+ $ret = $dbr->query( $sql, __METHOD__ );
+ if ( !$dbr->numRows( $ret ) ) {
+ $s = $this->noMatchesMessage( $user ) . "\n";
+ } else {
+ $blockip = SpecialPage::getTitleFor( 'Blockip' );
+ $ips_edits = array();
+ $counter = 0;
+ while ( $row = $dbr->fetchObject( $ret ) ) {
+ if ( $counter >= 5000 ) {
+ // FIXME: addWikiMSG
+ $wgOut->addHTML( wfMsgExt( 'checkuser-limited', array( 'parse' ) ) );
+ break;
+ }
+ $ips_edits[$row->cuc_ip] = $row->count;
+ $ips_first[$row->cuc_ip] = $row->first;
+ $ips_last[$row->cuc_ip] = $row->last;
+ $ips_hex[$row->cuc_ip] = $row->cuc_ip_hex;
+ ++$counter;
+ }
+ // Count pinging might take some time...make sure it is there
+ wfSuppressWarnings();
+ set_time_limit( 60 );
+ wfRestoreWarnings();
+
+ $logs = SpecialPage::getTitleFor( 'Log' );
+ $s = '<div id="checkuserresults"><ul>';
+ foreach ( $ips_edits as $ip => $edits ) {
+ $s .= '<li>';
+ $s .= '<a href="' .
+ $this->getTitle()->escapeLocalURL( 'user=' . urlencode( $ip ) . '&reason=' . urlencode( $reason ) ) . '">' .
+ htmlspecialchars( $ip ) . '</a>';
+ $s .= ' (<a href="' . $blockip->escapeLocalURL( 'ip=' . urlencode( $ip ) ) . '">' .
+ wfMsgHtml( 'blocklink' ) . '</a>)';
+ if ( $ips_first[$ip] == $ips_last[$ip] ) {
+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp( TS_MW, $ips_first[$ip] ), true ) . ') ';
+ } else {
+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp( TS_MW, $ips_first[$ip] ), true ) .
+ ' -- ' . $wgLang->timeanddate( wfTimestamp( TS_MW, $ips_last[$ip] ), true ) . ') ';
+ }
+ $s .= ' <strong>[' . $edits . ']</strong>';
+
+ # If we get some results, it helps to know if the IP in general
+ # has a lot more edits, e.g. "tip of the iceberg"...
+ $ipedits = $dbr->estimateRowCount( 'cu_changes', '*',
+ array( 'cuc_ip_hex' => $ips_hex[$ip], $time_conds ),
+ __METHOD__ );
+ # If small enough, get a more accurate count
+ if ( $ipedits <= 1000 ) {
+ $ipedits = $dbr->selectField( 'cu_changes', 'COUNT(*)',
+ array( 'cuc_ip_hex' => $ips_hex[$ip], $time_conds ),
+ __METHOD__ );
+ }
+ if ( $ipedits > $ips_edits[$ip] ) {
+ $s .= ' <i>(' . wfMsgHtml( 'checkuser-ipeditcount', $ipedits ) . ')</i>';
+ }
+
+ # If this IP is blocked, give a link to the block log
+ $s .= $this->getIPBlockInfo( $ip );
+ $s .= '<div style="margin-left:5%">';
+ $s .= '<small>' . wfMsgExt( 'checkuser-toollinks', array( 'parseinline' ), urlencode( $ip ) ) . '</small>';
+ $s .= '</div>';
+ $s .= "</li>\n";
+ }
+ $s .= '</ul></div>';
+ }
+ $wgOut->addHTML( $s );
+ $dbr->freeResult( $ret );
+ }
+
+ protected function getIPBlockInfo( $ip ) {
+ static $blocklist;
+ $blocklist = SpecialPage::getTitleFor( 'Ipblocklist' );
+ $block = new Block();
+ $block->fromMaster( false ); // use slaves
+ if ( $block->load( $ip, 0 ) ) {
+ if ( IP::isIPAddress( $block->mAddress ) && strpos( $block->mAddress, '/' ) ) {
+ $userpage = Title::makeTitle( NS_USER, $block->mAddress );
+ $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml( 'checkuser-blocked' ),
+ 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
+ return ' <strong>(' . $blocklog . ' - ' . $block->mAddress . ')</strong>';
+ } elseif ( $block->mAuto ) {
+ $blocklog = $this->sk->makeKnownLinkObj( $blocklist, wfMsgHtml( 'checkuser-blocked' ),
+ 'ip=' . urlencode( "#$block->mId" ) );
+ return ' <strong>(' . $blocklog . ')</strong>';
+ } else {
+ $userpage = Title::makeTitle( NS_USER, $ip );
+ $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml( 'checkuser-blocked' ),
+ 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
+ return ' <strong>(' . $blocklog . ')</strong>';
+ }
+ }
+ return '';
+ }
+
+ /**
+ * @param string $ip
+ * @param bool $xfor
+ * @param string $reason
+ * FIXME: $period ???
+ * Shows all edits in Recent Changes by this IP (or range) and who made them
+ */
+ protected function doIPEditsRequest( $ip, $xfor = false, $reason = '', $period = 0 ) {
+ global $wgOut, $wgLang;
+ $dbr = wfGetDB( DB_SLAVE );
+ # Invalid IPs are passed in as a blank string
+ $ip_conds = $this->getIpConds( $dbr, $ip, $xfor );
+ if ( !$ip || $ip_conds === false ) {
+ $wgOut->addWikiMsg( 'badipaddress' );
+ return;
+ }
+
+ $logType = 'ipedits';
+ if ( $xfor ) {
+ $logType .= '-xff';
+ }
+ # Record check...
+ if ( !$this->addLogEntry( $logType, 'ip', $ip, $reason ) ) {
+ $wgOut->addWikiMsg( 'checkuser-log-fail' );
+ }
+
+ $ip_conds = $dbr->makeList( $ip_conds, LIST_AND );
+ $time_conds = $this->getTimeConds( $period );
+ $cu_changes = $dbr->tableName( 'cu_changes' );
+ # Ordered in descent by timestamp. Can cause large filesorts on range scans.
+ # Check how many rows will need sorting ahead of time to see if this is too big.
+ # Also, if we only show 5000, too many will be ignored as well.
+ $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time';
+ if ( strpos( $ip, '/' ) !== false ) {
+ # Quick index check only OK if no time constraint
+ if ( $period ) {
+ $rangecount = $dbr->selectField( 'cu_changes', 'COUNT(*)',
+ array( $ip_conds, $time_conds ),
+ __METHOD__,
+ array( 'USE INDEX' => $index ) );
+ } else {
+ $rangecount = $dbr->estimateRowCount( 'cu_changes', '*',
+ array( $ip_conds ),
+ __METHOD__,
+ array( 'USE INDEX' => $index ) );
+ }
+ // Sorting might take some time...make sure it is there
+ wfSuppressWarnings();
+ set_time_limit( 60 );
+ wfRestoreWarnings();
+ }
+ $counter = 0;
+ # See what is best to do after testing the waters...
+ if ( isset( $rangecount ) && $rangecount > 5000 ) {
+ $use_index = $dbr->useIndexClause( $index );
+ $sql = "SELECT cuc_ip_hex, COUNT(*) AS count,
+ MIN(cuc_timestamp) AS first, MAX(cuc_timestamp) AS last
+ FROM $cu_changes $use_index
+ WHERE $ip_conds AND $time_conds
+ GROUP BY cuc_ip_hex ORDER BY cuc_ip_hex LIMIT 5001";
+ $ret = $dbr->query( $sql, __METHOD__ );
+ # List out each IP that has edits
+ $s = wfMsgExt( 'checkuser-too-many', array( 'parse' ) );
+ $s .= '<ol>';
+ while ( $row = $ret->fetchObject() ) {
+ if ( $counter >= 5000 ) {
+ // FIXME: addWikiMsg
+ $wgOut->addHTML( wfMsgExt( 'checkuser-limited', array( 'parse' ) ) );
+ break;
+ }
+ # Convert the IP hexes into normal form
+ if ( strpos( $row->cuc_ip_hex, 'v6-' ) !== false ) {
+ $ip = substr( $row->cuc_ip_hex, 3 );
+ $ip = IP::HextoOctet( $ip );
+ } else {
+ $ip = long2ip( wfBaseConvert( $row->cuc_ip_hex, 16, 10, 8 ) );
+ }
+ $s .= '<li><a href="' .
+ $this->getTitle()->escapeLocalURL( 'user=' . urlencode( $ip ) . '&reason=' . urlencode( $reason ) . '&checktype=subipusers' ) .
+ '">' . $ip . '</a>';
+ if ( $row->first == $row->last ) {
+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp( TS_MW, $row->first ), true ) . ') ';
+ } else {
+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp( TS_MW, $row->first ), true ) .
+ ' -- ' . $wgLang->timeanddate( wfTimestamp( TS_MW, $row->last ), true ) . ') ';
+ }
+ $s .= ' [<strong>' . $row->count . "</strong>]</li>\n";
+ ++$counter;
+ }
+ $s .= '</ol>';
+ $dbr->freeResult( $ret );
+
+ $wgOut->addHTML( $s );
+ return;
+ } elseif ( isset( $rangecount ) && !$rangecount ) {
+ $s = $this->noMatchesMessage( $ip ) . "\n";
+ $wgOut->addHTML( $s );
+ return;
+ }
+ # OK, do the real query...
+ $use_index = $dbr->useIndexClause( $index );
+ $sql = "SELECT cuc_namespace,cuc_title,cuc_user,cuc_user_text,cuc_comment,cuc_actiontext,
+ cuc_timestamp,cuc_minor,cuc_page_id,cuc_type,cuc_this_oldid,cuc_last_oldid,cuc_ip,cuc_xff,cuc_agent
+ FROM $cu_changes $use_index WHERE $ip_conds AND $time_conds ORDER BY cuc_timestamp DESC LIMIT 5001";
+ $ret = $dbr->query( $sql, __METHOD__ );
+
+ if ( !$dbr->numRows( $ret ) ) {
+ $s = $this->noMatchesMessage( $ip ) . "\n";
+ } else {
+ # Cache common messages
+ $this->preCacheMessages();
+ # Try to optimize this query
+ $lb = new LinkBatch;
+ while ( $row = $ret->fetchObject() ) {
+ $userText = str_replace( ' ', '_', $row->cuc_user_text );
+ $lb->add( $row->cuc_namespace, $row->cuc_title );
+ $lb->add( NS_USER, $userText );
+ $lb->add( NS_USER_TALK, $userText );
+ }
+ $lb->execute();
+ $ret->seek( 0 );
+ # List out the edits
+ $s = '<div id="checkuserresults">';
+ while ( $row = $ret->fetchObject() ) {
+ if ( $counter >= 5000 ) {
+ // FIXME: addWikiMsg
+ $wgOut->addHTML( wfMsgExt( 'checkuser-limited', array( 'parse' ) ) );
+ break;
+ }
+ $s .= $this->CUChangesLine( $row, $reason );
+ ++$counter;
+ }
+ $s .= '</ul></div>';
+ $dbr->freeResult( $ret );
+ }
+
+ $wgOut->addHTML( $s );
+ }
+
+ /**
+ * @param string $user
+ * @param string $reason
+ * Shows all edits in Recent Changes by this user
+ */
+ protected function doUserEditsRequest( $user, $reason = '', $period = 0 ) {
+ global $wgOut;
+
+ $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 ) {
+ $wgOut->addWikiMsg( 'nouserspecified' );
+ return;
+ }
+ # Get ID, works better than text as user may have been renamed
+ $user_id = User::idFromName( $user );
+
+ # If user is not IP or nonexistent
+ if ( !$user_id ) {
+ $s = wfMsgExt( 'nosuchusershort', array( 'parse' ), $user );
+ $wgOut->addHTML( $s );
+ return;
+ }
+
+ # Record check...
+ if ( !$this->addLogEntry( 'useredits', 'user', $user, $reason, $user_id ) ) {
+ $wgOut->addHTML( '<p>' . wfMsgHtml( 'checkuser-log-fail' ) . '</p>' );
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $user_cond = "cuc_user = '$user_id'";
+ $time_conds = $this->getTimeConds( $period );
+ $cu_changes = $dbr->tableName( 'cu_changes' );
+ # Ordered in descent by timestamp. Causes large filesorts if there are many edits.
+ # Check how many rows will need sorting ahead of time to see if this is too big.
+ # If it is, sort by IP,time to avoid the filesort.
+ if ( $period ) {
+ $count = $dbr->selectField( 'cu_changes', 'COUNT(*)',
+ array( $user_cond, $time_conds ),
+ __METHOD__,
+ array( 'USE INDEX' => 'cuc_user_ip_time' ) );
+ } else {
+ $count = $dbr->estimateRowCount( 'cu_changes', '*',
+ array( $user_cond, $time_conds ),
+ __METHOD__,
+ array( 'USE INDEX' => 'cuc_user_ip_time' ) );
+ }
+ # Cache common messages
+ $this->preCacheMessages();
+ # See what is best to do after testing the waters...
+ if ( $count > 5000 ) {
+ $wgOut->addHTML( wfMsgExt( 'checkuser-limited', array( 'parse' ) ) );
+ $use_index = $dbr->useIndexClause( 'cuc_user_ip_time' );
+ $sql = "SELECT * FROM $cu_changes $use_index
+ WHERE $user_cond AND $time_conds
+ ORDER BY cuc_ip ASC, cuc_timestamp DESC LIMIT 5000";
+ $ret = $dbr->query( $sql, __METHOD__ );
+ # Try to optimize this query
+ $lb = new LinkBatch;
+ while ( $row = $ret->fetchObject() ) {
+ $lb->add( $row->cuc_namespace, $row->cuc_title );
+ }
+ $lb->execute();
+ $ret->seek( 0 );
+ $s = '';
+ while ( $row = $ret->fetchObject() ) {
+ if ( !$ip = htmlspecialchars( $row->cuc_ip ) ) {
+ continue;
+ }
+ if ( !isset( $lastIP ) ) {
+ $lastIP = $row->cuc_ip;
+ $s .= "\n<h2>$ip</h2>\n<div class=\"special\">";
+ } elseif ( $lastIP != $row->cuc_ip ) {
+ $s .= "</ul></div>\n<h2>$ip</h2>\n<div class=\"special\">";
+ $lastIP = $row->cuc_ip;
+ unset( $this->lastdate ); // start over
+ }
+ $s .= $this->CUChangesLine( $row, $reason );
+ }
+ $s .= '</ul></div>';
+ $dbr->freeResult( $ret );
+
+ $wgOut->addHTML( $s );
+ return;
+ }
+ // Sorting might take some time...make sure it is there
+ wfSuppressWarnings();
+ set_time_limit( 60 );
+ wfRestoreWarnings();
+ # OK, do the real query...
+ $use_index = $dbr->useIndexClause( 'cuc_user_ip_time' );
+ $sql = "SELECT * FROM $cu_changes $use_index
+ WHERE $user_cond AND $time_conds ORDER BY cuc_timestamp DESC LIMIT 5000";
+ $ret = $dbr->query( $sql, __METHOD__ );
+
+ if ( !$dbr->numRows( $ret ) ) {
+ $s = $this->noMatchesMessage( $user ) . "\n";
+ } else {
+ # Try to optimize this query
+ $lb = new LinkBatch;
+ while ( $row = $ret->fetchObject() ) {
+ $lb->add( $row->cuc_namespace, $row->cuc_title );
+ }
+ $lb->execute();
+ $ret->seek( 0 );
+ # List out the edits
+ $s = '<div id="checkuserresults">';
+ while ( $row = $ret->fetchObject() ) {
+ $s .= $this->CUChangesLine( $row, $reason );
+ }
+ $s .= '</ul></div>';
+ $dbr->freeResult( $ret );
+ }
+
+ $wgOut->addHTML( $s );
+ }
+
+ /**
+ * @param string $ip
+ * @param bool $xfor
+ * @param string $reason
+ * @param int $period
+ * @param string $tag
+ * @param string $talkTag
+ * Lists all users in recent changes who used an IP, newest to oldest down
+ * Outputs usernames, latest and earliest found edit date, and count
+ * List unique IPs used for each user in time order, list corresponding user agent
+ */
+ protected function doIPUsersRequest( $ip, $xfor = false, $reason = '', $period = 0, $tag = '', $talkTag = '' ) {
+ global $wgUser, $wgOut, $wgLang;
+ $dbr = wfGetDB( DB_SLAVE );
+ # Invalid IPs are passed in as a blank string
+ $ip_conds = $this->getIpConds( $dbr, $ip, $xfor );
+ if ( !$ip || $ip_conds === false ) {
+ $wgOut->addWikiMsg( 'badipaddress' );
+ return;
+ }
+
+ $logType = 'ipusers';
+ if ( $xfor ) {
+ $logType .= '-xff';
+ }
+ # Log the check...
+ if ( !$this->addLogEntry( $logType, 'ip', $ip, $reason ) ) {
+ $wgOut->addHTML( '<p>' . wfMsgHtml( 'checkuser-log-fail' ) . '</p>' );
+ }
+
+ $ip_conds = $dbr->makeList( $ip_conds, LIST_AND );
+ $time_conds = $this->getTimeConds( $period );
+ $cu_changes = $dbr->tableName( 'cu_changes' );
+ $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time';
+ # Ordered in descent by timestamp. Can cause large filesorts on range scans.
+ # Check how many rows will need sorting ahead of time to see if this is too big.
+ if ( strpos( $ip, '/' ) !== false ) {
+ # Quick index check only OK if no time constraint
+ if ( $period ) {
+ $rangecount = $dbr->selectField( 'cu_changes', 'COUNT(*)',
+ array( $ip_conds, $time_conds ),
+ __METHOD__,
+ array( 'USE INDEX' => $index ) );
+ } else {
+ $rangecount = $dbr->estimateRowCount( 'cu_changes', '*',
+ array( $ip_conds ),
+ __METHOD__,
+ array( 'USE INDEX' => $index ) );
+ }
+ // Sorting might take some time...make sure it is there
+ wfSuppressWarnings();
+ set_time_limit( 120 );
+ wfRestoreWarnings();
+ }
+ // Are there too many edits?
+ if ( isset( $rangecount ) && $rangecount > 10000 ) {
+ $use_index = $dbr->useIndexClause( $index );
+ $sql = "SELECT cuc_ip_hex, COUNT(*) AS count,
+ MIN(cuc_timestamp) AS first, MAX(cuc_timestamp) AS last
+ FROM $cu_changes $use_index WHERE $ip_conds AND $time_conds
+ GROUP BY cuc_ip_hex ORDER BY cuc_ip_hex LIMIT 5001";
+ $ret = $dbr->query( $sql, __METHOD__ );
+ # List out each IP that has edits
+ $s = '<h5>' . wfMsg( 'checkuser-too-many' ) . '</h5>';
+ $s .= '<ol>';
+ $counter = 0;
+ while ( $row = $ret->fetchObject() ) {
+ if ( $counter >= 5000 ) {
+ $wgOut->addHTML( wfMsgExt( 'checkuser-limited', array( 'parse' ) ) );
+ break;
+ }
+ # Convert the IP hexes into normal form
+ if ( strpos( $row->cuc_ip_hex, 'v6-' ) !== false ) {
+ $ip = substr( $row->cuc_ip_hex, 3 );
+ $ip = IP::HextoOctet( $ip );
+ } else {
+ $ip = long2ip( wfBaseConvert( $row->cuc_ip_hex, 16, 10, 8 ) );
+ }
+ $s .= '<li><a href="' .
+ $this->getTitle()->escapeLocalURL( 'user=' . urlencode( $ip ) . '&reason=' . urlencode( $reason ) . '&checktype=subipusers' ) .
+ '">' . $ip . '</a>';
+ if ( $row->first == $row->last ) {
+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp( TS_MW, $row->first ), true ) . ') ';
+ } else {
+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp( TS_MW, $row->first ), true ) .
+ ' -- ' . $wgLang->timeanddate( wfTimestamp( TS_MW, $row->last ), true ) . ') ';
+ }
+ $s .= ' [<strong>' . $row->count . "</strong>]</li>\n";
+ ++$counter;
+ }
+ $s .= '</ol>';
+ $dbr->freeResult( $ret );
+
+ $wgOut->addHTML( $s );
+ return;
+ } elseif ( isset( $rangecount ) && !$rangecount ) {
+ $s = $this->noMatchesMessage( $ip ) . "\n";
+ $wgOut->addHTML( $s );
+ return;
+ }
+
+ global $wgMemc;
+ # OK, do the real query...
+ $use_index = $dbr->useIndexClause( $index );
+ $sql = "SELECT cuc_user_text, cuc_timestamp, cuc_user, cuc_ip, cuc_agent, cuc_xff
+ FROM $cu_changes $use_index WHERE $ip_conds AND $time_conds
+ ORDER BY cuc_timestamp DESC LIMIT 10000";
+ $ret = $dbr->query( $sql, __METHOD__ );
+
+ $users_first = $users_last = $users_edits = $users_ids = array();
+ if ( !$dbr->numRows( $ret ) ) {
+ $s = $this->noMatchesMessage( $ip ) . "\n";
+ } else {
+ global $wgAuth;
+ while ( ( $row = $dbr->fetchObject( $ret ) ) != false ) {
+ 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_ids[$row->cuc_user_text] = $row->cuc_user;
+ $users_infosets[$row->cuc_user_text] = array();
+ $users_agentsets[$row->cuc_user_text] = array();
+ }
+ $users_edits[$row->cuc_user_text] += 1;
+ $users_first[$row->cuc_user_text] = $row->cuc_timestamp;
+ # Treat blank or NULL xffs as empty strings
+ $xff = empty( $row->cuc_xff ) ? null : $row->cuc_xff;
+ $xff_ip_combo = array( $row->cuc_ip, $xff );
+ # Add this IP/XFF combo for this username if it's not already there
+ if ( !in_array( $xff_ip_combo, $users_infosets[$row->cuc_user_text] ) ) {
+ $users_infosets[$row->cuc_user_text][] = $xff_ip_combo;
+ }
+ # Add this agent string if it's not already there; 10 max.
+ if ( count( $users_agentsets[$row->cuc_user_text] ) < 10 ) {
+ if ( !in_array( $row->cuc_agent, $users_agentsets[$row->cuc_user_text] ) ) {
+ $users_agentsets[$row->cuc_user_text][] = $row->cuc_agent;
+ }
+ }
+ }
+ $dbr->freeResult( $ret );
+
+ $action = $this->getTitle()->escapeLocalURL( 'action=block' );
+ $s = "<form name='checkuserblock' id='checkuserblock' action=\"$action\" method='post'>";
+ $s .= '<div id="checkuserresults"><ul>';
+ foreach ( $users_edits as $name => $count ) {
+ $s .= '<li>';
+ $s .= Xml::check( 'users[]', false, array( 'value' => $name ) ) . '&#160;';
+ # Load user object
+ $user = User::newFromName( $name, false );
+ # Add user tool links
+ $s .= $this->sk->userLink( - 1 , $name ) . $this->sk->userToolLinks( - 1 , $name );
+ # Add CheckUser link
+ $s .= ' (<a href="' . $this->getTitle()->escapeLocalURL( 'user=' . urlencode( $name ) .
+ '&reason=' . urlencode( $reason ) ) . '">' . wfMsgHtml( 'checkuser-check' ) . '</a>)';
+ # Show edit time range
+ if ( $users_first[$name] == $users_last[$name] ) {
+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp( TS_MW, $users_first[$name] ), true ) . ') ';
+ } else {
+ $s .= ' (' . $wgLang->timeanddate( wfTimestamp( TS_MW, $users_first[$name] ), true ) .
+ ' -- ' . $wgLang->timeanddate( wfTimestamp( TS_MW, $users_last[$name] ), true ) . ') ';
+ }
+ # Total edit count
+ $s .= ' [<strong>' . $count . '</strong>]<br />';
+ # Check if this user or IP is blocked. If so, give a link to the block log...
+ $ip = IP::isIPAddress( $name ) ? $name : '';
+ $flags = $this->userBlockFlags( $ip, $users_ids[$name], $user );
+ # Show if account is local only
+ $authUser = $wgAuth->getUserInstance( $user );
+ if ( $user->getId() && $authUser->getId() === 0 ) {
+ $flags[] = '<strong>(' . wfMsgHtml( 'checkuser-localonly' ) . ')</strong>';
+ }
+ # Check for extra user rights...
+ if ( $users_ids[$name] ) {
+ if ( $user->isLocked() ) {
+ $flags[] = '<b>(' . wfMsgHtml( 'checkuser-locked' ) . ')</b>';
+ }
+ $list = array();
+ foreach ( $user->getGroups() as $group ) {
+ $list[] = self::buildGroupLink( $group );
+ }
+ $groups = $wgLang->commaList( $list );
+ if ( $groups ) {
+ $flags[] = '<i>(' . $groups . ')</i>';
+ }
+ }
+ # Check how many accounts the user made recently?
+ if ( $ip ) {
+ $key = wfMemcKey( 'acctcreate', 'ip', $ip );
+ $count = intval( $wgMemc->get( $key ) );
+ if ( $count ) {
+ $flags[] = '<strong>[' . wfMsgExt( 'checkuser-accounts', 'parsemag', $wgLang->formatNum( $count ) ) . ']</strong>';
+ }
+ }
+ $s .= implode( ' ', $flags );
+ $s .= '<ol>';
+ # List out each IP/XFF combo for this username
+ for ( $i = ( count( $users_infosets[$name] ) - 1 ); $i >= 0; $i-- ) {
+ $set = $users_infosets[$name][$i];
+ # IP link
+ $s .= '<li>';
+ $s .= '<a href="' . $this->getTitle()->escapeLocalURL( 'user=' . urlencode( $set[0] ) ) . '">' . htmlspecialchars( $set[0] ) . '</a>';
+ # XFF string, link to /xff search
+ if ( $set[1] ) {
+ # Flag our trusted proxies
+ list( $client, $trusted ) = efGetClientIPfromXFF( $set[1], $set[0] );
+ $c = $trusted ? '#F0FFF0' : '#FFFFCC';
+ $s .= '&#160;&#160;&#160;<span style="background-color: ' . $c . '"><strong>XFF</strong>: ';
+ $s .= $this->sk->makeKnownLinkObj( $this->getTitle(),
+ htmlspecialchars( $set[1] ),
+ 'user=' . urlencode( $client ) . '/xff' ) . '</span>';
+ }
+ $s .= "</li>\n";
+ }
+ $s .= '</ol><br /><ol>';
+ # List out each agent for this username
+ for ( $i = ( count( $users_agentsets[$name] ) - 1 ); $i >= 0; $i-- ) {
+ $agent = $users_agentsets[$name][$i];
+ $s .= '<li><i>' . htmlspecialchars( $agent ) . "</i></li>\n";
+ }
+ $s .= '</ol>';
+ $s .= '</li>';
+ }
+ $s .= "</ul></div>\n";
+ if ( $wgUser->isAllowed( 'block' ) && !$wgUser->isBlocked() ) {
+ $s .= "<fieldset>\n";
+ $s .= '<legend>' . wfMsgHtml( 'checkuser-massblock' ) . "</legend>\n";
+ $s .= '<p>' . wfMsgExt( 'checkuser-massblock-text', array( 'parseinline' ) ) . "</p>\n";
+ $s .= '<table><tr>' .
+ '<td>' . Xml::check( 'usetag', false, array( 'id' => 'usetag' ) ) . '</td>' .
+ '<td>' . Xml::label( wfMsgHtml( 'checkuser-blocktag' ), 'usetag' ) . '</td>' .
+ '<td>' . Xml::input( 'tag', 46, $tag, array( 'id' => 'blocktag' ) ) . '</td>' .
+ '</tr><tr>' .
+ '<td>' . Xml::check( 'usettag', false, array( 'id' => 'usettag' ) ) . '</td>' .
+ '<td>' . Xml::label( wfMsgHtml( 'checkuser-blocktag-talk' ), 'usettag' ) . '</td>' .
+ '<td>' . Xml::input( 'talktag', 46, $talkTag, array( 'id' => 'talktag' ) ) . '</td>' .
+ '</tr></table>';
+ $s .= '<p>' . wfMsgHtml( 'checkuser-reason' ) . '&#160;';
+ $s .= Xml::input( 'blockreason', 46, '', array( 'maxlength' => '150', 'id' => 'blockreason' ) );
+ $s .= '&#160;' . Xml::submitButton( wfMsgHtml( 'checkuser-massblock-commit' ),
+ array( 'id' => 'checkuserblocksubmit', 'name' => 'checkuserblock' ) ) . "</p>\n";
+ $s .= "</fieldset>\n";
+ }
+ $s .= '</form>';
+ }
+
+ $wgOut->addHTML( $s );
+ }
+
+ protected function userBlockFlags( $ip, $userId, $user ) {
+ static $logs, $blocklist;
+ $logs = SpecialPage::getTitleFor( 'Log' );
+ $blocklist = SpecialPage::getTitleFor( 'Ipblocklist' );
+ $block = new Block();
+ $block->fromMaster( false ); // use slaves
+ $flags = array();
+ if ( $block->load( $ip, $userId ) ) {
+ // Range blocked?
+ if ( IP::isIPAddress( $block->mAddress ) && strpos( $block->mAddress, '/' ) ) {
+ $userpage = Title::makeTitle( NS_USER, $block->mAddress );
+ $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml( 'checkuser-blocked' ),
+ 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
+ $flags[] = '<strong>(' . $blocklog . ' - ' . $block->mAddress . ')</strong>';
+ // Auto blocked?
+ } elseif ( $block->mAuto ) {
+ $blocklog = $this->sk->makeKnownLinkObj( $blocklist,
+ wfMsgHtml( 'checkuser-blocked' ), 'ip=' . urlencode( "#{$block->mId}" ) );
+ $flags[] = '<strong>(' . $blocklog . ')</strong>';
+ } else {
+ $userpage = $user->getUserPage();
+ $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml( 'checkuser-blocked' ),
+ 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
+ $flags[] = '<strong>(' . $blocklog . ')</strong>';
+ }
+ // IP that is blocked on all wikis?
+ } elseif ( $ip == $user->getName() && $user->isBlockedGlobally( $ip ) ) {
+ $flags[] = '<strong>(' . wfMsgHtml( 'checkuser-gblocked' ) . ')</strong>';
+ } elseif ( self::userWasBlocked( $user->getName() ) ) {
+ $userpage = $user->getUserPage();
+ $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml( 'checkuser-wasblocked' ),
+ 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) );
+ $flags[] = '<strong>(' . $blocklog . ')</strong>';
+ }
+ return $flags;
+ }
+
+ /**
+ * @param Row $row
+ * @param string $reason
+ * @return a streamlined recent changes line with IP data
+ */
+ protected function CUChangesLine( $row, $reason ) {
+ global $wgLang;
+ static $cuTitle, $flagCache;
+ $cuTitle = SpecialPage::getTitleFor( 'CheckUser' );
+ # Add date headers as needed
+ $date = $wgLang->date( wfTimestamp( TS_MW, $row->cuc_timestamp ), true, true );
+ if ( !isset( $this->lastdate ) ) {
+ $this->lastdate = $date;
+ $line = "\n<h4>$date</h4>\n<ul class=\"special\">";
+ } elseif ( $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->getLinksFromRow( $row );
+ # Show date
+ $line .= ' . . ' . $wgLang->time( wfTimestamp( TS_MW, $row->cuc_timestamp ), true, true ) . ' . . ';
+ # Userlinks
+ $line .= $this->sk->userLink( $row->cuc_user, $row->cuc_user_text );
+ $line .= $this->sk->userToolLinks( $row->cuc_user, $row->cuc_user_text );
+ # Get block info
+ if ( isset( $flagCache[$row->cuc_user_text] ) ) {
+ $flags = $flagCache[$row->cuc_user_text];
+ } else {
+ $user = User::newFromName( $row->cuc_user_text, false );
+ $ip = IP::isIPAddress( $row->cuc_user_text ) ? $row->cuc_user_text : '';
+ $flags = $this->userBlockFlags( $ip, $row->cuc_user, $user );
+ $flagCache[$row->cuc_user_text] = $flags;
+ }
+ # Add any block information
+ if ( count( $flags ) ) {
+ $line .= ' ' . implode( ' ', $flags );
+ }
+ # Action text, hackish ...
+ if ( $row->cuc_actiontext ) {
+ $line .= ' ' . $this->sk->formatComment( $row->cuc_actiontext ) . ' ';
+ }
+ # Comment
+ $line .= $this->sk->commentBlock( $row->cuc_comment );
+ $line .= '<br />&#160; &#160; &#160; &#160; <small>';
+ # IP
+ $line .= ' <strong>IP</strong>: ' . $this->sk->makeKnownLinkObj( $cuTitle,
+ htmlspecialchars( $row->cuc_ip ), 'user=' . urlencode( $row->cuc_ip ) . '&reason=' . urlencode( $reason ) );
+ # XFF
+ if ( $row->cuc_xff != null ) {
+ # Flag our trusted proxies
+ list( $client, $trusted ) = efGetClientIPfromXFF( $row->cuc_xff, $row->cuc_ip );
+ $c = $trusted ? '#F0FFF0' : '#FFFFCC';
+ $line .= '&#160;&#160;&#160;<span class="mw-checkuser-xff" style="background-color: ' . $c . '">' .
+ '<strong>XFF</strong>: ';
+ $line .= $this->sk->makeKnownLinkObj( $cuTitle,
+ htmlspecialchars( $row->cuc_xff ),
+ 'user=' . urlencode( $client ) . '/xff&reason=' . urlencode( $reason ) ) . '</span>';
+ }
+ # User agent
+ $line .= '&#160;&#160;&#160;<span class="mw-checkuser-agent" style="color:#888;">' .
+ htmlspecialchars( $row->cuc_agent ) . '</span>';
+
+ $line .= "</small></li>\n";
+
+ return $line;
+ }
+
+ /**
+ * @param $row
+ * @create diff/hist/page link
+ */
+ protected function getLinksFromRow( $row ) {
+ // Log items (old format) and events to logs
+
+ if ( $row->cuc_type == RC_LOG && $row->cuc_namespace == NS_SPECIAL ) {
+ list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $row->cuc_title );
+ $logname = LogPage::logName( $logtype );
+ $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
+ $links = '(' . $this->sk->makeKnownLinkObj( $title, $logname ) . ')';
+ // Log items
+ } elseif ( $row->cuc_type == RC_LOG ) {
+ $title = Title::makeTitle( $row->cuc_namespace, $row->cuc_title );
+ $links = '(' . $this->sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), $this->message['log'],
+ wfArrayToCGI( array( 'page' => $title->getPrefixedText() ) ) ) . ')';
+ } 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->sk->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->sk->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->sk->makeLinkObj( $title );
+ }
+ return $links;
+ }
+
+ protected static function userWasBlocked( $name ) {
+ $userpage = Title::makeTitle( NS_USER, $name );
+ return wfGetDB( DB_SLAVE )->selectField( 'logging', '1',
+ array( 'log_type' => array( 'block', 'suppress' ),
+ 'log_action' => 'block',
+ 'log_namespace' => $userpage->getNamespace(),
+ 'log_title' => $userpage->getDBkey() ),
+ __METHOD__,
+ array( 'USE INDEX' => 'page_time' ) );
+ }
+
+ /**
+ * Format a link to a group description page
+ *
+ * @param string $group
+ * @return string
+ */
+ protected static function buildGroupLink( $group ) {
+ static $cache = array();
+ if ( !isset( $cache[$group] ) ) {
+ $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) );
+ }
+ return $cache[$group];
+ }
+
+ /**
+ * @param Database $db
+ * @param string $ip
+ * @param string $xfor
+ * @return mixed array/false conditions
+ */
+ protected 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 false; // invalid
+ }
+ list( $start, $end ) = IP::parseRange( $ip );
+ return array( 'cuc_' . $type . '_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) );
+ } elseif ( 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, 96-128 bits
+ if ( $matches[1] < 96 || $matches[1] > 128 ) {
+ return false; // invalid
+ }
+ list( $start, $end ) = IP::parseRange6( $ip );
+ return array( 'cuc_' . $type . '_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) );
+ } elseif ( preg_match( '#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#', $ip ) ) {
+ // 32 bit IPv4
+ $ip_hex = IP::toHex( $ip );
+ return array( 'cuc_' . $type . '_hex' => $ip_hex );
+ } elseif ( 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 );
+ }
+ // throw away this query, incomplete IP, these don't get through the entry point anyway
+ return false; // invalid
+ }
+
+ protected function getTimeConds( $period ) {
+ if ( !$period ) {
+ return '1 = 1';
+ }
+ $dbr = wfGetDB( DB_SLAVE );
+ $cutoff_unixtime = time() - ( $period * 24 * 3600 );
+ $cutoff_unixtime = $cutoff_unixtime - ( $cutoff_unixtime % 86400 );
+ $cutoff = $dbr->addQuotes( $dbr->timestamp( $cutoff_unixtime ) );
+ return "cuc_timestamp > $cutoff";
+ }
+
+ protected function showLog() {
+ global $wgRequest, $wgOut;
+ $type = $wgRequest->getVal( 'cuSearchType' );
+ $target = $wgRequest->getVal( 'cuSearch' );
+ $year = $wgRequest->getIntOrNull( 'year' );
+ $month = $wgRequest->getIntOrNull( 'month' );
+ $error = false;
+ $dbr = wfGetDB( DB_SLAVE );
+ $searchConds = false;
+
+ $wgOut->setPageTitle( wfMsg( 'checkuser-log' ) );
+
+ $wgOut->addHTML( $this->sk->makeKnownLinkObj( $this->getTitle(), wfMsgHtml( 'checkuser-log-return' ) ) );
+
+ if ( $type === null ) {
+ $type = 'target';
+ } elseif ( $type == 'initiator' ) {
+ $user = User::newFromName( $target );
+ if ( !$user || !$user->getID() ) {
+ $error = 'checkuser-user-nonexistent';
+ } else {
+ $searchConds = array( 'cul_user' => $user->getID() );
+ }
+ } else /* target */ {
+ $type = 'target';
+ // Is it an IP?
+ list( $start, $end ) = IP::parseRange( $target );
+ if ( $start !== false ) {
+ if ( $start == $end ) {
+ $searchConds = array( 'cul_target_hex = ' . $dbr->addQuotes( $start ) . ' OR ' .
+ '(cul_range_end >= ' . $dbr->addQuotes( $start ) . ' AND ' .
+ 'cul_range_start <= ' . $dbr->addQuotes( $end ) . ')'
+ );
+ } else {
+ $searchConds = array(
+ '(cul_target_hex >= ' . $dbr->addQuotes( $start ) . ' AND ' .
+ 'cul_target_hex <= ' . $dbr->addQuotes( $end ) . ') OR ' .
+ '(cul_range_end >= ' . $dbr->addQuotes( $start ) . ' AND ' .
+ 'cul_range_start <= ' . $dbr->addQuotes( $end ) . ')'
+ );
+ }
+ } else {
+ // Is it a user?
+ $user = User::newFromName( $target );
+ if ( $user && $user->getID() ) {
+ $searchConds = array(
+ 'cul_type' => array( 'userips', 'useredits' ),
+ 'cul_target_id' => $user->getID(),
+ );
+ } elseif ( $target ) {
+ $error = 'checkuser-user-nonexistent';
+ }
+ }
+ }
+
+ $searchTypes = array( 'initiator', 'target' );
+ $select = "<select name=\"cuSearchType\" style='margin-top:.2em;'>\n";
+ foreach ( $searchTypes as $searchType ) {
+ if ( $type == $searchType ) {
+ $checked = 'selected="selected"';
+ } else {
+ $checked = '';
+ }
+ $caption = wfMsgHtml( 'checkuser-search-' . $searchType );
+ $select .= "<option value=\"$searchType\" $checked>$caption</option>\n";
+ }
+ $select .= '</select>';
+
+ $encTarget = htmlspecialchars( $target );
+ $msgSearch = wfMsgHtml( 'checkuser-search' );
+ $input = "<input type=\"text\" name=\"cuSearch\" value=\"$encTarget\" size=\"40\"/>";
+ $msgSearchForm = wfMsgHtml( 'checkuser-search-form', $select, $input );
+ $formAction = $this->getLogSubpageTitle()->escapeLocalURL();
+ $msgSearchSubmit = '&#160;&#160;' . wfMsgHtml( 'checkuser-search-submit' ) . '&#160;&#160;';
+
+ $s = "<form method='get' action=\"$formAction\">\n" .
+ "<fieldset><legend>$msgSearch</legend>\n" .
+ "<p>$msgSearchForm</p>\n" .
+ "<p>" . $this->getDateMenu( $year, $month ) . "&#160;&#160;&#160;\n" .
+ "<input type=\"submit\" name=\"cuSearchSubmit\" value=\"$msgSearchSubmit\"/></p>\n" .
+ "</fieldset></form>\n";
+ $wgOut->addHTML( $s );
+
+ if ( $error !== false ) {
+ $wgOut->addWikiText( '<div class="errorbox">' . wfMsg( $error ) . '</div>' );
+ return;
+ }
+
+ $pager = new CheckUserLogPager( $this, $searchConds, $year, $month );
+ $wgOut->addHTML(
+ $pager->getNavigationBar() .
+ $pager->getBody() .
+ $pager->getNavigationBar()
+ );
+ }
+
+ /**
+ * @return string Formatted HTML
+ * @param int $year
+ * @param int $month
+ */
+ protected function getDateMenu( $year, $month ) {
+ # Offset overrides year/month selection
+ if ( $month && $month !== - 1 ) {
+ $encMonth = intval( $month );
+ } else {
+ $encMonth = '';
+ }
+ if ( $year ) {
+ $encYear = intval( $year );
+ } elseif ( $encMonth ) {
+ $thisMonth = intval( gmdate( 'n' ) );
+ $thisYear = intval( gmdate( 'Y' ) );
+ if ( intval( $encMonth ) > $thisMonth ) {
+ $thisYear--;
+ }
+ $encYear = $thisYear;
+ } else {
+ $encYear = '';
+ }
+ return Xml::label( wfMsg( 'year' ), 'year' ) . ' ' .
+ Xml::input( 'year', 4, $encYear, array( 'id' => 'year', 'maxlength' => 4 ) ) .
+ ' ' .
+ Xml::label( wfMsg( 'month' ), 'month' ) . ' ' .
+ Xml::monthSelector( $encMonth, - 1 );
+ }
+
+ protected function addLogEntry( $logType, $targetType, $target, $reason, $targetID = 0 ) {
+ global $wgUser;
+
+ if ( $targetType == 'ip' ) {
+ list( $rangeStart, $rangeEnd ) = IP::parseRange( $target );
+ $targetHex = $rangeStart;
+ if ( $rangeStart == $rangeEnd ) {
+ $rangeStart = $rangeEnd = '';
+ }
+ } else {
+ $targetHex = $rangeStart = $rangeEnd = '';
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+ $cul_id = $dbw->nextSequenceValue( 'cu_log_cul_id_seq' );
+ $dbw->insert( 'cu_log',
+ array(
+ 'cul_id' => $cul_id,
+ 'cul_timestamp' => $dbw->timestamp(),
+ 'cul_user' => $wgUser->getID(),
+ 'cul_user_text' => $wgUser->getName(),
+ 'cul_reason' => $reason,
+ 'cul_type' => $logType,
+ 'cul_target_id' => $targetID,
+ 'cul_target_text' => $target,
+ 'cul_target_hex' => $targetHex,
+ 'cul_range_start' => $rangeStart,
+ 'cul_range_end' => $rangeEnd,
+ ), __METHOD__ );
+ return true;
+ }
+}
+
+class CheckUserLogPager extends ReverseChronologicalPager {
+ var $searchConds, $specialPage, $y, $m;
+
+ function __construct( $specialPage, $searchConds, $y, $m ) {
+ parent::__construct();
+ /*
+ $this->messages = array_map( 'wfMsg',
+ array( 'comma-separator', 'checkuser-log-userips', 'checkuser-log-ipedits', 'checkuser-log-ipusers',
+ 'checkuser-log-ipedits-xff', 'checkuser-log-ipusers-xff' ) );*/
+
+ $this->getDateCond( $y, $m );
+ $this->searchConds = $searchConds ? $searchConds : array();
+ $this->specialPage = $specialPage;
+ }
+
+ function formatRow( $row ) {
+ global $wgLang;
+
+ $skin = $this->getSkin();
+
+ if ( $row->cul_reason === '' ) {
+ $comment = '';
+ } else {
+ $comment = $skin->commentBlock( $row->cul_reason );
+ }
+
+ $user = $skin->userLink( $row->cul_user, $row->user_name );
+
+ if ( $row->cul_type == 'userips' || $row->cul_type == 'useredits' ) {
+ $target = $skin->userLink( $row->cul_target_id, $row->cul_target_text ) .
+ $skin->userToolLinks( $row->cul_target_id, $row->cul_target_text );
+ } else {
+ $target = $row->cul_target_text;
+ }
+
+ return '<li>' .
+ $wgLang->timeanddate( wfTimestamp( TS_MW, $row->cul_timestamp ), true ) .
+ wfMsg( 'comma-separator' ) .
+ wfMsg(
+ 'checkuser-log-' . $row->cul_type,
+ $user,
+ $target
+ ) .
+ $comment .
+ '</li>';
+ }
+
+ function getStartBody() {
+ if ( $this->getNumRows() ) {
+ return '<ul>';
+ } else {
+ return '';
+ }
+ }
+
+ function getEndBody() {
+ if ( $this->getNumRows() ) {
+ return '</ul>';
+ } else {
+ return '';
+ }
+ }
+
+ function getEmptyBody() {
+ return '<p>' . wfMsgHtml( 'checkuser-empty' ) . '</p>';
+ }
+
+ function getQueryInfo() {
+ $this->searchConds[] = 'user_id = cul_user';
+ return array(
+ 'tables' => array( 'cu_log', 'user' ),
+ 'fields' => $this->selectFields(),
+ 'conds' => $this->searchConds
+ );
+ }
+
+ function getIndexField() {
+ return 'cul_timestamp';
+ }
+
+ function getTitle() {
+ return $this->specialPage->getLogSubpageTitle();
+ }
+
+ function selectFields() {
+ return array(
+ 'cul_id', 'cul_timestamp', 'cul_user', 'cul_reason', 'cul_type',
+ 'cul_target_id', 'cul_target_text', 'user_name'
+ );
+ }
+}