Ticket #134: contrib syslog - Tracker.php

File contrib syslog - Tracker.php, 16.5 KB (added by matt, 4 months ago)
Line 
1<?php
2/**
3 * Piwik - Open source web analytics
4 *
5 * @link http://piwik.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 * @version $Id: Tracker.php 5296 2011-10-14 02:52:44Z matt $
8 *
9 * @category Piwik
10 * @package Piwik
11 */
12
13/**
14 * Class used by the logging script piwik.php called by the javascript tag.
15 * Handles the visitor & his/her actions on the website, saves the data in the DB,
16 * saves information in the cookie, etc.
17 *
18 * We try to include as little files as possible (no dependency on 3rd party modules).
19 *
20 * @package Piwik
21 * @subpackage Piwik_Tracker
22 */
23class Piwik_Tracker
24{   
25    protected $stateValid = self::STATE_NOTHING_TO_NOTICE;
26    /**
27     * @var Piwik_Tracker_Db
28     */
29    protected static $db = null;
30   
31    const STATE_NOTHING_TO_NOTICE = 1;
32    const STATE_LOGGING_DISABLE = 10;
33    const STATE_EMPTY_REQUEST = 11;
34    const STATE_NOSCRIPT_REQUEST = 13;
35       
36    // We use hex ID that are 16 chars in length, ie. 64 bits IDs
37    const LENGTH_HEX_ID_STRING = 16;
38    const LENGTH_BINARY_ID = 8;
39   
40    // These are also hardcoded in the Javascript
41    const MAX_CUSTOM_VARIABLES = 5;
42    const MAX_LENGTH_CUSTOM_VARIABLE = 200;
43   
44    protected $authenticated = false;
45    static protected $toDatabase = true;
46    static protected $toSyslog = true;
47    static protected $forcedDateTime = null;
48    static protected $forcedIpString = null;
49    static protected $forcedVisitorId = null;
50   
51    static protected $pluginsNotToLoad = array();
52   
53    public function __construct($args = null)
54    {
55        $this->request = $args ? $args : $_GET + $_POST;
56    }
57    public static function setForceIp($ipString)
58    {
59        self::$forcedIpString = $ipString;
60    }
61    public static function setForceDateTime( $dateTime )
62    {
63        self::$forcedDateTime = $dateTime;
64    }
65   
66    public static function setForceVisitorId($visitorId)
67    {
68        self::$forcedVisitorId = $visitorId;
69    }
70   
71    public function getCurrentTimestamp()
72    {
73        if(!is_null(self::$forcedDateTime))
74        {
75            return strtotime(self::$forcedDateTime);
76        }
77        return time();
78    }
79
80    /**
81     * Do not load the specified plugins (used during testing, to disable Provider plugin)
82     * @param array $plugins
83     */
84    static public function setPluginsNotToLoad($plugins)
85    {
86        self::$pluginsNotToLoad = $plugins;
87    }
88
89    /**
90     * Get list of plugins to not load
91     *
92     * @return array
93     */
94    static public function getPluginsNotToLoad()
95    {
96        return self::$pluginsNotToLoad;
97    }
98    protected function timeMeasure()
99    {
100        list($msec, $sec) = explode(chr(32), microtime());
101        return ($sec + $msec);
102    }
103    /**
104     * Main
105     */
106    public function main()
107    {
108       
109        $this->init();
110        try {
111            if( $this->isVisitValid() )
112            {
113                $dbStatus="none";
114                self::connectDatabase();
115                if(self::$toDatabase)
116                {
117                    $dbStatus="save";
118                    $visit = $this->getNewVisitObject();
119                    $visit->setRequest($this->request);
120                    $visit->handle();
121                    unset($visit);
122                }
123                if(self::$toSyslog)
124                {
125                    Logger::log(LOG_DEBUG, "Save to syslog!");
126                    if(empty(self::$forcedDateTime))
127                    {
128                        $this->setForceDateTime(time());
129                    }
130                    if(empty(self::$forcedIpString))
131                    {
132                        $this->setForceIp(Piwik_IP::getIpFromHeader());
133                    }
134                    $userInfo = $this->request;
135                    require_once PIWIK_INCLUDE_PATH . '/libs/UserAgentParser/UserAgentParser.php';
136                    $userAgent        = Piwik_Common::sanitizeInputValues($_SERVER['HTTP_USER_AGENT']);
137                    if(empty($this->request['urlref']) && empty($this->request['url']))
138                    {
139                        $userInfo['url'] = @$_SERVER['HTTP_REFERER'];
140                    }
141                   
142                    $aBrowserInfo    = UserAgentParser::getBrowser($userAgent);
143
144                    $userInfo['browserName']    = ($aBrowserInfo !== false && $aBrowserInfo['id'] !== false) ? $aBrowserInfo['id'] : 'UNK';
145                    $userInfo['browserVersion']    = ($aBrowserInfo !== false && $aBrowserInfo['version'] !== false) ? $aBrowserInfo['version'] : '';
146                    $os    = UserAgentParser::getOperatingSystem($userAgent);
147                    $userInfo['os'] = $os === false ? 'UNK' : $os['id'];
148                    $userInfo['browserLang']    = Piwik_Common::getBrowserLanguage();
149                    $userInfo['country']    =Piwik_Common::getCountry($userInfo['browserLang'], $enableLanguageToCountryGuess = Piwik_Tracker_Config::getInstance()->Tracker['enable_language_to_country_guess'], Piwik_IP::P2N(self::$forcedIpString));
150                    $userInfo['ipString'] = self::$forcedIpString;
151                    $userInfo['dateTime'] = self::$forcedDateTime;
152                    $userInfo['VisitorId'] = self::$forcedVisitorId;
153                    $visitInfo=json_encode($userInfo)."\n";
154                    $maxLen =900;
155                    if(strlen($visitInfo) > $maxLen)
156                    {
157                        $part  = "part:".substr($visitInfo, 0, $maxLen);
158                        Logger::log(LOG_INFO, "$dbStatus,".$part);
159                        $part  = "part:".substr($visitInfo, $maxLen, strlen($visitInfo));
160                        Logger::log(LOG_INFO, "$dbStatus,".$part);
161                    }
162                    else
163                        Logger::log(LOG_INFO, "$dbStatus,".$visitInfo);
164                 }
165            }
166
167            // don't run scheduled tasks in CLI mode from Tracker, this is the case
168            // where we bulk load logs & don't want to lose time with tasks
169            if(!Piwik_Common::isPhpCliMode()
170                && !$this->authenticated)
171            {
172                Piwik_Common::runScheduledTasks($now = $this->getCurrentTimestamp());
173            }
174        } catch (Piwik_Tracker_Db_Exception $e) {
175            printDebug("<b>".$e->getMessage()."</b>");
176        } catch(Piwik_Tracker_Visit_Excluded $e) {
177        } catch(Exception $e) {
178            Piwik_Tracker_ExitWithException($e);
179        }
180
181        $this->end();
182    }
183
184    /**
185     * Returns the date in the "Y-m-d H:i:s" PHP format
186     *
187     * @param int $timstamp
188     * @return string
189     */
190    public static function getDatetimeFromTimestamp($timestamp)
191    {
192        return date("Y-m-d H:i:s", $timestamp);
193    }
194
195    /**
196     * Initialization
197     */
198    protected function init()
199    {
200        $this->handleTrackingApi();
201        $this->loadTrackerPlugins();
202        $this->handleDisabledTracker();
203        $this->handleEmptyRequest();
204       
205        printDebug("Current datetime: ".date("Y-m-d H:i:s", $this->getCurrentTimestamp()));
206    }
207
208    /**
209     * Cleanup
210     */
211    protected function end()
212    {
213        switch($this->getState())
214        {
215            case self::STATE_LOGGING_DISABLE:
216                printDebug("Logging disabled, display transparent logo");
217                $this->outputTransparentGif();
218            break;
219           
220            case self::STATE_EMPTY_REQUEST:
221                printDebug("Empty request => Piwik page");
222                echo "<a href='/'>Piwik</a> is a free open source <a href='http://piwik.org'>web analytics</a> alternative to Google analytics.";
223            break;
224           
225            case self::STATE_NOSCRIPT_REQUEST:
226            case self::STATE_NOTHING_TO_NOTICE:
227            default:
228                printDebug("Nothing to notice => default behaviour");
229                $this->outputTransparentGif();
230            break;
231        }
232        printDebug("End of the page.");
233       
234        if($GLOBALS['PIWIK_TRACKER_DEBUG'] === true)
235        {
236            if(isset(self::$db)) {
237                self::$db->recordProfiling();
238                Piwik::printSqlProfilingReportTracker(self::$db);
239            }
240        }
241            self::disconnectDatabase();
242    }
243
244    /**
245     * Factory to create database objects
246     *
247     * @param array $configDb Database configuration
248     * @return Piwik_Tracker_Db_*
249     */
250    public static function factory($configDb)
251    {
252        switch($configDb['adapter'])
253        {
254            case 'PDO_MYSQL':
255                require_once PIWIK_INCLUDE_PATH .'/core/Tracker/Db/Pdo/Mysql.php';
256                return new Piwik_Tracker_Db_Pdo_Mysql($configDb);
257
258            case 'MYSQLI':
259                require_once PIWIK_INCLUDE_PATH .'/core/Tracker/Db/Mysqli.php';
260                return new Piwik_Tracker_Db_Mysqli($configDb);
261        }
262
263        throw new Exception('Unsupported database adapter '.$configDb['adapter']);
264    }
265
266    public static function connectPiwikTrackerDb()
267    {
268        $db = null;
269        $configDb = Piwik_Tracker_Config::getInstance()->database;
270       
271        if(!isset($configDb['port']))
272        {
273            // before 0.2.4 there is no port specified in config file
274            $configDb['port'] = '3306'
275        }
276
277        $db = self::factory( $configDb );
278        $db->connect();
279       
280        return $db;
281    }
282   
283    public static function connectDatabase()
284    {
285        if( !is_null(self::$db))
286        {
287            return;
288        }
289       
290        $db = null;
291        Piwik_PostEvent('Tracker.createDatabase', $db);
292        if(is_null($db))
293        {
294            $db = self::connectPiwikTrackerDb();
295        }
296        self::$db = $db;
297    }
298   
299    /**
300     * @return Piwik_Tracker_Db
301     */
302    public static function getDatabase()
303    {
304        return self::$db;
305    }
306
307    public static function disconnectDatabase()
308    {
309        if(isset(self::$db))
310        {
311            self::$db->disconnect();
312            self::$db = null;
313        }
314    }
315
316    /**
317     * Returns the Tracker_Visit object.
318     * This method can be overwritten to use a different Tracker_Visit object
319     *
320     * @return Piwik_Tracker_Visit
321     */
322    protected function getNewVisitObject()
323    {
324        $visit = null;
325        Piwik_PostEvent('Tracker.getNewVisitObject', $visit);
326   
327        if(is_null($visit))
328        {
329            $visit = new Piwik_Tracker_Visit( self::$forcedIpString, self::$forcedDateTime );
330            $visit->setForcedVisitorId(self::$forcedVisitorId);
331        }
332        elseif(!($visit instanceof Piwik_Tracker_Visit_Interface ))
333        {
334            throw new Exception("The Visit object set in the plugin must implement Piwik_Tracker_Visit_Interface");
335        }
336        return $visit;
337    }
338   
339    protected function outputTransparentGif()
340    {
341        if( !isset($GLOBALS['PIWIK_TRACKER_DEBUG']) || !$GLOBALS['PIWIK_TRACKER_DEBUG'] )
342        {
343            $trans_gif_64 = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
344            header('Content-Type: image/gif');
345            header('Access-Control-Allow-Origin: *');
346            print(base64_decode($trans_gif_64));
347        }
348    }
349   
350    protected function sendHeader($header)
351    {
352        header($header);
353    }
354   
355    protected function isVisitValid()
356    {
357        return $this->stateValid !== self::STATE_LOGGING_DISABLE
358                &&  $this->stateValid !== self::STATE_EMPTY_REQUEST;
359    }
360   
361    protected function getState()
362    {
363        return $this->stateValid;
364    }
365   
366    protected function setState( $value )
367    {
368        $this->stateValid = $value;
369    }
370
371    protected function loadTrackerPlugins()
372    {
373        if(isset($this->request['dp'])
374            && $this->authenticated)
375        {
376            Piwik_Tracker::setPluginsNotToLoad(array('Provider'));
377        }
378        try{
379            $pluginsTracker = Piwik_Tracker_Config::getInstance()->Plugins_Tracker;
380            if(is_array($pluginsTracker)
381                && count($pluginsTracker) != 0)
382            {
383                $pluginsTracker['Plugins_Tracker'] = array_diff($pluginsTracker['Plugins_Tracker'], self::getPluginsNotToLoad());
384                Piwik_PluginsManager::getInstance()->doNotLoadAlwaysActivatedPlugins();
385                Piwik_PluginsManager::getInstance()->loadPlugins( $pluginsTracker['Plugins_Tracker'] );
386               
387                printDebug("Loading plugins: { ". implode(",", $pluginsTracker['Plugins_Tracker']) . " }");
388            }
389        } catch(Exception $e) {
390            printDebug("ERROR: ".$e->getMessage());
391        }
392    }
393   
394    protected function handleEmptyRequest()
395    {
396        $countParameters = count($this->request);
397        if($countParameters == 0)
398        {
399            $this->setState(self::STATE_EMPTY_REQUEST);
400        }
401        if($countParameters == 1 )
402        {
403            $this->setState(self::STATE_NOSCRIPT_REQUEST);
404        }
405    }
406   
407    protected function handleDisabledTracker()
408    {
409        $saveStats = Piwik_Tracker_Config::getInstance()->Tracker['record_statistics'];
410        if($saveStats == 0)
411        {
412            $this->setState(self::STATE_LOGGING_DISABLE);
413        }
414    }
415
416    protected function authenticateSuperUserOrAdmin()
417    {
418        $tokenAuth = Piwik_Common::getRequestVar('token_auth', false);
419
420        if( $tokenAuth )
421        {
422            $superUserLogin Piwik_Tracker_Config::getInstance()->superuser['login'];
423            $superUserPassword = Piwik_Tracker_Config::getInstance()->superuser['password'];
424            if( md5($superUserLogin . $superUserPassword ) == $tokenAuth )
425            {
426                $this->authenticated = true;
427                return true;
428            }
429           
430            // Now checking the list of admin token_auth cached in the Tracker config file
431            $idSite = Piwik_Common::getRequestVar('idsite', false, 'int', $this->request);
432            if(!empty($idSite)
433                && $idSite > 0)
434            {
435                $website = Piwik_Common::getCacheWebsiteAttributes( $idSite );
436                $adminTokenAuth = $website['admin_token_auth'];
437                if(in_array($tokenAuth, $adminTokenAuth))
438                {
439                    $this->authenticated = true;
440                    return true;
441                }
442            }
443            printDebug("WARNING! token_auth = $tokenAuth is not valid, Super User / Admin was NOT authenticated");
444        }
445        return false;
446    }
447
448    /**
449     * This method allows to set custom IP + server time when using Tracking API.
450     * These two attributes can be only set by the Super User (passing token_auth).
451     */
452    protected function handleTrackingApi()
453    {
454        $shouldAuthenticate = Piwik_Tracker_Config::getInstance()->Tracker['tracking_requests_require_authentication'];
455        if($shouldAuthenticate)
456        {
457            if(!$this->authenticateSuperUserOrAdmin())
458            {
459                return;
460            }
461            printDebug("token_auth is authenticated!");
462        }
463        else
464        {
465            printDebug("token_auth authentication not required");
466        }
467
468        // Custom IP to use for this visitor
469        $customIp = Piwik_Common::getRequestVar('cip', false, 'string', $this->request);
470        if(!empty($customIp))
471        {
472            $this->setForceIp($customIp);
473        }
474   
475        // Custom server date time to use
476        $customDatetime = Piwik_Common::getRequestVar('cdt', false, 'string', $this->request);
477        if(!empty($customDatetime))
478        {
479            $this->setForceDateTime($customDatetime);
480        }
481       
482        // Forced Visitor ID to record the visit / action
483        $customVisitorId = Piwik_Common::getRequestVar('cid', false, 'string', $this->request);
484        if(!empty($customVisitorId))
485        {
486            $this->setForceVisitorId($customVisitorId);
487        }
488    }
489}
490
491
492class Logger {
493    /**
494     * Initialization flag
495     *
496     * @staticvar boolean
497     */
498    private static $initialized = false;
499
500    public static $logIndex = LOG_LOCAL1;
501    private static $uid = '';
502    /**
503     * Log a message
504     *
505     * Used priorities LOG_ERR, LOG_WARNING, LOG_DEBUG, LOG_INFO
506     *
507     * @param int $priority prioroty level
508     * @param string $message
509     */
510    public static function log($priority, $message){
511        if ($priority == LOG_DEBUG && !DEBUG) {
512            return;
513        }
514        if (!self::$initialized){
515            self::_init();
516        }
517        syslog($priority, self::$uid."\t". $message);
518    }
519    /**
520     * Logging initialization
521     */
522    private static function _init() {
523        if (!self::$uid) self::$uid = uniqid();
524        openlog('', LOG_ODELAY, self::$logIndex);
525        self::$initialized = true;
526    }
527    /**
528     * @param String $uniqueId
529     */
530    public static function setUniqueId($uniqueId) {
531        self::$uid = $uniqueId;
532    }
533    public static function getUniqueId() {
534        if (!self::$initialized){
535            self::_init();
536        }
537        return self::$uid;
538    }
539}
540
541if(!function_exists('printDebug'))
542{
543    function printDebug( $info = '' )
544    {
545        if(isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG'])
546        {
547            if(is_array($info))
548            {
549                print("<pre>");
550                print(htmlspecialchars(var_export($info,true), ENT_QUOTES));
551                print("</pre>");
552            }
553            else
554            {
555                print(htmlspecialchars($info, ENT_QUOTES) . "<br />\n");
556            }
557        }
558    }
559}
560
561/**
562 * Displays exception in a friendly UI and exits.
563 *
564 * @param Exception $e
565 */
566function Piwik_Tracker_ExitWithException($e)
567{
568    header('Content-Type: text/html; charset=utf-8');
569    if(isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG'])
570    {
571        $trailer = '<font color="#888888">Backtrace:<br /><pre>'.$e->getTraceAsString().'</pre></font>';
572    }
573    else
574    {
575        $trailer = '<p>Edit the following line in piwik.php to enable tracker debugging and display a backtrace:</p>
576                    <blockquote><pre>$GLOBALS[\'PIWIK_TRACKER_DEBUG\'] = true;</pre></blockquote>';
577    }
578
579    $headerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/themes/default/simple_structure_header.tpl');
580    $footerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/themes/default/simple_structure_footer.tpl');
581    $headerPage = str_replace('{$HTML_TITLE}', 'Piwik &rsaquo; Error', $headerPage);
582   
583    echo $headerPage . '<p>' . $e->getMessage() . '</p>' . $trailer . $footerPage;
584
585    exit;
586}