PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Common.php

https://github.com/CodeYellowBV/piwik
PHP | 1101 lines | 525 code | 117 blank | 459 comment | 98 complexity | db270e52e0d70850a02d0c54f517edf7 MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik;
  10. use Exception;
  11. use Piwik\Plugins\UserCountry\LocationProvider\DefaultProvider;
  12. use Piwik\Tracker;
  13. use Piwik\Tracker\Cache;
  14. /**
  15. * Contains helper methods used by both Piwik Core and the Piwik Tracking engine.
  16. *
  17. * This is the only non-Tracker class loaded by the **\/piwik.php** file.
  18. */
  19. class Common
  20. {
  21. // constants used to map the referrer type to an integer in the log_visit table
  22. const REFERRER_TYPE_DIRECT_ENTRY = 1;
  23. const REFERRER_TYPE_SEARCH_ENGINE = 2;
  24. const REFERRER_TYPE_WEBSITE = 3;
  25. const REFERRER_TYPE_CAMPAIGN = 6;
  26. // Flag used with htmlspecialchar. See php.net/htmlspecialchars.
  27. const HTML_ENCODING_QUOTE_STYLE = ENT_QUOTES;
  28. public static $isCliMode = null;
  29. /*
  30. * Database
  31. */
  32. /**
  33. * Hashes a string into an integer which should be very low collision risks
  34. * @param string $string String to hash
  35. * @return int Resulting int hash
  36. */
  37. public static function hashStringToInt($string)
  38. {
  39. $stringHash = substr(md5($string), 0, 8);
  40. return base_convert($stringHash, 16, 10);
  41. }
  42. /**
  43. * Returns a prefixed table name.
  44. *
  45. * The table prefix is determined by the `[database] tables_prefix` INI config
  46. * option.
  47. *
  48. * @param string $table The table name to prefix, ie "log_visit"
  49. * @return string The prefixed name, ie "piwik-production_log_visit".
  50. * @api
  51. */
  52. public static function prefixTable($table)
  53. {
  54. $prefix = Config::getInstance()->database['tables_prefix'];
  55. return $prefix . $table;
  56. }
  57. /**
  58. * Returns an array containing the prefixed table names of every passed argument.
  59. *
  60. * @param string ... The table names to prefix, ie "log_visit"
  61. * @return array The prefixed names in an array.
  62. */
  63. public static function prefixTables()
  64. {
  65. $result = array();
  66. foreach (func_get_args() as $table) {
  67. $result[] = self::prefixTable($table);
  68. }
  69. return $result;
  70. }
  71. /**
  72. * Removes the prefix from a table name and returns the result.
  73. *
  74. * The table prefix is determined by the `[database] tables_prefix` INI config
  75. * option.
  76. *
  77. * @param string $table The prefixed table name, eg "piwik-production_log_visit".
  78. * @return string The unprefixed table name, eg "log_visit".
  79. * @api
  80. */
  81. public static function unprefixTable($table)
  82. {
  83. static $prefixTable = null;
  84. if (is_null($prefixTable)) {
  85. $prefixTable = Config::getInstance()->database['tables_prefix'];
  86. }
  87. if (empty($prefixTable)
  88. || strpos($table, $prefixTable) !== 0
  89. ) {
  90. return $table;
  91. }
  92. $count = 1;
  93. return str_replace($prefixTable, '', $table, $count);
  94. }
  95. /*
  96. * Tracker
  97. */
  98. public static function isGoalPluginEnabled()
  99. {
  100. return \Piwik\Plugin\Manager::getInstance()->isPluginActivated('Goals');
  101. }
  102. public static function isActionsPluginEnabled()
  103. {
  104. return \Piwik\Plugin\Manager::getInstance()->isPluginActivated('Actions');
  105. }
  106. /**
  107. * Returns true if PHP was invoked from command-line interface (shell)
  108. *
  109. * @since added in 0.4.4
  110. * @return bool true if PHP invoked as a CGI or from CLI
  111. */
  112. public static function isPhpCliMode()
  113. {
  114. if (is_bool(self::$isCliMode)) {
  115. return self::$isCliMode;
  116. }
  117. $remoteAddr = @$_SERVER['REMOTE_ADDR'];
  118. return PHP_SAPI == 'cli' ||
  119. (self::isPhpCgiType() && empty($remoteAddr));
  120. }
  121. /**
  122. * Returns true if PHP is executed as CGI type.
  123. *
  124. * @since added in 0.4.4
  125. * @return bool true if PHP invoked as a CGI
  126. */
  127. public static function isPhpCgiType()
  128. {
  129. $sapiType = php_sapi_name();
  130. return substr($sapiType, 0, 3) === 'cgi';
  131. }
  132. /**
  133. * Returns true if the current request is a console command, eg. ./console xx:yy
  134. * @return bool
  135. */
  136. public static function isRunningConsoleCommand()
  137. {
  138. $searched = '/console';
  139. $consolePos = strpos($_SERVER['SCRIPT_NAME'], $searched);
  140. $expectedConsolePos = strlen($_SERVER['SCRIPT_NAME']) - strlen($searched);
  141. $isScriptIsConsole = $consolePos == $expectedConsolePos;
  142. return self::isPhpCliMode() && $isScriptIsConsole;
  143. }
  144. /*
  145. * String operations
  146. */
  147. /**
  148. * Multi-byte substr() - works with UTF-8.
  149. *
  150. * Calls `mb_substr` if available and falls back to `substr` if it's not.
  151. *
  152. * @param string $string
  153. * @param int $start
  154. * @param int ... optional length
  155. * @return string
  156. * @api
  157. */
  158. public static function mb_substr($string, $start)
  159. {
  160. $length = func_num_args() > 2
  161. ? func_get_arg(2)
  162. : self::mb_strlen($string);
  163. if (function_exists('mb_substr')) {
  164. return mb_substr($string, $start, $length, 'UTF-8');
  165. }
  166. return substr($string, $start, $length);
  167. }
  168. /**
  169. * Multi-byte strlen() - works with UTF-8
  170. *
  171. * Calls `mb_substr` if available and falls back to `substr` if not.
  172. *
  173. * @param string $string
  174. * @return int
  175. * @api
  176. */
  177. public static function mb_strlen($string)
  178. {
  179. if (function_exists('mb_strlen')) {
  180. return mb_strlen($string, 'UTF-8');
  181. }
  182. return strlen($string);
  183. }
  184. /**
  185. * Multi-byte strtolower() - works with UTF-8.
  186. *
  187. * Calls `mb_strtolower` if available and falls back to `strtolower` if not.
  188. *
  189. * @param string $string
  190. * @return string
  191. * @api
  192. */
  193. public static function mb_strtolower($string)
  194. {
  195. if (function_exists('mb_strtolower')) {
  196. return mb_strtolower($string, 'UTF-8');
  197. }
  198. return strtolower($string);
  199. }
  200. /*
  201. * Escaping input
  202. */
  203. /**
  204. * Sanitizes a string to help avoid XSS vulnerabilities.
  205. *
  206. * This function is automatically called when {@link getRequestVar()} is called,
  207. * so you should not normally have to use it.
  208. *
  209. * This function should be used when outputting data that isn't escaped and was
  210. * obtained from the user (for example when using the `|raw` twig filter on goal names).
  211. *
  212. * _NOTE: Sanitized input should not be used directly in an SQL query; SQL placeholders
  213. * should still be used._
  214. *
  215. * **Implementation Details**
  216. *
  217. * - [htmlspecialchars](http://php.net/manual/en/function.htmlspecialchars.php) is used to escape text.
  218. * - Single quotes are not escaped so **Piwik's amazing community** will still be
  219. * **Piwik's amazing community**.
  220. * - Use of the `magic_quotes` setting will not break this method.
  221. * - Boolean, numeric and null values are not modified.
  222. *
  223. * @param mixed $value The variable to be sanitized. If an array is supplied, the contents
  224. * of the array will be sanitized recursively. The keys of the array
  225. * will also be sanitized.
  226. * @param bool $alreadyStripslashed Implementation detail, ignore.
  227. * @throws Exception If `$value` is of an incorrect type.
  228. * @return mixed The sanitized value.
  229. * @api
  230. */
  231. public static function sanitizeInputValues($value, $alreadyStripslashed = false)
  232. {
  233. if (is_numeric($value)) {
  234. return $value;
  235. } elseif (is_string($value)) {
  236. $value = self::sanitizeInputValue($value);
  237. if (!$alreadyStripslashed) // a JSON array was already stripslashed, don't do it again for each value
  238. {
  239. $value = self::undoMagicQuotes($value);
  240. }
  241. } elseif (is_array($value)) {
  242. foreach (array_keys($value) as $key) {
  243. $newKey = $key;
  244. $newKey = self::sanitizeInputValues($newKey, $alreadyStripslashed);
  245. if ($key != $newKey) {
  246. $value[$newKey] = $value[$key];
  247. unset($value[$key]);
  248. }
  249. $value[$newKey] = self::sanitizeInputValues($value[$newKey], $alreadyStripslashed);
  250. }
  251. } elseif (!is_null($value)
  252. && !is_bool($value)
  253. ) {
  254. throw new Exception("The value to escape has not a supported type. Value = " . var_export($value, true));
  255. }
  256. return $value;
  257. }
  258. /**
  259. * Sanitize a single input value
  260. *
  261. * @param string $value
  262. * @return string sanitized input
  263. */
  264. public static function sanitizeInputValue($value)
  265. {
  266. // $_GET and $_REQUEST already urldecode()'d
  267. // decode
  268. // note: before php 5.2.7, htmlspecialchars() double encodes &#x hex items
  269. $value = html_entity_decode($value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8');
  270. // filter
  271. $value = self::sanitizeLineBreaks($value);
  272. // escape
  273. $tmp = @htmlspecialchars($value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8');
  274. // note: php 5.2.5 and above, htmlspecialchars is destructive if input is not UTF-8
  275. if ($value != '' && $tmp == '') {
  276. // convert and escape
  277. $value = utf8_encode($value);
  278. $tmp = htmlspecialchars($value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8');
  279. }
  280. return $tmp;
  281. }
  282. /**
  283. * Unsanitizes a single input value and returns the result.
  284. *
  285. * @param string $value
  286. * @return string unsanitized input
  287. */
  288. public static function unsanitizeInputValue($value)
  289. {
  290. return htmlspecialchars_decode($value, self::HTML_ENCODING_QUOTE_STYLE);
  291. }
  292. /**
  293. * Unsanitizes one or more values and returns the result.
  294. *
  295. * This method should be used when you need to unescape data that was obtained from
  296. * the user.
  297. *
  298. * Some data in Piwik is stored sanitized (such as site name). In this case you may
  299. * have to use this method to unsanitize it in order to, for example, output it in JSON.
  300. *
  301. * @param string|array $value The data to unsanitize. If an array is passed, the
  302. * array is sanitized recursively. Key values are not unsanitized.
  303. * @return string|array The unsanitized data.
  304. * @api
  305. */
  306. public static function unsanitizeInputValues($value)
  307. {
  308. if (is_array($value)) {
  309. $result = array();
  310. foreach ($value as $key => $arrayValue) {
  311. $result[$key] = self::unsanitizeInputValues($arrayValue);
  312. }
  313. return $result;
  314. } else {
  315. return self::unsanitizeInputValue($value);
  316. }
  317. }
  318. /**
  319. * Undo the damage caused by magic_quotes; deprecated in php 5.3 but not removed until php 5.4
  320. *
  321. * @param string
  322. * @return string modified or not
  323. */
  324. private static function undoMagicQuotes($value)
  325. {
  326. return version_compare(PHP_VERSION, '5.4', '<')
  327. && get_magic_quotes_gpc()
  328. ? stripslashes($value)
  329. : $value;
  330. }
  331. /**
  332. *
  333. * @param string
  334. * @return string Line breaks and line carriage removed
  335. */
  336. public static function sanitizeLineBreaks($value)
  337. {
  338. $value = str_replace(array("\n", "\r", "\0"), '', $value);
  339. return $value;
  340. }
  341. /**
  342. * Gets a sanitized request parameter by name from the `$_GET` and `$_POST` superglobals.
  343. *
  344. * Use this function to get request parameter values. **_NEVER use `$_GET` and `$_POST` directly._**
  345. *
  346. * If the variable cannot be found, and a default value was not provided, an exception is raised.
  347. *
  348. * _See {@link sanitizeInputValues()} to learn more about sanitization._
  349. *
  350. * @param string $varName Name of the request parameter to get. By default, we look in `$_GET[$varName]`
  351. * and `$_POST[$varName]` for the value.
  352. * @param string|null $varDefault The value to return if the request parameter cannot be found or has an empty value.
  353. * @param string|null $varType Expected type of the request variable. This parameters value must be one of the following:
  354. * `'array'`, `'int'`, `'integer'`, `'string'`, `'json'`.
  355. *
  356. * If `'json'`, the string value will be `json_decode`-d and then sanitized.
  357. * @param array|null $requestArrayToUse The array to use instead of `$_GET` and `$_POST`.
  358. * @throws Exception If the request parameter doesn't exist and there is no default value, or if the request parameter
  359. * exists but has an incorrect type.
  360. * @return mixed The sanitized request parameter.
  361. * @api
  362. */
  363. public static function getRequestVar($varName, $varDefault = null, $varType = null, $requestArrayToUse = null)
  364. {
  365. if (is_null($requestArrayToUse)) {
  366. $requestArrayToUse = $_GET + $_POST;
  367. }
  368. $varDefault = self::sanitizeInputValues($varDefault);
  369. if ($varType === 'int') {
  370. // settype accepts only integer
  371. // 'int' is simply a shortcut for 'integer'
  372. $varType = 'integer';
  373. }
  374. // there is no value $varName in the REQUEST so we try to use the default value
  375. if (empty($varName)
  376. || !isset($requestArrayToUse[$varName])
  377. || (!is_array($requestArrayToUse[$varName])
  378. && strlen($requestArrayToUse[$varName]) === 0
  379. )
  380. ) {
  381. if (is_null($varDefault)) {
  382. throw new Exception("The parameter '$varName' isn't set in the Request, and a default value wasn't provided.");
  383. } else {
  384. if (!is_null($varType)
  385. && in_array($varType, array('string', 'integer', 'array'))
  386. ) {
  387. settype($varDefault, $varType);
  388. }
  389. return $varDefault;
  390. }
  391. }
  392. // Normal case, there is a value available in REQUEST for the requested varName:
  393. // we deal w/ json differently
  394. if ($varType == 'json') {
  395. $value = self::undoMagicQuotes($requestArrayToUse[$varName]);
  396. $value = self::json_decode($value, $assoc = true);
  397. return self::sanitizeInputValues($value, $alreadyStripslashed = true);
  398. }
  399. $value = self::sanitizeInputValues($requestArrayToUse[$varName]);
  400. if (!is_null($varType)) {
  401. $ok = false;
  402. if ($varType === 'string') {
  403. if (is_string($value)) $ok = true;
  404. } elseif ($varType === 'integer') {
  405. if ($value == (string)(int)$value) $ok = true;
  406. } elseif ($varType === 'float') {
  407. if ($value == (string)(float)$value) $ok = true;
  408. } elseif ($varType === 'array') {
  409. if (is_array($value)) $ok = true;
  410. } else {
  411. throw new Exception("\$varType specified is not known. It should be one of the following: array, int, integer, float, string");
  412. }
  413. // The type is not correct
  414. if ($ok === false) {
  415. if ($varDefault === null) {
  416. throw new Exception("The parameter '$varName' doesn't have a correct type, and a default value wasn't provided.");
  417. } // we return the default value with the good type set
  418. else {
  419. settype($varDefault, $varType);
  420. return $varDefault;
  421. }
  422. }
  423. settype($value, $varType);
  424. }
  425. return $value;
  426. }
  427. /*
  428. * Generating unique strings
  429. */
  430. /**
  431. * Returns a 32 characters long uniq ID
  432. *
  433. * @return string 32 chars
  434. */
  435. public static function generateUniqId()
  436. {
  437. return md5(uniqid(rand(), true));
  438. }
  439. /**
  440. * Configureable hash() algorithm (defaults to md5)
  441. *
  442. * @param string $str String to be hashed
  443. * @param bool $raw_output
  444. * @return string Hash string
  445. */
  446. public static function hash($str, $raw_output = false)
  447. {
  448. static $hashAlgorithm = null;
  449. if (is_null($hashAlgorithm)) {
  450. $hashAlgorithm = @Config::getInstance()->General['hash_algorithm'];
  451. }
  452. if ($hashAlgorithm) {
  453. $hash = @hash($hashAlgorithm, $str, $raw_output);
  454. if ($hash !== false)
  455. return $hash;
  456. }
  457. return md5($str, $raw_output);
  458. }
  459. /**
  460. * Generate random string.
  461. * Do not use for security related purposes (the string is not truly random).
  462. *
  463. * @param int $length string length
  464. * @param string $alphabet characters allowed in random string
  465. * @return string random string with given length
  466. */
  467. public static function getRandomString($length = 16, $alphabet = "abcdefghijklmnoprstuvwxyz0123456789")
  468. {
  469. $chars = $alphabet;
  470. $str = '';
  471. list($usec, $sec) = explode(" ", microtime());
  472. $seed = ((float)$sec + (float)$usec) * 100000;
  473. mt_srand($seed);
  474. for ($i = 0; $i < $length; $i++) {
  475. $rand_key = mt_rand(0, strlen($chars) - 1);
  476. $str .= substr($chars, $rand_key, 1);
  477. }
  478. return str_shuffle($str);
  479. }
  480. /*
  481. * Conversions
  482. */
  483. /**
  484. * Convert hexadecimal representation into binary data.
  485. * !! Will emit warning if input string is not hex!!
  486. *
  487. * @see http://php.net/bin2hex
  488. *
  489. * @param string $str Hexadecimal representation
  490. * @return string
  491. */
  492. public static function hex2bin($str)
  493. {
  494. return pack("H*", $str);
  495. }
  496. /**
  497. * This function will convert the input string to the binary representation of the ID
  498. * but it will throw an Exception if the specified input ID is not correct
  499. *
  500. * This is used when building segments containing visitorId which could be an invalid string
  501. * therefore throwing Unexpected PHP error [pack(): Type H: illegal hex digit i] severity [E_WARNING]
  502. *
  503. * It would be simply to silent fail the pack() call above but in all other cases, we don't expect an error,
  504. * so better be safe and get the php error when something unexpected is happening
  505. * @param string $id
  506. * @throws Exception
  507. * @return string binary string
  508. */
  509. public static function convertVisitorIdToBin($id)
  510. {
  511. if (strlen($id) !== Tracker::LENGTH_HEX_ID_STRING
  512. || @bin2hex(self::hex2bin($id)) != $id
  513. ) {
  514. throw new Exception("visitorId is expected to be a " . Tracker::LENGTH_HEX_ID_STRING . " hex char string");
  515. }
  516. return self::hex2bin($id);
  517. }
  518. /**
  519. * Convert IP address (in network address format) to presentation format.
  520. * This is a backward compatibility function for code that only expects
  521. * IPv4 addresses (i.e., doesn't support IPv6).
  522. *
  523. * @see IP::N2P()
  524. *
  525. * This function does not support the long (or its string representation)
  526. * returned by the built-in ip2long() function, from Piwik 1.3 and earlier.
  527. *
  528. * @deprecated 1.4
  529. *
  530. * @param string $ip IP address in network address format
  531. * @return string
  532. */
  533. public static function long2ip($ip)
  534. {
  535. return IP::long2ip($ip);
  536. }
  537. /**
  538. * JSON encode wrapper
  539. * - missing or broken in some php 5.x versions
  540. *
  541. * @param mixed $value
  542. * @return string
  543. * @deprecated
  544. */
  545. public static function json_encode($value)
  546. {
  547. return @json_encode($value);
  548. }
  549. /**
  550. * JSON decode wrapper
  551. * - missing or broken in some php 5.x versions
  552. *
  553. * @param string $json
  554. * @param bool $assoc
  555. * @return mixed
  556. * @deprecated
  557. */
  558. public static function json_decode($json, $assoc = false)
  559. {
  560. return json_decode($json, $assoc);
  561. }
  562. /**
  563. * Detects whether an error occurred during the last json encode/decode.
  564. * @return bool
  565. */
  566. public static function hasJsonErrorOccurred()
  567. {
  568. return json_last_error() != JSON_ERROR_NONE;
  569. }
  570. /**
  571. * Returns a human readable error message in case an error occcurred during the last json encode/decode.
  572. * Returns an empty string in case there was no error.
  573. *
  574. * @return string
  575. */
  576. public static function getLastJsonError()
  577. {
  578. switch (json_last_error()) {
  579. case JSON_ERROR_NONE:
  580. return '';
  581. case JSON_ERROR_DEPTH:
  582. return 'Maximum stack depth exceeded';
  583. case JSON_ERROR_STATE_MISMATCH:
  584. return 'Underflow or the modes mismatch';
  585. case JSON_ERROR_CTRL_CHAR:
  586. return 'Unexpected control character found';
  587. case JSON_ERROR_SYNTAX:
  588. return 'Syntax error, malformed JSON';
  589. case JSON_ERROR_UTF8:
  590. return 'Malformed UTF-8 characters, possibly incorrectly encoded';
  591. }
  592. return 'Unknown error';
  593. }
  594. public static function stringEndsWith($haystack, $needle)
  595. {
  596. if ('' === $needle) {
  597. return true;
  598. }
  599. $lastCharacters = substr($haystack, -strlen($needle));
  600. return $lastCharacters === $needle;
  601. }
  602. /**
  603. * Returns the list of parent classes for the given class.
  604. *
  605. * @param string $klass A class name.
  606. * @return string[] The list of parent classes in order from highest ancestor to the descended class.
  607. */
  608. public static function getClassLineage($klass)
  609. {
  610. $klasses = array_merge(array($klass), array_values(class_parents($klass, $autoload = false)));
  611. return array_reverse($klasses);
  612. }
  613. /*
  614. * DataFiles
  615. */
  616. /**
  617. * Returns list of continent codes
  618. *
  619. * @see core/DataFiles/Countries.php
  620. *
  621. * @return array Array of 3 letter continent codes
  622. */
  623. public static function getContinentsList()
  624. {
  625. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php';
  626. $continentsList = $GLOBALS['Piwik_ContinentList'];
  627. return $continentsList;
  628. }
  629. /**
  630. * Returns list of valid country codes
  631. *
  632. * @see core/DataFiles/Countries.php
  633. *
  634. * @param bool $includeInternalCodes
  635. * @return array Array of (2 letter ISO codes => 3 letter continent code)
  636. */
  637. public static function getCountriesList($includeInternalCodes = false)
  638. {
  639. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php';
  640. $countriesList = $GLOBALS['Piwik_CountryList'];
  641. $extras = $GLOBALS['Piwik_CountryList_Extras'];
  642. if ($includeInternalCodes) {
  643. return array_merge($countriesList, $extras);
  644. }
  645. return $countriesList;
  646. }
  647. /**
  648. * Returns the list of valid language codes.
  649. *
  650. * See [core/DataFiles/Languages.php](https://github.com/piwik/piwik/blob/master/core/DataFiles/Languages.php).
  651. *
  652. * @return array Array of two letter ISO codes mapped with their associated language names (in English). E.g.
  653. * `array('en' => 'English', 'ja' => 'Japanese')`.
  654. * @api
  655. */
  656. public static function getLanguagesList()
  657. {
  658. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Languages.php';
  659. $languagesList = $GLOBALS['Piwik_LanguageList'];
  660. return $languagesList;
  661. }
  662. /**
  663. * Returns a list of language to country mappings.
  664. *
  665. * See [core/DataFiles/LanguageToCountry.php](https://github.com/piwik/piwik/blob/master/core/DataFiles/LanguageToCountry.php).
  666. *
  667. * @return array Array of two letter ISO language codes mapped with two letter ISO country codes:
  668. * `array('fr' => 'fr') // French => France`
  669. * @api
  670. */
  671. public static function getLanguageToCountryList()
  672. {
  673. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/LanguageToCountry.php';
  674. $languagesList = $GLOBALS['Piwik_LanguageToCountry'];
  675. return $languagesList;
  676. }
  677. /**
  678. * Returns list of search engines by URL
  679. *
  680. * @see core/DataFiles/SearchEngines.php
  681. *
  682. * @return array Array of ( URL => array( searchEngineName, keywordParameter, path, charset ) )
  683. */
  684. public static function getSearchEngineUrls()
  685. {
  686. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/SearchEngines.php';
  687. $searchEngines = $GLOBALS['Piwik_SearchEngines'];
  688. Piwik::postEvent('Referrer.addSearchEngineUrls', array(&$searchEngines));
  689. return $searchEngines;
  690. }
  691. /**
  692. * Returns list of search engines by name
  693. *
  694. * @see core/DataFiles/SearchEngines.php
  695. *
  696. * @return array Array of ( searchEngineName => URL )
  697. */
  698. public static function getSearchEngineNames()
  699. {
  700. $searchEngines = self::getSearchEngineUrls();
  701. $nameToUrl = array();
  702. foreach ($searchEngines as $url => $info) {
  703. if (!isset($nameToUrl[$info[0]])) {
  704. $nameToUrl[$info[0]] = $url;
  705. }
  706. }
  707. return $nameToUrl;
  708. }
  709. /**
  710. * Returns list of social networks by URL
  711. *
  712. * @see core/DataFiles/Socials.php
  713. *
  714. * @return array Array of ( URL => Social Network Name )
  715. */
  716. public static function getSocialUrls()
  717. {
  718. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Socials.php';
  719. $socialUrls = $GLOBALS['Piwik_socialUrl'];
  720. Piwik::postEvent('Referrer.addSocialUrls', array(&$socialUrls));
  721. return $socialUrls;
  722. }
  723. /**
  724. * Returns list of provider names
  725. *
  726. * @see core/DataFiles/Providers.php
  727. *
  728. * @return array Array of ( dnsName => providerName )
  729. */
  730. public static function getProviderNames()
  731. {
  732. require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Providers.php';
  733. $providers = $GLOBALS['Piwik_ProviderNames'];
  734. return $providers;
  735. }
  736. /*
  737. * Language, country, continent
  738. */
  739. /**
  740. * Returns the browser language code, eg. "en-gb,en;q=0.5"
  741. *
  742. * @param string|null $browserLang Optional browser language, otherwise taken from the request header
  743. * @return string
  744. */
  745. public static function getBrowserLanguage($browserLang = null)
  746. {
  747. static $replacementPatterns = array(
  748. // extraneous bits of RFC 3282 that we ignore
  749. '/(\\\\.)/', // quoted-pairs
  750. '/(\s+)/', // CFWcS white space
  751. '/(\([^)]*\))/', // CFWS comments
  752. '/(;q=[0-9.]+)/', // quality
  753. // found in the LANG environment variable
  754. '/\.(.*)/', // charset (e.g., en_CA.UTF-8)
  755. '/^C$/', // POSIX 'C' locale
  756. );
  757. if (is_null($browserLang)) {
  758. $browserLang = self::sanitizeInputValues(@$_SERVER['HTTP_ACCEPT_LANGUAGE']);
  759. if (empty($browserLang) && self::isPhpCliMode()) {
  760. $browserLang = @getenv('LANG');
  761. }
  762. }
  763. if (is_null($browserLang)) {
  764. // a fallback might be to infer the language in HTTP_USER_AGENT (i.e., localized build)
  765. $browserLang = "";
  766. } else {
  767. // language tags are case-insensitive per HTTP/1.1 s3.10 but the region may be capitalized per ISO3166-1;
  768. // underscores are not permitted per RFC 4646 or 4647 (which obsolete RFC 1766 and 3066),
  769. // but we guard against a bad user agent which naively uses its locale
  770. $browserLang = strtolower(str_replace('_', '-', $browserLang));
  771. // filters
  772. $browserLang = preg_replace($replacementPatterns, '', $browserLang);
  773. $browserLang = preg_replace('/((^|,)chrome:.*)/', '', $browserLang, 1); // Firefox bug
  774. $browserLang = preg_replace('/(,)(?:en-securid,)|(?:(^|,)en-securid(,|$))/', '$1', $browserLang, 1); // unregistered language tag
  775. $browserLang = str_replace('sr-sp', 'sr-rs', $browserLang); // unofficial (proposed) code in the wild
  776. }
  777. return $browserLang;
  778. }
  779. /**
  780. * Returns the visitor country based on the Browser 'accepted language'
  781. * information, but provides a hook for geolocation via IP address.
  782. *
  783. * @param string $lang browser lang
  784. * @param bool $enableLanguageToCountryGuess If set to true, some assumption will be made and detection guessed more often, but accuracy could be affected
  785. * @param string $ip
  786. * @return string 2 letter ISO code
  787. */
  788. public static function getCountry($lang, $enableLanguageToCountryGuess, $ip)
  789. {
  790. if (empty($lang) || strlen($lang) < 2 || $lang == 'xx') {
  791. return 'xx';
  792. }
  793. $validCountries = self::getCountriesList();
  794. return self::extractCountryCodeFromBrowserLanguage($lang, $validCountries, $enableLanguageToCountryGuess);
  795. }
  796. /**
  797. * Returns list of valid country codes
  798. *
  799. * @param string $browserLanguage
  800. * @param array $validCountries Array of valid countries
  801. * @param bool $enableLanguageToCountryGuess (if true, will guess country based on language that lacks region information)
  802. * @return array Array of 2 letter ISO codes
  803. */
  804. public static function extractCountryCodeFromBrowserLanguage($browserLanguage, $validCountries, $enableLanguageToCountryGuess)
  805. {
  806. $langToCountry = self::getLanguageToCountryList();
  807. if ($enableLanguageToCountryGuess) {
  808. if (preg_match('/^([a-z]{2,3})(?:,|;|$)/', $browserLanguage, $matches)) {
  809. // match language (without region) to infer the country of origin
  810. if (array_key_exists($matches[1], $langToCountry)) {
  811. return $langToCountry[$matches[1]];
  812. }
  813. }
  814. }
  815. if (!empty($validCountries) && preg_match_all('/[-]([a-z]{2})/', $browserLanguage, $matches, PREG_SET_ORDER)) {
  816. foreach ($matches as $parts) {
  817. // match location; we don't make any inferences from the language
  818. if (array_key_exists($parts[1], $validCountries)) {
  819. return $parts[1];
  820. }
  821. }
  822. }
  823. return 'xx';
  824. }
  825. /**
  826. * Returns the visitor language based only on the Browser 'accepted language' information
  827. *
  828. * @param string $browserLanguage Browser's accepted langauge header
  829. * @param array $validLanguages array of valid language codes
  830. * @return string 2 letter ISO 639 code
  831. */
  832. public static function extractLanguageCodeFromBrowserLanguage($browserLanguage, $validLanguages)
  833. {
  834. // assumes language preference is sorted;
  835. // does not handle language-script-region tags or language range (*)
  836. if (!empty($validLanguages) && preg_match_all('/(?:^|,)([a-z]{2,3})([-][a-z]{2})?/', $browserLanguage, $matches, PREG_SET_ORDER)) {
  837. foreach ($matches as $parts) {
  838. if (count($parts) == 3) {
  839. // match locale (language and location)
  840. if (in_array($parts[1] . $parts[2], $validLanguages)) {
  841. return $parts[1] . $parts[2];
  842. }
  843. }
  844. // match language only (where no region provided)
  845. if (in_array($parts[1], $validLanguages)) {
  846. return $parts[1];
  847. }
  848. }
  849. }
  850. return 'xx';
  851. }
  852. /**
  853. * Returns the continent of a given country
  854. *
  855. * @param string $country 2 letters isocode
  856. *
  857. * @return string Continent (3 letters code : afr, asi, eur, amn, ams, oce)
  858. */
  859. public static function getContinent($country)
  860. {
  861. $countryList = self::getCountriesList();
  862. if (isset($countryList[$country])) {
  863. return $countryList[$country];
  864. }
  865. return 'unk';
  866. }
  867. /*
  868. * Campaign
  869. */
  870. /**
  871. * Returns the list of Campaign parameter names that will be read to classify
  872. * a visit as coming from a Campaign
  873. *
  874. * @return array array(
  875. * 0 => array( ... ) // campaign names parameters
  876. * 1 => array( ... ) // campaign keyword parameters
  877. * );
  878. */
  879. public static function getCampaignParameters()
  880. {
  881. $return = array(
  882. Config::getInstance()->Tracker['campaign_var_name'],
  883. Config::getInstance()->Tracker['campaign_keyword_var_name'],
  884. );
  885. foreach ($return as &$list) {
  886. if (strpos($list, ',') !== false) {
  887. $list = explode(',', $list);
  888. } else {
  889. $list = array($list);
  890. }
  891. $list = array_map('trim', $list);
  892. }
  893. return $return;
  894. }
  895. /*
  896. * Referrer
  897. */
  898. /**
  899. * Returns a string with a comma separated list of placeholders for use in an SQL query. Used mainly
  900. * to fill the `IN (...)` part of a query.
  901. *
  902. * @param array|string $fields The names of the mysql table fields to bind, e.g.
  903. * `array(fieldName1, fieldName2, fieldName3)`.
  904. *
  905. * _Note: The content of the array isn't important, just its length._
  906. * @return string The placeholder string, e.g. `"?, ?, ?"`.
  907. * @api
  908. */
  909. public static function getSqlStringFieldsArray($fields)
  910. {
  911. if (is_string($fields)) {
  912. $fields = array($fields);
  913. }
  914. $count = count($fields);
  915. if ($count == 0) {
  916. return "''";
  917. }
  918. return '?' . str_repeat(',?', $count - 1);
  919. }
  920. /**
  921. * Sets outgoing header.
  922. *
  923. * @param string $header The header.
  924. * @param bool $replace Whether to replace existing or not.
  925. */
  926. public static function sendHeader($header, $replace = true)
  927. {
  928. // don't send header in CLI mode
  929. if(Common::isPhpCliMode()) {
  930. return;
  931. }
  932. if (isset($GLOBALS['PIWIK_TRACKER_LOCAL_TRACKING']) && $GLOBALS['PIWIK_TRACKER_LOCAL_TRACKING']) {
  933. @header($header, $replace);
  934. } else {
  935. header($header, $replace);
  936. }
  937. }
  938. /**
  939. * Returns the ID of the current LocationProvider (see UserCountry plugin code) from
  940. * the Tracker cache.
  941. */
  942. public static function getCurrentLocationProviderId()
  943. {
  944. $cache = Cache::getCacheGeneral();
  945. return empty($cache['currentLocationProviderId'])
  946. ? DefaultProvider::ID
  947. : $cache['currentLocationProviderId'];
  948. }
  949. /**
  950. * Marks an orphaned object for garbage collection.
  951. *
  952. * For more information: {@link http://dev.piwik.org/trac/ticket/374}
  953. * @param $var The object to destroy.
  954. * @api
  955. */
  956. static public function destroy(&$var)
  957. {
  958. if (is_object($var) && method_exists($var, '__destruct')) {
  959. $var->__destruct();
  960. }
  961. unset($var);
  962. $var = null;
  963. }
  964. static public function printDebug($info = '')
  965. {
  966. if (isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG']) {
  967. if (is_object($info)) {
  968. $info = var_export($info, true);
  969. }
  970. Log::getInstance()->setLogLevel(Log::DEBUG);
  971. if (is_array($info) || is_object($info)) {
  972. $info = Common::sanitizeInputValues($info);
  973. $out = var_export($info, true);
  974. foreach (explode("\n", $out) as $line) {
  975. Log::debug($line);
  976. }
  977. } else {
  978. foreach (explode("\n", $info) as $line) {
  979. Log::debug(htmlspecialchars($line, ENT_QUOTES));
  980. }
  981. }
  982. }
  983. }
  984. }