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 .= '
' . wfMsgHtml( 'checkuser-query' ) . ''; $form .= ''; $form .= ''; $form .= ''; $form .= ''; $form .= ''; $form .= ''; $form .= ''; $form .= ''; $form .= '
' . wfMsgHtml( 'checkuser-target' ) . '' . Xml::input( 'user', 46, $user, array( 'id' => 'checktarget' ) ); $form .= ' ' . $this->getPeriodMenu( $period ) . '
'; $form .= ''; $form .= ''; $form .= ''; $form .= ''; $form .= '
' . Xml::radio( 'checktype', 'subuserips', $encuserips, array( 'id' => 'subuserips' ) ); $form .= ' ' . Xml::label( wfMsg( 'checkuser-ips' ), 'subuserips' ) . '' . Xml::radio( 'checktype', 'subipedits', $encipedits, array( 'id' => 'subipedits' ) ); $form .= ' ' . Xml::label( wfMsg( 'checkuser-edits' ), 'subipedits' ) . '' . Xml::radio( 'checktype', 'subipusers', $encipusers, array( 'id' => 'subipusers' ) ); $form .= ' ' . Xml::label( wfMsg( 'checkuser-users' ), 'subipusers' ) . '' . Xml::radio( 'checktype', 'subuseredits', $encuseredits, array( 'id' => 'subuseredits' ) ); $form .= ' ' . Xml::label( wfMsg( 'checkuser-account' ), 'subuseredits' ) . '
' . wfMsgHtml( 'checkuser-reason' ) . '' . Xml::input( 'reason', 46, $reason, array( 'maxlength' => '150', 'id' => 'checkreason' ) ); $form .= '   ' . Xml::submitButton( wfMsg( 'checkuser-check' ), array( 'id' => 'checkusersubmit', 'name' => 'checkusersubmit' ) ) . '
'; # 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( "" ); } /** * Get a selector of time period options * @param int $selected, selected level */ protected function getPeriodMenu( $selected = null ) { $s = ' '; $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 = ''; $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 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( '

' . wfMsgHtml( 'checkuser-log-fail' ) . '

' ); } $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 = '
'; } $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 ' (' . $blocklog . ' - ' . $block->mAddress . ')'; } elseif ( $block->mAuto ) { $blocklog = $this->sk->makeKnownLinkObj( $blocklist, wfMsgHtml( 'checkuser-blocked' ), 'ip=' . urlencode( "#$block->mId" ) ); return ' (' . $blocklog . ')'; } else { $userpage = Title::makeTitle( NS_USER, $ip ); $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml( 'checkuser-blocked' ), 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) ); return ' (' . $blocklog . ')'; } } 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 .= '
    '; 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 .= '
  1. ' . $ip . ''; 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 .= ' [' . $row->count . "]
  2. \n"; ++$counter; } $s .= '
'; $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 = '
'; while ( $row = $ret->fetchObject() ) { if ( $counter >= 5000 ) { // FIXME: addWikiMsg $wgOut->addHTML( wfMsgExt( 'checkuser-limited', array( 'parse' ) ) ); break; } $s .= $this->CUChangesLine( $row, $reason ); ++$counter; } $s .= '
'; $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( '

' . wfMsgHtml( 'checkuser-log-fail' ) . '

' ); } $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

$ip

\n
"; } elseif ( $lastIP != $row->cuc_ip ) { $s .= "
\n

$ip

\n
"; $lastIP = $row->cuc_ip; unset( $this->lastdate ); // start over } $s .= $this->CUChangesLine( $row, $reason ); } $s .= '
'; $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 = '
'; while ( $row = $ret->fetchObject() ) { $s .= $this->CUChangesLine( $row, $reason ); } $s .= '
'; $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( '

' . wfMsgHtml( 'checkuser-log-fail' ) . '

' ); } $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 = '
' . wfMsg( 'checkuser-too-many' ) . '
'; $s .= '
    '; $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 .= '
  1. ' . $ip . ''; 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 .= ' [' . $row->count . "]
  2. \n"; ++$counter; } $s .= '
'; $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 = "
"; $s .= '
\n"; if ( $wgUser->isAllowed( 'block' ) && !$wgUser->isBlocked() ) { $s .= "
\n"; $s .= '' . wfMsgHtml( 'checkuser-massblock' ) . "\n"; $s .= '

' . wfMsgExt( 'checkuser-massblock-text', array( 'parseinline' ) ) . "

\n"; $s .= '' . '' . '' . '' . '' . '' . '' . '' . '
' . Xml::check( 'usetag', false, array( 'id' => 'usetag' ) ) . '' . Xml::label( wfMsgHtml( 'checkuser-blocktag' ), 'usetag' ) . '' . Xml::input( 'tag', 46, $tag, array( 'id' => 'blocktag' ) ) . '
' . Xml::check( 'usettag', false, array( 'id' => 'usettag' ) ) . '' . Xml::label( wfMsgHtml( 'checkuser-blocktag-talk' ), 'usettag' ) . '' . Xml::input( 'talktag', 46, $talkTag, array( 'id' => 'talktag' ) ) . '
'; $s .= '

' . wfMsgHtml( 'checkuser-reason' ) . ' '; $s .= Xml::input( 'blockreason', 46, '', array( 'maxlength' => '150', 'id' => 'blockreason' ) ); $s .= ' ' . Xml::submitButton( wfMsgHtml( 'checkuser-massblock-commit' ), array( 'id' => 'checkuserblocksubmit', 'name' => 'checkuserblock' ) ) . "

\n"; $s .= "
\n"; } $s .= '
'; } $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[] = '(' . $blocklog . ' - ' . $block->mAddress . ')'; // Auto blocked? } elseif ( $block->mAuto ) { $blocklog = $this->sk->makeKnownLinkObj( $blocklist, wfMsgHtml( 'checkuser-blocked' ), 'ip=' . urlencode( "#{$block->mId}" ) ); $flags[] = '(' . $blocklog . ')'; } else { $userpage = $user->getUserPage(); $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml( 'checkuser-blocked' ), 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) ); $flags[] = '(' . $blocklog . ')'; } // IP that is blocked on all wikis? } elseif ( $ip == $user->getName() && $user->isBlockedGlobally( $ip ) ) { $flags[] = '(' . wfMsgHtml( 'checkuser-gblocked' ) . ')'; } elseif ( self::userWasBlocked( $user->getName() ) ) { $userpage = $user->getUserPage(); $blocklog = $this->sk->makeKnownLinkObj( $logs, wfMsgHtml( 'checkuser-wasblocked' ), 'type=block&page=' . urlencode( $userpage->getPrefixedText() ) ); $flags[] = '(' . $blocklog . ')'; } 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

$date

\n\n

$date

\n