PageRenderTime 136ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 1ms

/core/Common.php

https://github.com/quarkness/piwik
PHP | 1761 lines | 1052 code | 150 blank | 559 comment | 161 complexity | 43301ac6601e5dd37107e879bd8565fc MD5 | raw file
  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$
  8. *
  9. * @category Piwik
  10. * @package Piwik
  11. */
  12. /**
  13. * Static class providing functions used by both the CORE of Piwik and the visitor Tracking engine.
  14. *
  15. * This is the only external class loaded by the /piwik.php file.
  16. * This class should contain only the functions that are used in
  17. * both the CORE and the piwik.php statistics logging engine.
  18. *
  19. * @package Piwik
  20. */
  21. class Piwik_Common
  22. {
  23. /**
  24. * Const used to map the referer type to an integer in the log_visit table
  25. */
  26. const REFERER_TYPE_DIRECT_ENTRY = 1;
  27. const REFERER_TYPE_SEARCH_ENGINE = 2;
  28. const REFERER_TYPE_WEBSITE = 3;
  29. const REFERER_TYPE_CAMPAIGN = 6;
  30. /**
  31. * Flag used with htmlspecialchar
  32. * See php.net/htmlspecialchars
  33. */
  34. const HTML_ENCODING_QUOTE_STYLE = ENT_QUOTES;
  35. /*
  36. * Database
  37. */
  38. /**
  39. * Hashes a string into an integer which should be very low collision risks
  40. * @param string $string String to hash
  41. * @return int Resulting int hash
  42. */
  43. static public function hashStringToInt($string)
  44. {
  45. $stringHash = substr(md5($string), 0, 8);
  46. return base_convert($stringHash, 16, 10);
  47. }
  48. /**
  49. * Returns the table name prefixed by the table prefix.
  50. * Works in both Tracker and UI mode.
  51. *
  52. * @param string The table name to prefix, ie "log_visit"
  53. * @return string The table name prefixed, ie "piwik-production_log_visit"
  54. */
  55. static public function prefixTable($table)
  56. {
  57. static $prefixTable = null;
  58. if(is_null($prefixTable))
  59. {
  60. if(!empty($GLOBALS['PIWIK_TRACKER_MODE']))
  61. {
  62. $prefixTable = Piwik_Tracker_Config::getInstance()->database['tables_prefix'];
  63. }
  64. else
  65. {
  66. $config = Zend_Registry::get('config');
  67. if($config !== false)
  68. {
  69. $prefixTable = $config->database->tables_prefix;
  70. }
  71. }
  72. }
  73. return $prefixTable . $table;
  74. }
  75. /*
  76. * Tracker
  77. */
  78. static protected function initCorePiwikInTrackerMode()
  79. {
  80. static $init = false;
  81. if(!empty($GLOBALS['PIWIK_TRACKER_MODE'])
  82. && $init === false)
  83. {
  84. $init = true;
  85. require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
  86. require_once PIWIK_INCLUDE_PATH . '/core/Translate.php';
  87. require_once PIWIK_INCLUDE_PATH . '/core/Option.php';
  88. try {
  89. $access = Zend_Registry::get('access');
  90. } catch (Exception $e) {
  91. Piwik::createAccessObject();
  92. }
  93. try {
  94. $config = Zend_Registry::get('config');
  95. } catch (Exception $e) {
  96. Piwik::createConfigObject();
  97. }
  98. try {
  99. $db = Zend_Registry::get('db');
  100. } catch (Exception $e) {
  101. Piwik::createDatabaseObject();
  102. }
  103. $pluginsManager = Piwik_PluginsManager::getInstance();
  104. $pluginsToLoad = Zend_Registry::get('config')->Plugins->Plugins->toArray();
  105. $pluginsForcedNotToLoad = Piwik_Tracker::getPluginsNotToLoad();
  106. $pluginsToLoad = array_diff($pluginsToLoad, $pluginsForcedNotToLoad);
  107. $pluginsManager->loadPlugins( $pluginsToLoad );
  108. }
  109. }
  110. static public function isGoalPluginEnabled()
  111. {
  112. return Piwik_PluginsManager::getInstance()->isPluginActivated('Goals');
  113. }
  114. /*
  115. * File-based Cache
  116. */
  117. /**
  118. * @var Piwik_CacheFile
  119. */
  120. static $trackerCache = null;
  121. static protected function getTrackerCache()
  122. {
  123. if(is_null(self::$trackerCache))
  124. {
  125. self::$trackerCache = new Piwik_CacheFile('tracker');
  126. }
  127. return self::$trackerCache;
  128. }
  129. /**
  130. * Returns array containing data about the website: goals, URLs, etc.
  131. *
  132. * @param int $idSite
  133. * @return array
  134. */
  135. static function getCacheWebsiteAttributes( $idSite )
  136. {
  137. $cache = self::getTrackerCache();
  138. if(($cacheContent = $cache->get($idSite)) !== false)
  139. {
  140. return $cacheContent;
  141. }
  142. self::initCorePiwikInTrackerMode();
  143. // save current user privilege and temporarily assume super user privilege
  144. $isSuperUser = Piwik::isUserIsSuperUser();
  145. Piwik::setUserIsSuperUser();
  146. $content = array();
  147. Piwik_PostEvent('Common.fetchWebsiteAttributes', $content, $idSite);
  148. // restore original user privilege
  149. Piwik::setUserIsSuperUser($isSuperUser);
  150. // if nothing is returned from the plugins, we don't save the content
  151. // this is not expected: all websites are expected to have at least one URL
  152. if(!empty($content))
  153. {
  154. $cache->set($idSite, $content);
  155. }
  156. return $content;
  157. }
  158. /**
  159. * Clear general (global) cache
  160. */
  161. static public function clearCacheGeneral()
  162. {
  163. self::getTrackerCache()->delete('general');
  164. }
  165. /**
  166. * Returns contents of general (global) cache
  167. * @return array
  168. */
  169. static protected function getCacheGeneral()
  170. {
  171. $cache = self::getTrackerCache();
  172. $cacheId = 'general';
  173. $expectedRows = 2;
  174. if(($cacheContent = $cache->get($cacheId)) !== false
  175. && count($cacheContent) == $expectedRows)
  176. {
  177. return $cacheContent;
  178. }
  179. self::initCorePiwikInTrackerMode();
  180. $cacheContent = array (
  181. 'isBrowserTriggerArchivingEnabled' => Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled(),
  182. 'lastTrackerCronRun' => Piwik_GetOption('lastTrackerCronRun')
  183. );
  184. return $cacheContent;
  185. }
  186. /**
  187. * Store data in general (global cache)
  188. *
  189. * @param mixed $value
  190. */
  191. static protected function setCacheGeneral($value)
  192. {
  193. $cache = self::getTrackerCache();
  194. $cacheId = 'general';
  195. $cache->set($cacheId, $value);
  196. return true;
  197. }
  198. /**
  199. * Regenerate Tracker cache files
  200. *
  201. * @param array $idSites Array of idSites to clear cache for
  202. */
  203. static public function regenerateCacheWebsiteAttributes($idSites = array())
  204. {
  205. if(!is_array($idSites))
  206. {
  207. $idSites = array( $idSites );
  208. }
  209. foreach($idSites as $idSite) {
  210. self::deleteCacheWebsiteAttributes($idSite);
  211. self::getCacheWebsiteAttributes($idSite);
  212. }
  213. }
  214. /**
  215. * Delete existing Tracker cache
  216. *
  217. * @param string $idSite (website ID of the site to clear cache for
  218. */
  219. static public function deleteCacheWebsiteAttributes( $idSite )
  220. {
  221. $cache = new Piwik_CacheFile('tracker');
  222. $cache->delete($idSite);
  223. }
  224. /**
  225. * Deletes all Tracker cache files
  226. */
  227. static public function deleteTrackerCache()
  228. {
  229. $cache = new Piwik_CacheFile('tracker');
  230. $cache->deleteAll();
  231. }
  232. /**
  233. * Tracker requests will automatically trigger the Scheduled tasks.
  234. * This is useful for users who don't setup the cron,
  235. * but still want daily/weekly/monthly PDF reports emailed automatically.
  236. *
  237. * This is similar to calling the API CoreAdminHome.runScheduledTasks (see misc/cron/archive.sh)
  238. *
  239. * @param int $now Current timestamp
  240. */
  241. public static function runScheduledTasks($now)
  242. {
  243. // Currently, there is no hourly tasks. When there are some,
  244. // this could be too agressive minimum interval (some hours would be skipped in case of low traffic)
  245. $minimumInterval = Piwik_Tracker_Config::getInstance()->Tracker['scheduled_tasks_min_interval'];
  246. // If the user disabled browser archiving, he has already setup a cron
  247. // To avoid parallel requests triggering the Scheduled Tasks,
  248. // Get last time tasks started executing
  249. $cache = Piwik_Common::getCacheGeneral();
  250. if($minimumInterval <= 0
  251. || empty($cache['isBrowserTriggerArchivingEnabled']))
  252. {
  253. printDebug("-> Scheduled tasks not running in Tracker: Browser archiving is disabled.");
  254. return;
  255. }
  256. $nextRunTime = $cache['lastTrackerCronRun'] + $minimumInterval;
  257. if( (isset($GLOBALS['PIWIK_TRACKER_DEBUG_FORCE_SCHEDULED_TASKS']) && $GLOBALS['PIWIK_TRACKER_DEBUG_FORCE_SCHEDULED_TASKS'])
  258. || $cache['lastTrackerCronRun'] === false
  259. || $nextRunTime < $now )
  260. {
  261. $cache['lastTrackerCronRun'] = $now;
  262. Piwik_Common::setCacheGeneral( $cache );
  263. Piwik_Common::initCorePiwikInTrackerMode();
  264. Piwik_SetOption('lastTrackerCronRun', $cache['lastTrackerCronRun']);
  265. printDebug('-> Scheduled Tasks: Starting...');
  266. // save current user privilege and temporarily assume super user privilege
  267. $isSuperUser = Piwik::isUserIsSuperUser();
  268. // Scheduled tasks assume Super User is running
  269. Piwik::setUserIsSuperUser();
  270. // While each plugins should ensure that necessary languages are loaded,
  271. // we ensure English translations at least are loaded
  272. Piwik_Translate::getInstance()->loadEnglishTranslation();
  273. $resultTasks = Piwik_TaskScheduler::runTasks();
  274. // restore original user privilege
  275. Piwik::setUserIsSuperUser($isSuperUser);
  276. printDebug($resultTasks);
  277. printDebug('Finished Scheduled Tasks.');
  278. }
  279. else
  280. {
  281. printDebug("-> Scheduled tasks not triggered.");
  282. }
  283. printDebug("Next run will be from: ". date('Y-m-d H:i:s', $nextRunTime) .' UTC');
  284. }
  285. /*
  286. * URLs
  287. */
  288. /**
  289. * Returns the path and query part from a URL.
  290. * Eg. http://piwik.org/test/index.php?module=CoreHome will return /test/index.php?module=CoreHome
  291. *
  292. * @param string $url either http://piwik.org/test or /
  293. * @return string
  294. */
  295. static function getPathAndQueryFromUrl($url)
  296. {
  297. $parsedUrl = parse_url( $url );
  298. $result = '';
  299. if(isset($parsedUrl['path']))
  300. {
  301. $result .= substr($parsedUrl['path'], 1);
  302. }
  303. if(isset($parsedUrl['query']))
  304. {
  305. $result .= '?'.$parsedUrl['query'];
  306. }
  307. return $result;
  308. }
  309. /**
  310. * Returns the value of a GET parameter $parameter in an URL query $urlQuery
  311. *
  312. * @param string $urlQuery result of parse_url()['query'] and htmlentitied (& is &amp;) eg. module=test&amp;action=toto or ?page=test
  313. * @param string $parameter
  314. * @return string|bool Parameter value if found (can be the empty string!), null if not found
  315. */
  316. static public function getParameterFromQueryString( $urlQuery, $parameter)
  317. {
  318. $nameToValue = self::getArrayFromQueryString($urlQuery);
  319. if(isset($nameToValue[$parameter]))
  320. {
  321. return $nameToValue[$parameter];
  322. }
  323. return null;
  324. }
  325. /**
  326. * Returns an URL query string in an array format
  327. *
  328. * @param string urlQuery
  329. * @return array array( param1=> value1, param2=>value2)
  330. */
  331. static public function getArrayFromQueryString( $urlQuery )
  332. {
  333. if(strlen($urlQuery) == 0)
  334. {
  335. return array();
  336. }
  337. if($urlQuery[0] == '?')
  338. {
  339. $urlQuery = substr($urlQuery, 1);
  340. }
  341. $separator = '&';
  342. $urlQuery = $separator . $urlQuery;
  343. // $urlQuery = str_replace(array('%20'), ' ', $urlQuery);
  344. $refererQuery = trim($urlQuery);
  345. $values = explode($separator, $refererQuery);
  346. $nameToValue = array();
  347. foreach($values as $value)
  348. {
  349. $pos = strpos($value, '=');
  350. if($pos !== false)
  351. {
  352. $name = substr($value, 0, $pos);
  353. $value = substr($value, $pos+1);
  354. if ($value === false)
  355. {
  356. $value = '';
  357. }
  358. }
  359. else
  360. {
  361. $name = $value;
  362. $value = false;
  363. }
  364. // if array without indexes
  365. $count = 0;
  366. $tmp = preg_replace('/(\[|%5b)(]|%5d)$/i', '', $name, -1, $count);
  367. if(!empty($tmp) && $count)
  368. {
  369. $name = $tmp;
  370. if( isset($nameToValue[$name]) == false || is_array($nameToValue[$name]) == false )
  371. {
  372. $nameToValue[$name] = array();
  373. }
  374. array_push($nameToValue[$name], $value);
  375. }
  376. else if(!empty($name))
  377. {
  378. $nameToValue[$name] = $value;
  379. }
  380. }
  381. return $nameToValue;
  382. }
  383. /**
  384. * Builds a URL from the result of parse_url function
  385. * Copied from the PHP comments at http://php.net/parse_url
  386. * @param array
  387. */
  388. static public function getParseUrlReverse($parsed)
  389. {
  390. if (!is_array($parsed))
  391. {
  392. return false;
  393. }
  394. $uri = !empty($parsed['scheme']) ? $parsed['scheme'].':'.(!strcasecmp($parsed['scheme'], 'mailto') ? '' : '//') : '';
  395. $uri .= !empty($parsed['user']) ? $parsed['user'].(!empty($parsed['pass']) ? ':'.$parsed['pass'] : '').'@' : '';
  396. $uri .= !empty($parsed['host']) ? $parsed['host'] : '';
  397. $uri .= !empty($parsed['port']) ? ':'.$parsed['port'] : '';
  398. if (!empty($parsed['path']))
  399. {
  400. $uri .= (!strncmp($parsed['path'], '/', 1))
  401. ? $parsed['path']
  402. : ((!empty($uri) ? '/' : '' ) . $parsed['path']);
  403. }
  404. $uri .= !empty($parsed['query']) ? '?'.$parsed['query'] : '';
  405. $uri .= !empty($parsed['fragment']) ? '#'.$parsed['fragment'] : '';
  406. return $uri;
  407. }
  408. /**
  409. * Returns true if the string passed may be a URL.
  410. * We don't need a precise test here because the value comes from the website
  411. * tracked source code and the URLs may look very strange.
  412. *
  413. * @param string $url
  414. * @return bool
  415. */
  416. static function isLookLikeUrl( $url )
  417. {
  418. return preg_match('~^(ftp|news|http|https)?://(.*)$~D', $url, $matches) !== 0
  419. && strlen($matches[2]) > 0;
  420. }
  421. /*
  422. * File operations
  423. */
  424. /**
  425. * ending WITHOUT slash
  426. * @return string
  427. */
  428. static public function getPathToPiwikRoot()
  429. {
  430. return realpath( dirname(__FILE__). "/.." );
  431. }
  432. /**
  433. * Create directory if permitted
  434. *
  435. * @param string $path
  436. * @param bool $denyAccess
  437. */
  438. static public function mkdir( $path, $denyAccess = true )
  439. {
  440. if(!is_dir($path))
  441. {
  442. // the mode in mkdir is modified by the current umask
  443. @mkdir($path, $mode = 0755, $recursive = true);
  444. }
  445. // try to overcome restrictive umask (mis-)configuration
  446. if(!is_writable($path))
  447. {
  448. @chmod($path, 0755);
  449. if(!is_writable($path))
  450. {
  451. @chmod($path, 0775);
  452. // enough! we're not going to make the directory world-writeable
  453. }
  454. }
  455. if($denyAccess)
  456. {
  457. self::createHtAccess($path);
  458. }
  459. }
  460. /**
  461. * Create .htaccess file in specified directory
  462. *
  463. * Apache-specific; for IIS @see web.config
  464. *
  465. * @param string $path without trailing slash
  466. * @param string $content
  467. */
  468. static public function createHtAccess( $path, $content = "<Files \"*\">\nDeny from all\n</Files>\n" )
  469. {
  470. if(self::isApache())
  471. {
  472. @file_put_contents($path . '/.htaccess', $content);
  473. }
  474. }
  475. /**
  476. * Get canonicalized absolute path
  477. * See http://php.net/realpath
  478. *
  479. * @param string $path
  480. * @return string canonicalized absolute path
  481. */
  482. static public function realpath($path)
  483. {
  484. if (file_exists($path))
  485. {
  486. return realpath($path);
  487. }
  488. return $path;
  489. }
  490. /**
  491. * Returns true if the string is a valid filename
  492. * File names that start with a-Z or 0-9 and contain a-Z, 0-9, underscore(_), dash(-), and dot(.) will be accepted.
  493. * File names beginning with anything but a-Z or 0-9 will be rejected (including .htaccess for example).
  494. * File names containing anything other than above mentioned will also be rejected (file names with spaces won't be accepted).
  495. *
  496. * @param string filename
  497. * @return bool
  498. *
  499. */
  500. static public function isValidFilename($filename)
  501. {
  502. return (0 !== preg_match('/(^[a-zA-Z0-9]+([a-zA-Z_0-9.-]*))$/D', $filename));
  503. }
  504. /*
  505. * String operations
  506. */
  507. /**
  508. * byte-oriented substr() - ASCII
  509. *
  510. * @param string $string
  511. * @param int $start
  512. * @param int $length optional length
  513. * @return string
  514. */
  515. static public function substr($string, $start)
  516. {
  517. // in case mbstring overloads substr function
  518. $substr = function_exists('mb_orig_substr') ? 'mb_orig_substr' : 'substr';
  519. $length = func_num_args() > 2
  520. ? func_get_arg(2)
  521. : self::strlen($string);
  522. return $substr($string, $start, $length);
  523. }
  524. /**
  525. * byte-oriented strlen() - ASCII
  526. *
  527. * @param string $string
  528. * @return int
  529. */
  530. static public function strlen($string)
  531. {
  532. // in case mbstring overloads strlen function
  533. $strlen = function_exists('mb_orig_strlen') ? 'mb_orig_strlen' : 'strlen';
  534. return $strlen($string);
  535. }
  536. /**
  537. * multi-byte substr() - UTF-8
  538. *
  539. * @param string $string
  540. * @param int $start
  541. * @param int $length optional length
  542. * @return string
  543. */
  544. static public function mb_substr($string, $start)
  545. {
  546. $length = func_num_args() > 2
  547. ? func_get_arg(2)
  548. : self::mb_strlen($string);
  549. if (function_exists('mb_substr'))
  550. {
  551. return mb_substr($string, $start, $length, 'UTF-8');
  552. }
  553. return substr($string, $start, $length);
  554. }
  555. /**
  556. * multi-byte strlen() - UTF-8
  557. *
  558. * @param string $string
  559. * @return int
  560. */
  561. static public function mb_strlen($string)
  562. {
  563. if (function_exists('mb_strlen'))
  564. {
  565. return mb_strlen($string, 'UTF-8');
  566. }
  567. return strlen($string);
  568. }
  569. /*
  570. * Escaping input
  571. */
  572. /**
  573. * Returns the variable after cleaning operations.
  574. * NB: The variable still has to be escaped before going into a SQL Query!
  575. *
  576. * If an array is passed the cleaning is done recursively on all the sub-arrays.
  577. * The array's keys are filtered as well!
  578. *
  579. * How this method works:
  580. * - The variable returned has been htmlspecialchars to avoid the XSS security problem.
  581. * - The single quotes are not protected so "Piwik's amazing" will still be "Piwik's amazing".
  582. *
  583. * - Transformations are:
  584. * - '&' (ampersand) becomes '&amp;'
  585. * - '"'(double quote) becomes '&quot;'
  586. * - '<' (less than) becomes '&lt;'
  587. * - '>' (greater than) becomes '&gt;'
  588. * - It handles the magic_quotes setting.
  589. * - A non string value is returned without modification
  590. *
  591. * @param mixed The variable to be cleaned
  592. * @return mixed The variable after cleaning
  593. */
  594. static public function sanitizeInputValues($value)
  595. {
  596. if(is_numeric($value))
  597. {
  598. return $value;
  599. }
  600. elseif(is_string($value))
  601. {
  602. $value = self::sanitizeInputValue($value);
  603. // Undo the damage caused by magic_quotes; deprecated in php 5.3 but not removed until php 5.4
  604. if(version_compare(PHP_VERSION, '5.4', '<')
  605. && get_magic_quotes_gpc())
  606. {
  607. $value = stripslashes($value);
  608. }
  609. }
  610. elseif (is_array($value))
  611. {
  612. foreach (array_keys($value) as $key)
  613. {
  614. $newKey = $key;
  615. $newKey = self::sanitizeInputValues($newKey);
  616. if ($key != $newKey)
  617. {
  618. $value[$newKey] = $value[$key];
  619. unset($value[$key]);
  620. }
  621. $value[$newKey] = self::sanitizeInputValues($value[$newKey]);
  622. }
  623. }
  624. elseif( !is_null($value)
  625. && !is_bool($value))
  626. {
  627. throw new Exception("The value to escape has not a supported type. Value = ".var_export($value, true));
  628. }
  629. return $value;
  630. }
  631. /**
  632. * Sanitize a single input value
  633. *
  634. * @param string $value
  635. * @return string sanitized input
  636. */
  637. static public function sanitizeInputValue($value)
  638. {
  639. // $_GET and $_REQUEST already urldecode()'d
  640. // decode
  641. // note: before php 5.2.7, htmlspecialchars() double encodes &#x hex items
  642. $value = html_entity_decode($value, Piwik_Common::HTML_ENCODING_QUOTE_STYLE, 'UTF-8');
  643. // filter
  644. $value = str_replace(array("\n", "\r", "\0"), '', $value);
  645. // escape
  646. $tmp = @htmlspecialchars( $value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8' );
  647. // note: php 5.2.5 and above, htmlspecialchars is destructive if input is not UTF-8
  648. if($value != '' && $tmp == '')
  649. {
  650. // convert and escape
  651. $value = utf8_encode($value);
  652. $tmp = htmlspecialchars( $value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8' );
  653. }
  654. return $tmp;
  655. }
  656. /**
  657. * Unsanitize a single input value
  658. *
  659. * @param string $value
  660. * @return string unsanitized input
  661. */
  662. static public function unsanitizeInputValue($value)
  663. {
  664. return htmlspecialchars_decode($value, self::HTML_ENCODING_QUOTE_STYLE);
  665. }
  666. /**
  667. * Returns a sanitized variable value from the $_GET and $_POST superglobal.
  668. * If the variable doesn't have a value or an empty value, returns the defaultValue if specified.
  669. * If the variable doesn't have neither a value nor a default value provided, an exception is raised.
  670. *
  671. * @see sanitizeInputValues() for the applied sanitization
  672. *
  673. * @param string $varName name of the variable
  674. * @param string $varDefault default value. If '', and if the type doesn't match, exit() !
  675. * @param string $varType Expected type, the value must be one of the following: array, int, integer, string
  676. * @param array $requestArrayToUse
  677. *
  678. * @exception if the variable type is not known
  679. * @exception if the variable we want to read doesn't have neither a value nor a default value specified
  680. *
  681. * @return mixed The variable after cleaning
  682. */
  683. static public function getRequestVar($varName, $varDefault = null, $varType = null, $requestArrayToUse = null)
  684. {
  685. if(is_null($requestArrayToUse))
  686. {
  687. $requestArrayToUse = $_GET + $_POST;
  688. }
  689. $varDefault = self::sanitizeInputValues( $varDefault );
  690. if($varType === 'int')
  691. {
  692. // settype accepts only integer
  693. // 'int' is simply a shortcut for 'integer'
  694. $varType = 'integer';
  695. }
  696. // there is no value $varName in the REQUEST so we try to use the default value
  697. if(empty($varName)
  698. || !isset($requestArrayToUse[$varName])
  699. || ( !is_array($requestArrayToUse[$varName])
  700. && strlen($requestArrayToUse[$varName]) === 0
  701. )
  702. )
  703. {
  704. if( is_null($varDefault))
  705. {
  706. throw new Exception("The parameter '$varName' isn't set in the Request, and a default value wasn't provided.");
  707. }
  708. else
  709. {
  710. if( !is_null($varType)
  711. && in_array($varType, array('string', 'integer', 'array'))
  712. )
  713. {
  714. settype($varDefault, $varType);
  715. }
  716. return $varDefault;
  717. }
  718. }
  719. // Normal case, there is a value available in REQUEST for the requested varName
  720. $value = self::sanitizeInputValues( $requestArrayToUse[$varName] );
  721. if( !is_null($varType))
  722. {
  723. $ok = false;
  724. if($varType === 'string')
  725. {
  726. if(is_string($value)) $ok = true;
  727. }
  728. elseif($varType === 'integer')
  729. {
  730. if($value == (string)(int)$value) $ok = true;
  731. }
  732. elseif($varType === 'float')
  733. {
  734. if($value == (string)(float)$value) $ok = true;
  735. }
  736. elseif($varType === 'array')
  737. {
  738. if(is_array($value)) $ok = true;
  739. }
  740. else
  741. {
  742. throw new Exception("\$varType specified is not known. It should be one of the following: array, int, integer, float, string");
  743. }
  744. // The type is not correct
  745. if($ok === false)
  746. {
  747. if($varDefault === null)
  748. {
  749. throw new Exception("The parameter '$varName' doesn't have a correct type, and a default value wasn't provided.");
  750. }
  751. // we return the default value with the good type set
  752. else
  753. {
  754. settype($varDefault, $varType);
  755. return $varDefault;
  756. }
  757. }
  758. settype($value, $varType);
  759. }
  760. return $value;
  761. }
  762. /*
  763. * Generating unique strings
  764. */
  765. /**
  766. * Returns a 32 characters long uniq ID
  767. *
  768. * @return string 32 chars
  769. */
  770. static public function generateUniqId()
  771. {
  772. return md5(uniqid(rand(), true));
  773. }
  774. /**
  775. * Get salt from [superuser] section
  776. *
  777. * @return string
  778. */
  779. static public function getSalt()
  780. {
  781. static $salt = null;
  782. if(is_null($salt))
  783. {
  784. if(!empty($GLOBALS['PIWIK_TRACKER_MODE']))
  785. {
  786. $salt = @Piwik_Tracker_Config::getInstance()->superuser['salt'];
  787. }
  788. else
  789. {
  790. $config = Zend_Registry::get('config');
  791. if($config !== false)
  792. {
  793. $salt = @$config->superuser->salt;
  794. }
  795. }
  796. }
  797. return $salt;
  798. }
  799. /**
  800. * Configureable hash() algorithm (defaults to md5)
  801. *
  802. * @param string $str String to be hashed
  803. * @param bool $raw_output
  804. * @return string Hash string
  805. */
  806. static function hash($str, $raw_output = false)
  807. {
  808. static $hashAlgorithm = null;
  809. if(is_null($hashAlgorithm))
  810. {
  811. if(!empty($GLOBALS['PIWIK_TRACKER_MODE']))
  812. {
  813. $hashAlgorithm = @Piwik_Tracker_Config::getInstance()->General['hash_algorithm'];
  814. }
  815. else
  816. {
  817. $config = Zend_Registry::get('config');
  818. if($config !== false)
  819. {
  820. $hashAlgorithm = @$config->General->hash_algorithm;
  821. }
  822. }
  823. }
  824. if($hashAlgorithm)
  825. {
  826. $hash = @hash($hashAlgorithm, $str, $raw_output);
  827. if($hash !== false)
  828. return $hash;
  829. }
  830. return md5($str, $raw_output);
  831. }
  832. /**
  833. * Returns the list of Campaign parameter names that will be read to classify
  834. * a visit as coming from a Campaign
  835. *
  836. * @return array array(
  837. * 0 => array( ... ) // campaign names parameters
  838. * 1 => array( ... ) // campaign keyword parameters
  839. * );
  840. */
  841. static public function getCampaignParameters()
  842. {
  843. if(!empty($GLOBALS['PIWIK_TRACKER_MODE']))
  844. {
  845. $return = array(
  846. Piwik_Tracker_Config::getInstance()->Tracker['campaign_var_name'],
  847. Piwik_Tracker_Config::getInstance()->Tracker['campaign_keyword_var_name'],
  848. );
  849. }
  850. else
  851. {
  852. $return = array(
  853. Zend_Registry::get('config')->Tracker->campaign_var_name,
  854. Zend_Registry::get('config')->Tracker->campaign_keyword_var_name,
  855. );
  856. }
  857. foreach($return as &$list)
  858. {
  859. if(strpos($list, ',') !== false)
  860. {
  861. $list = explode(',', $list);
  862. }
  863. else
  864. {
  865. $list = array($list);
  866. }
  867. }
  868. array_walk_recursive($return, 'trim');
  869. return $return;
  870. }
  871. /**
  872. * Generate random string
  873. *
  874. * @param string $length string length
  875. * @param string $alphabet characters allowed in random string
  876. * @return string random string with given length
  877. */
  878. public static function getRandomString($length = 16, $alphabet = "abcdefghijklmnoprstuvwxyz0123456789")
  879. {
  880. $chars = $alphabet;
  881. $str = '';
  882. list($usec, $sec) = explode(" ", microtime());
  883. $seed = ((float)$sec+(float)$usec)*100000;
  884. mt_srand($seed);
  885. for($i = 0; $i < $length; $i++)
  886. {
  887. $rand_key = mt_rand(0, strlen($chars)-1);
  888. $str .= substr($chars, $rand_key, 1);
  889. }
  890. return str_shuffle($str);
  891. }
  892. /*
  893. * Conversions
  894. */
  895. /**
  896. * Convert hexadecimal representation into binary data.
  897. * !! Will emit warning if input string is not hex!!
  898. *
  899. * @see http://php.net/bin2hex
  900. *
  901. * @param string $str Hexadecimal representation
  902. * @return string
  903. */
  904. static public function hex2bin($str)
  905. {
  906. return pack("H*" , $str);
  907. }
  908. /**
  909. * This function will convert the input string to the binary representation of the ID
  910. * but it will throw an Exception if the specified input ID is not correct
  911. *
  912. * This is used when building segments containing visitorId which could be an invalid string
  913. * therefore throwing Unexpected PHP error [pack(): Type H: illegal hex digit i] severity [E_WARNING]
  914. *
  915. * It would be simply to silent fail the pack() call above but in all other cases, we don't expect an error,
  916. * so better be safe and get the php error when something unexpected is happening
  917. * @param string $id
  918. * @return binary string
  919. */
  920. static public function convertVisitorIdToBin($id)
  921. {
  922. if(strlen($id) !== Piwik_Tracker::LENGTH_HEX_ID_STRING
  923. || @bin2hex(self::hex2bin($id)) != $id)
  924. {
  925. throw new Exception("visitorId is expected to be a ".Piwik_Tracker::LENGTH_HEX_ID_STRING." hex char string");
  926. }
  927. return self::hex2bin($id);
  928. }
  929. /**
  930. * Convert IP address (in network address format) to presentation format.
  931. * This is a backward compatibility function for code that only expects
  932. * IPv4 addresses (i.e., doesn't support IPv6).
  933. *
  934. * @see Piwik_IP::N2P()
  935. *
  936. * This function does not support the long (or its string representation)
  937. * returned by the built-in ip2long() function, from Piwik 1.3 and earlier.
  938. *
  939. * @deprecated 1.4
  940. *
  941. * @param string $ip IP address in network address format
  942. * @return string
  943. */
  944. static public function long2ip($ip)
  945. {
  946. return Piwik_IP::long2ip($ip);
  947. }
  948. /**
  949. * Should we use the replacement json_encode/json_decode functions?
  950. *
  951. * @return bool True if broken; false otherwise
  952. */
  953. static private function useJsonLibrary()
  954. {
  955. static $useLib;
  956. if (!isset($useLib))
  957. {
  958. /*
  959. * 5.1.x - doesn't have json extension; we use lib/upgradephp instead
  960. * 5.2 to 5.2.4 - broken in various ways, including:
  961. *
  962. * @see https://bugs.php.net/bug.php?id=38680 'json_decode cannot decode basic types'
  963. * @see https://bugs.php.net/bug.php?id=41403 'json_decode cannot decode floats'
  964. * @see https://bugs.php.net/bug.php?id=42785 'json_encode outputs numbers according to locale'
  965. */
  966. $useLib = false;
  967. if (version_compare(PHP_VERSION, '5.2.1') < 0)
  968. {
  969. $useLib = true;
  970. }
  971. else if (version_compare(PHP_VERSION, '5.2.5') < 0)
  972. {
  973. $info = localeconv();
  974. $useLib = $info['decimal_point'] != '.';
  975. }
  976. }
  977. return $useLib;
  978. }
  979. /**
  980. * JSON encode wrapper
  981. * - missing or broken in some php 5.x versions
  982. *
  983. * @param mixed $value
  984. * @return string
  985. */
  986. static public function json_encode($value)
  987. {
  988. if (self::useJsonLibrary())
  989. {
  990. return _json_encode($value);
  991. }
  992. return json_encode($value);
  993. }
  994. /**
  995. * JSON decode wrapper
  996. * - missing or broken in some php 5.x versions
  997. *
  998. * @param string $json
  999. * @param bool $assoc
  1000. * @return mixed
  1001. */
  1002. static public function json_decode($json, $assoc = false)
  1003. {
  1004. if (self::useJsonLibrary())
  1005. {
  1006. return _json_decode($json, $assoc);
  1007. }
  1008. return json_decode($json, $assoc);
  1009. }
  1010. /*
  1011. * DataFiles
  1012. */
  1013. /**
  1014. * Returns list of valid country codes
  1015. *
  1016. * @see core/DataFiles/Countries.php
  1017. *
  1018. * @return array Array of 2 letter ISO codes
  1019. */
  1020. static public function getContinentsList()
  1021. {
  1022. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php';
  1023. $continentsList = $GLOBALS['Piwik_ContinentList'];
  1024. return $continentsList;
  1025. }
  1026. /**
  1027. * Returns list of valid country codes
  1028. *
  1029. * @see core/DataFiles/Countries.php
  1030. *
  1031. * @param bool $includeInternalCodes
  1032. * @return array Array of (2 letter ISO codes => 3 letter continent code)
  1033. */
  1034. static public function getCountriesList($includeInternalCodes = false)
  1035. {
  1036. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php';
  1037. $countriesList = $GLOBALS['Piwik_CountryList'];
  1038. $extras = $GLOBALS['Piwik_CountryList_Extras'];
  1039. if($includeInternalCodes)
  1040. {
  1041. return array_merge($countriesList, $extras);
  1042. }
  1043. return $countriesList;
  1044. }
  1045. /**
  1046. * Returns list of valid language codes
  1047. *
  1048. * @see core/DataFiles/Languages.php
  1049. *
  1050. * @return array Array of 2 letter ISO codes => Language name (in English)
  1051. */
  1052. static public function getLanguagesList()
  1053. {
  1054. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Languages.php';
  1055. $languagesList = $GLOBALS['Piwik_LanguageList'];
  1056. return $languagesList;
  1057. }
  1058. /**
  1059. * Returns list of language to country mappings
  1060. *
  1061. * @see core/DataFiles/LanguageToCountry.php
  1062. *
  1063. * @return array Array of ( 2 letter ISO language codes => 2 letter ISO country codes )
  1064. */
  1065. static public function getLanguageToCountryList()
  1066. {
  1067. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/LanguageToCountry.php';
  1068. $languagesList = $GLOBALS['Piwik_LanguageToCountry'];
  1069. return $languagesList;
  1070. }
  1071. /**
  1072. * Returns list of search engines by URL
  1073. *
  1074. * @see core/DataFiles/SearchEngines.php
  1075. *
  1076. * @return array Array of ( URL => array( searchEngineName, keywordParameter, path, charset ) )
  1077. */
  1078. static public function getSearchEngineUrls()
  1079. {
  1080. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/SearchEngines.php';
  1081. $searchEngines = $GLOBALS['Piwik_SearchEngines'];
  1082. return $searchEngines;
  1083. }
  1084. /**
  1085. * Returns list of search engines by name
  1086. *
  1087. * @see core/DataFiles/SearchEngines.php
  1088. *
  1089. * @return array Array of ( searchEngineName => URL )
  1090. */
  1091. static public function getSearchEngineNames()
  1092. {
  1093. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/SearchEngines.php';
  1094. $searchEngines = $GLOBALS['Piwik_SearchEngines_NameToUrl'];
  1095. return $searchEngines;
  1096. }
  1097. /*
  1098. * Language, country, continent
  1099. */
  1100. /**
  1101. * Returns the browser language code, eg. "en-gb,en;q=0.5"
  1102. *
  1103. * @param string $browserLang Optional browser language, otherwise taken from the request header
  1104. * @return string
  1105. */
  1106. static public function getBrowserLanguage($browserLang = NULL)
  1107. {
  1108. static $replacementPatterns = array(
  1109. // extraneous bits of RFC 3282 that we ignore
  1110. '/(\\\\.)/', // quoted-pairs
  1111. '/(\s+)/', // CFWcS white space
  1112. '/(\([^)]*\))/', // CFWS comments
  1113. '/(;q=[0-9.]+)/', // quality
  1114. // found in the LANG environment variable
  1115. '/\.(.*)/', // charset (e.g., en_CA.UTF-8)
  1116. '/^C$/', // POSIX 'C' locale
  1117. );
  1118. if(is_null($browserLang))
  1119. {
  1120. $browserLang = self::sanitizeInputValues(@$_SERVER['HTTP_ACCEPT_LANGUAGE']);
  1121. if(empty($browserLang) && self::isPhpCliMode())
  1122. {
  1123. $browserLang = @getenv('LANG');
  1124. }
  1125. }
  1126. if(is_null($browserLang))
  1127. {
  1128. // a fallback might be to infer the language in HTTP_USER_AGENT (i.e., localized build)
  1129. $browserLang = "";
  1130. }
  1131. else
  1132. {
  1133. // language tags are case-insensitive per HTTP/1.1 s3.10 but the region may be capitalized per ISO3166-1;
  1134. // underscores are not permitted per RFC 4646 or 4647 (which obsolete RFC 1766 and 3066),
  1135. // but we guard against a bad user agent which naively uses its locale
  1136. $browserLang = strtolower(str_replace('_', '-', $browserLang));
  1137. // filters
  1138. $browserLang = preg_replace($replacementPatterns, '', $browserLang);
  1139. $browserLang = preg_replace('/((^|,)chrome:.*)/', '', $browserLang, 1); // Firefox bug
  1140. $browserLang = preg_replace('/(,)(?:en-securid,)|(?:(^|,)en-securid(,|$))/', '$1', $browserLang, 1); // unregistered language tag
  1141. $browserLang = str_replace('sr-sp', 'sr-rs', $browserLang); // unofficial (proposed) code in the wild
  1142. }
  1143. return $browserLang;
  1144. }
  1145. /**
  1146. * Returns the visitor country based on the Browser 'accepted language'
  1147. * information, but provides a hook for geolocation via IP address.
  1148. *
  1149. * @param string $lang browser lang
  1150. * @param bool $enableLanguageToCountryGuess If set to true, some assumption will be made and detection guessed more often, but accuracy could be affected
  1151. * @param string $ip
  1152. * @return string 2 letter ISO code
  1153. */
  1154. static public function getCountry( $lang, $enableLanguageToCountryGuess, $ip )
  1155. {
  1156. $country = null;
  1157. Piwik_PostEvent('Common.getCountry', $country, $ip);
  1158. if(!empty($country))
  1159. {
  1160. return strtolower($country);
  1161. }
  1162. if(empty($lang) || strlen($lang) < 2)
  1163. {
  1164. return 'xx';
  1165. }
  1166. $validCountries = self::getCountriesList();
  1167. return self::extractCountryCodeFromBrowserLanguage($lang, $validCountries, $enableLanguageToCountryGuess);
  1168. }
  1169. /**
  1170. * Returns list of valid country codes
  1171. *
  1172. * @param string $browserLanguage
  1173. * @param array $validCountries Arrayof valid countries
  1174. * @param bool $enableLanguageToCountryGuess (if true, will guess country based on language that lacks region information)
  1175. * @return array Array of 2 letter ISO codes
  1176. */
  1177. static public function extractCountryCodeFromBrowserLanguage($browserLanguage, $validCountries, $enableLanguageToCountryGuess)
  1178. {
  1179. $langToCountry = self::getLanguageToCountryList();
  1180. if($enableLanguageToCountryGuess)
  1181. {
  1182. if(preg_match('/^([a-z]{2,3})(?:,|;|$)/', $browserLanguage, $matches))
  1183. {
  1184. // match language (without region) to infer the country of origin
  1185. if(array_key_exists($matches[1], $langToCountry))
  1186. {
  1187. return $langToCountry[$matches[1]];
  1188. }
  1189. }
  1190. }
  1191. if(!empty($validCountries) && preg_match_all('/[-]([a-z]{2})/', $browserLanguage, $matches, PREG_SET_ORDER))
  1192. {
  1193. foreach($matches as $parts)
  1194. {
  1195. // match location; we don't make any inferences from the language
  1196. if(array_key_exists($parts[1], $validCountries))
  1197. {
  1198. return $parts[1];
  1199. }
  1200. }
  1201. }
  1202. return 'xx';
  1203. }
  1204. /**
  1205. * Returns the visitor language based only on the Browser 'accepted language' information
  1206. *
  1207. * @param string $browserLang Browser's accepted langauge header
  1208. * @param array Array of valid language codes
  1209. * @return string 2 letter ISO 639 code
  1210. */
  1211. static public function extractLanguageCodeFromBrowserLanguage($browserLanguage, $validLanguages)
  1212. {
  1213. // assumes language preference is sorted;
  1214. // does not handle language-script-region tags or language range (*)
  1215. if(!empty($validLanguages) && preg_match_all('/(?:^|,)([a-z]{2,3})([-][a-z]{2})?/', $browserLanguage, $matches, PREG_SET_ORDER))
  1216. {
  1217. foreach($matches as $parts)
  1218. {
  1219. if(count($parts) == 3)
  1220. {
  1221. // match locale (langauge and location)
  1222. if(in_array($parts[1].$parts[2], $validLanguages))
  1223. {
  1224. return $parts[1].$parts[2];
  1225. }
  1226. }
  1227. // match language only (where no region provided)
  1228. if(in_array($parts[1], $validLanguages))
  1229. {
  1230. return $parts[1];
  1231. }
  1232. }
  1233. }
  1234. return 'xx';
  1235. }
  1236. /**
  1237. * Returns the continent of a given country
  1238. *
  1239. * @param string Country 2 letters isocode
  1240. *
  1241. * @return string Continent (3 letters code : afr, asi, eur, amn, ams, oce)
  1242. */
  1243. static public function getContinent($country)
  1244. {
  1245. $countryList = self::getCountriesList();
  1246. if(isset($countryList[$country]))
  1247. {
  1248. return $countryList[$country];
  1249. }
  1250. return 'unk';
  1251. }
  1252. /*
  1253. * Referrer
  1254. */
  1255. /**
  1256. * Reduce URL to more minimal form. 2 letter country codes are
  1257. * replaced by '{}', while other parts are simply removed.
  1258. *
  1259. * Examples:
  1260. * www.example.com -> example.com
  1261. * search.example.com -> example.com
  1262. * m.example.com -> example.com
  1263. * de.example.com -> {}.example.com
  1264. * example.de -> example.{}
  1265. * example.co.uk -> example.{}
  1266. *
  1267. * @param string $url
  1268. * @return string
  1269. */
  1270. static public function getLossyUrl($url)
  1271. {
  1272. static $countries;
  1273. if(!isset($countries))
  1274. {
  1275. $countries = implode('|', array_keys(self::getCountriesList(true)));
  1276. }
  1277. return preg_replace(
  1278. array(
  1279. '/^(w+[0-9]*|search)\./',
  1280. '/(^|\.)m\./',
  1281. '/(\.(com|org|net|co|it|edu))?\.('.$countries.')(\/|$)/',
  1282. '/(^|\.)('.$countries.')\./',
  1283. ),
  1284. array(
  1285. '',
  1286. '$1',
  1287. '.{}$4',
  1288. '$1{}.',
  1289. ),
  1290. $url);
  1291. }
  1292. /**
  1293. * Extracts a keyword from a raw not encoded URL.
  1294. * Will only extract keyword if a known search engine has been detected.
  1295. * Returns the keyword:
  1296. * - in UTF8: automatically converted from other charsets when applicable
  1297. * - strtolowered: "QUErY test!" will return "query test!"
  1298. * - trimmed: extra spaces before and after are removed
  1299. *
  1300. * Lists of supported search engines can be found in /core/DataFiles/SearchEngines.php
  1301. * The function returns false when a keyword couldn't be found.
  1302. * eg. if the url is "http://www.google.com/partners.html" this will return false,
  1303. * as the google keyword parameter couldn't be found.
  1304. *
  1305. * @see unit tests in /tests/core/Common.test.php
  1306. * @param string URL referer URL, eg. $_SERVER['HTTP_REFERER']
  1307. * @return array|false false if a keyword couldn't be extracted,
  1308. * or array(
  1309. * 'name' => 'Google',
  1310. * 'keywords' => 'my searched keywords')
  1311. */
  1312. static public function extractSearchEngineInformationFromUrl($referrerUrl)
  1313. {
  1314. $refererParsed = @parse_url($referrerUrl);
  1315. $refererHost = '';
  1316. if(isset($refererParsed['host']))
  1317. {
  1318. $refererHost = $refererParsed['host'];
  1319. }
  1320. if(empty($refererHost))
  1321. {
  1322. return false;
  1323. }
  1324. // some search engines (eg. Bing Images) use the same domain
  1325. // as an existing search engine (eg. Bing), we must also use the url path
  1326. $refererPath = '';
  1327. if(isset($refererParsed['path']))
  1328. {
  1329. $refererPath = $refererParsed['path'];
  1330. }
  1331. // no search query
  1332. if(!isset($refererParsed['query']))
  1333. {
  1334. $refererParsed['query'] = '';
  1335. }
  1336. $query = $refererParsed['query'];
  1337. $searchEngines = self::getSearchEngineUrls();
  1338. $hostPattern = self::getLossyUrl($refererHost);
  1339. if(array_key_exists($refererHost . $refererPath, $searchEngines))
  1340. {
  1341. $refererHost = $refererHost . $refererPath;
  1342. }
  1343. elseif(array_key_exists($hostPattern . $refererPath, $searchEngines))
  1344. {
  1345. $refererHost = $hostPattern . $refererPath;
  1346. }
  1347. elseif(array_key_exists($hostPattern, $searchEngines))
  1348. {
  1349. $refererHost = $hostPattern;
  1350. }
  1351. elseif(!array_key_exists($refererHost, $searchEngines))
  1352. {
  1353. if(!strncmp($query, 'cx=partner-pub-', 15))
  1354. {
  1355. // Google custom search engine
  1356. $refererHost = 'google.com/cse';
  1357. }
  1358. elseif(!strncmp($refererPath, '/pemonitorhosted/ws/results/', 28))
  1359. {
  1360. // private-label search powered by InfoSpace Metasearch
  1361. $refererHost = 'infospace.com';
  1362. }
  1363. elseif(strpos($refererHost, '.images.search.yahoo.com') != false)
  1364. {
  1365. // Yahoo! Images
  1366. $refererHost = 'images.search.yahoo.com';
  1367. }
  1368. elseif(strpos($refererHost, '.search.yahoo.com') != false)
  1369. {
  1370. // Yahoo!
  1371. $refererHost = 'search.yahoo.com';
  1372. }
  1373. else
  1374. {
  1375. return false;
  1376. }
  1377. }
  1378. $searchEngineName = $searchEngines[$refererHost][0];
  1379. $variableNames = null;
  1380. if(isset($searchEngines[$refererHost][1]))
  1381. {
  1382. $variableNames = $searchEngines[$refererHost][1];
  1383. }
  1384. if(!$variableNames)
  1385. {
  1386. $searchEngineNames = self::getSearchEngineNames();
  1387. $url = $searchEngineNames[$searchEngineName];
  1388. $variableNames = $searchEngines[$url][1];
  1389. }
  1390. if(!is_array($variableNames))
  1391. {
  1392. $variableNames = array($variableNames);
  1393. }
  1394. if($searchEngineName === 'Google Images'
  1395. || ($searchEngineName === 'Google' && strpos($referrerUrl, '/imgres') !== false) )
  1396. {
  1397. if (strpos($query, '&prev') !== false)
  1398. {
  1399. $query = urldecode(trim(self::getParameterFromQueryString($query, 'prev')));
  1400. $query = str_replace('&', '&amp;', strstr($query, '?'));
  1401. }
  1402. $searchEngineName = 'Google Images';
  1403. }
  1404. else if($searchEngineName === 'Google' && (strpos($query, '&as_') !== false || strpos($query, 'as_') === 0))
  1405. {
  1406. $keys = array();
  1407. $key = self::getParameterFromQueryString($query, 'as_q');
  1408. if(!empty($key))
  1409. {
  1410. array_push($keys, $key);
  1411. }
  1412. $key = self::getParameterFromQueryString($query, 'as_oq');
  1413. if(!empty($key))
  1414. {
  1415. array_push($keys, str_replace('+', ' OR ', $key));
  1416. }
  1417. $key = self::getParameterFromQueryString($query, 'as_epq');
  1418. if(!empty($key))
  1419. {
  1420. array_push($keys, "\"$key\"");
  1421. }
  1422. $key = self::getParameterFromQueryString($query, 'as_eq');
  1423. if(!empty($key))
  1424. {
  1425. array_push($keys, "-$key");
  1426. }
  1427. $key = trim(urldecode(implode(' ', $keys)));
  1428. }
  1429. if ($searchEngineName === 'Google')
  1430. {
  1431. // top bar menu
  1432. $tbm = self::getParameterFromQueryString($query, 'tbm');
  1433. switch ($tbm)
  1434. {
  1435. case 'isch':
  1436. $searchEngineName = 'Google Images'; break;
  1437. case 'vid':
  1438. $searchEngineName = 'Google Video'; break;
  1439. case 'shop':
  1440. $searchEngineName = 'Google Shopping'; break;
  1441. }
  1442. }
  1443. if(empty($key))
  1444. {
  1445. foreach($variableNames as $variableName)
  1446. {
  1447. if($variableName[0] == '/')
  1448. {
  1449. // regular expression match
  1450. if(preg_match($variableName, $referrerUrl, $matches))
  1451. {
  1452. $key = trim(urldecode($matches[1]));
  1453. break;
  1454. }
  1455. }
  1456. else
  1457. {
  1458. // search for keywords now &vname=keyword
  1459. $key = self::getParameterFromQueryString($query, $variableName);
  1460. $key = trim(urldecode($key));
  1461. if(!empty($key))
  1462. {
  1463. break;
  1464. }
  1465. }
  1466. }
  1467. }
  1468. if(empty($key))
  1469. {
  1470. return false;
  1471. }
  1472. if(function_exists('iconv')
  1473. && isset($searchEngines[$refererHost][3]))
  1474. {
  1475. $charset = trim($searchEngines[$refererHost][3]);
  1476. if(!empty($charset))
  1477. {
  1478. $newkey = @iconv($charset, 'UTF-8//IGNORE', $key);
  1479. if(!empty($newkey))
  1480. {
  1481. $key = $newkey;
  1482. }
  1483. }
  1484. }
  1485. $key = mb_strtolower($key, 'UTF-8');
  1486. return array(
  1487. 'name' => $searchEngineName,
  1488. 'keywords' => $key,
  1489. );
  1490. }
  1491. /*
  1492. * System environment
  1493. */
  1494. /**
  1495. * Returns true if PHP was invoked from command-line interface (shell)
  1496. *
  1497. * @since added in 0.4.4
  1498. * @return bool true if PHP invoked as a CGI or from CLI
  1499. */
  1500. static public function isPhpCliMode()
  1501. {
  1502. $remoteAddr = @$_SERVER['REMOTE_ADDR'];
  1503. return PHP_SAPI == 'cli' ||
  1504. (!strncmp(PHP_SAPI, 'cgi', 3) && empty($remoteAddr));
  1505. }
  1506. /**
  1507. * Is the current script execution triggered by misc/cron/archive.php ?
  1508. *
  1509. * Helpful for error handling: directly throw error without HTML (eg. when DB is down)
  1510. */
  1511. static public function isArchivePhpTriggered()
  1512. {
  1513. return !empty($_GET['trigger'])
  1514. && $_GET['trigger'] == 'archivephp';
  1515. }
  1516. /**
  1517. * Assign CLI parameters as if they were REQUEST or GET parameters.
  1518. * You can trigger Piwik from the command line by
  1519. * # /usr/bin/php5 /path/to/piwik/index.php -- "module=API&method=Actions.getActions&idSite=1&period=day&date=previous8&format=php"
  1520. */
  1521. static public function assignCliParametersToRequest()
  1522. {
  1523. if(isset($_SERVER['argc'])
  1524. && $_SERVER['argc'] > 0)
  1525. {
  1526. for ($i=1; $i < $_SERVER['argc']; $i++)
  1527. {
  1528. parse_str($_SERVER['argv'][$i],$tmp);
  1529. $_GET = array_merge($_GET, $tmp);
  1530. }
  1531. }
  1532. }
  1533. /**
  1534. * Returns true if running on a Windows operating system
  1535. *
  1536. * @since added in 0.6.5
  1537. * @return bool true if PHP detects it is running on Windows; else false
  1538. */
  1539. static public function isWindows()
  1540. {
  1541. return DIRECTORY_SEPARATOR == '\\';
  1542. }
  1543. /**
  1544. * Returns true if running on an Apache web server
  1545. *
  1546. * @return bool
  1547. */
  1548. static public function isApache()
  1549. {
  1550. $apache = isset($_SERVER['SERVER_SOFTWARE']) &&
  1551. !strncmp($_SERVER['SERVER_SOFTWARE'], 'Apache', 6);
  1552. return $apache;
  1553. }
  1554. /**
  1555. * Returns true if running on Microsoft IIS 7 (or above)
  1556. *
  1557. * @return bool
  1558. */
  1559. static public function isIIS()
  1560. {
  1561. $iis = isset($_SERVER['SERVER_SOFTWARE']) &&
  1562. preg_match('/^Microsoft-IIS\/(.+)/', $_SERVER['SERVER_SOFTWARE'], $matches) &&
  1563. version_compare($matches[1], '7') >= 0;
  1564. return $iis;
  1565. }
  1566. /**
  1567. * Takes a list of fields defining numeric values and returns the corresponding
  1568. * unnamed parameters to be bound to the field names in the where clause of a SQL query
  1569. *
  1570. * @param array|string $fields array( fieldName1, fieldName2, fieldName3) Names of the mysql table fields to load
  1571. * @return string "?, ?, ?"
  1572. */
  1573. public static function getSqlStringFieldsArray( $fields )
  1574. {
  1575. if(is_string($fields))
  1576. {
  1577. $fields = array($fields);
  1578. }
  1579. $count = count($fields);
  1580. if($count == 0)
  1581. {
  1582. return "''";
  1583. }
  1584. return '?'.str_repeat(',?', $count-1);
  1585. }
  1586. }
  1587. /**
  1588. * Mark orphaned object for garbage collection
  1589. *
  1590. * For more information: @link http://dev.piwik.org/trac/ticket/374
  1591. */
  1592. function destroy(&$var)
  1593. {
  1594. if (is_object($var)) $var->__destruct();
  1595. unset($var);
  1596. $var = null;
  1597. }
  1598. // http://bugs.php.net/bug.php?id=53632
  1599. if (strpos(str_replace('.','',serialize($_REQUEST)), '22250738585072011') !== false)
  1600. {
  1601. header('Status: 422 Unprocessable Entity');
  1602. die('Exit');
  1603. }