/core/Common.php
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
- <?php
- /**
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
- */
- namespace Piwik;
- use Exception;
- use Piwik\Plugins\UserCountry\LocationProvider\DefaultProvider;
- use Piwik\Tracker;
- use Piwik\Tracker\Cache;
- /**
- * Contains helper methods used by both Piwik Core and the Piwik Tracking engine.
- *
- * This is the only non-Tracker class loaded by the **\/piwik.php** file.
- */
- class Common
- {
- // constants used to map the referrer type to an integer in the log_visit table
- const REFERRER_TYPE_DIRECT_ENTRY = 1;
- const REFERRER_TYPE_SEARCH_ENGINE = 2;
- const REFERRER_TYPE_WEBSITE = 3;
- const REFERRER_TYPE_CAMPAIGN = 6;
- // Flag used with htmlspecialchar. See php.net/htmlspecialchars.
- const HTML_ENCODING_QUOTE_STYLE = ENT_QUOTES;
- public static $isCliMode = null;
- /*
- * Database
- */
- /**
- * Hashes a string into an integer which should be very low collision risks
- * @param string $string String to hash
- * @return int Resulting int hash
- */
- public static function hashStringToInt($string)
- {
- $stringHash = substr(md5($string), 0, 8);
- return base_convert($stringHash, 16, 10);
- }
- /**
- * Returns a prefixed table name.
- *
- * The table prefix is determined by the `[database] tables_prefix` INI config
- * option.
- *
- * @param string $table The table name to prefix, ie "log_visit"
- * @return string The prefixed name, ie "piwik-production_log_visit".
- * @api
- */
- public static function prefixTable($table)
- {
- $prefix = Config::getInstance()->database['tables_prefix'];
- return $prefix . $table;
- }
- /**
- * Returns an array containing the prefixed table names of every passed argument.
- *
- * @param string ... The table names to prefix, ie "log_visit"
- * @return array The prefixed names in an array.
- */
- public static function prefixTables()
- {
- $result = array();
- foreach (func_get_args() as $table) {
- $result[] = self::prefixTable($table);
- }
- return $result;
- }
- /**
- * Removes the prefix from a table name and returns the result.
- *
- * The table prefix is determined by the `[database] tables_prefix` INI config
- * option.
- *
- * @param string $table The prefixed table name, eg "piwik-production_log_visit".
- * @return string The unprefixed table name, eg "log_visit".
- * @api
- */
- public static function unprefixTable($table)
- {
- static $prefixTable = null;
- if (is_null($prefixTable)) {
- $prefixTable = Config::getInstance()->database['tables_prefix'];
- }
- if (empty($prefixTable)
- || strpos($table, $prefixTable) !== 0
- ) {
- return $table;
- }
- $count = 1;
- return str_replace($prefixTable, '', $table, $count);
- }
- /*
- * Tracker
- */
- public static function isGoalPluginEnabled()
- {
- return \Piwik\Plugin\Manager::getInstance()->isPluginActivated('Goals');
- }
- public static function isActionsPluginEnabled()
- {
- return \Piwik\Plugin\Manager::getInstance()->isPluginActivated('Actions');
- }
- /**
- * Returns true if PHP was invoked from command-line interface (shell)
- *
- * @since added in 0.4.4
- * @return bool true if PHP invoked as a CGI or from CLI
- */
- public static function isPhpCliMode()
- {
- if (is_bool(self::$isCliMode)) {
- return self::$isCliMode;
- }
- $remoteAddr = @$_SERVER['REMOTE_ADDR'];
- return PHP_SAPI == 'cli' ||
- (self::isPhpCgiType() && empty($remoteAddr));
- }
- /**
- * Returns true if PHP is executed as CGI type.
- *
- * @since added in 0.4.4
- * @return bool true if PHP invoked as a CGI
- */
- public static function isPhpCgiType()
- {
- $sapiType = php_sapi_name();
- return substr($sapiType, 0, 3) === 'cgi';
- }
- /**
- * Returns true if the current request is a console command, eg. ./console xx:yy
- * @return bool
- */
- public static function isRunningConsoleCommand()
- {
- $searched = '/console';
- $consolePos = strpos($_SERVER['SCRIPT_NAME'], $searched);
- $expectedConsolePos = strlen($_SERVER['SCRIPT_NAME']) - strlen($searched);
- $isScriptIsConsole = $consolePos == $expectedConsolePos;
- return self::isPhpCliMode() && $isScriptIsConsole;
- }
- /*
- * String operations
- */
- /**
- * Multi-byte substr() - works with UTF-8.
- *
- * Calls `mb_substr` if available and falls back to `substr` if it's not.
- *
- * @param string $string
- * @param int $start
- * @param int ... optional length
- * @return string
- * @api
- */
- public static function mb_substr($string, $start)
- {
- $length = func_num_args() > 2
- ? func_get_arg(2)
- : self::mb_strlen($string);
- if (function_exists('mb_substr')) {
- return mb_substr($string, $start, $length, 'UTF-8');
- }
- return substr($string, $start, $length);
- }
- /**
- * Multi-byte strlen() - works with UTF-8
- *
- * Calls `mb_substr` if available and falls back to `substr` if not.
- *
- * @param string $string
- * @return int
- * @api
- */
- public static function mb_strlen($string)
- {
- if (function_exists('mb_strlen')) {
- return mb_strlen($string, 'UTF-8');
- }
- return strlen($string);
- }
- /**
- * Multi-byte strtolower() - works with UTF-8.
- *
- * Calls `mb_strtolower` if available and falls back to `strtolower` if not.
- *
- * @param string $string
- * @return string
- * @api
- */
- public static function mb_strtolower($string)
- {
- if (function_exists('mb_strtolower')) {
- return mb_strtolower($string, 'UTF-8');
- }
- return strtolower($string);
- }
- /*
- * Escaping input
- */
- /**
- * Sanitizes a string to help avoid XSS vulnerabilities.
- *
- * This function is automatically called when {@link getRequestVar()} is called,
- * so you should not normally have to use it.
- *
- * This function should be used when outputting data that isn't escaped and was
- * obtained from the user (for example when using the `|raw` twig filter on goal names).
- *
- * _NOTE: Sanitized input should not be used directly in an SQL query; SQL placeholders
- * should still be used._
- *
- * **Implementation Details**
- *
- * - [htmlspecialchars](http://php.net/manual/en/function.htmlspecialchars.php) is used to escape text.
- * - Single quotes are not escaped so **Piwik's amazing community** will still be
- * **Piwik's amazing community**.
- * - Use of the `magic_quotes` setting will not break this method.
- * - Boolean, numeric and null values are not modified.
- *
- * @param mixed $value The variable to be sanitized. If an array is supplied, the contents
- * of the array will be sanitized recursively. The keys of the array
- * will also be sanitized.
- * @param bool $alreadyStripslashed Implementation detail, ignore.
- * @throws Exception If `$value` is of an incorrect type.
- * @return mixed The sanitized value.
- * @api
- */
- public static function sanitizeInputValues($value, $alreadyStripslashed = false)
- {
- if (is_numeric($value)) {
- return $value;
- } elseif (is_string($value)) {
- $value = self::sanitizeInputValue($value);
- if (!$alreadyStripslashed) // a JSON array was already stripslashed, don't do it again for each value
- {
- $value = self::undoMagicQuotes($value);
- }
- } elseif (is_array($value)) {
- foreach (array_keys($value) as $key) {
- $newKey = $key;
- $newKey = self::sanitizeInputValues($newKey, $alreadyStripslashed);
- if ($key != $newKey) {
- $value[$newKey] = $value[$key];
- unset($value[$key]);
- }
- $value[$newKey] = self::sanitizeInputValues($value[$newKey], $alreadyStripslashed);
- }
- } elseif (!is_null($value)
- && !is_bool($value)
- ) {
- throw new Exception("The value to escape has not a supported type. Value = " . var_export($value, true));
- }
- return $value;
- }
- /**
- * Sanitize a single input value
- *
- * @param string $value
- * @return string sanitized input
- */
- public static function sanitizeInputValue($value)
- {
- // $_GET and $_REQUEST already urldecode()'d
- // decode
- // note: before php 5.2.7, htmlspecialchars() double encodes &#x hex items
- $value = html_entity_decode($value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8');
- // filter
- $value = self::sanitizeLineBreaks($value);
- // escape
- $tmp = @htmlspecialchars($value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8');
- // note: php 5.2.5 and above, htmlspecialchars is destructive if input is not UTF-8
- if ($value != '' && $tmp == '') {
- // convert and escape
- $value = utf8_encode($value);
- $tmp = htmlspecialchars($value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8');
- }
- return $tmp;
- }
- /**
- * Unsanitizes a single input value and returns the result.
- *
- * @param string $value
- * @return string unsanitized input
- */
- public static function unsanitizeInputValue($value)
- {
- return htmlspecialchars_decode($value, self::HTML_ENCODING_QUOTE_STYLE);
- }
- /**
- * Unsanitizes one or more values and returns the result.
- *
- * This method should be used when you need to unescape data that was obtained from
- * the user.
- *
- * Some data in Piwik is stored sanitized (such as site name). In this case you may
- * have to use this method to unsanitize it in order to, for example, output it in JSON.
- *
- * @param string|array $value The data to unsanitize. If an array is passed, the
- * array is sanitized recursively. Key values are not unsanitized.
- * @return string|array The unsanitized data.
- * @api
- */
- public static function unsanitizeInputValues($value)
- {
- if (is_array($value)) {
- $result = array();
- foreach ($value as $key => $arrayValue) {
- $result[$key] = self::unsanitizeInputValues($arrayValue);
- }
- return $result;
- } else {
- return self::unsanitizeInputValue($value);
- }
- }
- /**
- * Undo the damage caused by magic_quotes; deprecated in php 5.3 but not removed until php 5.4
- *
- * @param string
- * @return string modified or not
- */
- private static function undoMagicQuotes($value)
- {
- return version_compare(PHP_VERSION, '5.4', '<')
- && get_magic_quotes_gpc()
- ? stripslashes($value)
- : $value;
- }
- /**
- *
- * @param string
- * @return string Line breaks and line carriage removed
- */
- public static function sanitizeLineBreaks($value)
- {
- $value = str_replace(array("\n", "\r", "\0"), '', $value);
- return $value;
- }
- /**
- * Gets a sanitized request parameter by name from the `$_GET` and `$_POST` superglobals.
- *
- * Use this function to get request parameter values. **_NEVER use `$_GET` and `$_POST` directly._**
- *
- * If the variable cannot be found, and a default value was not provided, an exception is raised.
- *
- * _See {@link sanitizeInputValues()} to learn more about sanitization._
- *
- * @param string $varName Name of the request parameter to get. By default, we look in `$_GET[$varName]`
- * and `$_POST[$varName]` for the value.
- * @param string|null $varDefault The value to return if the request parameter cannot be found or has an empty value.
- * @param string|null $varType Expected type of the request variable. This parameters value must be one of the following:
- * `'array'`, `'int'`, `'integer'`, `'string'`, `'json'`.
- *
- * If `'json'`, the string value will be `json_decode`-d and then sanitized.
- * @param array|null $requestArrayToUse The array to use instead of `$_GET` and `$_POST`.
- * @throws Exception If the request parameter doesn't exist and there is no default value, or if the request parameter
- * exists but has an incorrect type.
- * @return mixed The sanitized request parameter.
- * @api
- */
- public static function getRequestVar($varName, $varDefault = null, $varType = null, $requestArrayToUse = null)
- {
- if (is_null($requestArrayToUse)) {
- $requestArrayToUse = $_GET + $_POST;
- }
- $varDefault = self::sanitizeInputValues($varDefault);
- if ($varType === 'int') {
- // settype accepts only integer
- // 'int' is simply a shortcut for 'integer'
- $varType = 'integer';
- }
- // there is no value $varName in the REQUEST so we try to use the default value
- if (empty($varName)
- || !isset($requestArrayToUse[$varName])
- || (!is_array($requestArrayToUse[$varName])
- && strlen($requestArrayToUse[$varName]) === 0
- )
- ) {
- if (is_null($varDefault)) {
- throw new Exception("The parameter '$varName' isn't set in the Request, and a default value wasn't provided.");
- } else {
- if (!is_null($varType)
- && in_array($varType, array('string', 'integer', 'array'))
- ) {
- settype($varDefault, $varType);
- }
- return $varDefault;
- }
- }
- // Normal case, there is a value available in REQUEST for the requested varName:
- // we deal w/ json differently
- if ($varType == 'json') {
- $value = self::undoMagicQuotes($requestArrayToUse[$varName]);
- $value = self::json_decode($value, $assoc = true);
- return self::sanitizeInputValues($value, $alreadyStripslashed = true);
- }
- $value = self::sanitizeInputValues($requestArrayToUse[$varName]);
- if (!is_null($varType)) {
- $ok = false;
- if ($varType === 'string') {
- if (is_string($value)) $ok = true;
- } elseif ($varType === 'integer') {
- if ($value == (string)(int)$value) $ok = true;
- } elseif ($varType === 'float') {
- if ($value == (string)(float)$value) $ok = true;
- } elseif ($varType === 'array') {
- if (is_array($value)) $ok = true;
- } else {
- throw new Exception("\$varType specified is not known. It should be one of the following: array, int, integer, float, string");
- }
- // The type is not correct
- if ($ok === false) {
- if ($varDefault === null) {
- throw new Exception("The parameter '$varName' doesn't have a correct type, and a default value wasn't provided.");
- } // we return the default value with the good type set
- else {
- settype($varDefault, $varType);
- return $varDefault;
- }
- }
- settype($value, $varType);
- }
- return $value;
- }
- /*
- * Generating unique strings
- */
- /**
- * Returns a 32 characters long uniq ID
- *
- * @return string 32 chars
- */
- public static function generateUniqId()
- {
- return md5(uniqid(rand(), true));
- }
- /**
- * Configureable hash() algorithm (defaults to md5)
- *
- * @param string $str String to be hashed
- * @param bool $raw_output
- * @return string Hash string
- */
- public static function hash($str, $raw_output = false)
- {
- static $hashAlgorithm = null;
- if (is_null($hashAlgorithm)) {
- $hashAlgorithm = @Config::getInstance()->General['hash_algorithm'];
- }
- if ($hashAlgorithm) {
- $hash = @hash($hashAlgorithm, $str, $raw_output);
- if ($hash !== false)
- return $hash;
- }
- return md5($str, $raw_output);
- }
- /**
- * Generate random string.
- * Do not use for security related purposes (the string is not truly random).
- *
- * @param int $length string length
- * @param string $alphabet characters allowed in random string
- * @return string random string with given length
- */
- public static function getRandomString($length = 16, $alphabet = "abcdefghijklmnoprstuvwxyz0123456789")
- {
- $chars = $alphabet;
- $str = '';
- list($usec, $sec) = explode(" ", microtime());
- $seed = ((float)$sec + (float)$usec) * 100000;
- mt_srand($seed);
- for ($i = 0; $i < $length; $i++) {
- $rand_key = mt_rand(0, strlen($chars) - 1);
- $str .= substr($chars, $rand_key, 1);
- }
- return str_shuffle($str);
- }
- /*
- * Conversions
- */
- /**
- * Convert hexadecimal representation into binary data.
- * !! Will emit warning if input string is not hex!!
- *
- * @see http://php.net/bin2hex
- *
- * @param string $str Hexadecimal representation
- * @return string
- */
- public static function hex2bin($str)
- {
- return pack("H*", $str);
- }
- /**
- * This function will convert the input string to the binary representation of the ID
- * but it will throw an Exception if the specified input ID is not correct
- *
- * This is used when building segments containing visitorId which could be an invalid string
- * therefore throwing Unexpected PHP error [pack(): Type H: illegal hex digit i] severity [E_WARNING]
- *
- * It would be simply to silent fail the pack() call above but in all other cases, we don't expect an error,
- * so better be safe and get the php error when something unexpected is happening
- * @param string $id
- * @throws Exception
- * @return string binary string
- */
- public static function convertVisitorIdToBin($id)
- {
- if (strlen($id) !== Tracker::LENGTH_HEX_ID_STRING
- || @bin2hex(self::hex2bin($id)) != $id
- ) {
- throw new Exception("visitorId is expected to be a " . Tracker::LENGTH_HEX_ID_STRING . " hex char string");
- }
- return self::hex2bin($id);
- }
- /**
- * Convert IP address (in network address format) to presentation format.
- * This is a backward compatibility function for code that only expects
- * IPv4 addresses (i.e., doesn't support IPv6).
- *
- * @see IP::N2P()
- *
- * This function does not support the long (or its string representation)
- * returned by the built-in ip2long() function, from Piwik 1.3 and earlier.
- *
- * @deprecated 1.4
- *
- * @param string $ip IP address in network address format
- * @return string
- */
- public static function long2ip($ip)
- {
- return IP::long2ip($ip);
- }
- /**
- * JSON encode wrapper
- * - missing or broken in some php 5.x versions
- *
- * @param mixed $value
- * @return string
- * @deprecated
- */
- public static function json_encode($value)
- {
- return @json_encode($value);
- }
- /**
- * JSON decode wrapper
- * - missing or broken in some php 5.x versions
- *
- * @param string $json
- * @param bool $assoc
- * @return mixed
- * @deprecated
- */
- public static function json_decode($json, $assoc = false)
- {
- return json_decode($json, $assoc);
- }
- /**
- * Detects whether an error occurred during the last json encode/decode.
- * @return bool
- */
- public static function hasJsonErrorOccurred()
- {
- return json_last_error() != JSON_ERROR_NONE;
- }
- /**
- * Returns a human readable error message in case an error occcurred during the last json encode/decode.
- * Returns an empty string in case there was no error.
- *
- * @return string
- */
- public static function getLastJsonError()
- {
- switch (json_last_error()) {
- case JSON_ERROR_NONE:
- return '';
- case JSON_ERROR_DEPTH:
- return 'Maximum stack depth exceeded';
- case JSON_ERROR_STATE_MISMATCH:
- return 'Underflow or the modes mismatch';
- case JSON_ERROR_CTRL_CHAR:
- return 'Unexpected control character found';
- case JSON_ERROR_SYNTAX:
- return 'Syntax error, malformed JSON';
- case JSON_ERROR_UTF8:
- return 'Malformed UTF-8 characters, possibly incorrectly encoded';
- }
- return 'Unknown error';
- }
- public static function stringEndsWith($haystack, $needle)
- {
- if ('' === $needle) {
- return true;
- }
- $lastCharacters = substr($haystack, -strlen($needle));
- return $lastCharacters === $needle;
- }
- /**
- * Returns the list of parent classes for the given class.
- *
- * @param string $klass A class name.
- * @return string[] The list of parent classes in order from highest ancestor to the descended class.
- */
- public static function getClassLineage($klass)
- {
- $klasses = array_merge(array($klass), array_values(class_parents($klass, $autoload = false)));
- return array_reverse($klasses);
- }
- /*
- * DataFiles
- */
- /**
- * Returns list of continent codes
- *
- * @see core/DataFiles/Countries.php
- *
- * @return array Array of 3 letter continent codes
- */
- public static function getContinentsList()
- {
- require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php';
- $continentsList = $GLOBALS['Piwik_ContinentList'];
- return $continentsList;
- }
- /**
- * Returns list of valid country codes
- *
- * @see core/DataFiles/Countries.php
- *
- * @param bool $includeInternalCodes
- * @return array Array of (2 letter ISO codes => 3 letter continent code)
- */
- public static function getCountriesList($includeInternalCodes = false)
- {
- require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php';
- $countriesList = $GLOBALS['Piwik_CountryList'];
- $extras = $GLOBALS['Piwik_CountryList_Extras'];
- if ($includeInternalCodes) {
- return array_merge($countriesList, $extras);
- }
- return $countriesList;
- }
- /**
- * Returns the list of valid language codes.
- *
- * See [core/DataFiles/Languages.php](https://github.com/piwik/piwik/blob/master/core/DataFiles/Languages.php).
- *
- * @return array Array of two letter ISO codes mapped with their associated language names (in English). E.g.
- * `array('en' => 'English', 'ja' => 'Japanese')`.
- * @api
- */
- public static function getLanguagesList()
- {
- require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Languages.php';
- $languagesList = $GLOBALS['Piwik_LanguageList'];
- return $languagesList;
- }
- /**
- * Returns a list of language to country mappings.
- *
- * See [core/DataFiles/LanguageToCountry.php](https://github.com/piwik/piwik/blob/master/core/DataFiles/LanguageToCountry.php).
- *
- * @return array Array of two letter ISO language codes mapped with two letter ISO country codes:
- * `array('fr' => 'fr') // French => France`
- * @api
- */
- public static function getLanguageToCountryList()
- {
- require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/LanguageToCountry.php';
- $languagesList = $GLOBALS['Piwik_LanguageToCountry'];
- return $languagesList;
- }
- /**
- * Returns list of search engines by URL
- *
- * @see core/DataFiles/SearchEngines.php
- *
- * @return array Array of ( URL => array( searchEngineName, keywordParameter, path, charset ) )
- */
- public static function getSearchEngineUrls()
- {
- require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/SearchEngines.php';
- $searchEngines = $GLOBALS['Piwik_SearchEngines'];
- Piwik::postEvent('Referrer.addSearchEngineUrls', array(&$searchEngines));
- return $searchEngines;
- }
- /**
- * Returns list of search engines by name
- *
- * @see core/DataFiles/SearchEngines.php
- *
- * @return array Array of ( searchEngineName => URL )
- */
- public static function getSearchEngineNames()
- {
- $searchEngines = self::getSearchEngineUrls();
- $nameToUrl = array();
- foreach ($searchEngines as $url => $info) {
- if (!isset($nameToUrl[$info[0]])) {
- $nameToUrl[$info[0]] = $url;
- }
- }
- return $nameToUrl;
- }
- /**
- * Returns list of social networks by URL
- *
- * @see core/DataFiles/Socials.php
- *
- * @return array Array of ( URL => Social Network Name )
- */
- public static function getSocialUrls()
- {
- require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Socials.php';
- $socialUrls = $GLOBALS['Piwik_socialUrl'];
- Piwik::postEvent('Referrer.addSocialUrls', array(&$socialUrls));
- return $socialUrls;
- }
- /**
- * Returns list of provider names
- *
- * @see core/DataFiles/Providers.php
- *
- * @return array Array of ( dnsName => providerName )
- */
- public static function getProviderNames()
- {
- require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Providers.php';
- $providers = $GLOBALS['Piwik_ProviderNames'];
- return $providers;
- }
- /*
- * Language, country, continent
- */
- /**
- * Returns the browser language code, eg. "en-gb,en;q=0.5"
- *
- * @param string|null $browserLang Optional browser language, otherwise taken from the request header
- * @return string
- */
- public static function getBrowserLanguage($browserLang = null)
- {
- static $replacementPatterns = array(
- // extraneous bits of RFC 3282 that we ignore
- '/(\\\\.)/', // quoted-pairs
- '/(\s+)/', // CFWcS white space
- '/(\([^)]*\))/', // CFWS comments
- '/(;q=[0-9.]+)/', // quality
- // found in the LANG environment variable
- '/\.(.*)/', // charset (e.g., en_CA.UTF-8)
- '/^C$/', // POSIX 'C' locale
- );
- if (is_null($browserLang)) {
- $browserLang = self::sanitizeInputValues(@$_SERVER['HTTP_ACCEPT_LANGUAGE']);
- if (empty($browserLang) && self::isPhpCliMode()) {
- $browserLang = @getenv('LANG');
- }
- }
- if (is_null($browserLang)) {
- // a fallback might be to infer the language in HTTP_USER_AGENT (i.e., localized build)
- $browserLang = "";
- } else {
- // language tags are case-insensitive per HTTP/1.1 s3.10 but the region may be capitalized per ISO3166-1;
- // underscores are not permitted per RFC 4646 or 4647 (which obsolete RFC 1766 and 3066),
- // but we guard against a bad user agent which naively uses its locale
- $browserLang = strtolower(str_replace('_', '-', $browserLang));
- // filters
- $browserLang = preg_replace($replacementPatterns, '', $browserLang);
- $browserLang = preg_replace('/((^|,)chrome:.*)/', '', $browserLang, 1); // Firefox bug
- $browserLang = preg_replace('/(,)(?:en-securid,)|(?:(^|,)en-securid(,|$))/', '$1', $browserLang, 1); // unregistered language tag
- $browserLang = str_replace('sr-sp', 'sr-rs', $browserLang); // unofficial (proposed) code in the wild
- }
- return $browserLang;
- }
- /**
- * Returns the visitor country based on the Browser 'accepted language'
- * information, but provides a hook for geolocation via IP address.
- *
- * @param string $lang browser lang
- * @param bool $enableLanguageToCountryGuess If set to true, some assumption will be made and detection guessed more often, but accuracy could be affected
- * @param string $ip
- * @return string 2 letter ISO code
- */
- public static function getCountry($lang, $enableLanguageToCountryGuess, $ip)
- {
- if (empty($lang) || strlen($lang) < 2 || $lang == 'xx') {
- return 'xx';
- }
- $validCountries = self::getCountriesList();
- return self::extractCountryCodeFromBrowserLanguage($lang, $validCountries, $enableLanguageToCountryGuess);
- }
- /**
- * Returns list of valid country codes
- *
- * @param string $browserLanguage
- * @param array $validCountries Array of valid countries
- * @param bool $enableLanguageToCountryGuess (if true, will guess country based on language that lacks region information)
- * @return array Array of 2 letter ISO codes
- */
- public static function extractCountryCodeFromBrowserLanguage($browserLanguage, $validCountries, $enableLanguageToCountryGuess)
- {
- $langToCountry = self::getLanguageToCountryList();
- if ($enableLanguageToCountryGuess) {
- if (preg_match('/^([a-z]{2,3})(?:,|;|$)/', $browserLanguage, $matches)) {
- // match language (without region) to infer the country of origin
- if (array_key_exists($matches[1], $langToCountry)) {
- return $langToCountry[$matches[1]];
- }
- }
- }
- if (!empty($validCountries) && preg_match_all('/[-]([a-z]{2})/', $browserLanguage, $matches, PREG_SET_ORDER)) {
- foreach ($matches as $parts) {
- // match location; we don't make any inferences from the language
- if (array_key_exists($parts[1], $validCountries)) {
- return $parts[1];
- }
- }
- }
- return 'xx';
- }
- /**
- * Returns the visitor language based only on the Browser 'accepted language' information
- *
- * @param string $browserLanguage Browser's accepted langauge header
- * @param array $validLanguages array of valid language codes
- * @return string 2 letter ISO 639 code
- */
- public static function extractLanguageCodeFromBrowserLanguage($browserLanguage, $validLanguages)
- {
- // assumes language preference is sorted;
- // does not handle language-script-region tags or language range (*)
- if (!empty($validLanguages) && preg_match_all('/(?:^|,)([a-z]{2,3})([-][a-z]{2})?/', $browserLanguage, $matches, PREG_SET_ORDER)) {
- foreach ($matches as $parts) {
- if (count($parts) == 3) {
- // match locale (language and location)
- if (in_array($parts[1] . $parts[2], $validLanguages)) {
- return $parts[1] . $parts[2];
- }
- }
- // match language only (where no region provided)
- if (in_array($parts[1], $validLanguages)) {
- return $parts[1];
- }
- }
- }
- return 'xx';
- }
- /**
- * Returns the continent of a given country
- *
- * @param string $country 2 letters isocode
- *
- * @return string Continent (3 letters code : afr, asi, eur, amn, ams, oce)
- */
- public static function getContinent($country)
- {
- $countryList = self::getCountriesList();
- if (isset($countryList[$country])) {
- return $countryList[$country];
- }
- return 'unk';
- }
- /*
- * Campaign
- */
- /**
- * Returns the list of Campaign parameter names that will be read to classify
- * a visit as coming from a Campaign
- *
- * @return array array(
- * 0 => array( ... ) // campaign names parameters
- * 1 => array( ... ) // campaign keyword parameters
- * );
- */
- public static function getCampaignParameters()
- {
- $return = array(
- Config::getInstance()->Tracker['campaign_var_name'],
- Config::getInstance()->Tracker['campaign_keyword_var_name'],
- );
- foreach ($return as &$list) {
- if (strpos($list, ',') !== false) {
- $list = explode(',', $list);
- } else {
- $list = array($list);
- }
- $list = array_map('trim', $list);
- }
- return $return;
- }
- /*
- * Referrer
- */
- /**
- * Returns a string with a comma separated list of placeholders for use in an SQL query. Used mainly
- * to fill the `IN (...)` part of a query.
- *
- * @param array|string $fields The names of the mysql table fields to bind, e.g.
- * `array(fieldName1, fieldName2, fieldName3)`.
- *
- * _Note: The content of the array isn't important, just its length._
- * @return string The placeholder string, e.g. `"?, ?, ?"`.
- * @api
- */
- public static function getSqlStringFieldsArray($fields)
- {
- if (is_string($fields)) {
- $fields = array($fields);
- }
- $count = count($fields);
- if ($count == 0) {
- return "''";
- }
- return '?' . str_repeat(',?', $count - 1);
- }
- /**
- * Sets outgoing header.
- *
- * @param string $header The header.
- * @param bool $replace Whether to replace existing or not.
- */
- public static function sendHeader($header, $replace = true)
- {
- // don't send header in CLI mode
- if(Common::isPhpCliMode()) {
- return;
- }
- if (isset($GLOBALS['PIWIK_TRACKER_LOCAL_TRACKING']) && $GLOBALS['PIWIK_TRACKER_LOCAL_TRACKING']) {
- @header($header, $replace);
- } else {
- header($header, $replace);
- }
- }
- /**
- * Returns the ID of the current LocationProvider (see UserCountry plugin code) from
- * the Tracker cache.
- */
- public static function getCurrentLocationProviderId()
- {
- $cache = Cache::getCacheGeneral();
- return empty($cache['currentLocationProviderId'])
- ? DefaultProvider::ID
- : $cache['currentLocationProviderId'];
- }
- /**
- * Marks an orphaned object for garbage collection.
- *
- * For more information: {@link http://dev.piwik.org/trac/ticket/374}
- * @param $var The object to destroy.
- * @api
- */
- static public function destroy(&$var)
- {
- if (is_object($var) && method_exists($var, '__destruct')) {
- $var->__destruct();
- }
- unset($var);
- $var = null;
- }
- static public function printDebug($info = '')
- {
- if (isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG']) {
- if (is_object($info)) {
- $info = var_export($info, true);
- }
- Log::getInstance()->setLogLevel(Log::DEBUG);
- if (is_array($info) || is_object($info)) {
- $info = Common::sanitizeInputValues($info);
- $out = var_export($info, true);
- foreach (explode("\n", $out) as $line) {
- Log::debug($line);
- }
- } else {
- foreach (explode("\n", $info) as $line) {
- Log::debug(htmlspecialchars($line, ENT_QUOTES));
- }
- }
- }
- }
- }