diff options
author | mattpiwik <matthieu.aubry@gmail.com> | 2007-08-07 20:17:50 +0400 |
---|---|---|
committer | mattpiwik <matthieu.aubry@gmail.com> | 2007-08-07 20:17:50 +0400 |
commit | 9716c35f6d3a68e410a9f05b60ab4b4436aeb706 (patch) | |
tree | 2187d570ae0aec3a5443e1c09715f81c0843e253 | |
parent | d94998d4b6ea972c4dd4f4ecf95972f89662a886 (diff) |
- reorganized LogStats modules into separate files
- edited config.php to be able to change to path (simpletest was annoying and wouldn't want to behave properly with my include_path)
- implemented a simple class for Table partitionning by date (month or day). It generates a table name depeding on a given date. Useful for the archives and maybe later for the logging engine. + wrote tests for this class
git-svn-id: http://dev.piwik.org/svn/trunk@30 59fd770c-687e-43c8-a1e3-f5a4ff64c105
-rw-r--r-- | TODO | 2 | ||||
-rwxr-xr-x | config/config.ini.php | 7 | ||||
-rwxr-xr-x | index.php | 10 | ||||
-rw-r--r-- | misc/generateVisits.php | 15 | ||||
-rwxr-xr-x | modules/Config.php | 9 | ||||
-rwxr-xr-x | modules/Log.php | 3 | ||||
-rw-r--r-- | modules/LogStats.php | 1280 | ||||
-rw-r--r-- | modules/LogStats/Action.php | 198 | ||||
-rw-r--r-- | modules/LogStats/Config.php | 41 | ||||
-rw-r--r-- | modules/LogStats/Cookie.php | 228 | ||||
-rw-r--r-- | modules/LogStats/Db.php | 74 | ||||
-rw-r--r-- | modules/LogStats/Visit.php | 749 | ||||
-rwxr-xr-x | modules/Piwik.php | 29 | ||||
-rw-r--r-- | modules/TablePartitioning.php | 100 | ||||
-rw-r--r-- | piwik.php | 11 | ||||
-rwxr-xr-x | tests/config_test.php | 37 | ||||
-rwxr-xr-x | tests/modules/Database.test.php | 8 | ||||
-rwxr-xr-x | tests/modules/TablePartitioning.test.php | 138 | ||||
-rwxr-xr-x | tests/modules/blank.test.php | 9 |
19 files changed, 1621 insertions, 1327 deletions
@@ -5,7 +5,7 @@ FEATURES - in the piwik.php process, we could do without all the information in the cookie except for the idvisitor we could select the information last_action_time, last_id_action, etc. assuming we have the idvisitor in the cookie this would allow to save the logs later by big bulk - + BUGS ==== - the token md5 generation doesn't check that the md5 generated is unique, diff --git a/config/config.ini.php b/config/config.ini.php index cb2556f9c2..c410579b28 100755 --- a/config/config.ini.php +++ b/config/config.ini.php @@ -83,6 +83,13 @@ logger_query_profile[] = screen ;logger_query_profile[] = database ;logger_query_profile[] = file +[log_tests] +logger_message[] = screen +logger_api_call[] = screen +logger_error[] = screen +logger_exception[] = screen +logger_query_profile[] = screen + [path] log = logs/ @@ -9,8 +9,7 @@ define('PIWIK_INCLUDE_PATH', '.'); set_include_path(PIWIK_INCLUDE_PATH . PATH_SEPARATOR . PIWIK_INCLUDE_PATH . '/libs/' . PATH_SEPARATOR . PIWIK_INCLUDE_PATH . '/core/' - . PATH_SEPARATOR . PIWIK_INCLUDE_PATH . '/modules' - . PATH_SEPARATOR . PIWIK_INCLUDE_PATH . '/core/models' + . PATH_SEPARATOR . PIWIK_INCLUDE_PATH . '/modules/' . PATH_SEPARATOR . get_include_path()); assert_options(ASSERT_ACTIVE, 1); @@ -52,16 +51,19 @@ Zend_Loader::loadClass('Piwik'); //move into a init() method Piwik::createConfigObject(); + +// database object Piwik::createDatabaseObject(); +// Create the log objects +Piwik::createLogObject(); + //TODO move all DB related methods in a DB static class Piwik::createDatabase(); Piwik::createDatabaseObject(); Piwik::dropTables(); Piwik::createTables(); -// Create the log objects -Piwik::createLogObject(); // Create auth object $auth = Zend_Auth::getInstance(); diff --git a/misc/generateVisits.php b/misc/generateVisits.php index 74d491f9c5..39ac9618d7 100644 --- a/misc/generateVisits.php +++ b/misc/generateVisits.php @@ -16,10 +16,17 @@ set_include_path(PIWIK_INCLUDE_PATH require_once "Event/Dispatcher.php"; require_once "Common.php"; -require_once "LogStats.php"; require_once "PluginManager.php"; require_once "LogStats/Plugins.php"; +require_once "LogStats.php"; +require_once "LogStats/Plugins.php"; +require_once "LogStats/Config.php"; +require_once "LogStats/Action.php"; +require_once "LogStats/Cookie.php"; +require_once "LogStats/Db.php"; +require_once "LogStats/Visit.php"; + $GLOBALS['DEBUGPIWIK'] = false; @@ -326,13 +333,13 @@ class Piwik_LogStats_Generator private function saveVisit() { $this->setFakeRequest(); - $process = new Piwik_LogStats_Generator_Controller; + $process = new Piwik_LogStats_Generator_Main; $process->main('Piwik_LogStats_Generator_Visit'); } } -class Piwik_LogStats_Generator_Controller extends Piwik_LogStats_Controller +class Piwik_LogStats_Generator_Main extends Piwik_LogStats { protected function sendHeader($header) { @@ -376,7 +383,7 @@ $generator = new Piwik_LogStats_Generator; $generator->init(); $t = new Piwik_Timer; -$nbActionsTotal = $generator->generate(1000,5); +$nbActionsTotal = $generator->generate(10000,5); echo "<br>Request per sec: ". round($nbActionsTotal / $t->getTime(),0); echo "<br>".$t; diff --git a/modules/Config.php b/modules/Config.php index 541f6ac178..d39b688280 100755 --- a/modules/Config.php +++ b/modules/Config.php @@ -1,10 +1,12 @@ <?php class Piwik_Config extends Zend_Config_Ini { - function __construct() + function __construct($pathIniFile = null) { - $pathIniFile = PIWIK_INCLUDE_PATH . '/config/config.ini.php'; - + if(is_null($pathIniFile)) + { + $pathIniFile = PIWIK_INCLUDE_PATH . '/config/config.ini.php'; + } parent::__construct($pathIniFile, null, true); Zend_Registry::set('config', $this); @@ -15,6 +17,7 @@ class Piwik_Config extends Zend_Config_Ini public function setTestEnvironment() { $this->database = $this->database_tests; + $this->log = $this->log_tests; $this->setPrefixTables(); } diff --git a/modules/Log.php b/modules/Log.php index cb732bd94f..5dac91bb1b 100755 --- a/modules/Log.php +++ b/modules/Log.php @@ -1,6 +1,5 @@ <?php Zend_Loader::loadClass('Zend_Log'); -Zend_Loader::loadClass('Zend_Log'); Zend_Loader::loadClass('Zend_Log_Formatter_Interface'); Zend_Loader::loadClass('Zend_Log_Writer_Stream'); Zend_Loader::loadClass('Zend_Log_Writer_Db'); @@ -22,7 +21,6 @@ class Piwik_Log extends Zend_Log { parent::__construct(); - Piwik::mkdir(Zend_Registry::get('config')->path->log); $this->logToFileFilename = Zend_Registry::get('config')->path->log . $logToFileFilename; $this->fileFormatter = $fileFormatter; @@ -39,6 +37,7 @@ class Piwik_Log extends Zend_Log function addWriteToFile() { $writerFile = new Zend_Log_Writer_Stream($this->logToFileFilename); + Piwik::mkdir(Zend_Registry::get('config')->path->log); $writerFile->setFormatter( $this->fileFormatter ); $this->addWriter($writerFile); } diff --git a/modules/LogStats.php b/modules/LogStats.php index 711e780375..6d7827fb0b 100644 --- a/modules/LogStats.php +++ b/modules/LogStats.php @@ -1,116 +1,4 @@ <?php - -/** - * Simple database PDO wrapper - * - */ -class Piwik_LogStats_Db -{ - private $connection; - private $username; - private $password; - - public function __construct( $host, $username, $password, $dbname) - { - $this->dsn = "mysql:dbname=$dbname;host=$host"; - $this->username = $username; - $this->password = $password; - } - - public function connect() - { - try { - $pdoConnect = new PDO($this->dsn, $this->username, $this->password); - $pdoConnect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $this->connection = $pdoConnect; - } catch (PDOException $e) { - throw new Exception("Error connecting database: ".$e->getMessage()); - } - } - - public function prefixTable( $suffix ) - { - $prefix = Piwik_LogStats_Config::getInstance()->database['tables_prefix']; - - return $prefix . $suffix; - } - - public function fetchAll( $query, $parameters ) - { - try { - $sth = $this->query( $query, $parameters ); - return $sth->fetchAll(PDO::FETCH_ASSOC); - } catch (PDOException $e) { - throw new Exception("Error query: ".$e->getMessage()); - } - } - - public function fetch( $query, $parameters ) - { - try { - $sth = $this->query( $query, $parameters ); - return $sth->fetch(PDO::FETCH_ASSOC); - } catch (PDOException $e) { - throw new Exception("Error query: ".$e->getMessage()); - } - } - - public function query($query, $parameters = array()) - { - try { - $sth = $this->connection->prepare($query); - $sth->execute( $parameters ); - return $sth; - } catch (PDOException $e) { - throw new Exception("Error query: ".$e->getMessage()); - } - } - - public function lastInsertId() - { - return $this->connection->lastInsertId(); - } -} - -/** - * Simple class to access the configuration file - */ -class Piwik_LogStats_Config -{ - static private $instance = null; - - static public function getInstance() - { - if (self::$instance == null) - { - $c = __CLASS__; - self::$instance = new $c(); - } - return self::$instance; - } - - public $config = array(); - - private function __construct() - { - $pathIniFile = PIWIK_INCLUDE_PATH . '/config/config.ini.php'; - $this->config = parse_ini_file($pathIniFile, true); - } - - public function __get( $name ) - { - if(isset($this->config[$name])) - { - return $this->config[$name]; - } - else - { - throw new Exception("The config element $name is not available in the configuration (check the configuration file)."); - } - } -} - - /** * To maximise the performance of the logging module, we use different techniques. * @@ -147,1174 +35,8 @@ class Piwik_LogStats_Config * - use_cookie ; defines if we try to get/set a cookie to help recognize a unique visitor */ -/** - * Simple class to handle the cookies. - * Its features are: - * - * - read a cookie values - * - edit an existing cookie and save it - * - create a new cookie, set values, expiration date, etc. and save it - * - * The cookie content is saved in an optimized way. - */ -class Piwik_LogStats_Cookie -{ - /** - * The name of the cookie - */ - protected $name = null; - - /** - * The expire time for the cookie (expressed in UNIX Timestamp) - */ - protected $expire = null; - - /** - * The content of the cookie - */ - protected $value = array(); - - const VALUE_SEPARATOR = ':'; - - public function __construct( $cookieName, $expire = null) - { - $this->name = $cookieName; - - if(is_null($expire) - || !is_numeric($expire) - || $expire <= 0) - { - $this->expire = $this->getDefaultExpire(); - } - - if($this->isCookieFound()) - { - $this->loadContentFromCookie(); - } - } - - public function isCookieFound() - { - return isset($_COOKIE[$this->name]); - } - - protected function getDefaultExpire() - { - return time() + 86400*365*10; - } - - /** - * taken from http://usphp.com/manual/en/function.setcookie.php - * fix expires bug for IE users (should i say expected to fix the bug in 2.3 b2) - * TODO setCookie: use the other parameters of the function - */ - protected function setCookie($Name, $Value, $Expires, $Path = '', $Domain = '', $Secure = false, $HTTPOnly = false) - { - if (!empty($Domain)) - { - // Fix the domain to accept domains with and without 'www.'. - if (strtolower(substr($Domain, 0, 4)) == 'www.') $Domain = substr($Domain, 4); - - $Domain = '.' . $Domain; - - // Remove port information. - $Port = strpos($Domain, ':'); - if ($Port !== false) $Domain = substr($Domain, 0, $Port); - } - - $header = 'Set-Cookie: ' . rawurlencode($Name) . '=' . rawurlencode($Value) - . (empty($Expires) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', $Expires) . ' GMT') - . (empty($Path) ? '' : '; path=' . $Path) - . (empty($Domain) ? '' : '; domain=' . $Domain) - . (!$Secure ? '' : '; secure') - . (!$HTTPOnly ? '' : '; HttpOnly'); - - header($header, false); - } - - protected function setP3PHeader() - { - header("P3P: CP='OTI DSP COR NID STP UNI OTPa OUR'"); - } - - public function deleteCookie() - { - $this->setP3PHeader(); - setcookie($this->name, false, time() - 86400); - } - - public function save() - { - $this->setP3PHeader(); - $this->setCookie( $this->name, $this->generateContentString(), $this->expire); - } - - /** - * Load the cookie content into a php array - */ - protected function loadContentFromCookie() - { - $cookieStr = $_COOKIE[$this->name]; - - $values = explode( self::VALUE_SEPARATOR, $cookieStr); - foreach($values as $nameValue) - { - $equalPos = strpos($nameValue, '='); - $varName = substr($nameValue,0,$equalPos); - $varValue = substr($nameValue,$equalPos+1); - - // no numeric value are base64 encoded so we need to decode them - if(!is_numeric($varValue)) - { - $varValue = base64_decode($varValue); - - // some of the values may be serialized array so we try to unserialize it - if( ($arrayValue = @unserialize($varValue)) !== false - // we set the unserialized version only for arrays as you can have set a serialized string on purpose - && is_array($arrayValue) - ) - { - $varValue = $arrayValue; - } - } - - $this->set($varName, $varValue); - } - } - - /** - * Returns the string to save in the cookie frpm the $this->value array of values - * - */ - public function generateContentString() - { - $cookieStr = ''; - foreach($this->value as $name=>$value) - { - if(is_array($value)) - { - $value = base64_encode(serialize($value)); - } - elseif(is_string($value)) - { - $value = base64_encode($value); - } - - $cookieStr .= "$name=$value" . self::VALUE_SEPARATOR; - } - $cookieStr = substr($cookieStr, 0, strlen($cookieStr)-1); - return $cookieStr; - } - - /** - * Registers a new name => value association in the cookie. - * - * Registering new values is optimal if the value is a numeric value. - * If the value is a string, it will be saved as a base64 encoded string. - * If the value is an array, it will be saved as a serialized and base64 encoded - * string which is not very good in terms of bytes usage. - * You should save arrays only when you are sure about their maximum data size. - * - * @param string Name of the value to save; the name will be used to retrieve this value - * @param string|array|numeric Value to save - * - */ - public function set( $name, $value ) - { - $name = self::escapeValue($name); - $this->value[$name] = $value; - } - - /** - * Returns the value defined by $name from the cookie. - * - * @param string|integer Index name of the value to return - * @return mixed The value if found, false if the value is not found - */ - public function get( $name ) - { - $name = self::escapeValue($name); - return isset($this->value[$name]) ? self::escapeValue($this->value[$name]) : false; - } - - public function __toString() - { - $str = "<-- Content of the cookie '{$this->name}' <br>\n"; - foreach($this->value as $name => $value ) - { - $str .= $name . " = " . var_export($this->get($name), true) . "<br>\n"; - } - $str .= "--> <br>\n"; - return $str; - } - - static protected function escapeValue( $value ) - { - return Piwik_Common::sanitizeInputValues($value); - } -} - -// -//$c = new Piwik_LogStats_Cookie( 'piwik_logstats', 86400); -//echo $c; -//$c->set(1,1); -//$c->set('test',1); -//$c->set('test2','test=432:gea785'); -//$c->set('test3',array('test=432:gea785')); -//$c->set('test4',array(array(0=>1),1=>'test')); -//echo $c; -//echo "<br>"; -//echo $c->generateContentString(); -//echo "<br>"; -//$v=$c->get('more!'); -//if(empty($v)) $c->set('more!',1); -//$c->set('more!', array($c->get('more!'))); -//$c->save(); -//$c->deleteCookie(); - -class Piwik_LogStats_Action -{ - - /* - * Specifications - * - * - External file tracking - * - * * MANUAL Download tracking - * download = http://piwik.org/hellokity.zip - * (name = dir1/file alias name) - * - * * AUTOMATIC Download tracking for a known list of file extensions. - * Make a hit to the piwik.php with the parameter: - * download = http://piwik.org/hellokity.zip - * - * When 'name' is not specified, - * if AUTOMATIC and if anchor not empty => name = link title anchor - * else name = path+query of the URL - * Ex: myfiles/beta.zip - * - * - External link tracking - * - * * MANUAL External link tracking - * outlink = http://amazon.org/test - * (name = the big partners / amazon) - * - * * AUTOMATIC External link tracking - * When a link is not detected as being part of the same website - * AND when the url extension is not detected as being a file download - * outlink = http://amazon.org/test - * - * When 'name' is not specified, - * if AUTOMATIC and if anchor not empty => name = link title anchor - * else name = URL - * Ex: http://amazon.org/test - */ - private $actionName; - private $url; - private $defaultActionName; - private $nameDownloadOutlink; - - const TYPE_ACTION = 1; - const TYPE_DOWNLOAD = 3; - const TYPE_OUTLINK = 2; - - function __construct( $db ) - { - $this->actionName = Piwik_Common::getRequestVar( 'action_name', '', 'string'); - - $downloadVariableName = Piwik_LogStats_Config::getInstance()->LogStats['download_url_var_name']; - $this->downloadUrl = Piwik_Common::getRequestVar( $downloadVariableName, '', 'string'); - - $outlinkVariableName = Piwik_LogStats_Config::getInstance()->LogStats['outlink_url_var_name']; - $this->outlinkUrl = Piwik_Common::getRequestVar( $outlinkVariableName, '', 'string'); - - $nameVariableName = Piwik_LogStats_Config::getInstance()->LogStats['download_outlink_name_var']; - $this->nameDownloadOutlink = Piwik_Common::getRequestVar( $nameVariableName, '', 'string'); - - $this->url = Piwik_Common::getRequestVar( 'url', '', 'string'); - $this->db = $db; - $this->defaultActionName = Piwik_LogStats_Config::getInstance()->LogStats['default_action_name']; - } - - /** - * About the Action concept: - * - * - An action is defined by a name. - * - The name can be specified in the JS Code in the variable 'action_name' - * - Handling UTF8 in the action name - * PLUGIN_IDEA - An action is associated to URLs and link to the URL from the interface - * PLUGIN_IDEA - An action hit by a visitor is associated to the HTML title of the page that triggered the action - * - * + If the name is not specified, we use the URL(path+query) to build a default name. - * For example for "http://piwik.org/test/my_page/test.html" - * the name would be "test/my_page/test.html" - * - * We make sure it is clean and displayable. - * If the name is empty we set it to a default name. - * - * TODO UTF8 handling to test - * - */ - private function generateInfo() - { - if(!empty($this->downloadUrl)) - { - $this->actionType = self::TYPE_DOWNLOAD; - $url = $this->downloadUrl; - $actionName = $this->nameDownloadOutlink; - } - elseif(!empty($this->outlinkUrl)) - { - $this->actionType = self::TYPE_OUTLINK; - $url = $this->outlinkUrl; - $actionName = $this->nameDownloadOutlink; - if( empty($actionName) ) - { - $actionName = $url; - } - } - else - { - $this->actionType = self::TYPE_ACTION; - $url = $this->url; - $actionName = $this->actionName; - } - - // the ActionName wasn't specified - if( empty($actionName) ) - { - $parsedUrl = parse_url( $url ); - - $actionName = ''; - - if(isset($parsedUrl['path'])) - { - $actionName .= substr($parsedUrl['path'], 1); - } - - if(isset($parsedUrl['query'])) - { - $actionName .= '?'.$parsedUrl['query']; - } - } - - // clean the name - $actionName = str_replace(array("\n", "\r"), '', $actionName); - - if(empty($actionName)) - { - $actionName = $this->defaultActionName; - } - - $this->finalActionName = $actionName; - } - - /** - * Returns the idaction of the current action name. - * This idaction is used in the visitor logging table to link the visit information - * (entry action, exit action) to the actions. - * This idaction is also used in the table that links the visits and their actions. - * - * The methods takes care of creating a new record in the action table if the existing - * action name doesn't exist yet. - * - * @return int Id action - */ - function getActionId() - { - $this->loadActionId(); - return $this->idAction; - } - - /** - * @see getActionId() - */ - private function loadActionId() - { - $this->generateInfo(); - - $name = $this->finalActionName; - $type = $this->actionType; - - $idAction = $this->db->fetch(" SELECT idaction - FROM ".$this->db->prefixTable('log_action') - ." WHERE name = ? AND type = ?", array($name, $type) ); - - // the action name has not been found, create it - if($idAction === false) - { - $this->db->query("INSERT INTO ". $this->db->prefixTable('log_action'). "( name, type ) - VALUES (?,?)",array($name,$type) ); - $idAction = $this->db->lastInsertId(); - } - else - { - $idAction = $idAction['idaction']; - } - - $this->idAction = $idAction; - } - - /** - * Records in the DB the association between the visit and this action. - */ - public function record( $idVisit, $idRefererAction, $timeSpentRefererAction) - { - $this->db->query("INSERT INTO ".$this->db->prefixTable('log_link_visit_action') - ." (idvisit, idaction, idaction_ref, time_spent_ref_action) VALUES (?,?,?,?)", - array($idVisit, $this->idAction, $idRefererAction, $timeSpentRefererAction) - ); - } -} - -class Piwik_LogStats_Visit -{ - protected $cookieLog = null; - protected $visitorInfo = array(); - protected $userSettingsInformation = null; - - function __construct( $db ) - { - $this->db = $db; - - $idsite = Piwik_Common::getRequestVar('idsite', 0, 'int'); - if($idsite <= 0) - { - throw new Exception("The 'idsite' in the request is invalide."); - } - - $this->idsite = $idsite; - } - - protected function getCurrentDate( $format = "Y-m-d") - { - return date($format, $this->getCurrentTimestamp() ); - } - - protected function getCurrentTimestamp() - { - return time(); - } - - protected function getDatetimeFromTimestamp($timestamp) - { - return date("Y-m-d H:i:s",$timestamp); - } - - - - // test if the visitor is excluded because of - // - IP - // - cookie - // - configuration option? - private function isExcluded() - { - $excluded = 0; - - if($excluded) - { - printDebug("Visitor excluded."); - return true; - } - - return false; - } - - private function getCookieName() - { - return Piwik_LogStats_Config::getInstance()->LogStats['cookie_name'] . $this->idsite; - } - - - /** - * This methods tries to see if the visitor has visited the website before. - * - * We have to split the visitor into one of the category - * - Known visitor - * - New visitor - * - * A known visitor is a visitor that has already visited the website in the current month. - * We define a known visitor using the algorithm: - * - * 1) Checking if a cookie contains - * // a unique id for the visitor - * - id_visitor - * - * // the timestamp of the last action in the most recent visit - * - timestamp_last_action - * - * // the timestamp of the first action in the most recent visit - * - timestamp_first_action - * - * // the ID of the most recent visit (which could be in the past or the current visit) - * - id_visit - * - * // the ID of the most recent action - * - id_last_action - * - * 2) If the visitor doesn't have a cookie, we try to look for a similar visitor configuration. - * We search for a visitor with the same plugins/OS/Browser/Resolution for today for this website. - */ - private function recognizeTheVisitor() - { - $this->visitorKnown = false; - - $this->cookieLog = new Piwik_LogStats_Cookie( $this->getCookieName() ); - /* - * Case the visitor has the piwik cookie. - * We make sure all the data that should saved in the cookie is available. - */ - - if( false !== ($idVisitor = $this->cookieLog->get( Piwik_LogStats_Controller::COOKIE_INDEX_IDVISITOR )) ) - { - $timestampLastAction = $this->cookieLog->get( Piwik_LogStats_Controller::COOKIE_INDEX_TIMESTAMP_LAST_ACTION ); - $timestampFirstAction = $this->cookieLog->get( Piwik_LogStats_Controller::COOKIE_INDEX_TIMESTAMP_FIRST_ACTION ); - $idVisit = $this->cookieLog->get( Piwik_LogStats_Controller::COOKIE_INDEX_ID_VISIT ); - $idLastAction = $this->cookieLog->get( Piwik_LogStats_Controller::COOKIE_INDEX_ID_LAST_ACTION ); - - if( $timestampLastAction !== false && is_numeric($timestampLastAction) - && $timestampFirstAction !== false && is_numeric($timestampFirstAction) - && $idVisit !== false && is_numeric($idVisit) - && $idLastAction !== false && is_numeric($idLastAction) - ) - { - $this->visitorInfo['visitor_idcookie'] = $idVisitor; - $this->visitorInfo['visit_last_action_time'] = $timestampLastAction; - $this->visitorInfo['visit_first_action_time'] = $timestampFirstAction; - $this->visitorInfo['idvisit'] = $idVisit; - $this->visitorInfo['visit_exit_idaction'] = $idLastAction; - - $this->visitorKnown = true; - - printDebug("The visitor is known because he has the piwik cookie (idcookie = {$this->visitorInfo['visitor_idcookie']}, idvisit = {$this->visitorInfo['idvisit']}, last action = ".date("r", $this->visitorInfo['visit_last_action_time']).") "); - } - } - - /* - * If the visitor doesn't have the piwik cookie, we look for a visitor that has exactly the same configuration - * and that visited the website today. - */ - if( !$this->visitorKnown ) - { - $userInfo = $this->getUserSettingsInformation(); - $md5Config = $userInfo['config_md5config']; - - $visitRow = $this->db->fetch( - " SELECT visitor_idcookie, - UNIX_TIMESTAMP(visit_last_action_time) as visit_last_action_time, - UNIX_TIMESTAMP(visit_first_action_time) as visit_first_action_time, - idvisit, - visit_exit_idaction - FROM ".$this->db->prefixTable('log_visit'). - " WHERE visit_server_date = ? - AND idsite = ? - AND config_md5config = ? - ORDER BY visit_last_action_time DESC - LIMIT 1", - array( $this->getCurrentDate(), $this->idsite, $md5Config)); - if($visitRow - && count($visitRow) > 0) - { - $this->visitorInfo['visitor_idcookie'] = $visitRow['visitor_idcookie']; - $this->visitorInfo['visit_last_action_time'] = $visitRow['visit_last_action_time']; - $this->visitorInfo['visit_first_action_time'] = $visitRow['visit_first_action_time']; - $this->visitorInfo['idvisit'] = $visitRow['idvisit']; - $this->visitorInfo['visit_exit_idaction'] = $visitRow['visit_exit_idaction']; - - $this->visitorKnown = true; - - printDebug("The visitor is known because of his userSettings+IP (idcookie = {$visitRow['visitor_idcookie']}, idvisit = {$this->visitorInfo['idvisit']}, last action = ".date("r", $this->visitorInfo['visit_last_action_time']).") "); - } - } - } - - private function getUserSettingsInformation() - { - // we already called this method before, simply returns the result - if(is_array($this->userSettingsInformation)) - { - return $this->userSettingsInformation; - } - - - $plugin_Flash = Piwik_Common::getRequestVar( 'fla', 0, 'int'); - $plugin_Director = Piwik_Common::getRequestVar( 'dir', 0, 'int'); - $plugin_Quicktime = Piwik_Common::getRequestVar( 'qt', 0, 'int'); - $plugin_RealPlayer = Piwik_Common::getRequestVar( 'realp', 0, 'int'); - $plugin_Pdf = Piwik_Common::getRequestVar( 'pdf', 0, 'int'); - $plugin_WindowsMedia = Piwik_Common::getRequestVar( 'wma', 0, 'int'); - $plugin_Java = Piwik_Common::getRequestVar( 'java', 0, 'int'); - $plugin_Cookie = Piwik_Common::getRequestVar( 'cookie', 0, 'int'); - - $userAgent = Piwik_Common::sanitizeInputValues(@$_SERVER['HTTP_USER_AGENT']); - $aBrowserInfo = Piwik_Common::getBrowserInfo($userAgent); - $browserName = $aBrowserInfo['name']; - $browserVersion = $aBrowserInfo['version']; - - $os = Piwik_Common::getOs($userAgent); - - $resolution = Piwik_Common::getRequestVar('res', 'unknown', 'string'); - $colorDepth = Piwik_Common::getRequestVar('col', 32, 'numeric'); - - - $ip = Piwik_Common::getIp(); - $ip = ip2long($ip); - - $browserLang = Piwik_Common::sanitizeInputValues(@$_SERVER['HTTP_ACCEPT_LANGUAGE']); - if(is_null($browserLang)) - { - $browserLang = ''; - } - - - $configurationHash = $this->getConfigHash( - $os, - $browserName, - $browserVersion, - $resolution, - $colorDepth, - $plugin_Flash, - $plugin_Director, - $plugin_RealPlayer, - $plugin_Pdf, - $plugin_WindowsMedia, - $plugin_Java, - $plugin_Cookie, - $ip, - $browserLang); - - $this->userSettingsInformation = array( - 'config_md5config' => $configurationHash, - 'config_os' => $os, - 'config_browser_name' => $browserName, - 'config_browser_version' => $browserVersion, - 'config_resolution' => $resolution, - 'config_color_depth' => $colorDepth, - 'config_pdf' => $plugin_Pdf, - 'config_flash' => $plugin_Flash, - 'config_java' => $plugin_Java, - 'config_director' => $plugin_Director, - 'config_quicktime' => $plugin_Quicktime, - 'config_realplayer' => $plugin_RealPlayer, - 'config_windowsmedia' => $plugin_WindowsMedia, - 'config_cookie' => $plugin_RealPlayer, - 'location_ip' => $ip, - 'location_browser_lang' => $browserLang, - ); - - return $this->userSettingsInformation; - } - - /** - * Returns true if the last action was done during the last 30 minutes - */ - private function isLastActionInTheSameVisit() - { - return $this->visitorInfo['visit_last_action_time'] - >= ($this->getCurrentTimestamp() - Piwik_LogStats_Controller::VISIT_STANDARD_LENGTH); - } - - private function isVisitorKnown() - { - return $this->visitorKnown === true; - } - - /** - * Once we have the visitor information, we have to define if the visit is a new or a known visit. - * - * 1) When the last action was done more than 30min ago, - * or if the visitor is new, then this is a new visit. - * - * 2) If the last action is less than 30min ago, then the same visit is going on. - * Because the visit goes on, we can get the time spent during the last action. - * - * NB: - * - In the case of a new visit, then the time spent - * during the last action of the previous visit is unknown. - * - * - In the case of a new visit but with a known visitor, - * we can set the 'returning visitor' flag. - * - */ - - /** - * In all the cases we set a cookie to the visitor with the new information. - */ - public function handle() - { - if(!$this->isExcluded()) - { - $this->recognizeTheVisitor(); - - // known visitor - if($this->isVisitorKnown()) - { - // the same visit is going on - if($this->isLastActionInTheSameVisit()) - { - $this->handleKnownVisit(); - } - // new visit - else - { - $this->handleNewVisit(); - } - } - // new visitor => new visit - else - { - $this->handleNewVisit(); - } - - // we update the cookie with the new visit information - $this->updateCookie(); - - } - } - - private function updateCookie() - { - printDebug("We manage the cookie..."); - - // idcookie has been generated in handleNewVisit or we simply propagate the old value - $this->cookieLog->set( Piwik_LogStats_Controller::COOKIE_INDEX_IDVISITOR, - $this->visitorInfo['visitor_idcookie'] ); - - // the last action timestamp is the current timestamp - $this->cookieLog->set( Piwik_LogStats_Controller::COOKIE_INDEX_TIMESTAMP_LAST_ACTION, - $this->visitorInfo['visit_last_action_time'] ); - - // the first action timestamp is the timestamp of the first action of the current visit - $this->cookieLog->set( Piwik_LogStats_Controller::COOKIE_INDEX_TIMESTAMP_FIRST_ACTION, - $this->visitorInfo['visit_first_action_time'] ); - - // the idvisit has been generated by mysql in handleNewVisit or simply propagated here - $this->cookieLog->set( Piwik_LogStats_Controller::COOKIE_INDEX_ID_VISIT, - $this->visitorInfo['idvisit'] ); - - // the last action ID is the current exit idaction - $this->cookieLog->set( Piwik_LogStats_Controller::COOKIE_INDEX_ID_LAST_ACTION, - $this->visitorInfo['visit_exit_idaction'] ); - - $this->cookieLog->save(); - } - - - /** - * In the case of a known visit, we have to do the following actions: - * - * 1) Insert the new action - * - * 2) Update the visit information - */ - private function handleKnownVisit() - { - printDebug("Visit known."); - - /** - * Init the action - */ - $action = new Piwik_LogStats_Action( $this->db ); - - $actionId = $action->getActionId(); - - printDebug("idAction = $actionId"); - - $serverTime = $this->getCurrentTimestamp(); - $datetimeServer = $this->getDatetimeFromTimestamp($serverTime); - - $this->db->query("UPDATE ". $this->db->prefixTable('log_visit')." - SET visit_last_action_time = ?, - visit_exit_idaction = ?, - visit_total_actions = visit_total_actions + 1, - visit_total_time = UNIX_TIMESTAMP(visit_last_action_time) - UNIX_TIMESTAMP(visit_first_action_time) - WHERE idvisit = ? - LIMIT 1", - array( $datetimeServer, - $actionId, - $this->visitorInfo['idvisit'] ) - ); - /** - * Save the action - */ - $timespentLastAction = $serverTime - $this->visitorInfo['visit_last_action_time']; - - $action->record( $this->visitorInfo['idvisit'], - $this->visitorInfo['visit_exit_idaction'], - $timespentLastAction - ); - - - /** - * Cookie fields to be updated - */ - $this->visitorInfo['visit_last_action_time'] = $serverTime; - $this->visitorInfo['visit_exit_idaction'] = $actionId; - - - } - - /** - * In the case of a new visit, we have to do the following actions: - * - * 1) Insert the new action - * - * 2) Insert the visit information - */ - private function handleNewVisit() - { - printDebug("New Visit."); - - /** - * Get the variables from the REQUEST - */ - - // Configuration settings - $userInfo = $this->getUserSettingsInformation(); - - // General information - $localTime = Piwik_Common::getRequestVar( 'h', $this->getCurrentDate("H"), 'numeric') - .':'. Piwik_Common::getRequestVar( 'm', $this->getCurrentDate("i"), 'numeric') - .':'. Piwik_Common::getRequestVar( 's', $this->getCurrentDate("s"), 'numeric'); - $serverDate = $this->getCurrentDate(); - $serverTime = $this->getCurrentTimestamp(); - - if($this->isVisitorKnown()) - { - $idcookie = $this->visitorInfo['visitor_idcookie']; - $returningVisitor = 1; - } - else - { - $idcookie = $this->getVisitorUniqueId(); - $returningVisitor = 0; - } - - $defaultTimeOnePageVisit = Piwik_LogStats_Config::getInstance()->LogStats['default_time_one_page_visit']; - - // Location information - $country = Piwik_Common::getCountry($userInfo['location_browser_lang']); - $continent = Piwik_Common::getContinent( $country ); - - //Referer information - $refererInfo = $this->getRefererInformation(); - - /** - * Init the action - */ - $action = new Piwik_LogStats_Action( $this->db ); - - $actionId = $action->getActionId(); - - printDebug("idAction = $actionId"); - - - /** - * Save the visitor - */ - $informationToSave = array( - //'idvisit' => , - 'idsite' => $this->idsite, - 'visitor_localtime' => $localTime, - 'visitor_idcookie' => $idcookie, - 'visitor_returning' => $returningVisitor, - 'visit_first_action_time' => $this->getDatetimeFromTimestamp($serverTime), - 'visit_last_action_time' => $this->getDatetimeFromTimestamp($serverTime), - 'visit_server_date' => $serverDate, - 'visit_entry_idaction' => $actionId, - 'visit_exit_idaction' => $actionId, - 'visit_total_actions' => 1, - 'visit_total_time' => $defaultTimeOnePageVisit, - 'referer_type' => $refererInfo['referer_type'], - 'referer_name' => $refererInfo['referer_name'], - 'referer_url' => $refererInfo['referer_url'], - 'referer_keyword' => $refererInfo['referer_keyword'], - 'config_md5config' => $userInfo['config_md5config'], - 'config_os' => $userInfo['config_os'], - 'config_browser_name' => $userInfo['config_browser_name'], - 'config_browser_version' => $userInfo['config_browser_version'], - 'config_resolution' => $userInfo['config_resolution'], - 'config_color_depth' => $userInfo['config_color_depth'], - 'config_pdf' => $userInfo['config_pdf'], - 'config_flash' => $userInfo['config_flash'], - 'config_java' => $userInfo['config_java'], - 'config_director' => $userInfo['config_director'], - 'config_quicktime' => $userInfo['config_quicktime'], - 'config_realplayer' => $userInfo['config_realplayer'], - 'config_windowsmedia' => $userInfo['config_windowsmedia'], - 'config_cookie' => $userInfo['config_cookie'], - 'location_ip' => $userInfo['location_ip'], - 'location_browser_lang' => $userInfo['location_browser_lang'], - 'location_country' => $country, - 'location_continent' => $continent, - ); - - - $fields = implode(", ", array_keys($informationToSave)); - $values = substr(str_repeat( "?,",count($informationToSave)),0,-1); - - $this->db->query( "INSERT INTO ".$this->db->prefixTable('log_visit'). - " ($fields) VALUES ($values)", array_values($informationToSave)); - - $idVisit = $this->db->lastInsertId(); - - // Update the visitor information attribute with this information array - $this->visitorInfo = $informationToSave; - $this->visitorInfo['idvisit'] = $idVisit; - - // we have to save timestamp in the object properties, whereas mysql eats some other datetime format - $this->visitorInfo['visit_first_action_time'] = $serverTime; - $this->visitorInfo['visit_last_action_time'] = $serverTime; - - /** - * Save the action - */ - $action->record( $idVisit, 0, 0 ); - - } - - /** - * Returns an array containing the following information: - * - referer_type - * - direct -- absence of referer URL OR referer URL has the same host - * - site -- based on the referer URL - * - search_engine -- based on the referer URL - * - campaign -- based on campaign URL parameter - * - newsletter -- based on newsletter URL parameter - * - partner -- based on partner URL parameter - * - * - referer_name - * - () - * - piwik.net -- site host name - * - google.fr -- search engine host name - * - adwords-search -- campaign name - * - beta-release -- newsletter name - * - my-nice-partner -- partner name - * - * - referer_keyword - * - () - * - () - * - my keyword - * - my paid keyword - * - () - * - () - * - * - referer_url : the same for all the referer types - * - */ - private function getRefererInformation() - { - // bool that says if the referer detection is done - $refererAnalyzed = false; - $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; - $nameRefererAnalyzed = ''; - $keywordRefererAnalyzed = ''; - - $refererUrl = Piwik_Common::getRequestVar( 'urlref', '', 'string'); - $currentUrl = Piwik_Common::getRequestVar( 'url', '', 'string'); - - $refererUrlParse = @parse_url($refererUrl); - $currentUrlParse = @parse_url($currentUrl); - - if(isset($refererUrlParse['host']) - && !empty($refererUrlParse['host'])) - { - - $refererHost = $refererUrlParse['host']; - $refererSH = $refererUrlParse['scheme'].'://'.$refererUrlParse['host']; - - /* - * Search engine detection - */ - if( !$refererAnalyzed ) - { - /* - * A referer is a search engine if the URL's host is in the SearchEngines array - * and if we found the keyword in the URL. - * - * For example if someone comes from http://www.google.com/partners.html this will not - * be counted as a search engines, but as a website referer from google.com (because the - * keyword couldn't be found in the URL) - */ - require_once PIWIK_DATAFILES_INCLUDE_PATH . "/SearchEngines.php"; - - if(array_key_exists($refererHost, $GLOBALS['Piwik_SearchEngines'])) - { - // which search engine ? - $searchEngineName = $GLOBALS['Piwik_SearchEngines'][$refererHost][0]; - $variableName = $GLOBALS['Piwik_SearchEngines'][$refererHost][1]; - - // if there is a query, there may be a keyword... - if(isset($refererUrlParse['query'])) - { - $query = $refererUrlParse['query']; - - //TODO: change the search engine file and use REGEXP; performance downside? - //TODO: port the phpmyvisites google-images hack here - - // search for keywords now &vname=keyword - $key = strtolower(Piwik_Common::getParameterFromQueryString($query, $variableName)); - - //TODO test the search engine non-utf8 support - // for search engines that don't use utf-8 - if((function_exists('iconv')) - && (isset($GLOBALS['Piwik_SearchEngines'][$refererHost][2]))) - { - $charset = trim($GLOBALS['searchEngines'][$refererHost][2]); - - if(!empty($charset)) - { - $key = htmlspecialchars( - @iconv( $charset, - 'utf-8//TRANSLIT', - htmlspecialchars_decode($key, Piwik_Common::HTML_ENCODING_QUOTE_STYLE)) - , Piwik_Common::HTML_ENCODING_QUOTE_STYLE); - } - } - - - if(!empty($key)) - { - $refererAnalyzed = true; - $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_SEARCH_ENGINE; - $nameRefererAnalyzed = $searchEngineName; - $keywordRefererAnalyzed = $key; - } - } - } - } - - /* - * Newsletter analysis - */ - if( !$refererAnalyzed ) - { - if(isset($currentUrlParse['query'])) - { - $newsletterVariableName = Piwik_LogStats_Config::getInstance()->LogStats['newsletter_var_name']; - $newsletterVar = Piwik_Common::getParameterFromQueryString( $currentUrlParse['query'], $newsletterVariableName); - - if($newsletterVar !== false && !empty($newsletterVar)) - { - $refererAnalyzed = true; - $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_NEWSLETTER; - $nameRefererAnalyzed = $newsletterVar; - } - } - } - - /* - * Partner analysis - */ - //TODO handle partner from a list of known partner URLs - if( !$refererAnalyzed ) - { - if(isset($currentUrlParse['query'])) - { - $partnerVariableName = Piwik_LogStats_Config::getInstance()->LogStats['partner_var_name']; - $partnerVar = Piwik_Common::getParameterFromQueryString($currentUrlParse['query'], $partnerVariableName); - - if($partnerVar !== false && !empty($partnerVar)) - { - $refererAnalyzed = true; - $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_PARTNER; - $nameRefererAnalyzed = $partnerVar; - } - } - } - - /* - * Campaign analysis - */ - if( !$refererAnalyzed ) - { - if(isset($currentUrlParse['query'])) - { - $campaignVariableName = Piwik_LogStats_Config::getInstance()->LogStats['campaign_var_name']; - $campaignName = Piwik_Common::getParameterFromQueryString($currentUrlParse['query'], $campaignVariableName); - - if( $campaignName !== false && !empty($campaignName)) - { - $campaignKeywordVariableName = Piwik_LogStats_Config::getInstance()->LogStats['campaign_keyword_var_name']; - $campaignKeyword = Piwik_Common::getParameterFromQueryString($currentUrlParse['query'], $campaignKeywordVariableName); - - $refererAnalyzed = true; - $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_CAMPAIGN; - $nameRefererAnalyzed = $campaignName; - - if(!empty($campaignKeyword)) - { - $keywordRefererAnalyzed = $campaignKeyword; - } - } - } - } - - /* - * Direct entry (referer host is similar to current host) - * And we have previously tried to detect the newsletter/partner/campaign variables in the URL - * so it can only be a direct access - */ - if( !$refererAnalyzed ) - { - $currentUrlParse = @parse_url($currentUrl); - - if(isset($currentUrlParse['host'])) - { - $currentHost = $currentUrlParse['host']; - $currentSH = $currentUrlParse['scheme'].'://'.$currentUrlParse['host']; - - if($currentHost == $refererHost) - { - $refererAnalyzed = true; - $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; - } - } - - } - - /* - * Normal website referer - */ - if( !$refererAnalyzed ) - { - $refererAnalyzed = true; - $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_WEBSITE; - $nameRefererAnalyzed = $refererHost; - } - } - - - $refererInformation = array( - 'referer_type' => $typeRefererAnalyzed, - 'referer_name' => $nameRefererAnalyzed, - 'referer_keyword' => $keywordRefererAnalyzed, - 'referer_url' => $refererUrl, - ); - - return $refererInformation; - } - - private function getConfigHash( $os, $browserName, $browserVersion, $resolution, $colorDepth, $plugin_Flash, $plugin_Director, $plugin_RealPlayer, $plugin_Pdf, $plugin_WindowsMedia, $plugin_Java, $plugin_Cookie, $ip, $browserLang) - { - return md5( $os . $browserName . $browserVersion . $resolution . $colorDepth . $plugin_Flash . $plugin_Director . $plugin_RealPlayer . $plugin_Pdf . $plugin_WindowsMedia . $plugin_Java . $plugin_Cookie . $ip . $browserLang ); - } - - private function getVisitorUniqueId() - { - if($this->isVisitorKnown()) - { - return -1; - } - else - { - return Piwik_Common::generateUniqId(); - } - } - -} -class Piwik_LogStats_Controller +class Piwik_LogStats { private $stateValid; diff --git a/modules/LogStats/Action.php b/modules/LogStats/Action.php new file mode 100644 index 0000000000..021ff35328 --- /dev/null +++ b/modules/LogStats/Action.php @@ -0,0 +1,198 @@ +<?php + +class Piwik_LogStats_Action +{ + + /* + * Specifications + * + * - External file tracking + * + * * MANUAL Download tracking + * download = http://piwik.org/hellokity.zip + * (name = dir1/file alias name) + * + * * AUTOMATIC Download tracking for a known list of file extensions. + * Make a hit to the piwik.php with the parameter: + * download = http://piwik.org/hellokity.zip + * + * When 'name' is not specified, + * if AUTOMATIC and if anchor not empty => name = link title anchor + * else name = path+query of the URL + * Ex: myfiles/beta.zip + * + * - External link tracking + * + * * MANUAL External link tracking + * outlink = http://amazon.org/test + * (name = the big partners / amazon) + * + * * AUTOMATIC External link tracking + * When a link is not detected as being part of the same website + * AND when the url extension is not detected as being a file download + * outlink = http://amazon.org/test + * + * When 'name' is not specified, + * if AUTOMATIC and if anchor not empty => name = link title anchor + * else name = URL + * Ex: http://amazon.org/test + */ + private $actionName; + private $url; + private $defaultActionName; + private $nameDownloadOutlink; + + const TYPE_ACTION = 1; + const TYPE_DOWNLOAD = 3; + const TYPE_OUTLINK = 2; + + function __construct( $db ) + { + $this->actionName = Piwik_Common::getRequestVar( 'action_name', '', 'string'); + + $downloadVariableName = Piwik_LogStats_Config::getInstance()->LogStats['download_url_var_name']; + $this->downloadUrl = Piwik_Common::getRequestVar( $downloadVariableName, '', 'string'); + + $outlinkVariableName = Piwik_LogStats_Config::getInstance()->LogStats['outlink_url_var_name']; + $this->outlinkUrl = Piwik_Common::getRequestVar( $outlinkVariableName, '', 'string'); + + $nameVariableName = Piwik_LogStats_Config::getInstance()->LogStats['download_outlink_name_var']; + $this->nameDownloadOutlink = Piwik_Common::getRequestVar( $nameVariableName, '', 'string'); + + $this->url = Piwik_Common::getRequestVar( 'url', '', 'string'); + $this->db = $db; + $this->defaultActionName = Piwik_LogStats_Config::getInstance()->LogStats['default_action_name']; + } + + /** + * About the Action concept: + * + * - An action is defined by a name. + * - The name can be specified in the JS Code in the variable 'action_name' + * - Handling UTF8 in the action name + * PLUGIN_IDEA - An action is associated to URLs and link to the URL from the interface + * PLUGIN_IDEA - An action hit by a visitor is associated to the HTML title of the page that triggered the action + * + * + If the name is not specified, we use the URL(path+query) to build a default name. + * For example for "http://piwik.org/test/my_page/test.html" + * the name would be "test/my_page/test.html" + * + * We make sure it is clean and displayable. + * If the name is empty we set it to a default name. + * + * TODO UTF8 handling to test + * + */ + private function generateInfo() + { + if(!empty($this->downloadUrl)) + { + $this->actionType = self::TYPE_DOWNLOAD; + $url = $this->downloadUrl; + $actionName = $this->nameDownloadOutlink; + } + elseif(!empty($this->outlinkUrl)) + { + $this->actionType = self::TYPE_OUTLINK; + $url = $this->outlinkUrl; + $actionName = $this->nameDownloadOutlink; + if( empty($actionName) ) + { + $actionName = $url; + } + } + else + { + $this->actionType = self::TYPE_ACTION; + $url = $this->url; + $actionName = $this->actionName; + } + + // the ActionName wasn't specified + if( empty($actionName) ) + { + $parsedUrl = parse_url( $url ); + + $actionName = ''; + + if(isset($parsedUrl['path'])) + { + $actionName .= substr($parsedUrl['path'], 1); + } + + if(isset($parsedUrl['query'])) + { + $actionName .= '?'.$parsedUrl['query']; + } + } + + // clean the name + $actionName = str_replace(array("\n", "\r"), '', $actionName); + + if(empty($actionName)) + { + $actionName = $this->defaultActionName; + } + + $this->finalActionName = $actionName; + } + + /** + * Returns the idaction of the current action name. + * This idaction is used in the visitor logging table to link the visit information + * (entry action, exit action) to the actions. + * This idaction is also used in the table that links the visits and their actions. + * + * The methods takes care of creating a new record in the action table if the existing + * action name doesn't exist yet. + * + * @return int Id action + */ + function getActionId() + { + $this->loadActionId(); + return $this->idAction; + } + + /** + * @see getActionId() + */ + private function loadActionId() + { + $this->generateInfo(); + + $name = $this->finalActionName; + $type = $this->actionType; + + $idAction = $this->db->fetch(" SELECT idaction + FROM ".$this->db->prefixTable('log_action') + ." WHERE name = ? AND type = ?", array($name, $type) ); + + // the action name has not been found, create it + if($idAction === false) + { + $this->db->query("INSERT INTO ". $this->db->prefixTable('log_action'). "( name, type ) + VALUES (?,?)",array($name,$type) ); + $idAction = $this->db->lastInsertId(); + } + else + { + $idAction = $idAction['idaction']; + } + + $this->idAction = $idAction; + } + + /** + * Records in the DB the association between the visit and this action. + */ + public function record( $idVisit, $idRefererAction, $timeSpentRefererAction) + { + $this->db->query("INSERT INTO ".$this->db->prefixTable('log_link_visit_action') + ." (idvisit, idaction, idaction_ref, time_spent_ref_action) VALUES (?,?,?,?)", + array($idVisit, $this->idAction, $idRefererAction, $timeSpentRefererAction) + ); + } +} + +?> diff --git a/modules/LogStats/Config.php b/modules/LogStats/Config.php new file mode 100644 index 0000000000..bd486ac519 --- /dev/null +++ b/modules/LogStats/Config.php @@ -0,0 +1,41 @@ +<?php + +/** + * Simple class to access the configuration file + */ +class Piwik_LogStats_Config +{ + static private $instance = null; + + static public function getInstance() + { + if (self::$instance == null) + { + $c = __CLASS__; + self::$instance = new $c(); + } + return self::$instance; + } + + public $config = array(); + + private function __construct() + { + $pathIniFile = PIWIK_INCLUDE_PATH . '/config/config.ini.php'; + $this->config = parse_ini_file($pathIniFile, true); + } + + public function __get( $name ) + { + if(isset($this->config[$name])) + { + return $this->config[$name]; + } + else + { + throw new Exception("The config element $name is not available in the configuration (check the configuration file)."); + } + } +} + +?> diff --git a/modules/LogStats/Cookie.php b/modules/LogStats/Cookie.php new file mode 100644 index 0000000000..4799e0367f --- /dev/null +++ b/modules/LogStats/Cookie.php @@ -0,0 +1,228 @@ +<?php + +/** + * Simple class to handle the cookies. + * Its features are: + * + * - read a cookie values + * - edit an existing cookie and save it + * - create a new cookie, set values, expiration date, etc. and save it + * + * The cookie content is saved in an optimized way. + */ +class Piwik_LogStats_Cookie +{ + /** + * The name of the cookie + */ + protected $name = null; + + /** + * The expire time for the cookie (expressed in UNIX Timestamp) + */ + protected $expire = null; + + /** + * The content of the cookie + */ + protected $value = array(); + + const VALUE_SEPARATOR = ':'; + + public function __construct( $cookieName, $expire = null) + { + $this->name = $cookieName; + + if(is_null($expire) + || !is_numeric($expire) + || $expire <= 0) + { + $this->expire = $this->getDefaultExpire(); + } + + if($this->isCookieFound()) + { + $this->loadContentFromCookie(); + } + } + + public function isCookieFound() + { + return isset($_COOKIE[$this->name]); + } + + protected function getDefaultExpire() + { + return time() + 86400*365*10; + } + + /** + * taken from http://usphp.com/manual/en/function.setcookie.php + * fix expires bug for IE users (should i say expected to fix the bug in 2.3 b2) + * TODO setCookie: use the other parameters of the function + */ + protected function setCookie($Name, $Value, $Expires, $Path = '', $Domain = '', $Secure = false, $HTTPOnly = false) + { + if (!empty($Domain)) + { + // Fix the domain to accept domains with and without 'www.'. + if (strtolower(substr($Domain, 0, 4)) == 'www.') $Domain = substr($Domain, 4); + + $Domain = '.' . $Domain; + + // Remove port information. + $Port = strpos($Domain, ':'); + if ($Port !== false) $Domain = substr($Domain, 0, $Port); + } + + $header = 'Set-Cookie: ' . rawurlencode($Name) . '=' . rawurlencode($Value) + . (empty($Expires) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', $Expires) . ' GMT') + . (empty($Path) ? '' : '; path=' . $Path) + . (empty($Domain) ? '' : '; domain=' . $Domain) + . (!$Secure ? '' : '; secure') + . (!$HTTPOnly ? '' : '; HttpOnly'); + + header($header, false); + } + + protected function setP3PHeader() + { + header("P3P: CP='OTI DSP COR NID STP UNI OTPa OUR'"); + } + + public function deleteCookie() + { + $this->setP3PHeader(); + setcookie($this->name, false, time() - 86400); + } + + public function save() + { + $this->setP3PHeader(); + $this->setCookie( $this->name, $this->generateContentString(), $this->expire); + } + + /** + * Load the cookie content into a php array + */ + protected function loadContentFromCookie() + { + $cookieStr = $_COOKIE[$this->name]; + + $values = explode( self::VALUE_SEPARATOR, $cookieStr); + foreach($values as $nameValue) + { + $equalPos = strpos($nameValue, '='); + $varName = substr($nameValue,0,$equalPos); + $varValue = substr($nameValue,$equalPos+1); + + // no numeric value are base64 encoded so we need to decode them + if(!is_numeric($varValue)) + { + $varValue = base64_decode($varValue); + + // some of the values may be serialized array so we try to unserialize it + if( ($arrayValue = @unserialize($varValue)) !== false + // we set the unserialized version only for arrays as you can have set a serialized string on purpose + && is_array($arrayValue) + ) + { + $varValue = $arrayValue; + } + } + + $this->set($varName, $varValue); + } + } + + /** + * Returns the string to save in the cookie frpm the $this->value array of values + * + */ + public function generateContentString() + { + $cookieStr = ''; + foreach($this->value as $name=>$value) + { + if(is_array($value)) + { + $value = base64_encode(serialize($value)); + } + elseif(is_string($value)) + { + $value = base64_encode($value); + } + + $cookieStr .= "$name=$value" . self::VALUE_SEPARATOR; + } + $cookieStr = substr($cookieStr, 0, strlen($cookieStr)-1); + return $cookieStr; + } + + /** + * Registers a new name => value association in the cookie. + * + * Registering new values is optimal if the value is a numeric value. + * If the value is a string, it will be saved as a base64 encoded string. + * If the value is an array, it will be saved as a serialized and base64 encoded + * string which is not very good in terms of bytes usage. + * You should save arrays only when you are sure about their maximum data size. + * + * @param string Name of the value to save; the name will be used to retrieve this value + * @param string|array|numeric Value to save + * + */ + public function set( $name, $value ) + { + $name = self::escapeValue($name); + $this->value[$name] = $value; + } + + /** + * Returns the value defined by $name from the cookie. + * + * @param string|integer Index name of the value to return + * @return mixed The value if found, false if the value is not found + */ + public function get( $name ) + { + $name = self::escapeValue($name); + return isset($this->value[$name]) ? self::escapeValue($this->value[$name]) : false; + } + + public function __toString() + { + $str = "<-- Content of the cookie '{$this->name}' <br>\n"; + foreach($this->value as $name => $value ) + { + $str .= $name . " = " . var_export($this->get($name), true) . "<br>\n"; + } + $str .= "--> <br>\n"; + return $str; + } + + static protected function escapeValue( $value ) + { + return Piwik_Common::sanitizeInputValues($value); + } +} + +// +//$c = new Piwik_LogStats_Cookie( 'piwik_logstats', 86400); +//echo $c; +//$c->set(1,1); +//$c->set('test',1); +//$c->set('test2','test=432:gea785'); +//$c->set('test3',array('test=432:gea785')); +//$c->set('test4',array(array(0=>1),1=>'test')); +//echo $c; +//echo "<br>"; +//echo $c->generateContentString(); +//echo "<br>"; +//$v=$c->get('more!'); +//if(empty($v)) $c->set('more!',1); +//$c->set('more!', array($c->get('more!'))); +//$c->save(); +//$c->deleteCookie(); + +?> diff --git a/modules/LogStats/Db.php b/modules/LogStats/Db.php new file mode 100644 index 0000000000..1231de5eac --- /dev/null +++ b/modules/LogStats/Db.php @@ -0,0 +1,74 @@ +<?php + +/** + * Simple database PDO wrapper + * + */ +class Piwik_LogStats_Db +{ + private $connection; + private $username; + private $password; + + public function __construct( $host, $username, $password, $dbname) + { + $this->dsn = "mysql:dbname=$dbname;host=$host"; + $this->username = $username; + $this->password = $password; + } + + public function connect() + { + try { + $pdoConnect = new PDO($this->dsn, $this->username, $this->password); + $pdoConnect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->connection = $pdoConnect; + } catch (PDOException $e) { + throw new Exception("Error connecting database: ".$e->getMessage()); + } + } + + public function prefixTable( $suffix ) + { + $prefix = Piwik_LogStats_Config::getInstance()->database['tables_prefix']; + + return $prefix . $suffix; + } + + public function fetchAll( $query, $parameters ) + { + try { + $sth = $this->query( $query, $parameters ); + return $sth->fetchAll(PDO::FETCH_ASSOC); + } catch (PDOException $e) { + throw new Exception("Error query: ".$e->getMessage()); + } + } + + public function fetch( $query, $parameters ) + { + try { + $sth = $this->query( $query, $parameters ); + return $sth->fetch(PDO::FETCH_ASSOC); + } catch (PDOException $e) { + throw new Exception("Error query: ".$e->getMessage()); + } + } + + public function query($query, $parameters = array()) + { + try { + $sth = $this->connection->prepare($query); + $sth->execute( $parameters ); + return $sth; + } catch (PDOException $e) { + throw new Exception("Error query: ".$e->getMessage()); + } + } + + public function lastInsertId() + { + return $this->connection->lastInsertId(); + } +} +?> diff --git a/modules/LogStats/Visit.php b/modules/LogStats/Visit.php new file mode 100644 index 0000000000..026b17636a --- /dev/null +++ b/modules/LogStats/Visit.php @@ -0,0 +1,749 @@ +<?php + +class Piwik_LogStats_Visit +{ + protected $cookieLog = null; + protected $visitorInfo = array(); + protected $userSettingsInformation = null; + + function __construct( $db ) + { + $this->db = $db; + + $idsite = Piwik_Common::getRequestVar('idsite', 0, 'int'); + if($idsite <= 0) + { + throw new Exception("The 'idsite' in the request is invalide."); + } + + $this->idsite = $idsite; + } + + protected function getCurrentDate( $format = "Y-m-d") + { + return date($format, $this->getCurrentTimestamp() ); + } + + protected function getCurrentTimestamp() + { + return time(); + } + + protected function getDatetimeFromTimestamp($timestamp) + { + return date("Y-m-d H:i:s",$timestamp); + } + + + + // test if the visitor is excluded because of + // - IP + // - cookie + // - configuration option? + private function isExcluded() + { + $excluded = 0; + + if($excluded) + { + printDebug("Visitor excluded."); + return true; + } + + return false; + } + + private function getCookieName() + { + return Piwik_LogStats_Config::getInstance()->LogStats['cookie_name'] . $this->idsite; + } + + + /** + * This methods tries to see if the visitor has visited the website before. + * + * We have to split the visitor into one of the category + * - Known visitor + * - New visitor + * + * A known visitor is a visitor that has already visited the website in the current month. + * We define a known visitor using the algorithm: + * + * 1) Checking if a cookie contains + * // a unique id for the visitor + * - id_visitor + * + * // the timestamp of the last action in the most recent visit + * - timestamp_last_action + * + * // the timestamp of the first action in the most recent visit + * - timestamp_first_action + * + * // the ID of the most recent visit (which could be in the past or the current visit) + * - id_visit + * + * // the ID of the most recent action + * - id_last_action + * + * 2) If the visitor doesn't have a cookie, we try to look for a similar visitor configuration. + * We search for a visitor with the same plugins/OS/Browser/Resolution for today for this website. + */ + private function recognizeTheVisitor() + { + $this->visitorKnown = false; + + $this->cookieLog = new Piwik_LogStats_Cookie( $this->getCookieName() ); + /* + * Case the visitor has the piwik cookie. + * We make sure all the data that should saved in the cookie is available. + */ + + if( false !== ($idVisitor = $this->cookieLog->get( Piwik_LogStats::COOKIE_INDEX_IDVISITOR )) ) + { + $timestampLastAction = $this->cookieLog->get( Piwik_LogStats::COOKIE_INDEX_TIMESTAMP_LAST_ACTION ); + $timestampFirstAction = $this->cookieLog->get( Piwik_LogStats::COOKIE_INDEX_TIMESTAMP_FIRST_ACTION ); + $idVisit = $this->cookieLog->get( Piwik_LogStats::COOKIE_INDEX_ID_VISIT ); + $idLastAction = $this->cookieLog->get( Piwik_LogStats::COOKIE_INDEX_ID_LAST_ACTION ); + + if( $timestampLastAction !== false && is_numeric($timestampLastAction) + && $timestampFirstAction !== false && is_numeric($timestampFirstAction) + && $idVisit !== false && is_numeric($idVisit) + && $idLastAction !== false && is_numeric($idLastAction) + ) + { + $this->visitorInfo['visitor_idcookie'] = $idVisitor; + $this->visitorInfo['visit_last_action_time'] = $timestampLastAction; + $this->visitorInfo['visit_first_action_time'] = $timestampFirstAction; + $this->visitorInfo['idvisit'] = $idVisit; + $this->visitorInfo['visit_exit_idaction'] = $idLastAction; + + $this->visitorKnown = true; + + printDebug("The visitor is known because he has the piwik cookie (idcookie = {$this->visitorInfo['visitor_idcookie']}, idvisit = {$this->visitorInfo['idvisit']}, last action = ".date("r", $this->visitorInfo['visit_last_action_time']).") "); + } + } + + /* + * If the visitor doesn't have the piwik cookie, we look for a visitor that has exactly the same configuration + * and that visited the website today. + */ + if( !$this->visitorKnown ) + { + $userInfo = $this->getUserSettingsInformation(); + $md5Config = $userInfo['config_md5config']; + + $visitRow = $this->db->fetch( + " SELECT visitor_idcookie, + UNIX_TIMESTAMP(visit_last_action_time) as visit_last_action_time, + UNIX_TIMESTAMP(visit_first_action_time) as visit_first_action_time, + idvisit, + visit_exit_idaction + FROM ".$this->db->prefixTable('log_visit'). + " WHERE visit_server_date = ? + AND idsite = ? + AND config_md5config = ? + ORDER BY visit_last_action_time DESC + LIMIT 1", + array( $this->getCurrentDate(), $this->idsite, $md5Config)); + if($visitRow + && count($visitRow) > 0) + { + $this->visitorInfo['visitor_idcookie'] = $visitRow['visitor_idcookie']; + $this->visitorInfo['visit_last_action_time'] = $visitRow['visit_last_action_time']; + $this->visitorInfo['visit_first_action_time'] = $visitRow['visit_first_action_time']; + $this->visitorInfo['idvisit'] = $visitRow['idvisit']; + $this->visitorInfo['visit_exit_idaction'] = $visitRow['visit_exit_idaction']; + + $this->visitorKnown = true; + + printDebug("The visitor is known because of his userSettings+IP (idcookie = {$visitRow['visitor_idcookie']}, idvisit = {$this->visitorInfo['idvisit']}, last action = ".date("r", $this->visitorInfo['visit_last_action_time']).") "); + } + } + } + + private function getUserSettingsInformation() + { + // we already called this method before, simply returns the result + if(is_array($this->userSettingsInformation)) + { + return $this->userSettingsInformation; + } + + + $plugin_Flash = Piwik_Common::getRequestVar( 'fla', 0, 'int'); + $plugin_Director = Piwik_Common::getRequestVar( 'dir', 0, 'int'); + $plugin_Quicktime = Piwik_Common::getRequestVar( 'qt', 0, 'int'); + $plugin_RealPlayer = Piwik_Common::getRequestVar( 'realp', 0, 'int'); + $plugin_Pdf = Piwik_Common::getRequestVar( 'pdf', 0, 'int'); + $plugin_WindowsMedia = Piwik_Common::getRequestVar( 'wma', 0, 'int'); + $plugin_Java = Piwik_Common::getRequestVar( 'java', 0, 'int'); + $plugin_Cookie = Piwik_Common::getRequestVar( 'cookie', 0, 'int'); + + $userAgent = Piwik_Common::sanitizeInputValues(@$_SERVER['HTTP_USER_AGENT']); + $aBrowserInfo = Piwik_Common::getBrowserInfo($userAgent); + $browserName = $aBrowserInfo['name']; + $browserVersion = $aBrowserInfo['version']; + + $os = Piwik_Common::getOs($userAgent); + + $resolution = Piwik_Common::getRequestVar('res', 'unknown', 'string'); + $colorDepth = Piwik_Common::getRequestVar('col', 32, 'numeric'); + + + $ip = Piwik_Common::getIp(); + $ip = ip2long($ip); + + $browserLang = Piwik_Common::sanitizeInputValues(@$_SERVER['HTTP_ACCEPT_LANGUAGE']); + if(is_null($browserLang)) + { + $browserLang = ''; + } + + + $configurationHash = $this->getConfigHash( + $os, + $browserName, + $browserVersion, + $resolution, + $colorDepth, + $plugin_Flash, + $plugin_Director, + $plugin_RealPlayer, + $plugin_Pdf, + $plugin_WindowsMedia, + $plugin_Java, + $plugin_Cookie, + $ip, + $browserLang); + + $this->userSettingsInformation = array( + 'config_md5config' => $configurationHash, + 'config_os' => $os, + 'config_browser_name' => $browserName, + 'config_browser_version' => $browserVersion, + 'config_resolution' => $resolution, + 'config_color_depth' => $colorDepth, + 'config_pdf' => $plugin_Pdf, + 'config_flash' => $plugin_Flash, + 'config_java' => $plugin_Java, + 'config_director' => $plugin_Director, + 'config_quicktime' => $plugin_Quicktime, + 'config_realplayer' => $plugin_RealPlayer, + 'config_windowsmedia' => $plugin_WindowsMedia, + 'config_cookie' => $plugin_RealPlayer, + 'location_ip' => $ip, + 'location_browser_lang' => $browserLang, + ); + + return $this->userSettingsInformation; + } + + /** + * Returns true if the last action was done during the last 30 minutes + */ + private function isLastActionInTheSameVisit() + { + return $this->visitorInfo['visit_last_action_time'] + >= ($this->getCurrentTimestamp() - Piwik_LogStats::VISIT_STANDARD_LENGTH); + } + + private function isVisitorKnown() + { + return $this->visitorKnown === true; + } + + /** + * Once we have the visitor information, we have to define if the visit is a new or a known visit. + * + * 1) When the last action was done more than 30min ago, + * or if the visitor is new, then this is a new visit. + * + * 2) If the last action is less than 30min ago, then the same visit is going on. + * Because the visit goes on, we can get the time spent during the last action. + * + * NB: + * - In the case of a new visit, then the time spent + * during the last action of the previous visit is unknown. + * + * - In the case of a new visit but with a known visitor, + * we can set the 'returning visitor' flag. + * + */ + + /** + * In all the cases we set a cookie to the visitor with the new information. + */ + public function handle() + { + if(!$this->isExcluded()) + { + $this->recognizeTheVisitor(); + + // known visitor + if($this->isVisitorKnown()) + { + // the same visit is going on + if($this->isLastActionInTheSameVisit()) + { + $this->handleKnownVisit(); + } + // new visit + else + { + $this->handleNewVisit(); + } + } + // new visitor => new visit + else + { + $this->handleNewVisit(); + } + + // we update the cookie with the new visit information + $this->updateCookie(); + + } + } + + private function updateCookie() + { + printDebug("We manage the cookie..."); + + // idcookie has been generated in handleNewVisit or we simply propagate the old value + $this->cookieLog->set( Piwik_LogStats::COOKIE_INDEX_IDVISITOR, + $this->visitorInfo['visitor_idcookie'] ); + + // the last action timestamp is the current timestamp + $this->cookieLog->set( Piwik_LogStats::COOKIE_INDEX_TIMESTAMP_LAST_ACTION, + $this->visitorInfo['visit_last_action_time'] ); + + // the first action timestamp is the timestamp of the first action of the current visit + $this->cookieLog->set( Piwik_LogStats::COOKIE_INDEX_TIMESTAMP_FIRST_ACTION, + $this->visitorInfo['visit_first_action_time'] ); + + // the idvisit has been generated by mysql in handleNewVisit or simply propagated here + $this->cookieLog->set( Piwik_LogStats::COOKIE_INDEX_ID_VISIT, + $this->visitorInfo['idvisit'] ); + + // the last action ID is the current exit idaction + $this->cookieLog->set( Piwik_LogStats::COOKIE_INDEX_ID_LAST_ACTION, + $this->visitorInfo['visit_exit_idaction'] ); + + $this->cookieLog->save(); + } + + + /** + * In the case of a known visit, we have to do the following actions: + * + * 1) Insert the new action + * + * 2) Update the visit information + */ + private function handleKnownVisit() + { + printDebug("Visit known."); + + /** + * Init the action + */ + $action = new Piwik_LogStats_Action( $this->db ); + + $actionId = $action->getActionId(); + + printDebug("idAction = $actionId"); + + $serverTime = $this->getCurrentTimestamp(); + $datetimeServer = $this->getDatetimeFromTimestamp($serverTime); + + $this->db->query("UPDATE ". $this->db->prefixTable('log_visit')." + SET visit_last_action_time = ?, + visit_exit_idaction = ?, + visit_total_actions = visit_total_actions + 1, + visit_total_time = UNIX_TIMESTAMP(visit_last_action_time) - UNIX_TIMESTAMP(visit_first_action_time) + WHERE idvisit = ? + LIMIT 1", + array( $datetimeServer, + $actionId, + $this->visitorInfo['idvisit'] ) + ); + /** + * Save the action + */ + $timespentLastAction = $serverTime - $this->visitorInfo['visit_last_action_time']; + + $action->record( $this->visitorInfo['idvisit'], + $this->visitorInfo['visit_exit_idaction'], + $timespentLastAction + ); + + + /** + * Cookie fields to be updated + */ + $this->visitorInfo['visit_last_action_time'] = $serverTime; + $this->visitorInfo['visit_exit_idaction'] = $actionId; + + + } + + /** + * In the case of a new visit, we have to do the following actions: + * + * 1) Insert the new action + * + * 2) Insert the visit information + */ + private function handleNewVisit() + { + printDebug("New Visit."); + + /** + * Get the variables from the REQUEST + */ + + // Configuration settings + $userInfo = $this->getUserSettingsInformation(); + + // General information + $localTime = Piwik_Common::getRequestVar( 'h', $this->getCurrentDate("H"), 'numeric') + .':'. Piwik_Common::getRequestVar( 'm', $this->getCurrentDate("i"), 'numeric') + .':'. Piwik_Common::getRequestVar( 's', $this->getCurrentDate("s"), 'numeric'); + $serverDate = $this->getCurrentDate(); + $serverTime = $this->getCurrentTimestamp(); + + if($this->isVisitorKnown()) + { + $idcookie = $this->visitorInfo['visitor_idcookie']; + $returningVisitor = 1; + } + else + { + $idcookie = $this->getVisitorUniqueId(); + $returningVisitor = 0; + } + + $defaultTimeOnePageVisit = Piwik_LogStats_Config::getInstance()->LogStats['default_time_one_page_visit']; + + // Location information + $country = Piwik_Common::getCountry($userInfo['location_browser_lang']); + $continent = Piwik_Common::getContinent( $country ); + + //Referer information + $refererInfo = $this->getRefererInformation(); + + /** + * Init the action + */ + $action = new Piwik_LogStats_Action( $this->db ); + + $actionId = $action->getActionId(); + + printDebug("idAction = $actionId"); + + + /** + * Save the visitor + */ + $informationToSave = array( + //'idvisit' => , + 'idsite' => $this->idsite, + 'visitor_localtime' => $localTime, + 'visitor_idcookie' => $idcookie, + 'visitor_returning' => $returningVisitor, + 'visit_first_action_time' => $this->getDatetimeFromTimestamp($serverTime), + 'visit_last_action_time' => $this->getDatetimeFromTimestamp($serverTime), + 'visit_server_date' => $serverDate, + 'visit_entry_idaction' => $actionId, + 'visit_exit_idaction' => $actionId, + 'visit_total_actions' => 1, + 'visit_total_time' => $defaultTimeOnePageVisit, + 'referer_type' => $refererInfo['referer_type'], + 'referer_name' => $refererInfo['referer_name'], + 'referer_url' => $refererInfo['referer_url'], + 'referer_keyword' => $refererInfo['referer_keyword'], + 'config_md5config' => $userInfo['config_md5config'], + 'config_os' => $userInfo['config_os'], + 'config_browser_name' => $userInfo['config_browser_name'], + 'config_browser_version' => $userInfo['config_browser_version'], + 'config_resolution' => $userInfo['config_resolution'], + 'config_color_depth' => $userInfo['config_color_depth'], + 'config_pdf' => $userInfo['config_pdf'], + 'config_flash' => $userInfo['config_flash'], + 'config_java' => $userInfo['config_java'], + 'config_director' => $userInfo['config_director'], + 'config_quicktime' => $userInfo['config_quicktime'], + 'config_realplayer' => $userInfo['config_realplayer'], + 'config_windowsmedia' => $userInfo['config_windowsmedia'], + 'config_cookie' => $userInfo['config_cookie'], + 'location_ip' => $userInfo['location_ip'], + 'location_browser_lang' => $userInfo['location_browser_lang'], + 'location_country' => $country, + 'location_continent' => $continent, + ); + + + $fields = implode(", ", array_keys($informationToSave)); + $values = substr(str_repeat( "?,",count($informationToSave)),0,-1); + + $this->db->query( "INSERT INTO ".$this->db->prefixTable('log_visit'). + " ($fields) VALUES ($values)", array_values($informationToSave)); + + $idVisit = $this->db->lastInsertId(); + + // Update the visitor information attribute with this information array + $this->visitorInfo = $informationToSave; + $this->visitorInfo['idvisit'] = $idVisit; + + // we have to save timestamp in the object properties, whereas mysql eats some other datetime format + $this->visitorInfo['visit_first_action_time'] = $serverTime; + $this->visitorInfo['visit_last_action_time'] = $serverTime; + + /** + * Save the action + */ + $action->record( $idVisit, 0, 0 ); + + } + + /** + * Returns an array containing the following information: + * - referer_type + * - direct -- absence of referer URL OR referer URL has the same host + * - site -- based on the referer URL + * - search_engine -- based on the referer URL + * - campaign -- based on campaign URL parameter + * - newsletter -- based on newsletter URL parameter + * - partner -- based on partner URL parameter + * + * - referer_name + * - () + * - piwik.net -- site host name + * - google.fr -- search engine host name + * - adwords-search -- campaign name + * - beta-release -- newsletter name + * - my-nice-partner -- partner name + * + * - referer_keyword + * - () + * - () + * - my keyword + * - my paid keyword + * - () + * - () + * + * - referer_url : the same for all the referer types + * + */ + private function getRefererInformation() + { + // bool that says if the referer detection is done + $refererAnalyzed = false; + $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; + $nameRefererAnalyzed = ''; + $keywordRefererAnalyzed = ''; + + $refererUrl = Piwik_Common::getRequestVar( 'urlref', '', 'string'); + $currentUrl = Piwik_Common::getRequestVar( 'url', '', 'string'); + + $refererUrlParse = @parse_url($refererUrl); + $currentUrlParse = @parse_url($currentUrl); + + if(isset($refererUrlParse['host']) + && !empty($refererUrlParse['host'])) + { + + $refererHost = $refererUrlParse['host']; + $refererSH = $refererUrlParse['scheme'].'://'.$refererUrlParse['host']; + + /* + * Search engine detection + */ + if( !$refererAnalyzed ) + { + /* + * A referer is a search engine if the URL's host is in the SearchEngines array + * and if we found the keyword in the URL. + * + * For example if someone comes from http://www.google.com/partners.html this will not + * be counted as a search engines, but as a website referer from google.com (because the + * keyword couldn't be found in the URL) + */ + require_once PIWIK_DATAFILES_INCLUDE_PATH . "/SearchEngines.php"; + + if(array_key_exists($refererHost, $GLOBALS['Piwik_SearchEngines'])) + { + // which search engine ? + $searchEngineName = $GLOBALS['Piwik_SearchEngines'][$refererHost][0]; + $variableName = $GLOBALS['Piwik_SearchEngines'][$refererHost][1]; + + // if there is a query, there may be a keyword... + if(isset($refererUrlParse['query'])) + { + $query = $refererUrlParse['query']; + + //TODO: change the search engine file and use REGEXP; performance downside? + //TODO: port the phpmyvisites google-images hack here + + // search for keywords now &vname=keyword + $key = strtolower(Piwik_Common::getParameterFromQueryString($query, $variableName)); + + //TODO test the search engine non-utf8 support + // for search engines that don't use utf-8 + if((function_exists('iconv')) + && (isset($GLOBALS['Piwik_SearchEngines'][$refererHost][2]))) + { + $charset = trim($GLOBALS['searchEngines'][$refererHost][2]); + + if(!empty($charset)) + { + $key = htmlspecialchars( + @iconv( $charset, + 'utf-8//TRANSLIT', + htmlspecialchars_decode($key, Piwik_Common::HTML_ENCODING_QUOTE_STYLE)) + , Piwik_Common::HTML_ENCODING_QUOTE_STYLE); + } + } + + + if(!empty($key)) + { + $refererAnalyzed = true; + $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_SEARCH_ENGINE; + $nameRefererAnalyzed = $searchEngineName; + $keywordRefererAnalyzed = $key; + } + } + } + } + + /* + * Newsletter analysis + */ + if( !$refererAnalyzed ) + { + if(isset($currentUrlParse['query'])) + { + $newsletterVariableName = Piwik_LogStats_Config::getInstance()->LogStats['newsletter_var_name']; + $newsletterVar = Piwik_Common::getParameterFromQueryString( $currentUrlParse['query'], $newsletterVariableName); + + if($newsletterVar !== false && !empty($newsletterVar)) + { + $refererAnalyzed = true; + $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_NEWSLETTER; + $nameRefererAnalyzed = $newsletterVar; + } + } + } + + /* + * Partner analysis + */ + //TODO handle partner from a list of known partner URLs + if( !$refererAnalyzed ) + { + if(isset($currentUrlParse['query'])) + { + $partnerVariableName = Piwik_LogStats_Config::getInstance()->LogStats['partner_var_name']; + $partnerVar = Piwik_Common::getParameterFromQueryString($currentUrlParse['query'], $partnerVariableName); + + if($partnerVar !== false && !empty($partnerVar)) + { + $refererAnalyzed = true; + $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_PARTNER; + $nameRefererAnalyzed = $partnerVar; + } + } + } + + /* + * Campaign analysis + */ + if( !$refererAnalyzed ) + { + if(isset($currentUrlParse['query'])) + { + $campaignVariableName = Piwik_LogStats_Config::getInstance()->LogStats['campaign_var_name']; + $campaignName = Piwik_Common::getParameterFromQueryString($currentUrlParse['query'], $campaignVariableName); + + if( $campaignName !== false && !empty($campaignName)) + { + $campaignKeywordVariableName = Piwik_LogStats_Config::getInstance()->LogStats['campaign_keyword_var_name']; + $campaignKeyword = Piwik_Common::getParameterFromQueryString($currentUrlParse['query'], $campaignKeywordVariableName); + + $refererAnalyzed = true; + $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_CAMPAIGN; + $nameRefererAnalyzed = $campaignName; + + if(!empty($campaignKeyword)) + { + $keywordRefererAnalyzed = $campaignKeyword; + } + } + } + } + + /* + * Direct entry (referer host is similar to current host) + * And we have previously tried to detect the newsletter/partner/campaign variables in the URL + * so it can only be a direct access + */ + if( !$refererAnalyzed ) + { + $currentUrlParse = @parse_url($currentUrl); + + if(isset($currentUrlParse['host'])) + { + $currentHost = $currentUrlParse['host']; + $currentSH = $currentUrlParse['scheme'].'://'.$currentUrlParse['host']; + + if($currentHost == $refererHost) + { + $refererAnalyzed = true; + $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY; + } + } + + } + + /* + * Normal website referer + */ + if( !$refererAnalyzed ) + { + $refererAnalyzed = true; + $typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_WEBSITE; + $nameRefererAnalyzed = $refererHost; + } + } + + + $refererInformation = array( + 'referer_type' => $typeRefererAnalyzed, + 'referer_name' => $nameRefererAnalyzed, + 'referer_keyword' => $keywordRefererAnalyzed, + 'referer_url' => $refererUrl, + ); + + return $refererInformation; + } + + private function getConfigHash( $os, $browserName, $browserVersion, $resolution, $colorDepth, $plugin_Flash, $plugin_Director, $plugin_RealPlayer, $plugin_Pdf, $plugin_WindowsMedia, $plugin_Java, $plugin_Cookie, $ip, $browserLang) + { + return md5( $os . $browserName . $browserVersion . $resolution . $colorDepth . $plugin_Flash . $plugin_Director . $plugin_RealPlayer . $plugin_Pdf . $plugin_WindowsMedia . $plugin_Java . $plugin_Cookie . $ip . $browserLang ); + } + + private function getVisitorUniqueId() + { + if($this->isVisitorKnown()) + { + return -1; + } + else + { + return Piwik_Common::generateUniqId(); + } + } + +} +?> diff --git a/modules/Piwik.php b/modules/Piwik.php index 80c0ca1015..5cc09f234c 100755 --- a/modules/Piwik.php +++ b/modules/Piwik.php @@ -9,6 +9,18 @@ class Piwik Zend_Registry::get('logger_message')->log($message . "<br>" . PHP_EOL); } + static public function getTableCreateSql( $tableName ) + { + $tables = Piwik::getTablesCreateSql(); + + if(!isset($tables[$tableName])) + { + throw new Exception("The table '$tableName' SQL creation code couldn't be found."); + } + + return $tables[$tableName]; + } + static public function getTablesCreateSql() { $config = Zend_Registry::get('config'); @@ -217,8 +229,8 @@ class Piwik if(!is_dir($path)) { mkdir($path, $mode, true); - } + if($denyAccess) { Piwik::createHtAccess($path); @@ -250,11 +262,12 @@ class Piwik $allMyTables = self::getTablesNames(); $db = Zend_Registry::get('db'); - $allTables = $db->fetchCol('SHOW TABLES'); - - $intersect = array_intersect($allTables, $allMyTables); + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; - return $intersect; + $allTables = $db->fetchCol("SHOW TABLES LIKE '$prefixTables%'"); + + return $allTables; } static public function createDatabase() @@ -286,7 +299,7 @@ class Piwik $configAPI = Zend_Registry::get('config')->log; $aLoggers = array( - //'logger_query_profile' => new Piwik_Log_QueryProfile, // TODO Piwik_Log_QueryProfile +// 'logger_query_profile' => new Piwik_Log_QueryProfile, // TODO Piwik_Log_QueryProfile 'logger_api_call' => new Piwik_Log_APICall, 'logger_exception' => new Piwik_Log_Exception, 'logger_error' => new Piwik_Log_Error, @@ -333,9 +346,9 @@ class Piwik } } - static public function createConfigObject() + static public function createConfigObject( $pathConfigFile = null ) { - $config = new Piwik_Config; + $config = new Piwik_Config($pathConfigFile); assert(count($config) != 0); } diff --git a/modules/TablePartitioning.php b/modules/TablePartitioning.php new file mode 100644 index 0000000000..0b5d62d888 --- /dev/null +++ b/modules/TablePartitioning.php @@ -0,0 +1,100 @@ +<?php + +abstract class Piwik_TablePartitioning +{ + protected $tableName = null; + protected $generatedTableName = null; + protected $timestamp = null; + + public function __construct( $tableName ) + { + $this->tableName = $tableName; + } + + abstract protected function generateTableName() ; + + + public function setDate( $timestamp ) + { + $this->timestamp = $timestamp; + $this->generatedTableName = null; + } + + public function getTableName() + { + // table name already processed + if(!is_null($this->generatedTableName)) + { + return $this->generatedTableName; + } + + if(is_null($this->timestamp)) + { + throw new Exception("You have to specify a timestamp for a Table Partitioning by date."); + } + + // generate table name + $this->generatedTableName = $this->generateTableName(); + + // we make sure the table already exists + $this->checkTableExists(); + } + + protected function checkTableExists() + { + $tablesAlreadyInstalled = Piwik::getTablesInstalled(); + + if(!in_array($this->generatedTableName, $tablesAlreadyInstalled)) + { + $db = Zend_Registry::get('db'); + $sql = Piwik::getTableCreateSql($this->tableName); + + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + $sql = str_replace( $prefixTables . $this->tableName, $this->generatedTableName, $sql); + + $db->query( $sql ); + } + } + + protected function __toString() + { + return $this->getTableName(); + } +} + +class Piwik_TablePartitioning_Monthly extends Piwik_TablePartitioning +{ + public function __construct( $tableName ) + { + parent::__construct($tableName); + } + protected function generateTableName() + { + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + + $date = date("Y_m", $this->timestamp); + + return $prefixTables . $this->tableName . "_" . $date; + } + +} +class Piwik_TablePartitioning_Daily extends Piwik_TablePartitioning +{ + public function __construct( $tableName ) + { + parent::__construct($tableName); + } + protected function generateTableName() + { + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + + $date = date("Y_m_d", $this->timestamp); + + return $prefixTables . $this->tableName . "_" . $date; + } + +} +?> @@ -23,9 +23,15 @@ set_include_path(PIWIK_INCLUDE_PATH require_once "Event/Dispatcher.php"; require_once "Common.php"; -require_once "LogStats.php"; require_once "PluginManager.php"; + +require_once "LogStats.php"; require_once "LogStats/Plugins.php"; +require_once "LogStats/Config.php"; +require_once "LogStats/Action.php"; +require_once "LogStats/Cookie.php"; +require_once "LogStats/Db.php"; +require_once "LogStats/Visit.php"; $GLOBALS['DEBUGPIWIK'] = true; @@ -42,8 +48,7 @@ $GLOBALS['DEBUGPIWIK'] = true; ob_start(); printDebug($_GET); -$process = new Piwik_LogStats_Controller; -Piwik_PostEvent( 'LogsStats.NewVisitor' ); +$process = new Piwik_LogStats; $process->main(); // yet to do diff --git a/tests/config_test.php b/tests/config_test.php index f36342e7ba..041490aff1 100755 --- a/tests/config_test.php +++ b/tests/config_test.php @@ -3,41 +3,48 @@ if(!defined("PATH_TEST_TO_ROOT")) { define('PATH_TEST_TO_ROOT', '..'); } - -if(!defined('PIWIK_INCLUDE_PATH')) +if(!defined("PATH_TEST_TO_ROOT2")) { - define('PIWIK_INCLUDE_PATH', PATH_TEST_TO_ROOT); + define('PATH_TEST_TO_ROOT2', '../..'); } -if (! defined('SIMPLE_TEST')) +if(!defined('PIWIK_INCLUDE_PATH')) { - define('SIMPLE_TEST', PATH_TEST_TO_ROOT . '/tests/simpletest/'); + define('PIWIK_INCLUDE_PATH', PATH_TEST_TO_ROOT); } -require_once SIMPLE_TEST . 'autorun.php'; -require_once SIMPLE_TEST . 'mock_objects.php'; +set_include_path(PATH_TEST_TO_ROOT .'/' + . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/libs/' + . PATH_SEPARATOR . getcwd() . '/../../libs/' + . PATH_SEPARATOR . getcwd() . '/../../config/' + . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/core/' + . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/config/' + . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/modules/' + . PATH_SEPARATOR . PATH_TEST_TO_ROOT2 . '/libs/' + . PATH_SEPARATOR . PATH_TEST_TO_ROOT2 . '/config/' + . PATH_SEPARATOR . PATH_TEST_TO_ROOT2 . '/core/' + . PATH_SEPARATOR . PATH_TEST_TO_ROOT2 . '/modules/' + . PATH_SEPARATOR . get_include_path() + ); + +require_once 'simpletest/autorun.php'; +require_once 'simpletest/mock_objects.php'; SimpleTest :: prefer(new HtmlReporter()); error_reporting(E_ALL|E_NOTICE); date_default_timezone_set('Europe/London'); -set_include_path(PATH_TEST_TO_ROOT - . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/libs/' - . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/core/' - . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/modules' - . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/core/models' - . PATH_SEPARATOR . get_include_path()); require_once "Zend/Exception.php"; require_once "Zend/Loader.php"; - -require_once PIWIK_INCLUDE_PATH . "/modules/ErrorHandler.php"; +require_once "ErrorHandler.php"; //set_error_handler('Piwik_ErrorHandler'); Zend_Loader::loadClass('Zend_Registry'); Zend_Loader::loadClass('Zend_Config_Ini'); +Zend_Loader::loadClass('Zend_Config'); Zend_Loader::loadClass('Zend_Db'); Zend_Loader::loadClass('Zend_Db_Table'); Zend_Loader::loadClass('Zend_Debug'); diff --git a/tests/modules/Database.test.php b/tests/modules/Database.test.php index 2b0c07f767..df274fccc0 100755 --- a/tests/modules/Database.test.php +++ b/tests/modules/Database.test.php @@ -2,7 +2,7 @@ if(!defined("PATH_TEST_TO_ROOT")) { define('PATH_TEST_TO_ROOT', '../..'); } -require_once PATH_TEST_TO_ROOT ."/tests/config_test.php"; +require_once "config_test.php"; Mock::generate('Piwik_Access'); @@ -139,13 +139,15 @@ class Test_Database extends UnitTestCase public function setUp() { - Piwik::createConfigObject(); + Piwik::createConfigObject('config.ini.php'); // setup database Piwik::createDatabaseObject(); - Piwik::createLogObject(); Zend_Registry::get('config')->setTestEnvironment(); + + Piwik::createLogObject(); + Piwik::dropDatabase(); Piwik::createDatabase(); Piwik::createDatabaseObject(); diff --git a/tests/modules/TablePartitioning.test.php b/tests/modules/TablePartitioning.test.php new file mode 100755 index 0000000000..7f000d1212 --- /dev/null +++ b/tests/modules/TablePartitioning.test.php @@ -0,0 +1,138 @@ +<?php +if(!defined("PATH_TEST_TO_ROOT")) { + define('PATH_TEST_TO_ROOT', '..'); +} +require_once PATH_TEST_TO_ROOT ."/../tests/config_test.php"; +require_once "Database.test.php"; + +Zend_Loader::loadClass('Piwik_TablePartitioning'); +class Test_Piwik_TablePartitioning extends Test_Database +{ + function __construct() + { + parent::__construct(''); + } + public function setUp() + { + parent::setUp(); + } + + // test no timestamp => exception + function test_noTimestamp() + { + $p = new Piwik_TablePartitioning_Monthly('testtable'); + + try { + $p->getTableName(); + $this->fail("Exception not raised."); + } + catch (Exception $expected) { + return; + } + } + + // test table absent => create + function test_noTable() + { + $tableName ='log_visit'; + $p = new Piwik_TablePartitioning_Monthly($tableName); + $timestamp = strtotime("10 September 2000"); + $suffixShouldBe = "_2000_09"; + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + $tablename = $prefixTables.$tableName.$suffixShouldBe; + + $p->setDate( $timestamp ); + + $allTablesInstalled = Piwik::getTablesInstalled(); + $this->assertTrue( !in_array($tablename, $allTablesInstalled)); + $this->assertTrue( $tablename, $p->getTableName()); + + $allTablesInstalled = Piwik::getTablesInstalled(); + $this->assertTrue( in_array($tablename, $allTablesInstalled)); + $this->assertEqual( $tablename, (string)$p); + } + + // test table present => nothing + function test_tablePresent() + { + $tableName ='log_visit'; + $p = new Piwik_TablePartitioning_Monthly($tableName); + $timestamp = strtotime("10 September 2000"); + $suffixShouldBe = "_2000_09"; + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + $tablename = $prefixTables.$tableName.$suffixShouldBe; + + Zend_Registry::get('db')->query("CREATE TABLE $tablename (`test` VARCHAR( 255 ) NOT NULL)"); + + $p->setDate( $timestamp ); + + $allTablesInstalled = Piwik::getTablesInstalled(); + $this->assertTrue( in_array($tablename, $allTablesInstalled)); + $this->assertTrue( $tablename, $p->getTableName()); + } + + // test monthly + function test_monthlyPartition() + { + + $tableName ='log_visit'; + $p = new Piwik_TablePartitioning_Monthly($tableName); + $timestamp = strtotime("10 September 2000"); + $suffixShouldBe = "_2000_09"; + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + $tablename = $prefixTables.$tableName.$suffixShouldBe; + + $p->setDate( $timestamp ); + + $allTablesInstalled = Piwik::getTablesInstalled(); + $this->assertTrue( !in_array($tablename, $allTablesInstalled)); + $this->assertTrue( $tablename, $p->getTableName()); + + $allTablesInstalled = Piwik::getTablesInstalled(); + $this->assertTrue( in_array($tablename, $allTablesInstalled)); + $this->assertEqual( $tablename, (string)$p); + } + + // test daily + function test_dailyPartition() + { + + $tableName ='log_visit'; + $p = new Piwik_TablePartitioning_Daily($tableName); + $timestamp = strtotime("10 September 2000"); + $suffixShouldBe = "_2000_09_10"; + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + $tablename = $prefixTables.$tableName.$suffixShouldBe; + + $p->setDate( $timestamp ); + + $allTablesInstalled = Piwik::getTablesInstalled(); + $this->assertTrue( !in_array($tablename, $allTablesInstalled)); + $this->assertTrue( $tablename, $p->getTableName()); + + $allTablesInstalled = Piwik::getTablesInstalled(); + $this->assertTrue( in_array($tablename, $allTablesInstalled)); + $this->assertEqual( $tablename, (string)$p); + } + + + /** + * -> exception + */ + public function _test_() + { + try { + test(); + $this->fail("Exception not raised."); + } + catch (Exception $expected) { + $this->assertPattern("()", $expected->getMessage()); + return; + } + } +} +?> diff --git a/tests/modules/blank.test.php b/tests/modules/blank.test.php index b26a8f64aa..68522ef449 100755 --- a/tests/modules/blank.test.php +++ b/tests/modules/blank.test.php @@ -1,15 +1,14 @@ <?php -if (! defined('SIMPLE_TEST')) { - define('SIMPLE_TEST', '../simpletest/'); +if(!defined("PATH_TEST_TO_ROOT")) { + define('PATH_TEST_TO_ROOT', '../..'); } -require_once(SIMPLE_TEST.'autorun.php'); -SimpleTest :: prefer(new HtmlReporter()); +require_once PATH_TEST_TO_ROOT ."/tests/config_test.php"; class Test_Piwik_Blank extends UnitTestCase { function __construct() { - parent::__construct('Log class test'); + parent::__construct(''); } /** |