. */ /* * This PHP Class provides a hit counter that is able to track unique hits * without recording the visitor's IP address in the database. It does so by * recording the hash of the IP address and page name. * * By hashing the IP address with page name as salt, you prevent yourself from * being able to track a user as they navigate your site. You also prevent * yourself from being able to recover anyone's IP address without brute forcing * through all of the assigned IP address blocks in use by the internet. * * Contact: havoc AT defuse.ca * WWW: https://defuse.ca/ * * USAGE: * In your script, use reqire_once() to import this script, then call the * functions like PHPCount::AddHit(...); See each function for help. * * NOTE: You must set the database credentials in the InitDB method. */ //global $phpcountConfig; //var_dump($phpcountConfig); class PHPCount { /* * Defines how many seconds a hit should be rememberd for. This prevents the * database from perpetually increasing in size. Thirty days (the default) * works well. If someone visits a page and comes back in a month, it will be * counted as another unique hit. */ const HIT_OLD_AFTER_SECONDS = 2592000; // default: 30 days. // Don't count hits from search robots and crawlers. const IGNORE_SEARCH_BOTS = true; // Don't count the hit if the browser sends the DNT: 1 header. const HONOR_DO_NOT_TRACK = false; private static $IP_IGNORE_LIST = array( '127.0.0.1', ); private static $DB = false; private static function InitDB() { global $phpcountConfig; //var_dump($phpcountConfig); if(self::$DB) return; try { // TODO: Set the database login credentials. //echo(""); } catch(Exception $e) { self::$DB = false; echo("Failed to connect to phpcount database (".$e->getMessage().")\n"); // die('Failed to connect to phpcount database ('.$e->getMessage().')'); } } public static function setDBAdapter($db) { self::$DB = $db; return $db; } /* * Adds a hit to a page specified by a unique $pageID string. */ public static function AddHit($pageID) { if(self::IGNORE_SEARCH_BOTS && self::IsSearchBot()) return false; if(in_array($_SERVER['REMOTE_ADDR'], self::$IP_IGNORE_LIST)) return false; if( self::HONOR_DO_NOT_TRACK && isset($_SERVER['HTTP_DNT']) && $_SERVER['HTTP_DNT'] == "1" ) { return false; } self::InitDB(); self::Cleanup(); if(self::UniqueHit($pageID)) { self::CountHit($pageID, true); self::LogHit($pageID); } self::CountHit($pageID, false); self::LogPageHit($pageID); return true; } /* * Returns (int) the amount of hits a page has * $pageID - the page identifier * $unique - true if you want unique hit count */ public static function GetHits($pageID, $unique = false){ global $phpcountConfig; self::InitDB(); try{ $q = self::$DB->prepare( 'SELECT hitcount FROM '.$phpcountConfig["db"]["hitsTable"].' WHERE pageid = :pageid AND isunique = :isunique' ); $q->bindParam(':pageid', $pageID); $q->bindParam(':isunique', $unique); $q->execute(); } catch(Exeption $e){ echo("Failed to getHits from phpcount database (".$e->getMessage().")\n"); return -1; } if(($res = $q->fetch()) !== FALSE) { return (int)$res['hitcount']; } else { //die("Missing hit count from database!"); return 0; } } /* * Returns the total amount of hits to the entire website * When $unique is FALSE, it returns the sum of all non-unique hit counts * for every page. When $unique is TRUE, it returns the sum of all unique * hit counts for every page, so the value that's returned IS NOT the * amount of site-wide unique hits, it is the sum of each page's unique * hit count. */ public static function GetTotalHits($unique = false){ global $phpcountConfig; self::InitDB(); $q = self::$DB->prepare( 'SELECT hitcount FROM '.$phpcountConfig["db"]["hitsTable"].' WHERE isunique = :isunique' ); $q->bindParam(':isunique', $unique); $q->execute(); $rows = $q->fetchAll(); $total = 0; foreach($rows as $row) { $total += (int)$row['hitcount']; } return $total; } /*====================== PRIVATE METHODS =============================*/ private static function IsSearchBot() { // Of course, this is not perfect, but it at least catches the major // search engines that index most often. $keywords = array( 'bot', 'spider', 'spyder', 'crawler', 'walker', 'search', 'yahoo', 'holmes', 'htdig', 'archive', 'tineye', 'yacy', 'yeti', 'Chrome-Lighthouse', 'Barkrowler', ); $agent = strtolower($_SERVER['HTTP_USER_AGENT']); foreach($keywords as $keyword) { if(strpos($agent, $keyword) !== false) return true; } return false; } private static function UniqueHit($pageID){ global $phpcountConfig; $ids_hash = self::IDHash($pageID); $q = self::$DB->prepare( 'SELECT `time` FROM '.$phpcountConfig["db"]["nodupesTable"].' WHERE ids_hash = :ids_hash' ); $q->bindParam(':ids_hash', $ids_hash); $q->execute(); if(($res = $q->fetch()) !== false) { if($res['time'] > time() - self::HIT_OLD_AFTER_SECONDS) return false; else return true; } else { return true; } } private static function LogHit($pageID){ global $phpcountConfig; $ids_hash = self::IDHash($pageID); $q = self::$DB->prepare( 'SELECT `time` FROM '.$phpcountConfig["db"]["nodupesTable"].' WHERE ids_hash = :ids_hash' ); $q->bindParam(':ids_hash', $ids_hash); $q->execute(); $curTime = time(); if(($res = $q->fetch()) !== false) { $s = self::$DB->prepare( 'UPDATE '.$phpcountConfig["db"]["nodupesTable"].' SET `time` = :time WHERE ids_hash = :ids_hash' ); $s->bindParam(':time', $curTime); $s->bindParam(':ids_hash', $ids_hash); $s->execute(); } else { $s = self::$DB->prepare( 'INSERT INTO '.$phpcountConfig["db"]["nodupesTable"].' (ids_hash, `time`) VALUES( :ids_hash, :time )' ); $s->bindParam(':time', $curTime); $s->bindParam(':ids_hash', $ids_hash); $s->execute(); } } private static function LogPageHit($pageId){ global $phpcountConfig; try{ $q = self::$DB->prepare( 'INSERT INTO '.$phpcountConfig["db"]["pagehitsTable"].' (userHash, pageId, userAgentString) VALUES (:idsHash, :pageId, :userAgentString);' ); $q->bindParam(':idsHash', self::userHash()); $q->bindParam(':pageId', $pageId); $q->bindParam(':userAgentString', $_SERVER['HTTP_USER_AGENT']); $q->execute(); } catch(Exeption $e){ echo("Failed to logPageHits to phpcount database (".$e->getMessage().")\n"); return -1; } return 0; } private static function CountHit($pageID, $unique){ global $phpcountConfig; //echo("DEBUG: Counting Hit on ".$pageID."\n"); try{ $q = self::$DB->prepare( "INSERT INTO ".$phpcountConfig["db"]["hitsTable"]." (pageid, isunique, hitcount) VALUES (:pageid, :isunique, 1) " . "ON DUPLICATE KEY UPDATE hitcount = hitcount + 1" ); $q->bindParam(':pageid', $pageID); $unique = $unique ? '1' : '0'; $q->bindParam(':isunique', $unique); $q->execute(); } catch(Exception $e){ die('phpcount failed to CountHit ('.$e->getMessage().')'); } } private static function IDHash($pageID) { $visitorID = $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']; return hash("SHA256", $pageID . $visitorID); } private static function userHash() { $visitorID = $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']; return hash("SHA256", $visitorID); } private static function Cleanup(){ global $phpcountConfig; $last_interval = time() - self::HIT_OLD_AFTER_SECONDS; $q = self::$DB->prepare( 'DELETE FROM '.$phpcountConfig["db"]["nodupesTable"].' WHERE `time` < :time' ); $q->bindParam(':time', $last_interval); $q->execute(); $q = self::$DB->prepare( 'DELETE FROM '.$phpcountConfig["db"]["pageHitsTable"].' WHERE `timeStamp` < :time' ); $q->bindParam(':time', $last_interval); $q->execute(); } }